Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
LocaleApiController
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
4 / 4
14
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLocale
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
7
 parsePayload
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 jsonResponse
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\User\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Translation\LocaleContextInterface;
12use App\Core\Translation\Middleware\LocaleDetectionMiddleware;
13use App\Modules\User\Domain\Repository\UserRepositoryInterface;
14use Nyholm\Psr7\Factory\Psr17Factory;
15use Psr\Http\Message\ResponseInterface;
16use Psr\Http\Message\ServerRequestInterface;
17use Yiisoft\Session\SessionInterface;
18use Yiisoft\Translator\TranslatorInterface;
19use Yiisoft\User\CurrentUser;
20
21/**
22 * REST API Controller for User Language Preferences.
23 *
24 * Handles switching active locale for the session and saving to user account profile.
25 *
26 * @package App\Modules\User\Presentation\Api
27 */
28final readonly class LocaleApiController
29{
30    /** @var string JSON response content type. */
31    private const string JSON_CONTENT_TYPE = 'application/json';
32
33    /**
34     * LocaleApiController constructor.
35     *
36     * @param LocaleContext $localeContext Locale context manager.
37     * @param TranslatorInterface $translator Yii3 translator service.
38     * @param SessionInterface $session Session interface.
39     * @param CurrentUser $currentUser Current user identity.
40     * @param UserRepositoryInterface $userRepository User repository contract.
41     * @param Psr17Factory $psr17Factory PSR-17 response factory.
42     */
43    public function __construct(
44        private LocaleContextInterface $localeContext,
45        private TranslatorInterface $translator,
46        private SessionInterface $session,
47        private CurrentUser $currentUser,
48        private UserRepositoryInterface $userRepository,
49        private Psr17Factory $psr17Factory
50    ) {
51    }
52
53    /**
54     * Sets the active locale for the user session and database record.
55     *
56     * @param ServerRequestInterface $request HTTP server request.
57     * @return ResponseInterface JSON API response.
58     */
59    public function setLocale(ServerRequestInterface $request): ResponseInterface
60    {
61        $payload = $this->parsePayload($request);
62        $locale = strtolower(trim((string) ($payload['locale'] ?? '')));
63
64        if ($locale === '' || !$this->localeContext->isValidLocale($locale)) {
65            return $this->jsonResponse([
66                'success' => false,
67                'error' => 'Invalid or unsupported language locale.',
68            ], 400);
69        }
70
71        $this->localeContext->setLocale($locale);
72        $this->translator->setLocale($locale);
73
74        if ($this->session->isActive()) {
75            $this->session->set(LocaleDetectionMiddleware::SESSION_KEY, $locale);
76        }
77
78        $identity = $this->currentUser->getIdentity();
79        if (!$this->currentUser->isGuest() && $identity !== null && (int) $identity->getId() > 0) {
80            $userId = (int) $identity->getId();
81            $this->userRepository->updateLocale($userId, $locale);
82        }
83
84        return $this->jsonResponse([
85            'success' => true,
86            'data' => [
87                'locale' => $locale,
88            ],
89        ]);
90    }
91
92    /**
93     * Parses request body payload from JSON or POST body.
94     *
95     * @param ServerRequestInterface $request HTTP server request.
96     * @return array<string, mixed> Parsed parameters.
97     */
98    private function parsePayload(ServerRequestInterface $request): array
99    {
100        $body = (string) $request->getBody();
101        if ($body !== '') {
102            /** @var mixed $json */
103            $json = json_decode($body, true);
104            if (is_array($json)) {
105                /** @var array<string, mixed> $json */
106                return $json;
107            }
108        }
109
110        /** @var mixed $parsed */
111        $parsed = $request->getParsedBody();
112        return is_array($parsed) ? $parsed : [];
113    }
114
115    /**
116     * Builds standard JSON response.
117     *
118     * @param array<string, mixed> $data Payload data array.
119     * @param int $statusCode HTTP status code.
120     * @return ResponseInterface Formatted JSON response.
121     */
122    private function jsonResponse(array $data, int $statusCode = 200): ResponseInterface
123    {
124        $response = $this->psr17Factory->createResponse($statusCode)
125            ->withHeader('Content-Type', self::JSON_CONTENT_TYPE);
126        $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
127        $response->getBody()->write($json !== false ? $json : '{}');
128
129        return $response;
130    }
131}