Total Complexity | 52 |
Total Lines | 605 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like Localization_Editor_Template_PageScaffold 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.
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 Localization_Editor_Template_PageScaffold, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class Localization_Editor_Template_PageScaffold |
||
31 | { |
||
32 | /** |
||
33 | * @var Localization_Editor |
||
34 | */private $editor; |
||
35 | |||
36 | public function __construct(Localization_Editor $editor) |
||
37 | { |
||
38 | $this->editor = $editor; |
||
39 | } |
||
40 | |||
41 | /** |
||
42 | * @return string |
||
43 | * @throws OutputBuffering_Exception |
||
44 | */ |
||
45 | public function render() : string |
||
46 | { |
||
47 | OutputBuffering::start(); |
||
48 | |||
49 | ?> |
||
50 | <!doctype html> |
||
51 | <html lang="en"> |
||
52 | <head> |
||
53 | <meta charset="utf-8"> |
||
54 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
||
55 | <meta name="description" content=""> |
||
56 | <title><?php echo $this->editor->getAppName() ?></title> |
||
57 | <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> |
||
58 | <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> |
||
59 | <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> |
||
60 | <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> |
||
61 | <script src="https://kit.fontawesome.com/54212b9b2b.js" crossorigin="anonymous"></script> |
||
62 | <script><?php echo $this->getJavascript() ?></script> |
||
63 | <style><?php echo $this->getCSS() ?></style> |
||
64 | </head> |
||
65 | <body> |
||
66 | <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"> |
||
67 | <a class="navbar-brand" href="<?php echo $this->editor->getURL() ?>"><?php echo $this->editor->getAppName() ?></a> |
||
68 | <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation"> |
||
69 | <span class="navbar-toggler-icon"></span> |
||
70 | </button> |
||
71 | <div class="collapse navbar-collapse" id="navbarsExampleDefault"> |
||
72 | <?php $this->renderAppLocales(); ?> |
||
73 | <?php |
||
74 | $backURL = $this->editor->getBackURL(); |
||
75 | if(!empty($backURL)) |
||
76 | { |
||
77 | ?> |
||
78 | <a href="<?php echo $backURL ?>" class="btn btn-light btn-sm"> |
||
79 | <i class="fas fa-arrow-circle-left"></i> |
||
80 | <?php echo $this->editor->getBackButtonLabel(); ?> |
||
81 | </a> |
||
82 | <?php |
||
83 | } |
||
84 | ?> |
||
85 | </div> |
||
86 | </nav> |
||
87 | <main role="main" class="container"> |
||
88 | <div> |
||
89 | <?php $this->renderPageContent(); ?> |
||
90 | </div> |
||
91 | </main> |
||
92 | </body> |
||
93 | </html> |
||
94 | <?php |
||
95 | |||
96 | return OutputBuffering::get(); |
||
97 | } |
||
98 | |||
99 | protected function renderScannerWarningsList() : void |
||
100 | { |
||
101 | ?> |
||
102 | <h1><?php pt('Warnings') ?></h1> |
||
103 | <p class="abstract"> |
||
104 | <?php |
||
105 | pts('The following shows all texts where the system decided that they cannot be translated.'); |
||
106 | ?> |
||
107 | </p> |
||
108 | <dl> |
||
109 | <?php |
||
110 | $warnings = $this->editor->getScannerWarnings(); |
||
111 | |||
112 | foreach($warnings as $warning) |
||
113 | { |
||
114 | ?> |
||
115 | <dt><?php echo FileHelper::relativizePathByDepth($warning->getFile(), 3) ?>:<?php echo $warning->getLine() ?></dt> |
||
116 | <dd><?php echo $warning->getMessage() ?></dd> |
||
117 | <?php |
||
118 | } |
||
119 | |||
120 | ?> |
||
121 | </dl> |
||
122 | <?php |
||
123 | } |
||
124 | |||
125 | protected function displayList() : void |
||
126 | { |
||
127 | $strings = $this->editor->getFilteredStrings(); |
||
128 | |||
129 | if(empty($strings)) |
||
130 | { |
||
131 | ?> |
||
132 | <div class="alert alert-info"> |
||
133 | <?php pt('No matching strings found.') ?> |
||
134 | </div> |
||
135 | <?php |
||
136 | |||
137 | return; |
||
138 | } |
||
139 | |||
140 | $total = count($strings); |
||
141 | $page = $this->editor->getPageNumber(); |
||
142 | $pager = new PaginationHelper($total, $this->editor->getAmountPerPage(), $page); |
||
143 | |||
144 | $keep = array_slice($strings, $pager->getOffsetStart(), $this->editor->getAmountPerPage()); |
||
145 | |||
146 | ?> |
||
147 | <form method="post"> |
||
148 | <div class="form-hiddens"> |
||
149 | <?php |
||
150 | $params = $this-> editor->getRequestParams(); |
||
151 | foreach($params as $name => $value) { |
||
152 | ?> |
||
153 | <input type="hidden" name="<?php echo $name ?>" value="<?php echo $value ?>"> |
||
154 | <?php |
||
155 | } |
||
156 | ?> |
||
157 | </div> |
||
158 | <table class="table table-hover"> |
||
159 | <thead> |
||
160 | <tr> |
||
161 | <th><?php pt('Text') ?></th> |
||
162 | <th class="align-center"><?php pt('Translated?') ?></th> |
||
163 | <th class="align-center"><?php pt('Location') ?></th> |
||
164 | <th class="align-right"><?php pt('Sources') ?></th> |
||
165 | </tr> |
||
166 | </thead> |
||
167 | <tbody> |
||
168 | <?php |
||
169 | foreach($keep as $string) |
||
170 | { |
||
171 | $this->renderTextEditorEntry($string); |
||
172 | } |
||
173 | ?> |
||
174 | </tbody> |
||
175 | </table> |
||
176 | <?php |
||
177 | if($pager->hasPages()) |
||
178 | { |
||
179 | $prevUrl = $this->editor->getPaginationURL($pager->getPreviousPage()); |
||
180 | $nextUrl = $this->editor->getPaginationURL($pager->getNextPage()); |
||
181 | |||
182 | ?> |
||
183 | <nav aria-label="<?php pt('Navigate available pages of texts.') ?>"> |
||
184 | <ul class="pagination"> |
||
185 | <li class="page-item"> |
||
186 | <a class="page-link" href="<?php echo $prevUrl ?>"> |
||
187 | <i class="fa fa-arrow-left"></i> |
||
188 | </a> |
||
189 | </li> |
||
190 | <?php |
||
191 | $numbers = $pager->getPageNumbers(); |
||
192 | foreach($numbers as $number) |
||
193 | { |
||
194 | $url = $this->editor->getPaginationURL($number); |
||
195 | |||
196 | ?> |
||
197 | <li class="page-item <?php if($pager->isCurrentPage($number)) { echo 'active'; } ?>"> |
||
198 | <a class="page-link" href="<?php echo $url ?>"> |
||
199 | <?php echo $number ?> |
||
200 | </a> |
||
201 | </li> |
||
202 | <?php |
||
203 | } |
||
204 | ?> |
||
205 | <li class="page-item"> |
||
206 | <a class="page-link" href="<?php echo $nextUrl ?>"> |
||
207 | <i class="fa fa-arrow-right"></i> |
||
208 | </a> |
||
209 | </li> |
||
210 | </ul> |
||
211 | </nav> |
||
212 | <?php |
||
213 | } |
||
214 | ?> |
||
215 | <br> |
||
216 | <p> |
||
217 | <button type="submit" name="<?php echo $this->editor->getVarName('save') ?>" value="yes" class="btn btn-primary"> |
||
218 | <i class="fas fa-save"></i> |
||
219 | <?php pt('Save now') ?> |
||
220 | </button> |
||
221 | </p> |
||
222 | </form> |
||
223 | |||
224 | <?php |
||
225 | } |
||
226 | |||
227 | protected function renderTextEditorEntry(Localization_Scanner_StringHash $string) : void |
||
228 | { |
||
229 | $hash = $string->getHash(); |
||
230 | $text = $string->getText(); |
||
231 | |||
232 | if($text===null) |
||
233 | { |
||
234 | throw new EditorException( |
||
235 | 'String hash has no text', |
||
236 | '', |
||
237 | Localization_Editor::ERROR_STRING_HASH_WITHOUT_TEXT |
||
238 | ); |
||
239 | } |
||
240 | |||
241 | $previewText = $string->getTranslatedText(); |
||
242 | if(empty($previewText)) { |
||
243 | $previewText = $text->getText(); |
||
244 | } |
||
245 | |||
246 | $shortText = $this->renderText($previewText, 50); |
||
247 | |||
248 | $files = $string->getFiles(); |
||
249 | $labelID = JSHelper::nextElementID(); |
||
250 | |||
251 | ?> |
||
252 | <tr class="string-entry inactive" onclick="Editor.Toggle('<?php echo $hash ?>')" data-hash="<?php echo $hash ?>"> |
||
253 | <td class="string-text"><?php echo $shortText ?></td> |
||
254 | <td class="align-center string-status"><?php echo $this->renderStatus($string) ?></td> |
||
255 | <td class="align-center"><?php echo $this->renderTypes($string) ?></td> |
||
256 | <td class="align-right"><?php echo $this->renderFileNames($string) ?></td> |
||
257 | </tr> |
||
258 | <tr class="string-form"> |
||
259 | <td colspan="4"> |
||
260 | <label for="<?php echo $labelID ?>"><?php pt('Native text:') ?></label> |
||
261 | <p class="native-text"><?php echo $this->renderText($text->getText()) ?></p> |
||
262 | <p> |
||
263 | <textarea rows="4" id="<?php echo $labelID ?>" class="form-control" name="<?php echo $this->editor->getVarName('strings') ?>[<?php echo $hash ?>]"><?php echo $string->getTranslatedText() ?></textarea> |
||
264 | </p> |
||
265 | <?php |
||
266 | $explanation = $text->getExplanation(); |
||
267 | if(!empty($explanation)) |
||
268 | { |
||
269 | ?> |
||
270 | <p> |
||
271 | <?php pt('Context information:') ?><br> |
||
272 | <span class="native-text"><?php echo $explanation ?></span> |
||
273 | </p> |
||
274 | <?php |
||
275 | } |
||
276 | ?> |
||
277 | <p> |
||
278 | <button type="button" class="btn btn-outline-primary btn-sm" onclick="Editor.Confirm('<?php echo $hash ?>')"> |
||
279 | <?php ptex('OK', 'Button') ?> |
||
280 | </button> |
||
281 | <button type="button" class="btn btn-outline-secondary btn-sm" onclick="Editor.Toggle('<?php echo $hash ?>')"> |
||
282 | <?php ptex('Cancel', 'Button') ?> |
||
283 | </button> |
||
284 | </p> |
||
285 | <div class="files-list"> |
||
286 | <p> |
||
287 | <?php |
||
288 | $totalFiles = count($files); |
||
289 | |||
290 | if($totalFiles == 1) |
||
291 | { |
||
292 | pt('Found in a single file:'); |
||
293 | } |
||
294 | else |
||
295 | { |
||
296 | pt('Found in %1$s files:', $totalFiles); |
||
297 | } |
||
298 | ?> |
||
299 | </p> |
||
300 | <div class="files-scroller"> |
||
301 | <ul> |
||
302 | <?php |
||
303 | $locations = $string->getStrings(); |
||
304 | |||
305 | foreach($locations as $location) |
||
306 | { |
||
307 | $file = $location->getSourceFile(); |
||
308 | $line = $location->getLine(); |
||
309 | |||
310 | $ext = FileHelper::getExtension($file); |
||
311 | |||
312 | if($ext == 'php') { |
||
313 | $icon = 'fab fa-php'; |
||
314 | } else if($ext == 'js') { |
||
315 | $icon = 'fab fa-js-square'; |
||
316 | } else { |
||
317 | $icon = 'fas fa-file-code'; |
||
318 | } |
||
319 | |||
320 | ?> |
||
321 | <li> |
||
322 | <i class="<?php echo $icon ?>"></i> |
||
323 | <?php echo $file ?><span class="line-number">:<?php echo $line ?></span> |
||
324 | </li> |
||
325 | <?php |
||
326 | } |
||
327 | ?> |
||
328 | </ul> |
||
329 | </div> |
||
330 | </div> |
||
331 | </td> |
||
332 | </tr> |
||
333 | <?php |
||
334 | } |
||
335 | |||
336 | protected function renderText(string $text, int $cutAt=0) : string |
||
337 | { |
||
338 | if(empty($text)) { |
||
339 | return ''; |
||
340 | } |
||
341 | |||
342 | if($cutAt > 0) { |
||
343 | $text = ConvertHelper::text_cut($text, $cutAt); |
||
344 | } |
||
345 | |||
346 | $text = htmlspecialchars($text); |
||
347 | |||
348 | $vars = $this->editor->detectVariables($text); |
||
349 | |||
350 | foreach($vars as $var) { |
||
351 | $text = str_replace($var, '<span class="placeholder">'.$var.'</span>', $text); |
||
352 | } |
||
353 | |||
354 | return $text; |
||
355 | } |
||
356 | |||
357 | protected function getJavascript() : string |
||
358 | { |
||
359 | return FileHelper::readContents($this->editor->getInstallPath().'/js/editor.js'); |
||
360 | } |
||
361 | |||
362 | protected function getCSS() : string |
||
363 | { |
||
364 | return FileHelper::readContents($this->editor->getInstallPath().'/css/editor.css'); |
||
365 | } |
||
366 | |||
367 | private function renderAppLocales() : void |
||
368 | { |
||
369 | $locales = $this->editor->getAppLocales(); |
||
370 | |||
371 | if (empty($this->appLocales)) |
||
372 | { |
||
373 | return; |
||
374 | } |
||
375 | |||
376 | $activeLocale = $this->editor->getActiveLocale(); |
||
377 | |||
378 | ?> |
||
379 | <ul class="navbar-nav mr-auto"> |
||
380 | <li class="nav-item dropdown"> |
||
381 | <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" |
||
382 | aria-haspopup="true" aria-expanded="false"> |
||
383 | <?php pt('Text sources') ?> |
||
384 | </a> |
||
385 | <div class="dropdown-menu" aria-labelledby="dropdown01"> |
||
386 | <?php $this->renderSourceSelection(); ?> |
||
387 | </div> |
||
388 | </li> |
||
389 | <li class="nav-item dropdown"> |
||
390 | <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" |
||
391 | aria-haspopup="true" aria-expanded="false"> |
||
392 | <?php echo $activeLocale->getLabel() ?> |
||
393 | </a> |
||
394 | <div class="dropdown-menu" aria-labelledby="dropdown01"> |
||
395 | <?php |
||
396 | foreach ($locales as $locale) |
||
397 | { |
||
398 | ?> |
||
399 | <a class="dropdown-item" href="<?php echo $this->editor->getLocaleURL($locale) ?>"> |
||
400 | <?php echo $locale->getLabel() ?> |
||
401 | </a> |
||
402 | <?php |
||
403 | } |
||
404 | ?> |
||
405 | </div> |
||
406 | </li> |
||
407 | <li class="nav-item"> |
||
408 | <a href="<?php echo $this->editor->getScanURL() ?>" |
||
409 | class="btn btn-light btn-sm" |
||
410 | title="<?php pt('Scan all source files to find translatable texts.') ?>" |
||
411 | data-toggle="tooltip"> |
||
412 | <i class="fa fa-refresh"></i> |
||
413 | <?php pt('Scan') ?> |
||
414 | </a> |
||
415 | </li> |
||
416 | <?php |
||
417 | $this->renderNavScannerWarnings(); |
||
418 | ?> |
||
419 | </ul> |
||
420 | <?php |
||
421 | } |
||
422 | |||
423 | private function renderNavScannerWarnings() : void |
||
424 | { |
||
425 | $scanner = $this->editor->getScanner(); |
||
426 | |||
427 | if (!$scanner->hasWarnings()) |
||
428 | { |
||
429 | return; |
||
430 | } |
||
431 | |||
432 | $title = sb() |
||
433 | ->t('The last scan for translatable texts reported warnings.') |
||
434 | ->t('Click for details.'); |
||
435 | |||
436 | ?> |
||
437 | <li class="nav-item"> |
||
438 | <a href="<?php echo $this->editor->getWarningsURL() ?>"> |
||
439 | <span class="badge badge-warning" |
||
440 | title="<?php echo $title ?>" |
||
441 | data-toggle="tooltip"> |
||
442 | <i class="fa fa-exclamation-triangle"></i> |
||
443 | <?php echo $scanner->countWarnings() ?> |
||
444 | </span> |
||
445 | </a> |
||
446 | </li> |
||
447 | <?php |
||
448 | } |
||
449 | |||
450 | private function renderSourceSelection() : void |
||
451 | { |
||
452 | $sources = $this->editor->getSources(); |
||
453 | $activeSourceID = $this->editor->getActiveSource()->getID(); |
||
454 | $scanner = $this->editor->getScanner(); |
||
455 | |||
456 | foreach ($sources as $source) |
||
457 | { |
||
458 | ?> |
||
459 | <a class="dropdown-item" href="<?php echo $this->editor->getSourceURL($source) ?>"> |
||
460 | <?php |
||
461 | if ($source->getID() === $activeSourceID) |
||
462 | { |
||
463 | ?> |
||
464 | <b><?php echo $source->getLabel() ?></b> |
||
465 | <?php |
||
466 | } |
||
467 | else |
||
468 | { |
||
469 | echo $source->getLabel(); |
||
470 | } |
||
471 | ?> |
||
472 | <?php |
||
473 | $untranslated = $source->getSourceScanner($scanner)->countUntranslated(); |
||
474 | if ($untranslated > 0) |
||
475 | { |
||
476 | $title = tex( |
||
477 | '%1$s texts have not been translated in this text source.', |
||
478 | 'Amount of texts', |
||
479 | $untranslated |
||
480 | ); |
||
481 | |||
482 | ?> |
||
483 | (<span class="text-danger" title="<?php echo $title ?>"> |
||
484 | <?php echo $untranslated ?> |
||
485 | </span>) |
||
486 | <?php |
||
487 | } |
||
488 | ?> |
||
489 | </a> |
||
490 | <?php |
||
491 | } |
||
492 | } |
||
493 | |||
494 | private function renderPageContent() : void |
||
495 | { |
||
496 | if (!$this->editor->hasAppLocales()) |
||
497 | { |
||
498 | $this->renderNoAppLocales(); |
||
499 | return; |
||
500 | } |
||
501 | |||
502 | if ($this->editor->isShowWarningsEnabled()) |
||
503 | { |
||
504 | $this->renderScannerWarningsList(); |
||
505 | return; |
||
506 | } |
||
507 | |||
508 | $this->renderStringsList(); |
||
509 | } |
||
510 | |||
511 | private function renderNoAppLocales() : void |
||
512 | { |
||
513 | ?> |
||
514 | <div class="alert alert-danger"> |
||
515 | <i class="fa fa-exclamation-triangle"></i> |
||
516 | <b><?php pt('Nothing to translate:') ?></b> |
||
517 | <?php pt('No application locales were added to translate to.') ?> |
||
518 | </div> |
||
519 | <?php |
||
520 | } |
||
521 | |||
522 | private function renderStringsList() : void |
||
523 | { |
||
524 | $activeSource = $this->editor->getActiveSource(); |
||
525 | $activeLocale = $this->editor->getActiveLocale(); |
||
526 | $scanner = $this->editor->getScanner(); |
||
527 | |||
528 | ?> |
||
529 | <h1><?php echo $activeSource->getLabel() ?></h1> |
||
530 | <?php $this->renderUIMessages(); ?> |
||
531 | <p> |
||
532 | <?php |
||
533 | pt( |
||
534 | 'You are translating to %1$s', |
||
535 | '<span class="badge badge-info">' . $activeLocale->getLabel() . '</span>' |
||
536 | ); |
||
537 | ?><br> |
||
538 | |||
539 | <?php pt('Found %1$s texts to translate.', $activeSource->getSourceScanner($scanner)->countUntranslated()) ?> |
||
540 | </p> |
||
541 | <br> |
||
542 | <?php |
||
543 | if (!$scanner->isScanAvailable()) |
||
544 | { |
||
545 | ?> |
||
546 | <div class="alert alert-primary" role="alert"> |
||
547 | <b><?php pt('No texts found:') ?></b> |
||
548 | <?php pt('The source folders have not been scanned yet.') ?> |
||
549 | </div> |
||
550 | <p> |
||
551 | <a href="<?php echo $this->editor->getScanURL() ?>" class="btn btn-primary"> |
||
552 | <i class="fa fa-refresh"></i> |
||
553 | <?php pt('Scan files now') ?> |
||
554 | </a> |
||
555 | </p> |
||
556 | <?php |
||
557 | } |
||
558 | else |
||
559 | { |
||
560 | echo $this->editor->getFilters()->renderForm(); |
||
561 | |||
562 | $this->displayList(); |
||
563 | } |
||
564 | } |
||
565 | |||
566 | protected function renderFileNames(Localization_Scanner_StringHash $hash) : string |
||
567 | { |
||
568 | $max = 2; |
||
569 | $total = $hash->countFiles(); |
||
570 | $keep = $hash->getFileNames(); |
||
571 | $keepTotal = count($keep); // with duplicate file names, this can be less than the file total |
||
572 | |||
573 | // add a counter of the additional files if the total |
||
574 | // is higher than the maximum to show |
||
575 | if($total > $max) |
||
576 | { |
||
577 | $length = $max; |
||
578 | if($length > $keepTotal) { |
||
579 | $length = $keepTotal; |
||
580 | } |
||
581 | |||
582 | $keep = array_slice($keep, 0, $length); |
||
583 | $keep[] = '+'.($total - $length); |
||
584 | } |
||
585 | |||
586 | return implode(', ', $keep); |
||
587 | } |
||
588 | |||
589 | protected function renderStatus(Localization_Scanner_StringHash $hash) : string |
||
590 | { |
||
591 | if($hash->isTranslated()) { |
||
592 | return '<i class="fa fa-check text-success"></i>'; |
||
593 | } |
||
594 | |||
595 | return '<i class="fa fa-ban text-danger"></i>'; |
||
596 | } |
||
597 | |||
598 | protected function renderTypes(Localization_Scanner_StringHash $hash) : string |
||
611 | } |
||
612 | |||
613 | private function renderUIMessages() : void |
||
614 | { |
||
615 | if (empty($_SESSION['localization_messages'])) |
||
616 | { |
||
617 | return; |
||
618 | } |
||
619 | |||
620 | foreach ($_SESSION['localization_messages'] as $def) |
||
621 | { |
||
622 | ?> |
||
635 | } |
||
636 | } |
||
637 |