vendor/shopware/core/Content/Cms/SalesChannel/SalesChannelCmsPageLoader.php line 43

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Cms\SalesChannel;
  3. use Shopware\Core\Content\Cms\Aggregate\CmsBlock\CmsBlockEntity;
  4. use Shopware\Core\Content\Cms\Aggregate\CmsSection\CmsSectionCollection;
  5. use Shopware\Core\Content\Cms\Aggregate\CmsSection\CmsSectionEntity;
  6. use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
  7. use Shopware\Core\Content\Cms\CmsPageCollection;
  8. use Shopware\Core\Content\Cms\CmsPageEntity;
  9. use Shopware\Core\Content\Cms\DataResolver\CmsSlotsDataResolver;
  10. use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
  11. use Shopware\Core\Content\Cms\Events\CmsPageLoadedEvent;
  12. use Shopware\Core\Content\Cms\Events\CmsPageLoaderCriteriaEvent;
  13. use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductBoxStruct;
  14. use Shopware\Core\Content\Cms\SalesChannel\Struct\ProductSliderStruct;
  15. use Shopware\Core\Framework\Adapter\Cache\Event\AddCacheTagEvent;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  17. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  18. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  21. use Shopware\Core\Framework\Log\Package;
  22. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  23. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  24. use Symfony\Component\HttpFoundation\Request;
  25. #[Package('buyers-experience')]
  26. class SalesChannelCmsPageLoader implements SalesChannelCmsPageLoaderInterface
  27. {
  28.     /**
  29.      * @internal
  30.      *
  31.      * @param EntityRepository<CmsPageCollection> $cmsPageRepository
  32.      */
  33.     public function __construct(
  34.         private readonly EntityRepository $cmsPageRepository,
  35.         private readonly CmsSlotsDataResolver $slotDataResolver,
  36.         private readonly EventDispatcherInterface $dispatcher
  37.     ) {
  38.     }
  39.     public function load(
  40.         Request $request,
  41.         Criteria $criteria,
  42.         SalesChannelContext $context,
  43.         ?array $config null,
  44.         ?ResolverContext $resolverContext null
  45.     ): EntitySearchResult {
  46.         $this->dispatcher->dispatch(new CmsPageLoaderCriteriaEvent($request$criteria$context));
  47.         $config ??= [];
  48.         // ensure sections, blocks and slots are loaded, slots and blocks can be restricted by caller
  49.         $criteria->addAssociation('sections.backgroundMedia');
  50.         $criteria->addAssociation('sections.blocks.backgroundMedia');
  51.         $criteria->addAssociation('sections.blocks.slots');
  52.         // step 1, load cms pages with blocks and slots
  53.         $result $this->cmsPageRepository->search($criteria$context->getContext());
  54.         $pages $result->getEntities();
  55.         foreach ($pages as $page) {
  56.             $sections $page->getSections();
  57.             if ($sections === null) {
  58.                 continue;
  59.             }
  60.             $sections->sort(fn (CmsSectionEntity $aCmsSectionEntity $b) => $a->getPosition() <=> $b->getPosition());
  61.             if (!$resolverContext) {
  62.                 $resolverContext = new ResolverContext($context$request);
  63.             }
  64.             // step 2, sort blocks into sectionPositions
  65.             foreach ($sections as $section) {
  66.                 $blocks $section->getBlocks();
  67.                 if ($blocks === null) {
  68.                     continue;
  69.                 }
  70.                 $blocks->sort(fn (CmsBlockEntity $aCmsBlockEntity $b) => $a->getPosition() <=> $b->getPosition());
  71.                 foreach ($blocks as $block) {
  72.                     $slots $block->getSlots();
  73.                     if ($slots === null) {
  74.                         continue;
  75.                     }
  76.                     $slots->sort(fn (CmsSlotEntity $aCmsSlotEntity $b) => $a->getSlot() <=> $b->getSlot());
  77.                 }
  78.             }
  79.             // step 3, find config overwrite
  80.             $overwrite $config[$page->getId()] ?? $config;
  81.             // step 4, overwrite slot config
  82.             $this->overwriteSlotConfig($sections$overwrite);
  83.             // step 5, resolve slot data
  84.             $this->loadSlotData($sections$resolverContext);
  85.         }
  86.         $this->dispatcher->dispatch(new CmsPageLoadedEvent($request$pages$context));
  87.         $this->dispatcher->dispatch(new AddCacheTagEvent(...$this->extractProductIds($pages)));
  88.         return $result;
  89.     }
  90.     private function loadSlotData(CmsSectionCollection $sectionsResolverContext $resolverContext): void
  91.     {
  92.         $blocks $sections->getBlocks();
  93.         $slots $this->slotDataResolver->resolve($blocks->getSlots(), $resolverContext);
  94.         $blocks->setSlots($slots);
  95.     }
  96.     /**
  97.      * @param array<string, mixed> $config
  98.      */
  99.     private function overwriteSlotConfig(CmsSectionCollection $sections, array $config): void
  100.     {
  101.         foreach ($sections->getBlocks()->getSlots() as $slot) {
  102.             if ($slot->getConfig() === null && $slot->getTranslation('config') !== null) {
  103.                 $slot->setConfig($slot->getTranslation('config'));
  104.             }
  105.             if (empty($config)) {
  106.                 continue;
  107.             }
  108.             if (!isset($config[$slot->getId()])) {
  109.                 continue;
  110.             }
  111.             $defaultConfig $slot->getConfig() ?? [];
  112.             $merged array_replace_recursive(
  113.                 $defaultConfig,
  114.                 $config[$slot->getId()]
  115.             );
  116.             $slot->setConfig($merged);
  117.             $slot->addTranslated('config'$merged);
  118.         }
  119.     }
  120.     /**
  121.      * @param EntityCollection<CmsPageEntity> $pages
  122.      *
  123.      * @return array<string>
  124.      */
  125.     private function extractProductIds(EntityCollection $pages): array
  126.     {
  127.         $ids = [];
  128.         $streamIds = [];
  129.         /** @var CmsPageEntity $page */
  130.         foreach ($pages as $page) {
  131.             $slots $page->getElementsOfType('product-slider');
  132.             /** @var CmsSlotEntity $slot */
  133.             foreach ($slots as $slot) {
  134.                 $slider $slot->getData();
  135.                 if (!$slider instanceof ProductSliderStruct) {
  136.                     continue;
  137.                 }
  138.                 if ($slider->getStreamId() !== null) {
  139.                     $streamIds[] = $slider->getStreamId();
  140.                 }
  141.                 if ($slider->getProducts() === null) {
  142.                     continue;
  143.                 }
  144.                 foreach ($slider->getProducts() as $product) {
  145.                     $ids[] = $product->getId();
  146.                     $ids[] = $product->getParentId();
  147.                 }
  148.             }
  149.             $slots $page->getElementsOfType('product-box');
  150.             /** @var CmsSlotEntity $slot */
  151.             foreach ($slots as $slot) {
  152.                 $box $slot->getData();
  153.                 if (!$box instanceof ProductBoxStruct) {
  154.                     continue;
  155.                 }
  156.                 if ($box->getProduct() === null) {
  157.                     continue;
  158.                 }
  159.                 $ids[] = $box->getProduct()->getId();
  160.                 $ids[] = $box->getProduct()->getParentId();
  161.             }
  162.             $ids array_values(array_unique(array_filter($ids)));
  163.         }
  164.         return [
  165.             ...array_map(EntityCacheKeyGenerator::buildProductTag(...), $ids),
  166.             ...array_map(EntityCacheKeyGenerator::buildStreamTag(...), $streamIds),
  167.             ...array_map(EntityCacheKeyGenerator::buildCmsTag(...), $pages->getIds()),
  168.         ];
  169.     }
  170. }