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

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