MediaWiki  REL1_23
findHooks.php
Go to the documentation of this file.
00001 <?php
00037 require_once __DIR__ . '/Maintenance.php';
00038 
00044 class FindHooks extends Maintenance {
00045     /*
00046      * Hooks that are ignored
00047      */
00048     protected static $ignore = array( 'testRunLegacyHooks' );
00049 
00050     public function __construct() {
00051         parent::__construct();
00052         $this->mDescription = 'Find hooks that are undocumented, missing, or just plain wrong';
00053         $this->addOption( 'online', 'Check against MediaWiki.org hook documentation' );
00054     }
00055 
00056     public function getDbType() {
00057         return Maintenance::DB_NONE;
00058     }
00059 
00060     public function execute() {
00061         global $IP;
00062 
00063         $documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' );
00064         $potential = array();
00065         $bad = array();
00066         $pathinc = array(
00067             $IP . '/',
00068             $IP . '/includes/',
00069             $IP . '/includes/actions/',
00070             $IP . '/includes/api/',
00071             $IP . '/includes/cache/',
00072             $IP . '/includes/changes/',
00073             $IP . '/includes/clientpool/',
00074             $IP . '/includes/content/',
00075             $IP . '/includes/context/',
00076             $IP . '/includes/dao/',
00077             $IP . '/includes/db/',
00078             $IP . '/includes/debug/',
00079             $IP . '/includes/deferred/',
00080             $IP . '/includes/diff/',
00081             $IP . '/includes/externalstore/',
00082             $IP . '/includes/filebackend/',
00083             $IP . '/includes/filerepo/',
00084             $IP . '/includes/filerepo/file/',
00085             $IP . '/includes/gallery/',
00086             $IP . '/includes/htmlform/',
00087             $IP . '/includes/installer/',
00088             $IP . '/includes/interwiki/',
00089             $IP . '/includes/jobqueue/',
00090             $IP . '/includes/json/',
00091             $IP . '/includes/logging/',
00092             $IP . '/includes/media/',
00093             $IP . '/includes/parser/',
00094             $IP . '/includes/rcfeed/',
00095             $IP . '/includes/resourceloader/',
00096             $IP . '/includes/revisiondelete/',
00097             $IP . '/includes/search/',
00098             $IP . '/includes/site/',
00099             $IP . '/includes/specialpage/',
00100             $IP . '/includes/specials/',
00101             $IP . '/includes/upload/',
00102             $IP . '/languages/',
00103             $IP . '/maintenance/',
00104             $IP . '/maintenance/language/',
00105             $IP . '/tests/',
00106             $IP . '/tests/parser/',
00107             $IP . '/tests/phpunit/suites/',
00108             $IP . '/skins/',
00109         );
00110 
00111         foreach ( $pathinc as $dir ) {
00112             $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
00113             $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
00114         }
00115 
00116         $potential = array_unique( $potential );
00117         $bad = array_unique( $bad );
00118         $todo = array_diff( $potential, $documented );
00119         $deprecated = array_diff( $documented, $potential );
00120 
00121         // let's show the results:
00122         $this->printArray( 'Undocumented', $todo );
00123         $this->printArray( 'Documented and not found', $deprecated );
00124         $this->printArray( 'Unclear hook calls', $bad );
00125 
00126         if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 ) {
00127             $this->output( "Looks good!\n" );
00128         }
00129     }
00130 
00135     private function getHooksFromDoc( $doc ) {
00136         if ( $this->hasOption( 'online' ) ) {
00137             return $this->getHooksFromOnlineDoc();
00138         } else {
00139             return $this->getHooksFromLocalDoc( $doc );
00140         }
00141     }
00142 
00148     private function getHooksFromLocalDoc( $doc ) {
00149             $m = array();
00150             $content = file_get_contents( $doc );
00151             preg_match_all( "/\n'(.*?)':/", $content, $m );
00152             return array_unique( $m[1] );
00153     }
00154 
00159     private function getHooksFromOnlineDoc() {
00160             // All hooks
00161             $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' );
00162             $allhookdata = unserialize( $allhookdata );
00163             $allhooks = array();
00164             foreach ( $allhookdata['query']['categorymembers'] as $page ) {
00165                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00166                 if ( $found ) {
00167                     $hook = str_replace( ' ', '_', $matches[1] );
00168                     $allhooks[] = $hook;
00169                 }
00170             }
00171             // Removed hooks
00172             $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' );
00173             $oldhookdata = unserialize( $oldhookdata );
00174             $removed = array();
00175             foreach ( $oldhookdata['query']['categorymembers'] as $page ) {
00176                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
00177                 if ( $found ) {
00178                     $hook = str_replace( ' ', '_', $matches[1] );
00179                     $removed[] = $hook;
00180                 }
00181             }
00182             return array_diff( $allhooks, $removed );
00183     }
00184 
00190     private function getHooksFromFile( $file ) {
00191         $content = file_get_contents( $file );
00192         $m = array();
00193         preg_match_all( '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', $content, $m );
00194         return $m[2];
00195     }
00196 
00202     private function getHooksFromPath( $path ) {
00203         $hooks = array();
00204         $dh = opendir( $path );
00205         if ( $dh ) {
00206             while ( ( $file = readdir( $dh ) ) !== false ) {
00207                 if ( filetype( $path . $file ) == 'file' ) {
00208                     $hooks = array_merge( $hooks, $this->getHooksFromFile( $path . $file ) );
00209                 }
00210             }
00211             closedir( $dh );
00212         }
00213         return $hooks;
00214     }
00215 
00221     private function getBadHooksFromFile( $file ) {
00222         $content = file_get_contents( $file );
00223         $m = array();
00224         # We want to skip the "function wfRunHooks()" one.  :)
00225         preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m );
00226         $list = array();
00227         foreach ( $m[0] as $match ) {
00228             $list[] = $match . "(" . $file . ")";
00229         }
00230         return $list;
00231     }
00232 
00238     private function getBadHooksFromPath( $path ) {
00239         $hooks = array();
00240         $dh = opendir( $path );
00241         if ( $dh ) {
00242             while ( ( $file = readdir( $dh ) ) !== false ) {
00243                 # We don't want to read this file as it contains bad calls to wfRunHooks()
00244                 if ( filetype( $path . $file ) == 'file' && !$path . $file == __FILE__ ) {
00245                     $hooks = array_merge( $hooks, $this->getBadHooksFromFile( $path . $file ) );
00246                 }
00247             }
00248             closedir( $dh );
00249         }
00250         return $hooks;
00251     }
00252 
00259     private function printArray( $msg, $arr, $sort = true ) {
00260         if ( $sort ) {
00261             asort( $arr );
00262         }
00263 
00264         foreach ( $arr as $v ) {
00265             if ( !in_array( $v, self::$ignore ) ) {
00266                 $this->output( "$msg: $v\n" );
00267             }
00268         }
00269     }
00270 }
00271 
00272 $maintClass = 'FindHooks';
00273 require_once RUN_MAINTENANCE_IF_MAIN;