MediaWiki  REL1_21
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/content/',
00068                         $IP . '/includes/context/',
00069                         $IP . '/includes/db/',
00070                         $IP . '/includes/diff/',
00071                         $IP . '/includes/filerepo/',
00072                         $IP . '/includes/filerepo/file/',
00073                         $IP . '/includes/installer/',
00074                         $IP . '/includes/interwiki/',
00075                         $IP . '/includes/logging/',
00076                         $IP . '/includes/media/',
00077                         $IP . '/includes/parser/',
00078                         $IP . '/includes/resourceloader/',
00079                         $IP . '/includes/revisiondelete/',
00080                         $IP . '/includes/search/',
00081                         $IP . '/includes/specials/',
00082                         $IP . '/includes/upload/',
00083                         $IP . '/languages/',
00084                         $IP . '/maintenance/',
00085                         $IP . '/tests/',
00086                         $IP . '/tests/parser/',
00087                         $IP . '/tests/phpunit/suites/',
00088                         $IP . '/skins/',
00089                 );
00090 
00091                 foreach ( $pathinc as $dir ) {
00092                         $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
00093                         $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
00094                 }
00095 
00096                 $potential = array_unique( $potential );
00097                 $bad = array_unique( $bad );
00098                 $todo = array_diff( $potential, $documented );
00099                 $deprecated = array_diff( $documented, $potential );
00100 
00101                 // let's show the results:
00102                 $this->printArray( 'Undocumented', $todo );
00103                 $this->printArray( 'Documented and not found', $deprecated );
00104                 $this->printArray( 'Unclear hook calls', $bad );
00105 
00106                 if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 )
00107                 {
00108                         $this->output( "Looks good!\n" );
00109                 }
00110         }
00111 
00116         private function getHooksFromDoc( $doc ) {
00117                 if ( $this->hasOption( 'online' ) ) {
00118                         return $this->getHooksFromOnlineDoc();
00119                 } else {
00120                         return $this->getHooksFromLocalDoc( $doc );
00121                 }
00122         }
00123 
00129         private function getHooksFromLocalDoc( $doc ) {
00130                         $m = array();
00131                         $content = file_get_contents( $doc );
00132                         preg_match_all( "/\n'(.*?)'/", $content, $m );
00133                         return array_unique( $m[1] );
00134         }
00135 
00140         private function getHooksFromOnlineDoc() {
00141                         // All hooks
00142                         $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' );
00143                         $allhookdata = unserialize( $allhookdata );
00144                         $allhooks = array();
00145                         foreach ( $allhookdata['query']['categorymembers'] as $page ) {
00146                                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00147                                 if ( $found ) {
00148                                         $hook = str_replace( ' ', '_', $matches[1] );
00149                                         $allhooks[] = $hook;
00150                                 }
00151                         }
00152                         // Removed hooks
00153                         $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' );
00154                         $oldhookdata = unserialize( $oldhookdata );
00155                         $removed = array();
00156                         foreach ( $oldhookdata['query']['categorymembers'] as $page ) {
00157                                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00158                                 if ( $found ) {
00159                                         $hook = str_replace( ' ', '_', $matches[1] );
00160                                         $removed[] = $hook;
00161                                 }
00162                         }
00163                         return array_diff( $allhooks, $removed );
00164         }
00165 
00171         private function getHooksFromFile( $file ) {
00172                 $content = file_get_contents( $file );
00173                 $m = array();
00174                 preg_match_all( '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', $content, $m );
00175                 return $m[2];
00176         }
00177 
00183         private function getHooksFromPath( $path ) {
00184                 $hooks = array();
00185                 $dh = opendir( $path );
00186                 if ( $dh ) {
00187                         while ( ( $file = readdir( $dh ) ) !== false ) {
00188                                 if ( filetype( $path . $file ) == 'file' ) {
00189                                         $hooks = array_merge( $hooks, $this->getHooksFromFile( $path . $file ) );
00190                                 }
00191                         }
00192                         closedir( $dh );
00193                 }
00194                 return $hooks;
00195         }
00196 
00202         private function getBadHooksFromFile( $file ) {
00203                 $content = file_get_contents( $file );
00204                 $m = array();
00205                 # We want to skip the "function wfRunHooks()" one.  :)
00206                 preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m );
00207                 $list = array();
00208                 foreach ( $m[0] as $match ) {
00209                         $list[] = $match . "(" . $file . ")";
00210                 }
00211                 return $list;
00212         }
00213 
00219         private function getBadHooksFromPath( $path ) {
00220                 $hooks = array();
00221                 $dh = opendir( $path );
00222                 if ( $dh ) {
00223                         while ( ( $file = readdir( $dh ) ) !== false ) {
00224                                 # We don't want to read this file as it contains bad calls to wfRunHooks()
00225                                 if ( filetype( $path . $file ) == 'file' && !$path . $file == __FILE__ ) {
00226                                         $hooks = array_merge( $hooks, $this->getBadHooksFromFile( $path . $file ) );
00227                                 }
00228                         }
00229                         closedir( $dh );
00230                 }
00231                 return $hooks;
00232         }
00233 
00240         private function printArray( $msg, $arr, $sort = true ) {
00241                 if ( $sort ) {
00242                         asort( $arr );
00243                 }
00244                 foreach ( $arr as $v ) {
00245                         $this->output( "$msg: $v\n" );
00246                 }
00247         }
00248 }
00249 
00250 $maintClass = 'FindHooks';
00251 require_once( RUN_MAINTENANCE_IF_MAIN );