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

  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\Log\Package;
  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\Routing\RequestTransformer;
  16. use Shopware\Storefront\Page\Product\ProductPageLoadedHook;
  17. use Shopware\Storefront\Page\Product\ProductPageLoader;
  18. use Shopware\Storefront\Page\Product\QuickView\MinimalQuickViewPageLoader;
  19. use Shopware\Storefront\Page\Product\QuickView\ProductQuickViewWidgetLoadedHook;
  20. use Shopware\Storefront\Page\Product\Review\ProductReviewLoader;
  21. use Shopware\Storefront\Page\Product\Review\ProductReviewsWidgetLoadedHook;
  22. use Symfony\Component\HttpFoundation\JsonResponse;
  23. use Symfony\Component\HttpFoundation\Request;
  24. use Symfony\Component\HttpFoundation\Response;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. /**
  27.  * @internal
  28.  * Do not use direct or indirect repository calls in a controller. Always use a store-api route to get or put data
  29.  */
  30. #[Route(defaults: ['_routeScope' => ['storefront']])]
  31. #[Package('storefront')]
  32. class ProductController extends StorefrontController
  33. {
  34.     /**
  35.      * @internal
  36.      */
  37.     public function __construct(
  38.         private readonly ProductPageLoader $productPageLoader,
  39.         private readonly AbstractFindProductVariantRoute $findVariantRoute,
  40.         private readonly MinimalQuickViewPageLoader $minimalQuickViewPageLoader,
  41.         private readonly AbstractProductReviewSaveRoute $productReviewSaveRoute,
  42.         private readonly SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler,
  43.         private readonly ProductReviewLoader $productReviewLoader,
  44.         private readonly SystemConfigService $systemConfigService
  45.     ) {
  46.     }
  47.     #[Route(path'/detail/{productId}'name'frontend.detail.page'defaults: ['_httpCache' => true], methods: ['GET'])]
  48.     public function index(SalesChannelContext $contextRequest $request): Response
  49.     {
  50.         $page $this->productPageLoader->load($request$context);
  51.         $this->hook(new ProductPageLoadedHook($page$context));
  52.         $ratingSuccess $request->get('success');
  53.         /**
  54.          * @deprecated tag:v6.6.0 - remove complete if statement, cms page id is always set
  55.          *
  56.          * Fallback layout for non-assigned product layout
  57.          */
  58.         if (!$page->getCmsPage()) {
  59.             Feature::throwException('v6.6.0.0''Fallback will be removed because cms page is always set in subscriber.');
  60.             return $this->renderStorefront('@Storefront/storefront/page/product-detail/index.html.twig', ['page' => $page'ratingSuccess' => $ratingSuccess]);
  61.         }
  62.         return $this->renderStorefront('@Storefront/storefront/page/content/product-detail.html.twig', ['page' => $page]);
  63.     }
  64.     #[Route(path'/detail/{productId}/switch'name'frontend.detail.switch'defaults: ['XmlHttpRequest' => true'_httpCache' => true], methods: ['GET'])]
  65.     public function switch(string $productIdRequest $requestSalesChannelContext $salesChannelContext): JsonResponse
  66.     {
  67.         $switchedGroup $request->query->has('switched') ? (string) $request->query->get('switched') : null;
  68.         /** @var array<mixed>|null $options */
  69.         $options json_decode($request->query->get('options'''), true);
  70.         try {
  71.             $variantResponse $this->findVariantRoute->load(
  72.                 $productId,
  73.                 new Request(
  74.                     [
  75.                         'switchedGroup' => $switchedGroup,
  76.                         'options' => $options ?? [],
  77.                     ]
  78.                 ),
  79.                 $salesChannelContext
  80.             );
  81.             $productId $variantResponse->getFoundCombination()->getVariantId();
  82.         } catch (VariantNotFoundException|ProductNotFoundException) {
  83.             //nth
  84.         }
  85.         $host $request->attributes->get(RequestTransformer::SALES_CHANNEL_ABSOLUTE_BASE_URL)
  86.             . $request->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
  87.         $url $this->seoUrlPlaceholderHandler->replace(
  88.             $this->seoUrlPlaceholderHandler->generate(
  89.                 'frontend.detail.page',
  90.                 ['productId' => $productId]
  91.             ),
  92.             $host,
  93.             $salesChannelContext
  94.         );
  95.         return new JsonResponse([
  96.             'url' => $url,
  97.             'productId' => $productId,
  98.         ]);
  99.     }
  100.     #[Route(path'/quickview/{productId}'name'widgets.quickview.minimal'defaults: ['XmlHttpRequest' => true], methods: ['GET'])]
  101.     public function quickviewMinimal(Request $requestSalesChannelContext $context): Response
  102.     {
  103.         $page $this->minimalQuickViewPageLoader->load($request$context);
  104.         $this->hook(new ProductQuickViewWidgetLoadedHook($page$context));
  105.         return $this->renderStorefront('@Storefront/storefront/component/product/quickview/minimal.html.twig', ['page' => $page]);
  106.     }
  107.     #[Route(path'/product/{productId}/rating'name'frontend.detail.review.save'defaults: ['XmlHttpRequest' => true'_loginRequired' => true], methods: ['POST'])]
  108.     public function saveReview(string $productIdRequestDataBag $dataSalesChannelContext $context): Response
  109.     {
  110.         $this->checkReviewsActive($context);
  111.         try {
  112.             $this->productReviewSaveRoute->save($productId$data$context);
  113.         } catch (ConstraintViolationException $formViolations) {
  114.             return $this->forwardToRoute('frontend.product.reviews', [
  115.                 'productId' => $productId,
  116.                 'success' => -1,
  117.                 'formViolations' => $formViolations,
  118.                 'data' => $data,
  119.             ], ['productId' => $productId]);
  120.         }
  121.         $forwardParams = [
  122.             'productId' => $productId,
  123.             'success' => 1,
  124.             'data' => $data,
  125.             'parentId' => $data->get('parentId'),
  126.         ];
  127.         if ($data->has('id')) {
  128.             $forwardParams['success'] = 2;
  129.         }
  130.         return $this->forwardToRoute('frontend.product.reviews'$forwardParams, ['productId' => $productId]);
  131.     }
  132.     #[Route(path'/product/{productId}/reviews'name'frontend.product.reviews'defaults: ['XmlHttpRequest' => true], methods: ['GET''POST'])]
  133.     public function loadReviews(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  134.     {
  135.         $this->checkReviewsActive($context);
  136.         $reviews $this->productReviewLoader->load($request$context);
  137.         $this->hook(new ProductReviewsWidgetLoadedHook($reviews$context));
  138.         return $this->renderStorefront('storefront/page/product-detail/review/review.html.twig', [
  139.             'reviews' => $reviews,
  140.             'ratingSuccess' => $request->get('success'),
  141.         ]);
  142.     }
  143.     /**
  144.      * @throws ReviewNotActiveExeption
  145.      */
  146.     private function checkReviewsActive(SalesChannelContext $context): void
  147.     {
  148.         $showReview $this->systemConfigService->get('core.listing.showReview'$context->getSalesChannel()->getId());
  149.         if (!$showReview) {
  150.             throw new ReviewNotActiveExeption();
  151.         }
  152.     }
  153. }