Test Failed
Push — master ( c9926a...5520e1 )
by Julien
04:42
created

Locale::prepare()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 4
Bugs 0 Features 1
Metric Value
eloc 17
dl 0
loc 21
ccs 0
cts 19
cp 0
rs 9.7
c 4
b 0
f 1
cc 1
nc 1
nop 1
crap 2
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
     * Default (router only)
27
     */
28
    public const MODE_DEFAULT = 'default';
29
    
30
    /**
31
     * Router
32
     */
33
    public const MODE_ROUTE = 'route';
34
    
35
    /**
36
     * Router -> http
37
     */
38
    public const MODE_HTTP = 'http';
39
    
40
    /**
41
     * Router -> session -> http
42
     */
43
    public const MODE_SESSION = 'session';
44
    
45
    /**
46
     * Locale mode
47
     * Locale::MODE_DEFAULT 'default' (Router -> http)
48
     * Locale::MODE_SESSION 'session' (Router -> session -> http)
49
     * Locale::MODE_GEOIP 'geoip' (Router -> geoip -> http)
50
     * Locale::MODE_SESSION_GEOIP 'session_geoip' (Router -> session -> geoip -> http)
51
     */
52
    public string $mode = self::MODE_DEFAULT;
53
    
54
    /**
55
     * The actual locale that was picked
56
     * @var string|null
57
     */
58
    public $locale = null;
59
    
60
    /**
61
     * @var mixed|null|string
62
     */
63
    public string $sessionKey = 'zemit-locale';
64
    
65
    /**
66
     * Default locale to fall back
67
     */
68
    public string $default = 'en';
69
    
70
    /**
71
     * List of allowed locale
72
     */
73
    public array $allowed = ['en'];
74
    
75
    /**
76
     * Set options and prepare locale
77
     */
78
    public function initialize(): void
79
    {
80
        $this->sessionKey = $this->getOption('sessionKey', $this->sessionKey);
81
        $this->setAllowed($this->getOption('allowed', $this->allowed));
82
        $this->setDefault($this->getOption('default', $this->default));
83
        $this->setMode($this->getOption('mode', $this->mode));
84
        $this->prepare($this->getDefault());
85
    }
86
    
87
    /**
88
     * Alias of the getLocale() method
89
     */
90
    public function get(): ?string
91
    {
92
        return $this->getLocale();
93
    }
94
    
95
    /**
96
     * Get the locale directly from the variable
97
     * without processing the defined mode
98
     */
99
    public function getLocale(): ?string
100
    {
101
        return $this->locale;
102
    }
103
    
104
    /**
105
     * Set the current locale value
106
     */
107
    public function setLocale(?string $locale = null): void
108
    {
109
        $this->locale = $this->lookup($locale);
110
    }
111
    
112
    /**
113
     * Get the default locale
114
     */
115
    public function getDefault(): ?string
116
    {
117
        return $this->default;
118
    }
119
    
120
    /**
121
     * Set the default locale value
122
     */
123
    public function setDefault(?string $locale = null): void
124
    {
125
        $this->default = $locale;
126
    }
127
    
128
    /**
129
     * Get the list of possible locale
130
     */
131
    public function getAllowed(): array
132
    {
133
        return $this->allowed ?? [];
134
    }
135
    
136
    /**
137
     * Set the allowed locale
138
     */
139
    public function setAllowed(array $allowed): void
140
    {
141
        $this->allowed = array_values(array_unique($allowed));
142
    }
143
    
144
    /**
145
     * Get the defined mode
146
     */
147
    public function getMode(): string
148
    {
149
        return $this->mode;
150
    }
151
    
152
    /**
153
     * Set the mode
154
     */
155
    public function setMode(string $mode): void
156
    {
157
        $this->mode = $mode;
158
    }
159
    
160
    /**
161
     * Prepare and set and return the locale based on the defined mode
162
     */
163
    public function prepare(?string $default = null): ?string
164
    {
165
        $locale = match ($this->mode) {
166
            self::MODE_SESSION =>
167
                $this->getFromRoute() ??
168
                $this->getFromSession() ??
169
                $this->getFromHttp() ??
170
                $default,
171
            self::MODE_HTTP =>
172
                $this->getFromRoute() ??
173
                $this->getFromHttp() ??
174
                $default,
175
            default =>
176
                $this->getFromRoute() ??
177
                $default,
178
        };
179
        
180
        $locale ??= $this->locale;
181
        $this->setLocale($locale);
182
        $this->saveIntoSession($locale);
183
        return $this->getLocale();
184
    }
185
    
186
    /**
187
     * Retrieves the locale from the route
188
     */
189
    public function getFromRoute(?string $default = null): ?string
190
    {
191
        return $this->lookup($this->router->getParams()['locale'] ?? $default);
192
    }
193
    
194
    /**
195
     * Retrieves the locale from the dispatcher
196
     */
197
    public function getFromDispatcher(?string $default = null): ?string
198
    {
199
        return $this->lookup($this->dispatcher->getParams()['locale'] ?? $default);
200
    }
201
    
202
    /**
203
     * Retrieves the locale from the session
204
     */
205
    public function getFromSession(?string $default = null): ?string
206
    {
207
        return $this->lookup($this->session->get($this->sessionKey, $default));
208
    }
209
    
210
    /**
211
     * Retrieves the locale from the request
212
     * of getBestLanguage() header
213
     * or HTTP_ACCEPT_LANGUAGE header
214
     */
215
    public function getFromHttp(?String $default = null): ?string
216
    {
217
        return
218
            $this->lookup($this->request->getBestLanguage()) ??
219
            \Locale::acceptFromHttp($this->request->getHeader('HTTP_ACCEPT_LANGUAGE')) ??
220
            $default;
221
    }
222
    
223
    /**
224
     * Save locale into session if mode contain session handling
225
     */
226
    public function saveIntoSession(?string $locale = null, bool $force = null): void
227
    {
228
        $locale ??= $this->getLocale();
229
        
230
        // save into session
231
        $force = $force || $this->mode === self::MODE_SESSION || $this->mode === self::MODE_SESSION_GEOIP;
0 ignored issues
show
Bug introduced by
The constant Zemit\Locale::MODE_SESSION_GEOIP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
232
        if ($force) {
233
            $this->session->set($this->sessionKey, $locale);
234
        }
235
    }
236
    
237
    /**
238
     * @param string|null $locale The locale to use as the language range when matching.
239
     * @param array|null $allowed An array containing a list of language tags to compare to locale. Maximum 100 items allowed.
240
     * @param bool $canonicalize If true, the arguments will be converted to canonical form before matching.
241
     * @param string|null $default The locale to use if no match is found.
242
     * @return string|null The closest matching language tag or default value.
243
     */
244
    public function lookup(?string $locale = null, ?array $allowed = null, bool $canonicalize = false, ?string $default = null): ?string
245
    {
246
        if (is_null($locale)) {
247
            return null;
248
        }
249
        
250
        $allowed ??= $this->getAllowed();
251
        
252
        // lookup first
253
        $lookup = \Locale::lookup($allowed, $locale, $canonicalize, $default);
254
        
255
        // base locale found without the region
256
        $force = false;
257
        if ($locale === $lookup || strlen($lookup) === 2) {
258
            $locale = $lookup;
259
            $force = true;
260
        }
261
        
262
        // lookup for the first configured region based on the locale without region
263
        if (empty($lookup) || $force) {
264
            
265
            // matches all the possible regions from the locale
266
            $matches = array_filter($allowed, function ($haystack) use ($locale) {
267
                $needle = $locale . '_';
268
                return stripos($haystack, $needle) === 0;
269
            });
270
            
271
            // some matches
272
            if (count($matches)) {
273
                // lookup again with the first match
274
                $lookup = \Locale::lookup($matches, array_shift($matches), $canonicalize, $default);
275
            }
276
            else {
277
                // otherwise keep the lookup if set or set the default if not
278
                $lookup = empty($lookup) ? $default : $lookup;
279
            }
280
        }
281
        
282
        return empty($lookup) ? null : $lookup;
283
    }
284
}
285