src/Core/Content/Seo/SeoUrlPlaceholderHandler.php line 42

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Seo;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Framework\Log\Package;
  5. use Shopware\Core\Framework\Uuid\Uuid;
  6. use Shopware\Core\Profiling\Profiler;
  7. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  8. use Symfony\Component\HttpFoundation\RequestStack;
  9. use Symfony\Component\Routing\RouterInterface;
  10. use function preg_replace_callback;
  11. #[Package('sales-channel')]
  12. class SeoUrlPlaceholderHandler implements SeoUrlPlaceholderHandlerInterface
  13. {
  14.     final public const DOMAIN_PLACEHOLDER '124c71d524604ccbad6042edce3ac799';
  15.     /**
  16.      * @internal
  17.      */
  18.     public function __construct(private readonly RequestStack $requestStack, private readonly RouterInterface $router, private readonly Connection $connection)
  19.     {
  20.     }
  21.     /**
  22.      * @param string $name
  23.      */
  24.     public function generate($name, array $parameters = []): string
  25.     {
  26.         $path $this->router->generate($name$parametersRouterInterface::ABSOLUTE_PATH);
  27.         $request $this->requestStack->getMainRequest();
  28.         $basePath $request $request->getBasePath() : '';
  29.         $path $this->removePrefix($path$basePath);
  30.         return self::DOMAIN_PLACEHOLDER $path '#';
  31.     }
  32.     public function replace(string $contentstring $hostSalesChannelContext $context): string
  33.     {
  34.         return Profiler::trace('seo-url-replacer', function () use ($content$host$context) {
  35.             $matches = [];
  36.             if (preg_match_all('/' self::DOMAIN_PLACEHOLDER '[^#]*#/'$content$matches)) {
  37.                 $mapping $this->createDefaultMapping($matches[0]);
  38.                 $seoMapping $this->createSeoMapping($context$mapping);
  39.                 foreach ($seoMapping as $key => $value) {
  40.                     $seoMapping[$key] = $host '/' ltrim($value'/');
  41.                 }
  42.                 return (string) preg_replace_callback('/' self::DOMAIN_PLACEHOLDER '[^#]*#/', static fn (array $match) => $seoMapping[$match[0]], $content);
  43.             }
  44.             return $content;
  45.         });
  46.     }
  47.     private function createDefaultMapping(array $matches): array
  48.     {
  49.         $mapping = [];
  50.         $placeholder \strlen(self::DOMAIN_PLACEHOLDER);
  51.         foreach ($matches as $match) {
  52.             // remove self::DOMAIN_PLACEHOLDER from start
  53.             // remove # from end
  54.             $mapping[$match] = substr((string) $match$placeholder, -1);
  55.         }
  56.         return $mapping;
  57.     }
  58.     private function createSeoMapping(SalesChannelContext $context, array $mapping): array
  59.     {
  60.         if (empty($mapping)) {
  61.             return [];
  62.         }
  63.         $query $this->connection->createQueryBuilder();
  64.         $query->addSelect(['seo_path_info''path_info']);
  65.         $query->from('seo_url');
  66.         $query->andWhere('seo_url.is_canonical = 1');
  67.         $query->andWhere('seo_url.path_info IN (:pathInfo)');
  68.         $query->andWhere('seo_url.language_id = :languageId');
  69.         $query->andWhere('seo_url.sales_channel_id = :salesChannelId OR seo_url.sales_channel_id IS NULL');
  70.         $query->andWhere('is_deleted = 0');
  71.         $query->setParameter('pathInfo'$mappingConnection::PARAM_STR_ARRAY);
  72.         $query->setParameter('languageId'Uuid::fromHexToBytes($context->getContext()->getLanguageId()));
  73.         $query->setParameter('salesChannelId'Uuid::fromHexToBytes($context->getSalesChannelId()));
  74.         $query->addOrderBy('seo_url.sales_channel_id');
  75.         $seoUrls $query->executeQuery()->fetchAllAssociative();
  76.         foreach ($seoUrls as $seoUrl) {
  77.             $seoPathInfo trim((string) $seoUrl['seo_path_info']);
  78.             if ($seoPathInfo === '') {
  79.                 continue;
  80.             }
  81.             $key self::DOMAIN_PLACEHOLDER $seoUrl['path_info'] . '#';
  82.             $mapping[$key] = $seoPathInfo;
  83.         }
  84.         return $mapping;
  85.     }
  86.     private function removePrefix(string $subjectstring $prefix): string
  87.     {
  88.         if (!$prefix || mb_strpos($subject$prefix) !== 0) {
  89.             return $subject;
  90.         }
  91.         return mb_substr($subjectmb_strlen($prefix));
  92.     }
  93. }