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

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