Completed
Push — master ( a47eb4...10fc3d )
by Richard
06:23
created

Locale::getMessage()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
ccs 5
cts 6
cp 0.8333
crap 3.0416
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 30
    public static function getCurrent()
44
    {
45
        // if none set, take the top of the user locales
46 30
        if (null === static::$currentLocale) {
47 1
            $localeArray = static::getUserLocales();
48 1
            static::$currentLocale = reset($localeArray);
49
        }
50 30
        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 52
    public static function setCurrent($locale)
63
    {
64 52
        Data::setDefaultLocale($locale);
65 52
        static::$currentLocale = static::normalizeLocale($locale);
66 52
    }
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 52
    public static function setTimeZone(\DateTimeZone $timeZone)
98
    {
99 52
        static::$currentTimeZone = $timeZone;
100 52
    }
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);
0 ignored issues
show
Bug introduced by
The variable $locale seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
203
            } catch (InvalidLocale $e) {
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) {
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 3
            return $message;
333
        }
334
335 1
        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 1
        $p = [];
348 1
        foreach ($params as $name => $value) {
349 1
            $p['{' . $name . '}'] = $value;
350
        }
351
352 1
        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
     * @param string $dirname
370
     *
371
     * @return string
372
     */
373 3
    protected static function getClassFromDirname($dirname)
374
    {
375 3
        return ucfirst($dirname) . 'Locale';
376
    }
377
378
    /**
379
     * @param string $dirname
380
     *
381
     * @return string
382
     */
383 1
    protected static function getThemeClassFromDirname($dirname = '')
384
    {
385 1
        if (!$dirname) {
386 1
            $dirname = \Xoops::getInstance()->theme()->folderName;
387
        }
388 1
        return ucfirst($dirname) . 'ThemeLocale';
389
    }
390
391
    /**
392
     * getUserLocales()
393
     * Returns the user locales
394
     * Normally it returns an array like this:
395
     * 1. Forced language
396
     * 2. Language in $_GET['lang']
397
     * 3. Language in $_SESSION['lang']
398
     * 4. HTTP_ACCEPT_LANGUAGE
399
     * 5. Fallback language
400
     * Note: duplicate values are deleted.
401
     *
402
     * @return array with the user locales sorted by priority. Highest is best.
403
     */
404 11
    public static function getUserLocales()
405
    {
406 11
        if (empty(self::$userLocales)) {
407
            // reset user_lang array
408
            $userLocales = array();
409
410
            // Highest priority: forced language
411
            //if ($this->forcedLang != NULL) {
412
            //    $userLocales[] = $this->forcedLang;
413
            //}
414
415
            // 2nd highest priority: GET parameter 'lang'
416
            $requestLocale = self::normalizeLocale(Request::getString('lang', ''));
417
            if (!empty($requestLocale)) {
418
                $userLocales[] = $requestLocale;
419
            }
420
421
            // 3rd highest priority: SESSION parameter 'lang'
422
            if (isset($_SESSION['lang']) && is_string($_SESSION['lang'])) {
423
                $userLocales[] = self::normalizeLocale($_SESSION['lang']);
424
            }
425
426
            // 4th highest priority: HTTP_ACCEPT_LANGUAGE
427
            $browserLocales = HttpRequest::getInstance()->getAcceptedLanguages();
428
            $browserLocales = array_keys($browserLocales);
429
            foreach ($browserLocales as $bloc) {
430
                $userLocales[] = self::normalizeLocale($bloc);
431
            }
432
433
            $configLocale = \Xoops::getInstance()->getConfig('locale');
434
            if (!empty($configLocale)) {
435
                $userLocales[] = $configLocale;
436
            }
437
438
            // Lowest priority: fallback
439
            $userLocales[] = static::FALLBACK_LOCALE;
440
441
            static::$userLocales = array_unique($userLocales);
442
        }
443 11
        return static::$userLocales;
444
    }
445
446
    /**
447
     * Convert a locale designation to a normal form ll_Ssss_CC, where
448
     *   ll   is language code
449
     *   Ssss is the script code, if specified
450
     *   CC   is the country code, if specified
451
     *
452
     * @param string $locale     locale code
453
     * @param string $separator  string to use to join locale parts
454
     * @param bool   $withScript include script if specified, always remove if false
455
     *
456
     * @return string normalized locale, or empty string on error
457
     */
458 69
    public static function normalizeLocale($locale, $separator = '_', $withScript = true)
459
    {
460
        try {
461 69
            $keys = Data::explodeLocale($locale);
462 69
            $key = strtolower($keys['language']);
463 69
            $key .= (empty($keys['script']) || false === $withScript) ?
464 69
                '' : $separator . ucfirst(strtolower($keys['script']));
465 69
            $key .= empty($keys['territory']) ? '' : $separator . strtoupper($keys['territory']);
466
        } catch (InvalidLocale $e) {
467
            $key = '';
468
        }
469
470 69
        return $key;
471
    }
472
473
    /**
474
     * Return a normalized form of a resource domain. A resource domain is always lowercase.
475
     *
476
     * @param string $domain resource domain (usually a module dirname)
477
     *
478
     * @return string normalized resource domain
479
     */
480 1
    public static function normalizeDomain($domain)
481
    {
482 1
        return strtolower($domain);
483
    }
484
}
485