custom/static-plugins/NdFashion/src/Overwrite/ProductDetailRoute.php line 112

Open in your IDE?
  1. <?php
  2. namespace Nd\Fashion\Overwrite;
  3. use Shopware\Core\Content\Category\Service\CategoryBreadcrumbBuilder;
  4. use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
  5. use Shopware\Core\Content\Cms\SalesChannel\SalesChannelCmsPageLoaderInterface;
  6. use Shopware\Core\Content\Product\Aggregate\ProductVisibility\ProductVisibilityDefinition;
  7. use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
  8. use Shopware\Core\Content\Product\ProductDefinition;
  9. use Shopware\Core\Content\Product\SalesChannel\Detail\AbstractProductDetailRoute;
  10. use Shopware\Core\Content\Product\SalesChannel\Detail\ProductConfiguratorLoader;
  11. use Shopware\Core\Content\Product\SalesChannel\Detail\ProductDetailRouteResponse;
  12. use Shopware\Core\Content\Product\SalesChannel\ProductAvailableFilter;
  13. use Shopware\Core\Content\Product\SalesChannel\ProductCloseoutFilter;
  14. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductDefinition;
  15. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  20. use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
  21. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  22. use Shopware\Core\Profiling\Profiler;
  23. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
  24. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  25. use Shopware\Core\System\SystemConfig\SystemConfigService;
  26. use Symfony\Component\HttpFoundation\Request;
  27. class ProductDetailRoute extends \Shopware\Core\Content\Product\SalesChannel\Detail\ProductDetailRoute
  28. {
  29.     /**
  30.      * @var SalesChannelRepositoryInterface
  31.      */
  32.     private $productRepository;
  33.     /**
  34.      * @var SystemConfigService
  35.      */
  36.     private $config;
  37.     /**
  38.      * @var ProductConfiguratorLoader
  39.      */
  40.     private $configuratorLoader;
  41.     /**
  42.      * @var CategoryBreadcrumbBuilder
  43.      */
  44.     private $breadcrumbBuilder;
  45.     /**
  46.      * @var SalesChannelCmsPageLoaderInterface
  47.      */
  48.     private $cmsPageLoader;
  49.     /**
  50.      * @var ProductDefinition
  51.      */
  52.     private $productDefinition;
  53.     /**
  54.      * @internal
  55.      */
  56.     public function __construct(
  57.         SalesChannelRepositoryInterface $productRepository,
  58.         SystemConfigService $config,
  59.         ProductConfiguratorLoader $configuratorLoader,
  60.         CategoryBreadcrumbBuilder $breadcrumbBuilder,
  61.         SalesChannelCmsPageLoaderInterface $cmsPageLoader,
  62.         SalesChannelProductDefinition $productDefinition
  63.     ) {
  64.         $this->productRepository $productRepository;
  65.         $this->config $config;
  66.         $this->configuratorLoader $configuratorLoader;
  67.         $this->breadcrumbBuilder $breadcrumbBuilder;
  68.         $this->cmsPageLoader $cmsPageLoader;
  69.         $this->productDefinition $productDefinition;
  70.     }
  71.     public function getDecorated(): AbstractProductDetailRoute
  72.     {
  73.         throw new DecorationPatternException(self::class);
  74.     }
  75.     /**
  76.      * @Since("6.3.2.0")
  77.      * @Entity("product")
  78.      * @OA\Post(
  79.      *      path="/product/{productId}",
  80.      *      summary="Fetch a single product",
  81.      *      description="This route is used to load a single product with the corresponding details. In addition to loading the data, the best variant of the product is determined when a parent id is passed.",
  82.      *      operationId="readProductDetail",
  83.      *      tags={"Store API","Product"},
  84.      *      @OA\Parameter(
  85.      *          name="productId",
  86.      *          description="Product ID",
  87.      *          @OA\Schema(type="string"),
  88.      *          in="path",
  89.      *          required=true
  90.      *      ),
  91.      *      @OA\Response(
  92.      *          response="200",
  93.      *          description="Product information along with variant groups and options",
  94.      *          @OA\JsonContent(ref="#/components/schemas/ProductDetailResponse")
  95.      *     )
  96.      * )
  97.      * @Route("/store-api/product/{productId}", name="store-api.product.detail", methods={"POST"})
  98.      */
  99.     public function load(string $productIdRequest $requestSalesChannelContext $contextCriteria $criteria): ProductDetailRouteResponse
  100.     {
  101.         return Profiler::trace('product-detail-route', function () use ($productId$request$context$criteria) {
  102.             $productId $this->findBestVariant($productId$context);
  103.             $this->addFilters($context$criteria);
  104.             $criteria->setIds([$productId]);
  105.             $criteria->setTitle('product-detail-route');
  106.             $product $this->productRepository
  107.                 ->search($criteria$context)
  108.                 ->first();
  109.             if (!$product instanceof SalesChannelProductEntity) {
  110.                 throw new ProductNotFoundException($productId);
  111.             }
  112.             $product->setSeoCategory(
  113.                 $this->breadcrumbBuilder->getProductSeoCategory($product$context)
  114.             );
  115.             $configurator $this->configuratorLoader->load($product$context);
  116.             $pageId $product->getCmsPageId();
  117.             if ($pageId) {
  118.                 // clone product to prevent recursion encoding (see NEXT-17603)
  119.                 $resolverContext = new EntityResolverContext($context$request$this->productDefinition, clone $product);
  120.                 $pages $this->cmsPageLoader->load(
  121.                     $request,
  122.                     $this->createCriteria($pageId$request),
  123.                     $context,
  124.                     $product->getTranslation('slotConfig'),
  125.                     $resolverContext
  126.                 );
  127.                 if ($page $pages->first()) {
  128.                     $product->setCmsPage($page);
  129.                 }
  130.             }
  131.             return new ProductDetailRouteResponse($product$configurator);
  132.         });
  133.     }
  134.     private function addFilters(SalesChannelContext $contextCriteria $criteria): void
  135.     {
  136.         $criteria->addFilter(
  137.             new ProductAvailableFilter($context->getSalesChannel()->getId(), ProductVisibilityDefinition::VISIBILITY_LINK)
  138.         );
  139.         $salesChannelId $context->getSalesChannel()->getId();
  140.         $hideCloseoutProductsWhenOutOfStock $this->config->get('core.listing.hideCloseoutProductsWhenOutOfStock'$salesChannelId);
  141.         if ($hideCloseoutProductsWhenOutOfStock) {
  142.             $filter = new ProductCloseoutFilter();
  143.             $filter->addQuery(new EqualsFilter('product.parentId'null));
  144.             $criteria->addFilter($filter);
  145.         }
  146.     }
  147.     /**
  148.      * @throws InconsistentCriteriaIdsException
  149.      */
  150.     private function findBestVariant(string $productIdSalesChannelContext $context): string
  151.     {
  152.         return $productId;
  153.     }
  154.     /**
  155.      * @throws InconsistentCriteriaIdsException
  156.      */
  157.     private function findCheapestVariant(string $productIdSalesChannelContext $context): string
  158.     {
  159.         $criteria = (new Criteria())
  160.             ->addFilter(new EqualsFilter('product.parentId'$productId))
  161.             ->addSorting(new FieldSorting('product.price'))
  162.             ->setLimit(1);
  163.         $criteria->setTitle('product-detail-route::find-cheapest-variant');
  164.         $variantId $this->productRepository->searchIds($criteria$context);
  165.         return $variantId->firstId() ?? $productId;
  166.     }
  167.     private function createCriteria(string $pageIdRequest $request): Criteria
  168.     {
  169.         $criteria = new Criteria([$pageId]);
  170.         $criteria->setTitle('product::cms-page');
  171.         $slots $request->get('slots');
  172.         if (\is_string($slots)) {
  173.             $slots explode('|'$slots);
  174.         }
  175.         if (!empty($slots) && \is_array($slots)) {
  176.             $criteria
  177.                 ->getAssociation('sections.blocks')
  178.                 ->addFilter(new EqualsAnyFilter('slots.id'$slots));
  179.         }
  180.         return $criteria;
  181.     }
  182. }