Test Setup Failed
Push — master ( a3bd4e...7f5817 )
by Chauncey
10:18
created

LanguageMiddleware   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 5
dl 0
loc 278
rs 8.8
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 46 1
A __invoke() 0 16 3
D getLanguage() 0 32 9
A getLanguageFromPath() 0 15 3
B getLanguageFromParams() 0 14 5
A getLanguageFromSession() 0 11 4
A getLanguageFromBrowser() 0 4 1
A setLanguage() 0 14 4
B setLocale() 0 28 6
1
<?php
2
3
namespace Charcoal\Translator\Middleware;
4
5
// From PSR-7
6
use Psr\Http\Message\ServerRequestInterface;
7
use Psr\Http\Message\RequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
10
// From Pimple
11
use Pimple\Container;
12
13
// From `charcoal-translator`
14
use Charcoal\Translator\LocalesManager;
15
use Charcoal\Translator\TranslatorAwareTrait;
16
17
/**
18
 * Class LanguageMiddleware
19
 */
20
class LanguageMiddleware
21
{
22
    use TranslatorAwareTrait;
23
24
    /**
25
     * @var string
26
     */
27
    private $defaultLanguage;
28
29
    /**
30
     * @var string
31
     */
32
    private $browserLanguage;
33
34
    /**
35
     * @var array
36
     */
37
    private $excludedPath;
38
39
    /**
40
     * @var boolean
41
     */
42
    private $usePath;
43
44
    /**
45
     * @var string
46
     */
47
    private $pathRegexp;
48
49
    /**
50
     * @var boolean
51
     */
52
    private $useBrowser;
53
54
    /**
55
     * @var boolean
56
     */
57
    private $useSession;
58
59
    /**
60
     * @var string[]
61
     */
62
    private $sessionKey;
63
64
    /**
65
     * @var boolean
66
     */
67
    private $useParams;
68
69
    /**
70
     * @var string[]
71
     */
72
    private $paramKey;
73
74
    /**
75
     * @param array $data The middleware options.
76
     */
77
    public function __construct(array $data)
78
    {
79
        $this->setTranslator($data['translator']);
80
81
        $defaults = [
82
            'default_language' => null,
83
84
            'browser_language' => null,
85
86
            'use_path'      => true,
87
            'included_path' => null,
88
            'excluded_path' => [
89
                '~^/admin\b~'
90
            ],
91
            'path_regexp'   => '~^/([a-z]{2})\b~',
92
93
            'use_params'    => false,
94
            'param_key'     => 'current_language',
95
96
            'use_session'   => true,
97
            'session_key'   => 'current_language',
98
99
            'use_browser'   => true,
100
101
            'set_locale'    => true
102
        ];
103
        $data = array_replace($defaults, $data);
104
105
        $this->defaultLanguage = $data['default_language'];
106
        $this->browserLanguage = $data['browser_language'];
107
108
        $this->usePath      = !!$data['use_path'];
109
        $this->includedPath = $data['included_path'];
0 ignored issues
show
Bug introduced by
The property includedPath does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
110
        $this->excludedPath = $data['excluded_path'];
111
        $this->pathRegexp   = $data['path_regexp'];
112
113
        $this->useParams    = !!$data['use_params'];
114
        $this->paramKey     = (array)$data['param_key'];
115
116
        $this->useSession   = !!$data['use_session'];
117
        $this->sessionKey   = (array)$data['session_key'];
118
119
        $this->useBrowser   = !!$data['use_browser'];
120
121
        $this->setLocale    = !!$data['set_locale'];
0 ignored issues
show
Bug introduced by
The property setLocale does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
122
    }
123
124
    /**
125
     * @param  RequestInterface  $request  The PSR-7 HTTP request.
126
     * @param  ResponseInterface $response The PSR-7 HTTP response.
127
     * @param  callable          $next     The next middleware callable in the stack.
128
     * @return ResponseInterface
129
     */
130
    public function __invoke(RequestInterface $request, ResponseInterface $response, callable $next)
131
    {
132
        // Test if path is excluded from middleware.
133
        $uri  = $request->getUri();
134
        $path = $uri->getPath();
135
        foreach ($this->excludedPath as $excluded) {
136
            if (preg_match($excluded, $path)) {
137
                return $next($request, $response);
138
            }
139
        }
140
141
        $language = $this->getLanguage($request);
142
        $this->setLanguage($language);
143
144
        return $next($request, $response);
145
    }
146
147
    /**
148
     * @param  RequestInterface $request The PSR-7 HTTP request.
149
     * @return null|string
150
     */
151
    private function getLanguage(RequestInterface $request)
152
    {
153
        if ($this->usePath === true) {
154
            $lang = $this->getLanguageFromPath($request);
155
            if ($lang) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lang of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
156
                return $lang;
157
            }
158
        }
159
160
        if ($this->useParams === true) {
161
            $lang = $this->getLanguageFromParams($request);
162
            if ($lang) {
163
                return $lang;
164
            }
165
        }
166
167
        if ($this->useSession === true) {
168
            $lang = $this->getLanguageFromSession();
169
            if ($lang) {
170
                return $lang;
171
            }
172
        }
173
174
        if ($this->useBrowser === true) {
175
            $lang = $this->getLanguageFromBrowser();
176
            if ($lang) {
177
                return $lang;
178
            }
179
        }
180
181
        return $this->defaultLanguage;
182
    }
183
184
    /**
185
     * @param  RequestInterface $request The PSR-7 HTTP request.
186
     * @return null|string
187
     */
188
    private function getLanguageFromPath(RequestInterface $request)
189
    {
190
        $path = $request->getRequestTarget();
191
        if (preg_match($this->pathRegexp, $path, $matches)) {
192
            $lang = $matches[1];
193
        } else {
194
            return '';
195
        }
196
197
        if (in_array($lang, $this->translator()->availableLocales())) {
198
            return $lang;
199
        } else {
200
            return '';
201
        }
202
    }
203
204
    /**
205
     * @param  RequestInterface $request The PSR-7 HTTP request.
206
     * @return string
207
     */
208
    private function getLanguageFromParams(RequestInterface $request)
209
    {
210
        if ($request instanceof ServerRequestInterface) {
211
            $locales = $this->translator()->availableLocales();
212
            $params  = $request->getQueryParams();
213
            foreach ($this->paramKey as $key) {
214
                if (isset($params[$key]) && in_array($params[$key], $locales)) {
215
                    return $params[$key];
216
                }
217
            }
218
        }
219
220
        return '';
221
    }
222
223
    /**
224
     * @return string
225
     */
226
    private function getLanguageFromSession()
227
    {
228
        $locales = $this->translator()->availableLocales();
229
        foreach ($this->sessionKey as $key) {
230
            if (isset($_SESSION[$key]) && in_array($_SESSION[$key], $locales)) {
231
                return $_SESSION[$key];
232
            }
233
        }
234
235
        return '';
236
    }
237
238
    /**
239
     * @return mixed
240
     */
241
    private function getLanguageFromBrowser()
242
    {
243
        return $this->browserLanguage;
244
    }
245
246
    /**
247
     * @param  string $lang The language code to set.
248
     * @return void
249
     */
250
    private function setLanguage($lang)
251
    {
252
        $this->translator()->setLocale($lang);
253
254
        if ($this->useSession === true) {
255
            foreach ($this->sessionKey as $key) {
256
                $_SESSION[$key] = $this->translator()->getLocale();
257
            }
258
        }
259
260
        if ($this->setLocale === true) {
261
            $this->setLocale($lang);
262
        }
263
    }
264
265
    /**
266
     * @param  string $lang The language code to set.
267
     * @return void
268
     */
269
    private function setLocale($lang)
270
    {
271
        $translator = $this->translator();
272
        $available  = $translator->locales();
273
        $fallbacks  = $translator->getFallbackLocales();
274
275
        array_unshift($fallbacks, $lang);
276
        $fallbacks = array_unique($fallbacks);
277
278
        $locales = [];
279
        foreach ($fallbacks as $code) {
280
            if (isset($available[$code])) {
281
                $locale = $available[$code];
282
                if (isset($locale['locales'])) {
283
                    $choices = (array)$locale['locales'];
284
                    array_push($locales, ...$choices);
285
                } elseif (isset($locale['locale'])) {
286
                    array_push($locales, $locale['locale']);
287
                }
288
            }
289
        }
290
291
        $locales = array_unique($locales);
292
293
        if ($locales) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locales of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
294
            setlocale(LC_ALL, $locales);
295
        }
296
    }
297
}
298