custom/plugins/NewsletterSendinblue/src/Subscriber/NewsletterRecipientSubscriber.php line 148

Open in your IDE?
  1. <?php
  2. namespace NewsletterSendinblue\Subscriber;
  3. use NewsletterSendinblue\NewsletterSendinblue;
  4. use NewsletterSendinblue\Service\Cart\CartEventProducer;
  5. use NewsletterSendinblue\Service\Cart\SendinblueCartProcessorService;
  6. use NewsletterSendinblue\Service\ConfigService;
  7. use NewsletterSendinblue\Service\Constant;
  8. use NewsletterSendinblue\Service\Customer\CustomerProducer;
  9. use NewsletterSendinblue\Service\NewsletterRecipient\NewsletterRecipientProducer;
  10. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  11. use Shopware\Core\Checkout\Customer\CustomerEntity;
  12. use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
  13. use Shopware\Core\Checkout\Customer\Event\CustomerRegisterEvent;
  14. use Shopware\Core\Checkout\Customer\Event\GuestCustomerRegisterEvent;
  15. use Shopware\Core\Content\MailTemplate\Service\Event\MailBeforeValidateEvent;
  16. use Shopware\Core\Content\Newsletter\Event\NewsletterConfirmEvent;
  17. use Shopware\Core\Content\Newsletter\Event\NewsletterRegisterEvent;
  18. use Shopware\Core\Content\Newsletter\Event\NewsletterSubscribeUrlEvent;
  19. use Shopware\Core\Framework\Api\Context\SalesChannelApiSource;
  20. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  21. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
  22. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  23. use Shopware\Core\Framework\Context;
  24. use Shopware\Core\Framework\Struct\ArrayStruct;
  25. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  26. use Shopware\Core\System\SystemConfig\SystemConfigService;
  27. use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
  28. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  29. use Symfony\Component\HttpFoundation\RequestStack;
  30. class NewsletterRecipientSubscriber implements EventSubscriberInterface
  31. {
  32.     /**
  33.      * @var NewsletterRecipientProducer
  34.      */
  35.     private $newsletterRecipientProducer;
  36.     /**
  37.      * @var CustomerProducer
  38.      */
  39.     private $customerProducer;
  40.     /**
  41.      * @var ConfigService
  42.      */
  43.     private $configService;
  44.     /**
  45.      * @var SystemConfigService
  46.      */
  47.     private $systemConfigService;
  48.     /**
  49.      * @var EntityRepositoryInterface
  50.      */
  51.     private $mailTemplateRepository;
  52.     /**
  53.      * @var EntityRepositoryInterface
  54.      */
  55.     private $customerRepository;
  56.     /**
  57.      * @var RequestStack;
  58.      */
  59.     private $requestStack;
  60.     /**
  61.      * @var SendinblueCartProcessorService
  62.      */
  63.     private $cartProcessorService;
  64.     /**
  65.      * @var CartEventProducer
  66.      */
  67.     private $cartEventProducer;
  68.     /**
  69.      * @var CartService
  70.      */
  71.     private $cartService;
  72.     /**
  73.      * @var bool
  74.      */
  75.     private $isRecipientAlreadyUpdated = false;
  76.     /**
  77.      * @param NewsletterRecipientProducer $newsletterRecipientProducer
  78.      * @param CustomerProducer $customerProducer
  79.      * @param ConfigService $configService
  80.      * @param SystemConfigService $systemConfigService
  81.      * @param EntityRepositoryInterface $mailTemplateRepository
  82.      * @param EntityRepositoryInterface $customerRepository
  83.      * @param RequestStack $requestStack
  84.      * @param SendinblueCartProcessorService $cartProcessorService
  85.      * @param CartEventProducer $cartEventProducer
  86.      * @param CartService $cartService
  87.      */
  88.     public function __construct(
  89.         NewsletterRecipientProducer    $newsletterRecipientProducer,
  90.         CustomerProducer               $customerProducer,
  91.         ConfigService                  $configService,
  92.         SystemConfigService            $systemConfigService,
  93.         EntityRepositoryInterface      $mailTemplateRepository,
  94.         EntityRepositoryInterface      $customerRepository,
  95.         RequestStack                   $requestStack,
  96.         SendinblueCartProcessorService $cartProcessorService,
  97.         CartEventProducer              $cartEventProducer,
  98.         CartService                    $cartService
  99.     )
  100.     {
  101.         $this->newsletterRecipientProducer = $newsletterRecipientProducer;
  102.         $this->customerProducer = $customerProducer;
  103.         $this->configService = $configService;
  104.         $this->systemConfigService = $systemConfigService;
  105.         $this->mailTemplateRepository = $mailTemplateRepository;
  106.         $this->customerRepository = $customerRepository;
  107.         $this->requestStack = $requestStack;
  108.         $this->cartProcessorService = $cartProcessorService;
  109.         $this->cartEventProducer = $cartEventProducer;
  110.         $this->cartService = $cartService;
  111.     }
  112.     /**
  113.      * @return array
  114.      */
  115.     public static function getSubscribedEvents(): array
  116.     {
  117.         return [
  118.             NewsletterConfirmEvent::class => 'onNewsletterConfirmEvent',
  119.             NewsletterRegisterEvent::class => 'onNewsletterRegisterEvent',
  120.             NewsletterSubscribeUrlEvent::class => 'onNewsletterSubscribeUrlEvent',
  121.             EntityWrittenContainerEvent::class => 'onEntityWrittenContainerEvent',
  122.             MailBeforeValidateEvent::class => 'onMailBeforeValidateEvent',
  123.             CustomerRegisterEvent::class => 'onCustomerRegisterEvent',
  124.             GuestCustomerRegisterEvent::class => 'onCustomerRegisterEvent',
  125.             CustomerLoginEvent::class => 'onCustomerLoginEvent',
  126.         ];
  127.     }
  128.     /**
  129.      * @param NewsletterSubscribeUrlEvent $event
  130.      * @return void
  131.      */
  132.     public function onNewsletterSubscribeUrlEvent(NewsletterSubscribeUrlEvent $event): void
  133.     {
  134.         if ($this->isFooterSubscription()) {
  135.             $subscribeUrl = $event->getSubscribeUrl();
  136.             $key = 'brFt';
  137.             $val = '1';
  138.             $separator = (strpos($subscribeUrl, '?') !== false) ? '&' : '?';
  139.             $subscribeUrl .= $separator . urlencode($key) . '=' . urlencode($val);
  140.             $event->setSubscribeUrl($subscribeUrl);
  141.         }
  142.     }
  143.     /**
  144.      * @param CustomerLoginEvent $event
  145.      * @return void
  146.      */
  147.     public function onCustomerLoginEvent(CustomerLoginEvent $event)
  148.     {
  149.         if (method_exists($event, 'getSalesChannelContext') && $event->getSalesChannelContext() instanceof SalesChannelContext) {
  150.             $salesChannelId = $event->getSalesChannelContext()->getSalesChannelId();
  151.         } else if (method_exists($event, 'getContext') && $event->getContext() instanceof SalesChannelContext) {
  152.             $salesChannelId = $event->getContext()->getSalesChannelId();
  153.         } else {
  154.             return;
  155.         }
  156.         $this->configService->setSalesChannelId($salesChannelId);
  157.         if ($this->configService->isAbandonedCartTrackingEnabled()) {
  158.             $this->cartProcessorService->setShouldCollectData(true);
  159.         }
  160.     }
  161.     /**
  162.      * @param CustomerRegisterEvent $event
  163.      */
  164.     public function onCustomerRegisterEvent(CustomerRegisterEvent $event): void
  165.     {
  166.         // Determine the location of register: signup or checkout
  167.         $subscriptionLocation = CustomerProducer::SUBSCRIPTION_LOCATION_SIGNUP;
  168.         if ($this->requestStack
  169.             && $this->requestStack->getCurrentRequest()
  170.             && $this->requestStack->getCurrentRequest()->server->has('HTTP_REFERER')
  171.         ) {
  172.             $referrer = $this->requestStack->getCurrentRequest()->server->get('HTTP_REFERER');
  173.             if ($referrer && strpos($referrer, '/checkout') !== false) {
  174.                 $subscriptionLocation = CustomerProducer::SUBSCRIPTION_LOCATION_ORDER_CHECKOUT;
  175.             }
  176.         }
  177.         $event->getContext()->addExtension(CustomerProducer::SUBSCRIPTION_LOCATION_EXT, new ArrayStruct([
  178.             'value' => $subscriptionLocation
  179.         ]));
  180.         $this->configService->setSalesChannelId($event->getSalesChannelId());
  181.         if ($this->configService->isFullCustomerSyncEnabled()) {
  182.             $customer = $event->getCustomer();
  183.             $this->customerRepository->update([[
  184.                 'id' => $customer->getId(),
  185.                 'customFields' => [
  186.                     CustomerProducer::FULL_SYNC_CUSTOMER_REGISTERED => true
  187.                 ]
  188.             ]], $event->getContext());
  189.             $this->customerProducer->setAdditionalUpdateData([
  190.                 Constant::OPT_IN_ENABLED => 'false',
  191.                 Constant::OPT_IN_CHECKED => 'false'
  192.             ]);
  193.             $this->customerProducer->confirmContact($customer, $event->getContext(), $event->getSalesChannelId());
  194.         }
  195.         if ($this->configService->isAbandonedCartTrackingEnabled()) {
  196.             $salesChannelContext = $event->getSalesChannelContext();
  197.             $cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
  198.             if ($cart->getLineItems()->count() > 0) {
  199.                 $this->cartEventProducer->processCart($cart, $salesChannelContext);
  200.             }
  201.         }
  202.     }
  203.     /**
  204.      * @param NewsletterConfirmEvent $event
  205.      */
  206.     public function onNewsletterConfirmEvent(NewsletterConfirmEvent $event): void
  207.     {
  208.         /**
  209.          * This method is triggered when:
  210.          * 1. DOI (Double Opt-In) is enabled, after the user confirms via email.
  211.          * 2. DOI is not enabled, immediately after newsletter registration.
  212.          */
  213.         if ($this->isRecipientAlreadyUpdated) {
  214.             $this->isRecipientAlreadyUpdated = false;
  215.             return;
  216.         }
  217.         $additionalUpdateData = [
  218.             Constant::OPT_IN_ENABLED => 'true',
  219.             Constant::OPT_IN_CHECKED => 'true'
  220.         ];
  221.         if ($this->isFooterSubscription()) {
  222.             $additionalUpdateData[CustomerProducer::SUBSCRIPTION_LOCATION_KEY] =
  223.                 CustomerProducer::SUBSCRIPTION_LOCATION_FOOTER;
  224.         }
  225.         $this->newsletterRecipientProducer->setAdditionalUpdateData($additionalUpdateData);
  226.         $this->newsletterRecipientProducer->confirmContact($event->getNewsletterRecipient(), $event->getContext(), $event->getSalesChannelId());
  227.     }
  228.     /**
  229.      * @param NewsletterRegisterEvent $event
  230.      */
  231.     public function onNewsletterRegisterEvent(NewsletterRegisterEvent $event): void
  232.     {
  233.         $this->configService->setSalesChannelId($event->getSalesChannelId());
  234.         if ($this->configService->getSubscriptionMailing()) {
  235.             $additionalUpdateData = [
  236.                 Constant::OPT_IN_ENABLED => 'true',
  237.                 Constant::OPT_IN_CHECKED => 'true'
  238.             ];
  239.             if ($this->isFooterSubscription()) {
  240.                 $additionalUpdateData[CustomerProducer::SUBSCRIPTION_LOCATION_KEY] =
  241.                     CustomerProducer::SUBSCRIPTION_LOCATION_FOOTER;
  242.             }
  243.             $this->newsletterRecipientProducer->setAdditionalUpdateData($additionalUpdateData);
  244.             $this->newsletterRecipientProducer->confirmContact($event->getNewsletterRecipient(), $event->getContext(), $event->getSalesChannelId());
  245.             if ($this->requestStack
  246.                 && $this->requestStack->getCurrentRequest()
  247.                 && $this->requestStack->getCurrentRequest()->hasSession()
  248.                 && $this->requestStack->getSession()->has('sbCoreDoubleOptIn')
  249.             ) {
  250.                 $session = $this->requestStack->getSession();
  251.                 $this->systemConfigService->set('core.newsletter.doubleOptIn', $session->get('sbCoreDoubleOptIn'));
  252.                 $session->remove('sbCoreDoubleOptIn');
  253.             }
  254.         }
  255.     }
  256.     /**
  257.      * @param EntityWrittenContainerEvent $event
  258.      */
  259.     public function onEntityWrittenContainerEvent(EntityWrittenContainerEvent $event): void
  260.     {
  261.         // Stop sending calls to Brevo if they are coming from Brevo UI
  262.         if ($event->getContext()->hasExtension('brevoUiUnsubscribe')
  263.             || $event->getContext()->hasExtension('brevoUiSubscribe')
  264.             || $event->getContext()->hasExtension(NewsletterSendinblue::IGNORE_EVENT)
  265.         ) {
  266.             return;
  267.         }
  268.         foreach ($event->getEvents()->getElements() as $eventElement) {
  269.             if ($eventElement->getName() === 'newsletter_recipient.written') {
  270.                 foreach ($eventElement->getWriteResults() as $writeResult) {
  271.                     if ($writeResult->getOperation() === 'update') {
  272.                         $payload = $writeResult->getPayload();
  273.                         $newsletterRecipientId = $writeResult->getPrimaryKey();
  274.                         $salesChannelId = $payload['salesChannelId'] ?? null;
  275.                         if (empty($salesChannelId)) {
  276.                             if ($event->getContext()->getSource() instanceof SalesChannelApiSource) {
  277.                                 $salesChannelId = $event->getContext()->getSource()->getSalesChannelId();
  278.                             }
  279.                         }
  280.                         if (isset($payload['status']) && $payload['status'] == 'optOut') {
  281.                             $this->newsletterRecipientProducer->unsubscribeContact($newsletterRecipientId, $event->getContext(), $salesChannelId);
  282.                         } else {
  283.                             $additionalUpdateData = [
  284.                                 Constant::OPT_IN_ENABLED => 'true',
  285.                                 Constant::OPT_IN_CHECKED => 'true'
  286.                             ];
  287.                             if ($this->isDoiFooterSubscription()) {
  288.                                 $additionalUpdateData[CustomerProducer::SUBSCRIPTION_LOCATION_KEY] =
  289.                                     CustomerProducer::SUBSCRIPTION_LOCATION_FOOTER;
  290.                             }
  291.                             $this->newsletterRecipientProducer->setAdditionalUpdateData($additionalUpdateData);
  292.                             $this->newsletterRecipientProducer->updateContact($newsletterRecipientId, $event->getContext(), $salesChannelId);
  293.                             $this->isRecipientAlreadyUpdated = true;
  294.                         }
  295.                     }
  296.                 }
  297.             }
  298.             if ($eventElement->getName() === 'customer.written') {
  299.                 foreach ($eventElement->getWriteResults() as $writeResult) {
  300.                     if ($writeResult->getOperation() === 'update') {
  301.                         $payload = $writeResult->getPayload();
  302.                         // Skip if customer is created - not call updateContact
  303.                         if (array_key_exists('customFields', $payload)
  304.                             && count($payload['customFields']) == 1
  305.                             && (array_key_exists(CustomerProducer::FULL_SYNC_CUSTOMER_REGISTERED, $payload['customFields'])
  306.                                 || array_key_exists(CustomerProducer::SW_SUB_STATUS_ATTR, $payload['customFields']))
  307.                         ) {
  308.                             continue;
  309.                         }
  310.                         if (array_key_exists('lastLogin', $payload)
  311.                             || array_key_exists('remoteAddress', $payload)
  312.                         ) {
  313.                             continue;
  314.                         }
  315.                         if (count($payload) === 3
  316.                             && array_key_exists('newsletter', $payload)
  317.                             && array_key_exists('updatedAt', $payload)
  318.                             && !empty($payload['updatedAt'])
  319.                         ) {
  320.                             continue;
  321.                         }
  322.                         $customerId = $writeResult->getPrimaryKey();
  323.                         $salesChannelId = $payload['salesChannelId'] ?? null;
  324.                         if (empty($salesChannelId)) {
  325.                             if ($event->getContext()->getSource() instanceof SalesChannelApiSource) {
  326.                                 $salesChannelId = $event->getContext()->getSource()->getSalesChannelId();
  327.                             }
  328.                         }
  329.                         $this->customerProducer->updateContact($customerId, $event->getContext(), $salesChannelId);
  330.                     }
  331.                 }
  332.             }
  333.         }
  334.     }
  335.     /**
  336.      * @param MailBeforeValidateEvent $event
  337.      */
  338.     public function onMailBeforeValidateEvent(MailBeforeValidateEvent $event): void
  339.     {
  340.         $data = $event->getData();
  341.         $templateData = $event->getTemplateData();
  342.         $context = $event->getContext();
  343.         if (!isset($templateData['newsletterRecipient']) || !isset($data['templateId'])) {
  344.             return;
  345.         }
  346.         if (!$this->isNewsletterMailTemplate($data['templateId'], $context)) {
  347.             return;
  348.         }
  349.         $salesChannelId = null;
  350.         if (!empty($data['salesChannelId'])) {
  351.             $salesChannelId = $data['salesChannelId'];
  352.         }
  353.         if (empty($salesChannelId) && method_exists($context->getSource(), 'getSalesChannelId')) {
  354.             $salesChannelId = $context->getSource()->getSalesChannelId();
  355.         }
  356.         if (!empty($salesChannelId)) {
  357.             $this->configService->setSalesChannelId($salesChannelId);
  358.         }
  359.         if (!$this->configService->getSubscriptionMailing()) {
  360.             return;
  361.         }
  362.         $event->stopPropagation();
  363.     }
  364.     /**
  365.      * @param string $templateId
  366.      * @param Context $context
  367.      * @return bool
  368.      */
  369.     private function isNewsletterMailTemplate(string $templateId, Context $context)
  370.     {
  371.         $criteria = new Criteria([$templateId]);
  372.         $criteria->addAssociation('mailTemplateType');
  373.         $mailTemplate = $this->mailTemplateRepository->search($criteria, $context)->first();
  374.         if (empty($mailTemplate)) {
  375.             return false;
  376.         }
  377.         return in_array($mailTemplate->getMailTemplateType()->getTechnicalName(), ['newsletterDoubleOptIn', 'newsletterRegister']);
  378.     }
  379.     /**
  380.      * @return bool
  381.      */
  382.     private function isFooterSubscription(): bool
  383.     {
  384.         if ($this->requestStack
  385.             && $this->requestStack->getCurrentRequest()
  386.             && $this->requestStack->getCurrentRequest()->server->has('REQUEST_URI')
  387.         ) {
  388.             $requestUri = $this->requestStack->getCurrentRequest()->server->get('REQUEST_URI');
  389.             if ($requestUri === Constant::FOOTER_REQUEST_URI) {
  390.                 return true;
  391.             }
  392.         }
  393.         return false;
  394.     }
  395.     /**
  396.      * @return bool
  397.      */
  398.     private function isDoiFooterSubscription(): bool
  399.     {
  400.         if ($this->requestStack
  401.             && $this->requestStack->getCurrentRequest()
  402.             && $this->requestStack->getCurrentRequest()->server->has('QUERY_STRING')
  403.         ) {
  404.             $queryString = $this->requestStack->getCurrentRequest()->server->get('QUERY_STRING');
  405.             parse_str($queryString, $params);
  406.             if (isset($params['brFt'])) {
  407.                 return true;
  408.             }
  409.         }
  410.         return false;
  411.     }
  412. }