Passed
Push — master ( 4a5f5f...d4ccbb )
by Sebastian
03:14
created

Localization_Editor::displayList()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 96
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 7
eloc 68
c 1
b 1
f 0
nc 9
nop 0
dl 0
loc 96
rs 7.7648

How to fix   Long Method   

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
 * 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\OutputBuffering;use AppUtils\ConvertHelper;
15
use AppUtils\PaginationHelper;
16
use AppUtils\Traits_Optionable;
17
use AppUtils\Interface_Optionable;
18
use AppUtils\FileHelper;
19
use AppUtils\Request;
20
21
/**
22
 * User Interface handler for editing localization files.
23
 *
24
 * @package Localization
25
 * @subpackage Editor
26
 * @author Sebastian Mordziol <[email protected]>
27
 */
28
class Localization_Editor implements Interface_Optionable
29
{
30
    use Traits_Optionable;
31
    
32
    const MESSAGE_INFO = 'info';
33
    const MESSAGE_ERROR = 'danger';
34
    const MESSAGE_WARNING = 'warning';
35
    const MESSAGE_SUCCESS = 'success';
36
    
37
    const ERROR_NO_SOURCES_AVAILABLE = 40001;
38
    const ERROR_LOCAL_PATH_NOT_FOUND = 40002;
39
40
   /**
41
    * @var string
42
    */
43
    protected $installPath;
44
    
45
   /**
46
    * @var Localization_Source[]
47
    */
48
    protected $sources;
49
    
50
   /**
51
    * @var Request
52
    */
53
    protected $request;
54
    
55
   /**
56
    * @var Localization_Source
57
    */
58
    protected $activeSource;
59
    
60
   /**
61
    * @var Localization_Scanner
62
    */
63
    protected $scanner;
64
    
65
   /**
66
    * @var Localization_Locale[]
67
    */
68
    protected $appLocales = array();
69
    
70
   /**
71
    * @var Localization_Locale
72
    */
73
    protected $activeAppLocale;
74
    
75
   /**
76
    * @var Localization_Editor_Filters
77
    */
78
    protected $filters;
79
80
   /**
81
    * @var array<string,string>
82
    */
83
    protected $requestParams = array();
84
    
85
   /**
86
    * @var string
87
    */
88
    protected $varPrefix = 'applocalize_';
89
90
    /**
91
     * @var int
92
     */
93
    protected $perPage = 20;
94
95
    /**
96
     * @throws Localization_Exception
97
     * @see \AppLocalize\Localization_Editor::ERROR_LOCAL_PATH_NOT_FOUND
98
     */
99
    public function __construct()
100
    {
101
        $path = realpath(__DIR__.'/../');
102
        if($path === false)
103
        {
104
            throw new Localization_Exception(
105
                'Local path not found',
106
                sprintf(
107
                    'Could not get the parent folder\'s real path from [%s].',
108
                    __DIR__
109
                ),
110
                self::ERROR_LOCAL_PATH_NOT_FOUND
111
            );
112
        }
113
114
        $this->installPath = $path;
115
        $this->request = new Request();
116
        $this->scanner = Localization::createScanner();
117
        $this->scanner->load();
118
119
        $this->initSession();
120
        $this->initAppLocales();
121
    }
122
    
123
    public function getRequest() : Request
124
    {
125
        return $this->request;
126
    }
127
    
128
   /**
129
    * Adds a request parameter that will be persisted in all URLs
130
    * within the editor. This can be used when integrating the
131
    * editor in an existing page that needs specific request params.
132
    * 
133
    * @param string $name
134
    * @param string $value
135
    * @return Localization_Editor
136
    */
137
    public function addRequestParam(string $name, string $value) : Localization_Editor
138
    {
139
        $this->requestParams[$name] = $value;
140
        return $this;
141
    }
142
    
143
    public function getActiveLocale() : Localization_Locale
144
    {
145
        return $this->activeAppLocale;
146
    }
147
    
148
    public function getActiveSource() : Localization_Source 
149
    {
150
        return $this->activeSource;
151
    }
152
    
153
    protected function initSession() : void
154
    {
155
        if(session_status() != PHP_SESSION_ACTIVE) {
156
            session_start();
157
        }
158
        
159
        if(!isset($_SESSION['localization_messages'])) {
160
            $_SESSION['localization_messages'] = array();
161
        }
162
    }
163
    
164
    public function getVarName(string $name) : string
165
    {
166
        return $this->varPrefix.$name;
167
    }
168
169
    /**
170
     * @throws Localization_Exception
171
     */
172
    protected function initSources() : void
173
    {
174
        $this->sources = Localization::getSources();
175
        
176
        if(empty($this->sources)) 
177
        {
178
            throw new Localization_Exception(
179
                'Cannot start editor: no sources defined.',
180
                null,
181
                self::ERROR_NO_SOURCES_AVAILABLE
182
            );
183
        }
184
        
185
        $activeID = $this->request->registerParam($this->getVarName('source'))->setEnum(Localization::getSourceIDs())->get();
186
        if(empty($activeID)) {
187
            $activeID = $this->getDefaultSourceID();
188
        }
189
        
190
        $this->activeSource = Localization::getSourceByID($activeID);
191
    }
192
    
193
    protected function getDefaultSourceID() : string
194
    {
195
        $default = $this->getOption('default-source');
196
        if(!empty($default) && Localization::sourceAliasExists($default)) {
197
            return Localization::getSourceByAlias($default)->getID();
198
        }
199
        
200
        return $this->sources[0]->getID();
201
    }
202
    
203
    protected function initAppLocales() : void
204
    {
205
        $names = array();
206
        
207
        $locales = Localization::getAppLocales();
208
        foreach($locales as $locale) {
209
            if(!$locale->isNative()) {
210
                $this->appLocales[] = $locale;
211
                $names[] = $locale->getName();
212
            }
213
        }
214
        
215
        // use the default locale if no other is available.
216
        if(empty($names)) {
217
            $this->activeAppLocale = Localization::getAppLocale();
218
            return;
219
        }
220
       
221
        $activeID = $this->request->registerParam($this->getVarName('locale'))->setEnum($names)->get();
222
        if(empty($activeID)) {
223
            $activeID = $this->appLocales[0]->getName();
224
        }
225
        
226
        $this->activeAppLocale = Localization::getAppLocaleByName($activeID);
227
        
228
        Localization::selectAppLocale($activeID);
229
    }
230
    
231
    protected function handleActions() : void
232
    {
233
        $this->initSources();
234
        
235
        $this->filters = new Localization_Editor_Filters($this);
236
        
237
        if($this->request->getBool($this->getVarName('scan'))) 
238
        {
239
            $this->executeScan();
240
        } 
241
        else if($this->request->getBool($this->getVarName('save'))) 
242
        {
243
            $this->executeSave();
244
        }
245
    }
246
    
247
    public function getScanner() : Localization_Scanner
248
    {
249
        return $this->scanner;
250
    }
251
252
    /**
253
     * @return string
254
     * @throws Localization_Exception
255
     * @see \AppLocalize\Localization_Editor::ERROR_RENDERING_FAILED
256
     */
257
    public function render() : string
258
    {
259
        $this->handleActions();
260
        
261
        $appName = $this->getAppName();
262
        
263
        OutputBuffering::start();
264
        
265
?><!doctype html>
266
<html lang="en">
267
	<head>
268
        <meta charset="utf-8">
269
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
270
        <meta name="description" content="">
271
        <title><?php echo $appName ?></title>
272
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
273
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
274
        <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">
275
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
276
        <script src="https://kit.fontawesome.com/54212b9b2b.js" crossorigin="anonymous"></script>
277
        <script><?php echo $this->getJavascript() ?></script>
278
        <style><?php echo $this->getCSS() ?></style>
279
	</head>
280
	<body>
281
        <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
282
            <a class="navbar-brand" href="<?php echo $this->getURL() ?>"><?php echo $appName ?></a>
283
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
284
                <span class="navbar-toggler-icon"></span>
285
            </button>
286
            <div class="collapse navbar-collapse" id="navbarsExampleDefault">
287
                <?php 
288
                    if(!empty($this->appLocales))
289
                    {
290
                        ?>
291
                            <ul class="navbar-nav mr-auto">
292
                        		<li class="nav-item dropdown">
293
                                    <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
294
                                    	<?php pt('Text sources') ?>
295
                                	</a>
296
                                    <div class="dropdown-menu" aria-labelledby="dropdown01">
297
                                    	<?php 
298
                                    	    foreach($this->sources as $source)
299
                                    	    {
300
                                    	       ?>
301
                                        			<a class="dropdown-item" href="<?php echo $this->getSourceURL($source) ?>">
302
                                        				<?php 
303
                                            				if($source->getID() === $this->activeSource->getID()) 
304
                                            				{
305
                                            				    ?>
306
                                            				    	<b><?php echo $source->getLabel() ?></b>
307
                                        				    	<?php 
308
                                            				}
309
                                            				else
310
                                            				{
311
                                            				    echo $source->getLabel();
312
                                            				}
313
                                        				?>
314
                                        				<?php
315
                                        				    $untranslated = $source->countUntranslated($this->scanner);
316
                                        				    if($untranslated > 0) {
317
                                        				        ?>
318
                                        				        	(<span class="text-danger" title="<?php ptex('%1$s texts have not been translated in this text source.', 'Amount of texts', $untranslated) ?>"><?php echo $untranslated ?></span>)
319
                                				            	<?php 
320
                                        				    }
321
                                    				    ?>
322
                                    				</a>
323
                                    			<?php 
324
                                    	    }
325
                                	    ?>
326
                                    </div>
327
                                </li>
328
                                <li class="nav-item dropdown">
329
                                    <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
330
                                    	<?php echo $this->activeAppLocale->getLabel() ?>
331
                                	</a>
332
                                    <div class="dropdown-menu" aria-labelledby="dropdown01">
333
                                    	<?php 
334
                                    	    foreach($this->appLocales as $locale)
335
                                    	    {
336
                                    	       ?>
337
                                        			<a class="dropdown-item" href="<?php echo $this->getLocaleURL($locale) ?>">
338
                                        				<?php echo $locale->getLabel() ?>
339
                                    				</a>
340
                                    			<?php 
341
                                    	    }
342
                                	    ?>
343
                                    </div>
344
                                </li>
345
                                <li class="nav-item">
346
                    				<a href="<?php echo $this->getScanURL() ?>" class="btn btn-light btn-sm" title="<?php pt('Scan all source files to find translatable texts.') ?>" data-toggle="tooltip">
347
                                    	<i class="fa fa-refresh"></i>
348
                                    	<?php pt('Scan') ?>
349
                                    </a>
350
                    			</li>
351
                    			<?php 
352
                        			if($this->scanner->hasWarnings()) {
353
                        			    ?>
354
                        			    	<li class="nav-item">
355
                        			    		<a href="<?php echo $this->getWarningsURL() ?>">
356
                            			    		<span class="badge badge-warning" title="<?php pts('The last scan for translatable texts reported warnings.'); pts('Click for details.'); ?>" data-toggle="tooltip">
357
                            			    			<i class="fa fa-exclamation-triangle"></i>
358
                            			    			<?php echo $this->scanner->countWarnings() ?>
359
                            			    		</span>
360
                        			    		</a>
361
                        			    	</li>
362
                        			    <?php 
363
                        			}
364
                    			?>
365
                            </ul>
366
                        <?php 
367
                    }
368
                ?>
369
                <?php 
370
                    $backURL = $this->getOption('back-url');
371
                    if(!empty($backURL)) 
372
                    {
373
                        ?>
374
                            <a href="<?php echo $backURL ?>" class="btn btn-light btn-sm">
375
                            	<i class="fas fa-arrow-circle-left"></i>
376
                            	<?php echo $this->getOption('back-label'); ?>
377
                        	</a>
378
                    	<?php 
379
                    }
380
            	?>
381
    		</div>
382
		</nav>
383
		<main role="main" class="container">
384
			<div>
385
    			<?php 
386
    			    if(empty($this->appLocales))
387
    			    {
388
    			        ?>
389
    			        	<div class="alert alert-danger">
390
    			        		<i class="fa fa-exclamation-triangle"></i>
391
    			        		<b><?php pt('Nothing to translate:') ?></b>
392
    			        		<?php pt('No application locales were added to translate to.') ?>
393
    			        	</div>
394
    			        <?php 
395
    			    }
396
    			    else if($this->request->getBool($this->getVarName('warnings')))
397
    			    {
398
    			        echo $this->renderWarnings();
399
    			    }
400
    			    else
401
    			    {
402
    			        ?>
403
            				<h1><?php echo $this->activeSource->getLabel() ?></h1>
404
            				<?php 
405
                				if(!empty($_SESSION['localization_messages'])) 
406
                				{
407
                				    foreach($_SESSION['localization_messages'] as $def)
408
                				    {
409
                				        ?>
410
                				        	<div class="alert alert-<?php echo $def['type'] ?>" role="alert">
411
                                        		<?php echo $def['text'] ?>
412
                                        		<button type="button" class="close" data-dismiss="alert" aria-label="<?php pt('Close') ?>" title="<?php pt('Dismiss this message.') ?>" data-toggle="tooltip">
413
                									<span aria-hidden="true">&times;</span>
414
            									</button>
415
                                        	</div>
416
            				        	<?php 
417
                				    }
418
                				    
419
                				    // reset the messages after having displayed them
420
                				    $_SESSION['localization_messages'] = array();
421
                				}
422
            				?>
423
            				<p>
424
            					<?php 
425
            				        pt(
426
                					    'You are translating to %1$s', 
427
                					    '<span class="badge badge-info">'.
428
                					       $this->activeAppLocale->getLabel().
429
                				        '</span>'
430
                                    );
431
            				    ?><br>
432
            					<?php pt('Found %1$s texts to translate.', $this->activeSource->countUntranslated($this->scanner)) ?>
433
            				</p>
434
            				<br>
435
            				<?php
436
                				if(!$this->scanner->isScanAvailable()) 
437
                				{
438
                				    ?>
439
                				    	<div class="alert alert-primary" role="alert">
440
                                        	<b><?php pt('No texts found:') ?></b> 
441
                                        	<?php pt('The source folders have not been scanned yet.') ?>
442
                                        </div>
443
                                        <p>
444
                                            <a href="<?php echo $this->getScanURL() ?>" class="btn btn-primary">
445
                                            	<i class="fa fa-refresh"></i>
446
                                            	<?php pt('Scan files now') ?>
447
                                            </a>
448
                                        </p>
449
                				    <?php 
450
                				}
451
                				else
452
                				{
453
                				    echo $this->filters->renderForm();
454
455
                				    $this->displayList();
456
                				}
457
        				}
458
    				?>
459
			</div>
460
		</main>
461
	</body>
462
</html>
463
<?php
464
465
        return OutputBuffering::getClean();
466
    }
467
468
    protected function renderWarnings() : string
469
    {
470
        OutputBuffering::start();
471
472
        ?>
473
        	<h1><?php pt('Warnings') ?></h1>
474
        	<p class="abstract">
475
        		<?php 
476
        		    pts('The following shows all texts where the system decided that they cannot be translated.');
477
       		    ?>
478
        	</p>
479
        	<dl>
480
        		<?php 
481
        		    $warnings = $this->scanner->getWarnings();
482
        		    
483
        		    foreach($warnings as $warning)
484
        		    {
485
        		        ?>
486
        		        	<dt><?php echo FileHelper::relativizePathByDepth($warning->getFile(), 3) ?>:<?php echo $warning->getLine() ?></dt>
487
        		        	<dd><?php echo $warning->getMessage() ?></dd>
488
        		        <?php 
489
        		    }
490
        		        
491
        		?>
492
        	</dl>
493
    	<?php 
494
495
        return OutputBuffering::getClean();
496
    }
497
498
    /**
499
     * @return Localization_Scanner_StringHash[]
500
     */
501
    protected function getFilteredStrings() : array
502
    {
503
        $strings = $this->activeSource->getHashes($this->scanner);
504
        
505
        $result = array();
506
        
507
        foreach($strings as $string)
508
        {
509
            if($this->filters->isStringMatch($string)) {
510
                $result[] = $string;
511
            }
512
        }
513
514
        return $result;
515
    }
516
    
517
    public function getRequestParams() : array
518
    {
519
        $params = $this->requestParams;
520
        $params[$this->getVarName('locale')] = $this->activeAppLocale->getName();
521
        $params[$this->getVarName('source')] = $this->activeSource->getID();
522
        $params[$this->getVarName('page')] = $this->getPage();
523
524
        return $params;
525
    }
526
    
527
    protected function getPage() : int
528
    {
529
        return intval($this->request
530
            ->registerParam($this->getVarName('page'))
531
            ->setInteger()
532
            ->get(0)
533
        );
534
    }
535
    
536
    protected function displayList() : void
537
    {
538
        $strings = $this->getFilteredStrings();
539
        
540
        if(empty($strings))
541
        {
542
            ?>
543
            	<div class="alert alert-info">
544
            		<?php pt('No matching strings found.') ?>
545
            	</div>
546
            <?php 
547
            
548
            return;
549
        }
550
        
551
        $total = count($strings);
552
        $page = $this->getPage();
553
        $pager = new PaginationHelper($total, $this->perPage, $page);
554
        
555
        $keep = array_slice($strings, $pager->getOffsetStart(), $this->perPage);
556
        
557
        ?>
558
			<form method="post">
559
				<div class="form-hiddens">
560
					<?php 
561
    					$params = $this->getRequestParams();
562
    					foreach($params as $name => $value) {
563
    					    ?>
564
    					    	<input type="hidden" name="<?php echo $name ?>" value="<?php echo $value ?>">
565
    					    <?php 
566
    					}
567
					?>
568
				</div>
569
            	<table class="table table-hover">
570
    				<thead>
571
    					<tr>
572
    						<th><?php pt('Text') ?></th>
573
    						<th class="align-center"><?php pt('Translated?') ?></th>
574
    						<th class="align-center"><?php pt('Location') ?></th>
575
    						<th class="align-right"><?php pt('Sources') ?></th>
576
    					</tr>
577
    				</thead>
578
    				<tbody>
579
    					<?php 
580
    					    foreach($keep as $string)
581
    					    {
582
    					        $this->renderListEntry($string);
583
    					    }
584
    					?>
585
    				</tbody>
586
    			</table>
587
    			<?php 
588
        			if($pager->hasPages()) 
589
        			{
590
        			    $prevUrl = $this->getPaginationURL($pager->getPreviousPage());
591
        			    $nextUrl = $this->getPaginationURL($pager->getNextPage());
592
        			    
593
        			    ?>
594
        			    	<nav aria-label="<?php pt('Navigate available pages of texts.') ?>">
595
                                <ul class="pagination">
596
                                    <li class="page-item">
597
                                    	<a class="page-link" href="<?php echo $prevUrl ?>">
598
                                    		<i class="fa fa-arrow-left"></i>
599
                                		</a>
600
                            		</li>
601
                            		<?php 
602
                            		    $numbers = $pager->getPageNumbers();
603
                            		    foreach($numbers as $number) 
604
                            		    {
605
                            		        $url = $this->getPaginationURL($number);
606
                            		        
607
                            		        ?>
608
                            		        	<li class="page-item <?php if($pager->isCurrentPage($number)) { echo 'active'; } ?>">
609
                            		        		<a class="page-link" href="<?php echo $url ?>">
610
                            		        			<?php echo $number ?>
611
                        		        			</a>
612
                        		        		</li>
613
                            		        <?php 
614
                            		    }
615
                            		?>
616
                                    <li class="page-item">
617
                                    	<a class="page-link" href="<?php echo $nextUrl ?>">
618
                                    		<i class="fa fa-arrow-right"></i>
619
                                		</a>
620
                                	</li>
621
                                </ul>
622
                            </nav>
623
        			    <?php 
624
        			}
625
    			?>
626
				<br>
627
				<p>
628
					<button type="submit" name="<?php echo $this->getVarName('save') ?>" value="yes" class="btn btn-primary">
629
						<i class="fas fa-save"></i>
630
						<?php pt('Save now') ?>
631
					</button>
632
				</p>
633
			</form>
634
			
635
        <?php 
636
    }
637
    
638
    protected function getPaginationURL(int $page, array $params=array()) : string
639
    {
640
        $params[$this->getVarName('page')] = $page;
641
        
642
        return $this->getURL($params);
643
    }
644
    
645
    protected function renderListEntry(Localization_Scanner_StringHash $string) : void
646
    {
647
        $hash = $string->getHash();
648
        $text = $string->getText();
649
        
650
        $previewText = $string->getTranslatedText();
651
        if(empty($previewText)) {
652
            $previewText = $text->getText();
653
        }
654
        
655
        $shortText =  $this->renderText($previewText, 50);
656
        
657
        $files = $string->getFiles();
658
        
659
        ?>
660
        	<tr class="string-entry inactive" onclick="Editor.Toggle('<?php echo $hash ?>')" data-hash="<?php echo $hash ?>">
661
        		<td class="string-text"><?php echo $shortText ?></td>
662
        		<td class="align-center string-status"><?php echo $this->renderStatus($string) ?></td>
663
        		<td class="align-center"><?php echo $this->renderTypes($string) ?></td>
664
        		<td class="align-right"><?php echo $this->renderFileNames($string) ?></td>
665
        	</tr>
666
        	<tr class="string-form">
667
        		<td colspan="4">
668
        			<?php pt('Native text:') ?>
669
        			<p class="native-text"><?php echo $this->renderText($text->getText()) ?></p>
670
        			<p>
671
        				<textarea rows="4" class="form-control" name="<?php echo $this->getVarName('strings') ?>[<?php echo $hash ?>]"><?php echo $string->getTranslatedText() ?></textarea>
672
        			</p>
673
                    <?php
674
                        $explanation = $text->getExplanation();
675
                        if(!empty($explanation))
676
                        {
677
                            ?>
678
                            <p>
679
                                <?php pt('Context information:') ?><br>
680
                                <span class="native-text"><?php echo $explanation ?></span>
681
                            </p>
682
                            <?php
683
                        }
684
                    ?>
685
        			<p>
686
	        			<button type="button" class="btn btn-outline-primary btn-sm" onclick="Editor.Confirm('<?php echo $hash ?>')">
687
	        				<?php ptex('OK', 'Button') ?>
688
	        			</button>
689
	        			<button type="button" class="btn btn-outline-secondary btn-sm" onclick="Editor.Toggle('<?php echo $hash ?>')">
690
	        				<?php ptex('Cancel', 'Button') ?>
691
	        			</button>
692
        			</p>
693
        			<div class="files-list">
694
            			<p>
695
            				<?php 
696
            				    $totalFiles = count($files);
697
            				    
698
            				    if($totalFiles == 1)
699
            				    {
700
            				        pt('Found in a single file:');
701
            				    }
702
            				    else
703
            				    {
704
            				        pt('Found in %1$s files:', $totalFiles);
705
            				    }
706
    				        ?>
707
            			</p>
708
        				<div class="files-scroller">
709
                			<ul>
710
                				<?php 
711
                				    $locations = $string->getStrings();
712
                				    
713
                    				foreach($locations as $location) 
714
                    				{
715
                    				    $file = $location->getSourceFile();
716
                    				    $line = $location->getLine();
717
                    				    
718
                    				    $ext = FileHelper::getExtension($file);
719
                    				    
720
                    				    if($ext == 'php') {
721
                    				        $icon = 'fab fa-php';
722
                    				    } else if($ext == 'js') {
723
                    				        $icon = 'fab fa-js-square';
724
                    				    } else {
725
                    				        $icon = 'fas fa-file-code';
726
                    				    }
727
                    				    
728
                    				    ?>
729
                    				    	<li>
730
                    				    		<i class="<?php echo $icon ?>"></i>
731
                    				    		<?php echo $file ?><span class="line-number">:<?php echo $line ?></span>
732
                    				    	</li>
733
                    				    <?php 
734
                    				}
735
                				?>
736
                			</ul>
737
            			</div>
738
        			</div>
739
        		</td>
740
        	</tr>
741
        <?php 
742
    }
743
    
744
    protected function renderText(string $text, int $cutAt=0) : string
745
    {
746
        if(empty($text)) {
747
            return '';
748
        }
749
750
        if($cutAt > 0) {
751
            $text = ConvertHelper::text_cut($text, $cutAt);
752
        }
753
754
        $text = htmlspecialchars($text);
755
        
756
        $vars = $this->detectVariables($text);
757
        
758
        foreach($vars as $var) {
759
            $text = str_replace($var, '<span class="placeholder">'.$var.'</span>', $text);
760
        }
761
        
762
        return $text;
763
    }
764
    
765
    protected function detectVariables(string $string) : array
766
    {
767
        $result = array();
768
        preg_match_all('/%[0-9]+d|%s|%[0-9]+\$s/i', $string, $result, PREG_PATTERN_ORDER);
769
770
        if(isset($result[0]) && !empty($result[0])) {
771
            return $result[0];
772
        }
773
        
774
        return array();
775
    }
776
    
777
    protected function renderFileNames(Localization_Scanner_StringHash $hash) : string
778
    {
779
        $max = 2;
780
        $total = $hash->countFiles();
781
        $keep = $hash->getFileNames();
782
        $keepTotal = count($keep); // with duplicate file names, this can be less than the file total
783
        
784
        // add a counter of the additional files if the total
785
        // is higher than the maximum to show
786
        if($total > $max) 
787
        {
788
            $length = $max;
789
            if($length > $keepTotal) {
790
                $length = $keepTotal; 
791
            }
792
            
793
            $keep = array_slice($keep, 0, $length);
794
            $keep[] = '+'.($total - $length); 
795
        }
796
        
797
        $result = implode(', ', $keep);
798
        
799
        return $result;
800
    }
801
    
802
    public function display() : void
803
    {
804
        echo $this->render();
805
    }
806
    
807
    protected function getJavascript() : string
808
    {
809
        return FileHelper::readContents($this->installPath.'/js/editor.js');
810
    }
811
    
812
    protected function getCSS() : string
813
    {
814
        return FileHelper::readContents($this->installPath.'/css/editor.css');
815
    }
816
    
817
    public function getSourceURL(Localization_Source $source, array $params=array()) : string
818
    {
819
        $params[$this->getVarName('source')] = $source->getID();
820
        
821
        return $this->getURL($params);
822
    }
823
    
824
    public function getLocaleURL(Localization_Locale $locale, array $params=array()) : string
825
    {
826
        $params[$this->getVarName('locale')] = $locale->getName();
827
        
828
        return $this->getURL($params);
829
    }
830
    
831
    public function getScanURL() : string
832
    {
833
        return $this->getSourceURL($this->activeSource, array($this->getVarName('scan') => 'yes'));
834
    }
835
    
836
    public function getWarningsURL() : string
837
    {
838
        return $this->getSourceURL($this->activeSource, array($this->getVarName('warnings') => 'yes'));
839
    }
840
    
841
    public function getURL(array $params=array()) : string
842
    {
843
        $persist = $this->getRequestParams();
844
        
845
        foreach($persist as $name => $value) {
846
            if(!isset($params[$name])) {
847
                $params[$name] = $value;
848
            }
849
        }
850
        
851
        return '?'.http_build_query($params);
852
    }
853
854
    /**
855
     * @param string $url
856
     * @return never-returns
0 ignored issues
show
Documentation Bug introduced by
The doc comment never-returns at position 0 could not be parsed: Unknown type name 'never-returns' at position 0 in never-returns.
Loading history...
857
     */
858
    public function redirect(string $url) : void
859
    {
860
        header('Location:'.$url);
861
        exit;
862
    }
863
    
864
    protected function executeScan() : void
865
    {
866
        $this->scanner->scan();
867
868
        $this->addMessage(
869
            t('The source files have been analyzed successfully at %1$s.', date('H:i:s')),
870
            self::MESSAGE_SUCCESS
871
        );
872
        
873
        $this->redirect($this->getSourceURL($this->activeSource));
874
    }
875
    
876
    protected function executeSave() : void
877
    {
878
        $data = $_POST;
879
        
880
        $translator = Localization::getTranslator($this->activeAppLocale);
881
        
882
        $strings = $data[$this->getVarName('strings')];
883
        foreach($strings as $hash => $text) 
884
        {
885
            $text = trim($text);
886
            
887
            if(empty($text)) {
888
                continue;
889
            } 
890
            
891
            $translator->setTranslation($hash, $text);
892
        }
893
        
894
        $translator->save($this->activeSource, $this->scanner->getCollection());
895
        
896
        // refresh all the client files
897
        Localization::writeClientFiles(true);
898
        
899
        $this->addMessage(
900
            t('The texts have been updated successfully at %1$s.', date('H:i:s')),
901
            self::MESSAGE_SUCCESS
902
        );
903
        
904
        $this->redirect($this->getURL());
905
    }
906
    
907
    protected function renderStatus(Localization_Scanner_StringHash $hash) : string
908
    {
909
        if($hash->isTranslated()) {
910
            return '<i class="fa fa-check text-success"></i>';
911
        }        
912
        
913
        return '<i class="fa fa-ban text-danger"></i>';
914
    }
915
    
916
    protected function renderTypes(Localization_Scanner_StringHash $hash) : string
917
    {
918
        $types = array();
919
        
920
        if($hash->hasLanguageType('PHP')) {
921
            $types[] = t('Server');
922
        }
923
        
924
        if($hash->hasLanguageType('Javascript')) {
925
            $types[] = t('Client');
926
        }
927
        
928
        return implode(', ', $types);
929
    }
930
    
931
    protected function addMessage(string $message, string $type=self::MESSAGE_INFO) : void
932
    {
933
        $_SESSION['localization_messages'][] = array(
934
            'text' => $message,
935
            'type' => $type
936
        );
937
    }
938
939
    /**
940
     * @return array<string,string>
941
     */
942
    public function getDefaultOptions() : array
943
    {
944
        return array(
945
            'appname' => '',
946
            'default-source' => '',
947
            'back-url' => '',
948
            'back-label' => ''
949
        );
950
    }
951
    
952
   /**
953
    * Sets the application name shown in the main navigation
954
    * in the user interface.
955
    * 
956
    * @param string $name
957
    * @return Localization_Editor
958
    */
959
    public function setAppName(string $name) : Localization_Editor
960
    {
961
        $this->setOption('appname', $name);
962
        return $this;
963
    }
964
    
965
    public function getAppName() : string
966
    {
967
        $name = $this->getOption('appname');
968
        if(!empty($name)) {
969
            return $name;
970
        }
971
        
972
        return t('Localization editor');
973
    }
974
975
    /**
976
     * Selects the default source to use if none has been
977
     * explicitly selected.
978
     *
979
     * @param string $sourceID
980
     * @return Localization_Editor
981
     */
982
    public function selectDefaultSource(string $sourceID) : Localization_Editor
983
    {
984
        $this->setOption('default-source', $sourceID);
985
        return $this;
986
    }
987
    
988
   /**
989
    * Sets an URL that the translators can use to go back to
990
    * the main application, for example if it is integrated into
991
    * an existing application.
992
    * 
993
    * @param string $url The URL to use for the link
994
    * @param string $label Label of the link
995
    * @return Localization_Editor
996
    */
997
    public function setBackURL(string $url, string $label) : Localization_Editor
998
    {
999
        $this->setOption('back-url', $url);
1000
        $this->setOption('back-label', $label);
1001
        return $this;
1002
    }
1003
}
1004