custom/plugins/SwagEnterpriseSearchPlatform/src/Action/Validator/ActionValidator.php line 40

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Swag\EnterpriseSearch\Action\Validator;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  8. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  9. use Swag\EnterpriseSearch\Action\ActionDefinition;
  10. use Swag\EnterpriseSearch\Action\ActionSearchTerm\ActionSearchTermDefinition;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\Validator\ConstraintViolation;
  13. use Symfony\Component\Validator\ConstraintViolationInterface;
  14. use Symfony\Component\Validator\ConstraintViolationList;
  15. class ActionValidator implements EventSubscriberInterface
  16. {
  17.     /**
  18.      * @var Connection
  19.      */
  20.     private $connection;
  21.     /**
  22.      * @internal
  23.      */
  24.     public function __construct(Connection $connection)
  25.     {
  26.         $this->connection $connection;
  27.     }
  28.     public static function getSubscribedEvents(): array
  29.     {
  30.         return [
  31.             PreWriteValidationEvent::class => 'preValidate',
  32.         ];
  33.     }
  34.     public function preValidate(PreWriteValidationEvent $event): void
  35.     {
  36.         $writeCommands $event->getCommands();
  37.         $writeActionCommands array_filter($writeCommands, function ($command) {
  38.             return $command->getDefinition()->getEntityName() === ActionDefinition::ENTITY_NAME && ($command instanceof InsertCommand || $command instanceof UpdateCommand);
  39.         });
  40.         $writeSearchTermsCommands array_filter($writeCommands, function ($command) {
  41.             return $command->getDefinition()->getEntityName() === ActionSearchTermDefinition::ENTITY_NAME && ($command instanceof InsertCommand || $command instanceof UpdateCommand);
  42.         });
  43.         if (empty($writeActionCommands) && $writeSearchTermsCommands) {
  44.             return;
  45.         }
  46.         $violationList = new ConstraintViolationList();
  47.         if (count($writeActionCommands) > 0) {
  48.             $this->validateActions($writeActionCommands$violationList);
  49.         }
  50.         if (count($writeSearchTermsCommands) > 0) {
  51.             $this->validateActionSearchTerms($writeActionCommands$writeSearchTermsCommands$violationList);
  52.         }
  53.         if ($violationList->count() > 0) {
  54.             $event->getExceptions()->add(new WriteConstraintViolationException($violationList));
  55.         }
  56.     }
  57.     private function getActions(array $actionIds): array
  58.     {
  59.         $actions $this->connection->fetchAll(
  60.             'SELECT * FROM `action` WHERE `id` IN (:ids)',
  61.             ['ids' => $actionIds],
  62.             ['ids' => Connection::PARAM_STR_ARRAY]
  63.         );
  64.         $result = [];
  65.         foreach ($actions as $action) {
  66.             $result[$action['id']] = $action;
  67.         }
  68.         return $result;
  69.     }
  70.     private function validateActions(array $commandsConstraintViolationList $violationList): void
  71.     {
  72.         $actionIds = [];
  73.         $payload = [];
  74.         $actionNames = [];
  75.         $usedActionNames = [];
  76.         foreach ($commands as $command) {
  77.             $id $command->getPrimaryKey()['id'];
  78.             $actionIds[] = $id;
  79.             $payload[$id] = $command->getPayload();
  80.             if (isset($command->getPayload()['name'])) {
  81.                 $actionNames[] = $command->getPayload()['name'];
  82.             }
  83.         }
  84.         $actions $this->getActions($actionIds);
  85.         if (count($actionNames) > 0) {
  86.             $usedActionNames $this->getUsedActionNames($actionNames);
  87.         }
  88.         foreach ($actionIds as $actionId) {
  89.             $action $actions[$actionId] ?? [];
  90.             $action array_replace_recursive($action$payload[$actionId]);
  91.             $usedNames $usedActionNames[$action['sales_channel_id']] ?? [];
  92.             $name = isset($action['name']) ? mb_strtolower($action['name']) : null;
  93.             if (isset($usedNames[$name]) && $usedNames[$name] !== $actionId) {
  94.                 $violationList->add($this->buildViolation(
  95.                     'Name "' $name '" already exists. Please provide a different name.',
  96.                     $name,
  97.                     'name',
  98.                     'ACTION_DUPLICATE_NAME_VIOLATION',
  99.                     0
  100.                 ));
  101.             }
  102.             $validFrom $action['valid_from'] ?? null;
  103.             $validTo $action['valid_to'] ?? null;
  104.             if ($validFrom === null || $validTo === null) {
  105.                 continue;
  106.             }
  107.             $dateFrom = new \DateTime($validFrom);
  108.             $dateTo = new \DateTime($validTo);
  109.             if ($dateTo $dateFrom) {
  110.                 $violationList->add($this->buildViolation(
  111.                     'Expiration Date of action must be after Start date of action',
  112.                     $validTo,
  113.                     'validTo',
  114.                     'ACTION_VALID_TO_VIOLATION',
  115.                     0
  116.                 ));
  117.             }
  118.         }
  119.     }
  120.     private function validateActionSearchTerms(array $actionCommands, array $searchTermsCommandsConstraintViolationList $violationList): void
  121.     {
  122.         $actionPayloads = [];
  123.         $actionSearchTermPayloads = [];
  124.         $terms = [];
  125.         $actionIds = [];
  126.         foreach ($actionCommands as $command) {
  127.             $id $command->getPrimaryKey()['id'];
  128.             $actionPayloads[$id] = $command->getPayload();
  129.         }
  130.         foreach ($searchTermsCommands as $command) {
  131.             $term $command->getPayload()['term'] ?? null;
  132.             if (!$term) {
  133.                 continue;
  134.             }
  135.             $terms[] = $term;
  136.             $actionIds[] = $command->getPayload()['action_id'];
  137.             $actionSearchTermPayloads[] = $command->getPayload();
  138.         }
  139.         if (count($terms) === 0) {
  140.             return;
  141.         }
  142.         $usedSearchTerms $this->getUsedSearchTerms($terms);
  143.         $actions $this->getActions($actionIds);
  144.         foreach ($actionSearchTermPayloads as $actionSearchTerm) {
  145.             $term $actionSearchTerm['term'] ? mb_strtolower($actionSearchTerm['term']) : null;
  146.             if (!$term) {
  147.                 continue;
  148.             }
  149.             $actionId $actionSearchTerm['action_id'];
  150.             $actionDb $actions[$actionId] ?? [];
  151.             $actionPayload $actionPayloads[$actionId] ?? [];
  152.             $action array_replace_recursive($actionDb$actionPayload);
  153.             $salesChannelId $action['sales_channel_id'];
  154.             $usedTerms $usedSearchTerms[$salesChannelId] ?? [];
  155.             if (in_array($term$usedTermstrue)) {
  156.                 $violationList->add($this->buildViolation(
  157.                     'Term "' $term '" already exists. Please provide a different term.',
  158.                     $term,
  159.                     'value',
  160.                     'ACTION_SEARCH_TERM_DUPLICATE_TERM_VIOLATION',
  161.                     1
  162.                 ));
  163.             }
  164.         }
  165.     }
  166.     private function buildViolation(string $message$invalidValuestring $propertyPathstring $codeint $index): ConstraintViolationInterface
  167.     {
  168.         $formattedPath "/{$index}/{$propertyPath}";
  169.         return new ConstraintViolation(
  170.             $message,
  171.             '',
  172.             [
  173.                 'value' => $invalidValue,
  174.             ],
  175.             $invalidValue,
  176.             $formattedPath,
  177.             $invalidValue,
  178.             null,
  179.             $code
  180.         );
  181.     }
  182.     private function getUsedActionNames(array $names): array
  183.     {
  184.         $actions $this->connection->fetchAll(
  185.             'SELECT id, name, sales_channel_id
  186.             FROM action
  187.             WHERE name IN (:names)',
  188.             ['names' => $names],
  189.             ['names' => Connection::PARAM_STR_ARRAY]
  190.         );
  191.         $result = [];
  192.         foreach ($actions as $action) {
  193.             $result[$action['sales_channel_id']][mb_strtolower($action['name'])] = $action['id'];
  194.         }
  195.         return $result;
  196.     }
  197.     private function getUsedSearchTerms(array $terms): array
  198.     {
  199.         $searchTerms $this->connection->fetchAll(
  200.             'SELECT action_search_term.term as term, action.sales_channel_id as salesChannelId
  201.             FROM action_search_term
  202.             JOIN action ON action_search_term.action_id = action.id
  203.             WHERE term IN (:terms)',
  204.             ['terms' => $terms],
  205.             ['terms' => Connection::PARAM_STR_ARRAY]
  206.         );
  207.         $result = [];
  208.         foreach ($searchTerms as $searchTerm) {
  209.             $result[$searchTerm['salesChannelId']][] = mb_strtolower($searchTerm['term']);
  210.         }
  211.         return $result;
  212.     }
  213. }