custom/plugins/NewsletterSendinblue/src/Subscriber/ProductSubscriber.php line 257

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace NewsletterSendinblue\Subscriber;
  3. use Monolog\Logger;
  4. use NewsletterSendinblue\Service\BaseSyncService;
  5. use NewsletterSendinblue\Service\ConfigService;
  6. use NewsletterSendinblue\Traits\HelperTrait;
  7. use Shopware\Core\Content\Product\Aggregate\ProductTranslation\ProductTranslationDefinition;
  8. use Shopware\Core\Content\Product\Aggregate\ProductVisibility\ProductVisibilityDefinition;
  9. use Shopware\Core\Content\Product\ProductDefinition;
  10. use Shopware\Core\Content\Product\ProductEntity;
  11. use Shopware\Core\Content\Product\ProductEvents;
  12. use Shopware\Core\Defaults;
  13. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  14. use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent;
  16. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\ChangeSetAware;
  18. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\DeleteCommand;
  19. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  20. use Shopware\Core\Framework\Uuid\Uuid;
  21. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  22. use Symfony\Component\HttpFoundation\RequestStack;
  23. use Throwable;
  24. class ProductSubscriber implements EventSubscriberInterface
  25. {
  26.     use HelperTrait;
  27.     /** @var BaseSyncService */
  28.     private $productSyncService;
  29.     /** @var EntityRepositoryInterface */
  30.     private $systemConfigRepository;
  31.     /** @var RequestStack */
  32.     private $requestStack;
  33.     /** @var ConfigService */
  34.     private $configService;
  35.     /** @var Logger */
  36.     private $logger;
  37.     /** @var array */
  38.     private $deleteProducts = [];
  39.     /** @var bool */
  40.     private $onlyVisibilityRemoved false;
  41.     /** @var bool */
  42.     private $visibilityAlsoRemoved false;
  43.     /** @var bool  */
  44.     private $isProductSynced false;
  45.     /**
  46.      * @param BaseSyncService $productSyncService
  47.      * @param EntityRepositoryInterface $systemConfigRepository
  48.      * @param RequestStack $requestStack
  49.      * @param ConfigService $configService
  50.      * @param Logger $logger
  51.      */
  52.     public function __construct(
  53.         BaseSyncService           $productSyncService,
  54.         EntityRepositoryInterface $systemConfigRepository,
  55.         RequestStack              $requestStack,
  56.         ConfigService             $configService,
  57.         Logger                    $logger
  58.     )
  59.     {
  60.         $this->productSyncService $productSyncService;
  61.         $this->systemConfigRepository $systemConfigRepository;
  62.         $this->requestStack $requestStack;
  63.         $this->configService $configService;
  64.         $this->logger $logger;
  65.     }
  66.     /**
  67.      * @return string[]
  68.      */
  69.     public static function getSubscribedEvents(): array
  70.     {
  71.         return [
  72.             ProductEvents::PRODUCT_TRANSLATION_WRITTEN_EVENT => 'onProductTranslationWrittenEvent',
  73.             ProductEvents::PRODUCT_WRITTEN_EVENT => 'onProductWrittenEvent',
  74.             ProductEvents::PRODUCT_DELETED_EVENT => 'onProductDeletedEvent',
  75.             ProductEvents::PRODUCT_CATEGORY_WRITTEN_EVENT => 'onProductCategoryChangedEvent',
  76.             ProductEvents::PRODUCT_CATEGORY_DELETED_EVENT => 'onProductCategoryChangedEvent',
  77.             PreWriteValidationEvent::class => 'onPreWriteValidationEvent',
  78.         ];
  79.     }
  80.     /**
  81.      * @param PreWriteValidationEvent $event
  82.      * @return void
  83.      */
  84.     public function onPreWriteValidationEvent(PreWriteValidationEvent $event): void
  85.     {
  86.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  87.             return;
  88.         }
  89.         // Need to check if there are updating fields concerning to productEntity (e.g. stock, price etc).
  90.         // For example there can be only product sales channel (visibility) change etc...
  91.         $isProductFieldUpdated $this->isProductFieldUpdated($event->getCommands());
  92.         $deletableIds = [];
  93.         foreach ($event->getCommands() as $command) {
  94.             if (!$command instanceof ChangeSetAware) {
  95.                 continue;
  96.             }
  97.             // if one of the sales channels is removed from the product
  98.             if ($command->getDefinition()->getEntityName() === ProductVisibilityDefinition::ENTITY_NAME
  99.                 && get_class($command) === DeleteCommand::class
  100.             ) {
  101.                 if ($isProductFieldUpdated) {
  102.                     $this->visibilityAlsoRemoved true;
  103.                 } else {
  104.                     $this->onlyVisibilityRemoved true;
  105.                 }
  106.             }
  107.             // if product is removed
  108.             if ($command->getDefinition()->getEntityName() === ProductDefinition::ENTITY_NAME
  109.                 && get_class($command) === DeleteCommand::class
  110.                 && !empty($command->getPrimaryKey()['id'])
  111.             ) {
  112.                 $productId Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
  113.                 $deletableIds[] = $productId;
  114.                 $childrenIds $this->productSyncService->getChildrenIds($productId$event->getContext());
  115.                 $deletableIds array_merge($deletableIds$childrenIds);
  116.             }
  117.         }
  118.         foreach ($deletableIds as $id) {
  119.             $this->deleteProducts[$id] = $this->productSyncService->getEntity($id$event->getContext());
  120.         }
  121.         if (!empty($this->deleteProducts)) {
  122.             $this->productSyncService->setDeleteEntities($this->deleteProducts);
  123.         }
  124.     }
  125.     /**
  126.      * @param EntityDeletedEvent $event
  127.      * @return void
  128.      */
  129.     public function onProductDeletedEvent(EntityDeletedEvent $event): void
  130.     {
  131.         $connectionId $this->getAutoSyncConnectionId(
  132.             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
  133.             $event->getContext()
  134.         );
  135.         if (empty($connectionId)) {
  136.             return;
  137.         }
  138.         foreach ($event->getWriteResults() as $writeResult) {
  139.             $productId $writeResult->getPrimaryKey();
  140.             if (empty($productId)) {
  141.                 continue;
  142.             }
  143.             if ($writeResult->getOperation() === EntityWriteResult::OPERATION_DELETE
  144.                 && isset($this->deleteProducts[$productId])
  145.                 && $this->deleteProducts[$productId] instanceof ProductEntity
  146.             ) {
  147.                 $this->productSyncService->syncDelete($this->deleteProducts[$productId], $connectionId$event->getContext());
  148.             }
  149.         }
  150.     }
  151.     /**
  152.      * @param EntityWrittenEvent $event
  153.      * @return void
  154.      */
  155.     public function onProductWrittenEvent(EntityWrittenEvent $event): void
  156.     {
  157.         $connectionId $this->getAutoSyncConnectionId(
  158.             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
  159.             $event->getContext()
  160.         );
  161.         if (empty($connectionId)) {
  162.             return;
  163.         }
  164.         // this variable is not for "product deleted",
  165.         // it is in case when all sales channels are removed from the product
  166.         $deletableIds = [];
  167.         try {
  168.             foreach ($event->getWriteResults() as $writeResult) {
  169.                 $productId $writeResult->getPrimaryKey();
  170.                 if (empty($productId)) {
  171.                     continue;
  172.                 }
  173.                 if ($this->isRequestHasSession()
  174.                     && $writeResult->getOperation() === EntityWriteResult::OPERATION_INSERT
  175.                 ) {
  176.                     $this->productSyncService->sync($productId$connectionId$event->getContext());
  177.                     // It is to avoid calling "update" when product is created.
  178.                     // It will be checked onProductTranslationWrittenEvent
  179.                     $this->requestStack->getSession()->set('sbProductSynced'true);
  180.                 }
  181.                 if ($writeResult->getOperation() === EntityWriteResult::OPERATION_UPDATE
  182.                 ) {
  183.                     $changeSet $writeResult->getChangeSet();
  184.                     if ($changeSet
  185.                         && (int)$changeSet->getBefore('active') === 1
  186.                         && $writeResult->getChangeSet()->hasChanged('active')
  187.                         && (int)$changeSet->getAfter('active') === 0
  188.                     ) {
  189.                         $deletableIds[] = $productId;
  190.                         $childrenIds $this->productSyncService->getChildrenIds($productId$event->getContext());
  191.                         $deletableIds array_merge($deletableIds$childrenIds);
  192.                     } elseif ($this->onlyVisibilityRemoved || $this->visibilityAlsoRemoved) {
  193.                         /** @var ProductEntity $product */
  194.                         $product $this->productSyncService->getEntity($productId$event->getContext());
  195.                         if ($product->getVisibilities()->count() > && $this->onlyVisibilityRemoved) {
  196.                             $this->productSyncService->sync($productId$connectionId$event->getContext());
  197.                         }
  198.                         if ($product->getVisibilities()->count() === 0) {
  199.                             $deletableIds[] = $productId;
  200.                             $childrenIds $this->productSyncService->getChildrenIds($productId$event->getContext());
  201.                             $deletableIds array_merge($deletableIds$childrenIds);
  202.                         }
  203.                     }
  204.                 }
  205.             }
  206.             $deletableIds array_unique($deletableIds);
  207.             foreach ($deletableIds as $id) {
  208.                 $item $this->productSyncService->getEntity($id$event->getContext());
  209.                 if ($item instanceof ProductEntity
  210.                     && ($item->getVisibilities()->count() === || !$item->getActive())
  211.                 ) {
  212.                     $this->productSyncService->syncDelete($item$connectionId$event->getContext(), true);
  213.                 }
  214.             }
  215.         } catch (Throwable $e) {
  216.         }
  217.     }
  218.     /**
  219.      * @param EntityWrittenEvent $event
  220.      * @return void
  221.      */
  222.     public function onProductCategoryChangedEvent(EntityWrittenEvent $event): void
  223.     {
  224.         $connectionId $this->getAutoSyncConnectionId(
  225.             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
  226.             $event->getContext()
  227.         );
  228.         if (empty($connectionId)) {
  229.             return;
  230.         }
  231.         try {
  232.             foreach ($event->getWriteResults() as $writeResult) {
  233.                 $productId $writeResult->getPrimaryKey()['productId'];
  234.                 if (empty($productId)) {
  235.                     continue;
  236.                 }
  237.                 $product $this->productSyncService->getEntity($productId$event->getContext());
  238.                 if ($this->isRequestHasSession()
  239.                     && $product instanceof ProductEntity
  240.                     && !$this->productSyncService->isCustomFieldEmpty($product)
  241.                     && !$this->isProductSynced
  242.                 ) {
  243.                     $this->productSyncService->sync($productId$connectionId$event->getContext());
  244.                     $this->isProductSynced true;
  245.                     break;
  246.                 }
  247.             }
  248.         } catch (Throwable $e) {
  249.         }
  250.     }
  251.     /**
  252.      * @param EntityWrittenEvent $event
  253.      * @return void
  254.      */
  255.     public function onProductTranslationWrittenEvent(EntityWrittenEvent $event): void
  256.     {
  257.         $connectionId $this->getAutoSyncConnectionId(
  258.             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
  259.             $event->getContext()
  260.         );
  261.         if (empty($connectionId)) {
  262.             return;
  263.         }
  264.         foreach ($event->getWriteResults() as $writeResult) {
  265.             $productId $writeResult->getPrimaryKey()['productId'];
  266.             if (empty($productId)) {
  267.                 continue;
  268.             }
  269.             // it is for avoiding call "sync" action when custom field is saved
  270.             if (!$this->checkChangeSet($writeResult)) {
  271.                 continue;
  272.             }
  273.             if ($this->isRequestHasSession()) {
  274.                 if ($writeResult->getOperation() === EntityWriteResult::OPERATION_UPDATE
  275.                     && !$this->requestStack->getSession()->has('sbProductSynced')
  276.                 ) {
  277.                     $this->productSyncService->sync($productId$connectionId$event->getContext());
  278.                     $childrenIds $this->productSyncService->getChildrenIds($productId$event->getContext());
  279.                     foreach ($childrenIds as $id) {
  280.                         $this->productSyncService->sync($id$connectionId$event->getContext());
  281.                     }
  282.                     $this->isProductSynced true;
  283.                 }
  284.                 $this->requestStack->getSession()->remove('sbProductSynced');
  285.             }
  286.         }
  287.     }
  288.     /**
  289.      * @return bool
  290.      */
  291.     private function isRequestHasSession(): bool
  292.     {
  293.         if ($this->requestStack
  294.             && $this->requestStack->getCurrentRequest()
  295.             && $this->requestStack->getCurrentRequest()->hasSession()
  296.         ) {
  297.             return true;
  298.         }
  299.         return false;
  300.     }
  301.     /**
  302.      * @param array $commands
  303.      * @return bool
  304.      */
  305.     private function isProductFieldUpdated(array $commands): bool
  306.     {
  307.         foreach ($commands as $command) {
  308.             if ($command->getDefinition()->getEntityName() === ProductDefinition::ENTITY_NAME
  309.                 || $command->getDefinition()->getEntityName() === ProductTranslationDefinition::ENTITY_NAME
  310.             ) {
  311.                 return true;
  312.             }
  313.         }
  314.         return false;
  315.     }
  316. }