vendor/shopware/core/Checkout/Promotion/Subscriber/Storefront/StorefrontCartSubscriber.php line 46

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