vendor/shopware/core/System/Language/LanguageValidator.php line 58

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\System\Language;
  3. use Doctrine\DBAL\Connection;
  4. use Doctrine\DBAL\FetchMode;
  5. use Shopware\Core\Defaults;
  6. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\CascadeDeleteCommand;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\DeleteCommand;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
  10. use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PostWriteValidationEvent;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  13. use Shopware\Core\Framework\Uuid\Uuid;
  14. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  15. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  16. use Symfony\Component\Validator\ConstraintViolation;
  17. use Symfony\Component\Validator\ConstraintViolationInterface;
  18. use Symfony\Component\Validator\ConstraintViolationList;
  19. /**
  20.  * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  21.  */
  22. class LanguageValidator implements EventSubscriberInterface
  23. {
  24.     public const VIOLATION_PARENT_HAS_PARENT 'parent_has_parent_violation';
  25.     public const VIOLATION_CODE_REQUIRED_FOR_ROOT_LANGUAGE 'code_required_for_root_language';
  26.     public const VIOLATION_DELETE_DEFAULT_LANGUAGE 'delete_default_language_violation';
  27.     public const VIOLATION_DEFAULT_LANGUAGE_PARENT 'default_language_parent_violation';
  28.     /**
  29.      * @deprecated tag:v6.5.0 - const will be removed in v6.5.0
  30.      */
  31.     public const DEFAULT_LANGUAGES = [Defaults::LANGUAGE_SYSTEM];
  32.     private Connection $connection;
  33.     /**
  34.      * @internal
  35.      */
  36.     public function __construct(Connection $connection)
  37.     {
  38.         $this->connection $connection;
  39.     }
  40.     public static function getSubscribedEvents(): array
  41.     {
  42.         return [
  43.             PreWriteValidationEvent::class => 'preValidate',
  44.             PostWriteValidationEvent::class => 'postValidate',
  45.         ];
  46.     }
  47.     public function postValidate(PostWriteValidationEvent $event): void
  48.     {
  49.         $commands $event->getCommands();
  50.         $affectedIds $this->getAffectedIds($commands);
  51.         if (\count($affectedIds) === 0) {
  52.             return;
  53.         }
  54.         $violations = new ConstraintViolationList();
  55.         $violations->addAll($this->getInheritanceViolations($affectedIds));
  56.         $violations->addAll($this->getMissingTranslationCodeViolations($affectedIds));
  57.         if ($violations->count() > 0) {
  58.             $event->getExceptions()->add(new WriteConstraintViolationException($violations));
  59.         }
  60.     }
  61.     public function preValidate(PreWriteValidationEvent $event): void
  62.     {
  63.         $commands $event->getCommands();
  64.         foreach ($commands as $command) {
  65.             $violations = new ConstraintViolationList();
  66.             if ($command instanceof CascadeDeleteCommand || $command->getDefinition()->getClass() !== LanguageDefinition::class) {
  67.                 continue;
  68.             }
  69.             $pk $command->getPrimaryKey();
  70.             $id mb_strtolower(Uuid::fromBytesToHex($pk['id']));
  71.             if ($command instanceof DeleteCommand && $id === Defaults::LANGUAGE_SYSTEM) {
  72.                 $violations->add(
  73.                     $this->buildViolation(
  74.                         'The default language {{ id }} cannot be deleted.',
  75.                         ['{{ id }}' => $id],
  76.                         '/' $id,
  77.                         $id,
  78.                         self::VIOLATION_DELETE_DEFAULT_LANGUAGE
  79.                     )
  80.                 );
  81.             }
  82.             if ($command instanceof UpdateCommand && $id === Defaults::LANGUAGE_SYSTEM) {
  83.                 $payload $command->getPayload();
  84.                 if (\array_key_exists('parent_id'$payload) && $payload['parent_id'] !== null) {
  85.                     $violations->add(
  86.                         $this->buildViolation(
  87.                             'The default language {{ id }} cannot inherit from another language.',
  88.                             ['{{ id }}' => $id],
  89.                             '/parentId',
  90.                             $payload['parent_id'],
  91.                             self::VIOLATION_DEFAULT_LANGUAGE_PARENT
  92.                         )
  93.                     );
  94.                 }
  95.             }
  96.             if ($violations->count() > 0) {
  97.                 $event->getExceptions()->add(new WriteConstraintViolationException($violations$command->getPath()));
  98.             }
  99.         }
  100.     }
  101.     /**
  102.      * @param array<string> $affectedIds
  103.      */
  104.     private function getInheritanceViolations(array $affectedIds): ConstraintViolationList
  105.     {
  106.         $statement $this->connection->executeQuery(
  107.             'SELECT child.id
  108.              FROM language child
  109.              INNER JOIN language parent ON parent.id = child.parent_id
  110.              WHERE (child.id IN (:ids) OR child.parent_id IN (:ids))
  111.              AND parent.parent_id IS NOT NULL',
  112.             ['ids' => $affectedIds],
  113.             ['ids' => Connection::PARAM_STR_ARRAY]
  114.         );
  115.         $ids $statement->fetchAll(FetchMode::COLUMN);
  116.         $violations = new ConstraintViolationList();
  117.         foreach ($ids as $binId) {
  118.             $id Uuid::fromBytesToHex($binId);
  119.             $violations->add(
  120.                 $this->buildViolation(
  121.                     'Language inheritance limit for the child {{ id }} exceeded. A Language must not be nested deeper than one level.',
  122.                     ['{{ id }}' => $id],
  123.                     '/' $id '/parentId',
  124.                     $id,
  125.                     self::VIOLATION_PARENT_HAS_PARENT
  126.                 )
  127.             );
  128.         }
  129.         return $violations;
  130.     }
  131.     /**
  132.      * @param array<string> $affectedIds
  133.      */
  134.     private function getMissingTranslationCodeViolations(array $affectedIds): ConstraintViolationList
  135.     {
  136.         $statement $this->connection->executeQuery(
  137.             'SELECT lang.id
  138.              FROM language lang
  139.              LEFT JOIN locale l ON lang.translation_code_id = l.id
  140.              WHERE l.id IS NULL # no translation code
  141.              AND lang.parent_id IS NULL # root
  142.              AND lang.id IN (:ids)',
  143.             ['ids' => $affectedIds],
  144.             ['ids' => Connection::PARAM_STR_ARRAY]
  145.         );
  146.         $ids $statement->fetchAll(FetchMode::COLUMN);
  147.         $violations = new ConstraintViolationList();
  148.         foreach ($ids as $binId) {
  149.             $id Uuid::fromBytesToHex($binId);
  150.             $violations->add(
  151.                 $this->buildViolation(
  152.                     'Root language {{ id }} requires a translation code',
  153.                     ['{{ id }}' => $id],
  154.                     '/' $id '/translationCodeId',
  155.                     $id,
  156.                     self::VIOLATION_CODE_REQUIRED_FOR_ROOT_LANGUAGE
  157.                 )
  158.             );
  159.         }
  160.         return $violations;
  161.     }
  162.     /**
  163.      * @param WriteCommand[] $commands
  164.      *
  165.      * @return array<string>
  166.      */
  167.     private function getAffectedIds(array $commands): array
  168.     {
  169.         $ids = [];
  170.         foreach ($commands as $command) {
  171.             if ($command->getDefinition()->getClass() !== LanguageDefinition::class) {
  172.                 continue;
  173.             }
  174.             if ($command instanceof InsertCommand || $command instanceof UpdateCommand) {
  175.                 $ids[] = $command->getPrimaryKey()['id'];
  176.             }
  177.         }
  178.         return $ids;
  179.     }
  180.     /**
  181.      * @param array<string, string> $parameters
  182.      */
  183.     private function buildViolation(
  184.         string $messageTemplate,
  185.         array $parameters,
  186.         ?string $propertyPath null,
  187.         ?string $invalidValue null,
  188.         ?string $code null
  189.     ): ConstraintViolationInterface {
  190.         return new ConstraintViolation(
  191.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  192.             $messageTemplate,
  193.             $parameters,
  194.             null,
  195.             $propertyPath,
  196.             $invalidValue,
  197.             null,
  198.             $code
  199.         );
  200.     }
  201. }