Completed
Push — master ( efb497...d1ccba )
by Paweł
03:23
created

code_review::getPrivateFunctionsFromTokens()   C

Complexity

Conditions 13
Paths 14

Size

Total Lines 64
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 13.0501

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 13
eloc 42
c 2
b 0
f 1
nc 14
nop 2
dl 0
loc 64
ccs 42
cts 45
cp 0.9333
crap 13.0501
rs 6.0391

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 4
	public static function initConfig(array $options) {
50 4
		self::$config = $options;
51
52
		$names = array(
53 4
			'T_NAMESPACE',
54 4
			'T_NS_C',
55 4
			'T_NS_SEPARATOR',
56 4
		);
57
58 4
		foreach ($names as $name) {
59 4
			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 4
		}
64 4
	}
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 3
	public static function getPhpIterator($subPath = '/') {
132 3
		$i = new RecursiveDirectoryIterator(self::$config['engine_path'] . $subPath, RecursiveDirectoryIterator::SKIP_DOTS);
133 3
		$i = new RecursiveIteratorIterator($i, RecursiveIteratorIterator::LEAVES_ONLY);
134 3
		$i = new RegexIterator($i, "/.*\.php/");
135 3
		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
		usort($vv, 'version_compare');
157
		return $vv;
158
	}
159
160
	/**
161
	 * @val string
162
	 */
163
	const DEPRECATED_TAG_PREFIX = 'deprecated';
164
165
166
	/**
167
	 * @val string
168
	 */
169
	const PRIVATE_TAG_PREFIX = 'private';
170
171
	/**
172
	 * Filtering predicate
173
	 *
174
	 * @param $e
175
	 * @return bool
176
	 */
177 2
	public static function filterTagsByDeprecatedPrefix($e) {
178 2
		return strpos($e, self::DEPRECATED_TAG_PREFIX) === 0;
179
	}
180
181
	/**
182
	 * Filtering predicate
183
	 *
184
	 * @param $e
185
	 * @return bool
186
	 */
187
	public static function filterTagsByPrivatePrefix($e) {
188
		return strpos($e, self::PRIVATE_TAG_PREFIX) === 0;
189
	}
190
191 2
	private static function getDeprecatedInfoFromDocBlock($deprecatedInfo, $maxVersion) {
192 2
		if (strpos($deprecatedInfo, '@' . self::DEPRECATED_TAG_PREFIX) === false){
193 2
			return false;
194
		} else {
195 2
			$deprecatedInfo = explode('* @', $deprecatedInfo);
196 2
			$deprecatedInfo = array_filter($deprecatedInfo, array(__CLASS__, 'filterTagsByDeprecatedPrefix'));
197 2
			$deprecatedInfo = array_shift($deprecatedInfo);
198 2
			$deprecatedInfo = substr($deprecatedInfo, strlen(self::DEPRECATED_TAG_PREFIX));
199
200
			//strip leading whitechars and stars and closing tags
201 2
			$deprecatedInfo = preg_replace('#\n\s*(?:\*\/?\s*)+#', "\n", $deprecatedInfo);
202
			//save and strip leading version info
203 2
			$version = null;
204 2
			preg_match('#^\s*([0-9]+\.[0-9]+)#', $deprecatedInfo, $matches);
205 2
			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...
206 2
				$version = $matches[1];
207 2
			}
208 2
			$deprecatedInfo = preg_replace('#\s*[0-9](?:\.[0-9]+){1,2}\.?\s*#', "", $deprecatedInfo);
209
			//strip embedded @link docblock entries
210 2
			$deprecatedInfo = preg_replace('#\{\@[a-z]+\s([^\}]+)\}#', '$1', $deprecatedInfo);
211
			//trim possible whitechars at the end
212 2
			$deprecatedInfo = trim($deprecatedInfo);
213
214 2
			$shortDeprecatedInfo = $deprecatedInfo;
215 2
			if (($pos = strpos($shortDeprecatedInfo, "\n")) !== false) {
216
				$shortDeprecatedInfo = substr($shortDeprecatedInfo, 0, $pos);
217
			}
218
219
			$result = array(
220 2
				'deprecated' => true,
221
//				'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...
222 2
				'fixinfoshort' => strlen($shortDeprecatedInfo) > 0 ? $shortDeprecatedInfo : false,
223 2
			);
224 2
			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...
225
				//skip versions higher than selected
226 2
				if ($maxVersion && version_compare($version, $maxVersion) > 0) {
227 1
					return false;
228
				}
229 2
				$result['version'] = $version;
230 2
			}
231 2
			return $result;
232
		}
233
	}
234
235
	private static function getPrivateInfoFromDocBlock($privateInfo) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
236
		if (strpos($privateInfo, '@' . self::PRIVATE_TAG_PREFIX) === false){
237
			return false;
238
		} else {
239
			$result = array(
240
				'private' => true
241
			);
242
			return $result;
243
		}
244
	}
245
246
	/**
247
	 * @param string $maxVersion
248
	 * @return array
249
	 */
250 2
	public static function getDeprecatedFunctionsList($maxVersion = '') {
251 2
		$i1 = self::getPhpIterator('lib/');
252 2
		$i1 = new RegexIterator($i1, "/deprecated-.*/");
253 2
		$i2 = self::getPhpIterator('classes/');
254
255 2
		$i = new AppendIterator();
256 2
		$i->append($i1);
257 2
		$i->append($i2);
258
		
259 2
		$functs = array();
260
		
261 2
		foreach ($i as $file) {
262 2
			if ($file instanceof SplFileInfo) {
263 2
				if (preg_match('#^deprecated-([0-9\.]*)$#', $file->getBasename('.php'), $matches)) {
264 2
					$version = $matches[1];
265 2
				} else {
266 2
					$version = null;
267
				}
268
				
269
				//skip versions higher than selected
270 2
				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...
271 1
					continue;
272
				}
273
274 2
				$tokens = new PhpFileParser($file->getPathname());
275 2
				$functs = array_merge($functs, self::getDeprecatedFunctionsFromTokens($tokens, $file, $version, $maxVersion));
276 2
			}
277 2
		}
278 2
		return $functs;
279
	}
280
281
	/**
282
	 * @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...
283
	 * @return array
284
	 */
285 1
	public static function getPrivateFunctionsList() {
286 1
		$i1 = new DirectoryIterator(self::$config['engine_path'] . 'lib/');
287 1
		$i1 = new RegexIterator($i1, "/.*\.php/");
288 1
		$i2 = self::getPhpIterator('classes/');
289
290 1
		$i = new AppendIterator();
291 1
		$i->append($i1);
292 1
		$i->append($i2);
293
294 1
		$functs = array();
295
296 1
		foreach ($i as $file) {
297 1
			if ($file instanceof SplFileInfo) {
298 1
				$tokens = new PhpFileParser($file->getPathname());
299 1
				$functs = array_merge($functs, self::getPrivateFunctionsFromTokens($tokens, $file));
300 1
			}
301 1
		}
302 1
		return $functs;
303
	}
304
305
	/**
306
	 * Redurns deprecated functions from particular file.
307
	 *
308
	 * @param PhpFileParser $tokens
309
	 * @param SplFileInfo   $file
310
	 * @param               $version
311
	 * @param               $maxVersion max version to return
312
	 * @return array
313
	 */
314 2
	private static function getDeprecatedFunctionsFromTokens(PhpFileParser $tokens, SplFileInfo $file, $version, $maxVersion) {
315 2
		$namespace = '';
316 2
		$className = null;
317 2
		$functs = array();
318 2
		foreach ($tokens as $key => $token) {
319 2
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
320
				//we don't process interfaces for deprecated functions
321
				break;
322
			}
323 2
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
324 2
				$pos = $key+2;
325 2
				$namespace = '';
326 2
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
327 2
					$namespace .= $tokens[$pos][1];
328 2
						$pos++;
329 2
				}
330 2
				$namespace = '\\' . $namespace . '\\';
331 2
			}
332 2
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
333
				//mark class name for all following functions
334 2
				$className = $namespace . $tokens[$key+2][1];
335 2
			}
336
337
			//TODO we need to filter out closures
338
339 2
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
340 2
				if ($className) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type null|string 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...
341 2
					$functionName = $className . '::' . $tokens[$key+2][1];
342
					try {
343 2
						$reflection = new ReflectionMethod($className, $tokens[$key+2][1]);
344 2
					} catch (ReflectionException $e) {
345
						var_dump($e->getMessage());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($e->getMessage()); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
346
						continue;
347
					}
348
349 2
				} else {
350 1
					$functionName = $tokens[$key+2][1];
351
					try {
352 1
						$reflection = new ReflectionFunction($functionName);
353 1
					} catch (ReflectionException $e) {
354
						var_dump($e->getMessage());
355
						continue;
356
					}
357
				}
358
359
				//check if non empty version and try go guess
360
				$data = array(
361 2
					'name' => $functionName,
362 2
					'version' => $version,
363 2
					'file' => $file->getPathname(),
364 2
					'line' => $token[2],
365 2
				);
366
367 2
				$docBlock = $reflection->getDocComment();
368 2
				if ($docBlock) {
369 2
					$info = self::getDeprecatedInfoFromDocBlock($docBlock, $maxVersion);
370 2
					if (!$info) {
371 2
						if ($version) {
372
							// no details, but we have version, so everything is deprecated here
373
							$info = array(
374 1
								'deprecated' => true,
375 1
								'version' => $version,
376 1
								'fixinfoshort' => false,
377 1
							);
378 1
						} else {
379
							//skipping - not deprecated
380 2
							continue;
381
						}
382 1
					}
383 2
					$data = array_merge($data, $info);
384 2
				}
385
386 2
				$functs[strtolower($functionName)] = new CodeReview_Issues_Deprecated($data);
387 2
			}
388 2
		}
389 2
		return $functs;
390
	}
391
392
	/**
393
	 * Redurns deprecated functions from particular file.
394
	 *
395
	 * @param PhpFileParser $tokens
396
	 * @param SplFileInfo   $file
397
	 * @param               $version
398
	 * @return array
399
	 */
400 1
	private static function getPrivateFunctionsFromTokens(PhpFileParser $tokens, SplFileInfo $file) {
401 1
		$namespace = '';
402 1
		$className = null;
403 1
		$functs = array();
404 1
		foreach ($tokens as $key => $token) {
405 1
			if ($tokens->isEqualToToken(T_INTERFACE, $key)) {
406
				//we don't process interfaces for deprecated functions
407
				break;
408
			}
409 1
			if ($tokens->isEqualToToken(T_NAMESPACE, $key)) {
410 1
				$pos = $key+2;
411 1
				$namespace = '';
412 1
				while (isset($tokens[$pos]) && $tokens[$pos] !== ';') {
413 1
					$namespace .= $tokens[$pos][1];
414 1
					$pos++;
415 1
				}
416 1
				$namespace = '\\' . $namespace . '\\';
417 1
			}
418 1
			if ($tokens->isEqualToToken(T_CLASS, $key)) {
419
				//mark class name for all following functions
420 1
				$className = $namespace . $tokens[$key+2][1];
421 1
			}
422
423 1
			if ($tokens->isEqualToToken(T_FUNCTION, $key)) {
424 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...
425 1
					$functionName = $className . '::' . $tokens[$key+2][1];
426
					try {
427 1
						$reflection = new ReflectionMethod($className, $tokens[$key+2][1]);
428 1
					} catch (ReflectionException $e) {
429
						break;
430
					}
431 1
				} else {
432 1
					$functionName = $tokens[$key+2][1];
433
					try {
434 1
						$reflection = new ReflectionFunction($functionName);
435 1
					} catch (ReflectionException $e) {
436
						break;
437
					}
438
				}
439
440
				//check if non empty version and try go guess
441
				$data = array(
442 1
					'name' => $functionName,
443 1
					'file' => $file->getPathname(),
444 1
					'line' => $token[2],
445 1
				);
446
447 1
				$docBlock = $reflection->getDocComment();
448 1
				if ($docBlock) {
449 1
					if (preg_match('/@access\s+private/', $docBlock) < 1) {
450
						//skipping - not private
451 1
						continue;
452
					}
453 1
					$data = new CodeReview_Issues_Private($data);
454 1
				} else {
455
					//non documented means private
456 1
					$data = new CodeReview_Issues_NotDocumented($data);
457
				}
458
459 1
				$functs[strtolower($functionName)] = $data;
460 1
			}
461 1
		}
462 1
		return $functs;
463
	}
464
465
	/**
466
	 * Returns a list of plugin directory names from a base directory.
467
	 * Copied from 1.9 core due to elgg_get_plugin_ids_in_dir removal in 1.9
468
	 *
469
	 * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path.
470
	 *                    Must have a trailing slash.
471
	 *
472
	 * @return array Array of directory names (not full paths)
473
	 */
474 1
	public static function getPluginDirsInDir($dir = null) {
475 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...
476
			$dir = self::$config['pluginspath'];
477
		}
478
479 1
		$plugin_dirs = array();
480 1
		$handle = opendir($dir);
481
482 1
		if ($handle) {
483 1
			while ($plugin_dir = readdir($handle)) {
484
				// must be directory and not begin with a .
485 1
				if (substr($plugin_dir, 0, 1) !== '.' && is_dir($dir . $plugin_dir)) {
486 1
					$plugin_dirs[] = $plugin_dir;
487 1
				}
488 1
			}
489 1
		}
490
491 1
		sort($plugin_dirs);
492
493 1
		return $plugin_dirs;
494
	}
495
}