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

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