<?php
namespace Boldr\Cms\CmsBundle\EventSubscriber;
use Boldr\Cms\CmsBundle\Locale\LocaleDeterminerInterface;
use Boldr\Cms\CmsBundle\Controller\PermalinkController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\{ RequestEvent, ResponseEvent };
use Symfony\Component\HttpFoundation\{ Request, RedirectResponse, HeaderUtils };
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class LocaleSubscriber implements EventSubscriberInterface
{
/**
* @var string[]
*/
private array $enabledLocales;
/**
* @var iterable<LocaleDeterminerInterface>
*/
private iterable $localeDeterminers;
/**
* @var array<string,string|array>
*/
private array $localeBaseUrls;
private UrlGeneratorInterface $urlGenerator;
private PermalinkController $permalinkController;
private bool $prefixPermalinksWithLocale;
private bool $useHeaderLocale;
/**
* @param string[] $enabledLocales
* @param iterable<LocaleDeterminerInterface> $localeDeterminers
*/
public function __construct(
array $enabledLocales,
iterable $localeDeterminers,
array $localeBaseUrls,
UrlGeneratorInterface $urlGenerator,
PermalinkController $permalinkController,
bool $prefixPermalinksWithLocale,
bool $useHeaderLocale
)
{
$this->enabledLocales = $enabledLocales;
$this->localeDeterminers = $localeDeterminers;
$this->localeBaseUrls = $localeBaseUrls;
$this->urlGenerator = $urlGenerator;
$this->permalinkController = $permalinkController;
$this->prefixPermalinksWithLocale = $prefixPermalinksWithLocale;
$this->useHeaderLocale = $useHeaderLocale;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 25]],
KernelEvents::RESPONSE => [['onKernelResponse']]
];
}
public function onKernelResponse(ResponseEvent $event)
{
$request = $event->getRequest();
$urls = [];
/** @var array<string,string> */
if ($request->attributes->has('_permalinkable'))
{
foreach ($this->enabledLocales as $locale)
{
$urls[$locale] = $this->permalinkController->generate($request->attributes->get('_permalinkable'), $locale);
}
}
elseif ($request->attributes->has('_route'))
{
foreach ($this->enabledLocales as $locale)
{
$urls[$locale] = $this->urlGenerator->generate(
$request->attributes->get('_route'),
array_merge(
$request->attributes->get('_route_params') ?: [],
$request->query->all(),
['_locale' => $locale]
)
);
}
}
$response = $event->getResponse();
$linkHeader = $response->headers->get('Link');
$baseUrl = $request->getSchemeAndHttpHost() . $request->getBaseUrl();
foreach ($urls as $locale => $url)
{
if (!$url)
{
continue;
}
if ($linkHeader)
{
$linkHeader .= ', ';
}
$localeBaseUrl = $this->localeBaseUrls[$locale] ?? $baseUrl;
if (is_array($localeBaseUrl))
{
$localeBaseUrl = $localeBaseUrl[0];
}
$linkHeader .= '<'. $localeBaseUrl . $url .'>; rel="alternate"; hreflang="'. $locale .'"';
}
$response->headers->set('Link', $linkHeader);
}
private function getAcceptHeaderLocale(Request $request): ?string
{
$acceptLanguageHeader = $request->headers->get("accept-language");
if (empty($acceptLanguageHeader))
{
return null;
}
$arr = HeaderUtils::split($acceptLanguageHeader, ',;');
if (empty($arr[0][0]))
{
return null;
}
// Symfony expects underscore instead of dash in locale
$locale = str_replace('-', '_', $arr[0][0]);
return $locale;
}
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
$pathInfo = $request->getPathInfo();
$requestLocale = $request->attributes->get('_locale');
if ($requestLocale === null)
{
if ($request->attributes->get('_route') === 'cms_permalink' && $this->prefixPermalinksWithLocale)
{
$parts = explode('/', $request->attributes->get('permalink'), 2);
if (in_array($parts[0], $this->enabledLocales))
{
$requestLocale = $parts[0];
}
}
}
// check baseurl
$localeSetByBaseUrl = false;
$baseUrl = $request->getSchemeAndHttpHost().$request->getBaseUrl();
foreach ($this->localeBaseUrls as $locale => $localeBaseUrl)
{
if (is_array($localeBaseUrl) ? in_array($baseUrl, $localeBaseUrl) : $localeBaseUrl === $baseUrl)
{
$request->setLocale($locale);
$localeSetByBaseUrl = true;
}
}
if (str_starts_with($pathInfo, '/boldr-admin/') || str_starts_with($pathInfo, '/boldr-admin-api/') || str_starts_with($pathInfo, '/boldr-api/'))
{
return;
}
if (!$localeSetByBaseUrl && $this->useHeaderLocale)
{
// Use LocaleDeterminers to determine locale for this request
// Can be based on headers, sessions, request parameters, cookies, etc.
$locale = $this->getAcceptHeaderLocale($request);
// Check if the locale is enabled, or if the primary part of the locale is enabled
if (!in_array($locale, $this->enabledLocales))
{
$split = explode('_', $locale);
$locale = $split[0];
if (!in_array($locale, $this->enabledLocales))
{
$locale = $this->enabledLocales[0];
}
}
if ($locale !== null)
{
$request->setLocale($locale);
}
}
// Redirect to correct base URL if locale changed
if ($request->getMethod() === 'GET' && $localeSetByBaseUrl && $requestLocale !== null && $request->getLocale() !== $requestLocale)
{
// check baseurl
if (isset($this->localeBaseUrls[$requestLocale]))
{
$localeBaseUrl = $this->localeBaseUrls[$requestLocale];
$baseUrl = $request->getSchemeAndHttpHost().$request->getBaseUrl();
if (is_array($localeBaseUrl) ? in_array($baseUrl, $localeBaseUrl) : ($baseUrl !== $localeBaseUrl))
{
if (null !== $queryString = $request->getQueryString())
{
$queryString = '?'.$queryString;
}
$newUrl = (is_array($localeBaseUrl) ? $localeBaseUrl[0] : $localeBaseUrl) . $request->getPathInfo() . $queryString;
$event->setResponse(new RedirectResponse($newUrl));
return;
}
}
}
// Otherwise just use kernel default locale
}
}