src/Core/System/Language/TranslationValidator.php line 35

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\System\Language;
  3. use Shopware\Core\Defaults;
  4. use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
  5. use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
  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\WriteCommand;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
  10. use Shopware\Core\Framework\Log\Package;
  11. use Shopware\Core\Framework\Uuid\Uuid;
  12. use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
  13. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  14. use Symfony\Component\Validator\ConstraintViolation;
  15. use Symfony\Component\Validator\ConstraintViolationInterface;
  16. use Symfony\Component\Validator\ConstraintViolationList;
  17. /**
  18.  * @internal
  19.  */
  20. #[Package('core')]
  21. class TranslationValidator implements EventSubscriberInterface
  22. {
  23.     final public const VIOLATION_DELETE_SYSTEM_TRANSLATION 'delete-system-translation-violation';
  24.     public static function getSubscribedEvents(): array
  25.     {
  26.         return [
  27.             PreWriteValidationEvent::class => 'preValidate',
  28.         ];
  29.     }
  30.     public function preValidate(PreWriteValidationEvent $event): void
  31.     {
  32.         if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
  33.             return;
  34.         }
  35.         $violations = new ConstraintViolationList();
  36.         $violations->addAll($this->getDeletedSystemTranslationViolations($event->getCommands()));
  37.         if ($violations->count()) {
  38.             $event->getExceptions()->add(new WriteConstraintViolationException($violations));
  39.         }
  40.     }
  41.     /**
  42.      * @param list<WriteCommand> $writeCommands
  43.      */
  44.     private function getDeletedSystemTranslationViolations(array $writeCommands): ConstraintViolationList
  45.     {
  46.         $violations = new ConstraintViolationList();
  47.         foreach ($writeCommands as $writeCommand) {
  48.             if (!$writeCommand instanceof DeleteCommand || $writeCommand instanceof CascadeDeleteCommand) {
  49.                 continue;
  50.             }
  51.             $pk $writeCommand->getPrimaryKey();
  52.             if (!isset($pk['language_id'])) {
  53.                 continue;
  54.             }
  55.             $def $writeCommand->getDefinition();
  56.             if (!$def instanceof EntityTranslationDefinition) {
  57.                 continue;
  58.             }
  59.             if (Uuid::fromBytesToHex($pk['language_id']) !== Defaults::LANGUAGE_SYSTEM) {
  60.                 continue;
  61.             }
  62.             $fks $this->getFkFields($def);
  63.             $id Uuid::fromBytesToHex($pk[$fks['id']->getStorageName()]);
  64.             $violations->add(
  65.                 $this->buildViolation(
  66.                     'Cannot delete system translation',
  67.                     ['{{ id }}' => $id],
  68.                     '/' $id '/translations/' Defaults::LANGUAGE_SYSTEM,
  69.                     [$idDefaults::LANGUAGE_SYSTEM],
  70.                     self::VIOLATION_DELETE_SYSTEM_TRANSLATION
  71.                 )
  72.             );
  73.         }
  74.         return $violations;
  75.     }
  76.     /**
  77.      * @return FkField[]
  78.      */
  79.     private function getFkFields(EntityTranslationDefinition $definition): array
  80.     {
  81.         $rootEntity $definition->getParentDefinition();
  82.         $idStorageName $rootEntity->getEntityName() . '_id';
  83.         $versionIdStorageName $rootEntity->getEntityName() . '_version_id';
  84.         $pks $definition->getPrimaryKeys();
  85.         $idField $pks->getByStorageName($idStorageName);
  86.         if (!$idField || !$idField instanceof FkField) {
  87.             throw new \RuntimeException(sprintf('`%s` primary key should have column `%s`'$definition->getEntityName(), $idStorageName));
  88.         }
  89.         $fields = [
  90.             'id' => $idField,
  91.         ];
  92.         $versionIdField $pks->getByStorageName($versionIdStorageName);
  93.         if ($versionIdField && $versionIdField instanceof FkField) {
  94.             $fields['version'] = $versionIdField;
  95.         }
  96.         return $fields;
  97.     }
  98.     /**
  99.      * @param array<string, string> $parameters
  100.      * @param array<mixed>|null $invalidValue
  101.      */
  102.     private function buildViolation(
  103.         string $messageTemplate,
  104.         array $parameters,
  105.         ?string $propertyPath null,
  106.         ?array $invalidValue null,
  107.         ?string $code null
  108.     ): ConstraintViolationInterface {
  109.         return new ConstraintViolation(
  110.             str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
  111.             $messageTemplate,
  112.             $parameters,
  113.             null,
  114.             $propertyPath,
  115.             $invalidValue,
  116.             null,
  117.             $code
  118.         );
  119.     }
  120. }