Completed
Pull Request — master (#482)
by Richard
17:16 queued 06:22
created

Locale   C

Complexity

Total Complexity 62

Size/Duplication

Total Lines 400
Duplicated Lines 17 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 58.19%

Importance

Changes 6
Bugs 2 Features 0
Metric Value
wmc 62
lcom 2
cbo 6
dl 68
loc 400
rs 5.9493
c 6
b 2
f 0
ccs 103
cts 177
cp 0.5819

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setTimeZone() 0 4 1
A getClassFromDirname() 0 4 1
A getCurrent() 0 9 2
A setCurrent() 0 5 1
B getTimeZone() 0 16 5
A newDateTimeZone() 0 10 2
A getDefaultTimeZone() 11 11 3
A getSystemTimeZone() 11 11 3
B loadLanguage() 5 20 7
D loadLocale() 13 43 9
A loadThemeLocale() 7 17 4
A loadMailerLocale() 0 13 3
A translate() 10 10 3
A translateTheme() 11 11 3
A getThemeClassFromDirname() 0 7 2
C getUserLocales() 0 41 7
B normalizeLocale() 0 14 5
A normalizeDomain() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Locale often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Locale, and based on these observations, apply Extract Interface, too.

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
20
/**
21
 * Locale
22
 *
23
 * @author    trabis <[email protected]>
24
 * @copyright 2011-2015 XOOPS Project (http://xoops.org)
25
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
26
 */
27
class Locale
28
{
29
    const FALLBACK_LOCALE = 'en_US';
30
31
    protected static $currentLocale = null;
32
    protected static $currentTimeZone = null;
33
    protected static $defaultTimeZone = null;
34
    protected static $systemTimeZone = null;
35
    protected static $userLocales = array();
36
37
    /**
38
     * get the current active locale
39
     *
40
     * @return string current locale
41
     */
42 24
    public static function getCurrent()
43
    {
44
        // if none set, take the top of the user locales
45 24
        if (null === static::$currentLocale) {
46 1
            $localeArray = static::getUserLocales();
47 1
            static::$currentLocale = reset($localeArray);
48 1
        }
49 24
        return static::$currentLocale;
50
    }
51
52
    /**
53
     * Set the current locale
54
     *
55
     * @param string $locale local code
56
     *
57
     * @return void
58
     *
59
     * @throws InvalidLocale
60
     */
61 53
    public static function setCurrent($locale)
62
    {
63 53
        Data::setDefaultLocale($locale);
64 53
        static::$currentLocale = static::normalizeLocale($locale);
65 53
    }
66
67
    /**
68
     * Get the current timezone
69
     *
70
     * @return \DateTimeZone current timezone
71
     */
72 38
    public static function getTimeZone()
73
    {
74 38
        if (null === static::$currentTimeZone) {
75
            $xoops = \Xoops::getInstance();
76
            static::$currentTimeZone = static::getDefaultTimeZone();
77
            if ($xoops->isUser()) {
78
                $tz = $xoops->user->timezone();
79
                if (is_a($tz, '\DateTimeZone')) {
80
                    static::$currentTimeZone = $tz;
81
                } elseif (is_string($tz)) {
82
                    static::$currentTimeZone = static::newDateTimeZone($tz);
83
                }
84
            }
85
        }
86 38
        return static::$currentTimeZone;
87
    }
88
89
    /**
90
     * Set the current timezone
91
     *
92
     * @param \DateTimeZone $timeZone
93
     *
94
     * @return void
95
     */
96 53
    public static function setTimeZone(\DateTimeZone $timeZone)
97
    {
98 53
        static::$currentTimeZone = $timeZone;
99 53
    }
100
101
    /**
102
     * Instantiate a new DateTimeZone object for a timezone name, with fallback to UTC on error
103
     *
104
     * @param string $timeZoneName name of timezone
105
     *
106
     * @return \DateTimeZone
107
     */
108
    protected static function newDateTimeZone($timeZoneName)
109
    {
110
        try {
111
            $timeZone = new \DateTimeZone($timeZoneName);
112
        } catch (\Exception $e) {
113
            $timeZone = new \DateTimeZone('UTC');
114
        }
115
116
        return $timeZone;
117
    }
118
119
    /**
120
     * Get the default timezone as set in default_TZ config
121
     *
122
     * @return \DateTimeZone
123
     */
124 View Code Duplication
    public static function getDefaultTimeZone()
125
    {
126
        if (null === static::$defaultTimeZone) {
127
            $tz = \Xoops::getInstance()->getConfig('default_TZ');
128
            if (is_numeric($tz)) {
129
                $tz = 'UTC';
130
            }
131
            static::$defaultTimeZone = static::newDateTimeZone($tz);
132
        }
133
        return static::$defaultTimeZone;
134
    }
135
136
    /**
137
     * Get the server timezone as set in server_TZ config
138
     *
139
     * @return \DateTimeZone
140
     */
141 View Code Duplication
    public static function getSystemTimeZone()
142
    {
143
        if (null === static::$systemTimeZone) {
144
            $tz = \Xoops::getInstance()->getConfig('server_TZ');
145
            if (is_numeric($tz)) {
146
                $tz = 'UTC';
147
            }
148
            static::$systemTimeZone = static::newDateTimeZone($tz);
149
        }
150
        return static::$systemTimeZone;
151
    }
152
153
    /**
154
     * @param string $name     Name of language file to be loaded, without extension
155
     * @param mixed  $domain   string: Module dirname; global language file will be loaded if
156
     *                                 $domain is set to 'global' or not specified
157
     *                         array:  example; array('Frameworks/moduleclasses/moduleadmin')
158
     * @param string $language Language to be loaded, current language content will be loaded if not specified
159
     *
160
     * @return  boolean
161
     */
162 1
    public static function loadLanguage($name, $domain = '', $language = null)
163
    {
164 1
        if (empty($name)) {
165 1
            return false;
166
        }
167 1
        $language = empty($language) ? \XoopsLocale::getLegacyLanguage() : $language;
168
        // expanded domain to multiple categories, e.g. module:system, framework:filter, etc.
169 1 View Code Duplication
        if ((empty($domain) || 'global' === $domain)) {
170 1
            $path = '';
171 1
        } else {
172
            $path = (is_array($domain)) ? array_shift($domain) : "modules/{$domain}";
173
        }
174 1
        $xoops = \Xoops::getInstance();
175 1
        $fullPath = $xoops->path("{$path}/language/{$language}/{$name}.php");
176 1
        if (!$ret = \XoopsLoad::loadFile($fullPath)) {
177 1
            $fullPath2 = $xoops->path("{$path}/language/english/{$name}.php");
178 1
            $ret = \XoopsLoad::loadFile($fullPath2);
179 1
        }
180 1
        return $ret;
181
    }
182
183
    /**
184
     * @param string $domain       module dirname to load, if null will load global locale
185
     * @param string $forcedLocale Locale to be loaded, current language content will be loaded if not specified
186
     *
187
     * @return  boolean
188
     */
189 4
    public static function loadLocale($domain = null, $forcedLocale = null)
190
    {
191 4
        $xoops = \Xoops::getInstance();
192
        // expanded domain to multiple categories, e.g. module:system, framework:filter, etc.
193 4 View Code Duplication
        if ($domain === null) {
194 1
            $path = '';
195 1
            $domain = 'xoops';
196 1
        } else {
197 3
            $path = (is_array($domain)) ? array_shift($domain) : "modules/{$domain}";
198
        }
199 4
        if (null !== $forcedLocale) {
200
            try {
201
                Data::setDefaultLocale($locale);
202
            } 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...
203
                return false;
204
            }
205
            $locales = [$forcedLocale];
206
            $locale = $forcedLocale;
207
        } else {
208 4
            $locales = self::getUserLocales();
209 4
            $locale = reset($locales);
210
            try {
211 4
                Data::setDefaultLocale($locale);
212 4
            } 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...
213
                $locale = static::FALLBACK_LOCALE;
214
                array_shift($locales);
215
                array_unshift($locales, $locale);
216
                Data::setDefaultLocale($locale);
217
            }
218
        }
219 4
        foreach ($locales as $locale) {
220 4
            $fullPath = $xoops->path("{$path}/locale/{$locale}/locale.php");
221 4
            $fullPath2 = $xoops->path("{$path}/locale/{$locale}/{$locale}.php");
222 4 View Code Duplication
            if (\XoopsLoad::fileExists($fullPath)) {
223 4
                \XoopsLoad::addMap(array($domain . 'locale' => $fullPath));
224 4
                if (\XoopsLoad::fileExists($fullPath2)) {
225 4
                    \XoopsLoad::addMap(array(strtolower($domain . "locale{$locale}") => $fullPath2));
226 4
                }
227 4
                return true;
228
            }
229 4
        }
230
        return false;
231
    }
232
233
    /**
234
     * load locale for theme
235
     *
236
     * @param XoopsTheme $theme
237
     *
238
     * @return bool
239
     */
240 4
    public static function loadThemeLocale(XoopsTheme $theme)
241
    {
242 4
        $xoops = \Xoops::getInstance();
243 4
        $locales = self::getUserLocales();
244 4
        foreach ($locales as $locale) {
245 4
            $fullPath = $xoops->path($theme->resourcePath("locale/{$locale}/locale.php"));
246 4
            $fullPath2 = $xoops->path($theme->resourcePath("locale/{$locale}/{$locale}.php"));
247 4 View Code Duplication
            if (\XoopsLoad::fileExists($fullPath)) {
248 4
                \XoopsLoad::addMap(array(strtolower($theme->folderName . 'ThemeLocale') => $fullPath));
249 4
                if (\XoopsLoad::fileExists($fullPath2)) {
250 4
                    \XoopsLoad::addMap(array(strtolower($theme->folderName . "ThemeLocale{$locale}") => $fullPath2));
251 4
                }
252 4
                return true;
253
            }
254 4
        }
255
        return false;
256
    }
257
258
    /**
259
     * @return  boolean
260
     */
261 2
    public static function loadMailerLocale()
262
    {
263 2
        $xoops = \Xoops::getInstance();
264 2
        $locales = self::getUserLocales();
265 2
        foreach ($locales as $locale) {
266 2
            $fullPath = $xoops->path("locale/{$locale}/mailer.php");
267 2
            if (\XoopsLoad::fileExists($fullPath)) {
268 2
                \XoopsLoad::addMap(array(strtolower('XoopsMailerLocale') => $fullPath));
269 2
                return true;
270
            }
271 2
        }
272
        return false;
273
    }
274
275
    /**
276
     * @param string $key
277
     * @param string $dirname
278
     *
279
     * @return string
280
     */
281 3 View Code Duplication
    public static function translate($key, $dirname = 'xoops')
282
    {
283 3
        $class = self::getClassFromDirname($dirname);
284 3
        if (defined("$class::$key")) {
285 1
            return constant("$class::$key");
286 2
        } elseif (defined($key)) {
287
            return constant($key);
288
        }
289 2
        return $key;
290
    }
291
292
    /**
293
     * @param string $key
294
     * @param string $dirname
295
     *
296
     * @return string
297
     */
298 1 View Code Duplication
    public static function translateTheme($key, $dirname = '')
299
    {
300 1
        $class = self::getThemeClassFromDirname($dirname);
301
302 1
        if (defined("$class::$key")) {
303
            return constant("$class::$key");
304 1
        } elseif (defined($key)) {
305
            return constant($key);
306
        }
307 1
        return $key;
308
    }
309
310
    /**
311
     * @param string $dirname
312
     *
313
     * @return string
314
     */
315 3
    protected static function getClassFromDirname($dirname)
316
    {
317 3
        return ucfirst($dirname) . 'Locale';
318
    }
319
320
    /**
321
     * @param string $dirname
322
     *
323
     * @return string
324
     */
325 1
    protected static function getThemeClassFromDirname($dirname = '')
326
    {
327 1
        if (!$dirname) {
328 1
            $dirname = \Xoops::getInstance()->theme()->folderName;
329 1
        }
330 1
        return ucfirst($dirname) . 'ThemeLocale';
331
    }
332
333
    /**
334
     * getUserLocales()
335
     * Returns the user locales
336
     * Normally it returns an array like this:
337
     * 1. Forced language
338
     * 2. Language in $_GET['lang']
339
     * 3. Language in $_SESSION['lang']
340
     * 4. HTTP_ACCEPT_LANGUAGE
341
     * 5. Fallback language
342
     * Note: duplicate values are deleted.
343
     *
344
     * @return array with the user locales sorted by priority. Highest is best.
345
     */
346 10
    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...
347
    {
348 10
        if (empty(self::$userLocales)) {
349
            // reset user_lang array
350
            $userLocales = array();
351
352
            // Highest priority: forced language
353
            //if ($this->forcedLang != NULL) {
354
            //    $userLocales[] = $this->forcedLang;
355
            //}
356
357
            // 2nd highest priority: GET parameter 'lang'
358
            $requestLocale = self::normalizeLocale(Request::getString('lang', ''));
359
            if (!empty($requestLocale)) {
360
                $userLocales[] = $requestLocale;
361
            }
362
363
            // 3rd highest priority: SESSION parameter 'lang'
364
            if (isset($_SESSION['lang']) && is_string($_SESSION['lang'])) {
365
                $userLocales[] = self::normalizeLocale($_SESSION['lang']);
366
            }
367
368
            // 4th highest priority: HTTP_ACCEPT_LANGUAGE
369
            $browserLocales = HttpRequest::getInstance()->getAcceptedLanguages();
370
            $browserLocales = array_keys($browserLocales);
371
            foreach ($browserLocales as $bloc) {
372
                $userLocales[] = self::normalizeLocale($bloc);
373
            }
374
375
            $configLocale = \Xoops::getInstance()->getConfig('locale');
376
            if (!empty($configLocale)) {
377
                $userLocales[] = $configLocale;
378
            }
379
380
            // Lowest priority: fallback
381
            $userLocales[] = static::FALLBACK_LOCALE;
382
383
            static::$userLocales = array_unique($userLocales);
384
        }
385 10
        return static::$userLocales;
386
    }
387
388
    /**
389
     * Convert a locale designation to a normal form ll_Ssss_CC, where
390
     *   ll   is language code
391
     *   Ssss is the script code, if specified
392
     *   CC   is the country code, if specified
393
     *
394
     * @param string $locale     locale code
395
     * @param string $separator  string to use to join locale parts
396
     * @param bool   $withScript include script if specified, always remove if false
397
     *
398
     * @return string normalized locale, or empty string on error
399
     */
400 68
    public static function normalizeLocale($locale, $separator = '_', $withScript = true)
401
    {
402
        try {
403 68
            $keys = Data::explodeLocale($locale);
404 68
            $key = strtolower($keys['language']);
405 68
            $key .= (empty($keys['script']) || false===$withScript) ?
406 68
                '' : $separator . ucfirst(strtolower($keys['script']));
407 68
            $key .= empty($keys['territory']) ? '' : $separator . strtoupper($keys['territory']);
408 68
        } 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...
409
            $key = '';
410
        }
411
412 68
        return $key;
413
    }
414
415
    /**
416
     * Return a normalized form of a resource domain. A resource domain is always lowercase.
417
     *
418
     * @param string $domain resource domain (usually a module dirname)
419
     *
420
     * @return string normalized resource domain
421
     */
422
    public static function normalizeDomain($domain)
423
    {
424
        return strtolower($domain);
425
    }
426
}
427