custom/plugins/NetiNextProductDetailCms/src/Subscriber/ProductPageLoaderSubscriber.php line 98

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace NetInventors\NetiNextProductDetailCms\Subscriber;
  4. use NetInventors\NetiNextProductDetailCms\Core\Content\Product\Aggregate\ProductCms\ProductCmsEntity;
  5. use NetInventors\NetiNextProductDetailCms\Struct\PluginConfigStruct;
  6. use NetInventors\NetiNextProductDetailCms\Struct\Position;
  7. use NetInventors\NetiNextProductDetailCms\Struct\ProductCmsStruct;
  8. use Shopware\Core\Content\Cms\SalesChannel\SalesChannelCmsPageLoaderInterface;
  9. use Shopware\Core\Content\ProductStream\ProductStreamEntity;
  10. use Shopware\Core\Content\ProductStream\Service\ProductStreamBuilderInterface;
  11. use Shopware\Core\Framework\Context;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  20. use Shopware\Core\Framework\Uuid\Uuid;
  21. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  22. use Shopware\Storefront\Page\Page;
  23. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  24. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  25. use Symfony\Component\HttpFoundation\Request;
  26. use Twig\Environment;
  27. use Twig\Error\LoaderError;
  28. use Twig\Error\RuntimeError;
  29. use Twig\Error\SyntaxError;
  30. class ProductPageLoaderSubscriber implements EventSubscriberInterface
  31. {
  32.     /**
  33.      * @var EntityRepositoryInterface
  34.      */
  35.     private $productCmsRepository;
  36.     /**
  37.      * @var SalesChannelCmsPageLoaderInterface
  38.      */
  39.     private $cmsPageLoader;
  40.     /**
  41.      * @var Environment
  42.      */
  43.     private $twig;
  44.     /**
  45.      * @var PluginConfigStruct
  46.      */
  47.     private $pluginConfig;
  48.     /**
  49.      * @var ProductStreamBuilderInterface
  50.      */
  51.     private $productStreamBuilder;
  52.     /**
  53.      * @var EntityRepositoryInterface
  54.      */
  55.     private $productRepository;
  56.     public function __construct(
  57.         PluginConfigStruct $pluginConfig,
  58.         EntityRepositoryInterface $productCmsRepository,
  59.         SalesChannelCmsPageLoaderInterface $cmsPageLoader,
  60.         Environment $twig,
  61.         ProductStreamBuilderInterface $productStreamBuilder,
  62.         EntityRepositoryInterface $productRepository
  63.     ) {
  64.         $this->pluginConfig         $pluginConfig;
  65.         $this->productCmsRepository $productCmsRepository;
  66.         $this->cmsPageLoader        $cmsPageLoader;
  67.         $this->twig                 $twig;
  68.         $this->productStreamBuilder $productStreamBuilder;
  69.         $this->productRepository    $productRepository;
  70.     }
  71.     public static function getSubscribedEvents(): array
  72.     {
  73.         return [
  74.             ProductPageLoadedEvent::class => 'onProductPageLoaded',
  75.         ];
  76.     }
  77.     /**
  78.      * Loads the cms pages for the given product (or its parent product)
  79.      *
  80.      * @param ProductPageLoadedEvent $event
  81.      *
  82.      * @throws LoaderError
  83.      * @throws RuntimeError
  84.      * @throws SyntaxError
  85.      */
  86.     public function onProductPageLoaded(
  87.         ProductPageLoadedEvent $event
  88.     ): void {
  89.         if (false === $this->pluginConfig->isActive()) {
  90.             return;
  91.         }
  92.         $product  $event->getPage()->getProduct();
  93.         $cmsPages $this->getCmsPages($product->getId(), $product->getVersionId(), $event->getContext());
  94.         $productParentId $product->getParentId();
  95.         /** If the variant have no cms pages, we load it from it's parent (main variant) */
  96.         if (is_string($productParentId) && === $cmsPages->count()) {
  97.             $cmsPages $this->getCmsPages($productParentId$product->getVersionId(), $event->getContext());
  98.         }
  99.         $productCms = new ProductCmsStruct();
  100.         /** @var ProductCmsEntity $cmsPage */
  101.         foreach ($cmsPages->getElements() as $cmsPage) {
  102.             $cmsId $cmsPage->getCmsId();
  103.             if (!is_string($cmsId) || !Uuid::isValid($cmsId)) {
  104.                 continue;
  105.             }
  106.             $salesChannels  $cmsPage->getSalesChannels();
  107.             $salesChannelId $event->getSalesChannelContext()->getSalesChannelId();
  108.             if (!$salesChannels->has($salesChannelId) && $salesChannels->count()) {
  109.                 continue;
  110.             }
  111.             $html $this->buildCmsPageContent(
  112.                 $event->getRequest(),
  113.                 $event->getSalesChannelContext(),
  114.                 $cmsId
  115.             );
  116.             if ($cmsPage->isTwigCompiler()) {
  117.                 $html $this->twig->render(
  118.                     $this->twig->createTemplate($html),
  119.                     [
  120.                         'page' => $event->getPage(),
  121.                     ]
  122.                 );
  123.             }
  124.             if ($cmsPage->getTemplateMarker() && $cmsPage->getTemplateMarker()->isActive()) {
  125.                 $productCms->addTemplateMarkerContent(
  126.                     $cmsPage->getTemplateMarker()->getKey(),
  127.                     $html
  128.                 );
  129.                 continue;
  130.             }
  131.             switch ($cmsPage->getPosition()) {
  132.                 case Position::REPLACE_DESCRIPTION:
  133.                     $productCms->addReplaceProductDetailDescriptionContent($html);
  134.                     break;
  135.                 case Position::BELOW_DESCRIPTION:
  136.                     $productCms->addBelowProductDescriptionContent($html);
  137.                     break;
  138.                 case Position::ABOVE_DESCRIPTION:
  139.                     $productCms->addAboveProductDescriptionContent($html);
  140.                     break;
  141.                 case Position::ABOVE_DETAIL_DESCRIPTION_TITLE:
  142.                     $productCms->addAboveProductDetailDescriptionContent($html);
  143.                     break;
  144.                 case Position::BELOW_DETAIL_DESCRIPTION_CONTENT:
  145.                     $productCms->addBelowProductDetailDescriptionContent($html);
  146.                     break;
  147.                 case Position::ABOVE_PRODUCT_PROPERTIES:
  148.                     $productCms->addAboveProductPropertiesContent($html);
  149.                     break;
  150.                 case Position::BELOW_PRODUCT_PROPERTIES:
  151.                     $productCms->addBelowProductPropertiesContent($html);
  152.                     break;
  153.                 case Position::REPLACE_PRODUCT_PROPERTIES:
  154.                     $productCms->addReplaceProductPropertiesContent($html);
  155.                     break;
  156.                 case Position::LAST_PAGE_ELEMENT:
  157.                     $productCms->addLastPageElement($html);
  158.                     break;
  159.             }
  160.         }
  161.         $product->addExtension('netiProductCms'$productCms);
  162.     }
  163.     /**
  164.      * Builds the html content from the cmsPageId
  165.      *
  166.      * @param Request             $request
  167.      * @param SalesChannelContext $context
  168.      * @param string              $cmsPageId
  169.      *
  170.      * @return string
  171.      * @throws LoaderError
  172.      * @throws RuntimeError
  173.      * @throws SyntaxError
  174.      */
  175.     protected function buildCmsPageContent(
  176.         Request $request,
  177.         SalesChannelContext $context,
  178.         string $cmsPageId
  179.     ): string {
  180.         $pages $this->cmsPageLoader->load(
  181.             $request,
  182.             new Criteria([ $cmsPageId ]),
  183.             $context
  184.         );
  185.         return $this->twig->render(
  186.             '@Storefront/storefront/page/content/detail.html.twig',
  187.             [
  188.                 'cmsPage' => $pages->first(),
  189.             ]
  190.         );
  191.     }
  192.     /**
  193.      * Loads the associated cms pages from the product
  194.      *
  195.      * @param string  $productId
  196.      * @param string  $versionId
  197.      * @param Context $context
  198.      *
  199.      * @return EntitySearchResult
  200.      */
  201.     protected function getCmsPages(
  202.         string $productId,
  203.         ?string $versionId,
  204.         Context $context
  205.     ): EntitySearchResult {
  206.         $criteria = new Criteria();
  207.         $criteria->addFilter(
  208.             new EqualsFilter('productId'$productId),
  209.             new MultiFilter(
  210.                 MultiFilter::CONNECTION_OR,
  211.                 [
  212.                     new EqualsFilter('productVersionId'null),
  213.                     new EqualsFilter('productVersionId'$versionId),
  214.                 ]
  215.             )
  216.         );
  217.         $criteria->addSorting(new FieldSorting('priority'FieldSorting::DESCENDING));
  218.         $criteria->addAssociation('salesChannels');
  219.         $criteria->addAssociation('templateMarker');
  220.         $cmsPages $this->productCmsRepository->search($criteria$context);
  221.         $this->addCmsPagesByMatchingProductStreams($cmsPages$productId$versionId$context);
  222.         $cmsPages->sort(static function(ProductCmsEntity $aProductCmsEntity $b) {
  223.             return $b->getPriority() - $a->getPriority();
  224.         });
  225.         return $cmsPages;
  226.     }
  227.     /**
  228.      * Assigns cms pages whose product streams contains the given productId
  229.      *
  230.      * @param EntitySearchResult $result
  231.      * @param string             $productId
  232.      * @param string             $versionId
  233.      * @param Context            $context
  234.      */
  235.     protected function addCmsPagesByMatchingProductStreams(
  236.         EntitySearchResult $result,
  237.         string $productId,
  238.         ?string $versionId,
  239.         Context $context
  240.     ): void {
  241.         $cmsPages $this->getCmsPagesWithProductStream($context);
  242.         /** @var ProductCmsEntity $cmsPage */
  243.         foreach ($cmsPages->getElements() as $cmsPage) {
  244.             $filters         = [];
  245.             $productStreamId $cmsPage->getProductStreamId();
  246.             //Entity check is done to make sure, that the stream actually exists
  247.             if (is_string($productStreamId) && $cmsPage->getProductStream() instanceof ProductStreamEntity) {
  248.                 $filters $this->productStreamBuilder->buildFilters($productStreamId$context);
  249.             }
  250.             $criteria = new Criteria();
  251.             $criteria->addFilter(
  252.                 new EqualsFilter('id'$productId),
  253.                 new EqualsFilter('versionId'$versionId)
  254.             );
  255.             /** @var Filter $filter */
  256.             foreach ($filters as $filter) {
  257.                 $criteria->addFilter($filter);
  258.             }
  259.             $productIds $this->productRepository->searchIds($criteria$context);
  260.             if (null !== $productIds->firstId() && $cmsPage->getProductStream() instanceof ProductStreamEntity) {
  261.                 $result->add($cmsPage);
  262.             }
  263.         }
  264.     }
  265.     /**
  266.      * Finds all cms pages which have an associated product stream
  267.      *
  268.      * @param Context $context
  269.      *
  270.      * @return EntitySearchResult
  271.      */
  272.     protected function getCmsPagesWithProductStream(Context $context): EntitySearchResult
  273.     {
  274.         $criteria = new Criteria();
  275.         $criteria->addFilter(
  276.             new NotFilter(
  277.                 NotFilter::CONNECTION_OR,
  278.                 [
  279.                     new EqualsFilter('productStreamId'null),
  280.                 ]
  281.             )
  282.         );
  283.         $criteria->addSorting(new FieldSorting('priority'FieldSorting::DESCENDING));
  284.         $criteria->addAssociations(['templateMarker''productStream''salesChannels']);
  285.         return $this->productCmsRepository->search($criteria$context);
  286.     }
  287. }