custom/plugins/CompraEsiSW6/src/Administration/Subscriber/AdministrationSubscriber.php line 78

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Compra\EsiSW6\Administration\Subscriber;
  3. use Compra\EsiSW6\Core\System\Service\ApiService;
  4. use Compra\EsiSW6\Import\Service\ImportService;
  5. use Compra\FoundationSW6\Core\System\PluginConfigService;
  6. use Doctrine\DBAL\Connection;
  7. use Doctrine\DBAL\Exception;
  8. use Shopware\Core\Framework\Context;
  9. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  10. use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  16. use Shopware\Core\Framework\Event\NestedEventCollection;
  17. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  18. use Symfony\Component\Messenger\Event\WorkerRunningEvent;
  19. use Symfony\Component\Messenger\Event\WorkerStartedEvent;
  20. class AdministrationSubscriber implements EventSubscriberInterface
  21. {
  22.     public const B2B_SUITE_PLUGIN_NAME 'SwagB2bPlatform';
  23.     protected ImportService $importService;
  24.     protected ApiService $apiService;
  25.     protected Connection $connection;
  26.     protected PluginConfigService $pluginConfigService;
  27.     protected EntityRepository $esiImportChecksumRepository;
  28.     protected EntityRepository $customerRepository;
  29.     protected EntityRepository $pluginRepository;
  30.     protected array $deletedEntities = [];
  31.     public function __construct
  32.     (
  33.         ImportService $importService,
  34.         ApiService $apiService,
  35.         Connection $connection,
  36.         PluginConfigService $pluginConfigService,
  37.         EntityRepository $esiImportChecksumRepository,
  38.         EntityRepository $customerRepository,
  39.         EntityRepository $pluginRepository
  40.     )
  41.     {
  42.         $this->importService $importService;
  43.         $this->apiService $apiService;
  44.         $this->connection $connection;
  45.         $this->pluginConfigService $pluginConfigService;
  46.         $this->esiImportChecksumRepository $esiImportChecksumRepository;
  47.         $this->customerRepository $customerRepository;
  48.         $this->pluginRepository $pluginRepository;
  49.     }
  50.     public static function getSubscribedEvents(): array
  51.     {
  52.         return [
  53.             EntityWrittenContainerEvent::class => 'onEntityWritten',
  54.             WorkerStartedEvent::class => 'onWorkerStartedOrRunning',
  55.             WorkerRunningEvent::class => 'onWorkerStartedOrRunning'
  56.         ];
  57.     }
  58.     /**
  59.      * Subscriber to check for deleted entities and remove those entries from esi_import_checksum table.
  60.      *
  61.      * @param EntityWrittenContainerEvent $entityWrittenContainerEvent
  62.      * @throws Exception
  63.      */
  64.     public function onEntityWritten(EntityWrittenContainerEvent $entityWrittenContainerEvent): void
  65.     {
  66.         /** @var NestedEventCollection|null $nestedEvents */
  67.         $nestedEvents $entityWrittenContainerEvent->getEvents();
  68.         // only collect events if nestedEvents is set
  69.         if ($nestedEvents) {
  70.             // get all nested events
  71.             $events $nestedEvents->getElements();
  72.             foreach ($events as $event) {
  73.                 if (!($event instanceof EntityDeletedEvent)) {
  74.                     // if event is no EntityDeletedEvent, continue because we don't need to proceed
  75.                     continue;
  76.                 }
  77.                 /** @var EntityDeletedEvent $event */
  78.                 // get writeResults
  79.                 /** @var EntityWriteResult[] $writeResults */
  80.                 $writeResults $event->getWriteResults();
  81.                 // iterate writeResults and collect all entities that are deleted
  82.                 foreach ($writeResults as $writeResult) {
  83.                     $entityName $writeResult->getEntityName();
  84.                     $primaryKey $writeResult->getPrimaryKey();
  85.                     if (is_array($primaryKey)) {
  86.                         $this->addDeletedEntity($entityName$primaryKey,$this->deletedEntities);
  87.                     } else {
  88.                         $this->addDeletedEntity($entityName, [
  89.                             'id' => $primaryKey
  90.                         ], $this->deletedEntities);
  91.                     }
  92.                 }
  93.             }
  94.             // only check esi checksum if current AdminUser is NOT the ESI user
  95.             if ($this->apiService->getAdminUser($entityWrittenContainerEvent->getContext()) !== ApiService::ESI_API_USER) {
  96.                 $this->checkAndDeleteChecksum($entityWrittenContainerEvent->getContext());
  97.             }
  98.             // check B2B additional delete actions
  99.             $this->checkB2BAdditionalDeleteActions($entityWrittenContainerEvent->getContext());
  100.         }
  101.     }
  102.     /**
  103.      * Subscriber to handle the stop Workers during ESI import functionality.
  104.      *
  105.      * @param WorkerStartedEvent|WorkerRunningEvent $event
  106.      */
  107.     public function onWorkerStartedOrRunning($event): void
  108.     {
  109.         $esiIsRunningFilePath $this->importService->getEsiIsRunningFilePath();
  110.         $stopWorkersDuringEsiImport = (bool) $this->pluginConfigService->getPluginConfig('stopWorkersDuringEsiImport');
  111.         if (!$stopWorkersDuringEsiImport || !file_exists($esiIsRunningFilePath)) {
  112.             // skip further processing if should not stop workers during ESI import or "esi-is-running" file doesn't exist
  113.             return;
  114.         }
  115.         if ($this->importService->checkEsiIsRunningFileIsInAllowedLifetime()) {
  116.             // file last modified in allowed lifetime - stop the Worker
  117.             $event->getWorker()->stop();
  118.         } else {
  119.             // file is older than allowed lifetime - delete the file and don't stop the Worker
  120.             $this->importService->deleteEsiIsRunningFile();
  121.         }
  122.     }
  123.     /**
  124.      * @param string $entityName
  125.      * @param array $data
  126.      * @param array $elementsArray
  127.      * @return void
  128.      */
  129.     protected function addDeletedEntity(string $entityName, array $data, array &$elementsArray): void
  130.     {
  131.         if (array_key_exists($entityName$elementsArray)) {
  132.             $elementsArray[$entityName][] = $data;
  133.         } else {
  134.             $elementsArray[$entityName] = [];
  135.             $elementsArray[$entityName][] = $data;
  136.         }
  137.     }
  138.     /**
  139.      * Helper function to check the deletedElements in esi_import_checksum table.
  140.      * If en entry exists, deletes this entry from esi_import_checksum
  141.      *
  142.      * @param $context Context
  143.      * @throws Exception
  144.      */
  145.     protected function checkAndDeleteChecksum(Context $context): void
  146.     {
  147.         if (!$this->deletedEntities) {
  148.             // skip if no deleted elements set
  149.             return;
  150.         }
  151.         $esiImportChecksumItemsToDelete = [];
  152.         // iterate all deletedElements (grouped by entity as key)
  153.         foreach ($this->deletedEntities as $entityName => $entities) {
  154.             // create querybuilder for the current entity
  155.             $queryBuilder $this->connection->createQueryBuilder();
  156.             $queryBuilder->select("LOWER(HEX(id))")
  157.                 ->from("esi_import_checksum");
  158.             // iterate all deletedElements of the current entity
  159.             foreach ($entities as $entity) {
  160.                 $queryBuilder->resetQueryPart('where');
  161.                 $queryBuilder->where("entity = :entity");
  162.                 // iterate all entity keys
  163.                 foreach ($entity as $entityKey => $keyValue) {
  164.                     $queryBuilder->andWhere("JSON_EXTRACT(entity_keys, '$.$entityKey') = '$keyValue'");
  165.                 }
  166.                 $queryBuilder->setParameter('entity'$entityName);
  167.                 $resultId $this->connection->fetchOne($queryBuilder->getSQL(), $queryBuilder->getParameters());
  168.                 if ($resultId) {
  169.                     // add found result to array of esi_import_checksum entities that should be deleted
  170.                     $esiImportChecksumItemsToDelete[] = [
  171.                         'id' => $resultId
  172.                     ];
  173.                 }
  174.             }
  175.         }
  176.         if ($esiImportChecksumItemsToDelete) {
  177.             // delete entities from esi_import_checksum
  178.             $this->esiImportChecksumRepository->delete($esiImportChecksumItemsToDelete$context);
  179.         }
  180.     }
  181.     /**
  182.      * Helper function to check, if B2B relevant data were deleted.
  183.      * If so, we need to perform additional delete actions on specific B2B entities.
  184.      *
  185.      * @param Context $context
  186.      * @throws Exception
  187.      */
  188.     protected function checkB2BAdditionalDeleteActions(Context $context): void
  189.     {
  190.         // first check if SwagB2bPlatform is installed at all
  191.         $b2bSuiteActiveCriteria = new Criteria();
  192.         $b2bSuiteActiveCriteria->addFilter(new EqualsFilter('name'self::B2B_SUITE_PLUGIN_NAME));
  193.         $b2bSuiteActiveCriteria->addFilter(new EqualsFilter('active'true));
  194.         if (!$this->pluginRepository->searchIds($b2bSuiteActiveCriteria$context)->firstId()) {
  195.             // if B2B Suite is not active
  196.             return;
  197.         }
  198.         if (array_key_exists('customer'$this->deletedEntities)) {
  199.             // perform additional delete actions for customer delete
  200.             $this->performB2BAdditionalDeleteForCustomer($context);
  201.         }
  202.     }
  203.     /**
  204.      * Helper function to perform additional B2B delete action for `customer`.
  205.      * We need to:
  206.      *  1.) delete corresponding data in b2b_store_front_auth (we can use ID of deleted customer/debtor as provider_context)
  207.      *  2.) delete all customers/contacts with same customerNumber as deleted customer (from b2b_contact_debtor customField compra_eevo_debtor_customernumber)
  208.      *
  209.      * @param Context $context
  210.      * @throws Exception
  211.      * @throws \Doctrine\DBAL\Driver\Exception
  212.      */
  213.     protected function performB2BAdditionalDeleteForCustomer(Context $context): void
  214.     {
  215.         /* 1. delete corresponding data in b2b_store_front_auth */
  216.         // first collect all IDs for searching
  217.         $deletedCustomers array_column($this->deletedEntities['customer'], 'id');
  218.         // search possible data to delete from b2b_store_front_auth
  219.         $query $this->connection->createQueryBuilder();
  220.         $query->select('id')
  221.             ->from('b2b_store_front_auth')
  222.             ->where('provider_context IN (:deletedCustomers)')
  223.             ->setParameter('deletedCustomers'$deletedCustomersConnection::PARAM_STR_ARRAY);
  224.         $customerAuthIds $query->execute()->fetchFirstColumn();
  225.         if ($customerAuthIds) {
  226.             // collect all contact/debtor IDs for this customer/debtor
  227.             $query $this->connection->createQueryBuilder();
  228.             $query->select('id')
  229.                 ->from('b2b_store_front_auth')
  230.                 ->where('context_owner_id IN (:customerAuthIds)')
  231.                 ->setParameter('customerAuthIds'$customerAuthIdsConnection::PARAM_INT_ARRAY);
  232.             $contactAuthIds $query->execute()->fetchFirstColumn();
  233.             if ($contactAuthIds) {
  234.                 // get contacts to delete with customFields
  235.                 $query $this->connection->createQueryBuilder();
  236.                 $query->select('id, auth_id, JSON_UNQUOTE(JSON_EXTRACT(compra_custom_fields, \'$.compra_eevo_debtor_customernumber\')) AS customerNumber')
  237.                     ->from('b2b_debtor_contact')
  238.                     ->where('auth_id IN (:contactAuthIds)')
  239.                     ->setParameter('contactAuthIds'$contactAuthIdsConnection::PARAM_INT_ARRAY);
  240.                 $contacts $query->execute()->fetchAllAssociative();
  241.                 // delete all corresponding data from b2b_store_front_auth
  242.                 $query $this->connection->createQueryBuilder();
  243.                 $query->delete('b2b_store_front_auth')
  244.                     ->where('id IN (:contactAuthIds)')
  245.                     ->setParameter('contactAuthIds'$contactAuthIdsConnection::PARAM_INT_ARRAY);
  246.                 $query->execute();
  247.                 // delete all corresponding data from b2b_debtor_contact
  248.                 $query $this->connection->createQueryBuilder();
  249.                 $query->delete('b2b_debtor_contact')
  250.                     ->where('auth_id IN (:contactAuthIds)')
  251.                     ->setParameter('contactAuthIds'$contactAuthIdsConnection::PARAM_INT_ARRAY);
  252.                 $query->execute();
  253.                 /* 2.) delete all customers with same customerNumber as deleted customer */
  254.                 $customerNumbersToDelete array_filter(array_column($contacts'customerNumber'));
  255.                 $customerSearchCriteria = new Criteria();
  256.                 $customerSearchCriteria->addFilter(new EqualsAnyFilter('customerNumber'$customerNumbersToDelete));
  257.                 $result $this->customerRepository->searchIds($customerSearchCriteria$context);
  258.                 if ($result->firstId()) {
  259.                     // use array_values because getData() returns the found Ids with ID as key, but for deleting we need 0,1,2... as keys
  260.                     $customersToDeleteIds array_values($this->customerRepository->searchIds($customerSearchCriteria$context)->getData());
  261.                     $this->customerRepository->delete($customersToDeleteIds$context);
  262.                 }
  263.             }
  264.         }
  265.     }
  266. }