1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | /* |
||||
6 | * This file is part of the humbug/php-scoper package. |
||||
7 | * |
||||
8 | * Copyright (c) 2017 Théo FIDRY <[email protected]>, |
||||
9 | * Pádraic Brady <[email protected]> |
||||
10 | * |
||||
11 | * For the full copyright and license information, please view the LICENSE |
||||
12 | * file that was distributed with this source code. |
||||
13 | */ |
||||
14 | |||||
15 | namespace Humbug\PhpScoper\Configuration; |
||||
16 | |||||
17 | use Humbug\PhpScoper\Symbol\NamespaceRegistry; |
||||
18 | use Humbug\PhpScoper\Symbol\SymbolRegistry; |
||||
19 | use InvalidArgumentException; |
||||
20 | use function array_key_exists; |
||||
21 | use function array_keys; |
||||
22 | use function array_map; |
||||
23 | use function array_merge; |
||||
24 | use function array_pop; |
||||
25 | use function array_values; |
||||
26 | use function explode; |
||||
27 | use function get_debug_type; |
||||
28 | use function gettype; |
||||
29 | use function implode; |
||||
30 | use function is_array; |
||||
31 | use function is_bool; |
||||
32 | use function is_string; |
||||
33 | use function ltrim; |
||||
34 | use function Safe\preg_match as native_preg_match; |
||||
35 | use function Safe\sprintf; |
||||
36 | use function Safe\substr; |
||||
37 | use function str_replace; |
||||
38 | use function strpos; |
||||
39 | use function strtolower; |
||||
40 | use function trim; |
||||
41 | |||||
42 | final class SymbolsConfigurationFactory |
||||
43 | { |
||||
44 | private RegexChecker $regexChecker; |
||||
45 | |||||
46 | public function __construct(RegexChecker $regexChecker) |
||||
47 | { |
||||
48 | $this->regexChecker = $regexChecker; |
||||
49 | } |
||||
50 | |||||
51 | public function createSymbolsConfiguration(array $config): SymbolsConfiguration |
||||
52 | { |
||||
53 | [ |
||||
54 | $excludedNamespaceNames, |
||||
55 | $excludedNamespaceRegexes, |
||||
56 | ] = $this->retrieveElements( |
||||
57 | $config, |
||||
58 | ConfigurationKeys::EXCLUDE_NAMESPACES_KEYWORD, |
||||
59 | ); |
||||
60 | |||||
61 | [ |
||||
62 | $exposedNamespaceNames, |
||||
63 | $exposedNamespaceRegexes, |
||||
64 | ] = $this->retrieveElements( |
||||
65 | $config, |
||||
66 | ConfigurationKeys::EXPOSE_NAMESPACES_KEYWORD, |
||||
67 | ); |
||||
68 | |||||
69 | $legacyExposedElements = self::retrieveLegacyExposedElements($config); |
||||
70 | |||||
71 | [ |
||||
72 | $legacyExposedSymbols, |
||||
73 | $legacyExposedSymbolsPatterns, |
||||
74 | $legacyExposedConstants, |
||||
75 | $excludedNamespaceNames, |
||||
76 | ] = self::parseLegacyExposedElements($legacyExposedElements, $excludedNamespaceNames); |
||||
0 ignored issues
–
show
Deprecated Code
introduced
by
![]() |
|||||
77 | |||||
78 | $exposeGlobalConstants = self::retrieveExposeGlobalSymbol( |
||||
79 | $config, |
||||
80 | ConfigurationKeys::EXPOSE_GLOBAL_CONSTANTS_KEYWORD, |
||||
81 | ); |
||||
82 | $exposeGlobalClasses = self::retrieveExposeGlobalSymbol( |
||||
83 | $config, |
||||
84 | ConfigurationKeys::EXPOSE_GLOBAL_CLASSES_KEYWORD, |
||||
85 | ); |
||||
86 | $exposeGlobalFunctions = self::retrieveExposeGlobalSymbol( |
||||
87 | $config, |
||||
88 | ConfigurationKeys::EXPOSE_GLOBAL_FUNCTIONS_KEYWORD, |
||||
89 | ); |
||||
90 | |||||
91 | [$exposedClassNames, $exposedClassRegexes] = $this->retrieveElements( |
||||
92 | $config, |
||||
93 | ConfigurationKeys::EXPOSE_CLASSES_SYMBOLS_KEYWORD, |
||||
94 | ); |
||||
95 | |||||
96 | [$exposedFunctionNames, $exposedFunctionRegexes] = $this->retrieveElements( |
||||
97 | $config, |
||||
98 | ConfigurationKeys::EXPOSE_FUNCTIONS_SYMBOLS_KEYWORD, |
||||
99 | ); |
||||
100 | |||||
101 | [$exposedConstantNames, $exposedConstantRegexes] = $this->retrieveElements( |
||||
102 | $config, |
||||
103 | ConfigurationKeys::EXPOSE_CONSTANTS_SYMBOLS_KEYWORD, |
||||
104 | ); |
||||
105 | |||||
106 | $excludedClasses = SymbolRegistry::create( |
||||
107 | ...$this->retrieveElements( |
||||
0 ignored issues
–
show
$this->retrieveElements(...TERNAL_SYMBOLS_KEYWORD) is expanded, but the parameter $names of Humbug\PhpScoper\Symbol\SymbolRegistry::create() does not expect variable arguments.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
108 | $config, |
||||
109 | ConfigurationKeys::CLASSES_INTERNAL_SYMBOLS_KEYWORD, |
||||
110 | ), |
||||
111 | ); |
||||
112 | |||||
113 | $excludedFunctions = SymbolRegistry::create( |
||||
114 | ...$this->retrieveElements( |
||||
115 | $config, |
||||
116 | ConfigurationKeys::FUNCTIONS_INTERNAL_SYMBOLS_KEYWORD, |
||||
117 | ), |
||||
118 | ); |
||||
119 | |||||
120 | $excludedConstants = SymbolRegistry::createForConstants( |
||||
121 | ...$this->retrieveElements( |
||||
0 ignored issues
–
show
$this->retrieveElements(...TERNAL_SYMBOLS_KEYWORD) is expanded, but the parameter $names of Humbug\PhpScoper\Symbol\...y::createForConstants() does not expect variable arguments.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
122 | $config, |
||||
123 | ConfigurationKeys::CONSTANTS_INTERNAL_SYMBOLS_KEYWORD, |
||||
124 | ), |
||||
125 | ); |
||||
126 | |||||
127 | return SymbolsConfiguration::create( |
||||
128 | $exposeGlobalConstants, |
||||
129 | $exposeGlobalClasses, |
||||
130 | $exposeGlobalFunctions, |
||||
131 | NamespaceRegistry::create( |
||||
132 | $excludedNamespaceNames, |
||||
133 | $excludedNamespaceRegexes, |
||||
134 | ), |
||||
135 | NamespaceRegistry::create( |
||||
136 | $exposedNamespaceNames, |
||||
137 | $exposedNamespaceRegexes, |
||||
138 | ), |
||||
139 | SymbolRegistry::create( |
||||
140 | array_merge( |
||||
141 | $exposedClassNames, |
||||
142 | $legacyExposedSymbols, |
||||
143 | ), |
||||
144 | array_merge( |
||||
145 | $exposedClassRegexes, |
||||
146 | $legacyExposedSymbolsPatterns, |
||||
147 | ), |
||||
148 | ), |
||||
149 | SymbolRegistry::create( |
||||
150 | array_merge( |
||||
151 | $exposedFunctionNames, |
||||
152 | $legacyExposedSymbols, |
||||
153 | ), |
||||
154 | array_merge( |
||||
155 | $exposedFunctionRegexes, |
||||
156 | $legacyExposedSymbolsPatterns, |
||||
157 | ), |
||||
158 | ), |
||||
159 | SymbolRegistry::createForConstants( |
||||
160 | array_merge( |
||||
161 | $exposedConstantNames, |
||||
162 | $legacyExposedConstants, |
||||
163 | ), |
||||
164 | array_merge( |
||||
165 | $exposedConstantRegexes, |
||||
166 | $legacyExposedSymbolsPatterns, |
||||
167 | ), |
||||
168 | ), |
||||
169 | $excludedClasses, |
||||
170 | $excludedFunctions, |
||||
171 | $excludedConstants, |
||||
172 | ); |
||||
173 | } |
||||
174 | |||||
175 | private static function retrieveExposeGlobalSymbol(array $config, string $key): bool |
||||
176 | { |
||||
177 | if (!array_key_exists($key, $config)) { |
||||
178 | return false; |
||||
179 | } |
||||
180 | |||||
181 | $value = $config[$key]; |
||||
182 | |||||
183 | if (!is_bool($value)) { |
||||
184 | throw new InvalidArgumentException( |
||||
185 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
186 | 'Expected %s to be a boolean, found "%s" instead.', |
||||
187 | $key, |
||||
188 | gettype($value), |
||||
189 | ), |
||||
190 | ); |
||||
191 | } |
||||
192 | |||||
193 | return $value; |
||||
194 | } |
||||
195 | |||||
196 | /** |
||||
197 | * @return list<string> |
||||
0 ignored issues
–
show
The type
Humbug\PhpScoper\Configuration\list was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
198 | */ |
||||
199 | private static function retrieveLegacyExposedElements(array $config): array |
||||
200 | { |
||||
201 | $key = ConfigurationKeys::WHITELIST_KEYWORD; |
||||
202 | |||||
203 | if (!array_key_exists($key, $config)) { |
||||
204 | return []; |
||||
0 ignored issues
–
show
|
|||||
205 | } |
||||
206 | |||||
207 | $whitelist = $config[$key]; |
||||
208 | |||||
209 | if (!is_array($whitelist)) { |
||||
210 | throw new InvalidArgumentException( |
||||
211 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
212 | 'Expected "%s" to be an array of strings, found "%s" instead.', |
||||
213 | $key, |
||||
214 | gettype($whitelist), |
||||
215 | ), |
||||
216 | ); |
||||
217 | } |
||||
218 | |||||
219 | foreach ($whitelist as $index => $className) { |
||||
220 | if (is_string($className)) { |
||||
221 | continue; |
||||
222 | } |
||||
223 | |||||
224 | throw new InvalidArgumentException( |
||||
225 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
226 | 'Expected whitelist to be an array of string, the "%d" element is not.', |
||||
227 | $index, |
||||
228 | ), |
||||
229 | ); |
||||
230 | } |
||||
231 | |||||
232 | return array_values($whitelist); |
||||
0 ignored issues
–
show
|
|||||
233 | } |
||||
234 | |||||
235 | /** |
||||
236 | * @return array{list<string>, list<string>} |
||||
0 ignored issues
–
show
|
|||||
237 | */ |
||||
238 | private function retrieveElements(array $config, string $key): array |
||||
239 | { |
||||
240 | if (!array_key_exists($key, $config)) { |
||||
241 | return [[], []]; |
||||
242 | } |
||||
243 | |||||
244 | $symbolNamesAndRegexes = $config[$key]; |
||||
245 | |||||
246 | self::assertIsArrayOfStrings($config[$key], $key); |
||||
247 | |||||
248 | // Store the strings in the keys for avoiding a unique check later on |
||||
249 | $names = []; |
||||
250 | $regexes = []; |
||||
251 | |||||
252 | foreach ($symbolNamesAndRegexes as $index => $nameOrRegex) { |
||||
253 | if (!$this->regexChecker->isRegexLike($nameOrRegex)) { |
||||
254 | $names[$nameOrRegex] = null; |
||||
255 | |||||
256 | continue; |
||||
257 | } |
||||
258 | |||||
259 | $regex = $nameOrRegex; |
||||
260 | |||||
261 | $this->assertValidRegex($regex, $key, (string) $index); |
||||
262 | |||||
263 | $errorMessage = $this->regexChecker->validateRegex($regex); |
||||
264 | |||||
265 | if (null !== $errorMessage) { |
||||
266 | throw new InvalidArgumentException( |
||||
267 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
268 | 'Expected "%s" to be an array of valid regexes. The element "%s" with the index "%s" is not: %s.', |
||||
269 | $key, |
||||
270 | $regex, |
||||
271 | $index, |
||||
272 | $errorMessage, |
||||
273 | ), |
||||
274 | ); |
||||
275 | } |
||||
276 | |||||
277 | // Ensure namespace comparisons are always case-insensitive |
||||
278 | // TODO: double check that we are not adding it twice or that adding it twice does not break anything |
||||
279 | $regex .= 'i'; |
||||
280 | $regexes[$regex] = null; |
||||
281 | } |
||||
282 | |||||
283 | return [ |
||||
284 | array_keys($names), |
||||
285 | array_keys($regexes), |
||||
286 | ]; |
||||
287 | } |
||||
288 | |||||
289 | /** |
||||
290 | * @deprecated |
||||
291 | * |
||||
292 | * @param list<string> $elements |
||||
293 | * @param list<string> $excludedNamespaceNames |
||||
294 | */ |
||||
295 | private static function parseLegacyExposedElements(array $elements, array $excludedNamespaceNames): array |
||||
296 | { |
||||
297 | $exposedSymbols = []; |
||||
298 | $exposedConstants = []; |
||||
299 | $exposedSymbolsPatterns = []; |
||||
300 | $excludedNamespaceNames = array_map('strtolower', $excludedNamespaceNames); |
||||
301 | |||||
302 | foreach ($elements as $element) { |
||||
303 | $element = ltrim(trim($element), '\\'); |
||||
304 | |||||
305 | self::assertValidElement($element); |
||||
0 ignored issues
–
show
The function
Humbug\PhpScoper\Configu...y::assertValidElement() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
306 | |||||
307 | if ('\*' === substr($element, -2)) { |
||||
0 ignored issues
–
show
The function
Safe\substr() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
308 | $excludedNamespaceNames[] = strtolower(substr($element, 0, -2)); |
||||
0 ignored issues
–
show
The function
Safe\substr() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
309 | } elseif ('*' === $element) { |
||||
310 | $excludedNamespaceNames[] = ''; |
||||
311 | } elseif (false !== strpos($element, '*')) { |
||||
312 | $exposedSymbolsPatterns[] = self::createExposePattern($element); |
||||
0 ignored issues
–
show
The function
Humbug\PhpScoper\Configu...::createExposePattern() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
313 | } else { |
||||
314 | $exposedSymbols[] = strtolower($element); |
||||
315 | $exposedConstants[] = self::lowerCaseConstantName($element); |
||||
0 ignored issues
–
show
The function
Humbug\PhpScoper\Configu...lowerCaseConstantName() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
316 | } |
||||
317 | } |
||||
318 | |||||
319 | return [ |
||||
320 | $exposedSymbols, |
||||
321 | $exposedSymbolsPatterns, |
||||
322 | $exposedConstants, |
||||
323 | $excludedNamespaceNames, |
||||
324 | ]; |
||||
325 | } |
||||
326 | |||||
327 | /** |
||||
328 | * @psalm-assert string[] $value |
||||
329 | * |
||||
330 | * @param mixed $value |
||||
331 | */ |
||||
332 | private static function assertIsArrayOfStrings($value, string $key): void |
||||
333 | { |
||||
334 | if (!is_array($value)) { |
||||
335 | throw new InvalidArgumentException( |
||||
336 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
337 | 'Expected "%s" to be an array of strings, found "%s" instead.', |
||||
338 | $key, |
||||
339 | get_debug_type($value), |
||||
340 | ), |
||||
341 | ); |
||||
342 | } |
||||
343 | |||||
344 | foreach ($value as $index => $element) { |
||||
345 | if (is_string($element)) { |
||||
346 | continue; |
||||
347 | } |
||||
348 | |||||
349 | throw new InvalidArgumentException( |
||||
350 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
351 | 'Expected "%s" to be an array of strings, found "%s" for the element with the index "%s".', |
||||
352 | $key, |
||||
353 | get_debug_type($element), |
||||
354 | $index, |
||||
355 | ), |
||||
356 | ); |
||||
357 | } |
||||
358 | } |
||||
359 | |||||
360 | private function assertValidRegex(string $regex, string $key, string $index): void |
||||
361 | { |
||||
362 | $errorMessage = $this->regexChecker->validateRegex($regex); |
||||
363 | |||||
364 | if (null !== $errorMessage) { |
||||
365 | throw new InvalidArgumentException( |
||||
366 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
367 | 'Expected "%s" to be an array of valid regexes. The element "%s" with the index "%s" is not: %s.', |
||||
368 | $key, |
||||
369 | $regex, |
||||
370 | $index, |
||||
371 | $errorMessage, |
||||
372 | ), |
||||
373 | ); |
||||
374 | } |
||||
375 | } |
||||
376 | |||||
377 | /** |
||||
378 | * @deprecated |
||||
379 | */ |
||||
380 | private static function assertValidElement(string $element): void |
||||
381 | { |
||||
382 | if ('' !== $element) { |
||||
383 | return; |
||||
384 | } |
||||
385 | |||||
386 | throw new InvalidArgumentException( |
||||
387 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
388 | 'Invalid whitelist element "%s": cannot accept an empty string', |
||||
389 | $element, |
||||
390 | ), |
||||
391 | ); |
||||
392 | } |
||||
393 | |||||
394 | /** |
||||
395 | * @deprecated |
||||
396 | */ |
||||
397 | private static function createExposePattern(string $element): string |
||||
398 | { |
||||
399 | self::assertValidPattern($element); |
||||
0 ignored issues
–
show
The function
Humbug\PhpScoper\Configu...y::assertValidPattern() has been deprecated.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
400 | |||||
401 | return sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
402 | '/^%s$/u', |
||||
403 | str_replace( |
||||
404 | '\\', |
||||
405 | '\\\\', |
||||
406 | str_replace( |
||||
407 | '*', |
||||
408 | '.*', |
||||
409 | $element, |
||||
410 | ), |
||||
411 | ), |
||||
412 | ); |
||||
413 | } |
||||
414 | |||||
415 | /** |
||||
416 | * @deprecated |
||||
417 | */ |
||||
418 | private static function assertValidPattern(string $element): void |
||||
419 | { |
||||
420 | if (1 !== native_preg_match('/^(([\p{L}_]+\\\\)+)?[\p{L}_]*\*$/u', $element)) { |
||||
421 | throw new InvalidArgumentException( |
||||
422 | sprintf( |
||||
0 ignored issues
–
show
The function
Safe\sprintf() has been deprecated: The Safe version of this function is no longer needed in PHP 8.0+
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
423 | 'Invalid whitelist pattern "%s".', |
||||
424 | $element, |
||||
425 | ), |
||||
426 | ); |
||||
427 | } |
||||
428 | } |
||||
429 | |||||
430 | /** |
||||
431 | * @deprecated |
||||
432 | */ |
||||
433 | private static function lowerCaseConstantName(string $name): string |
||||
434 | { |
||||
435 | $parts = explode('\\', $name); |
||||
436 | |||||
437 | $lastPart = array_pop($parts); |
||||
438 | |||||
439 | $parts = array_map('strtolower', $parts); |
||||
440 | |||||
441 | $parts[] = $lastPart; |
||||
442 | |||||
443 | return implode('\\', $parts); |
||||
444 | } |
||||
445 | } |
||||
446 |