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

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