Passed
Push — master ( 7e3913...f58fec )
by Sebastian
05:25
created

Localization_Editor_Template_PageScaffold   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 605
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 374
c 1
b 0
f 0
dl 0
loc 605
rs 7.44
wmc 52

18 Methods

Rating   Name   Duplication   Size   Complexity  
A renderTypes() 0 13 3
A renderPageContent() 0 15 3
B displayList() 0 96 7
A renderSourceSelection() 0 40 4
A renderNavScannerWarnings() 0 22 2
A renderText() 0 19 4
A renderStatus() 0 7 2
A renderNoAppLocales() 0 8 1
A renderStringsList() 0 41 2
A __construct() 0 3 1
A renderAppLocales() 0 53 3
B renderTextEditorEntry() 0 102 8
A renderUIMessages() 0 22 3
A renderScannerWarningsList() 0 23 2
A render() 0 52 2
A getJavascript() 0 3 1
A getCSS() 0 3 1
A renderFileNames() 0 21 3

How to fix   Complexity   

Complex Class

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
2
/**
3
 * File containing the {@link Localization_Editor} class.
4
 *
5
 * @package Localization
6
 * @subpackage Editor
7
 * @see Localization_Translator
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppLocalize;
13
14
use AppLocalize\Editor\EditorException;
15
use AppUtils\ConvertHelper;
16
use AppUtils\JSHelper;
17
use AppUtils\OutputBuffering;
18
use AppUtils\OutputBuffering_Exception;
19
use AppUtils\PaginationHelper;
20
use AppUtils\FileHelper;
21
use function AppUtils\sb;
22
23
/**
24
 * User Interface handler for editing localization files.
25
 *
26
 * @package Localization
27
 * @subpackage Editor
28
 * @author Sebastian Mordziol <[email protected]>
29
 */
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
599
    {
600
        $types = array();
601
602
        if($hash->hasLanguageType('PHP')) {
603
            $types[] = t('Server');
604
        }
605
606
        if($hash->hasLanguageType('Javascript')) {
607
            $types[] = t('Client');
608
        }
609
610
        return implode(', ', $types);
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
            ?>
623
            <div class="alert alert-<?php echo $def['type'] ?>" role="alert">
624
                <?php echo $def['text'] ?>
625
                <button type="button" class="close" data-dismiss="alert" aria-label="<?php pt('Close') ?>"
626
                        title="<?php pt('Dismiss this message.') ?>" data-toggle="tooltip">
627
                    <span aria-hidden="true">&times;</span>
628
                </button>
629
            </div>
630
            <?php
631
        }
632
633
        // reset the messages after having displayed them
634
        $_SESSION['localization_messages'] = array();
635
    }
636
}
637