Passed
Pull Request — master (#383)
by Wilmer
03:15
created

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