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

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