vendor/shopware/storefront/Controller/ProductController.php line 88

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Controller;
  3. use Shopware\Core\Content\Product\Exception\ProductNotFoundException;
  4. use Shopware\Core\Content\Product\Exception\ReviewNotActiveExeption;
  5. use Shopware\Core\Content\Product\Exception\VariantNotFoundException;
  6. use Shopware\Core\Content\Product\SalesChannel\FindVariant\AbstractFindProductVariantRoute;
  7. use Shopware\Core\Content\Product\SalesChannel\Review\AbstractProductReviewSaveRoute;
  8. use Shopware\Core\Content\Seo\SeoUrlPlaceholderHandlerInterface;
  9. use Shopware\Core\Framework\Feature;
  10. use Shopware\Core\Framework\Routing\Annotation\Since;
  11. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  12. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  13. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  14. use Shopware\Core\System\SystemConfig\SystemConfigService;
  15. use Shopware\Storefront\Framework\Cache\Annotation\HttpCache;
  16. use Shopware\Storefront\Framework\Routing\RequestTransformer;
  17. use Shopware\Storefront\Page\Product\Configurator\ProductCombinationFinder;
  18. use Shopware\Storefront\Page\Product\ProductPageLoadedHook;
  19. use Shopware\Storefront\Page\Product\ProductPageLoader;
  20. use Shopware\Storefront\Page\Product\QuickView\MinimalQuickViewPageLoader;
  21. use Shopware\Storefront\Page\Product\QuickView\ProductQuickViewWidgetLoadedHook;
  22. use Shopware\Storefront\Page\Product\Review\ProductReviewLoader;
  23. use Shopware\Storefront\Page\Product\Review\ProductReviewsWidgetLoadedHook;
  24. use Symfony\Component\HttpFoundation\JsonResponse;
  25. use Symfony\Component\HttpFoundation\Request;
  26. use Symfony\Component\HttpFoundation\Response;
  27. use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
  28. use Symfony\Component\Routing\Annotation\Route;
  29. /**
  30.  * @Route(defaults={"_routeScope"={"storefront"}})
  31.  *
  32.  * @deprecated tag:v6.5.0 - reason:becomes-internal - Will be internal
  33.  */
  34. class ProductController extends StorefrontController
  35. {
  36.     private ProductPageLoader $productPageLoader;
  37.     private AbstractFindProductVariantRoute $findVariantRoute;
  38.     private MinimalQuickViewPageLoader $minimalQuickViewPageLoader;
  39.     private SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler;
  40.     private ProductReviewLoader $productReviewLoader;
  41.     private SystemConfigService $systemConfigService;
  42.     private AbstractProductReviewSaveRoute $productReviewSaveRoute;
  43.     /**
  44.      * @deprecated tag:v6.5.0 - will be removed
  45.      */
  46.     private ProductCombinationFinder $productCombinationFinder;
  47.     /**
  48.      * @internal
  49.      */
  50.     public function __construct(
  51.         ProductPageLoader $productPageLoader,
  52.         ProductCombinationFinder $productCombinationFinder,
  53.         AbstractFindProductVariantRoute $findVariantRoute,
  54.         MinimalQuickViewPageLoader $minimalQuickViewPageLoader,
  55.         AbstractProductReviewSaveRoute $productReviewSaveRoute,
  56.         SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler,
  57.         ProductReviewLoader $productReviewLoader,
  58.         SystemConfigService $systemConfigService
  59.     ) {
  60.         $this->productPageLoader $productPageLoader;
  61.         $this->findVariantRoute $findVariantRoute;
  62.         $this->minimalQuickViewPageLoader $minimalQuickViewPageLoader;
  63.         $this->seoUrlPlaceholderHandler $seoUrlPlaceholderHandler;
  64.         $this->productReviewLoader $productReviewLoader;
  65.         $this->systemConfigService $systemConfigService;
  66.         $this->productReviewSaveRoute $productReviewSaveRoute;
  67.         $this->productCombinationFinder $productCombinationFinder;
  68.     }
  69.     /**
  70.      * @Since("6.3.3.0")
  71.      * @HttpCache()
  72.      * @Route("/detail/{productId}", name="frontend.detail.page", methods={"GET"})
  73.      */
  74.     public function index(SalesChannelContext $contextRequest $request): Response
  75.     {
  76.         $page $this->productPageLoader->load($request$context);
  77.         $this->hook(new ProductPageLoadedHook($page$context));
  78.         $ratingSuccess $request->get('success');
  79.         /**
  80.          * @deprecated tag:v6.5.0 - remove complete if statement, cms page id is always set
  81.          *
  82.          * Fallback layout for non-assigned product layout
  83.          */
  84.         if (!$page->getCmsPage()) {
  85.             Feature::throwException('v6.5.0.0''Fallback will be removed because cms page is always set in subscriber.');
  86.             return $this->renderStorefront('@Storefront/storefront/page/product-detail/index.html.twig', ['page' => $page'ratingSuccess' => $ratingSuccess]);
  87.         }
  88.         return $this->renderStorefront('@Storefront/storefront/page/content/product-detail.html.twig', ['page' => $page]);
  89.     }
  90.     /**
  91.      * @Since("6.0.0.0")
  92.      * @HttpCache()
  93.      * @Route("/detail/{productId}/switch", name="frontend.detail.switch", methods={"GET"}, defaults={"XmlHttpRequest": true})
  94.      */
  95.     public function switch(string $productIdRequest $requestSalesChannelContext $salesChannelContext): JsonResponse
  96.     {
  97.         $switchedGroup $request->query->has('switched') ? (string) $request->query->get('switched') : null;
  98.         /** @var array<mixed>|null $options */
  99.         $options json_decode($request->query->get('options'''), true);
  100.         try {
  101.             if (Feature::isActive('v6.5.0.0')) {
  102.                 $variantResponse $this->findVariantRoute->load(
  103.                     $productId,
  104.                     new Request(
  105.                         [
  106.                             'switchedGroup' => $switchedGroup,
  107.                             'options' => $options ?? [],
  108.                         ]
  109.                     ),
  110.                     $salesChannelContext
  111.                 );
  112.                 $productId $variantResponse->getFoundCombination()->getVariantId();
  113.             } else {
  114.                 $finderResponse $this->productCombinationFinder->find(
  115.                     $productId,
  116.                     $switchedGroup,
  117.                     $options ?? [],
  118.                     $salesChannelContext
  119.                 );
  120.                 $productId $finderResponse->getVariantId();
  121.             }
  122.         } catch (VariantNotFoundException|ProductNotFoundException $productNotFoundException) {
  123.             //nth
  124.         }
  125.         $host $request->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL)
  126.             . $request->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
  127.         $url $this->seoUrlPlaceholderHandler->replace(
  128.             $this->seoUrlPlaceholderHandler->generate(
  129.                 'frontend.detail.page',
  130.                 ['productId' => $productId]
  131.             ),
  132.             $host,
  133.             $salesChannelContext
  134.         );
  135.         $response = new JsonResponse([
  136.             'url' => $url,
  137.             'productId' => $productId,
  138.         ]);
  139.         $response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER'1');
  140.         return $response;
  141.     }
  142.     /**
  143.      * @Since("6.0.0.0")
  144.      * @Route("/quickview/{productId}", name="widgets.quickview.minimal", methods={"GET"}, defaults={"XmlHttpRequest": true})
  145.      */
  146.     public function quickviewMinimal(Request $requestSalesChannelContext $context): Response
  147.     {
  148.         $page $this->minimalQuickViewPageLoader->load($request$context);
  149.         $this->hook(new ProductQuickViewWidgetLoadedHook($page$context));
  150.         return $this->renderStorefront('@Storefront/storefront/component/product/quickview/minimal.html.twig', ['page' => $page]);
  151.     }
  152.     /**
  153.      * @Since("6.0.0.0")
  154.      * @Route("/product/{productId}/rating", name="frontend.detail.review.save", methods={"POST"}, defaults={"XmlHttpRequest"=true, "_loginRequired"=true})
  155.      */
  156.     public function saveReview(string $productIdRequestDataBag $dataSalesChannelContext $context): Response
  157.     {
  158.         $this->checkReviewsActive($context);
  159.         try {
  160.             $this->productReviewSaveRoute->save($productId$data$context);
  161.         } catch (ConstraintViolationException $formViolations) {
  162.             return $this->forwardToRoute('frontend.product.reviews', [
  163.                 'productId' => $productId,
  164.                 'success' => -1,
  165.                 'formViolations' => $formViolations,
  166.                 'data' => $data,
  167.             ], ['productId' => $productId]);
  168.         }
  169.         $forwardParams = [
  170.             'productId' => $productId,
  171.             'success' => 1,
  172.             'data' => $data,
  173.             'parentId' => $data->get('parentId'),
  174.         ];
  175.         if ($data->has('id')) {
  176.             $forwardParams['success'] = 2;
  177.         }
  178.         return $this->forwardToRoute('frontend.product.reviews'$forwardParams, ['productId' => $productId]);
  179.     }
  180.     /**
  181.      * @Since("6.0.0.0")
  182.      * @Route("/product/{productId}/reviews", name="frontend.product.reviews", methods={"GET","POST"}, defaults={"XmlHttpRequest"=true})
  183.      */
  184.     public function loadReviews(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  185.     {
  186.         $this->checkReviewsActive($context);
  187.         $reviews $this->productReviewLoader->load($request$context);
  188.         $this->hook(new ProductReviewsWidgetLoadedHook($reviews$context));
  189.         return $this->renderStorefront('storefront/page/product-detail/review/review.html.twig', [
  190.             'reviews' => $reviews,
  191.             'ratingSuccess' => $request->get('success'),
  192.         ]);
  193.     }
  194.     /**
  195.      * @throws ReviewNotActiveExeption
  196.      */
  197.     private function checkReviewsActive(SalesChannelContext $context): void
  198.     {
  199.         $showReview $this->systemConfigService->get('core.listing.showReview'$context->getSalesChannel()->getId());
  200.         if (!$showReview) {
  201.             throw new ReviewNotActiveExeption();
  202.         }
  203.     }
  204. }