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

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