3 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
5 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
6 use Drupal\Core\Entity\ContentEntityInterface;
7 use Drupal\Core\Entity\EntityChangesDetectionTrait;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Core\Field\ChangedFieldItemList;
10 use Symfony\Component\DependencyInjection\ContainerInterface;
11 use Symfony\Component\Validator\Constraint;
12 use Symfony\Component\Validator\ConstraintValidator;
15 * Validates the EntityChanged constraint.
17 class EntityUntranslatableFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
19 use EntityChangesDetectionTrait;
22 * The entity type manager.
24 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
26 protected $entityTypeManager;
29 * Constructs an EntityUntranslatableFieldsConstraintValidator object.
31 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
32 * The entity type manager.
34 public function __construct(EntityTypeManagerInterface $entity_type_manager) {
35 $this->entityTypeManager = $entity_type_manager;
41 public static function create(ContainerInterface $container) {
43 $container->get('entity_type.manager')
50 public function validate($entity, Constraint $constraint) {
51 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
52 /** @var \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraint $constraint */
54 // Untranslatable field restrictions apply only to revisions of multilingual
56 if ($entity->isNew() || !$entity->isTranslatable() || !$entity->getEntityType()->isRevisionable()) {
59 if ($entity->isDefaultRevision() && !$entity->isDefaultTranslationAffectedOnly()) {
63 // To avoid unintentional reverts and data losses, we forbid changes to
64 // untranslatable fields in pending revisions for multilingual entities. The
65 // only case where changes in pending revisions are acceptable is when
66 // untranslatable fields affect only the default translation, in which case
67 // a pending revision contains only one affected translation. Even in this
68 // case, multiple translations would be affected in a single revision, if we
69 // allowed changes to untranslatable fields while editing non-default
70 // translations, so that is forbidden too. For the same reason, when changes
71 // to untranslatable fields affect all translations, we can only allow them
72 // in default revisions.
73 if ($this->hasUntranslatableFieldsChanges($entity)) {
74 if ($entity->isDefaultTranslationAffectedOnly()) {
75 foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
76 if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
77 $this->context->addViolation($constraint->defaultTranslationMessage);
83 $this->context->addViolation($constraint->defaultRevisionMessage);
89 * Checks whether an entity has untranslatable field changes.
91 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
92 * A content entity object.
95 * TRUE if untranslatable fields have changes, FALSE otherwise.
97 protected function hasUntranslatableFieldsChanges(ContentEntityInterface $entity) {
98 $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck($entity);
99 /** @var \Drupal\Core\Entity\ContentEntityInterface $original */
100 if (isset($entity->original)) {
101 $original = $entity->original;
104 $original = $this->entityTypeManager
105 ->getStorage($entity->getEntityTypeId())
106 ->loadRevision($entity->getLoadedRevisionId());
109 foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
110 if (in_array($field_name, $skip_fields, TRUE) || $definition->isTranslatable() || $definition->isComputed()) {
114 // When saving entities in the user interface, the changed timestamp is
115 // automatically incremented by ContentEntityForm::submitForm() even if
116 // nothing was actually changed. Thus, the changed time needs to be
117 // ignored when determining whether there are any actual changes in the
119 $field = $entity->get($field_name);
120 if ($field instanceof ChangedFieldItemList) {
124 $items = $field->filterEmptyItems();
125 $original_items = $original->get($field_name)->filterEmptyItems();
126 if (!$items->equals($original_items)) {