MediaWiki
REL1_24
|
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 00067 // TODO: Don't hardcode the list of directories 00068 $pathinc = array( 00069 $IP . '/', 00070 $IP . '/includes/', 00071 $IP . '/includes/actions/', 00072 $IP . '/includes/api/', 00073 $IP . '/includes/cache/', 00074 $IP . '/includes/changes/', 00075 $IP . '/includes/clientpool/', 00076 $IP . '/includes/content/', 00077 $IP . '/includes/context/', 00078 $IP . '/includes/dao/', 00079 $IP . '/includes/db/', 00080 $IP . '/includes/debug/', 00081 $IP . '/includes/deferred/', 00082 $IP . '/includes/diff/', 00083 $IP . '/includes/externalstore/', 00084 $IP . '/includes/filebackend/', 00085 $IP . '/includes/filerepo/', 00086 $IP . '/includes/filerepo/file/', 00087 $IP . '/includes/gallery/', 00088 $IP . '/includes/htmlform/', 00089 $IP . '/includes/installer/', 00090 $IP . '/includes/interwiki/', 00091 $IP . '/includes/jobqueue/', 00092 $IP . '/includes/json/', 00093 $IP . '/includes/logging/', 00094 $IP . '/includes/media/', 00095 $IP . '/includes/page/', 00096 $IP . '/includes/parser/', 00097 $IP . '/includes/rcfeed/', 00098 $IP . '/includes/resourceloader/', 00099 $IP . '/includes/revisiondelete/', 00100 $IP . '/includes/search/', 00101 $IP . '/includes/site/', 00102 $IP . '/includes/skins/', 00103 $IP . '/includes/specialpage/', 00104 $IP . '/includes/specials/', 00105 $IP . '/includes/upload/', 00106 $IP . '/includes/utils/', 00107 $IP . '/languages/', 00108 $IP . '/maintenance/', 00109 $IP . '/maintenance/language/', 00110 $IP . '/tests/', 00111 $IP . '/tests/parser/', 00112 $IP . '/tests/phpunit/suites/', 00113 ); 00114 00115 foreach ( $pathinc as $dir ) { 00116 $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) ); 00117 $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) ); 00118 } 00119 00120 $potential = array_unique( $potential ); 00121 $bad = array_unique( $bad ); 00122 $todo = array_diff( $potential, $documented ); 00123 $deprecated = array_diff( $documented, $potential ); 00124 00125 // let's show the results: 00126 $this->printArray( 'Undocumented', $todo ); 00127 $this->printArray( 'Documented and not found', $deprecated ); 00128 $this->printArray( 'Unclear hook calls', $bad ); 00129 00130 if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 ) { 00131 $this->output( "Looks good!\n" ); 00132 } 00133 } 00134 00140 private function getHooksFromDoc( $doc ) { 00141 if ( $this->hasOption( 'online' ) ) { 00142 return $this->getHooksFromOnlineDoc(); 00143 } else { 00144 return $this->getHooksFromLocalDoc( $doc ); 00145 } 00146 } 00147 00153 private function getHooksFromLocalDoc( $doc ) { 00154 $m = array(); 00155 $content = file_get_contents( $doc ); 00156 preg_match_all( "/\n'(.*?)':/", $content, $m ); 00157 00158 return array_unique( $m[1] ); 00159 } 00160 00165 private function getHooksFromOnlineDoc() { 00166 // All hooks 00167 $allhookdata = Http::get( 00168 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&' 00169 . 'cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' 00170 ); 00171 $allhookdata = unserialize( $allhookdata ); 00172 $allhooks = array(); 00173 foreach ( $allhookdata['query']['categorymembers'] as $page ) { 00174 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); 00175 if ( $found ) { 00176 $hook = str_replace( ' ', '_', $matches[1] ); 00177 $allhooks[] = $hook; 00178 } 00179 } 00180 // Removed hooks 00181 $oldhookdata = Http::get( 00182 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&' 00183 . 'cmtitle=Category:Removed_hooks&cmlimit=500&format=php' 00184 ); 00185 $oldhookdata = unserialize( $oldhookdata ); 00186 $removed = array(); 00187 foreach ( $oldhookdata['query']['categorymembers'] as $page ) { 00188 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches ); 00189 if ( $found ) { 00190 $hook = str_replace( ' ', '_', $matches[1] ); 00191 $removed[] = $hook; 00192 } 00193 } 00194 00195 return array_diff( $allhooks, $removed ); 00196 } 00197 00203 private function getHooksFromFile( $file ) { 00204 $content = file_get_contents( $file ); 00205 $m = array(); 00206 preg_match_all( 00207 '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', 00208 $content, 00209 $m 00210 ); 00211 00212 return $m[2]; 00213 } 00214 00220 private function getHooksFromPath( $path ) { 00221 $hooks = array(); 00222 $dh = opendir( $path ); 00223 if ( $dh ) { 00224 while ( ( $file = readdir( $dh ) ) !== false ) { 00225 if ( filetype( $path . $file ) == 'file' ) { 00226 $hooks = array_merge( $hooks, $this->getHooksFromFile( $path . $file ) ); 00227 } 00228 } 00229 closedir( $dh ); 00230 } 00231 00232 return $hooks; 00233 } 00234 00240 private function getBadHooksFromFile( $file ) { 00241 $content = file_get_contents( $file ); 00242 $m = array(); 00243 # We want to skip the "function wfRunHooks()" one. :) 00244 preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m ); 00245 $list = array(); 00246 foreach ( $m[0] as $match ) { 00247 $list[] = $match . "(" . $file . ")"; 00248 } 00249 00250 return $list; 00251 } 00252 00258 private function getBadHooksFromPath( $path ) { 00259 $hooks = array(); 00260 $dh = opendir( $path ); 00261 if ( $dh ) { 00262 while ( ( $file = readdir( $dh ) ) !== false ) { 00263 # We don't want to read this file as it contains bad calls to wfRunHooks() 00264 if ( filetype( $path . $file ) == 'file' && !$path . $file == __FILE__ ) { 00265 $hooks = array_merge( $hooks, $this->getBadHooksFromFile( $path . $file ) ); 00266 } 00267 } 00268 closedir( $dh ); 00269 } 00270 00271 return $hooks; 00272 } 00273 00280 private function printArray( $msg, $arr, $sort = true ) { 00281 if ( $sort ) { 00282 asort( $arr ); 00283 } 00284 00285 foreach ( $arr as $v ) { 00286 if ( !in_array( $v, self::$ignore ) ) { 00287 $this->output( "$msg: $v\n" ); 00288 } 00289 } 00290 } 00291 } 00292 00293 $maintClass = 'FindHooks'; 00294 require_once RUN_MAINTENANCE_IF_MAIN;