Completed
Push — master ( 316830...6154cf )
by Paweł
03:31
created

code_review::boot()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 6
Bugs 2 Features 4
Metric Value
cc 3
eloc 12
c 6
b 2
f 4
nc 4
nop 0
dl 0
loc 17
rs 9.4285
ccs 0
cts 0
cp 0
crap 12
1
<?php
2
/**
3
 * Main plugin class. 
4
 * Storage for various handlers.
5
 * @author Paweł Sroka ([email protected])
6
 */
7
class code_review {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
9
	/**
10
	 * Config array to allow mocking of configuration.
11
	 *
12
	 * @var array
13
	 */
14
	protected static $config = array();
15
16
	/**
17
	 * @codeCoverageIgnore
18
	 */
19
	public static function boot() {
20
		if (version_compare(elgg_get_version(true), '1.9', '<')) {
21
			$autoloader = new CodeReviewAutoloader();
22
			$autoloader->register();
23
		}
24
25
		$enginePath = elgg_get_config('path') . 'engine/';
26
		if (function_exists('elgg_get_engine_path')) {
27
			$enginePath = elgg_get_engine_path() . '/';
28
		}
29
		self::initConfig(array(
30
			'engine_path' => $enginePath,
31
			'path' => elgg_get_config('path'),
32
			'pluginspath' => elgg_get_plugins_path(),
33
			'plugins_getter' => 'elgg_get_plugins',
34
		));
35
	}
36
37
	/**
38
	 * @return array
39
	 */
40 1
	public static function getConfig() {
41 1
		return self::$config;
42
	}
43
44
	/**
45
	 * @param array $options
46
	 *
47
	 * @todo Move into CodeReviewConfig instead
48
	 */
49 5
	public static function initConfig(array $options) {
50 5
		self::$config = $options;
51
52
		$names = array(
53 5
			'T_NAMESPACE',
54 5
			'T_NS_C',
55 5
			'T_NS_SEPARATOR',
56 5
		);
57
58 5
		foreach ($names as $name) {
59 5
			if (!defined($name)) {
60
				// just define it with value unused by tokenizer to avoid errors on old PHP versions
61
				define($name, 10000);
62
			}
63 5
		}
64 5
	}
65
66
	/**
67
	 * @codeCoverageIgnore
68
	 */
69
	public static function init() {
70
71
		elgg_register_event_handler('pagesetup', 'system', array(__CLASS__, 'pagesetup'));
72
73
		elgg_register_plugin_hook_handler('register', 'menu:code_review', array(__CLASS__, 'menu_register'));
74
75
		elgg_register_ajax_view('graphics/ajax_loader');
76
		elgg_register_ajax_view('code_review/analysis');
77
		
78
		elgg_register_js('code_review', elgg_get_config('wwwroot') . 'mod/'
79
			. __CLASS__ . '/views/default/js/code_review.js');
80
	}
81
82
	/**
83
	 * @codeCoverageIgnore
84
	 */
85
	public static function pagesetup() {
86
		if (elgg_get_context() == 'admin') {
87
			elgg_register_menu_item('page', array(
88
				'name' => 'code/diagnostic',
89
				'href' => 'admin/code/diagnostic',
90
				'text' => elgg_echo('admin:code:diagnostic'),
91
				'context' => 'admin',
92
				'section' => 'develop'
93
			));
94
		}
95
	}
96
97
	/**
98
	 * @codeCoverageIgnore
99
	 */
100
	public static function menu_register() {
101
		$result = array();
102
		$result[] = ElggMenuItem::factory(array(
103
			'name' => 'admin/code/diagnostic',
104
			'href' => 'admin/code/diagnostic',
105
			'text' => elgg_echo('admin:code:diagnostic'),
106
		));
107
		$result[] = ElggMenuItem::factory(array(
108
			'name' => 'admin/code/diagnostic/deprecated_list',
109
			'href' => 'admin/code/diagnostic/deprecated_list',
110
			'text' => elgg_echo('admin:code:diagnostic:deprecated_list'),
111
		));
112
		$result[] = ElggMenuItem::factory(array(
113
			'name' => 'admin/code/diagnostic/private_list',
114
			'href' => 'admin/code/diagnostic/private_list',
115
			'text' => elgg_echo('admin:code:diagnostic:private_list'),
116
		));
117
		$result[] = ElggMenuItem::factory(array(
118
			'name' => 'admin/code/diagnostic/functions_list',
119
			'href' => 'admin/code/diagnostic/functions_list',
120
			'text' => elgg_echo('admin:code:diagnostic:functions_list'),
121
		));
122
		return $result;
123
	}
124
125
	/**
126
	 * @param string $subPath
127
	 * @return RegexIterator
128
	 */
129 4
	public static function getPhpIterator($subPath = '/') {
130 4
		$i = new RecursiveDirectoryIterator(self::$config['engine_path'] . $subPath, RecursiveDirectoryIterator::SKIP_DOTS);
131 4
		$i = new RecursiveIteratorIterator($i, RecursiveIteratorIterator::LEAVES_ONLY);
132 4
		$i = new RegexIterator($i, "/.*\.php/");
133 4
		return $i;
134
	}
135
136 1
	public static function getVersionsList() {
137 1
		$i = self::getPhpIterator('lib/');
138 1
		$i = new RegexIterator($i, "/deprecated-.*/");
139
		
140 1
		$vv = array();
141
		
142 1
		foreach ($i as $file) {
143 1
			if ($file instanceof SplFileInfo) {
144 1
				if (preg_match('#^deprecated-([0-9\.]*)$#', $file->getBasename('.php'), $matches)) {
145 1
					$version = $matches[1];
146 1
				} else {
147
					$version = null;
148
				}
149 1
				if ($version !== null) {
150 1
					$vv[] = $version;
151 1
				}
152 1
			}
153 1
		}
154 1
		usort($vv, 'version_compare');
155 1
		return $vv;
156
	}
157
158
	/**
159
	 * @val string
160
	 */
161
	const DEPRECATED_TAG_PREFIX = 'deprecated';
162
163
164
	/**
165
	 * @val string
166
	 */
167
	const PRIVATE_TAG_PREFIX = 'private';
168
169
	/**
170
	 * Filtering predicate
171
	 *
172
	 * @param $e
173
	 * @return bool
174
	 */
175 2
	public static function filterTagsByDeprecatedPrefix($e) {
176 2
		return strpos($e, self::DEPRECATED_TAG_PREFIX) === 0;
177
	}
178
179
	/**
180
	 * Filtering predicate
181
	 *
182
	 * @param $e
183
	 * @return bool
184
	 */
185
	public static function filterTagsByPrivatePrefix($e) {
186
		return strpos($e, self::PRIVATE_TAG_PREFIX) === 0;
187
	}
188
189 2
	private static function getDeprecatedInfoFromDocBlock($deprecatedInfo, $maxVersion) {
190 2
		if (strpos($deprecatedInfo, '@' . self::DEPRECATED_TAG_PREFIX) === false){
191 2
			return false;
192
		} else {
193 2
			$deprecatedInfo = explode('* @', $deprecatedInfo);
194 2
			$deprecatedInfo = array_filter($deprecatedInfo, array(__CLASS__, 'filterTagsByDeprecatedPrefix'));
195 2
			$deprecatedInfo = array_shift($deprecatedInfo);
196 2
			$deprecatedInfo = substr($deprecatedInfo, strlen(self::DEPRECATED_TAG_PREFIX));
197
198
			//strip leading whitechars and stars and closing tags
199 2
			$deprecatedInfo = preg_replace('#\n\s*(?:\*\/?\s*)+#', "\n", $deprecatedInfo);
200
			//save and strip leading version info
201 2
			$version = null;
202 2
			preg_match('#^\s*([0-9]+\.[0-9]+)#', $deprecatedInfo, $matches);
203 2
			if (!empty($matches)) {
204 2
				$version = $matches[1];
205 2
			}
206 2
			$deprecatedInfo = preg_replace('#\s*[0-9](?:\.[0-9]+){1,2}\.?\s*#', "", $deprecatedInfo);
207
			//strip embedded @link docblock entries
208 2
			$deprecatedInfo = preg_replace('#\{\@[a-z]+\s([^\}]+)\}#', '$1', $deprecatedInfo);
209
			//trim possible whitechars at the end
210 2
			$deprecatedInfo = trim($deprecatedInfo);
211
212 2
			$shortDeprecatedInfo = $deprecatedInfo;
213 2
			if (($pos = strpos($shortDeprecatedInfo, "\n")) !== false) {
214
				$shortDeprecatedInfo = substr($shortDeprecatedInfo, 0, $pos);
215
			}
216
217
			$result = array(
218 2
				'deprecated' => true,
219 2
				'fixinfoshort' => strlen($shortDeprecatedInfo) > 0 ? $shortDeprecatedInfo : false,
220 2
			);
221 2
			if ($version !== null) {
222
				//skip versions higher than selected
223 2
				if ($maxVersion && version_compare($version, $maxVersion) > 0) {
224 1
					return false;
225
				}
226 2
				$result['version'] = $version;
227 2
			}
228 2
			return $result;
229
		}
230
	}
231
232
	/**
233
	 * @param string $maxVersion
234
	 * @return array
235
	 */
236 2
	public static function getDeprecatedFunctionsList($maxVersion = '') {
237 2
		$i1 = self::getPhpIterator('lib/');
238 2
		$i1 = new RegexIterator($i1, "/deprecated-.*/");
239 2
		$i2 = self::getPhpIterator('classes/');
240
241 2
		$i = new AppendIterator();
242 2
		$i->append($i1);
243 2
		$i->append($i2);
244
		
245 2
		$functs = array();
246
		
247 2
		foreach ($i as $file) {
248 2
			if ($file instanceof SplFileInfo) {
249 2
				if (preg_match('#^deprecated-([0-9\.]*)$#', $file->getBasename('.php'), $matches)) {
250 2
					$version = $matches[1];
251 2
				} else {
252 2
					$version = null;
253
				}
254
				
255
				//skip versions higher than selected
256 2
				if ($maxVersion && $version !== null && version_compare($version, $maxVersion) > 0) {
257 1
					continue;
258
				}
259
260 2
				$tokens = new PhpFileParser($file->getPathname());
261 2
				$functs = array_merge($functs, self::getDeprecatedFunctionsFromTokens($tokens, $file, $version, $maxVersion));
262 2
			}
263 2
		}
264 2
		return $functs;
265
	}
266
267
	/**
268
	 * @return array
269
	 */
270 1
	public static function getPrivateFunctionsList() {
271 1
		$i1 = new DirectoryIterator(self::$config['engine_path'] . 'lib/');
272 1
		$i1 = new RegexIterator($i1, "/.*\.php/");
273 1
		$i2 = self::getPhpIterator('classes/');
274
275 1
		$i = new AppendIterator();
276 1
		$i->append($i1);
277 1
		$i->append($i2);
278
279 1
		$functs = array();
280
281 1
		foreach ($i as $file) {
282 1
			if ($file instanceof SplFileInfo) {
283 1
				$tokens = new PhpFileParser($file->getPathname());
284 1
				$functs = array_merge($functs, self::getPrivateFunctionsFromTokens($tokens, $file));
285 1
			}
286 1
		}
287 1
		return $functs;
288
	}
289
290
	/**
291
	 * Redurns deprecated functions from particular file.
292
	 *
293
	 * @param PhpFileParser $tokens
294
	 * @param SplFileInfo   $file
295
	 * @param               $version
296
	 * @param               $maxVersion max version to return
297
	 * @return array
298
	 */
299 2
	private static function getDeprecatedFunctionsFromTokens(PhpFileParser $tokens, SplFileInfo $file, $version, $maxVersion) {
300 2
		$namespace = '';
301 2
		$className = null;
302 2
		$functs = array();
303 2
		foreach ($tokens as $key => $token) {
304 2
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
305
				//we don't process interfaces for deprecated functions
306
				break;
307
			}
308 2
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
309 2
				$pos = $key+2;
310 2
				$namespace = '';
311 2
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
312 2
					$namespace .= $tokens[$pos][1];
313 2
						$pos++;
314 2
				}
315 2
				$namespace = '\\' . $namespace . '\\';
316 2
			}
317 2
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
318
				//mark class name for all following functions
319 2
				$className = $namespace . $tokens[$key+2][1];
320 2
			}
321
322
			//TODO we need to filter out closures
323
324 2
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
325 2
				if ($className !== null) {
326 2
					$functionName = $className . '::' . $tokens[$key+2][1];
327
					try {
328 2
						$reflection = new ReflectionMethod($className, $tokens[$key+2][1]);
329 2
					} catch (ReflectionException $e) {
330
//						var_dump($className, $functionName, $e->getMessage());
1 ignored issue
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
331
						continue;
332
					}
333
334 2
				} else {
335 1
					$functionName = $tokens[$key+2][1];
336
					try {
337 1
						$reflection = new ReflectionFunction($functionName);
338 1
					} catch (ReflectionException $e) {
339
//						var_dump($functionName, $e->getMessage());
1 ignored issue
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
340
						continue;
341
					}
342
				}
343
344
				//check if non empty version and try go guess
345
				$data = array(
346 2
					'name' => $functionName,
347 2
					'version' => $version,
348 2
					'file' => $file->getPathname(),
349 2
					'line' => $token[2],
350 2
				);
351
352 2
				$docBlock = $reflection->getDocComment();
353 2
				if ($docBlock) {
354 2
					$info = self::getDeprecatedInfoFromDocBlock($docBlock, $maxVersion);
355 2
					if (!$info) {
356 2
						if ($version) {
357
							// no details, but we have version, so everything is deprecated here
358
							$info = array(
359 1
								'deprecated' => true,
360 1
								'version' => $version,
361 1
								'fixinfoshort' => false,
362 1
							);
363 1
						} else {
364
							//skipping - not deprecated
365 2
							continue;
366
						}
367 1
					}
368 2
					$data = array_merge($data, $info);
369 2
				}
370
371 2
				$functs[strtolower($functionName)] = new CodeReview_Issues_Deprecated($data);
372 2
			}
373 2
		}
374 2
		return $functs;
375
	}
376
377
	/**
378
	 * Redurns deprecated functions from particular file.
379
	 *
380
	 * @param PhpFileParser $tokens
381
	 * @param SplFileInfo   $file
382
	 * @param               $version
383
	 * @return array
384
	 */
385 1
	private static function getPrivateFunctionsFromTokens(PhpFileParser $tokens, SplFileInfo $file) {
386 1
		$namespace = '';
387 1
		$className = null;
388 1
		$functs = array();
389 1
		foreach ($tokens as $key => $token) {
390 1
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
391
				//we don't process interfaces for deprecated functions
392
				break;
393
			}
394 1
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
395 1
				$pos = $key+2;
396 1
				$namespace = '';
397 1
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
398 1
					$namespace .= $tokens[$pos][1];
399 1
					$pos++;
400 1
				}
401 1
				$namespace = '\\' . $namespace . '\\';
402 1
			}
403 1
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
404
				//mark class name for all following functions
405 1
				$className = $namespace . $tokens[$key+2][1];
406 1
			}
407
408 1
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
409 1
				if ($className !== null) {
410 1
					$functionName = $className . '::' . $tokens[$key+2][1];
411
					try {
412 1
						$reflection = new ReflectionMethod($className, $tokens[$key+2][1]);
413 1
					} catch (ReflectionException $e) {
414
						break;
415
					}
416 1
				} else {
417 1
					$functionName = $tokens[$key+2][1];
418
					try {
419 1
						$reflection = new ReflectionFunction($functionName);
420 1
					} catch (ReflectionException $e) {
421
						break;
422
					}
423
				}
424
425
				//check if non empty version and try go guess
426
				$data = array(
427 1
					'name' => $functionName,
428 1
					'file' => $file->getPathname(),
429 1
					'line' => $token[2],
430 1
				);
431
432 1
				$docBlock = $reflection->getDocComment();
433 1
				if ($docBlock) {
434 1
					if (preg_match('/@access\s+private/', $docBlock) < 1) {
435
						//skipping - not private
436 1
						continue;
437
					}
438 1
					$data = new CodeReview_Issues_Private($data);
439 1
				} else {
440
					//non documented means private
441 1
					$data = new CodeReview_Issues_NotDocumented($data);
442
				}
443
444 1
				$functs[strtolower($functionName)] = $data;
445 1
			}
446 1
		}
447 1
		return $functs;
448
	}
449
450
	/**
451
	 * Returns a list of plugin directory names from a base directory.
452
	 * Copied from 1.9 core due to elgg_get_plugin_ids_in_dir removal in 1.9
453
	 *
454
	 * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path.
455
	 *                    Must have a trailing slash.
456
	 *
457
	 * @return array Array of directory names (not full paths)
458
	 */
459 1
	public static function getPluginDirsInDir($dir = null) {
460 1
		if ($dir === null) {
461
			$dir = self::$config['pluginspath'];
462
		}
463
464 1
		$plugin_dirs = array();
465 1
		$handle = opendir($dir);
466
467 1
		if ($handle) {
468 1
			while ($plugin_dir = readdir($handle)) {
469
				// must be directory and not begin with a .
470 1
				if (substr($plugin_dir, 0, 1) !== '.' && is_dir($dir . $plugin_dir)) {
471 1
					$plugin_dirs[] = $plugin_dir;
472 1
				}
473 1
			}
474 1
		}
475
476 1
		sort($plugin_dirs);
477
478 1
		return $plugin_dirs;
479
	}
480
}