custom/plugins/SwagCmsExtensions/src/Form/Aggregate/FormGroupField/Validation/ConfigValidator.php line 56

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /*
  3.  * (c) shopware AG <info@shopware.com>
  4.  * For the full copyright and license information, please view the LICENSE
  5.  * file that was distributed with this source code.
  6.  */
  7. namespace Swag\CmsExtensions\Form\Aggregate\FormGroupField\Validation;
  8. use Doctrine\DBAL\Connection;
  9. use Doctrine\DBAL\Driver\ResultStatement;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  14. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  15. use Swag\CmsExtensions\Form\Aggregate\FormGroupField\FormGroupFieldDefinition;
  16. use Swag\CmsExtensions\Form\Aggregate\FormGroupField\FormGroupFieldTypeRegistry;
  17. use Swag\CmsExtensions\Form\Aggregate\FormGroupFieldTranslation\FormGroupFieldTranslationDefinition;
  18. use Swag\CmsExtensions\Util\Administration\FormValidationController;
  19. use Swag\CmsExtensions\Util\Exception\FormValidationPassedException;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. use Symfony\Component\Validator\Constraint;
  22. use Symfony\Component\Validator\ConstraintViolation;
  23. use Symfony\Component\Validator\ConstraintViolationInterface;
  24. use Symfony\Component\Validator\ConstraintViolationList;
  25. use Symfony\Component\Validator\Validator\ValidatorInterface;
  26. class ConfigValidator implements EventSubscriberInterface
  27. {
  28.     public const CONFIG_ROOT_CONSTRAINTS '_root';
  29.     private FormGroupFieldTypeRegistry $typeRegistry;
  30.     private ValidatorInterface $validator;
  31.     private Connection $connection;
  32.     public function __construct(
  33.         ValidatorInterface $validator,
  34.         FormGroupFieldTypeRegistry $typeRegistry,
  35.         Connection $connection
  36.     ) {
  37.         $this->validator $validator;
  38.         $this->typeRegistry $typeRegistry;
  39.         $this->connection $connection;
  40.     }
  41.     public static function getSubscribedEvents(): array
  42.     {
  43.         return [
  44.             PreWriteValidationEvent::class => 'preValidate',
  45.         ];
  46.     }
  47.     public function preValidate(PreWriteValidationEvent $event): void
  48.     {
  49.         $violationList = new ConstraintViolationList();
  50.         foreach ($event->getCommands() as $command) {
  51.             if (!($command instanceof InsertCommand || $command instanceof UpdateCommand)) {
  52.                 continue;
  53.             }
  54.             if ($command->getDefinition()->getClass() !== FormGroupFieldTranslationDefinition::class) {
  55.                 continue;
  56.             }
  57.             $violationList->addAll($this->validateConfig($command$event));
  58.         }
  59.         if ($violationList->count() > 0) {
  60.             $event->getExceptions()->add(new WriteConstraintViolationException($violationList));
  61.             return;
  62.         }
  63.         if ($event->getContext()->hasExtension(FormValidationController::IS_FORM_VALIDATION)) {
  64.             $event->getExceptions()->add(new FormValidationPassedException());
  65.         }
  66.     }
  67.     private function validateConfig(WriteCommand $commandPreWriteValidationEvent $event): ConstraintViolationList
  68.     {
  69.         $violationList = new ConstraintViolationList();
  70.         $payload $command->getPayload();
  71.         $payload['config'] = $this->decodeConfig($payload);
  72.         $typeName $this->getFieldTypeOfTranslation($command$event);
  73.         $type $this->typeRegistry->getType($typeName ?? '');
  74.         if ($type === null) {
  75.             $violationList->add(
  76.                 $this->buildViolation(
  77.                     'Field type could not be resolved and configuration could not be validated.',
  78.                     [],
  79.                     \sprintf('%s/config'$command->getPath())
  80.                 )
  81.             );
  82.             return $violationList;
  83.         }
  84.         $constraints $type->getConfigConstraints();
  85.         if (isset($constraints[self::CONFIG_ROOT_CONSTRAINTS])) {
  86.             $violationList->addAll(
  87.                 $this->validate(
  88.                     $command->getPath(),
  89.                     ['config' => $constraints[self::CONFIG_ROOT_CONSTRAINTS]],
  90.                     $payload,
  91.                     true
  92.                 )
  93.             );
  94.             unset($constraints[self::CONFIG_ROOT_CONSTRAINTS]);
  95.         }
  96.         $configPath = \sprintf('%s/config'$command->getPath());
  97.         if (!empty($payload['config'])) {
  98.             $violationList->addAll($this->validate($configPath$constraints$payload['config']));
  99.             $violationList->addAll($type->validateConfig($payload['config'], $configPath));
  100.         }
  101.         return $violationList;
  102.     }
  103.     /**
  104.      * @param array<string, string> $parameters
  105.      */
  106.     private function buildViolation(
  107.         string $messageTemplate,
  108.         array $parameters,
  109.         string $propertyPath
  110.     ): ConstraintViolationInterface {
  111.         return new ConstraintViolation(
  112.             \str_replace(\array_keys($parameters), $parameters$messageTemplate),
  113.             $messageTemplate,
  114.             $parameters,
  115.             null,
  116.             $propertyPath,
  117.             null
  118.         );
  119.     }
  120.     /**
  121.      * @param array<string, Constraint|array<Constraint>|null> $fieldValidations
  122.      * @param array<string, mixed> $payload
  123.      */
  124.     private function validate(string $basePath, array $fieldValidations, array $payloadbool $allowUnknownFields false): ConstraintViolationList
  125.     {
  126.         $violations = new ConstraintViolationList();
  127.         foreach ($fieldValidations as $fieldName => $validations) {
  128.             $currentPath = \sprintf('%s/%s'$basePath$fieldName);
  129.             $violations->addAll(
  130.                 $this->validator->startContext()
  131.                     ->atPath($currentPath)
  132.                     ->validate($payload[$fieldName] ?? null$validations)
  133.                     ->getViolations()
  134.             );
  135.         }
  136.         if ($allowUnknownFields) {
  137.             return $violations;
  138.         }
  139.         foreach ($payload as $fieldName => $_value) {
  140.             if (!\array_key_exists($fieldName$fieldValidations)) {
  141.                 $currentPath = \sprintf('%s/%s'$basePath$fieldName);
  142.                 $violations->add(
  143.                     $this->buildViolation(
  144.                         'The property "{{ fieldName }}" is not allowed.',
  145.                         ['{{ fieldName }}' => $fieldName],
  146.                         $currentPath
  147.                     )
  148.                 );
  149.             }
  150.         }
  151.         return $violations;
  152.     }
  153.     /**
  154.      * @param array<string, string|mixed> $payload
  155.      *
  156.      * @return array<mixed>
  157.      */
  158.     private function decodeConfig(array $payload): ?array
  159.     {
  160.         if (!\array_key_exists('config'$payload) || $payload['config'] === null) {
  161.             return null;
  162.         }
  163.         $config = \json_decode($payload['config'], true512, \JSON_THROW_ON_ERROR);
  164.         foreach ($config as $key => $val) {
  165.             if ($val === null) {
  166.                 unset($config[$key]);
  167.             }
  168.         }
  169.         return $config;
  170.     }
  171.     private function getFieldTypeOfTranslation(WriteCommand $commandPreWriteValidationEvent $event): ?string
  172.     {
  173.         foreach ($event->getCommands() as $fieldCommand) {
  174.             if (!($fieldCommand instanceof InsertCommand || $fieldCommand instanceof UpdateCommand)
  175.                 || $fieldCommand->getDefinition()->getClass() !== FormGroupFieldDefinition::class
  176.             ) {
  177.                 continue;
  178.             }
  179.             $pathDiff = \str_replace($fieldCommand->getPath(), ''$command->getPath());
  180.             $matches = [];
  181.             \preg_match('/^\/translations\/[A-Fa-f0-9]{32}/'$pathDiff$matches);
  182.             if (!empty($matches)) {
  183.                 $payload $fieldCommand->getPayload();
  184.                 if (isset($payload['type'])) {
  185.                     return $payload['type'];
  186.                 }
  187.             }
  188.         }
  189.         $fieldId $command->getPrimaryKey()[\sprintf('%s_id'FormGroupFieldDefinition::ENTITY_NAME)] ?? null;
  190.         if ($fieldId === null) {
  191.             return null;
  192.         }
  193.         $query $this->connection->createQueryBuilder()
  194.             ->select('type')
  195.             ->from(FormGroupFieldDefinition::ENTITY_NAME)
  196.             ->where('id = :id')
  197.             ->setParameter('id'$fieldId)
  198.             ->setMaxResults(1)
  199.             ->execute();
  200.         if (!($query instanceof ResultStatement)) {
  201.             return null;
  202.         }
  203.         $fieldType $query->fetchColumn();
  204.         return $fieldType !== false $fieldType null;
  205.     }
  206. }