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

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