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

Open in your IDE?
  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\Event\CheckoutOrderPlacedEvent;
  5. use Shopware\Core\Checkout\Cart\Event\LineItemAddedEvent;
  6. use Shopware\Core\Checkout\Cart\Event\LineItemQuantityChangedEvent;
  7. use Shopware\Core\Checkout\Cart\Event\LineItemRemovedEvent;
  8. use Shopware\Core\Checkout\Cart\Exception\InvalidPayloadException;
  9. use Shopware\Core\Checkout\Cart\Exception\InvalidQuantityException;
  10. use Shopware\Core\Checkout\Cart\Exception\LineItemNotFoundException;
  11. use Shopware\Core\Checkout\Cart\Exception\LineItemNotRemovableException;
  12. use Shopware\Core\Checkout\Cart\Exception\LineItemNotStackableException;
  13. use Shopware\Core\Checkout\Cart\Exception\MixedLineItemTypeException;
  14. use Shopware\Core\Checkout\Cart\Exception\PayloadKeyNotFoundException;
  15. use Shopware\Core\Checkout\Cart\LineItem\LineItem;
  16. use Shopware\Core\Checkout\Promotion\Aggregate\PromotionDiscount\PromotionDiscountEntity;
  17. use Shopware\Core\Checkout\Promotion\Cart\PromotionItemBuilder;
  18. use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor;
  19. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. use Symfony\Component\HttpFoundation\Session\Session;
  22. class StorefrontCartSubscriber implements EventSubscriberInterface
  23. {
  24.     public const SESSION_KEY_PROMOTION_CODES 'cart-promotion-codes';
  25.     /**
  26.      * @var Session
  27.      */
  28.     private $session;
  29.     public function __construct(Session $session)
  30.     {
  31.         $this->session $session;
  32.     }
  33.     public static function getSubscribedEvents(): array
  34.     {
  35.         return [
  36.             LineItemAddedEvent::class => 'onLineItemAdded',
  37.             LineItemQuantityChangedEvent::class => 'onLineItemQuantityChanged',
  38.             LineItemRemovedEvent::class => 'onLineItemRemoved',
  39.             CheckoutOrderPlacedEvent::class => 'resetCodes',
  40.         ];
  41.     }
  42.     public function resetCodes(): void
  43.     {
  44.         $this->session->set(self::SESSION_KEY_PROMOTION_CODES, []);
  45.     }
  46.     /**
  47.      * This function is called whenever a new line item has been
  48.      * added to the cart from within the controllers.
  49.      * We verify if we have a placeholder line item for a promotion
  50.      * and add that code to our global session list.
  51.      * We also re-add all codes that the user
  52.      * has previously added in case they might work now.
  53.      */
  54.     public function onLineItemAdded(LineItemAddedEvent $event): void
  55.     {
  56.         $this->setupSession();
  57.         if ($event->getLineItem()->getType() === PromotionProcessor::LINE_ITEM_TYPE) {
  58.             $code $event->getLineItem()->getReferencedId();
  59.             if ($code !== null && $code !== '') {
  60.                 $this->addToSession($code);
  61.             }
  62.         }
  63.         $this->reAddPromotionsFromSession($event->getCart(), $event->getContext());
  64.     }
  65.     /**
  66.      * This function is called whenever a line item quantity changes.
  67.      * In this case we just make sure that we re-add all codes that the user
  68.      * has previously added in case they might work now.
  69.      */
  70.     public function onLineItemQuantityChanged(LineItemQuantityChangedEvent $event): void
  71.     {
  72.         $this->setupSession();
  73.         $this->reAddPromotionsFromSession($event->getCart(), $event->getContext());
  74.     }
  75.     /**
  76.      * This function is called whenever a line item is being removed
  77.      * from the cart from within a controller.
  78.      * We verify if it is a promotion item, and also remove that
  79.      * code from our global session, if existing.
  80.      * We also re-add all codes that the user
  81.      * has previously added in case they might work now.
  82.      */
  83.     public function onLineItemRemoved(LineItemRemovedEvent $event): void
  84.     {
  85.         $this->setupSession();
  86.         $cart $event->getCart();
  87.         if ($event->getLineItem()->getType() === PromotionProcessor::LINE_ITEM_TYPE) {
  88.             $lineItem $event->getLineItem();
  89.             $code $lineItem->getReferencedId();
  90.             if ($code !== null && $code !== '') {
  91.                 $this->checkFixedDiscountItems($cart$lineItem);
  92.                 $this->removeFromSession($code);
  93.             }
  94.         }
  95.         $this->reAddPromotionsFromSession($cart$event->getContext());
  96.     }
  97.     /**
  98.      * This function adds placeholder line items for promotions.
  99.      * It will always add items for all codes that are existing in
  100.      * the current session of the user.
  101.      * Thus it will re-add promotions that have been added before
  102.      * and where not explicitly removed by the user.
  103.      *
  104.      * @throws InvalidPayloadException
  105.      * @throws InvalidQuantityException
  106.      * @throws LineItemNotStackableException
  107.      * @throws MixedLineItemTypeException
  108.      */
  109.     private function reAddPromotionsFromSession(Cart $cartSalesChannelContext $context): void
  110.     {
  111.         /** @var string[] $allSessionCodes */
  112.         $allSessionCodes $this->session->get(self::SESSION_KEY_PROMOTION_CODES);
  113.         if (\count($allSessionCodes) <= 0) {
  114.             return;
  115.         }
  116.         $codesInCart $cart->getLineItems()->filterType(PromotionProcessor::LINE_ITEM_TYPE)->getReferenceIds();
  117.         $builder = new PromotionItemBuilder();
  118.         foreach ($allSessionCodes as $sessionCode) {
  119.             // only add a new placeholder item if that
  120.             // code is not already existing either as placeholder or real promotion item
  121.             if (!\in_array($sessionCode$codesInCarttrue)) {
  122.                 $lineItem $builder->buildPlaceholderItem($sessionCode$context->getContext()->getCurrencyPrecision());
  123.                 $cart->add($lineItem);
  124.             }
  125.         }
  126.     }
  127.     /**
  128.      * @throws LineItemNotFoundException
  129.      * @throws LineItemNotRemovableException
  130.      * @throws PayloadKeyNotFoundException
  131.      */
  132.     private function checkFixedDiscountItems(Cart $cartLineItem $lineItem): void
  133.     {
  134.         $lineItems $cart->getLineItems()->filterType(PromotionProcessor::LINE_ITEM_TYPE);
  135.         if ($lineItems->count() < 1) {
  136.             return;
  137.         }
  138.         if (!$lineItem->hasPayloadValue('discountType')) {
  139.             return;
  140.         }
  141.         if ($lineItem->getPayloadValue('discountType') !== PromotionDiscountEntity::TYPE_FIXED_UNIT) {
  142.             return;
  143.         }
  144.         if (!$lineItem->hasPayloadValue('discountId')) {
  145.             return;
  146.         }
  147.         $discountId $lineItem->getPayloadValue('discountId');
  148.         $removeThisDiscounts $lineItems->filter(static function (LineItem $lineItem) use ($discountId) {
  149.             return $lineItem->hasPayloadValue('discountId') && $lineItem->getPayloadValue('discountId') === $discountId;
  150.         });
  151.         foreach ($removeThisDiscounts as $discountItem) {
  152.             $cart->remove($discountItem->getId());
  153.         }
  154.     }
  155.     /**
  156.      * if a customer adds a promotion code it is stored in the session
  157.      * the promotion will be added each time if a change in cart occurs
  158.      * This ensures that is added and removed automatically if restrictions
  159.      * of promotions fit or do not fit
  160.      */
  161.     private function addToSession(string $code): void
  162.     {
  163.         /** @var array $allCodes */
  164.         $allCodes $this->session->get(self::SESSION_KEY_PROMOTION_CODES);
  165.         // add our new item
  166.         if (!\in_array($code$allCodestrue)) {
  167.             $allCodes[] = $code;
  168.         }
  169.         $this->session->set(self::SESSION_KEY_PROMOTION_CODES$allCodes);
  170.     }
  171.     /**
  172.      * if a customer removes a promotion code from the cart, he explicitly tells us
  173.      * that he doesn't want it => remove it from session store to ensure it is not
  174.      * added automatically any more
  175.      */
  176.     private function removeFromSession(string $code): void
  177.     {
  178.         /** @var array $allCodes */
  179.         $allCodes $this->session->get(self::SESSION_KEY_PROMOTION_CODES);
  180.         // remove our code string from the list
  181.         $allCodes array_diff($allCodes, [$code]);
  182.         $this->session->set(self::SESSION_KEY_PROMOTION_CODES$allCodes);
  183.     }
  184.     /**
  185.      * Creates an empty session list if not already existing.
  186.      */
  187.     private function setupSession(): void
  188.     {
  189.         if (!$this->session->has(self::SESSION_KEY_PROMOTION_CODES)) {
  190.             $this->session->set(self::SESSION_KEY_PROMOTION_CODES, []);
  191.         }
  192.     }
  193. }