<?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; }}