<?php declare(strict_types=1);
namespace Shopware\Storefront\Framework\Routing;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\PlatformRequest;
use Symfony\Bundle\FrameworkBundle\Routing\Router as SymfonyRouter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
#[Package('storefront')]
class Router implements RouterInterface, RequestMatcherInterface, WarmableInterface, ServiceSubscriberInterface
{
/**
* @var int Used to indicate the router that we only need the path info without the sales channel prefix
*/
public const PATH_INFO = 10;
/**
* @var SymfonyRouter
*/
private $decorated;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @internal
*/
public function __construct(SymfonyRouter $decorated, RequestStack $requestStack)
{
$this->decorated = $decorated;
$this->requestStack = $requestStack;
}
/**
* @return array
*/
public static function getSubscribedServices()
{
return SymfonyRouter::getSubscribedServices();
}
/**
* @return array<string>
*/
public function warmUp(string $cacheDir)
{
return $this->decorated->warmUp($cacheDir);
}
/**
* @return array
*/
public function matchRequest(Request $request)
{
if (!$request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID)) {
return $this->decorated->matchRequest($request);
}
$server = array_merge(
$request->server->all(),
['REQUEST_URI' => $request->attributes->get(RequestTransformer::SALES_CHANNEL_RESOLVED_URI)]
);
$localClone = $request->duplicate(null, null, null, null, null, $server);
return $this->decorated->matchRequest($localClone);
}
public function setContext(RequestContext $context)
{
return $this->decorated->setContext($context);
}
/**
* @return RequestContext
*/
public function getContext()
{
return $this->decorated->getContext();
}
/**
* @return RouteCollection
*/
public function getRouteCollection()
{
return $this->decorated->getRouteCollection();
}
/**
* @return string
*/
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH)
{
$basePath = $this->getBasePath();
if ($referenceType === self::PATH_INFO) {
$route = $this->decorated->generate($name, $parameters, self::ABSOLUTE_PATH);
return $this->removePrefix($route, $basePath);
}
if (!$this->isStorefrontRoute($name)) {
return $this->decorated->generate($name, $parameters, $referenceType);
}
$salesChannelBaseUrl = $this->getSalesChannelBaseUrl();
// we need to insert the sales channel base url between the baseUrl and the infoPath
switch ($referenceType) {
case self::NETWORK_PATH:
case self::ABSOLUTE_URL:
$schema = '';
if ($referenceType === self::ABSOLUTE_URL) {
$schema = $this->getContext()->getScheme() . ':';
}
$schemaAuthority = $schema . '//' . $this->getContext()->getHost();
if ($this->getContext()->getHttpPort() !== 80) {
$schemaAuthority .= ':' . $this->getContext()->getHttpPort();
} elseif ($this->getContext()->getHttpsPort() !== 443) {
$schemaAuthority .= ':' . $this->getContext()->getHttpsPort();
}
$generated = $this->decorated->generate($name, $parameters);
$pathInfo = $this->removePrefix($generated, $basePath);
$rewrite = $schemaAuthority . rtrim($basePath, '/') . rtrim($salesChannelBaseUrl, '/') . $pathInfo;
break;
case self::RELATIVE_PATH:
// remove base path from generated url (/shopware/public or /)
$generated = $this->removePrefix(
$this->decorated->generate($name, $parameters, self::RELATIVE_PATH),
$basePath
);
// url contains the base path and the base url
// base url /shopware/public/de
$rewrite = ltrim($salesChannelBaseUrl, '/') . $generated;
break;
case self::ABSOLUTE_PATH:
default:
$generated = $this->removePrefix(
$this->decorated->generate($name, $parameters),
$basePath
);
$rewrite = $basePath . rtrim($salesChannelBaseUrl, '/') . $generated;
break;
}
return $rewrite;
}
/**
* @return array
*/
public function match($pathinfo)
{
return $this->decorated->match($pathinfo);
}
private function removePrefix(string $subject, string $prefix): string
{
if (!$prefix || mb_strpos($subject, $prefix) !== 0) {
return $subject;
}
return mb_substr($subject, mb_strlen($prefix));
}
private function getSalesChannelBaseUrl(): string
{
$request = $this->requestStack->getMainRequest();
if (!$request) {
return '';
}
$url = (string) $request->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
if (empty($url)) {
return $url;
}
return '/' . trim($url, '/') . '/';
}
private function getBasePath(): string
{
$request = $this->requestStack->getMainRequest();
if (!$request) {
return '';
}
return $request->getBasePath();
}
private function isStorefrontRoute(string $name): bool
{
return strncmp($name, 'frontend.', 9) === 0
|| strncmp($name, 'widgets.', 8) === 0
|| strncmp($name, 'payment.', 8) === 0;
}
}