Completed
Pull Request — master (#563)
by Richard
08:33
created

Locale::loadMailerLocale()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 0
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
ccs 8
cts 9
cp 0.8889
crap 3.0123
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 38
    public static function getCurrent()
44
    {
45
        // if none set, take the top of the user locales
46 38
        if (null === static::$currentLocale) {
47 1
            $localeArray = static::getUserLocales();
48 1
            static::$currentLocale = reset($localeArray);
49
        }
50 38
        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 47
    public static function getTimeZone()
74
    {
75 47
        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 47
        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
    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
    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
        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
        if ($domain === null) {
195 1
            $path = '';
196 1
            $domain = 'xoops';
197
        } else {
198 4
            $path = (is_array($domain)) ? array_shift($domain) : "modules/{$domain}";
0 ignored issues
show
introduced by
The condition is_array($domain) can never be true.
Loading history...
199
        }
200 5
        if (null !== $forcedLocale) {
201
            try {
202
                Data::setDefaultLocale($locale);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $locale seems to be never defined.
Loading history...
203
            } catch (InvalidLocale $e) {
204
                return false;
205
            }
206
            $locales = [$forcedLocale];
207
            $locale = $forcedLocale;
0 ignored issues
show
Unused Code introduced by
The assignment to $locale is dead and can be removed.
Loading history...
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
            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
            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 76
    public static function normalizeLocale($locale, $separator = '_', $withScript = true)
459
    {
460
        try {
461 76
            $keys = Data::explodeLocale($locale);
462 76
            $key = strtolower($keys['language']);
463 76
            $key .= (empty($keys['script']) || false === $withScript) ?
464 76
                '' : $separator . ucfirst(strtolower($keys['script']));
465 76
            $key .= empty($keys['territory']) ? '' : $separator . strtoupper($keys['territory']);
466
        } catch (InvalidLocale $e) {
467
            $key = '';
468
        }
469
470 76
        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