custom/plugins/NewsletterSendinblue/src/Subscriber/AbandonedCartSubscriber.php line 171

Open in your IDE?
  1. <?php
  2. namespace NewsletterSendinblue\Subscriber;
  3. use Doctrine\DBAL\Connection;
  4. use NewsletterSendinblue\Core\Content\Cart\AbandonedCartEntity;
  5. use NewsletterSendinblue\Service\Cart\CartEventProducer;
  6. use NewsletterSendinblue\Service\ConfigService;
  7. use NewsletterSendinblue\Service\Cart\SendinblueCartProcessorService;
  8. use Shopware\Core\Checkout\Cart\Cart;
  9. use Shopware\Core\Checkout\Cart\AbstractCartPersister;
  10. use Shopware\Core\Checkout\Cart\Event\AfterLineItemAddedEvent;
  11. use Shopware\Core\Checkout\Cart\Event\AfterLineItemQuantityChangedEvent;
  12. use Shopware\Core\Checkout\Cart\Event\AfterLineItemRemovedEvent;
  13. use Shopware\Core\Checkout\Cart\Event\BeforeCartMergeEvent;
  14. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent;
  15. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemQuantityChangedEvent;
  16. use Shopware\Core\Checkout\Cart\Event\BeforeLineItemRemovedEvent;
  17. use Shopware\Core\Checkout\Cart\Event\LineItemAddedEVent;
  18. use Shopware\Core\Checkout\Cart\Event\LineItemQuantityChangedEvent;
  19. use Shopware\Core\Checkout\Cart\Event\LineItemRemovedEvent;
  20. use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection;
  21. use Shopware\Core\Checkout\Cart\Order\CartConvertedEvent;
  22. use Shopware\Core\Checkout\Customer\CustomerEntity;
  23. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  25. use Shopware\Core\Framework\Uuid\Uuid;
  26. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  27. use Shopware\Storefront\Page\Checkout\Cart\CheckoutCartPageLoadedEvent;
  28. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
  29. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  30. use Symfony\Component\HttpFoundation\RequestStack;
  31. use Throwable;
  32. class AbandonedCartSubscriber implements EventSubscriberInterface
  33. {
  34.     /**
  35.      * @var CartEventProducer
  36.      */
  37.     private $cartEventProducer;
  38.     /**
  39.      * @var SendinblueCartProcessorService
  40.      */
  41.     private $cartProcessorService;
  42.     /**
  43.      * @var ConfigService
  44.      */
  45.     private $configService;
  46.     /**
  47.      * @var AbstractCartPersister
  48.      */
  49.     private $cartPersister;
  50.     /**
  51.      * @var Connection
  52.      */
  53.     private $connection;
  54.     /**
  55.      * @var EntityRepositoryInterface
  56.      */
  57.     private $brevoAbandonedCartRepository;
  58.     /**
  59.      * @var RequestStack;
  60.      */
  61.     private $requestStack;
  62.     /**
  63.      * AbandonedCartSubscriber constructor.
  64.      *
  65.      * @param CartEventProducer $cartEventProducer
  66.      * @param SendinblueCartProcessorService $cartProcessorService
  67.      * @param ConfigService $configService
  68.      * @param AbstractCartPersister $cartPersister
  69.      * @param Connection $connection
  70.      * @param EntityRepositoryInterface $brevoAbandonedCartRepository
  71.      * @param RequestStack $requestStack
  72.      */
  73.     public function __construct(
  74.         CartEventProducer              $cartEventProducer,
  75.         SendinblueCartProcessorService $cartProcessorService,
  76.         ConfigService                  $configService,
  77.         AbstractCartPersister          $cartPersister,
  78.         Connection                     $connection,
  79.         EntityRepositoryInterface      $brevoAbandonedCartRepository,
  80.         RequestStack                   $requestStack
  81.     )
  82.     {
  83.         $this->cartEventProducer $cartEventProducer;
  84.         $this->cartProcessorService $cartProcessorService;
  85.         $this->configService $configService;
  86.         $this->cartPersister $cartPersister;
  87.         $this->connection $connection;
  88.         $this->brevoAbandonedCartRepository $brevoAbandonedCartRepository;
  89.         $this->requestStack $requestStack;
  90.     }
  91.     /**
  92.      * @return array
  93.      */
  94.     public static function getSubscribedEvents(): array
  95.     {
  96.         $events = [];
  97.         $events[CartConvertedEvent::class] = 'onCartConvertedEvent';
  98.         $events[CheckoutCartPageLoadedEvent::class] = 'onCheckoutCartPageLoadedEvent';
  99.         $events[CheckoutConfirmPageLoadedEvent::class] = 'onCheckoutConfirmPageLoadedEvent';
  100.         $events[BeforeCartMergeEvent::class] = 'onBeforeCartMergeEvent';
  101.         if (class_exists('\Shopware\Core\Checkout\Cart\Event\BeforeLineItemAddedEvent')) {
  102.             // for newer shopware versions
  103.             $events[BeforeLineItemAddedEvent::class] = 'onCartUpdatedEvent';
  104.             $events[BeforeLineItemQuantityChangedEvent::class] = 'onCartUpdatedEvent';
  105.             $events[BeforeLineItemRemovedEvent::class] = 'onCartUpdatedEvent';
  106.             $events[AfterLineItemAddedEvent::class] = 'onAfterCartUpdatedEvent';
  107.             $events[AfterLineItemQuantityChangedEvent::class] = 'onAfterCartUpdatedEvent';
  108.             $events[AfterLineItemRemovedEvent::class] = 'onAfterCartUpdatedEvent';
  109.         } else if (class_exists('\Shopware\Core\Checkout\Cart\Event\LineItemAddedEvent')) {
  110.             // for older shopware versions
  111.             $events[LineItemAddedEvent::class] = 'onCartUpdatedEvent';
  112.             $events[LineItemQuantityChangedEvent::class] = 'onCartUpdatedEvent';
  113.             $events[LineItemRemovedEvent::class] = 'onCartUpdatedEvent';
  114.         }
  115.         return $events;
  116.     }
  117.     /**
  118.      * @param CheckoutConfirmPageLoadedEvent $event
  119.      * @return void
  120.      */
  121.     public function onCheckoutConfirmPageLoadedEvent(CheckoutConfirmPageLoadedEvent $event)
  122.     {
  123.         if (method_exists($event'getSalesChannelContext') && $event->getSalesChannelContext() instanceof SalesChannelContext) {
  124.             $salesChannelId $event->getSalesChannelContext()->getSalesChannelId();
  125.         } else if (method_exists($event'getContext') && $event->getContext() instanceof SalesChannelContext) {
  126.             $salesChannelId $event->getContext()->getSalesChannelId();
  127.         } else {
  128.             return;
  129.         }
  130.         $this->configService->setSalesChannelId($salesChannelId);
  131.         if ($this->configService->isAbandonedCartTrackingEnabled()) {
  132.             $customer $event->getSalesChannelContext()->getCustomer();
  133.             if (!$customer instanceof CustomerEntity
  134.                 || !$event->getPage()->getCart() instanceof Cart
  135.             ) {
  136.                 return;
  137.             }
  138.             $payload $this->getCartPayload($event->getPage()->getCart(), $event->getSalesChannelContext());
  139.             try {
  140.                 $this->brevoAbandonedCartRepository->upsert([[
  141.                     'id' => md5($customer->getEmail()),
  142.                     'email' => $customer->getEmail(),
  143.                     'payload' => $payload,
  144.                 ]], $event->getContext());
  145.             } catch (Throwable $e) {
  146.             }
  147.         }
  148.     }
  149.     /**
  150.      * @param BeforeCartMergeEvent $event
  151.      * @return void
  152.      */
  153.     public function onBeforeCartMergeEvent(BeforeCartMergeEvent $event)
  154.     {
  155.         if ($this->requestStack
  156.             && $this->requestStack->getCurrentRequest()
  157.             && $this->requestStack->getCurrentRequest()->hasSession()
  158.             && $this->requestStack->getSession()->has('sbAbandonedCartIsUsed')
  159.         ) {
  160.             // to avoid duplicates in the cart
  161.             $event->getCustomerCart()->setLineItems(new LineItemCollection());
  162.             $this->requestStack->getSession()->remove('sbAbandonedCartIsUsed');
  163.         }
  164.     }
  165.     /**
  166.      * @param CheckoutCartPageLoadedEvent $event
  167.      * @return void
  168.      */
  169.     public function onCheckoutCartPageLoadedEvent(CheckoutCartPageLoadedEvent $event)
  170.     {
  171.         $bacId $event->getRequest()->get('bacId''');
  172.         if (!Uuid::isValid($bacId)
  173.             || !$event->getSalesChannelContext() instanceof SalesChannelContext
  174.             || $event->getSalesChannelContext()->getCustomer() instanceof CustomerEntity
  175.         ) {
  176.             return;
  177.         }
  178.         if (!$this->requestStack
  179.             || !$this->requestStack->getCurrentRequest()
  180.             || !$this->requestStack->getCurrentRequest()->hasSession()
  181.         ) {
  182.             return;
  183.         }
  184.         // It means it's already loaded from the abandoned cart
  185.         if ($this->requestStack->getSession()->has('sbAbandonedCartIsUsed')) {
  186.             return;
  187.         }
  188.         $criteria = new Criteria([$bacId]);
  189.         $brevoAbandonedCart $this->brevoAbandonedCartRepository->search($criteria$event->getContext())->first();
  190.         if (!$brevoAbandonedCart instanceof AbandonedCartEntity) {
  191.             return;
  192.         }
  193.         $abandonedCart unserialize($brevoAbandonedCart->getPayload());
  194.         if (!$abandonedCart instanceof Cart) {
  195.             $this->brevoAbandonedCartRepository->delete([
  196.                 ['id' => $bacId]
  197.             ], $event->getSalesChannelContext()->getContext());
  198.             return;
  199.         }
  200.         $abandonedCart->setToken($event->getSalesChannelContext()->getToken());
  201.         $this->cartPersister->save($abandonedCart$event->getSalesChannelContext());
  202.         $event->getPage()->setCart($abandonedCart);
  203.         $event->getRequest()->getSession()->set('sbAbandonedCartIsUsed'true);
  204.     }
  205.     /**
  206.      * @param CartConvertedEvent $event
  207.      */
  208.     public function onCartConvertedEvent(CartConvertedEvent $event): void
  209.     {
  210.         $this->configService->setSalesChannelId($event->getSalesChannelContext()->getSalesChannelId());
  211.         if ($this->configService->isAbandonedCartTrackingEnabled()) {
  212.             $this->cartEventProducer->processOrder(
  213.                 $event->getOriginalConvertedCart(),
  214.                 $event->getCart(),
  215.                 $event->getSalesChannelContext()
  216.             );
  217.         }
  218.     }
  219.     /**
  220.      * @param $event
  221.      * @return void
  222.      */
  223.     public function onAfterCartUpdatedEvent($event)
  224.     {
  225.         if (method_exists($event'getSalesChannelContext') && $event->getSalesChannelContext() instanceof SalesChannelContext) {
  226.             $salesChannelId $event->getSalesChannelContext()->getSalesChannelId();
  227.         } else if (method_exists($event'getContext') && $event->getContext() instanceof SalesChannelContext) {
  228.             $salesChannelId $event->getContext()->getSalesChannelId();
  229.         } else {
  230.             return;
  231.         }
  232.         $this->configService->setSalesChannelId($salesChannelId);
  233.         if ($this->configService->isAbandonedCartTrackingEnabled()) {
  234.             $customer $event->getSalesChannelContext()->getCustomer();
  235.             if (!$customer instanceof CustomerEntity
  236.                 || !$event->getCart() instanceof Cart
  237.             ) {
  238.                 return;
  239.             }
  240.             $payload $this->getCartPayload($event->getCart(), $event->getSalesChannelContext());
  241.             try {
  242.                 $this->brevoAbandonedCartRepository->upsert([[
  243.                     'id' => md5($customer->getEmail()),
  244.                     'email' => $customer->getEmail(),
  245.                     'payload' => $payload,
  246.                 ]], $event->getContext());
  247.             } catch (Throwable $e) {
  248.             }
  249.         }
  250.     }
  251.     /**
  252.      * @param $event
  253.      */
  254.     public function onCartUpdatedEvent($event): void
  255.     {
  256.         if (method_exists($event'getSalesChannelContext') && $event->getSalesChannelContext() instanceof SalesChannelContext) {
  257.             $salesChannelId $event->getSalesChannelContext()->getSalesChannelId();
  258.         } else if (method_exists($event'getContext') && $event->getContext() instanceof SalesChannelContext) {
  259.             $salesChannelId $event->getContext()->getSalesChannelId();
  260.         } else {
  261.             return;
  262.         }
  263.         $this->configService->setSalesChannelId($salesChannelId);
  264.         if ($this->configService->isAbandonedCartTrackingEnabled()) {
  265.             $this->cartProcessorService->setShouldCollectData(true);
  266.         }
  267.     }
  268.     /**
  269.      * @param Cart $cart
  270.      * @param SalesChannelContext $salesChannelContext
  271.      * @return string|null
  272.      */
  273.     private function getCartPayload(Cart $cartSalesChannelContext $salesChannelContext): ?string
  274.     {
  275.         $payload null;
  276.         try {
  277.             if ($cart->getLineItems()->count() > 0) {
  278.                 $cartContent $this->connection->fetchAssociative(
  279.                     '#cart-persister::load
  280.                     SELECT `cart`.`payload`, `cart`.`rule_ids`, `cart`.`compressed` FROM cart WHERE `token` = :token',
  281.                     ['token' => $salesChannelContext->getToken()]
  282.                 );
  283.                 if (!empty($cartContent)) {
  284.                     $payload $cartContent['payload'];
  285.                 }
  286.             }
  287.         } catch (Throwable $e) {
  288.         }
  289.         return $payload;
  290.     }
  291. }