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

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