Passed
Push — master ( f58fec...1e5d9f )
by Sebastian
06:14
created

Localization_Editor   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 532
Duplicated Lines 0 %

Importance

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

45 Methods

Rating   Name   Duplication   Size   Complexity  
A hasAppLocales() 0 3 1
A getPaginationURL() 0 5 1
A getActiveLocale() 0 3 1
A getVarName() 0 3 1
A addMessage() 0 5 1
A getRequest() 0 3 1
A getWarningsURL() 0 3 1
A getActiveSource() 0 3 1
A getFilters() 0 3 1
A getAppLocales() 0 3 1
A isShowWarningsEnabled() 0 3 1
A __construct() 0 22 2
A setBackURL() 0 5 1
A selectDefaultSource() 0 4 1
A render() 0 5 1
A getSources() 0 3 1
A getLocaleURL() 0 5 1
A getBackURL() 0 3 1
A getAmountPerPage() 0 3 1
A getDefaultSourceID() 0 8 3
A getInstallPath() 0 3 1
A getScanURL() 0 3 1
A getFilteredStrings() 0 14 3
A redirect() 0 4 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 getURL() 0 11 3
A getSourceURL() 0 5 1
A initSession() 0 8 3
A initSources() 0 19 3
A getAppName() 0 8 2
A getSaveVariableName() 0 3 1
A executeScan() 0 10 1
A handleActions() 0 13 3
A getRequestParams() 0 8 1
A addRequestParam() 0 4 1
A display() 0 3 1
A executeSave() 0 29 3
A getBackButtonLabel() 0 3 1
A getScanner() 0 3 1

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
27
{
28
    use Traits_Optionable;
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 $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