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.         $ruleInstance->assign($value);
  121.         $this->validateConsistence(
  122.             $ruleInstance->getConstraints(),
  123.             $value,
  124.             $violationList
  125.         );
  126.         if ($violationList->count() > 0) {
  127.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  128.         }
  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.     private function getConditionValue(?RuleConditionEntity $condition, array $payload): array
  139.     {
  140.         $value $condition !== null $condition->getValue() : [];
  141.         if (isset($payload['value']) && $payload['value'] !== null) {
  142.             $value json_decode($payload['value'], true);
  143.         }
  144.         return $value ?? [];
  145.     }
  146.     private function validateConsistence(array $fieldValidations, array $payloadConstraintViolationList $violationList): void
  147.     {
  148.         foreach ($fieldValidations as $fieldName => $validations) {
  149.             $violationList->addAll(
  150.                 $this->validator->startContext()
  151.                     ->atPath('/value/' $fieldName)
  152.                     ->validate($payload[$fieldName] ?? null$validations)
  153.                     ->getViolations()
  154.             );
  155.         }
  156.         foreach ($payload as $fieldName => $_value) {
  157.             if (!\array_key_exists($fieldName$fieldValidations) && $fieldName !== '_name') {
  158.                 $violationList->add(
  159.                     $this->buildViolation(
  160.                         'The property "{{ fieldName }}" is not allowed.',
  161.                         ['{{ fieldName }}' => $fieldName],
  162.                         null,
  163.                         '/value/' $fieldName
  164.                     )
  165.                 );
  166.             }
  167.         }
  168.     }
  169.     private function validateUpdateCommands(
  170.         array $commandQueue,
  171.         WriteException $writeException,
  172.         Context $context
  173.     ): void {
  174.         $conditions $this->getSavedConditions($commandQueue$context);
  175.         foreach ($commandQueue as $command) {
  176.             $id Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
  177.             $condition $conditions->get($id);
  178.             $this->validateCondition($condition$command$writeException);
  179.         }
  180.     }
  181.     private function getSavedConditions(array $commandQueueContext $context): EntityCollection
  182.     {
  183.         $ids array_map(function ($command) {
  184.             $uuidBytes $command->getPrimaryKey()['id'];
  185.             return Uuid::fromBytesToHex($uuidBytes);
  186.         }, $commandQueue);
  187.         $criteria = new Criteria($ids);
  188.         $criteria->setLimit(null);
  189.         return $this->ruleConditionRepository->search($criteria$context)->getEntities();
  190.     }
  191.     private function buildViolation(
  192.         string $messageTemplate,
  193.         array $parameters,
  194.         $root null,
  195.         ?string $propertyPath null,
  196.         $invalidValue null,
  197.         ?string $code null
  198.     ): ConstraintViolationInterface {
  199.         return new ConstraintViolation(
  200.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  201.             $messageTemplate,
  202.             $parameters,
  203.             $root,
  204.             $propertyPath,
  205.             $invalidValue,
  206.             null,
  207.             $code
  208.         );
  209.     }
  210. }