vendor/shopware/core/Content/Seo/SeoUrlPlaceholderHandler.php line 60

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