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