]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - maintenance/findhooks.php
MediaWiki 1.16.0-scripts
[autoinstalls/mediawiki.git] / maintenance / findhooks.php
1 <?php
2 /**
3  * Simple script that try to find documented hook and hooks actually
4  * in the code and show what's missing.
5  * 
6  * This script assumes that:
7  * - hooks names in hooks.txt are at the beginning of a line and single quoted.
8  * - hooks names in code are the first parameter of wfRunHooks.
9  *
10  * if --online option is passed, the script will compare the hooks in the code
11  * with the ones at http://www.mediawiki.org/wiki/Manual:Hooks
12  *
13  * Any instance of wfRunHooks that doesn't meet these parameters will be noted.
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, write to the Free Software Foundation, Inc.,
27  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28  * http://www.gnu.org/copyleft/gpl.html
29  *
30  * @ingroup Maintenance
31  *
32  * @author Ashar Voultoiz <hashar@altern.org>
33  * @copyright Copyright © Ashar voultoiz
34  * @license http://www.gnu.org/copyleft/gpl.html GNU General Public Licence 2.0 or later
35  */
36
37 require_once( dirname(__FILE__) . '/Maintenance.php' );
38
39 class FindHooks extends Maintenance {
40         public function __construct() {
41                 parent::__construct();
42                 $this->mDescription = "Find hooks that are undocumented, missing, or just plain wrong";
43                 $this->addOption( 'online', 'Check against mediawiki.org hook documentation' );
44         }
45
46         public function getDbType() {
47                 return Maintenance::DB_NONE;
48         }
49
50         public function execute() {
51                 global $IP;
52
53                 $documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' );
54                 $potential = array();
55                 $bad = array();
56                 $pathinc = array(
57                         $IP.'/',
58                         $IP.'/includes/',
59                         $IP.'/includes/api/',
60                         $IP.'/includes/db/',
61                         $IP.'/includes/diff/',
62                         $IP.'/includes/filerepo/',
63                         $IP.'/includes/parser/',
64                         $IP.'/includes/search/',
65                         $IP.'/includes/specials/',
66                         $IP.'/includes/upload/',
67                         $IP.'/languages/',
68                         $IP.'/maintenance/',
69                         $IP.'/skins/',
70                 );
71
72                 foreach( $pathinc as $dir ) {
73                         $potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
74                         $bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
75                 }
76         
77                 $potential = array_unique( $potential );
78                 $bad = array_unique( $bad );
79                 $todo = array_diff( $potential, $documented );
80                 $deprecated = array_diff( $documented, $potential );
81         
82                 // let's show the results:
83                 $this->printArray('Undocumented', $todo );
84                 $this->printArray('Documented and not found', $deprecated );
85                 $this->printArray('Unclear hook calls', $bad );
86         
87                 if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 ) 
88                         $this->output( "Looks good!\n" );
89         }
90
91         /**
92          * Get the hook documentation, either locally or from mediawiki.org
93          * @return array of documented hooks
94          */
95         private function getHooksFromDoc( $doc ) {
96                 if( $this->hasOption( 'online' ) ){
97                         // All hooks
98                         $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' );
99                         $allhookdata = unserialize( $allhookdata );
100                         $allhooks = array();
101                         foreach( $allhookdata['query']['categorymembers'] as $page ) {
102                                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
103                                 if( $found ) {
104                                         $hook = str_replace( ' ', '_', $matches[1] );
105                                         $allhooks[] = $hook;
106                                 }
107                         }
108                         // Removed hooks
109                         $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' );
110                         $oldhookdata = unserialize( $oldhookdata );
111                         $removed = array();
112                         foreach( $oldhookdata['query']['categorymembers'] as $page ) {
113                                 $found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
114                                 if( $found ) {
115                                         $hook = str_replace( ' ', '_', $matches[1] );
116                                         $removed[] = $hook;
117                                 }
118                         }
119                         return array_diff( $allhooks, $removed );
120                 } else {
121                         $m = array();
122                         $content = file_get_contents( $doc );
123                         preg_match_all( "/\n'(.*?)'/", $content, $m );
124                         return array_unique( $m[1] );
125                 }
126         }
127
128         /**
129          * Get hooks from a PHP file
130          * @param $file Full filename to the PHP file.
131          * @return array of hooks found.
132          */
133         private function getHooksFromFile( $file ) {
134                 $content = file_get_contents( $file );
135                 $m = array();
136                 preg_match_all( '/wfRunHooks\(\s*([\'"])(.*?)\1/', $content, $m);
137                 return $m[2];
138         }
139
140         /**
141          * Get hooks from the source code.
142          * @param $path Directory where the include files can be found
143          * @return array of hooks found.
144          */
145         private function getHooksFromPath( $path ) {
146                 $hooks = array();
147                 if( $dh = opendir($path) ) {
148                         while(($file = readdir($dh)) !== false) {
149                                 if( filetype($path.$file) == 'file' ) {
150                                         $hooks = array_merge( $hooks, $this->getHooksFromFile($path.$file) );
151                                 }
152                         }
153                         closedir($dh);
154                 }
155                 return $hooks;
156         }
157
158         /**
159          * Get bad hooks (where the hook name could not be determined) from a PHP file
160          * @param $file Full filename to the PHP file.
161          * @return array of bad wfRunHooks() lines
162          */
163         private function getBadHooksFromFile( $file ) {
164                 $content = file_get_contents( $file );
165                 $m = array();
166                 # We want to skip the "function wfRunHooks()" one.  :)
167                 preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m);
168                 $list = array();
169                 foreach( $m[0] as $match ){
170                         $list[] = $match . "(" . $file . ")";
171                 }
172                 return $list;
173         }
174
175         /**
176          * Get bad hooks from the source code.
177          * @param $path Directory where the include files can be found
178          * @return array of bad wfRunHooks() lines
179          */
180         private function getBadHooksFromPath( $path ) {
181                 $hooks = array();
182                 if( $dh = opendir($path) ) {
183                         while(($file = readdir($dh)) !== false) {
184                                 # We don't want to read this file as it contains bad calls to wfRunHooks()
185                                 if( filetype( $path.$file ) == 'file' && !$path.$file == __FILE__ ) {
186                                         $hooks = array_merge( $hooks, $this->getBadHooksFromFile($path.$file) );
187                                 }
188                         }
189                         closedir($dh);
190                 }
191                 return $hooks;
192         }
193
194         /**
195          * Nicely output the array
196          * @param $msg A message to show before the value
197          * @param $arr An array
198          * @param $sort Boolean : wheter to sort the array (Default: true)
199          */
200         private function printArray( $msg, $arr, $sort = true ) {
201                 if($sort) asort($arr); 
202                 foreach($arr as $v) $this->output( "$msg: $v\n" );
203         }
204 }
205
206 $maintClass = "FindHooks";
207 require_once( DO_MAINTENANCE );