MediaWiki  REL1_22
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;