<?php declare(strict_types=1);
namespace Dvdw\PlatformChoice\Framework\Routing;
use Dvdw\PlatformChoice\Framework\Util\TokenEncoder;
use Shopware\Core\Framework\Util\Random;
use Shopware\Core\PlatformRequest;
use Shopware\Core\SalesChannelRequest;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister;
use Shopware\Storefront\Framework\Routing\RequestTransformer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class StorefrontSubscriber implements EventSubscriberInterface
{
public CONST TOKEN_NAME = 'sw-identifier';
public CONST NONCE = 'nonce';
public CONST NONCE_LENGTH = 16;
private RequestStack $requestStack;
private SalesChannelContextPersister $contextPersister;
public function __construct(
RequestStack $requestStack,
SalesChannelContextPersister $contextPersister
)
{
$this->requestStack = $requestStack;
$this->contextPersister = $contextPersister;
}
public static function getSubscribedEvents(): iterable
{
return [KernelEvents::REQUEST => ['setContextToken', 100]];
}
public function setContextToken(RequestEvent $event): void
{
$master = $this->requestStack->getMainRequest();
if ($master === null) {
return;
}
$cookieToken = $master->cookies->get(self::TOKEN_NAME);
$queryToken = $master->query->get(self::TOKEN_NAME);
$queryNonce = $master->query->get(self::NONCE);
if ($master->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST) === null
|| !$master->hasSession()
|| ($cookieToken === null && $queryToken === null)
|| ($queryToken !== null && $queryNonce === null)
)
{
return;
}
$session = $master->getSession();
if (!$session->isStarted()) {
$session->setName('session-');
$session->start();
$session->set('sessionId', $session->getId());
}
if ($queryToken !== null) {
$this->setCleanedUrl($event, $master);
$token = TokenEncoder::decodeToken($queryToken);
$this->verifyAndSetCookie($token, $queryNonce, $queryToken, $master);
} else {
$token = TokenEncoder::decodeToken($cookieToken);
}
$session->set(PlatformRequest::HEADER_CONTEXT_TOKEN, $token);
$master->headers->set(
PlatformRequest::HEADER_CONTEXT_TOKEN,
$session->get(PlatformRequest::HEADER_CONTEXT_TOKEN)
);
}
private function getDomain(Request $request): string
{
$split = array_reverse(explode('.', $request->getHost()));
return sprintf('.%s.%s', $split[1], $split[0]);
}
private function setCleanedUrl(RequestEvent $event, Request $request): void
{
$url = $request->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL)
. $request->attributes->get(RequestTransformer::ORIGINAL_REQUEST_URI);
if (empty($url)) {
return;
}
$url = $this->removeFromUrl($url);
$event->setResponse(new RedirectResponse($url));
}
private function removeFromUrl(string $url): string
{
$parsedUrl = parse_url($url);
$query = [];
if (isset($parsedUrl['query'])) {
parse_str($parsedUrl['query'], $query);
unset($query[self::TOKEN_NAME]);
unset($query[self::NONCE]);
}
$path = $parsedUrl['path'] ?? '';
$query = !empty($query) ? '?'. http_build_query($query) : '';
return $parsedUrl['scheme']. '://'. $parsedUrl['host']. $path. $query;
}
private function verifyAndSetCookie(string $token, string $queryNonce, string $queryToken, Request $request): void
{
$context = $this->contextPersister->load($token, '');
$nonce = $context[self::NONCE] ?? null;
if ($nonce !== $queryNonce) {
return;
}
$this->contextPersister->save(
$token,
[self::NONCE => Random::getAlphanumericString(self::NONCE_LENGTH)],
''
);
setcookie(
self::TOKEN_NAME,
$queryToken,
(new \DateTime())->modify('+1 year')->getTimestamp(),
'/',
$this->getDomain($request)
);
}
}