<?php declare(strict_types=1);
namespace Samson\Controller;
/***
*
* This file is part of the "SAMSON Shop" project.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2020
*
***/
use GuzzleHttp\Exception\GuzzleException;
use Samson\Defaults;
use Samson\Exception\CustomerNeedsPasswordChangeException;
use Samson\Exception\OfferSimulationException;
use Samson\Service\IndustriesResolverService;
use Samson\Service\OfferSimulationService;
use Samson\Subscriber\ProductLoadedSerialNumberSubscriber;
use Samson\Pages\Cms\SalesChannel\SalesChannelCmsPageLoader;
use Sentry\State\Scope;
use Shopware\Core\Content\Cms\CmsPageEntity;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Product\SalesChannel\Review\AbstractProductReviewSaveRoute;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductDefinition;
use Shopware\Core\Content\Seo\SeoUrlPlaceholderHandlerInterface;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\ProductController as ShopwareProductController;
use Shopware\Storefront\Page\Product\Configurator\ProductCombinationFinder;
use Shopware\Storefront\Page\Product\ProductPage;
use Shopware\Storefront\Page\Product\ProductPageLoadedHook;
use Shopware\Storefront\Page\Product\ProductPageLoader;
use Shopware\Storefront\Page\Product\QuickView\MinimalQuickViewPageLoader;
use Shopware\Storefront\Page\Product\Review\ProductReviewLoader;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Symfony\Component\Routing\Annotation\Route;
use Shopware\Core\Framework\Routing\Annotation\Since;
use Shopware\Storefront\Framework\Cache\Annotation\HttpCache;
use Samson\Annotation\CustomerNeedsPasswordChange;
use Shopware\Core\Framework\Routing\Annotation\LoginRequired;
use function Sentry\captureException;
use function Sentry\withScope;
/**
* @RouteScope(scopes={"storefront"})
*/
class ProductController extends ShopwareProductController
{
protected const FLASH_NAME = 'product-configurator-flash-message';
private EntityRepositoryInterface $productRepository;
private ProductPageLoader $productPageLoader;
private ProductLoadedSerialNumberSubscriber $productLoadedSerialNumberSubscriber;
private OfferSimulationService $offerSimulationService;
private SystemConfigService $systemConfigService;
private EntityRepositoryInterface $mediaRepository;
private IndustriesResolverService $industries;
private SalesChannelCmsPageLoader $cmsPageLoader;
private SalesChannelProductDefinition $productDefinition;
public function __construct(
SeoUrlPlaceholderHandlerInterface $seoUrlPlaceholderHandler,
EntityRepositoryInterface $productRepository,
ProductPageLoader $productPageLoader,
ProductLoadedSerialNumberSubscriber $productLoadedSerialNumberSubscriber,
ProductCombinationFinder $combinationFinder,
MinimalQuickViewPageLoader $minimalQuickViewPageLoader,
AbstractProductReviewSaveRoute $productReviewSaveRoute,
ProductReviewLoader $productReviewLoader,
SystemConfigService $systemConfigService,
OfferSimulationService $offerSimulationService,
EntityRepositoryInterface $mediaRepository,
IndustriesResolverService $industries,
SalesChannelCmsPageLoader $cmsPageLoader,
SalesChannelProductDefinition $productDefinition
)
{
parent::__construct($productPageLoader,
$combinationFinder,
$minimalQuickViewPageLoader,
$productReviewSaveRoute,
$seoUrlPlaceholderHandler,
$productReviewLoader,
$systemConfigService);
$this->productRepository = $productRepository;
$this->productPageLoader = $productPageLoader;
$this->productLoadedSerialNumberSubscriber = $productLoadedSerialNumberSubscriber;
$this->offerSimulationService = $offerSimulationService;
$this->systemConfigService = $systemConfigService;
$this->mediaRepository = $mediaRepository;
$this->industries = $industries;
$this->cmsPageLoader = $cmsPageLoader;
$this->productDefinition = $productDefinition;
}
/**
* @Since("6.3.3.0")
* @HttpCache()
* @Route("/detail/{productId}/media/{mediaId}", name="frontend.detail.page.media", methods={"GET"})
* @Route("/detail/{productId}", name="frontend.detail.page", methods={"GET"})
* @throws GuzzleException
*/
public function index(SalesChannelContext $context, Request $request): Response
{
$page = $this->productPageLoader->load($request, $context);
$product = $page->getProduct();
$media = null;
if ($request->attributes->has('mediaId')) {
$mediaId = $request->attributes->get('mediaId');
$media = $this->mediaRepository->search(new Criteria([$mediaId]), $context->getContext())->first();
}
$ratingSuccess = $request->get('success');
if ($this->systemConfigService->get(Defaults::SAP_ACTIVATION_KEY, $context->getSalesChannelId())) {
try {
$this->offerSimulationService->setProductPriceFromSap($product, $context);
} catch (OfferSimulationException $exception) {
$this->addFlash(parent::DANGER, $exception->getMessage());
withScope(function (Scope $scope) use ($exception){
$scope->setContext("OfferSimulation", $exception->getContext());
captureException($exception);
});
$productDetailPage = $this->getProductDetailPageToRender($page, $context, $media, $ratingSuccess);
$parameters = $productDetailPage['parameters'];
$parameters['offerSimulationError'] = true;
return $this->renderStorefront($productDetailPage['url'], $parameters);
}
}
$this->hook(new ProductPageLoadedHook($page, $context));
$productDetailPage = $this->getProductDetailPageToRender($page, $context, $media, $ratingSuccess);
$parameters = $productDetailPage['parameters'];
return $this->renderStorefront($productDetailPage['url'], $parameters);
}
/**
* @Since("6.0.0.0")
* @LoginRequired()
* @CustomerNeedsPasswordChange()
* @Route("/product/{productId}/rating", name="frontend.detail.review.save", methods={"POST"}, defaults={"XmlHttpRequest"=true})
*
* @throws CustomerNeedsPasswordChangeException
*/
public function saveReview(string $productId, RequestDataBag $data, SalesChannelContext $context): Response
{
return parent::saveReview($productId, $data, $context);
}
/**
* @Route("/detail/info/{productNumber}", name="frontend.detail.info", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true})
* @Route("/detail/info/{productNumber}/{serialNumber}", name="frontend.detail.info.serial", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true})
* @param Request $request
* @param SalesChannelContext $context
* @return JsonResponse
*/
public function showProductInformation(Request $request, SalesChannelContext $context): Response
{
// todo: introduce custom page type, not using ProductPage (better caching etc)!
$serialNumber = $request->attributes->get('serialNumber');
$productNumber = $request->attributes->get('productNumber');
if ($serialNumber) {
$this->productLoadedSerialNumberSubscriber->setSerialNumber($serialNumber);
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productNumber', $productNumber));
$productId = $this->productRepository->searchIds($criteria, $context->getContext())->firstId();
$request->attributes->add(['productId' => $productId]);
$page = $this->productPageLoader->load($request, $context);
$pageId = $this->systemConfigService->get('SamsonCustomer.config.cmsTechnicalProductPage', $context->getSalesChannelId());
$resolverContext = new EntityResolverContext($context, $request, $this->productDefinition, clone $page->getProduct());
/** @var CmsPageEntity $technicalCmsPage */
$technicalCmsPage = $this->cmsPageLoader->load(
$request,
new Criteria([$pageId]),
$context,
$page->getProduct()->getTranslation('slotConfig'),
$resolverContext)->first();
$page->setCmsPage($technicalCmsPage);
return $this->renderStorefront('@Storefront/storefront/page/content/product-detail.html.twig', ['page' => $page]);
}
private function getProductDetailPageToRender(ProductPage $page, SalesChannelContext $context, $media, $ratingSuccess): array
{
// Fallback layout for non-assigned product layout
if (!$page->getCmsPage()) {
$industries = $this->industries->loadIndustries($page->getProduct(), $context);
if ($industries !== null) {
$page->getProduct()->addExtension('industries', $industries);
}
return [
'url' => '@Storefront/storefront/page/product-detail/index.html.twig',
'parameters' => [
'page' => $page,
'media' => $media,
'ratingSuccess' => $ratingSuccess
]
];
}
return [
'url' => '@Storefront/storefront/page/content/product-detail.html.twig',
'parameters' => [
'page' => $page,
'media' => $media
]
];
}
}