Localization   F
last analyzed

Complexity

Total Complexity 124

Size/Duplication

Total Lines 1318
Duplicated Lines 0 %

Importance

Changes 14
Bugs 0 Features 1
Metric Value
eloc 331
dl 0
loc 1318
rs 2
c 14
b 0
f 1
wmc 124

76 Methods

Rating   Name   Duplication   Size   Complexity  
A setClientLibrariesFolder() 0 5 2
A getLocaleNameByNS() 0 3 1
A getSourcesGrouped() 0 18 3
A setClientLibrariesCacheKey() 0 5 2
A getContentCurrency() 0 3 1
A getClientLibrariesCacheKey() 0 3 1
A localeExistsInNS() 0 3 2
A isConfigured() 0 3 1
A getClientFolder() 0 5 1
A countAppLocales() 0 3 1
A countContentLocales() 0 3 1
A injectLocalesSelectorNS() 0 16 3
A getClientLibrariesFolder() 0 3 1
A isLocaleSupported() 0 3 1
A createLocale() 0 25 3
A writeClientFiles() 0 3 1
A getSources() 0 3 1
A createCountry() 0 3 1
A getAppLocaleName() 0 3 1
A contentLocaleExists() 0 3 1
A getTranslator() 0 19 3
A getContentLocaleByName() 0 3 1
A getLocalesByNS() 0 13 2
A getAppLocaleByName() 0 3 1
A isActiveAppLocale() 0 3 1
A getSupportedLocaleNames() 0 9 2
A configure() 0 12 2
A injectContentLocalesSelector() 0 3 1
A onCacheKeyChanged() 0 3 1
A injectAppLocalesSelector() 0 3 1
A getSelectedLocaleByNS() 0 15 2
A addEventListener() 0 16 2
A log() 0 2 1
A reset() 0 10 1
A selectLocaleByNS() 0 30 3
A getSourceByID() 0 15 3
A getVersion() 0 21 3
A addExcludeFile() 0 4 2
A addSourceFolder() 0 10 1
A addLocaleByNS() 0 18 3
A getCurrencyNS() 0 3 1
A addAppLocale() 0 3 1
A createGenerator() 0 7 2
A getAppLocaleNames() 0 3 1
A getLocaleNamesByNS() 0 9 1
A addExcludeFolder() 0 4 2
A sourceAliasExists() 0 10 3
A getLocaleByNameNS() 0 16 3
A getAppCurrency() 0 3 1
A getAppLocale() 0 3 1
A getSourceAliases() 0 9 2
A triggerEvent() 0 22 3
A selectContentLocale() 0 3 1
A createEditor() 0 5 1
A requireNamespace() 0 13 2
A getContentLocale() 0 3 1
A sourceExists() 0 10 3
A createCurrencies() 0 7 2
A onLocaleChanged() 0 3 1
A selectAppLocale() 0 3 1
A requireConfiguration() 0 26 4
A isActiveContentLocale() 0 3 1
A getSourceIDs() 0 9 2
A createScanner() 0 5 1
A getSourceByAlias() 0 15 3
A countLocalesByNS() 0 9 2
A getContentLocaleNames() 0 3 1
A createCountries() 0 7 2
A getAppLocales() 0 3 1
A getContentLocales() 0 3 1
A resolveEventClass() 0 15 2
A init() 0 24 2
A getContentLocaleName() 0 3 1
A appLocaleExists() 0 3 1
A addContentLocale() 0 3 1
A onClientFolderChanged() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Localization 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, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * File containing the {@link Localization} class.
4
 * @package Localization
5
 * @subpackage Core
6
 * @see Localization
7
 */
8
9
declare(strict_types=1);
10
11
namespace AppLocalize;
12
13
use AppLocalize\Localization\Countries\BaseCountry;
14
use AppLocalize\Localization\Countries\CountryCollection;
15
use AppLocalize\Localization\Currencies\CurrencyCollection;
16
use AppLocalize\Localization\Currencies\CurrencyInterface;
17
use AppLocalize\Localization\Event\LocaleChanged;
18
use AppUtils\ClassHelper;
19
use AppUtils\FileHelper;
20
use AppUtils\FileHelper\FileInfo;
21
use AppUtils\FileHelper_Exception;
22
use HTML_QuickForm2_Container;
23
use HTML_QuickForm2_Element_Select;
24
use Mistralys\ChangelogParser\ChangelogParser;
25
use Throwable;
26
27
/**
28
 * Localization handling collection for both the
29
 * application itself and its user contents.
30
 *
31
 * @package Localization
32
 * @subpackage Core
33
 * @author Sebastian Mordziol <[email protected]>
34
 */
35
class Localization
36
{
37
    const ERROR_UNKNOWN_CONTENT_LOCALE = 39001;
38
    const ERROR_UNKNOWN_APPLICATION_LOCALE = 39002;
39
    const ERROR_NO_STORAGE_FILE_SET = 39003;
40
    const ERROR_CONFIGURE_NOT_CALLED = 39004;
41
    const ERROR_NO_SOURCES_ADDED = 39005;
42
    const ERROR_NO_LOCALE_SELECTED_IN_NS = 39006;
43
    const ERROR_NO_LOCALES_IN_NAMESPACE = 39007;
44
    const ERROR_UNKNOWN_NAMESPACE = 39008;
45
    const ERROR_UNKNOWN_LOCALE_IN_NS = 39009;
46
    const ERROR_UNKNOWN_EVENT_NAME = 39010;
47
    const ERROR_LOCALE_NOT_FOUND = 39011;
48
    const ERROR_COUNTRY_NOT_FOUND = 39012;
49
    
50
    /**
51
     * The name of the default application locale, i.e. the
52
     * locale in which application textual content is written.
53
     *
54
     * @var string
55
     */
56
    const BUILTIN_LOCALE_NAME = 'en_UK';
57
58
    const NAMESPACE_APPLICATION = '__application';
59
    const NAMESPACE_CONTENT = '__content';
60
61
    public const EVENT_LOCALE_CHANGED = 'LocaleChanged';
62
    public const EVENT_CLIENT_FOLDER_CHANGED = 'ClientFolderChanged';
63
    public const EVENT_CACHE_KEY_CHANGED = 'CacheKeyChanged';
64
65
    /**
66
    * Collection of all locales by namespace (application, content, custom...). 
67
    *
68
    * @var array<string,array<string,Localization_Locale>>
69
    * @see Localization::addLocale()
70
    */
71
    protected static $locales = array();
72
73
    /**
74
     * @var boolean
75
     * @see Localization::init()
76
     */
77
    private static $initDone = false;
78
79
   /**
80
    * Path to the file in which the scanner results are stored.
81
    * @var string
82
    * @see Localization::configure()
83
    */
84
    protected static $storageFile = '';
85
    
86
   /**
87
    * Path to the folder into which the client libraries are written.
88
    * @var string
89
    * @see Localization::setClientLibrariesFolder()
90
    */
91
    protected static $clientFolder = '';
92
    
93
   /**
94
    * If this key changes, client libraries are refreshed.
95
    * @var string
96
    * @see Localization::setClientLibrariesCacheKey()
97
    */
98
    protected static $clientCacheKey = '';
99
    
100
   /**
101
    * Whether the configuration has been made.
102
    * @var bool
103
    * @see Localization::configure()
104
    */
105
    protected static $configured = false;
106
    
107
   /**
108
    * Stores event listener instances.
109
    * @var array
110
    */
111
    protected static array $listeners = array();
112
    
113
   /**
114
    * @var integer
115
    * @see Localization::addEventListener()
116
    */
117
    protected static $listenersCounter = 0;
118
    
119
   /**
120
    * @var Localization_Translator|NULL
121
    */
122
    protected static $translator;
123
    
124
   /**
125
    * Initializes the localization layer. This is done
126
    * automatically, and only once per request.
127
    * 
128
    * (Called at the end of this file)
129
    */
130
    public static function init() : void
131
    {
132
        if(self::$initDone) {
133
            return;
134
        }
135
136
        self::reset();
137
        
138
        $installFolder = realpath(__DIR__.'/../');
139
        
140
        // add the localization package's own sources,
141
        // so the bundled localized strings can
142
        // always be translated.
143
        Localization::addSourceFolder(
144
            'application-localization',
145
            'Application Localization Package',
146
            'Composer packages',
147
            $installFolder.'/localization',
148
            $installFolder.'/src'
149
        )
150
        ->excludeFiles(array('jtokenizer'))
151
        ->excludeFolder('css');
152
        
153
        self::$initDone = true;
154
    }
155
156
    /**
157
     * Retrieves all available application locales, as an indexed
158
     * array with locale objects sorted by locale label.
159
     *
160
     * @return Localization_Locale[]
161
     * @see getAppLocale()
162
     */
163
    public static function getAppLocales()
164
    {
165
        return self::getLocalesByNS(self::NAMESPACE_APPLICATION);
166
    }
167
    
168
   /**
169
    * Retrieves all locales in the specified namespace.
170
    * 
171
    * @param string $namespace
172
    * @return Localization_Locale[]
173
    */
174
    public static function getLocalesByNS(string $namespace)
175
    {
176
        if(isset(self::$locales[$namespace])) {
177
            return array_values(self::$locales[$namespace]);
178
        }
179
        
180
        throw new Localization_Exception(
181
            'No locales available in namespace',
182
            sprintf(
183
                'The namespace [%s] does not exist.',
184
                $namespace
185
            ),
186
            self::ERROR_NO_LOCALES_IN_NAMESPACE
187
        );
188
    }
189
    
190
   /**
191
    * Adds an application locale to use in the application.
192
    * 
193
    * @param string $localeName
194
    * @return Localization_Locale
195
    */
196
    public static function addAppLocale(string $localeName) : Localization_Locale
197
    {
198
        return self::addLocaleByNS($localeName, self::NAMESPACE_APPLICATION);
199
    }
200
    
201
   /**
202
    * Adds a content locale to use for content in the application.
203
    * 
204
    * @param string $localeName
205
    * @return Localization_Locale
206
    */
207
    public static function addContentLocale(string $localeName) : Localization_Locale
208
    {
209
        return self::addLocaleByNS($localeName, self::NAMESPACE_CONTENT);
210
    }
211
    
212
   /**
213
    * Adds a locale to the specified namespace.
214
    * 
215
    * @param string $localeName
216
    * @param string $namespace
217
    * @return Localization_Locale
218
    */
219
    public static function addLocaleByNS(string $localeName, string $namespace) : Localization_Locale
220
    {
221
        if(!isset(self::$locales[$namespace])) {
222
            self::$locales[$namespace] = array();
223
        }
224
        
225
        if(!isset(self::$locales[$namespace][$localeName])) 
226
        {
227
            self::$locales[$namespace][$localeName] = self::createLocale($localeName);
228
            
229
            // sort the locales on add: less resource intensive
230
            // than doing it on getting locales.
231
            uasort(self::$locales[$namespace], function(Localization_Locale $a, Localization_Locale $b) {
232
                return strnatcasecmp($a->getLabel(), $b->getLabel());
233
            });
234
        }
235
        
236
        return self::$locales[$namespace][$localeName];
237
    }
238
239
    /**
240
     * @param string $localeName
241
     * @return Localization_Locale
242
     *
243
     * @throws Localization_Exception
244
     * @see Localization::ERROR_LOCALE_NOT_FOUND
245
     */
246
    protected static function createLocale(string $localeName) : Localization_Locale
247
    {
248
        $class = '\AppLocalize\Locale\\'.$localeName;
249
250
        try
251
        {
252
            $locale = new $class();
253
254
            if ($locale instanceof Localization_Locale)
255
            {
256
                return $locale;
257
            }
258
        }
259
        catch (Throwable $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
260
        {
261
262
        }
263
264
        throw new Localization_Exception(
265
            'Locale not supported.',
266
            sprintf(
267
                'The locale class [%s] does not exist.',
268
                $localeName
269
            ),
270
            self::ERROR_LOCALE_NOT_FOUND
271
        );
272
    }
273
274
    private static ?CountryCollection $countries = null;
275
276
    /**
277
     * Retrieves the country collection instance to
278
     * access all available countries.
279
     *
280
     * @return CountryCollection
281
     */
282
    public static function createCountries() : CountryCollection
283
    {
284
        if(!isset(self::$countries)) {
285
            self::$countries = CountryCollection::getInstance();
286
        }
287
288
        return self::$countries;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::countries could return the type null which is incompatible with the type-hinted return AppLocalize\Localization...tries\CountryCollection. Consider adding an additional type-check to rule them out.
Loading history...
289
    }
290
291
    private static ?CurrencyCollection $currencies = null;
292
293
    /**
294
     * Retrieves the currency collection instance to
295
     * access all available currencies.
296
     *
297
     * @return CurrencyCollection
298
     */
299
    public static function createCurrencies() : CurrencyCollection
300
    {
301
        if(!isset(self::$currencies)) {
302
            self::$currencies = CurrencyCollection::getInstance();
303
        }
304
305
        return self::$currencies;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::currencies could return the type null which is incompatible with the type-hinted return AppLocalize\Localization...cies\CurrencyCollection. Consider adding an additional type-check to rule them out.
Loading history...
306
    }
307
308
    /**
309
     * Creates a new country object for the specified country, e.g. "uk".
310
     *
311
     * @param string $id
312
     * @return BaseCountry
313
     *
314
     * @deprecated Use the collection instead: {@see self::createCountries()}
315
     */
316
    public static function createCountry(string $id) : BaseCountry
317
    {
318
        return self::createCountries()->getByID($id);
319
    }
320
321
    /**
322
     * Retrieves the currency of the selected app locale.
323
     *
324
     * @return CurrencyInterface
325
     *
326
     * @throws Localization_Exception
327
     * @see Localization::ERROR_NO_LOCALE_SELECTED_IN_NS
328
     */
329
    public static function getAppCurrency() : CurrencyInterface
330
    {
331
        return self::getCurrencyNS(self::NAMESPACE_APPLICATION);
332
    }
333
334
    /**
335
     * Retrieves the currency of the selected content locale.
336
     *
337
     * @return CurrencyInterface
338
     *
339
     * @throws Localization_Exception
340
     * @see Localization::ERROR_NO_LOCALE_SELECTED_IN_NS
341
     */
342
    public static function getContentCurrency() : CurrencyInterface
343
    {
344
        return self::getCurrencyNS(self::NAMESPACE_CONTENT);
345
    }
346
347
    /**
348
     * Retrieves the currency of the selected locale in the specified namespace.
349
     *
350
     * @param string $namespace
351
     * @return CurrencyInterface
352
     *
353
     * @throws Localization_Exception
354
     * @see Localization::ERROR_NO_LOCALE_SELECTED_IN_NS
355
     */
356
    public static function getCurrencyNS(string $namespace) : CurrencyInterface
357
    {
358
        return self::getSelectedLocaleByNS($namespace)->getCurrency();
359
    }
360
    
361
    /**
362
     * Retrieves the selected application locale instance. 
363
     *
364
     * @return Localization_Locale
365
     */
366
    public static function getAppLocale() : Localization_Locale
367
    {
368
        return self::getSelectedLocaleByNS(self::NAMESPACE_APPLICATION);
369
    }
370
    
371
   /**
372
    * Retrieves the name of the selected application locale.
373
    * 
374
    * @return string
375
    */
376
    public static function getAppLocaleName() : string
377
    {
378
        return self::getLocaleNameByNS(self::NAMESPACE_APPLICATION);
379
    }
380
    
381
   /**
382
    * Retrieves the names of the available application locales.
383
    * @return string[]
384
    */
385
    public static function getAppLocaleNames() : array
386
    {
387
        return self::getLocaleNamesByNS(self::NAMESPACE_APPLICATION);
388
    }
389
    
390
   /**
391
    * Retrieves the selected locale name in the specified namespace.
392
    * 
393
    * @param string $namespace
394
    * @throws Localization_Exception
395
    * @return string
396
    */
397
    public static function getLocaleNameByNS(string $namespace) : string
398
    {
399
        return self::getSelectedLocaleByNS($namespace)->getName();
400
    }
401
    
402
   /**
403
    * Retrieves the selected locale instance for the specified namespace.
404
    * 
405
    * @param string $namespace
406
    * @return Localization_Locale
407
    * @throws Localization_Exception
408
    * @see Localization::ERROR_NO_LOCALE_SELECTED_IN_NS
409
    */
410
    public static function getSelectedLocaleByNS(string $namespace) : Localization_Locale
411
    {
412
        self::requireNamespace($namespace);
413
        
414
        if(isset(self::$selected[$namespace])) {
415
            return self::$selected[$namespace];
416
        }
417
        
418
        throw new Localization_Exception(
419
            'No selected locale in namespace.',
420
            sprintf(
421
                'Cannot retrieve selected locale: no locale has been selected in the namespace [%s].',
422
                $namespace
423
            ),
424
            self::ERROR_NO_LOCALE_SELECTED_IN_NS
425
        );
426
    }
427
    
428
   /**
429
    * Stores the selected locale names by namespace.
430
    * @var array<string,Localization_Locale>
431
    */
432
    protected static $selected = array();
433
434
   /**
435
    * Selects the active locale for the specified namespace.
436
    *
437
    * NOTE: Triggers the "LocaleChanged" event.
438
    * 
439
    * @param string $localeName
440
    * @param string $namespace
441
    * @return Localization_Locale
442
    * @throws Localization_Exception
443
    *
444
    * @see LocaleChanged
445
    */
446
    public static function selectLocaleByNS(string $localeName, string $namespace) : Localization_Locale
447
    {
448
        self::requireNamespace($namespace);
449
        
450
        $locale = self::addLocaleByNS($localeName, $namespace);
451
        $previous = null;
452
        
453
        if(isset(self::$selected[$namespace])) 
454
        {
455
            if(self::$selected[$namespace]->getName() === $localeName) {
456
                return $locale;
457
            }
458
            
459
            $previous = self::$selected[$namespace];
460
        }
461
        
462
        self::$translator = null;
463
464
        self::$selected[$namespace] = $locale;
465
        
466
        self::triggerEvent(
467
            self::EVENT_LOCALE_CHANGED,
468
            array(
469
                $namespace,
470
                $previous, 
471
                self::$selected[$namespace]
472
            )
473
        );
474
        
475
        return $locale;
476
    }
477
478
    /**
479
     * Triggers the specified event, with the provided arguments.
480
     *
481
     * @param string $name The event name.
482
     * @param array $argsList
483
     * @return Localization_Event
484
     * @see Localization_Event
485
     */
486
    protected static function triggerEvent(string $name, array $argsList) : Localization_Event
487
    {
488
        $class = self::resolveEventClass($name);
489
490
        $event = ClassHelper::requireObjectInstanceOf(
491
            Localization_Event::class,
492
            new $class($argsList)
493
        );
494
        
495
        if(!isset(self::$listeners[$name])) {
496
            return $event;
497
        }
498
        
499
        foreach(self::$listeners[$name] as $listener) 
500
        {
501
            $callArgs = $listener['args'];
502
            array_unshift($callArgs, $event);
503
            
504
            call_user_func_array($listener['callback'], $callArgs);
505
        }
506
        
507
        return $event;
508
    }
509
510
    /**
511
     * Adds a listener to the specified event name.
512
     *
513
     * @param string $eventName
514
     * @param callable $callback
515
     * @param array $args Additional arguments to add to the event
516
     * @return int The listener number.
517
     *
518
     * @throws Localization_Exception
519
     * @see Localization::ERROR_UNKNOWN_EVENT_NAME
520
     */
521
    public static function addEventListener(string $eventName, $callback, array $args=array()) : int
522
    {
523
        if(!isset(self::$listeners[$eventName])) {
524
            self::$listeners[$eventName] = array();
525
        }
526
        
527
        self::$listenersCounter++;
528
        
529
        self::$listeners[$eventName][] = array(
530
            'class' => self::resolveEventClass($eventName),
531
            'callback' => $callback,
532
            'args' => $args,
533
            'id' => self::$listenersCounter
534
        );
535
        
536
        return self::$listenersCounter;
537
    }
538
539
    private static function resolveEventClass(string $eventName) : string
540
    {
541
        $className = ClassHelper::resolveClassName(
542
            Localization_Event::class.'_'.$eventName,
543
            'AppLocalize'
544
        );
545
546
        if(class_exists($className)) {
0 ignored issues
show
Bug introduced by
It seems like $className can also be of type null; however, parameter $class of class_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

546
        if(class_exists(/** @scrutinizer ignore-type */ $className)) {
Loading history...
547
            return $className;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $className could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
548
        }
549
550
        throw new Localization_Exception(
551
            sprintf('Unknown localization event [%s].', $eventName),
552
            sprintf('The required event class [%s] is not present.', $className),
553
            self::ERROR_UNKNOWN_EVENT_NAME
554
        );
555
    }
556
557
    /**
558
     * Adds an event listener for the <code>LocaleChanged</code> event,
559
     * which is triggered every time a locale is changed in any of the
560
     * available namespaces.
561
     *
562
     * The first parameter of the callback is always the event instance.
563
     *
564
     * @param callable $callback The listener function to call.
565
     * @param array $args Optional indexed array with additional arguments to pass on to the callback function.
566
     * @return int
567
     * @throws Localization_Exception
568
     * @see LocaleChanged
569
     */
570
    public static function onLocaleChanged($callback, array $args=array()) : int
571
    {
572
        return self::addEventListener(self::EVENT_LOCALE_CHANGED, $callback, $args);
573
    }
574
575
    public static function onClientFolderChanged(callable $callback, array $args=array()) : int
576
    {
577
        return self::addEventListener(self::EVENT_CLIENT_FOLDER_CHANGED, $callback, $args);
578
    }
579
580
    public static function onCacheKeyChanged(callable $callback, array $args=array()) : int
581
    {
582
        return self::addEventListener(self::EVENT_CACHE_KEY_CHANGED, $callback, $args);
583
    }
584
585
    /**
586
     * Selects the application locale to use.
587
     *
588
     * @param string $localeName
589
     * @return Localization_Locale
590
     * @throws Localization_Exception
591
     */
592
    public static function selectAppLocale(string $localeName) : Localization_Locale
593
    {
594
        return self::selectLocaleByNS($localeName, self::NAMESPACE_APPLICATION);
595
    }
596
597
   /**
598
    * Retrieves an application locale by its name. 
599
    * Note that the locale must have been added first.
600
    * 
601
    * @param string $localeName
602
    * @throws Localization_Exception
603
    * @return Localization_Locale
604
    * @see Localization::appLocaleExists()
605
    */
606
    public static function getAppLocaleByName(string $localeName) : Localization_Locale
607
    {
608
        return self::getLocaleByNameNS($localeName, self::NAMESPACE_APPLICATION);
609
    }
610
611
    /**
612
     * Checks by the locale name if the specified locale is
613
     * available as a locale for the application.
614
     *
615
     * @param string $localeName
616
     * @return boolean
617
     */
618
    public static function appLocaleExists(string $localeName) : bool
619
    {
620
        return self::localeExistsInNS($localeName, self::NAMESPACE_APPLICATION);
621
    }
622
   
623
    public static function localeExistsInNS(string $localeName, string $namespace) : bool
624
    {
625
        return isset(self::$locales[$namespace]) && isset(self::$locales[$namespace][$localeName]);
626
    }
627
628
    /**
629
     * Retrieves an indexed array with all available content locales,
630
     * sorted by locale label.
631
     *
632
     * @return Localization_Locale[];
633
     * @throws Localization_Exception
634
     */
635
    public static function getContentLocales()
636
    {
637
        return self::getLocalesByNS(self::NAMESPACE_CONTENT);
638
    }
639
    
640
   /**
641
    * Retrieves the names of all content locales that have been added.
642
    * @return string[]
643
    */
644
    public static function getContentLocaleNames()
645
    {
646
        return self::getLocaleNamesByNS(self::NAMESPACE_CONTENT);
647
    }
648
649
    /**
650
     * Retrieves all locale names available in the specified namespace.
651
     * The names are sorted alphabetically.
652
     *
653
     * @param string $namespace
654
     * @return string[]
655
     * @throws Localization_Exception
656
     */
657
    public static function getLocaleNamesByNS(string $namespace) : array
658
    {
659
        self::requireNamespace($namespace);
660
        
661
        $names = array_keys(self::$locales[$namespace]);
662
        
663
        sort($names);
664
        
665
        return $names;
666
     }
667
    
668
    /**
669
     * Checks by the locale name if the specified locale is
670
     * available as a locale for the user data.
671
     *
672
     * @param string $localeName
673
     * @return boolean
674
     */
675
    public static function contentLocaleExists(string $localeName) : bool
676
    {
677
        return self::localeExistsInNS($localeName, self::NAMESPACE_CONTENT);
678
    }
679
680
    /**
681
     * Retrieves a specific content locale object by the locale name.
682
     * Note that you should check if it exists first to avoid triggering
683
     * an Exception if it does not.
684
     *
685
     * @param string $localeName
686
     * @throws Localization_Exception
687
     * @return Localization_Locale
688
     * @see Localization::contentLocaleExists()
689
     */
690
    public static function getContentLocaleByName($localeName) : Localization_Locale
691
    {
692
        return self::getLocaleByNameNS($localeName, self::NAMESPACE_CONTENT);
693
    }
694
    
695
   /**
696
    * Retrieves a locale by its name in the specified namespace.
697
    * 
698
    * @param string $localeName
699
    * @param string $namespace
700
    * @throws Localization_Exception
701
    * @return Localization_Locale
702
    */
703
    public static function getLocaleByNameNS(string $localeName, string $namespace) : Localization_Locale
704
    {
705
        self::requireNamespace($namespace);
706
        
707
        if(isset(self::$locales[$namespace]) && isset(self::$locales[$namespace][$localeName])) {
708
            return self::$locales[$namespace][$localeName];
709
        }
710
        
711
        throw new Localization_Exception(
712
            'Unknown locale in namespace',
713
            sprintf(
714
                'The locale [%s] has not been added to the namespace [%s].',
715
                $localeName,
716
                $namespace
717
            ),
718
            self::ERROR_UNKNOWN_LOCALE_IN_NS
719
        );
720
    }
721
722
    /**
723
     * Retrieves the currently selected content locale.
724
     *
725
     * @return Localization_Locale
726
     * @throws Localization_Exception
727
     */
728
    public static function getContentLocale() : Localization_Locale
729
    {
730
        return self::getSelectedLocaleByNS(self::NAMESPACE_CONTENT);
731
    }
732
733
    /**
734
     * @return string
735
     * @throws Localization_Exception
736
     */
737
    public static function getContentLocaleName() : string
738
    {
739
        return self::getSelectedLocaleByNS(self::NAMESPACE_CONTENT)->getName();
740
    }
741
742
    /**
743
     * @param Localization_Locale $locale
744
     * @return bool
745
     */
746
    public static function isActiveAppLocale(Localization_Locale $locale) : bool
747
    {
748
        return $locale->getName() === self::getAppLocaleName();
749
    }
750
751
    /**
752
     * Checks whether the specified locale is the current content locale.
753
     * @param Localization_Locale $locale
754
     * @return boolean
755
     * @throws Localization_Exception
756
     */
757
    public static function isActiveContentLocale(Localization_Locale $locale) : bool
758
    {
759
        return $locale->getName() === self::getContentLocaleName();
760
    }
761
762
    /**
763
     * Selects a specific content locale
764
     * @param string $localeName
765
     * @return Localization_Locale
766
     * @throws Localization_Exception
767
     */
768
    public static function selectContentLocale(string $localeName) : Localization_Locale
769
    {
770
        return self::selectLocaleByNS($localeName, self::NAMESPACE_CONTENT);
771
    }
772
    
773
   /**
774
    * Checks whether the localization has been configured entirely.
775
    * @return bool
776
    */
777
    public static function isConfigured() : bool
778
    {
779
        return self::$configured;
780
    }
781
782
    /**
783
     * @param Localization_Locale|null $locale
784
     * @return Localization_Translator
785
     */
786
    public static function getTranslator(?Localization_Locale $locale=null) : Localization_Translator
787
    {
788
        if($locale !== null)
789
        {
790
            $obj = new Localization_Translator();
791
            $obj->addSources(self::getSources());
792
            $obj->setTargetLocale($locale);
793
            return $obj;
794
        }
795
            
796
        if(!isset(self::$translator)) 
797
        {
798
            $obj = new Localization_Translator();
799
            $obj->addSources(self::getSources());
800
            $obj->setTargetLocale(self::getAppLocale());
801
            self::$translator = $obj;
802
        }
803
804
        return self::$translator;
805
    }
806
807
    public static function countContentLocales() : int
808
    {
809
        return self::countLocalesByNS(self::NAMESPACE_CONTENT);
810
    }
811
812
    public static function countAppLocales() : int
813
    {
814
        return self::countLocalesByNS(self::NAMESPACE_APPLICATION);
815
    }
816
    
817
    public static function countLocalesByNS(string $namespace) : int
818
    {
819
        self::requireNamespace($namespace);
820
        
821
        if(isset(self::$locales[$namespace])) {
822
            return count(self::$locales[$namespace]);
823
        }
824
        
825
        return 0;
826
    }
827
828
    /**
829
     * @param string $namespace
830
     * @throws Localization_Exception
831
     */
832
    protected static function requireNamespace(string $namespace) : void
833
    {
834
        if(isset(self::$locales[$namespace])) {
835
            return;
836
        }
837
        
838
        throw new Localization_Exception(
839
            'Cannot count locales in unknown namespace',
840
            sprintf(
841
                'The namespace [%s] does not exist.',
842
                $namespace
843
            ),
844
            self::ERROR_UNKNOWN_NAMESPACE
845
        );
846
    }
847
    
848
   /**
849
    * Injects a content locales selector element into the specified
850
    * HTML QuickForm2 container.
851
    * 
852
    * @param string $elementName
853
    * @param HTML_QuickForm2_Container $container
854
    * @param string $label
855
    * @return HTML_QuickForm2_Element_Select
856
    */
857
    public static function injectContentLocalesSelector(string $elementName, HTML_QuickForm2_Container $container, string $label='') : HTML_QuickForm2_Element_Select
858
    {
859
        return self::injectLocalesSelectorNS($elementName, self::NAMESPACE_CONTENT, $container, $label);
860
    }
861
    
862
   /**
863
    * Injects an app locales selector element into the specified
864
     * HTML QuickForm2 container.
865
     * 
866
    * @param string $elementName
867
    * @param HTML_QuickForm2_Container $container
868
    * @param string $label
869
    * @return HTML_QuickForm2_Element_Select
870
    */
871
    public static function injectAppLocalesSelector(string $elementName, HTML_QuickForm2_Container $container, string $label='') : HTML_QuickForm2_Element_Select
872
    {
873
        return self::injectLocalesSelectorNS($elementName, self::NAMESPACE_APPLICATION, $container, $label);
874
    }
875
876
    /**
877
     * Injects a locales selector element into the specified
878
     * HTML QuickForm2 container, for the specified locales
879
     * namespace.
880
     *
881
     * @param string $elementName
882
     * @param string $namespace
883
     * @param HTML_QuickForm2_Container $container
884
     * @param string $label
885
     * @return HTML_QuickForm2_Element_Select
886
     * @throws Localization_Exception
887
     */
888
    public static function injectLocalesSelectorNS(string $elementName, string $namespace, HTML_QuickForm2_Container $container, string $label='') : HTML_QuickForm2_Element_Select
889
    {
890
        if(empty($label)) {
891
            $label = t('Language');
892
        }
893
894
        $select = $container->addSelect($elementName);
895
        $select->setLabel($label);
896
897
        $locales = self::getLocalesByNS($namespace);
898
        
899
        foreach($locales as $locale) {
900
            $select->addOption($locale->getLabel(), $locale->getName());
901
        }
902
903
        return $select;
904
    }
905
906
   /**
907
    * @var Localization_Source[]
908
    */
909
    protected static $sources = array();
910
    
911
   /**
912
    * @var string[]
913
    */
914
    protected static $excludeFolders = array();
915
    
916
   /**
917
    * @var string[]
918
    */
919
    protected static $excludeFiles = array();
920
    
921
   /**
922
    * Retrieves all currently available sources.
923
    * 
924
    * @return Localization_Source[]
925
    */
926
    public static function getSources() : array
927
    {
928
        return self::$sources;
929
    }
930
    
931
    public static function addExcludeFolder(string $folderName) : void
932
    { 
933
        if(!in_array($folderName, self::$excludeFolders)) {
934
            self::$excludeFolders[] = $folderName;
935
        }
936
    }
937
    
938
    public static function addExcludeFile(string $fileName) : void
939
    {
940
        if(!in_array($fileName, self::$excludeFiles)) {
941
            self::$excludeFiles[] = $fileName;
942
        }
943
    }
944
    
945
    public static function addSourceFolder(string $alias, string $label, string $group, string $storageFolder, string $path) : Localization_Source_Folder
946
    {
947
        $source = new Localization_Source_Folder($alias, $label, $group, $storageFolder, $path);
948
        self::$sources[] = $source;
949
950
        usort(self::$sources, function(Localization_Source $a, Localization_Source $b) {
951
            return strnatcasecmp($a->getLabel(), $b->getLabel());
952
        });
953
        
954
        return $source;
955
    }
956
    
957
   /**
958
    * Retrieves all sources grouped by their group name.
959
    * @return array
960
    */
961
    public static function getSourcesGrouped()
962
    {
963
        $sources = self::getSources();
964
        
965
        $grouped = array();
966
        
967
        foreach($sources as $source) 
968
        {
969
            $group = $source->getGroup();
970
            
971
            if(!isset($grouped[$group])) {
972
                $grouped[$group] = array();
973
            }
974
            
975
            $grouped[$group][] = $source;
976
        }
977
        
978
        return $grouped;
979
    }
980
    
981
   /**
982
    * Checks whether a specific source exists by its ID.
983
    * @param string $sourceID
984
    * @return boolean
985
    */
986
    public static function sourceExists(string $sourceID) : bool
987
    {
988
        $sources = self::getSources();
989
        foreach($sources as $source) {
990
            if($source->getID() == $sourceID) {
991
                return true;
992
            }
993
        }
994
        
995
        return false;
996
    }
997
    
998
   /**
999
    * Checks whether a specific source exists by its alias.
1000
    * @param string $sourceAlias
1001
    * @return boolean
1002
    */
1003
    public static function sourceAliasExists(string $sourceAlias) : bool
1004
    {
1005
        $sources = self::getSources();
1006
        foreach($sources as $source) {
1007
            if($source->getAlias() == $sourceAlias) {
1008
                return true;
1009
            }
1010
        }
1011
        
1012
        return false;
1013
    }
1014
1015
   /**
1016
    * Retrieves a localization source by its ID.
1017
    * 
1018
    * @param string $sourceID
1019
    * @throws Localization_Exception
1020
    * @return Localization_Source
1021
    */
1022
    public static function getSourceByID(string $sourceID) : Localization_Source
1023
    {
1024
        $sources = self::getSources();
1025
        foreach($sources as $source) {
1026
            if($source->getID() == $sourceID) {
1027
                return $source;
1028
            }
1029
        }
1030
        
1031
        throw new Localization_Exception(
1032
            'Unknown localization source',
1033
            sprintf(
1034
                'The source [%s] has not been added. Available soources are: [%s].',
1035
                $sourceID,
1036
                implode(', ', self::getSourceIDs())
1037
            )
1038
        );
1039
    }
1040
    
1041
    /**
1042
     * Retrieves a localization source by its ID.
1043
     *
1044
     * @param string $sourceAlias
1045
     * @throws Localization_Exception
1046
     * @return Localization_Source
1047
     */
1048
    public static function getSourceByAlias(string $sourceAlias) : Localization_Source
1049
    {
1050
        $sources = self::getSources();
1051
        foreach($sources as $source) {
1052
            if($source->getAlias() == $sourceAlias) {
1053
                return $source;
1054
            }
1055
        }
1056
        
1057
        throw new Localization_Exception(
1058
            'Unknown localization source',
1059
            sprintf(
1060
                'The source [%s] has not been added. Available soources are: [%s].',
1061
                $sourceAlias,
1062
                implode(', ', self::getSourceAliases())
1063
            )
1064
        );
1065
    }
1066
1067
    /**
1068
     * Creates the scanner instance that is used to find
1069
     * all translatable strings in the application.
1070
     *
1071
     * @return Localization_Scanner
1072
     * @throws Localization_Exception
1073
     */
1074
    public static function createScanner() : Localization_Scanner
1075
    {
1076
        self::requireConfiguration();
1077
        
1078
        return new Localization_Scanner(self::$storageFile);
1079
    }
1080
    
1081
    public static function log(string $message) : void
0 ignored issues
show
Unused Code introduced by
The parameter $message is not used and could be removed. ( Ignorable by Annotation )

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

1081
    public static function log(/** @scrutinizer ignore-unused */ string $message) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1082
    {
1083
        // FIXME: TODO: Add this
1084
    }
1085
1086
    /**
1087
     * Configures the localization for the application:
1088
     * sets the location of the required files and folders.
1089
     * Also updated the client library files as needed.
1090
     *
1091
     * @param string $storageFile Where to store the file analysis storage file.
1092
     * @param string $clientLibrariesFolder Where to put the client libraries and translation files. Will be created if it does not exist. Optional: if not set, client libraries will not be created.
1093
     * @throws FileHelper_Exception
1094
     * @throws Localization_Exception
1095
     */
1096
    public static function configure(string $storageFile, string $clientLibrariesFolder='') : void
1097
    {
1098
        self::$configured = true;
1099
        
1100
        self::$storageFile = $storageFile;
1101
        self::$clientFolder = $clientLibrariesFolder;
1102
1103
        // only write the client libraries to disk if the folder
1104
        // has been specified.
1105
        if(!empty($clientLibrariesFolder)) 
1106
        {
1107
            self::writeClientFiles();
1108
        }
1109
    }
1110
    
1111
   /**
1112
    * Sets a key that is used to verify whether the client
1113
    * libraries have to be refreshed. A common use is to set
1114
    * this to the application's version number to guarantee
1115
    * new texts are automatically used with each release.
1116
    * 
1117
    * NOTE: Otherwise files are refreshed only when saving 
1118
    * them in the editor UI.
1119
    *  
1120
    * @param string $key
1121
    */
1122
    public static function setClientLibrariesCacheKey(string $key) : void
1123
    {
1124
        if($key !== self::$clientCacheKey) {
1125
            self::$clientCacheKey = $key;
1126
            self::triggerEvent(self::EVENT_CACHE_KEY_CHANGED, array($key));
1127
        }
1128
    }
1129
    
1130
    public static function getClientLibrariesCacheKey() : string
1131
    {
1132
        return self::$clientCacheKey;
1133
    }
1134
    
1135
   /**
1136
    * Sets the folder where client libraries are to be stored.
1137
    * @param string $folder
1138
    */
1139
    public static function setClientLibrariesFolder(string $folder) : void
1140
    {
1141
        if($folder !== self::$clientFolder) {
1142
            self::$clientFolder = $folder;
1143
            self::triggerEvent(self::EVENT_CLIENT_FOLDER_CHANGED, array($folder));
1144
        }
1145
    }
1146
    
1147
   /**
1148
    * Retrieves the path to the folder in which the client
1149
    * libraries should be stored.
1150
    * 
1151
    * NOTE: Can return an empty string, when this is disabled.
1152
    * 
1153
    * @return string
1154
    */
1155
    public static function getClientLibrariesFolder() : string
1156
    {
1157
        return self::$clientFolder;
1158
    }
1159
1160
    /**
1161
     * Writes / updates the client library files on disk,
1162
     * at the location specified in the {@link Localization::configure()}
1163
     * method.
1164
     *
1165
     * @param bool $force Whether to refresh the files, even if they exist.
1166
     * @throws Localization_Exception|FileHelper_Exception
1167
     * @see Localization_ClientGenerator
1168
     */
1169
    public static function writeClientFiles(bool $force=false) : void
0 ignored issues
show
Unused Code introduced by
The parameter $force is not used and could be removed. ( Ignorable by Annotation )

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

1169
    public static function writeClientFiles(/** @scrutinizer ignore-unused */ bool $force=false) : void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1170
    {
1171
        self::createGenerator()->writeFiles();
1172
    }
1173
1174
    private static ?Localization_ClientGenerator $generator = null;
1175
1176
   /**
1177
    * Creates a new instance of the client generator class
1178
    * that is used to write the localization files into the
1179
    * target folder on disk.
1180
    * 
1181
    * @return Localization_ClientGenerator
1182
    */
1183
    public static function createGenerator() : Localization_ClientGenerator
1184
    {
1185
        if(!isset(self::$generator)) {
1186
            self::$generator = new Localization_ClientGenerator();
1187
        }
1188
1189
        return self::$generator;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::generator could return the type null which is incompatible with the type-hinted return AppLocalize\Localization_ClientGenerator. Consider adding an additional type-check to rule them out.
Loading history...
1190
    }
1191
1192
    /**
1193
     * @return string
1194
     * @throws Localization_Exception
1195
     */
1196
    public static function getClientFolder() : string
1197
    {
1198
        self::requireConfiguration();
1199
        
1200
        return self::$clientFolder;
1201
    }
1202
1203
    /**
1204
     * @throws Localization_Exception
1205
     */
1206
    protected static function requireConfiguration() : void
1207
    {
1208
        if(!self::$configured) 
1209
        {
1210
            throw new Localization_Exception(
1211
                'The localization configuration is incomplete.',
1212
                'The configure method has not been called.',
1213
                self::ERROR_CONFIGURE_NOT_CALLED
1214
            );
1215
        }
1216
1217
        if(empty(self::$storageFile))
1218
        {
1219
            throw new Localization_Exception(
1220
                'No localization storage file set',
1221
                'To use the scanner, the storage file has to be set using the setStorageFile method.',
1222
                self::ERROR_NO_STORAGE_FILE_SET
1223
            );
1224
        }
1225
        
1226
        if(empty(self::$sources)) 
1227
        {
1228
            throw new Localization_Exception(
1229
                'No source folders have been defined.',
1230
                'At least one source folder has to be configured using the addSourceFolder method.',
1231
                self::ERROR_NO_SOURCES_ADDED
1232
            );
1233
        }
1234
    }
1235
1236
    /**
1237
     * Creates the editor instance that can be used to
1238
     * display the localization UI to edit translatable
1239
     * strings in the browser.
1240
     *
1241
     * @return Localization_Editor
1242
     * @throws Localization_Exception
1243
     */
1244
    public static function createEditor() : Localization_Editor
1245
    {
1246
        self::requireConfiguration();
1247
        
1248
        return new Localization_Editor();
1249
    }
1250
    
1251
   /**
1252
    * Retrieves a list of all available source IDs.
1253
    * @return string[]
1254
    */
1255
    public static function getSourceIDs() : array
1256
    {
1257
        $ids = array();
1258
        
1259
        foreach(self::$sources as $source) {
1260
            $ids[] = $source->getID();
1261
        }
1262
        
1263
        return $ids;
1264
    }
1265
    
1266
    /**
1267
     * Retrieves a list of all available source aliases.
1268
     * @return string[]
1269
     */
1270
    public static function getSourceAliases() : array
1271
    {
1272
        $aliases = array();
1273
        
1274
        foreach(self::$sources as $source) {
1275
            $aliases[] = $source->getAlias();
1276
        }
1277
        
1278
        return $aliases;
1279
    }
1280
    
1281
   /**
1282
    * Resets all locales to the built-in locale.
1283
    */
1284
    public static function reset() : void
1285
    {
1286
        self::$locales = array();
1287
        self::$selected = array();
1288
1289
        self::addAppLocale(self::BUILTIN_LOCALE_NAME);
1290
        self::addContentLocale(self::BUILTIN_LOCALE_NAME);
1291
        
1292
        self::selectAppLocale(self::BUILTIN_LOCALE_NAME);
1293
        self::selectContentLocale(self::BUILTIN_LOCALE_NAME);
1294
    }
1295
    
1296
    /**
1297
     * Indexed array with locale names supported by the application
1298
     * @var string[]
1299
     */
1300
    protected static $supportedLocales = array();
1301
1302
    /**
1303
     * Retrieves a list of all supported locales.
1304
     *
1305
     * @return string[]
1306
     * @throws FileHelper_Exception
1307
     */
1308
    public static function getSupportedLocaleNames() : array
1309
    {
1310
        if(empty(self::$supportedLocales))
1311
        {
1312
            self::$supportedLocales = FileHelper::createFileFinder(__DIR__.'/Localization/Locale')
0 ignored issues
show
Deprecated Code introduced by
The function AppUtils\FileHelper\FileFinder::getPHPClassNames() has been deprecated: Use {@see self::getFiles()} > {@see FileCollector::PHPClassNames()} instead. ( Ignorable by Annotation )

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

1312
            self::$supportedLocales = /** @scrutinizer ignore-deprecated */ FileHelper::createFileFinder(__DIR__.'/Localization/Locale')

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

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

Loading history...
1313
                ->getPHPClassNames();
1314
        }
1315
1316
        return self::$supportedLocales;
1317
    }
1318
    
1319
   /**
1320
    * Checks whether the specified locale is supported.
1321
    * 
1322
    * @param string $localeName
1323
    * @return bool
1324
    */
1325
    public static function isLocaleSupported(string $localeName) : bool
1326
    {
1327
        return file_exists(__DIR__.'/Localization/Locale/'.$localeName.'.php');
1328
    }
1329
1330
    private static ?string $version = null;
1331
1332
    public static function getVersion() : string
1333
    {
1334
        if(isset(self::$version)) {
1335
            return self::$version;
1336
        }
1337
1338
        $versionFile = FileInfo::factory(__DIR__.'/../version.txt');
1339
1340
        if($versionFile->exists()) {
1341
            self::$version = $versionFile->getContents();
1342
            return self::$version;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::version returns the type null which is incompatible with the type-hinted return string.
Loading history...
1343
        }
1344
1345
        self::$version = ChangelogParser::parseMarkdownFile(__DIR__.'/../changelog.md')
1346
            ->requireLatestVersion()
1347
            ->getVersionInfo()
1348
            ->getTagVersion();
1349
1350
        $versionFile->putContents(self::$version);
0 ignored issues
show
Bug introduced by
self::version of type null is incompatible with the type string expected by parameter $content of AppUtils\FileHelper\FileInfo::putContents(). ( Ignorable by Annotation )

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

1350
        $versionFile->putContents(/** @scrutinizer ignore-type */ self::$version);
Loading history...
1351
1352
        return self::$version;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::version returns the type null which is incompatible with the type-hinted return string.
Loading history...
1353
    }
1354
}
1355
1356
Localization::init();
1357