src/Core/Checkout/Promotion/Subscriber/Storefront/StorefrontCartSubscriber.php line 65

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Checkout\Promotion\Subscriber\Storefront;
  3. use Shopware\Core\Checkout\Cart\Cart;
  4. use Shopware\Core\Checkout\Cart\CartException;
  5. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
  6. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemRemovedEvent;
  7. use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
  8. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  9. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  10. use Shopware\Core\Checkout\Promotion\Aggregate\PromotionDiscount\PromotionDiscountEntity;
  11. use Shopware\Core\Checkout\Promotion\Cart\Extension\CartExtension;
  12. use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor;
  13. use Shopware\Core\Framework\Log\Package;
  14. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  15. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  16. use Symfony\Component\HttpFoundation\RequestStack;
  17. /**
  18.  * @internal
  19.  */
  20. #[Package('checkout')]
  21. class StorefrontCartSubscriber implements EventSubscriberInterface
  22. {
  23.     final public const SESSION_KEY_PROMOTION_CODES 'cart-promotion-codes';
  24.     /**
  25.      * @internal
  26.      */
  27.     public function __construct(private readonly CartService $cartService, private readonly RequestStack $requestStack)
  28.     {
  29.     }
  30.     public static function getSubscribedEvents(): array
  31.     {
  32.         return [
  33.             BeforeLineItemAddedEvent::class => 'onLineItemAdded',
  34.             BeforeLineItemRemovedEvent::class => 'onLineItemRemoved',
  35.             CheckoutOrderPlacedEvent::class => 'resetCodes',
  36.         ];
  37.     }
  38.     public function resetCodes(): void
  39.     {
  40.         $mainRequest $this->requestStack->getMainRequest();
  41.         if ($mainRequest === null) {
  42.             return;
  43.         }
  44.         if (!$mainRequest->hasSession()) {
  45.             return;
  46.         }
  47.         $mainRequest->getSession()->set(self::SESSION_KEY_PROMOTION_CODES, []);
  48.     }
  49.     /**
  50.      * This function is called whenever a new line item has been
  51.      * added to the cart from within the controllers.
  52.      * We verify if we have a placeholder line item for a promotion
  53.      * and add that code to our extension list.
  54.      */
  55.     public function onLineItemAdded(BeforeLineItemAddedEvent $event): void
  56.     {
  57.         if ($event->getLineItem()->getType() === PromotionProcessor::LINE_ITEM_TYPE) {
  58.             $code $event->getLineItem()->getReferencedId();
  59.             if ($code !== null && $code !== '') {
  60.                 $this->addCode($code$event->getCart());
  61.             }
  62.         }
  63.     }
  64.     /**
  65.      * This function is called whenever a line item is being removed
  66.      * from the cart from within a controller.
  67.      * We verify if it is a promotion item, and also remove that
  68.      * code from our extension, if existing.
  69.      */
  70.     public function onLineItemRemoved(BeforeLineItemRemovedEvent $event): void
  71.     {
  72.         $cart $event->getCart();
  73.         if ($event->getLineItem()->getType() !== PromotionProcessor::LINE_ITEM_TYPE) {
  74.             return;
  75.         }
  76.         $lineItem $event->getLineItem();
  77.         $code $lineItem->getReferencedId();
  78.         if (!empty($code)) {
  79.             // promotion with code
  80.             $this->checkFixedDiscountItems($cart$lineItem);
  81.             //remove other discounts of the promotion that should be deleted
  82.             $this->removeOtherDiscountsOfPromotion($cart$lineItem$event->getSalesChannelContext());
  83.             $this->removeCode($code$cart);
  84.             return;
  85.         }
  86.         // the user wants to remove an automatic added
  87.         // promotions, so lets do this
  88.         if ($lineItem->hasPayloadValue('promotionId')) {
  89.             $promotionId = (string) $lineItem->getPayloadValue('promotionId');
  90.             $this->blockPromotion($promotionId$cart);
  91.         }
  92.     }
  93.     /**
  94.      * @throws CartException
  95.      */
  96.     private function checkFixedDiscountItems(Cart $cartLineItem $lineItem): void
  97.     {
  98.         $lineItems $cart->getLineItems()->filterType(PromotionProcessor::LINE_ITEM_TYPE);
  99.         if ($lineItems->count() < 1) {
  100.             return;
  101.         }
  102.         if (!$lineItem->hasPayloadValue('discountType')) {
  103.             return;
  104.         }
  105.         if ($lineItem->getPayloadValue('discountType') !== PromotionDiscountEntity::TYPE_FIXED_UNIT) {
  106.             return;
  107.         }
  108.         if (!$lineItem->hasPayloadValue('discountId')) {
  109.             return;
  110.         }
  111.         $discountId $lineItem->getPayloadValue('discountId');
  112.         $removeThisDiscounts $lineItems->filter(static fn (LineItem $lineItem) => $lineItem->hasPayloadValue('discountId') && $lineItem->getPayloadValue('discountId') === $discountId);
  113.         foreach ($removeThisDiscounts as $discountItem) {
  114.             $cart->remove($discountItem->getId());
  115.         }
  116.     }
  117.     private function removeOtherDiscountsOfPromotion(Cart $cartLineItem $lineItemSalesChannelContext $context): void
  118.     {
  119.         // ge all promotions from cart
  120.         $lineItems $cart->getLineItems()->filterType(PromotionProcessor::LINE_ITEM_TYPE);
  121.         if ($lineItems->count() < 1) {
  122.             return;
  123.         }
  124.         //filter them by the promotion which discounts should be deleted
  125.         $lineItems $lineItems->filter(fn (LineItem $promotionLineItem) => $promotionLineItem->getPayloadValue('promotionId') === $lineItem->getPayloadValue('promotionId'));
  126.         if ($lineItems->count() < 1) {
  127.             return;
  128.         }
  129.         $promotionLineItem $lineItems->first();
  130.         if ($promotionLineItem instanceof LineItem) {
  131.             // this is recursive because we are listening on LineItemRemovedEvent, it will stop if there
  132.             // are no discounts in the cart, that belong to the promotion that should be deleted
  133.             $this->cartService->remove($cart$promotionLineItem->getId(), $context);
  134.         }
  135.     }
  136.     private function addCode(string $codeCart $cart): void
  137.     {
  138.         $extension $this->getExtension($cart);
  139.         $extension->addCode($code);
  140.         $cart->addExtension(CartExtension::KEY$extension);
  141.     }
  142.     private function removeCode(string $codeCart $cart): void
  143.     {
  144.         $extension $this->getExtension($cart);
  145.         $extension->removeCode($code);
  146.         $cart->addExtension(CartExtension::KEY$extension);
  147.     }
  148.     private function blockPromotion(string $idCart $cart): void
  149.     {
  150.         $extension $this->getExtension($cart);
  151.         $extension->blockPromotion($id);
  152.         $cart->addExtension(CartExtension::KEY$extension);
  153.     }
  154.     private function getExtension(Cart $cart): CartExtension
  155.     {
  156.         if (!$cart->hasExtension(CartExtension::KEY)) {
  157.             $cart->addExtension(CartExtension::KEY, new CartExtension());
  158.         }
  159.         /** @var CartExtension $extension */
  160.         $extension $cart->getExtension(CartExtension::KEY);
  161.         return $extension;
  162.     }
  163. }