Test Failed
Pull Request — master (#494)
by
unknown
03:32
created

LocaleMiddleware::getLocaleFromPath()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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