custom/plugins/SwagCmsExtensions/src/Form/Aggregate/FormGroupField/Validation/TechnicalNameValidator.php line 54

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\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\Validation\WriteConstraintViolationException;
  16. use Swag\CmsExtensions\Form\Aggregate\FormGroup\FormGroupDefinition;
  17. use Swag\CmsExtensions\Form\Aggregate\FormGroupField\FormGroupFieldDefinition;
  18. use Swag\CmsExtensions\Form\FormDefinition;
  19. use Swag\CmsExtensions\Util\Administration\FormValidationController;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. use Symfony\Component\Validator\Constraints\Regex;
  22. use Symfony\Component\Validator\ConstraintViolation;
  23. use Symfony\Component\Validator\ConstraintViolationList;
  24. use Symfony\Component\Validator\ConstraintViolationListInterface;
  25. use Symfony\Component\Validator\Validator\ValidatorInterface;
  26. class TechnicalNameValidator implements EventSubscriberInterface
  27. {
  28.     /**
  29.      * @var Connection
  30.      */
  31.     private $connection;
  32.     /**
  33.      * @var ValidatorInterface
  34.      */
  35.     private $validator;
  36.     public function __construct(Connection $connectionValidatorInterface $validator)
  37.     {
  38.         $this->connection $connection;
  39.         $this->validator $validator;
  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.         if ($event->getContext()->hasExtension(FormValidationController::IS_FORM_VALIDATION)) {
  50.             // Skips validation, because administration has own unique validation
  51.             // This is needed, because simultaneous delete and create with same technicalName can not be validated
  52.             return;
  53.         }
  54.         $violationList = new ConstraintViolationList();
  55.         foreach ($event->getCommands() as $command) {
  56.             if (!($command instanceof InsertCommand || $command instanceof UpdateCommand)) {
  57.                 continue;
  58.             }
  59.             if ($command->getDefinition()->getClass() !== FormGroupFieldDefinition::class) {
  60.                 continue;
  61.             }
  62.             $violationList->addAll($this->validateTechnicalName($command$event));
  63.         }
  64.         if ($violationList->count() > 0) {
  65.             $event->getExceptions()->add(new WriteConstraintViolationException($violationList));
  66.             return;
  67.         }
  68.     }
  69.     private function validateTechnicalName(WriteCommand $commandPreWriteValidationEvent $event): ConstraintViolationListInterface
  70.     {
  71.         $payload $command->getPayload();
  72.         if (!isset($payload['technical_name'])) {
  73.             return new ConstraintViolationList();
  74.         }
  75.         $violationList $this->validator->startContext()
  76.             ->atPath(\sprintf('%s/technicalName'$command->getPath()))
  77.             ->validate(
  78.                 $payload['technical_name'],
  79.                 new Regex([
  80.                     'pattern' => "/^[^\s]+$/i",
  81.                     'message' => 'The technical name may not include whitespace characters.',
  82.                 ])
  83.             )
  84.             ->getViolations();
  85.         $formId $this->getFormId($command$event);
  86.         if ($formId === null) {
  87.             return $violationList;
  88.         }
  89.         if ($this->isTechnicalNameUniqueInForm($command->getPrimaryKey()['id'], $payload['technical_name'], $formId$event)) {
  90.             return $violationList;
  91.         }
  92.         $messageTemplate 'The technical name (%value%) is not unique in this form.';
  93.         $parameters = ['%value%' => $payload['technical_name'] ?? 'NULL'];
  94.         $violationList->add(new ConstraintViolation(
  95.             \str_replace(\array_keys($parameters), $parameters$messageTemplate),
  96.             $messageTemplate,
  97.             $parameters,
  98.             null,
  99.             \sprintf('%s/technicalName'$command->getPath()),
  100.             null
  101.         ));
  102.         return $violationList;
  103.     }
  104.     private function getFormId(WriteCommand $commandPreWriteValidationEvent $eventbool $loadFromConnection true): ?string
  105.     {
  106.         foreach ($event->getCommands() as $groupCommand) {
  107.             if (!($groupCommand instanceof InsertCommand || $groupCommand instanceof UpdateCommand)
  108.                 || $groupCommand->getDefinition()->getClass() !== FormGroupDefinition::class
  109.             ) {
  110.                 continue;
  111.             }
  112.             $pathDiff $groupCommand->getPath();
  113.             $pos = \mb_strpos($command->getPath(), $groupCommand->getPath());
  114.             if ($pos !== false) {
  115.                 $pathDiff = \substr_replace($command->getPath(), ''$pos, \mb_strlen($groupCommand->getPath()));
  116.             }
  117.             $matches = [];
  118.             \preg_match('/^\/fields\/\d+/'$pathDiff$matches);
  119.             if (empty($matches)) {
  120.                 // are we writing a field with a group "underneath"
  121.                 $pathDiff = \str_replace($command->getPath(), ''$groupCommand->getPath());
  122.                 \preg_match('/^\/group/'$pathDiff$matches);
  123.             }
  124.             if (!empty($matches)) {
  125.                 $payload $groupCommand->getPayload();
  126.                 if (isset($payload[\sprintf('%s_id'FormDefinition::ENTITY_NAME)])) {
  127.                     return $payload[\sprintf('%s_id'FormDefinition::ENTITY_NAME)];
  128.                 }
  129.             }
  130.         }
  131.         if (!$loadFromConnection) {
  132.             return null;
  133.         }
  134.         $payload $command->getPayload();
  135.         $groupId $payload[\sprintf('%s_id'FormGroupDefinition::ENTITY_NAME)] ?? null;
  136.         if ($groupId === null) {
  137.             return null;
  138.         }
  139.         $query $this->connection->createQueryBuilder()
  140.             ->select(\sprintf('%s_id'FormDefinition::ENTITY_NAME))
  141.             ->from(FormGroupDefinition::ENTITY_NAME)
  142.             ->where('id = :id')
  143.             ->setParameter('id'$groupId)
  144.             ->setMaxResults(1)
  145.             ->execute();
  146.         if (!($query instanceof ResultStatement)) {
  147.             return null;
  148.         }
  149.         $formId $query->fetchColumn();
  150.         return $formId !== false $formId null;
  151.     }
  152.     private function isTechnicalNameUniqueInForm(string $fieldIdstring $technicalNamestring $formIdPreWriteValidationEvent $event): bool
  153.     {
  154.         $ignoredIds = [$fieldId];
  155.         foreach ($event->getCommands() as $fieldCommand) {
  156.             if ($fieldCommand->getDefinition()->getClass() !== FormGroupFieldDefinition::class) {
  157.                 continue;
  158.             }
  159.             if ($fieldId === $fieldCommand->getPrimaryKey()['id']) {
  160.                 continue;
  161.             }
  162.             if ($fieldCommand instanceof DeleteCommand) {
  163.                 $ignoredIds[] = $fieldCommand->getPrimaryKey()['id'];
  164.                 continue;
  165.             }
  166.             $otherFormId $this->getFormId($fieldCommand$eventfalse);
  167.             if ($otherFormId !== $formId) {
  168.                 continue;
  169.             }
  170.             $payload $fieldCommand->getPayload();
  171.             if (isset($payload['technical_name']) && $payload['technical_name'] === $technicalName) {
  172.                 return false;
  173.             }
  174.         }
  175.         $query $this->connection->createQueryBuilder()
  176.             ->select('field.technical_name')
  177.             ->from(FormGroupFieldDefinition::ENTITY_NAME'field')
  178.             ->leftJoin('field'FormGroupDefinition::ENTITY_NAME'formgroup', \sprintf('formgroup.id = field.%s_id'FormGroupDefinition::ENTITY_NAME))
  179.             ->where('field.technical_name = :technical_name')
  180.             ->andWhere('field.id NOT IN (:ids)')
  181.             ->andWhere(\sprintf('formgroup.%s_id = :form_id'FormDefinition::ENTITY_NAME))
  182.             ->setParameter('technical_name'$technicalName)
  183.             ->setParameter('form_id'$formId)
  184.             ->setParameter('ids'$ignoredIdsConnection::PARAM_STR_ARRAY)
  185.             ->setMaxResults(1)
  186.             ->execute();
  187.         if (!($query instanceof ResultStatement)) {
  188.             return true;
  189.         }
  190.         return !(bool) $query->fetchColumn();
  191.     }
  192. }