src/Core/Content/Sitemap/Provider/CategoryUrlProvider.php line 50

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Sitemap\Provider;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Content\Category\CategoryDefinition;
  5. use Shopware\Core\Content\Category\CategoryEntity;
  6. use Shopware\Core\Content\Sitemap\Service\ConfigHandler;
  7. use Shopware\Core\Content\Sitemap\Struct\Url;
  8. use Shopware\Core\Content\Sitemap\Struct\UrlResult;
  9. use Shopware\Core\Defaults;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Dbal\Common\IteratorFactory;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Doctrine\FetchModeHelper;
  12. use Shopware\Core\Framework\Log\Package;
  13. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  14. use Shopware\Core\Framework\Uuid\Uuid;
  15. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  16. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  17. use Symfony\Component\Routing\RouterInterface;
  18. #[Package('sales-channel')]
  19. class CategoryUrlProvider extends AbstractUrlProvider
  20. {
  21.     final public const CHANGE_FREQ 'daily';
  22.     /**
  23.      * @internal
  24.      */
  25.     public function __construct(private readonly ConfigHandler $configHandler, private readonly Connection $connection, private readonly CategoryDefinition $definition, private readonly IteratorFactory $iteratorFactory, private readonly RouterInterface $router)
  26.     {
  27.     }
  28.     public function getDecorated(): AbstractUrlProvider
  29.     {
  30.         throw new DecorationPatternException(self::class);
  31.     }
  32.     public function getName(): string
  33.     {
  34.         return 'category';
  35.     }
  36.     /**
  37.      * {@inheritdoc}
  38.      *
  39.      * @throws \Exception
  40.      */
  41.     public function getUrls(SalesChannelContext $contextint $limit, ?int $offset null): UrlResult
  42.     {
  43.         $categories $this->getCategories($context$limit$offset);
  44.         if (empty($categories)) {
  45.             return new UrlResult([], null);
  46.         }
  47.         $keys FetchModeHelper::keyPair($categories);
  48.         $seoUrls $this->getSeoUrls(array_values($keys), 'frontend.navigation.page'$context$this->connection);
  49.         $seoUrls FetchModeHelper::groupUnique($seoUrls);
  50.         $urls = [];
  51.         $url = new Url();
  52.         foreach ($categories as $category) {
  53.             if (!isset($seoUrls[$category['id']])) {
  54.                 continue;
  55.             }
  56.             $lastMod $category['updated_at'] ?: $category['created_at'];
  57.             $lastMod = (new \DateTime($lastMod))->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  58.             $newUrl = clone $url;
  59.             if (isset($seoUrls[$category['id']])) {
  60.                 $newUrl->setLoc($seoUrls[$category['id']]['seo_path_info']);
  61.             } else {
  62.                 $newUrl->setLoc($this->router->generate('frontend.navigation.page', ['navigationId' => $category->getId()], UrlGeneratorInterface::ABSOLUTE_PATH));
  63.             }
  64.             $newUrl->setLastmod(new \DateTime($lastMod));
  65.             $newUrl->setChangefreq(self::CHANGE_FREQ);
  66.             $newUrl->setResource(CategoryEntity::class);
  67.             $newUrl->setIdentifier($category['id']);
  68.             $urls[] = $newUrl;
  69.         }
  70.         $keys array_keys($keys);
  71.         /** @var int|null $nextOffset */
  72.         $nextOffset array_pop($keys);
  73.         return new UrlResult($urls$nextOffset);
  74.     }
  75.     private function getCategories(SalesChannelContext $contextint $limit, ?int $offset): array
  76.     {
  77.         $lastId null;
  78.         if ($offset) {
  79.             $lastId = ['offset' => $offset];
  80.         }
  81.         $iterator $this->iteratorFactory->createIterator($this->definition$lastId);
  82.         $query $iterator->getQuery();
  83.         $query->setMaxResults($limit);
  84.         $query->addSelect([
  85.             '`category`.created_at',
  86.             '`category`.updated_at',
  87.         ]);
  88.         $wheres = [];
  89.         $categoryIds array_filter([
  90.             $context->getSalesChannel()->getNavigationCategoryId(),
  91.             $context->getSalesChannel()->getFooterCategoryId(),
  92.             $context->getSalesChannel()->getServiceCategoryId(),
  93.         ]);
  94.         foreach ($categoryIds as $id) {
  95.             $wheres[] = '`category`.path LIKE ' $query->createNamedParameter('%|' $id '|%');
  96.         }
  97.         $query->andWhere('(' implode(' OR '$wheres) . ')');
  98.         $query->andWhere('`category`.version_id = :versionId');
  99.         $query->andWhere('`category`.active = 1');
  100.         $query->andWhere('`category`.type != :linkType');
  101.         $excludedCategoryIds $this->getExcludedCategoryIds($context);
  102.         if (!empty($excludedCategoryIds)) {
  103.             $query->andWhere('`category`.id NOT IN (:categoryIds)');
  104.             $query->setParameter('categoryIds'Uuid::fromHexToBytesList($excludedCategoryIds), Connection::PARAM_STR_ARRAY);
  105.         }
  106.         $query->setParameter('versionId'Uuid::fromHexToBytes(Defaults::LIVE_VERSION));
  107.         $query->setParameter('linkType'CategoryDefinition::TYPE_LINK);
  108.         return $query->executeQuery()->fetchAllAssociative();
  109.     }
  110.     private function getExcludedCategoryIds(SalesChannelContext $salesChannelContext): array
  111.     {
  112.         $salesChannelId $salesChannelContext->getSalesChannel()->getId();
  113.         $excludedUrls $this->configHandler->get(ConfigHandler::EXCLUDED_URLS_KEY);
  114.         if (empty($excludedUrls)) {
  115.             return [];
  116.         }
  117.         $excludedUrls array_filter($excludedUrls, static function (array $excludedUrl) use ($salesChannelId) {
  118.             if ($excludedUrl['resource'] !== CategoryEntity::class) {
  119.                 return false;
  120.             }
  121.             if ($excludedUrl['salesChannelId'] !== $salesChannelId) {
  122.                 return false;
  123.             }
  124.             return true;
  125.         });
  126.         return array_column($excludedUrls'identifier');
  127.     }
  128. }