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

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.     private function getInheritanceViolations(array $affectedIds): ConstraintViolationList
  101.     {
  102.         $statement $this->connection->executeQuery(
  103.             'SELECT child.id
  104.              FROM language child
  105.              INNER JOIN language parent ON parent.id = child.parent_id
  106.              WHERE (child.id IN (:ids) OR child.parent_id IN (:ids))
  107.              AND parent.parent_id IS NOT NULL',
  108.             ['ids' => $affectedIds],
  109.             ['ids' => Connection::PARAM_STR_ARRAY]
  110.         );
  111.         $ids $statement->fetchAll(FetchMode::COLUMN);
  112.         $violations = new ConstraintViolationList();
  113.         foreach ($ids as $binId) {
  114.             $id Uuid::fromBytesToHex($binId);
  115.             $violations->add(
  116.                 $this->buildViolation(
  117.                     'Language inheritance limit for the child {{ id }} exceeded. A Language must not be nested deeper than one level.',
  118.                     ['{{ id }}' => $id],
  119.                     null,
  120.                     '/' $id '/parentId',
  121.                     $id,
  122.                     self::VIOLATION_PARENT_HAS_PARENT
  123.                 )
  124.             );
  125.         }
  126.         return $violations;
  127.     }
  128.     private function getMissingTranslationCodeViolations(array $affectedIds): ConstraintViolationList
  129.     {
  130.         $statement $this->connection->executeQuery(
  131.             'SELECT lang.id
  132.              FROM language lang
  133.              LEFT JOIN locale l ON lang.translation_code_id = l.id
  134.              WHERE l.id IS NULL # no translation code
  135.              AND lang.parent_id IS NULL # root
  136.              AND lang.id IN (:ids)',
  137.             ['ids' => $affectedIds],
  138.             ['ids' => Connection::PARAM_STR_ARRAY]
  139.         );
  140.         $ids $statement->fetchAll(FetchMode::COLUMN);
  141.         $violations = new ConstraintViolationList();
  142.         foreach ($ids as $binId) {
  143.             $id Uuid::fromBytesToHex($binId);
  144.             $violations->add(
  145.                 $this->buildViolation(
  146.                     'Root language {{ id }} requires a translation code',
  147.                     ['{{ id }}' => $id],
  148.                     null,
  149.                     '/' $id '/translationCodeId',
  150.                     $id,
  151.                     self::VIOLATION_CODE_REQUIRED_FOR_ROOT_LANGUAGE
  152.                 )
  153.             );
  154.         }
  155.         return $violations;
  156.     }
  157.     /**
  158.      * @param WriteCommand[] $commands
  159.      *
  160.      * @return string[]
  161.      */
  162.     private function getAffectedIds(array $commands): array
  163.     {
  164.         $ids = [];
  165.         foreach ($commands as $command) {
  166.             if ($command->getDefinition()->getClass() !== LanguageDefinition::class) {
  167.                 continue;
  168.             }
  169.             if ($command instanceof InsertCommand || $command instanceof UpdateCommand) {
  170.                 $ids[] = $command->getPrimaryKey()['id'];
  171.             }
  172.         }
  173.         return $ids;
  174.     }
  175.     private function buildViolation(
  176.         string $messageTemplate,
  177.         array $parameters,
  178.         $root null,
  179.         ?string $propertyPath null,
  180.         ?string $invalidValue null,
  181.         ?string $code null
  182.     ): ConstraintViolationInterface {
  183.         return new ConstraintViolation(
  184.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  185.             $messageTemplate,
  186.             $parameters,
  187.             $root,
  188.             $propertyPath,
  189.             $invalidValue,
  190.             null,
  191.             $code
  192.         );
  193.     }
  194. }