Completed
Branch master (486a28)
by Paweł
02:07
created

code_review   C

Complexity

Total Complexity 69

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 75.12%

Importance

Changes 16
Bugs 3 Features 7
Metric Value
c 16
b 3
f 7
dl 0
loc 472
rs 5.6445
ccs 160
cts 213
cp 0.7512
wmc 69
lcom 1
cbo 5

17 Methods

Rating   Name   Duplication   Size   Complexity  
A boot() 0 17 3
A getConfig() 0 3 1
A initConfig() 0 16 3
A init() 0 12 1
A pagesetup() 0 11 2
B menu_register() 0 26 1
A getPhpIterator() 0 6 1
B getVersionsList() 0 20 5
A filterTagsByDeprecatedPrefix() 0 3 1
A filterTagsByPrivatePrefix() 0 3 1
B getDeprecatedInfoFromDocBlock() 0 39 6
A getPrivateInfoFromDocBlock() 0 10 2
C getDeprecatedFunctionsList() 0 30 7
A getPrivateFunctionsList() 0 19 3
C getDeprecatedFunctionsFromTokens() 0 66 13
C getPrivateFunctionsFromTokens() 0 64 13
B getPluginDirsInDir() 0 21 6

How to fix   Complexity   

Complex Class

Complex classes like code_review often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use code_review, and based on these observations, apply Extract Interface, too.

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 3
	public static function initConfig(array $options) {
50 3
		self::$config = $options;
51
52
		$names = array(
53 3
			'T_NAMESPACE',
54 3
			'T_NS_C',
55 3
			'T_NS_SEPARATOR',
56 3
		);
57
58 3
		foreach ($names as $name) {
59 3
			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 3
		}
64 3
	}
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
//			'target' => '_blank',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
112
		));
113
		$result[] = ElggMenuItem::factory(array(
114
			'name' => 'admin/code/diagnostic/private_list',
115
			'href' => 'admin/code/diagnostic/private_list',
116
			'text' => elgg_echo('admin:code:diagnostic:private_list'),
117
//			'target' => '_blank',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
118
		));
119
		$result[] = ElggMenuItem::factory(array(
120
			'name' => 'admin/code/diagnostic/functions_list',
121
			'href' => 'admin/code/diagnostic/functions_list',
122
			'text' => elgg_echo('admin:code:diagnostic:functions_list'),
123
		));
124
		return $result;
125
	}
126
127
	/**
128
	 * @param string $subPath
129
	 * @return RegexIterator
130
	 */
131 2
	public static function getPhpIterator($subPath = '/') {
132 2
		$i = new RecursiveDirectoryIterator(self::$config['engine_path'] . $subPath, RecursiveDirectoryIterator::SKIP_DOTS);
133 2
		$i = new RecursiveIteratorIterator($i, RecursiveIteratorIterator::LEAVES_ONLY);
134 2
		$i = new RegexIterator($i, "/.*\.php/");
135 2
		return $i;
136
	}
137
138
	public static function getVersionsList() {
139
		$i = self::getPhpIterator('lib/');
140
		$i = new RegexIterator($i, "/deprecated-.*/");
141
		
142
		$vv = array();
143
		
144
		foreach ($i as $file) {
145
			if ($file instanceof SplFileInfo) {
146
				if (preg_match('#^deprecated-([0-9\.]*)$#', $file->getBasename('.php'), $matches)) {
147
					$version = $matches[1];
148
				} else {
149
					$version = null;
150
				}
151
				if ($version) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
152
					$vv[] = $version;
153
				}
154
			}
155
		}
156
		return $vv;
157
	}
158
159
	/**
160
	 * @val string
161
	 */
162
	const DEPRECATED_TAG_PREFIX = 'deprecated';
163
164
165
	/**
166
	 * @val string
167
	 */
168
	const PRIVATE_TAG_PREFIX = 'private';
169
170
	/**
171
	 * Filtering predicate
172
	 *
173
	 * @param $e
174
	 * @return bool
175
	 */
176 1
	public static function filterTagsByDeprecatedPrefix($e) {
177 1
		return strpos($e, self::DEPRECATED_TAG_PREFIX) === 0;
178
	}
179
180
	/**
181
	 * Filtering predicate
182
	 *
183
	 * @param $e
184
	 * @return bool
185
	 */
186
	public static function filterTagsByPrivatePrefix($e) {
187
		return strpos($e, self::PRIVATE_TAG_PREFIX) === 0;
188
	}
189
190 1
	private static function getDeprecatedInfoFromDocBlock($deprecatedInfo) {
191 1
		if (strpos($deprecatedInfo, '@' . self::DEPRECATED_TAG_PREFIX) === false){
192
			return false;
193
		} else {
194 1
			$deprecatedInfo = explode('* @', $deprecatedInfo);
195 1
			$deprecatedInfo = array_filter($deprecatedInfo, array(__CLASS__, 'filterTagsByDeprecatedPrefix'));
196 1
			$deprecatedInfo = array_shift($deprecatedInfo);
197 1
			$deprecatedInfo = substr($deprecatedInfo, strlen(self::DEPRECATED_TAG_PREFIX));
198
199
			//strip leading whitechars and stars and closing tags
200 1
			$deprecatedInfo = preg_replace('#\n\s*(?:\*\/?\s*)+#', "\n", $deprecatedInfo);
201
			//save and strip leading version info
202 1
			$version = null;
203 1
			preg_match('#^\s*([0-9]+\.[0-9]+)#', $deprecatedInfo, $matches);
204 1
			if ($matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
205 1
				$version = $matches[1];
206 1
			}
207 1
			$deprecatedInfo = preg_replace('#\s*[0-9](?:\.[0-9]+){1,2}\.?\s*#', "", $deprecatedInfo);
208
			//strip embedded @link docblock entries
209 1
			$deprecatedInfo = preg_replace('#\{\@[a-z]+\s([^\}]+)\}#', '$1', $deprecatedInfo);
210
			//trim possible whitechars at the end
211 1
			$deprecatedInfo = trim($deprecatedInfo);
212
213 1
			$shortDeprecatedInfo = $deprecatedInfo;
214 1
			if (($pos = strpos($shortDeprecatedInfo, "\n")) !== false) {
215
				$shortDeprecatedInfo = substr($shortDeprecatedInfo, 0, $pos);
216
			}
217
218
			$result = array(
219 1
				'deprecated' => true,
220
//				'fixinfo' => strlen($deprecatedInfo) > 0 ? $deprecatedInfo : false,
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
221 1
				'fixinfoshort' => strlen($shortDeprecatedInfo) > 0 ? $shortDeprecatedInfo : false,
222 1
			);
223 1
			if ($version) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
224 1
				$result['version'] = $version;
225 1
			}
226 1
			return $result;
227
		}
228
	}
229
230
	private static function getPrivateInfoFromDocBlock($privateInfo) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
231
		if (strpos($privateInfo, '@' . self::PRIVATE_TAG_PREFIX) === false){
232
			return false;
233
		} else {
234
			$result = array(
235
				'private' => true
236
			);
237
			return $result;
238
		}
239
	}
240
241
	/**
242
	 * @param string $maxVersion
243
	 * @return array
244
	 */
245 1
	public static function getDeprecatedFunctionsList($maxVersion = '') {
246 1
		$i1 = self::getPhpIterator('lib/');
247 1
		$i1 = new RegexIterator($i1, "/deprecated-.*/");
248 1
		$i2 = self::getPhpIterator('classes/');
249
250 1
		$i = new AppendIterator();
251 1
		$i->append($i1);
252 1
		$i->append($i2);
253
		
254 1
		$functs = array();
255
		
256 1
		foreach ($i as $file) {
257 1
			if ($file instanceof SplFileInfo) {
258 1
				if (preg_match('#^deprecated-([0-9\.]*)$#', $file->getBasename('.php'), $matches)) {
259 1
					$version = $matches[1];
260 1
				} else {
261 1
					$version = null;
262
				}
263
				
264
				//skip versions higher than selected
265 1
				if ($maxVersion && $version && version_compare($version, $maxVersion) > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $version of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
266 1
					continue;
267
				}
268
269 1
				$tokens = new PhpFileParser($file->getPathname());
270 1
				$functs = array_merge($functs, self::getDeprecatedFunctionsFromTokens($tokens, $file, $version));
271 1
			}
272 1
		}
273 1
		return $functs;
274
	}
275
276
	/**
277
	 * @param string $maxVersion
0 ignored issues
show
Bug introduced by
There is no parameter named $maxVersion. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
278
	 * @return array
279
	 */
280 1
	public static function getPrivateFunctionsList() {
281 1
		$i1 = new DirectoryIterator(self::$config['engine_path'] . 'lib/');
282 1
		$i1 = new RegexIterator($i1, "/.*\.php/");
283 1
		$i2 = self::getPhpIterator('classes/');
284
285 1
		$i = new AppendIterator();
286 1
		$i->append($i1);
287 1
		$i->append($i2);
288
289 1
		$functs = array();
290
291 1
		foreach ($i as $file) {
292 1
			if ($file instanceof SplFileInfo) {
293 1
				$tokens = new PhpFileParser($file->getPathname());
294 1
				$functs = array_merge($functs, self::getPrivateFunctionsFromTokens($tokens, $file));
295 1
			}
296 1
		}
297 1
		return $functs;
298
	}
299
300
	/**
301
	 * Redurns deprecated functions from particular file.
302
	 *
303
	 * @param PhpFileParser $tokens
304
	 * @param SplFileInfo   $file
305
	 * @param               $version
306
	 * @return array
307
	 */
308 1
	private static function getDeprecatedFunctionsFromTokens(PhpFileParser $tokens, SplFileInfo $file, $version) {
309 1
		$namespace = '';
310 1
		$className = null;
311 1
		$functs = array();
312 1
		foreach ($tokens as $key => $token) {
313 1
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
314
				//we don't process interfaces for deprecated functions
315
				break;
316
			}
317 1
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
318
				$pos = $key+2;
319
				$namespace = '';
320
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
321
					$namespace .= $tokens[$pos][1];
322
						$pos++;
323
				}
324
				$namespace = '\\' . $namespace . '\\';
325
			}
326 1
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
327
				//mark class name for all following functions
328 1
				$className = $namespace . $tokens[$key+2][1];
329 1
			}
330
331
			//TODO we need to filter out closures
332
333 1
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
334 1
				if ($className) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
335 1
					$functionName = $className . '::' . $tokens[$key+2][1];
336
					try {
337 1
						$reflection = new ReflectionMethod($className, $tokens[$key+2][1]);
338 1
					} catch (ReflectionException $e) {
339 1
						break;
340
					}
341
342
				} else {
343 1
					$functionName = $tokens[$key+2][1];
344
					try {
345 1
						$reflection = new ReflectionFunction($functionName);
346 1
					} catch (ReflectionException $e) {
347
						break;
348
					}
349
				}
350
351
				//check if non empty version and try go guess
352
				$data = array(
353 1
					'name' => $functionName,
354 1
					'version' => $version,
355 1
					'file' => $file->getPathname(),
356 1
					'line' => $token[2],
357 1
				);
358
359 1
				$docBlock = $reflection->getDocComment();
360 1
				if ($docBlock) {
361 1
					$info = self::getDeprecatedInfoFromDocBlock($docBlock);
362 1
					if (!$info) {
363
						//skipping - not deprecated
364
						continue;
365
					}
366 1
					$data = array_merge($data, $info);
367 1
				}
368
369 1
				$functs[strtolower($functionName)] = new CodeReview_Issues_Deprecated($data);
370 1
			}
371 1
		}
372 1
		return $functs;
373
	}
374
375
	/**
376
	 * Redurns deprecated functions from particular file.
377
	 *
378
	 * @param PhpFileParser $tokens
379
	 * @param SplFileInfo   $file
380
	 * @param               $version
381
	 * @return array
382
	 */
383 1
	private static function getPrivateFunctionsFromTokens(PhpFileParser $tokens, SplFileInfo $file) {
384 1
		$namespace = '';
385 1
		$className = null;
386 1
		$functs = array();
387 1
		foreach ($tokens as $key => $token) {
388 1
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
389
				//we don't process interfaces for deprecated functions
390
				break;
391
			}
392 1
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
393
				$pos = $key+2;
394
				$namespace = '';
395
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
396
					$namespace .= $tokens[$pos][1];
397
					$pos++;
398
				}
399
				$namespace = '\\' . $namespace . '\\';
400
			}
401 1
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
402
				//mark class name for all following functions
403 1
				$className = $namespace . $tokens[$key+2][1];
404 1
			}
405
406 1
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
407 1
				if ($className) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
408 1
					$functionName = $className . '::' . $tokens[$key+2][1];
409
					try {
410 1
						$reflection = new ReflectionMethod($className, $tokens[$key+2][1]);
411 1
					} catch (ReflectionException $e) {
412 1
						break;
413
					}
414
				} else {
415 1
					$functionName = $tokens[$key+2][1];
416
					try {
417 1
						$reflection = new ReflectionFunction($functionName);
418 1
					} catch (ReflectionException $e) {
419
						break;
420
					}
421
				}
422
423
				//check if non empty version and try go guess
424
				$data = array(
425 1
					'name' => $functionName,
426 1
					'file' => $file->getPathname(),
427 1
					'line' => $token[2],
428 1
				);
429
430 1
				$docBlock = $reflection->getDocComment();
431 1
				if ($docBlock) {
432 1
					if (preg_match('/@access\s+private/', $docBlock) < 1) {
433
						//skipping - not private
434 1
						continue;
435
					}
436 1
					$data = new CodeReview_Issues_Private($data);
437 1
				} else {
438
					//non documented means private
439 1
					$data = new CodeReview_Issues_NotDocumented($data);
440
				}
441
442 1
				$functs[strtolower($functionName)] = $data;
443 1
			}
444 1
		}
445 1
		return $functs;
446
	}
447
448
	/**
449
	 * Returns a list of plugin directory names from a base directory.
450
	 * Copied from 1.9 core due to elgg_get_plugin_ids_in_dir removal in 1.9
451
	 *
452
	 * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path.
453
	 *                    Must have a trailing slash.
454
	 *
455
	 * @return array Array of directory names (not full paths)
456
	 */
457 1
	public static function getPluginDirsInDir($dir = null) {
458 1
		if (!$dir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dir of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
459
			$dir = self::$config['pluginspath'];
460
		}
461
462 1
		$plugin_dirs = array();
463 1
		$handle = opendir($dir);
464
465 1
		if ($handle) {
466 1
			while ($plugin_dir = readdir($handle)) {
467
				// must be directory and not begin with a .
468 1
				if (substr($plugin_dir, 0, 1) !== '.' && is_dir($dir . $plugin_dir)) {
469 1
					$plugin_dirs[] = $plugin_dir;
470 1
				}
471 1
			}
472 1
		}
473
474 1
		sort($plugin_dirs);
475
476 1
		return $plugin_dirs;
477
	}
478
}