vendor/crehler/tools/src/Product/ProductLoader.php line 49

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /**
  3.  * @copyright 2022 Crehler Sp. z o. o.
  4.  * @link https://crehler.com/
  5.  * @support support@crehler.com
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Crehler\Tools\Product;
  11. use Crehler\Tools\Enum\LoaderFlags;
  12. use Crehler\Tools\Product\Exception\SalesChannelContextException;
  13. use Shopware\Core\Content\Category\Service\CategoryBreadcrumbBuilder;
  14. use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
  15. use Shopware\Core\Content\Cms\SalesChannel\SalesChannelCmsPageLoaderInterface;
  16. use Shopware\Core\Content\Product\Aggregate\ProductVisibility\ProductVisibilityDefinition;
  17. use Shopware\Core\Content\Product\ProductDefinition;
  18. use Shopware\Core\Content\Product\SalesChannel\AbstractProductCloseoutFilterFactory;
  19. use Shopware\Core\Content\Product\SalesChannel\ProductAvailableFilter;
  20. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductCollection;
  21. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator;
  23. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  25. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  26. use Shopware\Core\PlatformRequest;
  27. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepository;
  28. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
  29. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  30. use Shopware\Core\System\SystemConfig\SystemConfigService;
  31. use Symfony\Component\HttpFoundation\Request;
  32. use Symfony\Component\HttpFoundation\RequestStack;
  33. class ProductLoader implements ProductLoaderInterface {
  34.     private array $cache = [];
  35.     public function __construct(private readonly SalesChannelRepository|SalesChannelRepositoryInterface $productRepository,
  36.                                 private readonly CategoryBreadcrumbBuilder $breadcrumbBuilder,
  37.                                 private readonly SalesChannelCmsPageLoaderInterface $cmsPageLoader,
  38.                                 private readonly RequestStack $requestStack,
  39.                                 private readonly SystemConfigService $config,
  40.                                 private readonly ProductDefinition $productDefinition,
  41.                                 private readonly EntityCacheKeyGenerator $generator,
  42.                                 private readonly AbstractProductCloseoutFilterFactory $productCloseoutFilterFactory
  43.     ) {}
  44.     public function load(string|array $idsSalesChannelContext|null $context, ...$flags): SalesChannelProductCollection
  45.     {
  46.         if (is_string($ids)) {
  47.             $ids = [$ids];
  48.         }
  49.         if (empty($ids)) {
  50.             return new SalesChannelProductCollection();
  51.         }
  52.         if ($context === null) {
  53.             $context $this->requestStack->getMasterRequest()->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECTnull);
  54.             if ($context === null) {
  55.                 throw new SalesChannelContextException();
  56.             }
  57.         }
  58.         $stringFlags array_map(fn ($flag) => $flag->name$flags);
  59.         $cacheKey md5(json_encode([$this->generator->getSalesChannelContextHash($context), $stringFlags]));
  60.         if (!isset($this->cache[$cacheKey])) {
  61.             $this->cache[$cacheKey] = [];
  62.         }
  63.         $requestedIds $ids;
  64.         $ids array_filter($ids, fn($id) => !isset($this->cache[$cacheKey][$id]));
  65.         if (!empty($ids)) {
  66.             $criteria = new Criteria($ids);
  67.             $criteria->setTitle('crehler-product-loader');
  68.             $this->addFilters($context$criteriain_array(LoaderFlags::IgnoreHideCloseoutProductsWhenOutOfStock$flags));
  69.             $products $this->productRepository
  70.                 ->search($criteria$context);
  71.             $collection $products->getEntities();
  72.         } else {
  73.             $collection = new SalesChannelProductCollection();
  74.         }
  75.         /** @var SalesChannelProductEntity $product */
  76.         foreach ($collection as $product) {
  77.             if (!in_array(LoaderFlags::SkipSeoCategory$flags) && $product->getSeoCategory()){
  78.                 $product->setSeoCategory($this->breadcrumbBuilder->build($product->getSeoCategory()));
  79.             }
  80.             if (in_array(LoaderFlags::SkipPageLoading$flags)) continue;
  81.             $pageId $product->getCmsPageId();
  82.             if ($pageId) {
  83.                 // clone product to prevent recursion encoding (see NEXT-17603)
  84.                 $resolverContext = new EntityResolverContext($context$this->requestStack->getMainRequest(), $this->productDefinition, clone $product);
  85.                 $pages $this->cmsPageLoader->load(
  86.                     $this->requestStack->getMainRequest(),
  87.                     $this->createCriteria($pageId$this->requestStack->getMainRequest()),
  88.                     $context,
  89.                     $product->getTranslation('slotConfig'),
  90.                     $resolverContext
  91.                 );
  92.                 if ($page $pages->first()) {
  93.                     $product->setCmsPage($page);
  94.                 }
  95.             }
  96.         }
  97.         foreach ($requestedIds as $id) {
  98.             if (isset($this->cache[$cacheKey][$id])) {
  99.                 $collection->add($this->cache[$cacheKey][$id]);
  100.             }
  101.         }
  102.         foreach ($collection as $product) {
  103.             $this->cache[$cacheKey][$product->getId()] = $product;
  104.         }
  105.         return $collection;
  106.     }
  107.     private function addFilters(SalesChannelContext $contextCriteria $criteriabool $ignoreHideCloseoutProductsWhenOutOfStock): void
  108.     {
  109.         $criteria->addFilter(
  110.             new ProductAvailableFilter($context->getSalesChannel()->getId(), ProductVisibilityDefinition::VISIBILITY_LINK)
  111.         );
  112.         if ($ignoreHideCloseoutProductsWhenOutOfStock) return;
  113.         $salesChannelId $context->getSalesChannel()->getId();
  114.         $hideCloseoutProductsWhenOutOfStock $this->config->get('core.listing.hideCloseoutProductsWhenOutOfStock'$salesChannelId);
  115.         if ($hideCloseoutProductsWhenOutOfStock) {
  116.             $filter $this->productCloseoutFilterFactory->create($context);
  117.             $filter->addQuery(new EqualsFilter('product.parentId'null));
  118.             $criteria->addFilter($filter);
  119.         }
  120.     }
  121.     private function createCriteria(string $pageIdRequest $request): Criteria
  122.     {
  123.         $criteria = new Criteria([$pageId]);
  124.         $criteria->setTitle('product::cms-page');
  125.         $slots $request->get('slots');
  126.         if (\is_string($slots)) {
  127.             $slots explode('|'$slots);
  128.         }
  129.         if (!empty($slots) && \is_array($slots)) {
  130.             $criteria
  131.                 ->getAssociation('sections.blocks')
  132.                 ->addFilter(new EqualsAnyFilter('slots.id'$slots));
  133.         }
  134.         return $criteria;
  135.     }
  136. }