Passed
Pull Request — master (#10)
by Rustam
02:18
created

Locale::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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