PageRenderTime 274ms CodeModel.GetById 82ms app.highlight 88ms RepoModel.GetById 43ms app.codeStats 0ms

/wwwroot/mediawiki/maintenance/findHooks.php

https://github.com/spring/spring-website
PHP | 273 lines | 167 code | 20 blank | 86 comment | 22 complexity | 952f715241ce8755a61dd31dfd70731a MD5 | raw file
  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 * Copyright Š Antoine Musso
 16 *
 17 * This program is free software; you can redistribute it and/or modify
 18 * it under the terms of the GNU General Public License as published by
 19 * the Free Software Foundation; either version 2 of the License, or
 20 * (at your option) any later version.
 21 *
 22 * This program is distributed in the hope that it will be useful,
 23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 25 * GNU General Public License for more details.
 26 *
 27 * You should have received a copy of the GNU General Public License along
 28 * with this program; if not, write to the Free Software Foundation, Inc.,
 29 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 30 * http://www.gnu.org/copyleft/gpl.html
 31 *
 32 * @file
 33 * @ingroup Maintenance
 34 * @author Antoine Musso <hashar at free dot fr>
 35 */
 36
 37require_once __DIR__ . '/Maintenance.php';
 38
 39/**
 40 * Maintenance script that compares documented and actually present mismatches.
 41 *
 42 * @ingroup Maintenance
 43 */
 44class FindHooks extends Maintenance {
 45	/*
 46	 * Hooks that are ignored
 47	 */
 48	protected static $ignore = array( 'testRunLegacyHooks' );
 49
 50	public function __construct() {
 51		parent::__construct();
 52		$this->mDescription = 'Find hooks that are undocumented, missing, or just plain wrong';
 53		$this->addOption( 'online', 'Check against MediaWiki.org hook documentation' );
 54	}
 55
 56	public function getDbType() {
 57		return Maintenance::DB_NONE;
 58	}
 59
 60	public function execute() {
 61		global $IP;
 62
 63		$documented = $this->getHooksFromDoc( $IP . '/docs/hooks.txt' );
 64		$potential = array();
 65		$bad = array();
 66		$pathinc = array(
 67			$IP . '/',
 68			$IP . '/includes/',
 69			$IP . '/includes/actions/',
 70			$IP . '/includes/api/',
 71			$IP . '/includes/cache/',
 72			$IP . '/includes/changes/',
 73			$IP . '/includes/clientpool/',
 74			$IP . '/includes/content/',
 75			$IP . '/includes/context/',
 76			$IP . '/includes/dao/',
 77			$IP . '/includes/db/',
 78			$IP . '/includes/debug/',
 79			$IP . '/includes/deferred/',
 80			$IP . '/includes/diff/',
 81			$IP . '/includes/externalstore/',
 82			$IP . '/includes/filebackend/',
 83			$IP . '/includes/filerepo/',
 84			$IP . '/includes/filerepo/file/',
 85			$IP . '/includes/gallery/',
 86			$IP . '/includes/htmlform/',
 87			$IP . '/includes/installer/',
 88			$IP . '/includes/interwiki/',
 89			$IP . '/includes/jobqueue/',
 90			$IP . '/includes/json/',
 91			$IP . '/includes/logging/',
 92			$IP . '/includes/media/',
 93			$IP . '/includes/parser/',
 94			$IP . '/includes/rcfeed/',
 95			$IP . '/includes/resourceloader/',
 96			$IP . '/includes/revisiondelete/',
 97			$IP . '/includes/search/',
 98			$IP . '/includes/site/',
 99			$IP . '/includes/specialpage/',
100			$IP . '/includes/specials/',
101			$IP . '/includes/upload/',
102			$IP . '/languages/',
103			$IP . '/maintenance/',
104			$IP . '/maintenance/language/',
105			$IP . '/tests/',
106			$IP . '/tests/parser/',
107			$IP . '/tests/phpunit/suites/',
108			$IP . '/skins/',
109		);
110
111		foreach ( $pathinc as $dir ) {
112			$potential = array_merge( $potential, $this->getHooksFromPath( $dir ) );
113			$bad = array_merge( $bad, $this->getBadHooksFromPath( $dir ) );
114		}
115
116		$potential = array_unique( $potential );
117		$bad = array_unique( $bad );
118		$todo = array_diff( $potential, $documented );
119		$deprecated = array_diff( $documented, $potential );
120
121		// let's show the results:
122		$this->printArray( 'Undocumented', $todo );
123		$this->printArray( 'Documented and not found', $deprecated );
124		$this->printArray( 'Unclear hook calls', $bad );
125
126		if ( count( $todo ) == 0 && count( $deprecated ) == 0 && count( $bad ) == 0 ) {
127			$this->output( "Looks good!\n" );
128		}
129	}
130
131	/**
132	 * Get the hook documentation, either locally or from MediaWiki.org
133	 * @return array of documented hooks
134	 */
135	private function getHooksFromDoc( $doc ) {
136		if ( $this->hasOption( 'online' ) ) {
137			return $this->getHooksFromOnlineDoc();
138		} else {
139			return $this->getHooksFromLocalDoc( $doc );
140		}
141	}
142
143	/**
144	 * Get hooks from a local file (for example docs/hooks.txt)
145	 * @param $doc string: filename to look in
146	 * @return array of documented hooks
147	 */
148	private function getHooksFromLocalDoc( $doc ) {
149			$m = array();
150			$content = file_get_contents( $doc );
151			preg_match_all( "/\n'(.*?)':/", $content, $m );
152			return array_unique( $m[1] );
153	}
154
155	/**
156	 * Get hooks from www.mediawiki.org using the API
157	 * @return array of documented hooks
158	 */
159	private function getHooksFromOnlineDoc() {
160			// All hooks
161			$allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' );
162			$allhookdata = unserialize( $allhookdata );
163			$allhooks = array();
164			foreach ( $allhookdata['query']['categorymembers'] as $page ) {
165				$found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
166				if ( $found ) {
167					$hook = str_replace( ' ', '_', $matches[1] );
168					$allhooks[] = $hook;
169				}
170			}
171			// Removed hooks
172			$oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' );
173			$oldhookdata = unserialize( $oldhookdata );
174			$removed = array();
175			foreach ( $oldhookdata['query']['categorymembers'] as $page ) {
176				$found = preg_match( '/Manual\:Hooks\/([a-zA-Z0-9- :]+)/', $page['title'], $matches );
177				if ( $found ) {
178					$hook = str_replace( ' ', '_', $matches[1] );
179					$removed[] = $hook;
180				}
181			}
182			return array_diff( $allhooks, $removed );
183	}
184
185	/**
186	 * Get hooks from a PHP file
187	 * @param $file string Full filename to the PHP file.
188	 * @return array of hooks found.
189	 */
190	private function getHooksFromFile( $file ) {
191		$content = file_get_contents( $file );
192		$m = array();
193		preg_match_all( '/(?:wfRunHooks|Hooks\:\:run|ContentHandler\:\:runLegacyHooks)\(\s*([\'"])(.*?)\1/', $content, $m );
194		return $m[2];
195	}
196
197	/**
198	 * Get hooks from the source code.
199	 * @param $path Directory where the include files can be found
200	 * @return array of hooks found.
201	 */
202	private function getHooksFromPath( $path ) {
203		$hooks = array();
204		$dh = opendir( $path );
205		if ( $dh ) {
206			while ( ( $file = readdir( $dh ) ) !== false ) {
207				if ( filetype( $path . $file ) == 'file' ) {
208					$hooks = array_merge( $hooks, $this->getHooksFromFile( $path . $file ) );
209				}
210			}
211			closedir( $dh );
212		}
213		return $hooks;
214	}
215
216	/**
217	 * Get bad hooks (where the hook name could not be determined) from a PHP file
218	 * @param $file string Full filename to the PHP file.
219	 * @return array of bad wfRunHooks() lines
220	 */
221	private function getBadHooksFromFile( $file ) {
222		$content = file_get_contents( $file );
223		$m = array();
224		# We want to skip the "function wfRunHooks()" one.  :)
225		preg_match_all( '/(?<!function )wfRunHooks\(\s*[^\s\'"].*/', $content, $m );
226		$list = array();
227		foreach ( $m[0] as $match ) {
228			$list[] = $match . "(" . $file . ")";
229		}
230		return $list;
231	}
232
233	/**
234	 * Get bad hooks from the source code.
235	 * @param $path Directory where the include files can be found
236	 * @return array of bad wfRunHooks() lines
237	 */
238	private function getBadHooksFromPath( $path ) {
239		$hooks = array();
240		$dh = opendir( $path );
241		if ( $dh ) {
242			while ( ( $file = readdir( $dh ) ) !== false ) {
243				# We don't want to read this file as it contains bad calls to wfRunHooks()
244				if ( filetype( $path . $file ) == 'file' && !$path . $file == __FILE__ ) {
245					$hooks = array_merge( $hooks, $this->getBadHooksFromFile( $path . $file ) );
246				}
247			}
248			closedir( $dh );
249		}
250		return $hooks;
251	}
252
253	/**
254	 * Nicely output the array
255	 * @param $msg String: a message to show before the value
256	 * @param $arr Array: an array
257	 * @param $sort Boolean: whether to sort the array (Default: true)
258	 */
259	private function printArray( $msg, $arr, $sort = true ) {
260		if ( $sort ) {
261			asort( $arr );
262		}
263
264		foreach ( $arr as $v ) {
265			if ( !in_array( $v, self::$ignore ) ) {
266				$this->output( "$msg: $v\n" );
267			}
268		}
269	}
270}
271
272$maintClass = 'FindHooks';
273require_once RUN_MAINTENANCE_IF_MAIN;