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

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