<?php declare(strict_types=1);
namespace Shopware\Core\System\Language;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\CascadeDeleteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\DeleteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\WriteCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
/**
* @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
*/
#[Package('core')]
class TranslationValidator implements EventSubscriberInterface
{
public const VIOLATION_DELETE_SYSTEM_TRANSLATION = 'delete-system-translation-violation';
public static function getSubscribedEvents(): array
{
return [
PreWriteValidationEvent::class => 'preValidate',
];
}
public function preValidate(PreWriteValidationEvent $event): void
{
if ($event->getContext()->getVersionId() !== Defaults::LIVE_VERSION) {
return;
}
$violations = new ConstraintViolationList();
$violations->addAll($this->getDeletedSystemTranslationViolations($event->getCommands()));
if ($violations->count()) {
$event->getExceptions()->add(new WriteConstraintViolationException($violations));
}
}
/**
* @param list<WriteCommand> $writeCommands
*/
private function getDeletedSystemTranslationViolations(array $writeCommands): ConstraintViolationList
{
$violations = new ConstraintViolationList();
foreach ($writeCommands as $writeCommand) {
if (!$writeCommand instanceof DeleteCommand || $writeCommand instanceof CascadeDeleteCommand) {
continue;
}
$pk = $writeCommand->getPrimaryKey();
if (!isset($pk['language_id'])) {
continue;
}
$def = $writeCommand->getDefinition();
if (!$def instanceof EntityTranslationDefinition) {
continue;
}
if (Uuid::fromBytesToHex($pk['language_id']) !== Defaults::LANGUAGE_SYSTEM) {
continue;
}
$fks = $this->getFkFields($def);
$id = Uuid::fromBytesToHex($pk[$fks['id']->getStorageName()]);
$violations->add(
$this->buildViolation(
'Cannot delete system translation',
['{{ id }}' => $id],
'/' . $id . '/translations/' . Defaults::LANGUAGE_SYSTEM,
[$id, Defaults::LANGUAGE_SYSTEM],
self::VIOLATION_DELETE_SYSTEM_TRANSLATION
)
);
}
return $violations;
}
/**
* @return FkField[]
*/
private function getFkFields(EntityTranslationDefinition $definition): array
{
$rootEntity = $definition->getParentDefinition();
$idStorageName = $rootEntity->getEntityName() . '_id';
$versionIdStorageName = $rootEntity->getEntityName() . '_version_id';
$pks = $definition->getPrimaryKeys();
$idField = $pks->getByStorageName($idStorageName);
if (!$idField || !$idField instanceof FkField) {
throw new \RuntimeException(sprintf('`%s` primary key should have column `%s`', $definition->getEntityName(), $idStorageName));
}
$fields = [
'id' => $idField,
];
$versionIdField = $pks->getByStorageName($versionIdStorageName);
if ($versionIdField && $versionIdField instanceof FkField) {
$fields['version'] = $versionIdField;
}
return $fields;
}
/**
* @param array<string, string> $parameters
* @param array<mixed>|null $invalidValue
*/
private function buildViolation(
string $messageTemplate,
array $parameters,
?string $propertyPath = null,
?array $invalidValue = null,
?string $code = null
): ConstraintViolationInterface {
return new ConstraintViolation(
str_replace(array_keys($parameters), array_values($parameters), $messageTemplate),
$messageTemplate,
$parameters,
null,
$propertyPath,
$invalidValue,
null,
$code
);
}
}