5b1ad1f5c99591913d8e586ea2fae066d1d193e6
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Plugin / Validation / Constraint / EntityUntranslatableFieldsConstraintValidator.php
1 <?php
2
3 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
4
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;
13
14 /**
15  * Validates the EntityChanged constraint.
16  */
17 class EntityUntranslatableFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
18
19   use EntityChangesDetectionTrait;
20
21   /**
22    * The entity type manager.
23    *
24    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
25    */
26   protected $entityTypeManager;
27
28   /**
29    * Constructs an EntityUntranslatableFieldsConstraintValidator object.
30    *
31    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
32    *   The entity type manager.
33    */
34   public function __construct(EntityTypeManagerInterface $entity_type_manager) {
35     $this->entityTypeManager = $entity_type_manager;
36   }
37
38   /**
39    * {@inheritdoc}
40    */
41   public static function create(ContainerInterface $container) {
42     return new static(
43       $container->get('entity_type.manager')
44     );
45   }
46
47   /**
48    * {@inheritdoc}
49    */
50   public function validate($entity, Constraint $constraint) {
51     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
52     /** @var \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraint $constraint */
53
54     // Untranslatable field restrictions apply only to revisions of multilingual
55     // entities.
56     if ($entity->isNew() || !$entity->isTranslatable() || !$entity->getEntityType()->isRevisionable()) {
57       return;
58     }
59     if ($entity->isDefaultRevision() && !$entity->isDefaultTranslationAffectedOnly()) {
60       return;
61     }
62
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);
78             break;
79           }
80         }
81       }
82       else {
83         $this->context->addViolation($constraint->defaultRevisionMessage);
84       }
85     }
86   }
87
88   /**
89    * Checks whether an entity has untranslatable field changes.
90    *
91    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
92    *   A content entity object.
93    *
94    * @return bool
95    *   TRUE if untranslatable fields have changes, FALSE otherwise.
96    */
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;
102     }
103     else {
104       $original = $this->entityTypeManager
105         ->getStorage($entity->getEntityTypeId())
106         ->loadRevision($entity->getLoadedRevisionId());
107     }
108
109     foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
110       if (in_array($field_name, $skip_fields, TRUE) || $definition->isTranslatable() || $definition->isComputed()) {
111         continue;
112       }
113
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
118       // entity.
119       $field = $entity->get($field_name);
120       if ($field instanceof ChangedFieldItemList) {
121         continue;
122       }
123
124       $items = $field->filterEmptyItems();
125       $original_items = $original->get($field_name)->filterEmptyItems();
126       if (!$items->equals($original_items)) {
127         return TRUE;
128       }
129     }
130
131     return FALSE;
132   }
133
134 }