00001 <?php
00037 require_once( dirname(__FILE__) . '/Maintenance.php' );
00038
00039 class FindHooks extends Maintenance {
00040 public function __construct() {
00041 parent::__construct();
00042 $this->mDescription = "Find hooks that are undocumented, missing, or just plain wrong";
00043 $this->addOption( 'online', 'Check against mediawiki.org hook documentation' );
00044 }
00045
00046 public function getDbType() {
00047 return Maintenance::DB_NONE;
00048 }
00049
00050 public function execute() {
00051 global $IP;
00052
00053 $documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' );
00054 $potential = array();
00055 $bad = array();
00056 $pathinc = array(
00057 $IP.'/',
00058 $IP.'/includes/',
00059 $IP.'/includes/api/',
00060 $IP.'/includes/db/',
00061 $IP.'/includes/diff/',
00062 $IP.'/includes/filerepo/',
00063 $IP.'/includes/parser/',
00064 $IP.'/includes/search/',
00065 $IP.'/includes/specials/',
00066 $IP.'/includes/upload/',
00067 $IP.'/languages/',
00068 $IP.'/maintenance/',
00069 $IP.'/skins/',
00070 );
00071
00072 foreach( $pathinc as $dir ) {
00073 $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
00074 $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
00075 }
00076
00077 $potential = array_unique( $potential );
00078 $bad = array_unique( $bad );
00079 $todo = array_diff( $potential, $documented );
00080 $deprecated = array_diff( $documented, $potential );
00081
00082
00083 $this->printArray('Undocumented', $todo );
00084 $this->printArray('Documented and not found', $deprecated );
00085 $this->printArray('Unclear hook calls', $bad );
00086
00087 if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 )
00088 $this->output( "Looks good!\n" );
00089 }
00090
00095 private function getHooksFromDoc( $doc ) {
00096 if( $this->hasOption( 'online' ) ){
00097
00098 $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' );
00099 $allhookdata = unserialize( $allhookdata );
00100 $allhooks = array();
00101 foreach( $allhookdata['query']['categorymembers'] as $page ) {
00102 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00103 if( $found ) {
00104 $hook = str_replace( ' ', '_', $matches[1] );
00105 $allhooks[] = $hook;
00106 }
00107 }
00108
00109 $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' );
00110 $oldhookdata = unserialize( $oldhookdata );
00111 $removed = array();
00112 foreach( $oldhookdata['query']['categorymembers'] as $page ) {
00113 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00114 if( $found ) {
00115 $hook = str_replace( ' ', '_', $matches[1] );
00116 $removed[] = $hook;
00117 }
00118 }
00119 return array_diff( $allhooks, $removed );
00120 } else {
00121 $m = array();
00122 $content = file_get_contents( $doc );
00123 preg_match_all( "/\n'(.*?)'/", $content, $m );
00124 return array_unique( $m[1] );
00125 }
00126 }
00127
00133 private function getHooksFromFile( $file ) {
00134 $content = file_get_contents( $file );
00135 $m = array();
00136 preg_match_all( '/wfRunHooks\(\s*([\'"])(.*?)\1/', $content, $m);
00137 return $m[2];
00138 }
00139
00145 private function getHooksFromPath( $path ) {
00146 $hooks = array();
00147 if( $dh = opendir($path) ) {
00148 while(($file = readdir($dh)) !== false) {
00149 if( filetype($path.$file) == 'file' ) {
00150 $hooks = array_merge( $hooks, $this->getHooksFromFile($path.$file) );
00151 }
00152 }
00153 closedir($dh);
00154 }
00155 return $hooks;
00156 }
00157
00163 private function getBadHooksFromFile( $file ) {
00164 $content = file_get_contents( $file );
00165 $m = array();
00166 # We want to skip the "function wfRunHooks()" one. :)
00167 preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m);
00168 $list = array();
00169 foreach( $m[0] as $match ){
00170 $list[] = $match . "(" . $file . ")";
00171 }
00172 return $list;
00173 }
00174
00180 private function getBadHooksFromPath( $path ) {
00181 $hooks = array();
00182 if( $dh = opendir($path) ) {
00183 while(($file = readdir($dh)) !== false) {
00184 # We don't want to read this file as it contains bad calls to wfRunHooks()
00185 if( filetype( $path.$file ) == 'file' && !$path.$file == __FILE__ ) {
00186 $hooks = array_merge( $hooks, $this->getBadHooksFromFile($path.$file) );
00187 }
00188 }
00189 closedir($dh);
00190 }
00191 return $hooks;
00192 }
00193
00200 private function printArray( $msg, $arr, $sort = true ) {
00201 if($sort) asort($arr);
00202 foreach($arr as $v) $this->output( "$msg: $v\n" );
00203 }
00204 }
00205
00206 $maintClass = "FindHooks";
00207 require_once( DO_MAINTENANCE );