Test Failed
Push — master ( 0fa9b8...148ab9 )
by Alexander
04:58 queued 01:58
created

LocaleMiddleware::withEnableSaveLocale()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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