vendor/shopware/core/Content/Seo/SeoUrlGenerator.php line 66

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Seo;
  3. use Shopware\Core\Content\Seo\Exception\InvalidTemplateException;
  4. use Shopware\Core\Content\Seo\SeoUrl\SeoUrlEntity;
  5. use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlMapping;
  6. use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteConfig;
  7. use Shopware\Core\Content\Seo\SeoUrlRoute\SeoUrlRouteInterface;
  8. use Shopware\Core\Framework\Adapter\Twig\TwigVariableParser;
  9. use Shopware\Core\Framework\Context;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Common\RepositoryIterator;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
  12. use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  14. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  15. use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Runtime;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  19. use Shopware\Core\System\SalesChannel\SalesChannelEntity;
  20. use Symfony\Component\HttpFoundation\RequestStack;
  21. use Symfony\Component\Routing\RouterInterface;
  22. use Twig\Environment;
  23. use Twig\Error\SyntaxError;
  24. use Twig\Loader\ArrayLoader;
  25. class SeoUrlGenerator
  26. {
  27.     public const ESCAPE_SLUGIFY 'slugifyurlencode';
  28.     private RouterInterface $router;
  29.     private Environment $twig;
  30.     private DefinitionInstanceRegistry $definitionRegistry;
  31.     private RequestStack $requestStack;
  32.     private TwigVariableParser $twigVariableParser;
  33.     /**
  34.      * @internal
  35.      */
  36.     public function __construct(
  37.         DefinitionInstanceRegistry $definitionRegistry,
  38.         RouterInterface $router,
  39.         RequestStack $requestStack,
  40.         Environment $environment,
  41.         TwigVariableParser $twigVariableParser
  42.     ) {
  43.         $this->definitionRegistry $definitionRegistry;
  44.         $this->router $router;
  45.         $this->requestStack $requestStack;
  46.         $this->twig $environment;
  47.         $this->twigVariableParser $twigVariableParser;
  48.     }
  49.     /**
  50.      * @feature-deprecated (flag:FEATURE_NEXT_13410) Parameter $salesChannel will be required
  51.      *
  52.      * @param array<string|array<string, string>> $ids
  53.      *
  54.      * @return iterable<SeoUrlEntity>
  55.      */
  56.     public function generate(array $idsstring $templateSeoUrlRouteInterface $routeContext $context, ?SalesChannelEntity $salesChannel): iterable
  57.     {
  58.         $criteria = new Criteria($ids);
  59.         $route->prepareCriteria($criteria$salesChannel);
  60.         $config $route->getConfig();
  61.         $repository $this->definitionRegistry->getRepository($config->getDefinition()->getEntityName());
  62.         $associations $this->getAssociations($template$repository->getDefinition());
  63.         $criteria->addAssociations($associations);
  64.         $criteria->setLimit(50);
  65.         /** @var RepositoryIterator $iterator */
  66.         $iterator $context->enableInheritance(static function (Context $context) use ($repository$criteria) {
  67.             return new RepositoryIterator($repository$context$criteria);
  68.         });
  69.         $this->setTwigTemplate($config$template);
  70.         while ($entities $iterator->fetch()) {
  71.             yield from $this->generateUrls($route$config$salesChannel$entities);
  72.         }
  73.     }
  74.     /**
  75.      * @internal (flag:FEATURE_NEXT_13410) Parameter $salesChannel will be required
  76.      *
  77.      * @param EntityCollection<Entity> $entities
  78.      *
  79.      * @return iterable<SeoUrlEntity>
  80.      */
  81.     private function generateUrls(SeoUrlRouteInterface $seoUrlRouteSeoUrlRouteConfig $config, ?SalesChannelEntity $salesChannelEntityCollection $entities): iterable
  82.     {
  83.         $request $this->requestStack->getMainRequest();
  84.         $basePath $request $request->getBasePath() : '';
  85.         /** @var Entity $entity */
  86.         foreach ($entities as $entity) {
  87.             $seoUrl = new SeoUrlEntity();
  88.             $seoUrl->setForeignKey($entity->getUniqueIdentifier());
  89.             $seoUrl->setIsCanonical(true);
  90.             $seoUrl->setIsModified(false);
  91.             $seoUrl->setIsDeleted(false);
  92.             $copy = clone $seoUrl;
  93.             $mapping $seoUrlRoute->getMapping($entity$salesChannel);
  94.             $copy->setError($mapping->getError());
  95.             $pathInfo $this->router->generate($config->getRouteName(), $mapping->getInfoPathContext());
  96.             $pathInfo $this->removePrefix($pathInfo$basePath);
  97.             $copy->setPathInfo($pathInfo);
  98.             $seoPathInfo $this->getSeoPathInfo($mapping$config);
  99.             if ($seoPathInfo === null || $seoPathInfo === '') {
  100.                 continue;
  101.             }
  102.             $copy->setSeoPathInfo($seoPathInfo);
  103.             if ($salesChannel !== null) {
  104.                 $copy->setSalesChannelId($salesChannel->getId());
  105.             } else {
  106.                 $copy->setSalesChannelId(null);
  107.             }
  108.             yield $copy;
  109.         }
  110.     }
  111.     private function getSeoPathInfo(SeoUrlMapping $mappingSeoUrlRouteConfig $config): ?string
  112.     {
  113.         try {
  114.             return trim($this->twig->render('template'$mapping->getSeoPathInfoContext()));
  115.         } catch (\Throwable $error) {
  116.             if (!$config->getSkipInvalid()) {
  117.                 throw $error;
  118.             }
  119.             return null;
  120.         }
  121.     }
  122.     private function setTwigTemplate(SeoUrlRouteConfig $configstring $template): void
  123.     {
  124.         $template '{% autoescape \'' self::ESCAPE_SLUGIFY "' %}$template{% endautoescape %}";
  125.         $this->twig->setLoader(new ArrayLoader(['template' => $template]));
  126.         try {
  127.             $this->twig->loadTemplate($this->twig->getTemplateClass('template'), 'template');
  128.         } catch (SyntaxError $syntaxError) {
  129.             if (!$config->getSkipInvalid()) {
  130.                 throw new InvalidTemplateException('Syntax error: ' $syntaxError->getMessage());
  131.             }
  132.         }
  133.     }
  134.     private function removePrefix(string $subjectstring $prefix): string
  135.     {
  136.         if (!$prefix || mb_strpos($subject$prefix) !== 0) {
  137.             return $subject;
  138.         }
  139.         return mb_substr($subjectmb_strlen($prefix));
  140.     }
  141.     /**
  142.      * @return list<string>
  143.      */
  144.     private function getAssociations(string $templateEntityDefinition $definition): array
  145.     {
  146.         try {
  147.             $variables $this->twigVariableParser->parse($template);
  148.         } catch (\Exception $e) {
  149.             $e = new InvalidTemplateException($e->getMessage());
  150.             throw $e;
  151.         }
  152.         $associations = [];
  153.         foreach ($variables as $variable) {
  154.             $fields EntityDefinitionQueryHelper::getFieldsOfAccessor($definition$variabletrue);
  155.             /** @var Field|null $lastField */
  156.             $lastField end($fields);
  157.             $runtime = new Runtime();
  158.             if ($lastField && $lastField->getFlag(Runtime::class)) {
  159.                 $associations array_merge($associations$runtime->getDepends());
  160.             }
  161.             $associations[] = EntityDefinitionQueryHelper::getAssociationPath($variable$definition);
  162.         }
  163.         return array_filter(array_unique($associations));
  164.     }
  165. }