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