Passed
Push — master ( 75f605...f16558 )
by Alexander
02:18
created

Locale::withEnableSaveLocale()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Middleware;
6
7
use DateInterval;
8
use Psr\Http\Message\ResponseFactoryInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Psr\Log\LoggerInterface;
14
use Yiisoft\Cookies\Cookie;
15
use Yiisoft\Http\Header;
16
use Yiisoft\Http\Method;
17
use Yiisoft\Http\Status;
18
use Yiisoft\Router\UrlGeneratorInterface;
19
use Yiisoft\Session\SessionInterface;
20
use Yiisoft\Strings\WildcardPattern;
21
use Yiisoft\Translator\TranslatorInterface;
22
23
final class Locale implements MiddlewareInterface
24
{
25
    private const DEFAULT_LOCALE = 'en';
26
    private const DEFAULT_LOCALE_NAME = '_language';
27
28
    private bool $enableSaveLocale = true;
29
    private bool $enableDetectLocale = false;
30
    private string $defaultLocale = self::DEFAULT_LOCALE;
31
    private string $queryParameterName = self::DEFAULT_LOCALE_NAME;
32
    private string $sessionName = self::DEFAULT_LOCALE_NAME;
33
    private ?DateInterval $cookieDuration;
34
35 12
    public function __construct(
36
        private TranslatorInterface $translator,
37
        private UrlGeneratorInterface $urlGenerator,
38
        private SessionInterface $session,
39
        private LoggerInterface $logger,
40
        private ResponseFactoryInterface $responseFactory,
41
        private array $locales = [],
42
        private array $ignoredRequests = [],
43
        private bool $cookieSecure = false
44
    ) {
45 12
        $this->cookieDuration = new DateInterval('P30D');
46
    }
47
48 11
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
49
    {
50 11
        if ($this->locales === []) {
51 1
            return $handler->handle($request);
52
        }
53
54 10
        $uri = $request->getUri();
55 10
        $path = $uri->getPath();
56
57 10
        [$locale, $country] = $this->getLocaleFromPath($path);
58
59 10
        if ($locale !== null) {
60 5
            $this->translator->setLocale($locale);
61 5
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
62
63 5
            $response = $handler->handle($request);
64 5
            $newPath = null;
65 5
            if ($this->isDefaultLocale($locale, $country) && $request->getMethod() === Method::GET) {
66 3
                $length = strlen($locale);
67 3
                $newPath = substr($path, $length + 1);
68
            }
69 5
            return $this->applyLocaleFromPath($locale, $response, $newPath);
70
        }
71 5
        if ($this->enableSaveLocale) {
72 5
            [$locale, $country] = $this->getLocaleFromRequest($request);
73
        }
74 5
        if ($locale === null && $this->enableDetectLocale) {
75 2
            [$locale, $country] = $this->detectLocale($request);
76
        }
77 5
        if ($locale === null || $this->isDefaultLocale($locale, $country) || $this->isRequestIgnored($request)) {
78 2
            $this->urlGenerator->setDefaultArgument($this->queryParameterName, null);
79 2
            $request = $request->withUri($uri->withPath('/' . $this->defaultLocale . $path));
80 2
            return $handler->handle($request);
81
        }
82
83 3
        $this->translator->setLocale($locale);
84 3
        $this->urlGenerator->setDefaultArgument($this->queryParameterName, $locale);
85
86 3
        if ($request->getMethod() === Method::GET) {
87 2
            return $this->responseFactory
88 2
                ->createResponse(Status::FOUND)
89 2
                ->withHeader(Header::LOCATION, '/' . $locale . $path);
90
        }
91
92
93 1
        return $handler->handle($request);
94
    }
95
96 5
    private function applyLocaleFromPath(
97
        string $locale,
98
        ResponseInterface $response,
99
        ?string $newPath = null
100
    ): ResponseInterface {
101 5
        if ($newPath === '') {
102 2
            $newPath = '/';
103
        }
104
105 5
        if ($newPath !== null) {
106 3
            $response = $this->responseFactory
107 3
                ->createResponse(Status::FOUND)
108 3
                ->withHeader(Header::LOCATION, $newPath);
109
        }
110 5
        if ($this->enableSaveLocale) {
111 5
            $response = $this->saveLocale($locale, $response);
112
        }
113 5
        return $response;
114
    }
115
116 10
    private function getLocaleFromPath(string $path): array
117
    {
118 10
        $parts = [];
119 10
        foreach ($this->locales as $code => $locale) {
120 10
            $lang = is_string($code) ? $code : $locale;
121 10
            $parts[] = $lang;
122
        }
123
124 10
        $pattern = implode('|', $parts);
125 10
        if (preg_match("#^/($pattern)\b(/?)#i", $path, $matches)) {
126 5
            $locale = $matches[1];
127 5
            [$locale, $country] = $this->parseLocale($locale);
128 5
            if (isset($this->locales[$locale])) {
129 5
                $this->logger->debug(sprintf("Locale '%s' found in URL", $locale));
130 5
                return [$locale, $country];
131
            }
132
        }
133 5
        return [null, null];
134
    }
135
136 5
    private function getLocaleFromRequest(ServerRequestInterface $request): array
137
    {
138 5
        $cookies = $request->getCookieParams();
139 5
        $queryParameters = $request->getQueryParams();
140 5
        if (isset($cookies[$this->sessionName])) {
141
            $this->logger->debug(sprintf("Locale '%s' found in cookies", $cookies[$this->sessionName]));
142
            return $this->parseLocale($cookies[$this->sessionName]);
143
        }
144 5
        if (isset($queryParameters[$this->queryParameterName])) {
145 3
            $this->logger->debug(
146 3
                sprintf("Locale '%s' found in query string", $queryParameters[$this->queryParameterName])
147
            );
148 3
            return $this->parseLocale($queryParameters[$this->queryParameterName]);
149
        }
150 2
        return [null, null];
151
    }
152
153 9
    private function isDefaultLocale(string $locale, ?string $country): bool
154
    {
155 9
        return $locale === $this->defaultLocale || ($country !== null && $this->defaultLocale === "$locale-$country");
156
    }
157
158 2
    private function detectLocale(ServerRequestInterface $request): array
159
    {
160 2
        foreach ($request->getHeader(Header::ACCEPT_LANGUAGE) as $language) {
161 1
            return $this->parseLocale($language);
162
        }
163 1
        return [null, null];
164
    }
165
166 5
    private function saveLocale(string $locale, ResponseInterface $response): ResponseInterface
167
    {
168 5
        $this->logger->debug('Saving found locale to cookies');
169 5
        $this->session->set($this->sessionName, $locale);
170 5
        $cookie = new Cookie(name: $this->sessionName, value: $locale, secure: $this->cookieSecure);
171 5
        if ($this->cookieDuration !== null) {
172 5
            $cookie = $cookie->withMaxAge($this->cookieDuration);
173
        }
174 5
        return $cookie->addToResponse($response);
175
    }
176
177 9
    private function parseLocale(string $locale): array
178
    {
179 9
        if (str_contains($locale, '-')) {
180
            return explode('-', $locale, 2);
181
        }
182
183 9
        if (str_contains($locale, '_')) {
184
            return explode('_', $locale, 2);
185
        }
186 9
        if (isset($this->locales[$locale]) && str_contains($this->locales[$locale], '-')) {
187 9
            return explode('-', $this->locales[$locale], 2);
188
        }
189
        return [$locale, null];
190
    }
191
192 4
    private function isRequestIgnored(ServerRequestInterface $request): bool
193
    {
194 4
        foreach ($this->ignoredRequests as $ignoredRequest) {
195 1
            if ((new WildcardPattern($ignoredRequest))->match($request->getUri()->getPath())) {
196 1
                return true;
197
            }
198
        }
199 3
        return false;
200
    }
201
202 1
    public function withLocales(array $locales): self
203
    {
204 1
        $new = clone $this;
205 1
        $new->locales = $locales;
206 1
        return $new;
207
    }
208
209 3
    public function withDefaultLocale(string $defaultLocale): self
210
    {
211 3
        $new = clone $this;
212 3
        $new->defaultLocale = $defaultLocale;
213 3
        return $new;
214
    }
215
216 1
    public function withQueryParameterName(string $queryParameterName): self
217
    {
218 1
        $new = clone $this;
219 1
        $new->queryParameterName = $queryParameterName;
220 1
        return $new;
221
    }
222
223 1
    public function withSessionName(string $sessionName): self
224
    {
225 1
        $new = clone $this;
226 1
        $new->sessionName = $sessionName;
227 1
        return $new;
228
    }
229
230 1
    public function withEnableSaveLocale(bool $enableSaveLocale): self
231
    {
232 1
        $new = clone $this;
233 1
        $new->enableDetectLocale = $enableSaveLocale;
234 1
        return $new;
235
    }
236
237 3
    public function withEnableDetectLocale(bool $enableDetectLocale): self
238
    {
239 3
        $new = clone $this;
240 3
        $new->enableDetectLocale = $enableDetectLocale;
241 3
        return $new;
242
    }
243
244 2
    public function withIgnoredRequests(array $ignoredRequests): self
245
    {
246 2
        $new = clone $this;
247 2
        $new->ignoredRequests = $ignoredRequests;
248 2
        return $new;
249
    }
250
251 1
    public function withCookieSecure(bool $secure): self
252
    {
253 1
        $new = clone $this;
254 1
        $new->cookieSecure = $secure;
255 1
        return $new;
256
    }
257
}
258