vendor/shopware/core/Content/Rule/RuleValidator.php line 65

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Rule;
  3. use Shopware\Core\Content\Rule\Aggregate\RuleCondition\RuleConditionDefinition;
  4. use Shopware\Core\Content\Rule\Aggregate\RuleCondition\RuleConditionEntity;
  5. use Shopware\Core\Framework\Context;
  6. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Exception\UnsupportedCommandTypeException;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\DeleteCommand;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Write\WriteException;
  16. use Shopware\Core\Framework\Rule\Collector\RuleConditionRegistry;
  17. use Shopware\Core\Framework\Rule\Exception\InvalidConditionException;
  18. use Shopware\Core\Framework\Uuid\Uuid;
  19. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. use Symfony\Component\Validator\ConstraintViolation;
  22. use Symfony\Component\Validator\ConstraintViolationInterface;
  23. use Symfony\Component\Validator\ConstraintViolationList;
  24. use Symfony\Component\Validator\Validator\ValidatorInterface;
  25. class RuleValidator implements EventSubscriberInterface
  26. {
  27.     /**
  28.      * @var ValidatorInterface
  29.      */
  30.     private $validator;
  31.     /**
  32.      * @var RuleConditionRegistry
  33.      */
  34.     private $ruleConditionRegistry;
  35.     /**
  36.      * @var EntityRepositoryInterface
  37.      */
  38.     private $ruleConditionRepository;
  39.     public function __construct(
  40.         ValidatorInterface $validator,
  41.         RuleConditionRegistry $ruleConditionRegistry,
  42.         EntityRepositoryInterface $ruleConditionRepository
  43.     ) {
  44.         $this->validator $validator;
  45.         $this->ruleConditionRegistry $ruleConditionRegistry;
  46.         $this->ruleConditionRepository $ruleConditionRepository;
  47.     }
  48.     public static function getSubscribedEvents(): array
  49.     {
  50.         return [
  51.             PreWriteValidationEvent::class => 'preValidate',
  52.         ];
  53.     }
  54.     /**
  55.      * @throws UnsupportedCommandTypeException
  56.      */
  57.     public function preValidate(PreWriteValidationEvent $event): void
  58.     {
  59.         $writeException $event->getExceptions();
  60.         $commands $event->getCommands();
  61.         $updateQueue = [];
  62.         foreach ($commands as $command) {
  63.             if ($command->getDefinition()->getClass() !== RuleConditionDefinition::class) {
  64.                 continue;
  65.             }
  66.             if ($command instanceof DeleteCommand) {
  67.                 continue;
  68.             }
  69.             if ($command instanceof InsertCommand) {
  70.                 $this->validateCondition(null$command$writeException);
  71.                 continue;
  72.             }
  73.             if ($command instanceof UpdateCommand) {
  74.                 $updateQueue[] = $command;
  75.                 continue;
  76.             }
  77.             throw new UnsupportedCommandTypeException($command);
  78.         }
  79.         if (!empty($updateQueue)) {
  80.             $this->validateUpdateCommands($updateQueue$writeException$event->getContext());
  81.         }
  82.     }
  83.     private function validateCondition(
  84.         ?RuleConditionEntity $condition,
  85.         WriteCommand $command,
  86.         WriteException $writeException
  87.     ): void {
  88.         $payload $command->getPayload();
  89.         $violationList = new ConstraintViolationList();
  90.         $type $this->getConditionType($condition$payload);
  91.         if ($type === null) {
  92.             $violation $this->buildViolation(
  93.                 'Your condition is missing a type.',
  94.                 [],
  95.                 null,
  96.                 '/type',
  97.                 null,
  98.                 'CONTENT__MISSING_RULE_TYPE_EXCEPTION'
  99.             );
  100.             $violationList->add($violation);
  101.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  102.             return;
  103.         }
  104.         try {
  105.             $ruleInstance $this->ruleConditionRegistry->getRuleInstance($type);
  106.         } catch (InvalidConditionException $e) {
  107.             $violation $this->buildViolation(
  108.                 'This {{ value }} is not a valid condition type.',
  109.                 ['{{ value }}' => $type],
  110.                 null,
  111.                 '/type',
  112.                 null,
  113.                 'CONTENT__INVALID_RULE_TYPE_EXCEPTION'
  114.             );
  115.             $violationList->add($violation);
  116.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  117.             return;
  118.         }
  119.         $value $this->getConditionValue($condition$payload);
  120.         $this->validateConsistence(
  121.             $ruleInstance->getConstraints(),
  122.             $value,
  123.             $violationList
  124.         );
  125.         if ($violationList->count() > 0) {
  126.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  127.         }
  128.     }
  129.     private function getConditionType(?RuleConditionEntity $condition, array $payload): ?string
  130.     {
  131.         $type $condition !== null $condition->getType() : null;
  132.         if (array_key_exists('type'$payload)) {
  133.             $type $payload['type'];
  134.         }
  135.         return $type;
  136.     }
  137.     private function getConditionValue(?RuleConditionEntity $condition, array $payload): array
  138.     {
  139.         $value $condition !== null $condition->getValue() : [];
  140.         if (isset($payload['value']) && $payload['value'] !== null) {
  141.             $value json_decode($payload['value'], true);
  142.         }
  143.         return $value ?? [];
  144.     }
  145.     private function validateConsistence(array $fieldValidations, array $payloadConstraintViolationList $violationList): void
  146.     {
  147.         foreach ($fieldValidations as $fieldName => $validations) {
  148.             $violationList->addAll(
  149.                 $this->validator->startContext()
  150.                     ->atPath('/value/' $fieldName)
  151.                     ->validate($payload[$fieldName] ?? null$validations)
  152.                     ->getViolations()
  153.             );
  154.         }
  155.         foreach ($payload as $fieldName => $_value) {
  156.             if (!array_key_exists($fieldName$fieldValidations) && $fieldName !== '_name') {
  157.                 $violationList->add(
  158.                     $this->buildViolation(
  159.                         'The property "{{ fieldName }}" is not allowed.',
  160.                         ['{{ fieldName }}' => $fieldName],
  161.                         null,
  162.                         '/value/' $fieldName
  163.                     )
  164.                 );
  165.             }
  166.         }
  167.     }
  168.     private function validateUpdateCommands(
  169.         array $commandQueue,
  170.         WriteException $writeException,
  171.         Context $context
  172.     ): void {
  173.         $conditions $this->getSavedConditions($commandQueue$context);
  174.         foreach ($commandQueue as $command) {
  175.             $id Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
  176.             $condition $conditions->get($id);
  177.             $this->validateCondition($condition$command$writeException);
  178.         }
  179.     }
  180.     private function getSavedConditions(array $commandQueueContext $context): EntityCollection
  181.     {
  182.         $ids array_map(function ($command) {
  183.             $uuidBytes $command->getPrimaryKey()['id'];
  184.             return Uuid::fromBytesToHex($uuidBytes);
  185.         }, $commandQueue);
  186.         $criteria = new Criteria($ids);
  187.         $criteria->setLimit(null);
  188.         return $this->ruleConditionRepository->search($criteria$context)->getEntities();
  189.     }
  190.     private function buildViolation(
  191.         string $messageTemplate,
  192.         array $parameters,
  193.         $root null,
  194.         ?string $propertyPath null,
  195.         $invalidValue null,
  196.         ?string $code null
  197.     ): ConstraintViolationInterface {
  198.         return new ConstraintViolation(
  199.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  200.             $messageTemplate,
  201.             $parameters,
  202.             $root,
  203.             $propertyPath,
  204.             $invalidValue,
  205.             null,
  206.             $code
  207.         );
  208.     }
  209. }