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

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