- <?php declare(strict_types=1);
- namespace NewsletterSendinblue\Subscriber;
- use Monolog\Logger;
- use NewsletterSendinblue\Service\BaseSyncService;
- use NewsletterSendinblue\Service\ConfigService;
- use NewsletterSendinblue\Traits\HelperTrait;
- use Shopware\Core\Content\Product\Aggregate\ProductTranslation\ProductTranslationDefinition;
- use Shopware\Core\Content\Product\Aggregate\ProductVisibility\ProductVisibilityDefinition;
- use Shopware\Core\Content\Product\ProductDefinition;
- use Shopware\Core\Content\Product\ProductEntity;
- use Shopware\Core\Content\Product\ProductEvents;
- use Shopware\Core\Defaults;
- use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
- use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
- use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent;
- use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
- use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\ChangeSetAware;
- use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\DeleteCommand;
- use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
- use Shopware\Core\Framework\Uuid\Uuid;
- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\HttpFoundation\RequestStack;
- use Throwable;
- class ProductSubscriber implements EventSubscriberInterface
- {
-     use HelperTrait;
-     /** @var BaseSyncService */
-     private $productSyncService;
-     /** @var EntityRepositoryInterface */
-     private $systemConfigRepository;
-     /** @var RequestStack */
-     private $requestStack;
-     /** @var ConfigService */
-     private $configService;
-     /** @var Logger */
-     private $logger;
-     /** @var array */
-     private $deleteProducts = [];
-     /** @var bool */
-     private $onlyVisibilityRemoved = false;
-     /** @var bool */
-     private $visibilityAlsoRemoved = false;
-     /** @var bool  */
-     private $isProductSynced = false;
-     /**
-      * @param BaseSyncService $productSyncService
-      * @param EntityRepositoryInterface $systemConfigRepository
-      * @param RequestStack $requestStack
-      * @param ConfigService $configService
-      * @param Logger $logger
-      */
-     public function __construct(
-         BaseSyncService           $productSyncService,
-         EntityRepositoryInterface $systemConfigRepository,
-         RequestStack              $requestStack,
-         ConfigService             $configService,
-         Logger                    $logger
-     )
-     {
-         $this->productSyncService = $productSyncService;
-         $this->systemConfigRepository = $systemConfigRepository;
-         $this->requestStack = $requestStack;
-         $this->configService = $configService;
-         $this->logger = $logger;
-     }
-     /**
-      * @return string[]
-      */
-     public static function getSubscribedEvents(): array
-     {
-         return [
-             ProductEvents::PRODUCT_TRANSLATION_WRITTEN_EVENT => 'onProductTranslationWrittenEvent',
-             ProductEvents::PRODUCT_WRITTEN_EVENT => 'onProductWrittenEvent',
-             ProductEvents::PRODUCT_DELETED_EVENT => 'onProductDeletedEvent',
-             ProductEvents::PRODUCT_CATEGORY_WRITTEN_EVENT => 'onProductCategoryChangedEvent',
-             ProductEvents::PRODUCT_CATEGORY_DELETED_EVENT => 'onProductCategoryChangedEvent',
-             PreWriteValidationEvent::class => 'onPreWriteValidationEvent',
-         ];
-     }
-     /**
-      * @param PreWriteValidationEvent $event
-      * @return void
-      */
-     public function onPreWriteValidationEvent(PreWriteValidationEvent $event): void
-     {
-         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
-             return;
-         }
-         // Need to check if there are updating fields concerning to productEntity (e.g. stock, price etc).
-         // For example there can be only product sales channel (visibility) change etc...
-         $isProductFieldUpdated = $this->isProductFieldUpdated($event->getCommands());
-         $deletableIds = [];
-         foreach ($event->getCommands() as $command) {
-             if (!$command instanceof ChangeSetAware) {
-                 continue;
-             }
-             // if one of the sales channels is removed from the product
-             if ($command->getDefinition()->getEntityName() === ProductVisibilityDefinition::ENTITY_NAME
-                 && get_class($command) === DeleteCommand::class
-             ) {
-                 if ($isProductFieldUpdated) {
-                     $this->visibilityAlsoRemoved = true;
-                 } else {
-                     $this->onlyVisibilityRemoved = true;
-                 }
-             }
-             // if product is removed
-             if ($command->getDefinition()->getEntityName() === ProductDefinition::ENTITY_NAME
-                 && get_class($command) === DeleteCommand::class
-                 && !empty($command->getPrimaryKey()['id'])
-             ) {
-                 $productId = Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
-                 $deletableIds[] = $productId;
-                 $childrenIds = $this->productSyncService->getChildrenIds($productId, $event->getContext());
-                 $deletableIds = array_merge($deletableIds, $childrenIds);
-             }
-         }
-         foreach ($deletableIds as $id) {
-             $this->deleteProducts[$id] = $this->productSyncService->getEntity($id, $event->getContext());
-         }
-         if (!empty($this->deleteProducts)) {
-             $this->productSyncService->setDeleteEntities($this->deleteProducts);
-         }
-     }
-     /**
-      * @param EntityDeletedEvent $event
-      * @return void
-      */
-     public function onProductDeletedEvent(EntityDeletedEvent $event): void
-     {
-         $connectionId = $this->getAutoSyncConnectionId(
-             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
-             $event->getContext()
-         );
-         if (empty($connectionId)) {
-             return;
-         }
-         foreach ($event->getWriteResults() as $writeResult) {
-             $productId = $writeResult->getPrimaryKey();
-             if (empty($productId)) {
-                 continue;
-             }
-             if ($writeResult->getOperation() === EntityWriteResult::OPERATION_DELETE
-                 && isset($this->deleteProducts[$productId])
-                 && $this->deleteProducts[$productId] instanceof ProductEntity
-             ) {
-                 $this->productSyncService->syncDelete($this->deleteProducts[$productId], $connectionId, $event->getContext());
-             }
-         }
-     }
-     /**
-      * @param EntityWrittenEvent $event
-      * @return void
-      */
-     public function onProductWrittenEvent(EntityWrittenEvent $event): void
-     {
-         $connectionId = $this->getAutoSyncConnectionId(
-             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
-             $event->getContext()
-         );
-         if (empty($connectionId)) {
-             return;
-         }
-         // this variable is not for "product deleted",
-         // it is in case when all sales channels are removed from the product
-         $deletableIds = [];
-         try {
-             foreach ($event->getWriteResults() as $writeResult) {
-                 $productId = $writeResult->getPrimaryKey();
-                 if (empty($productId)) {
-                     continue;
-                 }
-                 if ($this->isRequestHasSession()
-                     && $writeResult->getOperation() === EntityWriteResult::OPERATION_INSERT
-                 ) {
-                     $this->productSyncService->sync($productId, $connectionId, $event->getContext());
-                     // It is to avoid calling "update" when product is created.
-                     // It will be checked onProductTranslationWrittenEvent
-                     $this->requestStack->getSession()->set('sbProductSynced', true);
-                 }
-                 if ($writeResult->getOperation() === EntityWriteResult::OPERATION_UPDATE
-                 ) {
-                     $changeSet = $writeResult->getChangeSet();
-                     if ($changeSet
-                         && (int)$changeSet->getBefore('active') === 1
-                         && $writeResult->getChangeSet()->hasChanged('active')
-                         && (int)$changeSet->getAfter('active') === 0
-                     ) {
-                         $deletableIds[] = $productId;
-                         $childrenIds = $this->productSyncService->getChildrenIds($productId, $event->getContext());
-                         $deletableIds = array_merge($deletableIds, $childrenIds);
-                     } elseif ($this->onlyVisibilityRemoved || $this->visibilityAlsoRemoved) {
-                         /** @var ProductEntity $product */
-                         $product = $this->productSyncService->getEntity($productId, $event->getContext());
-                         if ($product->getVisibilities()->count() > 0 && $this->onlyVisibilityRemoved) {
-                             $this->productSyncService->sync($productId, $connectionId, $event->getContext());
-                         }
-                         if ($product->getVisibilities()->count() === 0) {
-                             $deletableIds[] = $productId;
-                             $childrenIds = $this->productSyncService->getChildrenIds($productId, $event->getContext());
-                             $deletableIds = array_merge($deletableIds, $childrenIds);
-                         }
-                     }
-                 }
-             }
-             $deletableIds = array_unique($deletableIds);
-             foreach ($deletableIds as $id) {
-                 $item = $this->productSyncService->getEntity($id, $event->getContext());
-                 if ($item instanceof ProductEntity
-                     && ($item->getVisibilities()->count() === 0 || !$item->getActive())
-                 ) {
-                     $this->productSyncService->syncDelete($item, $connectionId, $event->getContext(), true);
-                 }
-             }
-         } catch (Throwable $e) {
-         }
-     }
-     /**
-      * @param EntityWrittenEvent $event
-      * @return void
-      */
-     public function onProductCategoryChangedEvent(EntityWrittenEvent $event): void
-     {
-         $connectionId = $this->getAutoSyncConnectionId(
-             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
-             $event->getContext()
-         );
-         if (empty($connectionId)) {
-             return;
-         }
-         try {
-             foreach ($event->getWriteResults() as $writeResult) {
-                 $productId = $writeResult->getPrimaryKey()['productId'];
-                 if (empty($productId)) {
-                     continue;
-                 }
-                 $product = $this->productSyncService->getEntity($productId, $event->getContext());
-                 if ($this->isRequestHasSession()
-                     && $product instanceof ProductEntity
-                     && !$this->productSyncService->isCustomFieldEmpty($product)
-                     && !$this->isProductSynced
-                 ) {
-                     $this->productSyncService->sync($productId, $connectionId, $event->getContext());
-                     $this->isProductSynced = true;
-                     break;
-                 }
-             }
-         } catch (Throwable $e) {
-         }
-     }
-     /**
-      * @param EntityWrittenEvent $event
-      * @return void
-      */
-     public function onProductTranslationWrittenEvent(EntityWrittenEvent $event): void
-     {
-         $connectionId = $this->getAutoSyncConnectionId(
-             ConfigService::CONFIG_IS_PRODUCTS_AUTO_SYNC_ENABLED,
-             $event->getContext()
-         );
-         if (empty($connectionId)) {
-             return;
-         }
-         foreach ($event->getWriteResults() as $writeResult) {
-             $productId = $writeResult->getPrimaryKey()['productId'];
-             if (empty($productId)) {
-                 continue;
-             }
-             // it is for avoiding call "sync" action when custom field is saved
-             if (!$this->checkChangeSet($writeResult)) {
-                 continue;
-             }
-             if ($this->isRequestHasSession()) {
-                 if ($writeResult->getOperation() === EntityWriteResult::OPERATION_UPDATE
-                     && !$this->requestStack->getSession()->has('sbProductSynced')
-                 ) {
-                     $this->productSyncService->sync($productId, $connectionId, $event->getContext());
-                     $childrenIds = $this->productSyncService->getChildrenIds($productId, $event->getContext());
-                     foreach ($childrenIds as $id) {
-                         $this->productSyncService->sync($id, $connectionId, $event->getContext());
-                     }
-                     $this->isProductSynced = true;
-                 }
-                 $this->requestStack->getSession()->remove('sbProductSynced');
-             }
-         }
-     }
-     /**
-      * @return bool
-      */
-     private function isRequestHasSession(): bool
-     {
-         if ($this->requestStack
-             && $this->requestStack->getCurrentRequest()
-             && $this->requestStack->getCurrentRequest()->hasSession()
-         ) {
-             return true;
-         }
-         return false;
-     }
-     /**
-      * @param array $commands
-      * @return bool
-      */
-     private function isProductFieldUpdated(array $commands): bool
-     {
-         foreach ($commands as $command) {
-             if ($command->getDefinition()->getEntityName() === ProductDefinition::ENTITY_NAME
-                 || $command->getDefinition()->getEntityName() === ProductTranslationDefinition::ENTITY_NAME
-             ) {
-                 return true;
-             }
-         }
-         return false;
-     }
- }
-