TranslationManager::startup()   A
last analyzed

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
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
// +---------------------------------------------------------------------------+
4
// | This file is part of the Agavi package.                                   |
5
// | Copyright (c) 2005-2011 the Agavi Project.                                |
6
// |                                                                           |
7
// | For the full copyright and license information, please view the LICENSE   |
8
// | file that was distributed with this source code. You can also view the    |
9
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
10
// |   vi: set noexpandtab:                                                    |
11
// |   Local Variables:                                                        |
12
// |   indent-tabs-mode: t                                                     |
13
// |   End:                                                                    |
14
// +---------------------------------------------------------------------------+
15
16
namespace Agavi\Translation;
17
18
use Agavi\Config\Config;
19
use Agavi\Config\ConfigCache;
20
use Agavi\Core\Context;
21
use Agavi\Date\Calendar;
22
use Agavi\Date\DateDefinitions;
23
use Agavi\Date\DateFormat;
24
use Agavi\Date\GregorianCalendar;
25
use Agavi\Date\OlsonTimeZone;
26
use Agavi\Date\TimeZone;
27
use Agavi\Exception\AgaviException;
28
use Agavi\Exception\Exception;
29
30
/**
31
 * The translation manager manages the interface between the application and the
32
 * current translation engine implementation
33
 *
34
 * @package    agavi
35
 * @subpackage translation
36
 *
37
 * @author     Dominik del Bondio <[email protected]>
38
 * @copyright  Authors
39
 * @copyright  The Agavi Project
40
 *
41
 * @since      0.11.0
42
 *
43
 * @version    $Id$
44
 */
45
class TranslationManager
46
{
47
    const MESSAGE = 'msg';
48
    const NUMBER = 'num';
49
    const CURRENCY = 'cur';
50
    const DATETIME = 'date';
51
52
    /**
53
     * @var        Context A Context instance.
54
     */
55
    protected $context = null;
56
57
    /**
58
     * @var        array An array of the translator instances for the domains.
59
     */
60
    protected $translators = array();
61
62
    /**
63
     * @var        Locale The current locale.
64
     */
65
    protected $currentLocale = null;
66
67
    /**
68
     * @var        string The original locale identifier given to this instance.
69
     */
70
    protected $givenLocaleIdentifier = null;
71
72
    /**
73
     * @var        string The identifier of the current locale.
74
     */
75
    protected $currentLocaleIdentifier = null;
76
77
    /**
78
     * @var        string The default locale identifier.
79
     */
80
    protected $defaultLocaleIdentifier = null;
81
82
    /**
83
     * @var        string The default domain which shall be used for translation.
84
     */
85
    protected $defaultDomain = null;
86
87
    /**
88
     * @var        array The available locales which have been defined in the
89
     *                   translation.xml config file.
90
     */
91
    protected $availableConfigLocales = array();
92
93
    /**
94
     * @var        array All available locales. Just stores the info for lazyload.
95
     */
96
    protected $availableLocales = array();
97
98
    /**
99
     * @var        array A cache for locale instances.
100
     */
101
    protected $localeCache = array();
102
103
    /**
104
     * @var        array A cache for locale identifiers resolved from a string.
105
     */
106
    protected $localeIdentifierCache = array();
107
108
    /**
109
     * @var        array A cache for the data of the available locales.
110
     */
111
    protected $localeDataCache = array();
112
113
    /**
114
     * @var        array The supplemental data from the cldr
115
     */
116
    protected $supplementalData = array();
117
118
    /**
119
     * @var        array The list of available time zones.
120
     */
121
    protected $timeZoneList = array();
122
123
    /**
124
     * @var        array A cache for the time zone instances.
125
     */
126
    protected $timeZoneCache = array();
127
128
    /**
129
     * @var        string The default time zone. If not set the timezone php
130
     *                    will be used as default.
131
     */
132
    protected $defaultTimeZone = null;
133
134
    /**
135
     * Initialize this TranslationManager.
136
     *
137
     * @param      Context $context    The current application context.
138
     * @param      array   $parameters An associative array of initialization parameters.
139
     *
140
     * @author     Dominik del Bondio <[email protected]>
141
     * @since      0.11.0
142
     */
143
    public function initialize(Context $context, array $parameters = array())
144
    {
145
        $this->context = $context;
146
147
        include(ConfigCache::checkConfig(Config::get('core.config_dir') . '/translation.xml'));
148
        $this->loadSupplementalData();
149
        $this->loadTimeZoneData();
150
        $this->loadAvailableLocales();
151
        if ($this->defaultLocaleIdentifier === null) {
152
            throw new AgaviException('Tried to use the translation system without a default locale and without a locale set');
153
        }
154
        $this->setLocale($this->defaultLocaleIdentifier);
155
156
        if ($this->defaultTimeZone === null) {
157
            $this->defaultTimeZone = date_default_timezone_get();
158
        }
159
        
160
        if ($this->defaultTimeZone === 'System/Localtime') {
161
            // http://trac.agavi.org/ticket/1008
162
            throw new AgaviException("Your default timezone is 'System/Localtime', which likely means that you're running Debian, Ubuntu or some other Linux distribution that chose to include a useless and broken patch for system timezone database lookups into their PHP package, despite this very change being declined by the PHP development team for inclusion into PHP itself.\nThis pseudo-timezone, which is not defined in the standard 'tz' database used across many operating systems and applications, works for internal PHP classes and functions because the 'real' system timezone is resolved instead, but there is no way for an application to obtain the actual timezone name that 'System/Localtime' resolves to internally - information Agavi needs to perform accurate calculations and operations on dates and times.\n\nPlease set a correct timezone name (e.g. Europe/London) via 'date.timezone' in php.ini, use date_default_timezone_set() to set it in your code, or define a default timezone for Agavi to use in translation.xml. If you have some minutes to spare, file a bug report with your operating system vendor about this problem.\n\nIf you'd like to learn more about this issue, please refer to http://trac.agavi.org/ticket/1008");
163
        }
164
    }
165
166
    /**
167
     * Do any necessary startup work after initialization.
168
     *
169
     * This method is not called directly after initialize().
170
     *
171
     * @author     David Zülke <[email protected]>
172
     * @since      0.11.0
173
     */
174
    public function startup()
175
    {
176
    }
177
178
    /**
179
     * Execute the shutdown procedure.
180
     *
181
     * @author     David Zülke <[email protected]>
182
     * @since      0.11.0
183
     */
184
    public function shutdown()
185
    {
186
    }
187
188
    /**
189
     * Retrieve the current application context.
190
     *
191
     * @return     Context The current Context instance.
192
     *
193
     * @author     Dominik del Bondio <[email protected]>
194
     * @since      0.11.0
195
     */
196
    final public function getContext()
197
    {
198
        return $this->context;
199
    }
200
201
    /**
202
     * Returns the list of available locales.
203
     *
204
     * @author     David Zülke <[email protected]>
205
     * @since      0.11.0
206
     */
207
    public function getAvailableLocales()
208
    {
209
        return $this->availableLocales;
210
    }
211
212
    /**
213
     * Sets the current locale.
214
     *
215
     * @param      string $identifier The locale identifier.
216
     *
217
     * @author     Dominik del Bondio <[email protected]>
218
     * @since      0.11.0
219
     */
220
    public function setLocale($identifier)
221
    {
222
        $this->currentLocaleIdentifier = $this->getLocaleIdentifier($identifier);
223
        $givenData = Locale::parseLocaleIdentifier($identifier);
224
        $actualData = Locale::parseLocaleIdentifier($this->currentLocaleIdentifier);
225
        // construct the given name from the locale from the closest match and the options that were given to the requested locale identifier
226
        $this->givenLocaleIdentifier = $actualData['locale_str'] . $givenData['option_str'];
227
    }
228
229
    /**
230
     * Retrieve the current locale.
231
     *
232
     * @return     Locale The current locale.
233
     *
234
     * @author     Dominik del Bondio <[email protected]>
235
     * @since      0.11.0
236
     */
237
    public function getCurrentLocale()
238
    {
239
        $this->loadCurrentLocale();
240
        return $this->currentLocale;
241
    }
242
243
    /**
244
     * Retrieve the current locale identifier. This may not necessarily match
245
     * what has be given to setLocale() but instead the identifier of the closest
246
     * match from the available locales.
247
     *
248
     * @return     string The locale identifier.
249
     *
250
     * @author     Dominik del Bondio <[email protected]>
251
     * @since      0.11.0
252
     */
253
    public function getCurrentLocaleIdentifier()
254
    {
255
        return $this->currentLocaleIdentifier;
256
    }
257
258
    /**
259
     * Retrieve the default locale.
260
     *
261
     * @return     Locale The current default.
262
     *
263
     * @author     David Zülke <[email protected]>
264
     * @since      0.11.0
265
     */
266
    public function getDefaultLocale()
267
    {
268
        return $this->getLocale($this->getDefaultLocaleIdentifier());
269
    }
270
271
    /**
272
     * Retrieve the default locale identifier.
273
     *
274
     * @return     string The default locale identifier.
275
     *
276
     * @author     David Zülke <[email protected]>
277
     * @since      0.11.0
278
     */
279
    public function getDefaultLocaleIdentifier()
280
    {
281
        return $this->defaultLocaleIdentifier;
282
    }
283
284
    /**
285
     * Sets the default domain.
286
     *
287
     * @param      string $domain The new default domain.
288
     *
289
     * @author     Dominik del Bondio <[email protected]>
290
     * @since      0.11.0
291
     */
292
    public function setDefaultDomain($domain)
293
    {
294
        $this->defaultDomain = $domain;
295
    }
296
297
    /**
298
     * Retrieve the default domain.
299
     *
300
     * @return     string The default domain.
301
     *
302
     * @author     Dominik del Bondio <[email protected]>
303
     * @since      0.11.0
304
     */
305
    public function getDefaultDomain()
306
    {
307
        return $this->defaultDomain;
308
    }
309
310
    /**
311
     * Formats a date in the current locale.
312
     *
313
     * @param      mixed  $date   The date to be formatted.
314
     * @param      string $domain The domain in which the date should be formatted.
315
     * @param      Locale $locale The locale which should be used for formatting.
316
     *                            Defaults to the currently active locale.
317
     *
318
     * @return     string The formatted date.
319
     *
320
     * @author     Dominik del Bondio <[email protected]>
321
     * @since      0.11.0
322
     */
323 View Code Duplication
    public function _d($date, $domain = null, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
    {
325
        if ($domain === null) {
326
            $domain = $this->defaultDomain;
327
        }
328
329
        if ($locale === null) {
330
            $this->loadCurrentLocale();
331
        } elseif (is_string($locale)) {
332
            $locale = $this->getLocale($locale);
333
        }
334
        
335
        $domainExtra = '';
336
        $translator = $this->getTranslators($domain, $domainExtra, self::DATETIME);
337
338
        $retval = $translator->translate($date, $domainExtra, $locale);
339
        
340
        $retval = $this->applyFilters($retval, $domain, self::DATETIME);
341
        
342
        return $retval;
343
    }
344
345
    /**
346
     * Formats a currency amount in the current locale.
347
     *
348
     * @param      mixed  $number The number to be formatted.
349
     * @param      string $domain The domain in which the amount should be formatted.
350
     * @param      Locale $locale The locale which should be used for formatting.
351
     *                            Defaults to the currently active locale.
352
     *
353
     * @return     string The formatted number.
354
     *
355
     * @author     Dominik del Bondio <[email protected]>
356
     * @since      0.11.0
357
     */
358 View Code Duplication
    public function _c($number, $domain = null, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
359
    {
360
        if ($domain === null) {
361
            $domain = $this->defaultDomain;
362
        }
363
364
        if ($locale === null) {
365
            $this->loadCurrentLocale();
366
        } elseif (is_string($locale)) {
367
            $locale = $this->getLocale($locale);
368
        }
369
        
370
        $domainExtra = '';
371
        $translator = $this->getTranslators($domain, $domainExtra, self::CURRENCY);
372
373
        $retval = $translator->translate($number, $domainExtra, $locale);
374
        
375
        $retval = $this->applyFilters($retval, $domain, self::CURRENCY);
376
        
377
        return $retval;
378
    }
379
380
    /**
381
     * Formats a number in the current locale.
382
     *
383
     * @param      mixed  $number The number to be formatted.
384
     * @param      string $domain The domain in which the number should be formatted.
385
     * @param      Locale $locale The locale which should be used for formatting.
386
     *                            Defaults to the currently active locale.
387
     *
388
     * @return     string The formatted number.
389
     *
390
     * @author     Dominik del Bondio <[email protected]>
391
     * @since      0.11.0
392
     */
393 View Code Duplication
    public function _n($number, $domain = null, $locale = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394
    {
395
        if ($domain === null) {
396
            $domain = $this->defaultDomain;
397
        }
398
399
        if ($locale === null) {
400
            $this->loadCurrentLocale();
401
        } elseif (is_string($locale)) {
402
            $locale = $this->getLocale($locale);
403
        }
404
        
405
        $domainExtra = '';
406
        $translator = $this->getTranslators($domain, $domainExtra, self::NUMBER);
407
408
        $retval = $translator->translate($number, $domainExtra, $locale);
409
        
410
        $retval = $this->applyFilters($retval, $domain, self::NUMBER);
411
        
412
        return $retval;
413
    }
414
415
    /**
416
     * Translate a message into the current locale.
417
     *
418
     * @param      mixed  $message    The message.
419
     * @param      string $domain     The domain in which the translation should be done.
420
     * @param      Locale $locale     The locale which should be used for formatting.
421
     *                                Defaults to the currently active locale.
422
     * @param      array  $parameters The parameters which should be used for sprintf on
423
     *                                the translated string.
424
     *
425
     * @return     string The translated message.
426
     *
427
     * @author     Dominik del Bondio <[email protected]>
428
     * @since      0.11.0
429
     */
430
    public function _($message, $domain = null, $locale = null, array $parameters = null)
431
    {
432
        if ($domain === null) {
433
            $domain = $this->defaultDomain;
434
        }
435
        
436
        if ($locale === null) {
437
            $this->loadCurrentLocale();
438
        } elseif (is_string($locale)) {
439
            $locale = $this->getLocale($locale);
440
        }
441
        
442
        $domainExtra = '';
443
        $translator = $this->getTranslators($domain, $domainExtra, self::MESSAGE);
444
445
        $retval = $translator->translate($message, $domainExtra, $locale);
446
        if (is_array($parameters)) {
447
            $retval = vsprintf($retval, $parameters);
448
        }
449
        
450
        $retval = $this->applyFilters($retval, $domain, self::MESSAGE);
451
        
452
        return $retval;
453
    }
454
455
    /**
456
     * Translate a singular/plural message into the current locale.
457
     *
458
     * @param      string $singularMessage The message for the singular form.
459
     * @param      string $pluralMessage   The message for the plural form.
460
     * @param      int    $amount          The amount for which the translation should happen.
461
     * @param      string $domain          The domain in which the translation should be done.
462
     * @param      Locale $locale          The locale which should be used for formatting.
463
     *                                     Defaults to the currently active locale.
464
     * @param      array  $parameters      The parameters which should be used for sprintf on
465
     *                                     the translated string.
466
     *
467
     * @return     string The translated message.
468
     *
469
     * @author     David Zülke <[email protected]>
470
     * @since      0.11.0
471
     */
472
    public function __($singularMessage, $pluralMessage, $amount, $domain = null, $locale = null, array $parameters = null)
473
    {
474
        return $this->_(array($singularMessage, $pluralMessage, $amount), $domain, $locale, $parameters);
475
    }
476
477
    /**
478
     * Returns the translators for a given domain.
479
     *
480
     * @param      string $domain      The domain.
481
     * @param      string $domainExtra The remaining part in the domain which didn't match
482
     * @param      string $type        The type of the translator
483
     *
484
     * @return     TranslatorInterface An array of translators for the given domain
485
     *
486
     * @author     Dominik del Bondio <[email protected]>
487
     * @since      0.11.0
488
     */
489
    protected function getTranslators(&$domain, &$domainExtra, $type = null)
490
    {
491
        if ($domain[0] == '.') {
492
            $domain = $this->defaultDomain . $domain;
493
        }
494
495
        $domainParts = explode('.', $domain);
496
497
        do {
498
            if (count($domainParts) == 0) {
499
                throw new \InvalidArgumentException(sprintf('No translator exists for the domain "%s"', $domain));
500
            }
501
            $td = implode('.', $domainParts);
502
            array_pop($domainParts);
503
        } while (!isset($this->translators[$td]) || ($type && !isset($this->translators[$td][$type])));
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
504
505
        $domainExtra = substr($domain, strlen($td) + 1);
506
        $domain = $td;
507
        return $type ? $this->translators[$td][$type] : $this->translators[$td];
508
    }
509
510
    /**
511
     * Returns the translators for a given domain and type. The domain can contain
512
     * any extra parts which will be ignored. Will return null when no translator
513
     * is defined.
514
     *
515
     * @param      string $domain The domain.
516
     * @param      string $type   The type of the translator.
517
     *
518
     * @return     TranslatorInterface The translator instance.
519
     *
520
     * @author     Dominik del Bondio <[email protected]>
521
     * @since      0.11.0
522
     */
523
    public function getDomainTranslator($domain, $type)
524
    {
525
        try {
526
            $domainExtra = '';
527
            return $this->getTranslators($domain, $domainExtra, $type);
528
        } catch (\InvalidArgumentException $e) {
529
            return null;
530
        }
531
    }
532
533
    /**
534
     * Returns the translator filters for a given domain.
535
     *
536
     * @param      string $message The message.
537
     * @param      string $domain  The domain (w/o extra parts).
538
     * @param      string $type    The type.
539
     *
540
     * @return     string The new message.
541
     *
542
     * @author     David Zülke <[email protected]>
543
     * @since      0.11.0
544
     */
545
    protected function applyFilters($message, $domain, $type = self::MESSAGE)
546
    {
547
        if (isset($this->translatorFilters[$domain][$type])) {
548
            foreach ($this->translatorFilters[$domain][$type] as $filter) {
0 ignored issues
show
Bug introduced by
The property translatorFilters does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
549
                $message = call_user_func($filter, $message);
550
            }
551
        }
552
        
553
        return $message;
554
    }
555
556
    /**
557
     * Loads the available locales into the instance variable
558
     *
559
     * @author     Dominik del Bondio <[email protected]>
560
     * @since      0.11.0
561
     */
562
    protected function loadAvailableLocales()
563
    {
564
        $this->availableLocales = $this->availableConfigLocales;
565
    }
566
567
    /**
568
     * Lazy loads the current locale if necessary.
569
     *
570
     * @author     Dominik del Bondio <[email protected]>
571
     * @since      0.11.0
572
     */
573
    protected function loadCurrentLocale()
574
    {
575
        if (!$this->currentLocale || $this->currentLocale->getIdentifier() != $this->givenLocaleIdentifier) {
576
            $this->currentLocale = $this->getLocale($this->givenLocaleIdentifier);
577
            // we first need to initialize all message translators before the number formatters
578 View Code Duplication
            foreach ($this->translators as $translatorList) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
579
                /**
580
                 * @var string $type
581
                 * @var TranslatorInterface $translator
582
                 */
583
                foreach ($translatorList as $type => $translator) {
584
                    if ($type == self::MESSAGE) {
585
                        $translator->localeChanged($this->currentLocale);
586
                    }
587
                }
588
            }
589 View Code Duplication
            foreach ($this->translators as $translatorList) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                foreach ($translatorList as $type => $translator) {
591
                    if ($type != self::MESSAGE) {
592
                        $translator->localeChanged($this->currentLocale);
593
                    }
594
                }
595
            }
596
        }
597
    }
598
599
    /**
600
     * Loads the supplemental data into the instance variable
601
     *
602
     * @author     Dominik del Bondio <[email protected]>
603
     * @since      0.11.0
604
     */
605
    protected function loadSupplementalData()
606
    {
607
        $this->supplementalData = include(ConfigCache::checkConfig(Config::get('core.cldr_dir') . '/supplementalData.xml'));
608
    }
609
610
    /**
611
     * Loads the time zone data.
612
     *
613
     * @author     Dominik del Bondio <[email protected]>
614
     * @since      0.11.0
615
     */
616
    protected function loadTimeZoneData()
617
    {
618
        $this->timeZoneList = include(Config::get('core.cldr_dir') . '/timezones/zonelist.php');
619
    }
620
621
    /**
622
     * Returns all the identifiers of the available locales which match the given
623
     * locale identifier.
624
     *
625
     * @param      string $identifier A locale identifier
626
     *
627
     * @return     array The actual locale identifiers of the available locales.
628
     *
629
     * @author     Dominik del Bondio <[email protected]>
630
     * @author     David Zülke <[email protected]>
631
     * @author     Thomas Bachem <[email protected]>
632
     */
633
    public function getMatchingLocaleIdentifiers($identifier)
634
    {
635
        // if a locale with the given identifier doesn't exist try to find the closest matches
636
        if (isset($this->availableLocales[$identifier])) {
637
            return array($identifier);
638
        }
639
        
640
        $idData = Locale::parseLocaleIdentifier($identifier);
641
        
642
        $matchingLocaleIdentifiers = array();
643
        // iterate over all available locales
644
        foreach ($this->availableLocales as $availableLocaleIdentifier => $availableLocale) {
645
            $matched = false;
646
            // iterate over possible properties to compare against (all given ones must match)
647
            foreach (array('language', 'script', 'territory', 'variant') as $propertyName) {
648
                // only perform check if property was in $identifier
649
                if (isset($idData[$propertyName])) {
650
                    // compare against data in locale
651
                    if ($idData[$propertyName] == $availableLocale['identifierData'][$propertyName]) {
652
                        // fine, continue with next
653
                        $matched = true;
654
                    } else {
655
                        // failed, so we can bail out early and declare as non-matched
656
                        $matched = false;
657
                        break;
658
                    }
659
                }
660
            }
661
            if ($matched) {
662
                $matchingLocaleIdentifiers[] = $availableLocaleIdentifier;
663
            }
664
        }
665
        
666
        return $matchingLocaleIdentifiers;
667
    }
668
669
    /**
670
     * Returns the identifier of the available locale which matches the given
671
     * locale identifier most.
672
     *
673
     * @param      string $identifier A locale identifier
674
     *
675
     * @return     string The actual locale identifier of the available locale.
676
     *
677
     * @author     Dominik del Bondio <[email protected]>
678
     * @author     David Zülke <[email protected]>
679
     * @since      0.11.0
680
     */
681
    public function getLocaleIdentifier($identifier)
682
    {
683
        if (isset($this->localeIdentifierCache[$identifier])) {
684
            return $this->localeIdentifierCache[$identifier];
685
        }
686
        
687
        $matchingLocaleIdentifiers = $this->getMatchingLocaleIdentifiers($identifier);
688
        
689
        switch (count($matchingLocaleIdentifiers)) {
690
            case 1:
691
                $availableLocaleIdentifier = current($matchingLocaleIdentifiers);
692
                break;
693
            case 0:
694
                throw new AgaviException('Specified locale identifier ' . $identifier . ' which has no matching available locale defined');
695
            default:
696
                throw new AgaviException('Specified ambiguous locale identifier ' . $identifier . ' which has matches: ' . implode(', ', $matchingLocaleIdentifiers));
697
        }
698
        
699
        return $this->localeIdentifierCache[$identifier] = $availableLocaleIdentifier;
700
    }
701
702
    /**
703
     * Returns a new AgaviLocale object from the given identifier.
704
     *
705
     * @param      string $identifier The locale identifier
706
     * @param      bool   $forceNew   Force a new instance even if an identical one exists.
707
     *
708
     * @return     Locale The locale instance which matches the available
709
     *                         locales most.
710
     *
711
     * @author     Dominik del Bondio <[email protected]>
712
     * @author     David Zülke <[email protected]>
713
     * @since      0.11.0
714
     */
715
    public function getLocale($identifier, $forceNew = false)
716
    {
717
        // enable shortcut notation to only set options to the current locale
718
        if ($identifier[0] == '@' && $this->currentLocaleIdentifier) {
719
            $idData = Locale::parseLocaleIdentifier($this->currentLocaleIdentifier);
720
            $identifier = $idData['locale_str'] . $identifier;
721
722
            $newIdData = Locale::parseLocaleIdentifier($identifier);
723
            $idData['options'] = array_merge($idData['options'], $newIdData['options']);
724
        } else {
725
            $idData = Locale::parseLocaleIdentifier($identifier);
726
        }
727
        // this doesn't care about the options
728
        $availableLocale = $this->availableLocales[$this->getLocaleIdentifier($identifier)];
729
730
        // if the user wants all options reset he supplies an 'empty' option set (identifier ends with @)
731
        if (substr($identifier, -1) == '@') {
732
            $idData['options'] = array();
733
        } else {
734
            $idData['options'] = array_merge($availableLocale['identifierData']['options'], $idData['options']);
735
        }
736
737
        if (($atPos = strpos($identifier, '@')) !== false) {
738
            $identifier = $availableLocale['identifierData']['locale_str'] . substr($identifier, $atPos);
739
        } else {
740
            $identifier = $availableLocale['identifier'];
741
        }
742
743
        if (!$forceNew && isset($this->localeCache[$identifier])) {
744
            return $this->localeCache[$identifier];
745
        }
746
747
        if (!isset($this->localeDataCache[$idData['locale_str']])) {
748
            $lookupPath = Locale::getLookupPath($availableLocale['identifierData']);
749
            $cldrDir = Config::get('core.cldr_dir');
750
            $data = null;
751
752
            foreach ($lookupPath as $localeName) {
753
                $fileName = $cldrDir . '/locales/' . $localeName . '.xml';
754
                if (is_readable($fileName)) {
755
                    $data = include(ConfigCache::checkConfig($fileName));
756
                    break;
757
                }
758
            }
759
            if ($data === null) {
760
                throw new AgaviException('No data available for locale ' . $identifier);
761
            }
762
763
            if ($availableLocale['identifierData']['territory']) {
764
                $territory = $availableLocale['identifierData']['territory'];
765
                if (isset($this->supplementalData['territories'][$territory]['currencies'])) {
766
                    $slice = array_slice($this->supplementalData['territories'][$territory]['currencies'], 0, 1);
767
                    $currency = current($slice);
768
                    $data['locale']['currency'] = $currency['currency'];
769
                }
770
            }
771
772
            $this->localeDataCache[$idData['locale_str']] = $data;
773
        }
774
775
        $data = $this->localeDataCache[$idData['locale_str']];
776
777 View Code Duplication
        if (isset($idData['options']['calendar'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
778
            $data['locale']['calendar'] = $idData['options']['calendar'];
779
        }
780
781 View Code Duplication
        if (isset($idData['options']['currency'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
782
            $data['locale']['currency'] = $idData['options']['currency'];
783
        }
784
785 View Code Duplication
        if (isset($idData['options']['timezone'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
786
            $data['locale']['timezone'] = $idData['options']['timezone'];
787
        }
788
789
        $locale = new Locale();
790
        $locale->initialize($this->context, $availableLocale['parameters'], $identifier, $data);
791
792
        if (!$forceNew) {
793
            $this->localeCache[$identifier] = $locale;
794
        }
795
796
        return $locale;
797
    }
798
799
    /**
800
     * Sets the default time zone.
801
     *
802
     * @param      string $id The timezone identifier
803
     *
804
     * @author     Dominik del Bondio <[email protected]>
805
     * @since      0.11.0
806
     */
807
    public function setDefaultTimeZone($id)
808
    {
809
        $this->defaultTimeZone = $id;
810
    }
811
812
    /**
813
     * Gets the instance of the current timezone.
814
     *
815
     * @return     TimeZone The current timezone instance.
816
     *
817
     * @author     Dominik del Bondio <[email protected]>
818
     * @since      0.11.0
819
     *
820
     * @deprecated Superseded by TranslationManager::getDefaultTimeZone()
821
     */
822
    public function getCurrentTimeZone()
823
    {
824
        return $this->getDefaultTimeZone();
825
    }
826
827
    /**
828
     * Get the default timezone instance.
829
     *
830
     * @return     TimeZone The default timezone instance.
831
     *
832
     * @author     David Zülke <[email protected]>
833
     * @since      1.0.0
834
     */
835
    public function getDefaultTimeZone()
836
    {
837
        return $this->createTimeZone($this->defaultTimeZone);
838
    }
839
840
    /**
841
     * Gets the territory id a (resolved) timezone id belongs to.
842
     *
843
     * @param      string $id The resolved timezone id.
844
     * @param      bool   $hasMultipleZones Will receive whether the territory has multiple
845
     *                    time zones
846
     *
847
     * @return     string The territory identifier or null.
848
     *
849
     * @author     Dominik del Bondio <[email protected]>
850
     * @since      0.11.0
851
     */
852
    public function getTimeZoneTerritory($id, &$hasMultipleZones = false)
853
    {
854
        if (isset($this->supplementalData['timezones']['territories'][$id])) {
855
            $territory = $this->supplementalData['timezones']['territories'][$id];
856
            $hasMultipleZones = isset($this->supplementalData['timezones']['multiZones'][$territory]);
857
            return $territory;
858
        }
859
860
        return null;
861
    }
862
    
863
    /**
864
     * Resolved the given timezone identifier to its 'real' timezone id.
865
     *
866
     * This provides the same functionality like
867
     * $tm->createTimeZone(id)->getResolvedId() with the difference, that using
868
     * this method will not create a new timezone instance and look up the
869
     * resolved id there, but instead directly returns the resolved id by using
870
     * a simple lookup.
871
     *
872
     * @param      int $id The timezone id to be resolved
873
     * @return     int The resolved timezone id
874
     *
875
     * @author     Dominik del Bondio <[email protected]>
876
     * @since      1.0.0
877
     */
878
    public function resolveTimeZoneId($id)
879
    {
880
        if (isset($this->timeZoneList[$id])) {
881 View Code Duplication
            while ($this->timeZoneList[$id]['type'] == 'link') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
882
                $id = $this->timeZoneList[$id]['to'];
883
            }
884
        }
885
        
886
        return $id;
887
    }
888
    
889
890
    /**
891
     * Creates a new timezone instance for the given identifier.
892
     *
893
     * Please note that this method caches the results for each identifier, so
894
     * if you plan to modify the timezones returned by this method you need to
895
     * clone them first. Alternatively you can set the cache parameter to false,
896
     * but this will mean the data for this timezone will be loaded from the
897
     * hdd again.
898
     *
899
     * @return     TimeZone The timezone instance for the given id.
900
     *
901
     * @author     Dominik del Bondio <[email protected]>
902
     * @since      0.11.0
903
     */
904
    public function createTimeZone($id, $cache = true)
905
    {
906
        if (!isset($this->timeZoneList[$id])) {
907
            try {
908
                return TimeZone::createCustomTimeZone($this, $id);
909
            } catch (\Exception $e) {
910
                return null;
911
            }
912
        }
913
914
        if (!isset($this->timeZoneCache[$id]) || !$cache) {
915
            $currId = $id;
916
917
            // resolve links
918 View Code Duplication
            while ($this->timeZoneList[$currId]['type'] == 'link') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
919
                $currId = $this->timeZoneList[$currId]['to'];
920
            }
921
922
            $zoneData = include(Config::get('core.cldr_dir') . '/timezones/' . $this->timeZoneList[$currId]['filename']);
923
924
            $zone = new OlsonTimeZone($this, $id, $zoneData);
925
            $zone->setResolvedId($currId);
926
            if ($cache) {
927
                $this->timeZoneCache[$id] = $zone;
928
            }
929
        } else {
930
            $zone = $this->timeZoneCache[$id];
931
        }
932
933
        return $zone;
934
    }
935
936
    /**
937
     * Creates a new calendar instance with the current time set.
938
     *
939
     * @param      mixed $type This can be either a Locale, a TimeZone or
940
     *                   a string specifying the calendar type.
941
     *
942
     * @return     Calendar The current timezone instance.
943
     *
944
     * @author     Dominik del Bondio <[email protected]>
945
     * @since      0.11.0
946
     */
947
    public function createCalendar($type = null)
948
    {
949
        $locale = $this->getCurrentLocale();
950
        $calendarType = null;
951
        $zone = null;
952
        $time = null;
953
        if ($type instanceof Locale) {
954
            $locale = $type;
955
        } elseif ($type instanceof TimeZone) {
956
            $zone = $type;
957
        } elseif ($type instanceof \DateTime) {
958
            $time = $type;
959
        } elseif (is_int($type)) {
960
            $time = $type * DateDefinitions::MILLIS_PER_SECOND;
961
        } elseif ($type !== null) {
962
            $calendarType = $type;
963
        }
964
        if ($time === null) {
965
            $time = Calendar::getNow();
966
        }
967
968
        if (!$zone) {
969
            if ($locale->getLocaleTimeZone()) {
970
                $zone = $this->createTimeZone($locale->getLocaleTimeZone());
971
            }
972
        }
973
974
        if (!$calendarType) {
975
            $calendarType = $locale->getLocaleCalendar();
976
            if (!$calendarType) {
977
                $calendarType = Calendar::GREGORIAN;
978
            }
979
        }
980
981
        switch ($calendarType) {
982
            case Calendar::GREGORIAN:
983
                $c = new GregorianCalendar($this /* $locale */);
984
                break;
985
            default:
986
                throw new AgaviException('Calendar type ' . $calendarType . ' not supported');
987
        }
988
989
        // Now, reset calendar to default state:
990
        if ($zone) {
991
            $c->setTimeZone($zone);
992
        }
993
994
        if ($time instanceof \DateTime) {
995
            // FIXME: we can't use $time->getTimezone()->getName() here since that triggers
996
            // https://github.com/facebook/hhvm/issues/1777 but luckily using format('e')
997
            // works for both php and hhvm
998
            $tzName = $time->format('e');
999
1000
            if (preg_match('/^[+-0-9]/', $tzName)) {
1001
                $tzName = 'GMT' . $tzName;
1002
            }
1003
            $c->setTimeZone($this->createTimeZone($tzName));
1004
            $dateStr = $time->format('Y z G i s');
1005
            list($year, $doy, $hour, $minute, $second) = explode(' ', $dateStr);
1006
            $c->set(DateDefinitions::YEAR, $year);
1007
            $c->set(DateDefinitions::DAY_OF_YEAR, $doy + 1);
1008
            $c->set(DateDefinitions::HOUR_OF_DAY, $hour);
1009
            $c->set(DateDefinitions::MINUTE, $minute);
1010
            $c->set(DateDefinitions::SECOND, $second);
1011
1012
            // complete the calendar
1013
            $c->getAll();
1014
        } else {
1015
            $c->setTime($time); // let the new calendar have the current time.
1016
        }
1017
1018
        return $c;
1019
    }
1020
    
1021
    /**
1022
     * Creates a new date format instance with the given format.
1023
     *
1024
     * @param      string $format The date format.
1025
     *
1026
     * @return     DateFormat The dateformat instance.
1027
     *
1028
     * @author     Dominik del Bondio <[email protected]>
1029
     * @since      0.11.0
1030
     */
1031
    public function createDateFormat($format)
1032
    {
1033
        $dateFormat = new DateFormat($format);
1034
        $dateFormat->initialize($this->getContext());
1035
        return $dateFormat;
1036
    }
1037
    
1038
1039
    /**
1040
     * Returns the stored information from the ldml supplemental data about a
1041
     * territory.
1042
     *
1043
     * @param      string $country The uppercase 2 letter country iso code.
1044
     *
1045
     * @return     array The data.
1046
     *
1047
     * @author     Dominik del Bondio <[email protected]>
1048
     * @since      0.11.0
1049
     */
1050
    public function getTerritoryData($country)
1051
    {
1052
        if (!isset($this->supplementalData['territories'][$country])) {
1053
            return array();
1054
        }
1055
        return $this->supplementalData['territories'][$country];
1056
    }
1057
1058
    /**
1059
     * Returns an array containing digits and rounding information for a currency.
1060
     *
1061
     * @param      string $currency The uppercase 3 letter currency iso code.
1062
     *
1063
     * @return     array The data.
1064
     *
1065
     * @author     Dominik del Bondio <[email protected]>
1066
     * @since      0.11.0
1067
     */
1068
    public function getCurrencyFraction($currency)
1069
    {
1070
        if (!isset($this->supplementalData['fractions'][$currency])) {
1071
            return $this->supplementalData['fractions']['DEFAULT'];
1072
        }
1073
        return $this->supplementalData['fractions'][$currency];
1074
    }
1075
}
1076