Completed
Pull Request — master (#536)
by
unknown
07:35
created

Locale::loadLocale()   D

Complexity

Conditions 9
Paths 39

Size

Total Lines 43
Code Lines 33

Duplication

Lines 13
Ratio 30.23 %

Code Coverage

Tests 18
CRAP Score 13.4201

Importance

Changes 0
Metric Value
cc 9
eloc 33
c 0
b 0
f 0
nc 39
nop 2
dl 13
loc 43
rs 4.909
ccs 18
cts 29
cp 0.6207
crap 13.4201
1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 */
11
12
namespace Xoops;
13
14
use Punic\Data;
15
use Punic\Exception\InvalidLocale;
16
use Xoops\Core\HttpRequest;
17
use Xmf\Request;
18
use Xoops\Core\Theme\XoopsTheme;
19
use Xoops\Locale\MessageFormatter;
20
21
/**
22
 * Locale
23
 *
24
 * @author    trabis <[email protected]>
25
 * @copyright 2011-2015 XOOPS Project (http://xoops.org)
26
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
27
 */
28
class Locale
29
{
30
    const FALLBACK_LOCALE = 'en_US';
31
32
    protected static $currentLocale = null;
33
    protected static $currentTimeZone = null;
34
    protected static $defaultTimeZone = null;
35
    protected static $systemTimeZone = null;
36
    protected static $userLocales = array();
37
38
    /**
39
     * get the current active locale
40
     *
41
     * @return string current locale
42
     */
43 31
    public static function getCurrent()
44
    {
45
        // if none set, take the top of the user locales
46 31
        if (null === static::$currentLocale) {
47 1
            $localeArray = static::getUserLocales();
48 1
            static::$currentLocale = reset($localeArray);
49
        }
50 31
        return static::$currentLocale;
51
    }
52
53
    /**
54
     * Set the current locale
55
     *
56
     * @param string $locale local code
57
     *
58
     * @return void
59
     *
60
     * @throws InvalidLocale
61
     */
62 53
    public static function setCurrent($locale)
63
    {
64 53
        Data::setDefaultLocale($locale);
65 53
        static::$currentLocale = static::normalizeLocale($locale);
66 53
    }
67
68
    /**
69
     * Get the current timezone
70
     *
71
     * @return \DateTimeZone current timezone
72
     */
73 41
    public static function getTimeZone()
74
    {
75 41
        if (null === static::$currentTimeZone) {
76 1
            $xoops = \Xoops::getInstance();
77 1
            static::$currentTimeZone = static::getDefaultTimeZone();
78 1
            if ($xoops->isUser()) {
79
                $tz = $xoops->user->timezone();
80
                if (is_a($tz, '\DateTimeZone')) {
81
                    static::$currentTimeZone = $tz;
82
                } elseif (is_string($tz)) {
83
                    static::$currentTimeZone = static::newDateTimeZone($tz);
84
                }
85
            }
86
        }
87 41
        return static::$currentTimeZone;
88
    }
89
90
    /**
91
     * Set the current timezone
92
     *
93
     * @param \DateTimeZone $timeZone
94
     *
95
     * @return void
96
     */
97 53
    public static function setTimeZone(\DateTimeZone $timeZone)
98
    {
99 53
        static::$currentTimeZone = $timeZone;
100 53
    }
101
102
    /**
103
     * Instantiate a new DateTimeZone object for a timezone name, with fallback to UTC on error
104
     *
105
     * @param string $timeZoneName name of timezone
106
     *
107
     * @return \DateTimeZone
108
     */
109 1
    protected static function newDateTimeZone($timeZoneName)
110
    {
111
        try {
112 1
            $timeZone = new \DateTimeZone($timeZoneName);
113
        } catch (\Exception $e) {
114
            $timeZone = new \DateTimeZone('UTC');
115
        }
116
117 1
        return $timeZone;
118
    }
119
120
    /**
121
     * Get the default timezone as set in default_TZ config
122
     *
123
     * @return \DateTimeZone
124
     */
125 1 View Code Duplication
    public static function getDefaultTimeZone()
126
    {
127 1
        if (null === static::$defaultTimeZone) {
128 1
            $tz = \Xoops::getInstance()->getConfig('default_TZ');
129 1
            if (is_numeric($tz)) {
130
                $tz = 'UTC';
131
            }
132 1
            static::$defaultTimeZone = static::newDateTimeZone($tz);
133
        }
134 1
        return static::$defaultTimeZone;
135
    }
136
137
    /**
138
     * Get the server timezone as set in server_TZ config
139
     *
140
     * @return \DateTimeZone
141
     */
142 View Code Duplication
    public static function getSystemTimeZone()
143
    {
144
        if (null === static::$systemTimeZone) {
145
            $tz = \Xoops::getInstance()->getConfig('server_TZ');
146
            if (is_numeric($tz)) {
147
                $tz = 'UTC';
148
            }
149
            static::$systemTimeZone = static::newDateTimeZone($tz);
150
        }
151
        return static::$systemTimeZone;
152
    }
153
154
    /**
155
     * @param string $name Name of language file to be loaded, without extension
156
     * @param mixed $domain string: Module dirname; global language file will be loaded if
157
     *                                 $domain is set to 'global' or not specified
158
     *                         array:  example; array('Frameworks/moduleclasses/moduleadmin')
159
     * @param string $language Language to be loaded, current language content will be loaded if not specified
160
     *
161
     * @return  boolean
162
     */
163 1
    public static function loadLanguage($name, $domain = '', $language = null)
164
    {
165 1
        if (empty($name)) {
166 1
            return false;
167
        }
168 1
        $language = empty($language) ? \XoopsLocale::getLegacyLanguage() : $language;
169
        // expanded domain to multiple categories, e.g. module:system, framework:filter, etc.
170 1 View Code Duplication
        if ((empty($domain) || 'global' === $domain)) {
171 1
            $path = '';
172
        } else {
173
            $path = (is_array($domain)) ? array_shift($domain) : "modules/{$domain}";
174
        }
175 1
        $xoops = \Xoops::getInstance();
176 1
        $fullPath = $xoops->path("{$path}/language/{$language}/{$name}.php");
177 1
        if (!$ret = \XoopsLoad::loadFile($fullPath)) {
178 1
            $fullPath2 = $xoops->path("{$path}/language/english/{$name}.php");
179 1
            $ret = \XoopsLoad::loadFile($fullPath2);
180
        }
181 1
        return $ret;
182
    }
183
184
    /**
185
     * @param string $domain module dirname to load, if null will load global locale
186
     * @param string $forcedLocale Locale to be loaded, current language content will be loaded if not specified
187
     *
188
     * @return  boolean
189
     */
190 5
    public static function loadLocale($domain = null, $forcedLocale = null)
191
    {
192 5
        $xoops = \Xoops::getInstance();
193
        // expanded domain to multiple categories, e.g. module:system, framework:filter, etc.
194 5 View Code Duplication
        if ($domain === null) {
195 1
            $path = '';
196 1
            $domain = 'xoops';
197
        } else {
198 4
            $path = (is_array($domain)) ? array_shift($domain) : "modules/{$domain}";
199
        }
200 5
        if (null !== $forcedLocale) {
201
            try {
202
                Data::setDefaultLocale($locale);
203
            } catch (InvalidLocale $e) {
0 ignored issues
show
Bug introduced by
The class Punic\Exception\InvalidLocale does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
204
                return false;
205
            }
206
            $locales = [$forcedLocale];
207
            $locale = $forcedLocale;
208
        } else {
209 5
            $locales = self::getUserLocales();
210 5
            $locale = reset($locales);
211
            try {
212 5
                Data::setDefaultLocale($locale);
213
            } catch (InvalidLocale $e) {
0 ignored issues
show
Bug introduced by
The class Punic\Exception\InvalidLocale does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
214
                $locale = static::FALLBACK_LOCALE;
215
                array_shift($locales);
216
                array_unshift($locales, $locale);
217
                Data::setDefaultLocale($locale);
218
            }
219
        }
220 5
        foreach ($locales as $locale) {
221 5
            $fullPath = $xoops->path("{$path}/locale/{$locale}/locale.php");
222 5
            $fullPath2 = $xoops->path("{$path}/locale/{$locale}/{$locale}.php");
223 5 View Code Duplication
            if (\XoopsLoad::fileExists($fullPath)) {
224 5
                \XoopsLoad::addMap(array($domain . 'locale' => $fullPath));
225 5
                if (\XoopsLoad::fileExists($fullPath2)) {
226 5
                    \XoopsLoad::addMap(array(strtolower($domain . "locale{$locale}") => $fullPath2));
227
                }
228 5
                return true;
229
            }
230
        }
231
        return false;
232
    }
233
234
    /**
235
     * load locale for theme
236
     *
237
     * @param XoopsTheme $theme
238
     *
239
     * @return bool
240
     */
241 4
    public static function loadThemeLocale(XoopsTheme $theme)
242
    {
243 4
        $xoops = \Xoops::getInstance();
244 4
        $locales = self::getUserLocales();
245 4
        foreach ($locales as $locale) {
246 4
            $fullPath = $xoops->path($theme->resourcePath("locale/{$locale}/locale.php"));
247 4
            $fullPath2 = $xoops->path($theme->resourcePath("locale/{$locale}/{$locale}.php"));
248 4 View Code Duplication
            if (\XoopsLoad::fileExists($fullPath)) {
249 4
                \XoopsLoad::addMap(array(strtolower($theme->folderName . 'ThemeLocale') => $fullPath));
250 4
                if (\XoopsLoad::fileExists($fullPath2)) {
251 4
                    \XoopsLoad::addMap(array(strtolower($theme->folderName . "ThemeLocale{$locale}") => $fullPath2));
252
                }
253 4
                return true;
254
            }
255
        }
256
        return false;
257
    }
258
259
    /**
260
     * @return  boolean
261
     */
262 2
    public static function loadMailerLocale()
263
    {
264 2
        $xoops = \Xoops::getInstance();
265 2
        $locales = self::getUserLocales();
266 2
        foreach ($locales as $locale) {
267 2
            $fullPath = $xoops->path("locale/{$locale}/mailer.php");
268 2
            if (\XoopsLoad::fileExists($fullPath)) {
269 2
                \XoopsLoad::addMap(array(strtolower('XoopsMailerLocale') => $fullPath));
270 2
                return true;
271
            }
272
        }
273
        return false;
274
    }
275
276
    /**
277
     * @param string $key
278
     * @param string $dirname
279
     * @param array  $params
280
     * @return string
281
     */
282 3
    public static function translate($key, $dirname = 'xoops', $params = [])
283
    {
284 3
        $class = self::getClassFromDirname($dirname);
285 3
        $message = self::getMessage($class, $key);
286 3
        return self::format($message, $params, self::getCurrent());
287
    }
288
289
    /**
290
     * @param string $key
291
     * @param string $dirname
292
     * @param array  $params
293
     * @return string
294
     */
295 1
    public static function translateTheme($key, $dirname = '', $params = [])
296
    {
297 1
        $class = self::getThemeClassFromDirname($dirname);
298 1
        $message = self::getMessage($class, $key);
299 1
        return self::format($message, $params, self::getCurrent());
300
    }
301
302
    /**
303
     * Returns the raw translation
304
     *
305
     * @param string $class
306
     * @param string $key
307
     * @return string
308
     */
309 4
    private static function getMessage($class, $key) {
310 4
        if (defined("$class::$key")) {
311 1
            return constant("$class::$key");
312 3
        } elseif (defined($key)) {
313
            return constant($key);
314
        }
315 3
        return $key;
316
    }
317
318
    /**
319
     * Formats a message using [[MessageFormatter]].
320
     *
321
     * @copyright Copyright (c) 2008 Yii Software LLC
322
     *
323
     * @param string $message the message to be formatted.
324
     * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
325
     * @param string $language the language code (e.g. `en-US`, `en`).
326
     * @return string the formatted message.
327
     */
328 4
    private static function format($message, $params, $language)
329
    {
330 4
        $params = (array)$params;
331 4
        if ($params === []) {
332 4
            return $message;
333
        }
334
335
        if (preg_match('~{\s*[\d\w]+\s*,~u', $message)) {
336
            $formatter = self::getMessageFormatter();
337
            $result = $formatter->format($message, $params, $language);
338
            if ($result === false) {
339
                $errorMessage = $formatter->getErrorMessage();
340
                \Xoops::getInstance()->logger()->warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.", [__METHOD__]);
341
                return $message;
342
            } else {
343
                return $result;
344
            }
345
        }
346
347
        $p = [];
348
        foreach ($params as $name => $value) {
349
            $p['{' . $name . '}'] = $value;
350
        }
351
352
        return strtr($message, $p);
353
    }
354
355
    /**
356
     * Returns the message formatter instance.
357
     * @return MessageFormatter the message formatter to be used to format message via ICU message format.
358
     */
359
    private static function getMessageFormatter()
360
    {
361
        static $messageFormatter = null;
362
        if ($messageFormatter === null) {
363
            $messageFormatter = new MessageFormatter();
364
        }
365
        return $messageFormatter;
366
    }
367
368
369
    /**
370
     * @param string $dirname
371
     *
372
     * @return string
373
     */
374 3
    protected static function getClassFromDirname($dirname)
375
    {
376 3
        return ucfirst($dirname) . 'Locale';
377
    }
378
379
    /**
380
     * @param string $dirname
381
     *
382
     * @return string
383
     */
384 1
    protected static function getThemeClassFromDirname($dirname = '')
385
    {
386 1
        if (!$dirname) {
387 1
            $dirname = \Xoops::getInstance()->theme()->folderName;
388
        }
389 1
        return ucfirst($dirname) . 'ThemeLocale';
390
    }
391
392
    /**
393
     * getUserLocales()
394
     * Returns the user locales
395
     * Normally it returns an array like this:
396
     * 1. Forced language
397
     * 2. Language in $_GET['lang']
398
     * 3. Language in $_SESSION['lang']
399
     * 4. HTTP_ACCEPT_LANGUAGE
400
     * 5. Fallback language
401
     * Note: duplicate values are deleted.
402
     *
403
     * @return array with the user locales sorted by priority. Highest is best.
404
     */
405 11
    public static function getUserLocales()
0 ignored issues
show
Coding Style introduced by
getUserLocales uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
406
    {
407 11
        if (empty(self::$userLocales)) {
408
            // reset user_lang array
409
            $userLocales = array();
410
411
            // Highest priority: forced language
412
            //if ($this->forcedLang != NULL) {
413
            //    $userLocales[] = $this->forcedLang;
414
            //}
415
416
            // 2nd highest priority: GET parameter 'lang'
417
            $requestLocale = self::normalizeLocale(Request::getString('lang', ''));
418
            if (!empty($requestLocale)) {
419
                $userLocales[] = $requestLocale;
420
            }
421
422
            // 3rd highest priority: SESSION parameter 'lang'
423
            if (isset($_SESSION['lang']) && is_string($_SESSION['lang'])) {
424
                $userLocales[] = self::normalizeLocale($_SESSION['lang']);
425
            }
426
427
            // 4th highest priority: HTTP_ACCEPT_LANGUAGE
428
            $browserLocales = HttpRequest::getInstance()->getAcceptedLanguages();
429
            $browserLocales = array_keys($browserLocales);
430
            foreach ($browserLocales as $bloc) {
431
                $userLocales[] = self::normalizeLocale($bloc);
432
            }
433
434
            $configLocale = \Xoops::getInstance()->getConfig('locale');
435
            if (!empty($configLocale)) {
436
                $userLocales[] = $configLocale;
437
            }
438
439
            // Lowest priority: fallback
440
            $userLocales[] = static::FALLBACK_LOCALE;
441
442
            static::$userLocales = array_unique($userLocales);
443
        }
444 11
        return static::$userLocales;
445
    }
446
447
    /**
448
     * Convert a locale designation to a normal form ll_Ssss_CC, where
449
     *   ll   is language code
450
     *   Ssss is the script code, if specified
451
     *   CC   is the country code, if specified
452
     *
453
     * @param string $locale locale code
454
     * @param string $separator string to use to join locale parts
455
     * @param bool $withScript include script if specified, always remove if false
456
     *
457
     * @return string normalized locale, or empty string on error
458
     */
459 70
    public static function normalizeLocale($locale, $separator = '_', $withScript = true)
460
    {
461
        try {
462 70
            $keys = Data::explodeLocale($locale);
463 70
            $key = strtolower($keys['language']);
464 70
            $key .= (empty($keys['script']) || false === $withScript) ?
465 70
                '' : $separator . ucfirst(strtolower($keys['script']));
466 70
            $key .= empty($keys['territory']) ? '' : $separator . strtoupper($keys['territory']);
467
        } catch (InvalidLocale $e) {
0 ignored issues
show
Bug introduced by
The class Punic\Exception\InvalidLocale does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
468
            $key = '';
469
        }
470
471 70
        return $key;
472
    }
473
474
    /**
475
     * Return a normalized form of a resource domain. A resource domain is always lowercase.
476
     *
477
     * @param string $domain resource domain (usually a module dirname)
478
     *
479
     * @return string normalized resource domain
480
     */
481 1
    public static function normalizeDomain($domain)
482
    {
483 1
        return strtolower($domain);
484
    }
485
}
486