2023-08-05 15:49:46 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace DigiComp\FlowTranslationEndpoint\Http;
|
|
|
|
|
|
|
|
use GuzzleHttp\Psr7\Response;
|
|
|
|
use Neos\Cache\Exception as CacheException;
|
|
|
|
use Neos\Cache\Exception\InvalidDataException;
|
|
|
|
use Neos\Cache\Frontend\StringFrontend;
|
2023-08-08 17:02:13 +02:00
|
|
|
use Neos\Flow\Annotations as Flow;
|
2023-08-05 15:49:46 +02:00
|
|
|
use Neos\Flow\I18n\Detector;
|
|
|
|
use Neos\Flow\I18n\Service;
|
|
|
|
use Neos\Flow\I18n\Xliff\Service\XliffFileProvider;
|
|
|
|
use Neos\Utility\Arrays;
|
|
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
|
|
use Psr\Http\Server\MiddlewareInterface;
|
|
|
|
use Psr\Http\Server\RequestHandlerInterface;
|
|
|
|
|
2023-08-08 12:11:46 +02:00
|
|
|
class TranslationRequestMiddleware implements MiddlewareInterface
|
2023-08-05 15:49:46 +02:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2023-08-08 17:02:13 +02:00
|
|
|
protected string $reactOnPath;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected string $getParameterName;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected int $browserCacheMaxAge;
|
2023-08-05 15:49:46 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Service
|
|
|
|
*/
|
|
|
|
protected Service $i18nService;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var XliffFileProvider
|
|
|
|
*/
|
|
|
|
protected XliffFileProvider $fileProvider;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Detector
|
|
|
|
*/
|
|
|
|
protected Detector $detector;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var StringFrontend
|
|
|
|
*/
|
|
|
|
protected StringFrontend $responseCache;
|
|
|
|
|
|
|
|
public function __construct(
|
2023-08-08 17:02:13 +02:00
|
|
|
string $reactOnPath,
|
|
|
|
string $getParameterName,
|
|
|
|
int $browserCacheMaxAge,
|
2023-08-05 15:49:46 +02:00
|
|
|
Service $i18nService,
|
|
|
|
XliffFileProvider $fileProvider,
|
|
|
|
Detector $detector,
|
|
|
|
StringFrontend $responseCache
|
|
|
|
) {
|
2023-08-08 17:02:13 +02:00
|
|
|
$this->reactOnPath = $reactOnPath;
|
|
|
|
$this->getParameterName = $getParameterName;
|
|
|
|
$this->browserCacheMaxAge = $browserCacheMaxAge;
|
2023-08-05 15:49:46 +02:00
|
|
|
$this->i18nService = $i18nService;
|
|
|
|
$this->fileProvider = $fileProvider;
|
|
|
|
$this->detector = $detector;
|
|
|
|
$this->responseCache = $responseCache;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ServerRequestInterface $request
|
|
|
|
* @param RequestHandlerInterface $handler
|
|
|
|
* @return ResponseInterface
|
|
|
|
* @throws CacheException
|
|
|
|
* @throws InvalidDataException
|
|
|
|
* @throws \JsonException
|
|
|
|
*/
|
|
|
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
|
|
|
{
|
2023-08-08 17:02:13 +02:00
|
|
|
if ($request->getUri()->getPath() === $this->reactOnPath) {
|
2023-08-05 15:49:46 +02:00
|
|
|
return $this->createResponse($request);
|
|
|
|
}
|
|
|
|
return $handler->handle($request);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ServerRequestInterface $request
|
|
|
|
* @return ResponseInterface
|
|
|
|
* @throws \JsonException
|
|
|
|
* @throws CacheException
|
|
|
|
* @throws InvalidDataException
|
|
|
|
*/
|
|
|
|
protected function createResponse(ServerRequestInterface $request): ResponseInterface
|
|
|
|
{
|
|
|
|
if ($request->hasHeader('Accept-Language')) {
|
|
|
|
$wishedLocale = $this->detector->detectLocaleFromHttpHeader($request->getHeader('Accept-Language')[0]);
|
|
|
|
$bestMatching = $this->i18nService->findBestMatchingLocale($wishedLocale);
|
|
|
|
$this->i18nService->getConfiguration()->setCurrentLocale($bestMatching);
|
|
|
|
}
|
2023-08-08 17:02:13 +02:00
|
|
|
$idPatternList = $request->getQueryParams()[$this->getParameterName] ?? '';
|
2023-08-05 15:49:46 +02:00
|
|
|
$cacheId = $this->i18nService->getConfiguration()->getCurrentLocale() . '_' . \sha1(\serialize($idPatternList));
|
|
|
|
if ($this->responseCache->has($cacheId)) {
|
|
|
|
$response = $this->responseCache->get($cacheId);
|
|
|
|
} else {
|
|
|
|
$result = $this->getTranslations(Arrays::trimExplode(',', $idPatternList));
|
|
|
|
$response = \json_encode($result, \JSON_THROW_ON_ERROR);
|
|
|
|
$this->responseCache->set($cacheId, $response);
|
|
|
|
}
|
2023-08-08 17:02:13 +02:00
|
|
|
return new Response(
|
|
|
|
200,
|
|
|
|
[
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
'Cache-Control' => 'max-age=' . $this->browserCacheMaxAge . ', must-revalidate',
|
|
|
|
'Last-Modified' => $this->getCacheDate(),
|
|
|
|
'Vary' => 'Accept, Accept-Language'
|
|
|
|
],
|
|
|
|
$response
|
|
|
|
);
|
2023-08-05 15:49:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array $idPatternList
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getTranslations(array $idPatternList): array
|
|
|
|
{
|
|
|
|
$result = [];
|
|
|
|
foreach ($idPatternList as $idPattern) {
|
|
|
|
$package = 'Neos.Flow:Main';
|
|
|
|
$parts = \explode('|', $idPattern);
|
|
|
|
switch (\count($parts)) {
|
|
|
|
case 2:
|
|
|
|
[$package, $pattern] = $parts;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
[$pattern] = $parts;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new \InvalidArgumentException('Could not parse idPattern: ' . $idPattern);
|
|
|
|
}
|
|
|
|
$translationUnits = $this->fileProvider->getFile(
|
|
|
|
$package,
|
|
|
|
$this->i18nService->getConfiguration()->getCurrentLocale()
|
|
|
|
)->getTranslationUnits();
|
|
|
|
if (!isset($result[$package])) {
|
|
|
|
$result[$package] = [];
|
|
|
|
}
|
|
|
|
$matchingUnits = \array_filter(
|
|
|
|
$translationUnits,
|
|
|
|
static fn($unitId) => \preg_match('~^' . $pattern . '$~', $unitId),
|
|
|
|
\ARRAY_FILTER_USE_KEY
|
|
|
|
);
|
|
|
|
$result[$package] += \array_map(
|
|
|
|
static fn($value) => $value[0]['target'],
|
|
|
|
$matchingUnits
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
2023-08-08 17:02:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getCacheDate(): string
|
|
|
|
{
|
|
|
|
// the translation file monitor will flush our complete cache, each time a file is modified. That way it resets
|
|
|
|
// the last modified date here, too.
|
|
|
|
if (false === $lastModified = $this->responseCache->get('lastModified')) {
|
|
|
|
$lastModified = (new \DateTimeImmutable())->format(\DATE_RFC7231);
|
|
|
|
$this->responseCache->set('lastModified', $lastModified);
|
|
|
|
}
|
|
|
|
return $lastModified;
|
|
|
|
}
|
2023-08-05 15:49:46 +02:00
|
|
|
}
|