MediaWiki  REL1_20
findHooks.php
Go to the documentation of this file.
00001 <?php
00037 require_once( __DIR__ . '/Maintenance.php' );
00038 
00044 class FindHooks extends Maintenance {
00045         public function __construct() {
00046                 parent::__construct();
00047                 $this->mDescription = 'Find hooks that are undocumented, missing, or just plain wrong';
00048                 $this->addOption( 'online', 'Check against MediaWiki.org hook documentation' );
00049         }
00050 
00051         public function getDbType() {
00052                 return Maintenance::DB_NONE;
00053         }
00054 
00055         public function execute() {
00056                 global $IP;
00057 
00058                 $documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' );
00059                 $potential = array();
00060                 $bad = array();
00061                 $pathinc = array(
00062                         $IP . '/',
00063                         $IP . '/includes/',
00064                         $IP . '/includes/actions/',
00065                         $IP . '/includes/api/',
00066                         $IP . '/includes/cache/',
00067                         $IP . '/includes/context/',
00068                         $IP . '/includes/db/',
00069                         $IP . '/includes/diff/',
00070                         $IP . '/includes/filerepo/',
00071                         $IP . '/includes/filerepo/file/',
00072                         $IP . '/includes/installer/',
00073                         $IP . '/includes/interwiki/',
00074                         $IP . '/includes/logging/',
00075                         $IP . '/includes/media/',
00076                         $IP . '/includes/parser/',
00077                         $IP . '/includes/resourceloader/',
00078                         $IP . '/includes/revisiondelete/',
00079                         $IP . '/includes/search/',
00080                         $IP . '/includes/specials/',
00081                         $IP . '/includes/upload/',
00082                         $IP . '/languages/',
00083                         $IP . '/maintenance/',
00084                         $IP . '/tests/',
00085                         $IP . '/tests/parser/',
00086                         $IP . '/tests/phpunit/suites/',
00087                         $IP . '/skins/',
00088                 );
00089 
00090                 foreach ( $pathinc as $dir ) {
00091                         $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
00092                         $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
00093                 }
00094 
00095                 $potential = array_unique( $potential );
00096                 $bad = array_unique( $bad );
00097                 $todo = array_diff( $potential, $documented );
00098                 $deprecated = array_diff( $documented, $potential );
00099 
00100                 // let's show the results:
00101                 $this->printArray( 'Undocumented', $todo );
00102                 $this->printArray( 'Documented and not found', $deprecated );
00103                 $this->printArray( 'Unclear hook calls', $bad );
00104 
00105                 if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 )
00106                 {
00107                         $this->output( "Looks good!\n" );
00108                 }
00109         }
00110 
00115         private function getHooksFromDoc( $doc ) {
00116                 if ( $this->hasOption( 'online' ) ) {
00117                         return $this->getHooksFromOnlineDoc( );
00118                 } else {
00119                         return $this->getHooksFromLocalDoc( $doc );
00120                 }
00121         }
00122 
00128         private function getHooksFromLocalDoc( $doc ) {
00129                         $m = array();
00130                         $content = file_get_contents( $doc );
00131                         preg_match_all( "/\n'(.*?)'/", $content, $m );
00132                         return array_unique( $m[1] );
00133         }
00134 
00139         private function getHooksFromOnlineDoc( ) {
00140                         // All hooks
00141                         $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' );
00142                         $allhookdata = unserialize( $allhookdata );
00143                         $allhooks = array();
00144                         foreach ( $allhookdata['query']['categorymembers'] as $page ) {
00145                                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00146                                 if ( $found ) {
00147                                         $hook = str_replace( ' ', '_', $matches[1] );
00148                                         $allhooks[] = $hook;
00149                                 }
00150                         }
00151                         // Removed hooks
00152                         $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' );
00153                         $oldhookdata = unserialize( $oldhookdata );
00154                         $removed = array();
00155                         foreach ( $oldhookdata['query']['categorymembers'] as $page ) {
00156                                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00157                                 if ( $found ) {
00158                                         $hook = str_replace( ' ', '_', $matches[1] );
00159                                         $removed[] = $hook;
00160                                 }
00161                         }
00162                         return array_diff( $allhooks, $removed );
00163         }
00164 
00170         private function getHooksFromFile( $file ) {
00171                 $content = file_get_contents( $file );
00172                 $m = array();
00173                 preg_match_all( '/(?:wfRunHooks|Hooks\:\:run)\(\s*([\'"])(.*?)\1/', $content, $m );
00174                 return $m[2];
00175         }
00176 
00182         private function getHooksFromPath( $path ) {
00183                 $hooks = array();
00184                 $dh = opendir( $path );
00185                 if ( $dh ) {
00186                         while ( ( $file = readdir( $dh ) ) !== false ) {
00187                                 if ( filetype( $path . $file ) == 'file' ) {
00188                                         $hooks = array_merge( $hooks, $this->getHooksFromFile( $path . $file ) );
00189                                 }
00190                         }
00191                         closedir( $dh );
00192                 }
00193                 return $hooks;
00194         }
00195 
00201         private function getBadHooksFromFile( $file ) {
00202                 $content = file_get_contents( $file );
00203                 $m = array();
00204                 # We want to skip the "function wfRunHooks()" one.  :)
00205                 preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m );
00206                 $list = array();
00207                 foreach ( $m[0] as $match ) {
00208                         $list[] = $match . "(" . $file . ")";
00209                 }
00210                 return $list;
00211         }
00212 
00218         private function getBadHooksFromPath( $path ) {
00219                 $hooks = array();
00220                 $dh = opendir( $path );
00221                 if ( $dh ) {
00222                         while ( ( $file = readdir( $dh ) ) !== false ) {
00223                                 # We don't want to read this file as it contains bad calls to wfRunHooks()
00224                                 if ( filetype( $path . $file ) == 'file' && !$path . $file == __FILE__ ) {
00225                                         $hooks = array_merge( $hooks, $this->getBadHooksFromFile( $path . $file ) );
00226                                 }
00227                         }
00228                         closedir( $dh );
00229                 }
00230                 return $hooks;
00231         }
00232 
00239         private function printArray( $msg, $arr, $sort = true ) {
00240                 if ( $sort ) {
00241                         asort( $arr );
00242                 }
00243                 foreach ( $arr as $v ) {
00244                         $this->output( "$msg: $v\n" );
00245                 }
00246         }
00247 }
00248 
00249 $maintClass = 'FindHooks';
00250 require_once( RUN_MAINTENANCE_IF_MAIN );