Localization_Editor   F
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 532
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 1
Metric Value
eloc 171
c 8
b 1
f 1
dl 0
loc 532
rs 3.04
wmc 67

45 Methods

Rating   Name   Duplication   Size   Complexity  
A hasAppLocales() 0 3 1
A getPaginationURL() 0 5 1
A getVarName() 0 3 1
A addMessage() 0 5 1
A getWarningsURL() 0 3 1
A getFilters() 0 3 1
A getAppLocales() 0 3 1
A isShowWarningsEnabled() 0 3 1
A setBackURL() 0 5 1
A selectDefaultSource() 0 4 1
A getSources() 0 3 1
A getBackURL() 0 3 1
A getAmountPerPage() 0 3 1
A getDefaultSourceID() 0 8 3
A getInstallPath() 0 3 1
A detectVariables() 0 10 3
A getPageNumber() 0 6 1
A setAppName() 0 4 1
A getScannerWarnings() 0 3 1
A getStringsVariableName() 0 3 1
A initAppLocales() 0 26 5
A getDefaultOptions() 0 7 1
A initSession() 0 8 3
A initSources() 0 19 3
A getAppName() 0 8 2
A getSaveVariableName() 0 3 1
A getRequestParams() 0 8 1
A addRequestParam() 0 4 1
A getBackButtonLabel() 0 3 1
A getScanner() 0 3 1
A getActiveLocale() 0 3 1
A getActiveSource() 0 3 1
A getLocaleURL() 0 5 1
A getScanURL() 0 3 1
A getSourceURL() 0 5 1
A display() 0 3 1
A getRequest() 0 3 1
A __construct() 0 22 2
A render() 0 5 1
A getFilteredStrings() 0 14 3
A redirect() 0 4 1
A getURL() 0 11 3
A executeScan() 0 10 1
A handleActions() 0 13 3
A executeSave() 0 29 3

How to fix   Complexity   

Complex Class

Complex classes like Localization_Editor 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, 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 AppUtils\OutputBuffering_Exception;
15
use AppUtils\Traits_Optionable;
16
use AppUtils\Interface_Optionable;
17
use AppUtils\Request;
18
19
/**
20
 * User Interface handler for editing localization files.
21
 *
22
 * @package Localization
23
 * @subpackage Editor
24
 * @author Sebastian Mordziol <[email protected]>
25
 */
26
class Localization_Editor implements Interface_Optionable
0 ignored issues
show
Deprecated Code introduced by
The interface AppUtils\Interface_Optionable has been deprecated: Use {@see OptionableInterface} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

26
class Localization_Editor implements /** @scrutinizer ignore-deprecated */ Interface_Optionable

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
27
{
28
    use Traits_Optionable;
0 ignored issues
show
Deprecated Code introduced by
The trait AppUtils\Traits_Optionable has been deprecated: Use {@see OptionableTrait} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

28
    use /** @scrutinizer ignore-deprecated */ Traits_Optionable;

This trait has been deprecated. The supplier of the trait has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the trait will be removed and what other trait to use instead.

Loading history...
29
    
30
    const MESSAGE_INFO = 'info';
31
    const MESSAGE_ERROR = 'danger';
32
    const MESSAGE_WARNING = 'warning';
33
    const MESSAGE_SUCCESS = 'success';
34
    
35
    const ERROR_NO_SOURCES_AVAILABLE = 40001;
36
    const ERROR_LOCAL_PATH_NOT_FOUND = 40002;
37
    const ERROR_STRING_HASH_WITHOUT_TEXT = 40003;
38
    const VARIABLE_STRINGS = 'strings';
39
    const VARIABLE_SAVE = 'save';
40
    const VARIABLE_SCAN = 'scan';
41
    const VARIABLE_WARNINGS = 'warnings';
42
43
    /**
44
    * @var string
45
    */
46
    protected $installPath;
47
    
48
   /**
49
    * @var Localization_Source[]
50
    */
51
    protected $sources;
52
    
53
   /**
54
    * @var Request
55
    */
56
    protected $request;
57
    
58
   /**
59
    * @var Localization_Source
60
    */
61
    protected $activeSource;
62
    
63
   /**
64
    * @var Localization_Scanner
65
    */
66
    protected $scanner;
67
    
68
   /**
69
    * @var Localization_Locale[]
70
    */
71
    protected array $appLocales = array();
72
    
73
   /**
74
    * @var Localization_Locale
75
    */
76
    protected $activeAppLocale;
77
    
78
   /**
79
    * @var Localization_Editor_Filters
80
    */
81
    protected $filters;
82
83
   /**
84
    * @var array<string,string>
85
    */
86
    protected $requestParams = array();
87
    
88
   /**
89
    * @var string
90
    */
91
    protected $varPrefix = 'applocalize_';
92
93
    /**
94
     * @var int
95
     */
96
    protected $perPage = 20;
97
98
    /**
99
     * @throws Localization_Exception
100
     * @see \AppLocalize\Localization_Editor::ERROR_LOCAL_PATH_NOT_FOUND
101
     */
102
    public function __construct()
103
    {
104
        $path = realpath(__DIR__.'/../');
105
        if($path === false)
106
        {
107
            throw new Localization_Exception(
108
                'Local path not found',
109
                sprintf(
110
                    'Could not get the parent folder\'s real path from [%s].',
111
                    __DIR__
112
                ),
113
                self::ERROR_LOCAL_PATH_NOT_FOUND
114
            );
115
        }
116
117
        $this->installPath = $path;
118
        $this->request = new Request();
119
        $this->scanner = Localization::createScanner();
120
        $this->scanner->load();
121
122
        $this->initSession();
123
        $this->initAppLocales();
124
    }
125
    
126
    public function getRequest() : Request
127
    {
128
        return $this->request;
129
    }
130
    
131
   /**
132
    * Adds a request parameter that will be persisted in all URLs
133
    * within the editor. This can be used when integrating the
134
    * editor in an existing page that needs specific request params.
135
    * 
136
    * @param string $name
137
    * @param string $value
138
    * @return Localization_Editor
139
    */
140
    public function addRequestParam(string $name, string $value) : Localization_Editor
141
    {
142
        $this->requestParams[$name] = $value;
143
        return $this;
144
    }
145
    
146
    public function getActiveSource() : Localization_Source
147
    {
148
        return $this->activeSource;
149
    }
150
    
151
    protected function initSession() : void
152
    {
153
        if(session_status() != PHP_SESSION_ACTIVE) {
154
            session_start();
155
        }
156
        
157
        if(!isset($_SESSION['localization_messages'])) {
158
            $_SESSION['localization_messages'] = array();
159
        }
160
    }
161
    
162
    public function getVarName(string $name) : string
163
    {
164
        return $this->varPrefix.$name;
165
    }
166
167
    /**
168
     * @throws Localization_Exception
169
     */
170
    protected function initSources() : void
171
    {
172
        $this->sources = Localization::getSources();
173
        
174
        if(empty($this->sources)) 
175
        {
176
            throw new Localization_Exception(
177
                'Cannot start editor: no sources defined.',
178
                null,
179
                self::ERROR_NO_SOURCES_AVAILABLE
180
            );
181
        }
182
        
183
        $activeID = $this->request->registerParam($this->getVarName('source'))->setEnum(Localization::getSourceIDs())->get();
184
        if(empty($activeID)) {
185
            $activeID = $this->getDefaultSourceID();
186
        }
187
        
188
        $this->activeSource = Localization::getSourceByID($activeID);
189
    }
190
    
191
    protected function getDefaultSourceID() : string
192
    {
193
        $default = $this->getOption('default-source');
194
        if(!empty($default) && Localization::sourceAliasExists($default)) {
195
            return Localization::getSourceByAlias($default)->getID();
196
        }
197
        
198
        return $this->sources[0]->getID();
199
    }
200
    
201
    protected function initAppLocales() : void
202
    {
203
        $names = array();
204
        
205
        $locales = Localization::getAppLocales();
206
        foreach($locales as $locale) {
207
            if(!$locale->isNative()) {
208
                $this->appLocales[] = $locale;
209
                $names[] = $locale->getName();
210
            }
211
        }
212
213
        // use the default locale if no other is available.
214
        if(empty($names)) {
215
            $this->activeAppLocale = Localization::getAppLocale();
216
            return;
217
        }
218
       
219
        $activeID = $this->request->registerParam($this->getVarName('locale'))->setEnum($names)->get();
220
        if(empty($activeID)) {
221
            $activeID = $this->appLocales[0]->getName();
222
        }
223
        
224
        $this->activeAppLocale = Localization::getAppLocaleByName($activeID);
225
        
226
        Localization::selectAppLocale($activeID);
227
    }
228
229
    /**
230
     * @return Localization_Locale[]
231
     */
232
    public function getAppLocales() : array
233
    {
234
        return $this->appLocales;
235
    }
236
237
    /**
238
     * @return Localization_Source[]
239
     */
240
    public function getSources() : array
241
    {
242
        return $this->sources;
243
    }
244
245
    public function getBackURL() : string
246
    {
247
        return strval($this->getOption('back-url'));
248
    }
249
250
    public function getBackButtonLabel() : string
251
    {
252
        return strval($this->getOption('back-label'));
253
    }
254
255
    public function getSaveVariableName() : string
256
    {
257
        return $this->getVarName(self::VARIABLE_SAVE);
258
    }
259
260
    public function getStringsVariableName() : string
261
    {
262
        return $this->getVarName(self::VARIABLE_STRINGS);
263
    }
264
265
    protected function handleActions() : void
266
    {
267
        $this->initSources();
268
        
269
        $this->filters = new Localization_Editor_Filters($this);
270
        
271
        if($this->request->getBool($this->getVarName(self::VARIABLE_SCAN)))
272
        {
273
            $this->executeScan();
274
        } 
275
        else if($this->request->getBool($this->getSaveVariableName()))
276
        {
277
            $this->executeSave();
278
        }
279
    }
280
    
281
    public function getScanner() : Localization_Scanner
282
    {
283
        return $this->scanner;
284
    }
285
286
    /**
287
     * @return string
288
     * @throws OutputBuffering_Exception
289
     */
290
    public function render() : string
291
    {
292
        $this->handleActions();
293
        
294
        return (new Localization_Editor_Template_PageScaffold($this))->render();
295
    }
296
297
    /**
298
     * @return Localization_Scanner_StringsCollection_Warning[]
299
     */
300
    public function getScannerWarnings() : array
301
    {
302
        return $this->scanner->getWarnings();
303
    }
304
305
    public function hasAppLocales() : bool
306
    {
307
        return !empty($this->appLocales);
308
    }
309
310
    public function isShowWarningsEnabled() : bool
311
    {
312
        return $this->request->getBool($this->getVarName(self::VARIABLE_WARNINGS));
313
    }
314
315
    public function getFilters() : Localization_Editor_Filters
316
    {
317
        return $this->filters;
318
    }
319
320
    /**
321
     * @return Localization_Scanner_StringHash[]
322
     */
323
    public function getFilteredStrings() : array
324
    {
325
        $strings = $this->activeSource->getSourceScanner($this->scanner)->getHashes();
326
        
327
        $result = array();
328
        
329
        foreach($strings as $string)
330
        {
331
            if($this->filters->isStringMatch($string)) {
332
                $result[] = $string;
333
            }
334
        }
335
336
        return $result;
337
    }
338
    
339
    public function getRequestParams() : array
340
    {
341
        $params = $this->requestParams;
342
        $params[$this->getVarName('locale')] = $this->activeAppLocale->getName();
343
        $params[$this->getVarName('source')] = $this->activeSource->getID();
344
        $params[$this->getVarName('page')] = $this->getPageNumber();
345
346
        return $params;
347
    }
348
349
    public function getAmountPerPage() : int
350
    {
351
        return $this->perPage;
352
    }
353
    
354
    public function getPageNumber() : int
355
    {
356
        return intval($this->request
357
            ->registerParam($this->getVarName('page'))
358
            ->setInteger()
359
            ->get(0)
360
        );
361
    }
362
    
363
    public function getActiveLocale() : Localization_Locale
364
    {
365
        return $this->activeAppLocale;
366
    }
367
368
    public function getPaginationURL(int $page, array $params=array()) : string
369
    {
370
        $params[$this->getVarName('page')] = $page;
371
        
372
        return $this->getURL($params);
373
    }
374
    
375
    public function detectVariables(string $string) : array
376
    {
377
        $result = array();
378
        preg_match_all('/%[0-9]+d|%s|%[0-9]+\$s/i', $string, $result, PREG_PATTERN_ORDER);
379
380
        if(isset($result[0]) && !empty($result[0])) {
381
            return $result[0];
382
        }
383
        
384
        return array();
385
    }
386
    
387
    public function display() : void
388
    {
389
        echo $this->render();
390
    }
391
    
392
    public function getSourceURL(Localization_Source $source, array $params=array()) : string
393
    {
394
        $params[$this->getVarName('source')] = $source->getID();
395
        
396
        return $this->getURL($params);
397
    }
398
    
399
    public function getLocaleURL(Localization_Locale $locale, array $params=array()) : string
400
    {
401
        $params[$this->getVarName('locale')] = $locale->getName();
402
        
403
        return $this->getURL($params);
404
    }
405
    
406
    public function getScanURL() : string
407
    {
408
        return $this->getSourceURL($this->activeSource, array($this->getVarName(self::VARIABLE_SCAN) => 'yes'));
409
    }
410
    
411
    public function getWarningsURL() : string
412
    {
413
        return $this->getSourceURL($this->activeSource, array($this->getVarName(self::VARIABLE_WARNINGS) => 'yes'));
414
    }
415
    
416
    public function getURL(array $params=array()) : string
417
    {
418
        $persist = $this->getRequestParams();
419
        
420
        foreach($persist as $name => $value) {
421
            if(!isset($params[$name])) {
422
                $params[$name] = $value;
423
            }
424
        }
425
        
426
        return '?'.http_build_query($params);
427
    }
428
429
    /**
430
     * @param string $url
431
     * @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...
432
     */
433
    public function redirect(string $url) : void
434
    {
435
        header('Location:'.$url);
436
        exit;
437
    }
438
    
439
    protected function executeScan() : void
440
    {
441
        $this->scanner->scan();
442
443
        $this->addMessage(
444
            t('The source files have been analyzed successfully at %1$s.', date('H:i:s')),
445
            self::MESSAGE_SUCCESS
446
        );
447
        
448
        $this->redirect($this->getSourceURL($this->activeSource));
449
    }
450
    
451
    protected function executeSave() : void
452
    {
453
        $data = $_POST;
454
        
455
        $translator = Localization::getTranslator($this->activeAppLocale);
456
        
457
        $strings = $data[$this->getVarName(self::VARIABLE_STRINGS)];
458
        foreach($strings as $hash => $text) 
459
        {
460
            $text = trim($text);
461
            
462
            if(empty($text)) {
463
                continue;
464
            } 
465
            
466
            $translator->setTranslation($hash, $text);
467
        }
468
        
469
        $translator->save($this->activeSource, $this->scanner->getCollection());
470
        
471
        // refresh all the client files
472
        Localization::writeClientFiles(true);
473
        
474
        $this->addMessage(
475
            t('The texts have been updated successfully at %1$s.', date('H:i:s')),
476
            self::MESSAGE_SUCCESS
477
        );
478
        
479
        $this->redirect($this->getURL());
480
    }
481
    
482
    protected function addMessage(string $message, string $type=self::MESSAGE_INFO) : void
483
    {
484
        $_SESSION['localization_messages'][] = array(
485
            'text' => $message,
486
            'type' => $type
487
        );
488
    }
489
490
    /**
491
     * @return array<string,string>
492
     */
493
    public function getDefaultOptions() : array
494
    {
495
        return array(
496
            'appname' => '',
497
            'default-source' => '',
498
            'back-url' => '',
499
            'back-label' => ''
500
        );
501
    }
502
    
503
   /**
504
    * Sets the application name shown in the main navigation
505
    * in the user interface.
506
    * 
507
    * @param string $name
508
    * @return Localization_Editor
509
    */
510
    public function setAppName(string $name) : Localization_Editor
511
    {
512
        $this->setOption('appname', $name);
513
        return $this;
514
    }
515
    
516
    public function getAppName() : string
517
    {
518
        $name = $this->getOption('appname');
519
        if(!empty($name)) {
520
            return $name;
521
        }
522
        
523
        return t('Localization editor');
524
    }
525
526
    /**
527
     * Selects the default source to use if none has been
528
     * explicitly selected.
529
     *
530
     * @param string $sourceID
531
     * @return Localization_Editor
532
     */
533
    public function selectDefaultSource(string $sourceID) : Localization_Editor
534
    {
535
        $this->setOption('default-source', $sourceID);
536
        return $this;
537
    }
538
    
539
   /**
540
    * Sets an URL that the translators can use to go back to
541
    * the main application, for example if it is integrated into
542
    * an existing application.
543
    * 
544
    * @param string $url The URL to use for the link
545
    * @param string $label Label of the link
546
    * @return Localization_Editor
547
    */
548
    public function setBackURL(string $url, string $label) : Localization_Editor
549
    {
550
        $this->setOption('back-url', $url);
551
        $this->setOption('back-label', $label);
552
        return $this;
553
    }
554
555
    public function getInstallPath() : string
556
    {
557
        return $this->installPath;
558
    }
559
}
560