src/Core/Content/Sitemap/Service/SitemapExporter.php line 54

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Sitemap\Service;
  3. use League\Flysystem\FilesystemOperator;
  4. use Psr\Cache\CacheItemPoolInterface;
  5. use Shopware\Core\Content\Sitemap\Event\SitemapGeneratedEvent;
  6. use Shopware\Core\Content\Sitemap\Exception\AlreadyLockedException;
  7. use Shopware\Core\Content\Sitemap\Provider\AbstractUrlProvider;
  8. use Shopware\Core\Content\Sitemap\Struct\SitemapGenerationResult;
  9. use Shopware\Core\Content\Sitemap\Struct\Url;
  10. use Shopware\Core\Content\Sitemap\Struct\UrlResult;
  11. use Shopware\Core\Framework\Log\Package;
  12. use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelDomain\SalesChannelDomainCollection;
  13. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  14. use Shopware\Core\System\SystemConfig\Exception\InvalidDomainException;
  15. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  16. #[Package('sales-channel')]
  17. class SitemapExporter implements SitemapExporterInterface
  18. {
  19.     /**
  20.      * @var array<string, SitemapHandleInterface>
  21.      */
  22.     private array $sitemapHandles = [];
  23.     /**
  24.      * @internal
  25.      *
  26.      * @param iterable<AbstractUrlProvider> $urlProvider
  27.      */
  28.     public function __construct(
  29.         private readonly iterable $urlProvider,
  30.         private readonly CacheItemPoolInterface $cache,
  31.         private readonly int $batchSize,
  32.         private readonly FilesystemOperator $filesystem,
  33.         private readonly SitemapHandleFactoryInterface $sitemapHandleFactory,
  34.         private readonly EventDispatcherInterface $dispatcher
  35.     ) {
  36.     }
  37.     /**
  38.      * {@inheritdoc}
  39.      */
  40.     public function generate(SalesChannelContext $contextbool $force false, ?string $lastProvider null, ?int $offset null): SitemapGenerationResult
  41.     {
  42.         $this->lock($context$force);
  43.         try {
  44.             $this->initSitemapHandles($context);
  45.             foreach ($this->urlProvider as $urlProvider) {
  46.                 do {
  47.                     $result $urlProvider->getUrls($context$this->batchSize$offset);
  48.                     $this->processSiteMapHandles($result);
  49.                     $needRun $result->getNextOffset() !== null;
  50.                     $offset $result->getNextOffset();
  51.                 } while ($needRun);
  52.             }
  53.             $this->finishSitemapHandles();
  54.         } finally {
  55.             $this->unlock($context);
  56.         }
  57.         $this->dispatcher->dispatch(new SitemapGeneratedEvent($context));
  58.         return new SitemapGenerationResult(
  59.             true,
  60.             $lastProvider,
  61.             null,
  62.             $context->getSalesChannel()->getId(),
  63.             $context->getLanguageId()
  64.         );
  65.     }
  66.     private function lock(SalesChannelContext $salesChannelContextbool $force): void
  67.     {
  68.         $key $this->generateCacheKeyForSalesChannel($salesChannelContext);
  69.         $item $this->cache->getItem($key);
  70.         if ($item->isHit() && !$force) {
  71.             throw new AlreadyLockedException($salesChannelContext);
  72.         }
  73.         $item->set(true);
  74.         $this->cache->save($item);
  75.     }
  76.     private function unlock(SalesChannelContext $salesChannelContext): void
  77.     {
  78.         $this->cache->deleteItem($this->generateCacheKeyForSalesChannel($salesChannelContext));
  79.     }
  80.     private function generateCacheKeyForSalesChannel(SalesChannelContext $salesChannelContext): string
  81.     {
  82.         return sprintf('sitemap-exporter-running-%s-%s'$salesChannelContext->getSalesChannel()->getId(), $salesChannelContext->getLanguageId());
  83.     }
  84.     private function initSitemapHandles(SalesChannelContext $context): void
  85.     {
  86.         $languageId $context->getLanguageId();
  87.         $domainsEntity $context->getSalesChannel()->getDomains();
  88.         $sitemapDomains = [];
  89.         if ($domainsEntity instanceof SalesChannelDomainCollection) {
  90.             foreach ($domainsEntity as $domain) {
  91.                 if ($domain->getLanguageId() === $languageId) {
  92.                     $urlParts \parse_url($domain->getUrl());
  93.                     if ($urlParts === false) {
  94.                         continue;
  95.                     }
  96.                     $arrayKey = ($urlParts['host'] ?? '') . ($urlParts['path'] ?? '');
  97.                     if (\array_key_exists($arrayKey$sitemapDomains) && $sitemapDomains[$arrayKey]['scheme'] === 'https') {
  98.                         // NEXT-21735 - does not execute on every test run
  99.                         continue;
  100.                     }
  101.                     $sitemapDomains[$arrayKey] = [
  102.                         'url' => $domain->getUrl(),
  103.                         'scheme' => $urlParts['scheme'] ?? '',
  104.                     ];
  105.                 }
  106.             }
  107.         }
  108.         $sitemapHandles = [];
  109.         foreach ($sitemapDomains as $sitemapDomain) {
  110.             $sitemapHandles[$sitemapDomain['url']] = $this->sitemapHandleFactory->create($this->filesystem$context$sitemapDomain['url']);
  111.         }
  112.         if (empty($sitemapHandles)) {
  113.             throw new InvalidDomainException('Empty domain');
  114.         }
  115.         $this->sitemapHandles $sitemapHandles;
  116.     }
  117.     private function processSiteMapHandles(UrlResult $result): void
  118.     {
  119.         /** @var SitemapHandle $sitemapHandle */
  120.         foreach ($this->sitemapHandles as $host => $sitemapHandle) {
  121.             /** @var Url[] $urls */
  122.             $urls = [];
  123.             foreach ($result->getUrls() as $url) {
  124.                 $newUrl = clone $url;
  125.                 $newUrl->setLoc(empty($newUrl->getLoc()) ? $host $host '/' $newUrl->getLoc());
  126.                 $urls[] = $newUrl;
  127.             }
  128.             $sitemapHandle->write($urls);
  129.         }
  130.     }
  131.     private function finishSitemapHandles(): void
  132.     {
  133.         /** @var SitemapHandle $sitemapHandle */
  134.         foreach ($this->sitemapHandles as $index => $sitemapHandle) {
  135.             if ($index === array_key_first($this->sitemapHandles)) {
  136.                 $sitemapHandle->finish();
  137.                 continue;
  138.             }
  139.             $sitemapHandle->finish(false);
  140.         }
  141.     }
  142. }