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

Localization_Editor::hasAppLocales()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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