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

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