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

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.     /**
  30.      * @var FormGroupFieldTypeRegistry
  31.      */
  32.     private $typeRegistry;
  33.     /**
  34.      * @var ValidatorInterface
  35.      */
  36.     private $validator;
  37.     /**
  38.      * @var Connection
  39.      */
  40.     private $connection;
  41.     public function __construct(
  42.         ValidatorInterface $validator,
  43.         FormGroupFieldTypeRegistry $typeRegistry,
  44.         Connection $connection
  45.     ) {
  46.         $this->validator $validator;
  47.         $this->typeRegistry $typeRegistry;
  48.         $this->connection $connection;
  49.     }
  50.     public static function getSubscribedEvents(): array
  51.     {
  52.         return [
  53.             PreWriteValidationEvent::class => 'preValidate',
  54.         ];
  55.     }
  56.     public function preValidate(PreWriteValidationEvent $event): void
  57.     {
  58.         $violationList = new ConstraintViolationList();
  59.         foreach ($event->getCommands() as $command) {
  60.             if (!($command instanceof InsertCommand || $command instanceof UpdateCommand)) {
  61.                 continue;
  62.             }
  63.             if ($command->getDefinition()->getClass() !== FormGroupFieldTranslationDefinition::class) {
  64.                 continue;
  65.             }
  66.             $violationList->addAll($this->validateConfig($command$event));
  67.         }
  68.         if ($violationList->count() > 0) {
  69.             $event->getExceptions()->add(new WriteConstraintViolationException($violationList));
  70.             return;
  71.         }
  72.         if ($event->getContext()->hasExtension(FormValidationController::IS_FORM_VALIDATION)) {
  73.             $event->getExceptions()->add(new FormValidationPassedException());
  74.         }
  75.     }
  76.     private function validateConfig(WriteCommand $commandPreWriteValidationEvent $event): ConstraintViolationList
  77.     {
  78.         $violationList = new ConstraintViolationList();
  79.         $payload $command->getPayload();
  80.         $payload['config'] = $this->decodeConfig($payload);
  81.         $typeName $this->getFieldTypeOfTranslation($command$event);
  82.         $type $this->typeRegistry->getType($typeName ?? '');
  83.         if ($type === null) {
  84.             $violationList->add(
  85.                 $this->buildViolation(
  86.                     'Field type could not be resolved and configuration could not be validated.',
  87.                     [],
  88.                     \sprintf('%s/config'$command->getPath())
  89.                 )
  90.             );
  91.             return $violationList;
  92.         }
  93.         $constraints $type->getConfigConstraints();
  94.         if (isset($constraints[self::CONFIG_ROOT_CONSTRAINTS])) {
  95.             $violationList->addAll(
  96.                 $this->validate(
  97.                     $command->getPath(),
  98.                     ['config' => $constraints[self::CONFIG_ROOT_CONSTRAINTS]],
  99.                     $payload,
  100.                     true
  101.                 )
  102.             );
  103.             unset($constraints[self::CONFIG_ROOT_CONSTRAINTS]);
  104.         }
  105.         $configPath \sprintf('%s/config'$command->getPath());
  106.         if (!empty($payload['config'])) {
  107.             $violationList->addAll($this->validate($configPath$constraints$payload['config']));
  108.             $violationList->addAll($type->validateConfig($payload['config'], $configPath));
  109.         }
  110.         return $violationList;
  111.     }
  112.     /**
  113.      * @param array<string, string> $parameters
  114.      */
  115.     private function buildViolation(
  116.         string $messageTemplate,
  117.         array $parameters,
  118.         string $propertyPath
  119.     ): ConstraintViolationInterface {
  120.         return new ConstraintViolation(
  121.             \str_replace(\array_keys($parameters), $parameters$messageTemplate),
  122.             $messageTemplate,
  123.             $parameters,
  124.             null,
  125.             $propertyPath,
  126.             null
  127.         );
  128.     }
  129.     /**
  130.      * @param array<string, Constraint|array<Constraint>|null> $fieldValidations
  131.      * @param array<string, mixed> $payload
  132.      */
  133.     private function validate(string $basePath, array $fieldValidations, array $payloadbool $allowUnknownFields false): ConstraintViolationList
  134.     {
  135.         $violations = new ConstraintViolationList();
  136.         foreach ($fieldValidations as $fieldName => $validations) {
  137.             $currentPath \sprintf('%s/%s'$basePath$fieldName);
  138.             $violations->addAll(
  139.                 $this->validator->startContext()
  140.                     ->atPath($currentPath)
  141.                     ->validate($payload[$fieldName] ?? null$validations)
  142.                     ->getViolations()
  143.             );
  144.         }
  145.         if ($allowUnknownFields) {
  146.             return $violations;
  147.         }
  148.         foreach ($payload as $fieldName => $_value) {
  149.             if (!\array_key_exists($fieldName$fieldValidations)) {
  150.                 $currentPath \sprintf('%s/%s'$basePath$fieldName);
  151.                 $violations->add(
  152.                     $this->buildViolation(
  153.                         'The property "{{ fieldName }}" is not allowed.',
  154.                         ['{{ fieldName }}' => $fieldName],
  155.                         $currentPath
  156.                     )
  157.                 );
  158.             }
  159.         }
  160.         return $violations;
  161.     }
  162.     /**
  163.      * @param array<string, string|mixed> $payload
  164.      *
  165.      * @return array<mixed>
  166.      */
  167.     private function decodeConfig(array $payload): ?array
  168.     {
  169.         if (!\array_key_exists('config'$payload) || $payload['config'] === null) {
  170.             return null;
  171.         }
  172.         $config \json_decode($payload['config'], true);
  173.         foreach ($config as $key => $val) {
  174.             if ($val === null) {
  175.                 unset($config[$key]);
  176.             }
  177.         }
  178.         return $config;
  179.     }
  180.     private function getFieldTypeOfTranslation(WriteCommand $commandPreWriteValidationEvent $event): ?string
  181.     {
  182.         foreach ($event->getCommands() as $fieldCommand) {
  183.             if (!($fieldCommand instanceof InsertCommand || $fieldCommand instanceof UpdateCommand)
  184.                 || $fieldCommand->getDefinition()->getClass() !== FormGroupFieldDefinition::class
  185.             ) {
  186.                 continue;
  187.             }
  188.             $pathDiff \str_replace($fieldCommand->getPath(), ''$command->getPath());
  189.             $matches = [];
  190.             \preg_match('/^\/translations\/[A-Fa-f0-9]{32}/'$pathDiff$matches);
  191.             if (!empty($matches)) {
  192.                 $payload $fieldCommand->getPayload();
  193.                 if (isset($payload['type'])) {
  194.                     return $payload['type'];
  195.                 }
  196.             }
  197.         }
  198.         $fieldId $command->getPrimaryKey()[\sprintf('%s_id'FormGroupFieldDefinition::ENTITY_NAME)] ?? null;
  199.         if ($fieldId === null) {
  200.             return null;
  201.         }
  202.         $query $this->connection->createQueryBuilder()
  203.             ->select('type')
  204.             ->from(FormGroupFieldDefinition::ENTITY_NAME)
  205.             ->where('id = :id')
  206.             ->setParameter('id'$fieldId)
  207.             ->setMaxResults(1)
  208.             ->execute();
  209.         if (!($query instanceof ResultStatement)) {
  210.             return null;
  211.         }
  212.         $fieldType $query->fetchColumn();
  213.         return $fieldType !== false $fieldType null;
  214.     }
  215. }