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

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