custom/plugins/GrimmTheme/src/Core/Storefront/Controller/ExtendedCheckoutController.php line 166

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace GrimmTheme\Core\Storefront\Controller;
  4. use Shopware\Storefront\Controller\CheckoutController;
  5. use Shopware\Core\Checkout\Cart\Cart;
  6. use Shopware\Core\Checkout\Cart\Error\Error;
  7. use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
  8. use Shopware\Core\Checkout\Cart\Exception\InvalidCartException;
  9. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  10. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
  11. use Shopware\Core\Checkout\Order\Exception\EmptyCartException;
  12. use Shopware\Core\Checkout\Order\SalesChannel\OrderService;
  13. use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException;
  14. use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException;
  15. use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException;
  16. use Shopware\Core\Checkout\Payment\PaymentService;
  17. use Shopware\Core\Framework\Feature;
  18. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  19. use Shopware\Core\Framework\Routing\Annotation\Since;
  20. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  21. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  22. use Shopware\Core\Profiling\Profiler;
  23. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  24. use Shopware\Core\System\SystemConfig\SystemConfigService;
  25. use Shopware\Storefront\Checkout\Cart\Error\PaymentMethodChangedError;
  26. use Shopware\Storefront\Checkout\Cart\Error\ShippingMethodChangedError;
  27. use Shopware\Storefront\Framework\AffiliateTracking\AffiliateTrackingListener;
  28. use Shopware\Storefront\Framework\Routing\Annotation\NoStore;
  29. use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedHook;
  30. use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoader;
  31. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedHook;
  32. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoader;
  33. use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedHook;
  34. use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoader;
  35. use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutInfoWidgetLoadedHook;
  36. use Shopware\Storefront\Page\Checkout\Offcanvas\CheckoutOffcanvasWidgetLoadedHook;
  37. use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPageLoader;
  38. use Symfony\Component\HttpFoundation\RedirectResponse;
  39. use Symfony\Component\HttpFoundation\Request;
  40. use Symfony\Component\HttpFoundation\Response;
  41. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  42. use Symfony\Component\Routing\Annotation\Route;
  43. use Shopware\Storefront\Page\Address\Listing\AddressListingPageLoader;
  44. use Shopware\Storefront\Page\Address\AddressEditorModalStruct;
  45. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  46. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  47. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  48. use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepositoryInterface;
  49. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  50. use Shopware\Core\Content\Product\Aggregate\ProductCrossSelling\ProductCrossSellingEntity;
  51. /**
  52.  * @RouteScope(scopes={"storefront"})
  53.  */
  54. class ExtendedCheckoutController extends CheckoutController {
  55.     private const REDIRECTED_FROM_SAME_ROUTE 'redirected';
  56.     private CartService $cartService;
  57.     private CheckoutCartPageLoader $cartPageLoader;
  58.     private CheckoutConfirmPageLoader $confirmPageLoader;
  59.     private CheckoutFinishPageLoader $finishPageLoader;
  60.     private OrderService $orderService;
  61.     private PaymentService $paymentService;
  62.     private OffcanvasCartPageLoader $offcanvasCartPageLoader;
  63.     private SystemConfigService $config;
  64.     private AbstractLogoutRoute $logoutRoute;
  65.     private AddressListingPageLoader $addressListingPageLoader;
  66.     /**
  67.      * @var SalesChannelRepositoryInterface
  68.      */
  69.     protected $productRepository;
  70.     public function __construct(
  71.         CartService $cartService,
  72.         CheckoutCartPageLoader $cartPageLoader,
  73.         CheckoutConfirmPageLoader $confirmPageLoader,
  74.         CheckoutFinishPageLoader $finishPageLoader,
  75.         OrderService $orderService,
  76.         PaymentService $paymentService,
  77.         OffcanvasCartPageLoader $offcanvasCartPageLoader,
  78.         SystemConfigService $config,
  79.         AbstractLogoutRoute $logoutRoute,
  80.         AddressListingPageLoader $addressListingPageLoader,
  81.         SalesChannelRepositoryInterface $productRepository
  82.     ) {
  83.         $this->cartService $cartService;
  84.         $this->cartPageLoader $cartPageLoader;
  85.         $this->confirmPageLoader $confirmPageLoader;
  86.         $this->finishPageLoader $finishPageLoader;
  87.         $this->orderService $orderService;
  88.         $this->paymentService $paymentService;
  89.         $this->offcanvasCartPageLoader $offcanvasCartPageLoader;
  90.         $this->config $config;
  91.         $this->logoutRoute $logoutRoute;
  92.         $this->addressListingPageLoader $addressListingPageLoader;
  93.         $this->productRepository $productRepository;
  94.         parent::__construct($cartService$cartPageLoader$confirmPageLoader
  95.             $finishPageLoader$orderService$paymentService$offcanvasCartPageLoader,
  96.             $config$logoutRoute);
  97.     }
  98.     /**
  99.      * @Since("6.0.0.0")
  100.      * @NoStore
  101.      * @Route("/checkout/cart", name="frontend.checkout.cart.page", options={"seo"="false"}, methods={"GET"})
  102.      */
  103.     public function cartPage(Request $requestSalesChannelContext $context): Response
  104.     {
  105.         $page $this->cartPageLoader->load($request$context);
  106.         $cart $page->getCart();
  107.         $cartErrors $cart->getErrors();
  108.         $this->hook(new CheckoutCartPageLoadedHook($page$context));
  109.         $this->addCartErrors($cart);
  110.         if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
  111.             $cartErrors->clear();
  112.             // To prevent redirect loops add the identifier that the request already got redirected from the same origin
  113.             return $this->redirectToRoute(
  114.                 'frontend.checkout.cart.page',
  115.                 \array_merge(
  116.                     $request->query->all(),
  117.                     [self::REDIRECTED_FROM_SAME_ROUTE => true]
  118.                 ),
  119.             );
  120.         }
  121.         $cartErrors->clear();
  122.         foreach ($cart->getLineItems() as $lineItem) {
  123.             $productId $lineItem->getId();
  124.             $productEntity $this->getProductById($productId$context)->first();
  125.             if($productEntity == null) continue;
  126.             if(count($productEntity->getCrossSellings()) === 0) continue;
  127.             $crossSellings $productEntity->getCrossSellings();
  128.             $crossSellings->sort(function (ProductCrossSellingEntity $crossSellingEntityAProductCrossSellingEntity $crossSellingEntityB) {
  129.                 return $crossSellingEntityA->getPosition() <=> $crossSellingEntityB->getPosition();
  130.             });
  131.             if(count($crossSellings->first()->getAssignedProducts()) === 0) continue;
  132.             $assignedProductIds = [];
  133.             foreach($crossSellings->first()->getAssignedProducts() as $assignedProduct) {
  134.                 $assignedProductIds[] = $assignedProduct->getProductId();
  135.             }
  136.             $resolvedProducts $this->resolveAssignedProducts($assignedProductIds$context);
  137.             $lineItem->resolvedProducts $resolvedProducts;
  138.         }
  139.         return $this->renderStorefront('@Storefront/storefront/page/checkout/cart/index.html.twig', ['page' => $page]);
  140.     }
  141.     /**
  142.      * @Since("6.0.0.0")
  143.      * @Route("/checkout/offcanvas", name="frontend.cart.offcanvas", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true})
  144.      */
  145.     public function offcanvas(Request $requestSalesChannelContext $context): Response
  146.     {
  147.         $page $this->offcanvasCartPageLoader->load($request$context);
  148.         $this->hook(new CheckoutOffcanvasWidgetLoadedHook($page$context));
  149.         $cart $page->getCart();
  150.         $this->addCartErrors($cart);
  151.         $cartErrors $cart->getErrors();
  152.         if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
  153.             $cartErrors->clear();
  154.             // To prevent redirect loops add the identifier that the request already got redirected from the same origin
  155.             return $this->redirectToRoute(
  156.                 'frontend.cart.offcanvas',
  157.                 \array_merge(
  158.                     $request->query->all(),
  159.                     [self::REDIRECTED_FROM_SAME_ROUTE => true]
  160.                 ),
  161.             );
  162.         }
  163.         $cartErrors->clear();
  164.         // Add Crosssellings for newest Product
  165.         $resolvedProducts = [];
  166.         foreach ($cart->getLineItems() as $lineItem) {
  167.             $productId $lineItem->getId();
  168.             $productEntity $this->getProductById($productId$context)->first();
  169.             if($productEntity == null) continue;
  170.             if(count($productEntity->getCrossSellings()) === 0) continue;
  171.             $crossSellings $productEntity->getCrossSellings();
  172.             $crossSellings->sort(function (ProductCrossSellingEntity $crossSellingEntityAProductCrossSellingEntity $crossSellingEntityB) {
  173.                 return $crossSellingEntityA->getPosition() <=> $crossSellingEntityB->getPosition();
  174.             });
  175.             if(count($crossSellings->first()->getAssignedProducts()) === 0) continue;
  176.             $assignedProductIds = [];
  177.             foreach($crossSellings->first()->getAssignedProducts() as $assignedProduct) {
  178.                 $assignedProductIds[] = $assignedProduct->getProductId();
  179.             }
  180.             $assignedProducts $this->resolveAssignedProducts($assignedProductIds$context);
  181.             array_push($resolvedProducts$assignedProducts);
  182.         }
  183.         $page->resolvedProducts $resolvedProducts;
  184.         return $this->renderStorefront('@Storefront/storefront/component/checkout/offcanvas-cart.html.twig', ['page' => $page]);
  185.     }
  186.     
  187.     /**
  188.      * @Route("/checkout/confirm", name="frontend.checkout.confirm.page", options={"seo"="false"}, methods={"GET"}, defaults={"XmlHttpRequest"=true})
  189.      */
  190.     public function confirmPage(Request $requestSalesChannelContext $context): Response
  191.     {
  192.         if (!$context->getCustomer()) {
  193.             return $this->redirectToRoute('frontend.checkout.register.page');
  194.         }
  195.         if ($this->cartService->getCart($context->getToken(), $context)->getLineItems()->count() === 0) {
  196.             return $this->redirectToRoute('frontend.checkout.cart.page');
  197.         }
  198.         $page $this->confirmPageLoader->load($request$context);
  199.         $cart $page->getCart();
  200.         $cartErrors $cart->getErrors();
  201.         $this->hook(new CheckoutConfirmPageLoadedHook($page$context));
  202.         $this->addCartErrors($cart);
  203.         if (!$request->query->getBoolean(self::REDIRECTED_FROM_SAME_ROUTE) && $this->routeNeedsReload($cartErrors)) {
  204.             $cartErrors->clear();
  205.             // To prevent redirect loops add the identifier that the request already got redirected from the same origin
  206.             return $this->redirectToRoute(
  207.                 'frontend.checkout.confirm.page',
  208.                 \array_merge(
  209.                     $request->query->all(),
  210.                     [self::REDIRECTED_FROM_SAME_ROUTE => true]
  211.                 ),
  212.             );
  213.         }
  214.         $cartErrors->clear();
  215.         $page->addresses $this->addressListingPageLoader->load($request$context$context->getCustomer())->getAddresses();
  216.         $totalShippingCosts 0;
  217.         $deliveries $cart->getDeliveries();
  218.         foreach ($deliveries as $delivery) {
  219.             $totalShippingCosts += $delivery->getShippingCosts()->getTotalPrice();
  220.         }
  221.         $page->freeShipping $totalShippingCosts == 0;
  222.         return $this->renderStorefront('@Storefront/storefront/page/checkout/confirm/index.html.twig', ['page' => $page]);
  223.     }
  224.     private function routeNeedsReload(ErrorCollection $cartErrors): bool
  225.     {
  226.         foreach ($cartErrors as $error) {
  227.             if ($error instanceof ShippingMethodChangedError || $error instanceof PaymentMethodChangedError) {
  228.                 return true;
  229.             }
  230.         }
  231.         return false;
  232.     }
  233.     private function getProductById(String $productIdSalesChannelContext $context):EntitySearchResult
  234.     {
  235.         $criteria = (new Criteria())
  236.             ->addFilter(new EqualsFilter('id'$productId))
  237.             ->addAssociation('crossSellings')
  238.             ->addAssociation('crossSellings.assignedProducts');
  239.         // EntitySearchResult
  240.         return $this->productRepository->search($criteria$context);
  241.     }
  242.     private function resolveAssignedProducts(Array $productIdsSalesChannelContext $context):EntitySearchResult
  243.     {
  244.         $criteria = (new Criteria())
  245.             ->addFilter(new EqualsAnyFilter('id'$productIds))
  246.             ->addAssociation('deliveryTime')
  247.             ->addAssociation('cover');
  248.         // EntitySearchResult
  249.         return $this->productRepository->search($criteria$context);
  250.     }
  251. }