Test Failed
Push — master ( 255210...8cd4bd )
by Julien
05:11
created

Locale::prepare()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 48
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 7
eloc 40
c 3
b 0
f 1
nc 7
nop 1
dl 0
loc 48
ccs 0
cts 40
cp 0
crap 56
rs 8.3466
1
<?php
2
3
/**
4
 * This file is part of the Zemit Framework.
5
 *
6
 * (c) Zemit Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zemit;
13
14
use Zemit\Di\Injectable;
15
use Zemit\Support\Options\Options;
16
use Zemit\Support\Options\OptionsInterface;
17
18
/**
19
 * Allow to manage and lookup the locale for the localisation
20
 */
21
class Locale extends Injectable implements OptionsInterface
22
{
23
    use Options;
24
    
25
    /**
26
     * Router
27
     */
28
    public const MODE_ROUTE = 'route';
29
    public const MODE_DEFAULT = 'default';
30
    
31
    /**
32
     * Router -> http
33
     */
34
    public const MODE_HTTP = 'http';
35
    
36
    /**
37
     * Router -> session -> http
38
     */
39
    public const MODE_SESSION = 'session';
40
    
41
    /**
42
     * Router -> geoip -> http
43
     */
44
    public const MODE_GEOIP = 'geoip';
45
    
46
    /**
47
     * Router -> session -> geoip -> http
48
     */
49
    public const MODE_SESSION_GEOIP = 'session_geoip';
50
    
51
    /**
52
     * Locale mode
53
     * Locale::MODE_DEFAULT 'default' (Router -> http)
54
     * Locale::MODE_SESSION 'session' (Router -> session -> http)
55
     * Locale::MODE_GEOIP 'geoip' (Router -> geoip -> http)
56
     * Locale::MODE_SESSION_GEOIP 'session_geoip' (Router -> session -> geoip -> http)
57
     */
58
    public string $mode = self::MODE_DEFAULT;
59
    
60
    /**
61
     * The actual locale that was picked
62
     * @var string|null
63
     */
64
    public $locale = null;
65
    
66
    /**
67
     * @var mixed|null|string
68
     */
69
    public string $sessionKey = 'zemit-locale';
70
    
71
    /**
72
     * Default locale to fall back
73
     */
74
    public string $default = 'en';
75
    
76
    /**
77
     * List of allowed locale
78
     */
79
    public array $allowed = ['en'];
80
    
81
    /**
82
     * Set options and prepare locale
83
     */
84
    public function initialize(): void
85
    {
86
        $this->sessionKey = $this->getOption('sessionKey', $this->sessionKey);
87
        $this->setAllowed($this->getOption('allowed', $this->allowed));
88
        $this->setDefault($this->getOption('default', $this->default));
89
        $this->setMode($this->getOption('mode', $this->mode));
90
        $this->prepare($this->getDefault());
91
    }
92
    
93
    /**
94
     * Alias of the getLocale() method
95
     */
96
    public function get(): ?string
97
    {
98
        return $this->getLocale();
99
    }
100
    
101
    /**
102
     * Get the locale directly from the variable
103
     * without processing the defined mode
104
     */
105
    public function getLocale(): ?string
106
    {
107
        return $this->locale;
108
    }
109
    
110
    /**
111
     * Set the current locale value
112
     */
113
    public function setLocale(?string $locale = null): void
114
    {
115
        $this->locale = $this->lookup($locale);
116
    }
117
    
118
    /**
119
     * Get the default locale
120
     */
121
    public function getDefault(): ?string
122
    {
123
        return $this->default;
124
    }
125
    
126
    /**
127
     * Set the default locale value
128
     */
129
    public function setDefault(?string $locale = null): void
130
    {
131
        $this->default = $locale;
132
    }
133
    
134
    /**
135
     * Get the list of possible locale
136
     */
137
    public function getAllowed(): array
138
    {
139
        return $this->allowed ?? [];
140
    }
141
    
142
    /**
143
     * Set the allowed locale
144
     */
145
    public function setAllowed(array $allowed): void
146
    {
147
        $this->allowed = array_values(array_unique($allowed));
148
    }
149
    
150
    /**
151
     * Get the defined mode
152
     */
153
    public function getMode(): string
154
    {
155
        return $this->mode;
156
    }
157
    
158
    /**
159
     * Set the mode
160
     */
161
    public function setMode(string $mode): void
162
    {
163
        $this->mode = $mode;
164
    }
165
    
166
    /**
167
     * Prepare and set and return the locale based on the defined mode
168
     */
169
    public function prepare(?string $default = null): ?string
170
    {
171
        switch ($this->mode) {
172
            case self::MODE_SESSION:
173
                $locale =
174
                    $this->getFromRoute() ??
175
                    $this->getFromSession() ??
176
                    $this->getFromHttp() ??
177
                    $default;
178
                break;
179
            
180
            case self::MODE_GEOIP:
181
                $locale =
182
                    $this->getFromRoute() ??
183
                    $this->getFromGeoIP() ??
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getFromGeoIP() targeting Zemit\Locale::getFromGeoIP() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
184
                    $this->getFromHttp() ??
185
                    $default;
186
                break;
187
            
188
            case self::MODE_SESSION_GEOIP:
189
                $locale =
190
                    $this->getFromRoute() ??
191
                    $this->getFromSession() ??
192
                    $this->getFromGeoIP() ??
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getFromGeoIP() targeting Zemit\Locale::getFromGeoIP() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
193
                    $this->getFromHttp() ??
194
                    $default;
195
                break;
196
                
197
            case self::MODE_HTTP:
198
                $locale =
199
                    $this->getFromRoute() ??
200
                    $this->getFromHttp() ??
201
                    $default;
202
                break;
203
            
204
            case self::MODE_DEFAULT:
205
            case self::MODE_ROUTE:
206
            default:
207
                $locale =
208
                    $this->getFromRoute() ??
209
                    $default;
210
                break;
211
        }
212
        
213
        $locale ??= $this->locale;
214
        $this->setLocale($locale);
215
        $this->saveIntoSession($locale);
216
        return $this->getLocale();
217
    }
218
    
219
    /**
220
     * Retrieves the locale from the route
221
     */
222
    public function getFromRoute(?string $default = null): ?string
223
    {
224
        return $this->lookup($this->router->getParams()['locale'] ?? $default);
0 ignored issues
show
Bug introduced by
The method getParams() does not exist on Zemit\Router\RouterInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Zemit\Router\RouterInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

224
        return $this->lookup($this->router->/** @scrutinizer ignore-call */ getParams()['locale'] ?? $default);
Loading history...
225
    }
226
    
227
    /**
228
     * Retrieves the locale from the dispatcher
229
     */
230
    public function getFromDispatcher(?string $default = null): ?string
231
    {
232
        return $this->lookup($this->router->getParams()['locale'] ?? $default);
233
    }
234
    
235
    /**
236
     * Retrieves the locale from the session
237
     */
238
    public function getFromSession(?string $default = null): ?string
239
    {
240
        return $this->lookup($this->session->get($this->sessionKey, $default));
241
    }
242
    
243
    /**
244
     * Retrieves the locale from the geolocation
245
     * @todo not ready yet
246
     */
247
    public function getFromGeoIP(?string $default = null): ?string
248
    {
249
        return $this->lookup($default);
250
    }
251
    
252
    /**
253
     * Retrieves the locale from the request
254
     * of getBestLanguage() header
255
     * or HTTP_ACCEPT_LANGUAGE header
256
     */
257
    public function getFromHttp(?String $default = null): ?string
258
    {
259
        return
260
            $this->lookup($this->request->getBestLanguage()) ??
261
            \Locale::acceptFromHttp($this->request->getHeader('HTTP_ACCEPT_LANGUAGE')) ??
262
            $default;
263
    }
264
    
265
    /**
266
     * Save locale into session if mode contain session handling
267
     */
268
    public function saveIntoSession(?string $locale = null, bool $force = null): void
269
    {
270
        $locale ??= $this->getLocale();
271
        
272
        // save into session
273
        $force = $force || $this->mode === self::MODE_SESSION || $this->mode === self::MODE_SESSION_GEOIP;
274
        if ($force) {
275
            $this->session->set($this->sessionKey, $locale);
276
        }
277
    }
278
    
279
    /**
280
     * @param string|null $locale The locale to use as the language range when matching.
281
     * @param array|null $allowed An array containing a list of language tags to compare to locale. Maximum 100 items allowed.
282
     * @param bool $canonicalize If true, the arguments will be converted to canonical form before matching.
283
     * @param string|null $default The locale to use if no match is found.
284
     * @return string|null The closest matching language tag or default value.
285
     */
286
    public function lookup(?string $locale = null, ?array $allowed = null, bool $canonicalize = false, ?string $default = null): ?string
287
    {
288
        if (is_null($locale)) {
289
            return null;
290
        }
291
        
292
        $allowed ??= $this->getAllowed();
293
        
294
        // lookup first
295
        $lookup = \Locale::lookup($allowed, $locale, $canonicalize, $default);
296
        
297
        // base locale found without the region
298
        $force = false;
299
        if ($locale === $lookup || strlen($lookup) === 2) {
300
            $locale = $lookup;
301
            $force = true;
302
        }
303
        
304
        // lookup for the first configured region based on the locale without region
305
        if (empty($lookup) || $force) {
306
            
307
            // matches all the possible regions from the locale
308
            $matches = array_filter($allowed, function ($haystack) use ($locale) {
309
                $needle = $locale . '_';
310
                return stripos($haystack, $needle) === 0;
311
            });
312
            
313
            // some matches
314
            if (count($matches)) {
315
                // lookup again with the first match
316
                $lookup = \Locale::lookup($matches, array_shift($matches), $canonicalize, $default);
317
            }
318
            else {
319
                // otherwise keep the lookup if set or set the default if not
320
                $lookup = empty($lookup) ? $default : $lookup;
321
            }
322
        }
323
        
324
        return empty($lookup) ? null : $lookup;
325
    }
326
}
327