code_review::getPrivateFunctionsList()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

Changes 5
Bugs 1 Features 3
Metric Value
cc 3
eloc 13
c 5
b 1
f 3
nc 3
nop 0
dl 0
loc 19
ccs 15
cts 15
cp 1
crap 3
rs 9.4285
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 \CodeReview\Autoloader();
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 12
	public static function getConfig() {
41 12
		return self::$config;
42
	}
43
44
	/**
45
	 * @param array $options
46
	 *
47
	 * @todo Move into \CodeReview\Config instead
48
	 */
49 21
	public static function initConfig(array $options) {
50 21
		self::$config = $options;
51
52
		$names = array(
53 21
			'T_NAMESPACE',
54 21
			'T_NS_C',
55 21
			'T_NS_SEPARATOR',
56 21
		);
57
58 21
		foreach ($names as $name) {
59 21
			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 21
		}
64 21
	}
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 12
	public static function getPhpIterator($subPath = '/') {
130 12
		$i = new RecursiveDirectoryIterator(self::$config['engine_path'] . $subPath, RecursiveDirectoryIterator::SKIP_DOTS);
131 12
		$i = new RecursiveIteratorIterator($i, RecursiveIteratorIterator::LEAVES_ONLY);
132 12
		$i = new RegexIterator($i, "/.*\.php/");
133 12
		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 10
	public static function filterTagsByDeprecatedPrefix($e) {
176 10
		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 10
	private static function getDeprecatedInfoFromDocBlock($deprecatedInfo, $maxVersion) {
190 10
		if (strpos($deprecatedInfo, '@' . self::DEPRECATED_TAG_PREFIX) === false){
191 10
			return false;
192
		} else {
193 10
			$deprecatedInfo = explode('* @', $deprecatedInfo);
194 10
			$deprecatedInfo = array_filter($deprecatedInfo, array(__CLASS__, 'filterTagsByDeprecatedPrefix'));
195 10
			$deprecatedInfo = array_shift($deprecatedInfo);
196 10
			$deprecatedInfo = substr($deprecatedInfo, strlen(self::DEPRECATED_TAG_PREFIX));
197
198
			//strip leading whitechars and stars and closing tags
199 10
			$deprecatedInfo = preg_replace('#\n\s*(?:\*\/?\s*)+#', "\n", $deprecatedInfo);
200
			//save and strip leading version info
201 10
			$version = null;
202 10
			preg_match('#^\s*([0-9]+\.[0-9]+)#', $deprecatedInfo, $matches);
203 10
			if (!empty($matches)) {
204 10
				$version = $matches[1];
205 10
			}
206 10
			$deprecatedInfo = preg_replace('#\s*[0-9](?:\.[0-9]+){1,2}\.?\s*#', "", $deprecatedInfo);
207
			//strip embedded @link docblock entries
208 10
			$deprecatedInfo = preg_replace('#\{\@[a-z]+\s([^\}]+)\}#', '$1', $deprecatedInfo);
209
			//trim possible whitechars at the end
210 10
			$deprecatedInfo = trim($deprecatedInfo);
211
212 10
			$shortDeprecatedInfo = $deprecatedInfo;
213 10
			if (($pos = strpos($shortDeprecatedInfo, "\n")) !== false) {
214
				$shortDeprecatedInfo = substr($shortDeprecatedInfo, 0, $pos);
215
			}
216
217
			$result = array(
218 10
				'deprecated' => true,
219 10
				'fixinfoshort' => strlen($shortDeprecatedInfo) > 0 ? $shortDeprecatedInfo : false,
220 10
			);
221 10
			if ($version !== null) {
222
				//skip versions higher than selected
223 10
				if ($maxVersion && version_compare($version, $maxVersion) > 0) {
224 2
					return false;
225
				}
226 10
				$result['version'] = $version;
227 10
			}
228 10
			return $result;
229
		}
230
	}
231
232
	/**
233
	 * @param string $maxVersion
234
	 * @return array
235
	 */
236 10
	public static function getDeprecatedFunctionsList($maxVersion = '') {
237 10
		$i1 = self::getPhpIterator('lib/');
238 10
		$i1 = new RegexIterator($i1, "/deprecated-.*/");
239 10
		$i2 = self::getPhpIterator('classes/');
240
241 10
		$i = new AppendIterator();
242 10
		$i->append($i1);
243 10
		$i->append($i2);
244
		
245 10
		$functs = array();
246
		
247 10
		foreach ($i as $file) {
248 10
			if ($file instanceof \SplFileInfo) {
249 10
				if (preg_match('#^deprecated-([0-9\.]*)$#', $file->getBasename('.php'), $matches)) {
250 10
					$version = $matches[1];
251 10
				} else {
252 10
					$version = null;
253
				}
254
				
255
				//skip versions higher than selected
256 10
				if ($maxVersion && $version !== null && version_compare($version, $maxVersion) > 0) {
257 2
					continue;
258
				}
259
260 10
				$tokens = new \CodeReview\PhpFileParser($file->getPathname());
261 10
				$functs = array_merge($functs, self::getDeprecatedFunctionsFromTokens($tokens, $file, $version, $maxVersion));
262 10
			}
263 10
		}
264 10
		return $functs;
265
	}
266
267
	/**
268
	 * @return array
269
	 */
270 6
	public static function getPrivateFunctionsList() {
271 6
		$i1 = new DirectoryIterator(self::$config['engine_path'] . 'lib/');
272 6
		$i1 = new RegexIterator($i1, "/.*\.php/");
273 6
		$i2 = self::getPhpIterator('classes/');
274
275 6
		$i = new AppendIterator();
276 6
		$i->append($i1);
277 6
		$i->append($i2);
278
279 6
		$functs = array();
280
281 6
		foreach ($i as $file) {
282 6
			if ($file instanceof \SplFileInfo) {
283 6
				$tokens = new \CodeReview\PhpFileParser($file->getPathname());
284 6
				$functs = array_merge($functs, self::getPrivateFunctionsFromTokens($tokens, $file));
285 6
			}
286 6
		}
287 6
		return $functs;
288
	}
289
290
	/**
291
	 * Redurns deprecated functions from particular file.
292
	 *
293
	 * @param \CodeReview\PhpFileParser $tokens
294
	 * @param \SplFileInfo   $file
295
	 * @param               $version
296
	 * @param               $maxVersion max version to return
297
	 * @return array
298
	 */
299 10
	private static function getDeprecatedFunctionsFromTokens(\CodeReview\PhpFileParser $tokens, \SplFileInfo $file, $version, $maxVersion) {
300 10
		$namespace = '';
301 10
		$className = null;
302 10
		$functs = array();
303 10
		foreach ($tokens as $key => $token) {
304 10
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
305
				//we don't process interfaces for deprecated functions
306
				break;
307
			}
308 10
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
309 10
				$pos = $key+2;
310 10
				$namespace = '';
311 10
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
312 10
					$namespace .= $tokens[$pos][1];
313 10
						$pos++;
314 10
				}
315 10
				$namespace = '\\' . $namespace . '\\';
316 10
			}
317 10
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
318
				//mark class name for all following functions
319 10
				$className = $namespace . $tokens[$key+2][1];
320 10
			}
321
322
			//TODO we need to filter out closures
323
324 10
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
325 10
				if ($className !== null) {
326 10
					$functionName = $className . '::' . $tokens[$key+2][1];
327
					try {
328 10
						$reflection = new \ReflectionMethod($className, $tokens[$key+2][1]);
329 10
					} catch (\ReflectionException $e) {
330
//						var_dump($className, $functionName, $e->getMessage());
331
						continue;
332
					}
333
334 10
				} else {
335 8
					$functionName = $tokens[$key+2][1];
336
					try {
337 8
						$reflection = new \ReflectionFunction($functionName);
338 8
					} catch (\ReflectionException $e) {
339
//						var_dump($functionName, $e->getMessage());
340
						continue;
341
					}
342
				}
343
344
				//check if non empty version and try go guess
345
				$data = array(
346 10
					'name' => $functionName,
347 10
					'version' => $version,
348 10
					'file' => $file->getPathname(),
349 10
					'line' => $token[2],
350 10
				);
351
352 10
				$docBlock = $reflection->getDocComment();
353 10
				if ($docBlock) {
354 10
					$info = self::getDeprecatedInfoFromDocBlock($docBlock, $maxVersion);
355 10
					if (!$info) {
356 10
						if ($version) {
357
							// no details, but we have version, so everything is deprecated here
358
							$info = array(
359 8
								'deprecated' => true,
360 8
								'version' => $version,
361 8
								'fixinfoshort' => false,
362 8
							);
363 8
						} else {
364
							//skipping - not deprecated
365 10
							continue;
366
						}
367 8
					}
368 10
					$data = array_merge($data, $info);
369 10
				}
370
371 10
				$functs[strtolower($functionName)] = new \CodeReview\Issues\DeprecatedIssue($data);
372 10
			}
373 10
		}
374 10
		return $functs;
375
	}
376
377
	/**
378
	 * Redurns deprecated functions from particular file.
379
	 *
380
	 * @param \CodeReview\PhpFileParser $tokens
381
	 * @param \SplFileInfo   $file
382
	 * @param               $version
383
	 * @return array
384
	 */
385 6
	private static function getPrivateFunctionsFromTokens(\CodeReview\PhpFileParser $tokens, \SplFileInfo $file) {
386 6
		$namespace = '';
387 6
		$className = null;
388 6
		$functs = array();
389 6
		foreach ($tokens as $key => $token) {
390 6
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
391
				//we don't process interfaces for deprecated functions
392
				break;
393
			}
394 6
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
395 6
				$pos = $key+2;
396 6
				$namespace = '';
397 6
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
398 6
					$namespace .= $tokens[$pos][1];
399 6
					$pos++;
400 6
				}
401 6
				$namespace = '\\' . $namespace . '\\';
402 6
			}
403 6
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
404
				//mark class name for all following functions
405 6
				$className = $namespace . $tokens[$key+2][1];
406 6
			}
407
408 6
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
409 6
				if ($className !== null) {
410 6
					$functionName = $className . '::' . $tokens[$key+2][1];
411
					try {
412 6
						$reflection = new \ReflectionMethod($className, $tokens[$key+2][1]);
413 6
					} catch (\ReflectionException $e) {
414
						continue;
415
					}
416 6
				} else {
417 6
					$functionName = $tokens[$key+2][1];
418
					try {
419 6
						$reflection = new \ReflectionFunction($functionName);
420 6
					} catch (\ReflectionException $e) {
421
						continue;
422
					}
423
				}
424
425
				//check if non empty version and try go guess
426
				$data = array(
427 6
					'name' => $functionName,
428 6
					'file' => $file->getPathname(),
429 6
					'line' => $token[2],
430 6
				);
431
432 6
				$docBlock = $reflection->getDocComment();
433 6
				if ($docBlock) {
434 6
					if (preg_match('/@access\s+private/', $docBlock) < 1) {
435
						//skipping - not private
436 6
						continue;
437
					}
438 6
					$data = new \CodeReview\Issues\PrivateIssue($data);
439 6
				} else {
440
					//non documented means private
441 6
					$data = new \CodeReview\Issues\NotDocumentedIssue($data);
442
				}
443
444 6
				$functs[strtolower($functionName)] = $data;
445 6
			}
446 6
		}
447 6
		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 5
	public static function getPluginDirsInDir($dir = null) {
460 5
		if ($dir === null) {
461
			$dir = self::$config['pluginspath'];
462
		}
463
464 5
		$plugin_dirs = array();
465 5
		$handle = opendir($dir);
466
467 5
		if ($handle) {
468 5
			while ($plugin_dir = readdir($handle)) {
469
				// must be directory and not begin with a .
470 5
				if (substr($plugin_dir, 0, 1) !== '.' && is_dir($dir . $plugin_dir)) {
471 5
					$plugin_dirs[] = $plugin_dir;
472 5
				}
473 5
			}
474 5
		}
475
476 5
		sort($plugin_dirs);
477
478 5
		return $plugin_dirs;
479
	}
480
}