platform/src/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.                 '/type',
  96.                 'CONTENT__MISSING_RULE_TYPE_EXCEPTION'
  97.             );
  98.             $violationList->add($violation);
  99.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  100.             return;
  101.         }
  102.         try {
  103.             $ruleInstance $this->ruleConditionRegistry->getRuleInstance($type);
  104.         } catch (InvalidConditionException $e) {
  105.             $violation $this->buildViolation(
  106.                 'This {{ value }} is not a valid condition type.',
  107.                 ['{{ value }}' => $type],
  108.                 '/type',
  109.                 'CONTENT__INVALID_RULE_TYPE_EXCEPTION'
  110.             );
  111.             $violationList->add($violation);
  112.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  113.             return;
  114.         }
  115.         $value $this->getConditionValue($condition$payload);
  116.         $ruleInstance->assign($value);
  117.         $this->validateConsistence(
  118.             $ruleInstance->getConstraints(),
  119.             $value,
  120.             $violationList
  121.         );
  122.         if ($violationList->count() > 0) {
  123.             $writeException->add(new WriteConstraintViolationException($violationList$command->getPath()));
  124.         }
  125.     }
  126.     private function getConditionType(?RuleConditionEntity $condition, array $payload): ?string
  127.     {
  128.         $type $condition !== null $condition->getType() : null;
  129.         if (\array_key_exists('type'$payload)) {
  130.             $type $payload['type'];
  131.         }
  132.         return $type;
  133.     }
  134.     private function getConditionValue(?RuleConditionEntity $condition, array $payload): array
  135.     {
  136.         $value $condition !== null $condition->getValue() : [];
  137.         if (isset($payload['value']) && $payload['value'] !== null) {
  138.             $value json_decode($payload['value'], true);
  139.         }
  140.         return $value ?? [];
  141.     }
  142.     private function validateConsistence(array $fieldValidations, array $payloadConstraintViolationList $violationList): void
  143.     {
  144.         foreach ($fieldValidations as $fieldName => $validations) {
  145.             $violationList->addAll(
  146.                 $this->validator->startContext()
  147.                     ->atPath('/value/' $fieldName)
  148.                     ->validate($payload[$fieldName] ?? null$validations)
  149.                     ->getViolations()
  150.             );
  151.         }
  152.         foreach ($payload as $fieldName => $_value) {
  153.             if (!\array_key_exists($fieldName$fieldValidations) && $fieldName !== '_name') {
  154.                 $violationList->add(
  155.                     $this->buildViolation(
  156.                         'The property "{{ fieldName }}" is not allowed.',
  157.                         ['{{ fieldName }}' => $fieldName],
  158.                         '/value/' $fieldName
  159.                     )
  160.                 );
  161.             }
  162.         }
  163.     }
  164.     private function validateUpdateCommands(
  165.         array $commandQueue,
  166.         WriteException $writeException,
  167.         Context $context
  168.     ): void {
  169.         $conditions $this->getSavedConditions($commandQueue$context);
  170.         foreach ($commandQueue as $command) {
  171.             $id Uuid::fromBytesToHex($command->getPrimaryKey()['id']);
  172.             $condition $conditions->get($id);
  173.             $this->validateCondition($condition$command$writeException);
  174.         }
  175.     }
  176.     private function getSavedConditions(array $commandQueueContext $context): EntityCollection
  177.     {
  178.         $ids array_map(function ($command) {
  179.             $uuidBytes $command->getPrimaryKey()['id'];
  180.             return Uuid::fromBytesToHex($uuidBytes);
  181.         }, $commandQueue);
  182.         $criteria = new Criteria($ids);
  183.         $criteria->setLimit(null);
  184.         return $this->ruleConditionRepository->search($criteria$context)->getEntities();
  185.     }
  186.     private function buildViolation(
  187.         string $messageTemplate,
  188.         array $parameters,
  189.         ?string $propertyPath null,
  190.         ?string $code null
  191.     ): ConstraintViolationInterface {
  192.         return new ConstraintViolation(
  193.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  194.             $messageTemplate,
  195.             $parameters,
  196.             null,
  197.             $propertyPath,
  198.             null,
  199.             null,
  200.             $code
  201.         );
  202.     }
  203. }