Security update for Core, with self-updated composer
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Entity / ContentEntityCloneTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Entity;
4
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\entity_test\Entity\EntityTestMul;
7 use Drupal\entity_test\Entity\EntityTestMulRev;
8 use Drupal\language\Entity\ConfigurableLanguage;
9
10 /**
11  * Tests proper cloning of content entities.
12  *
13  * @group Entity
14  */
15 class ContentEntityCloneTest extends EntityKernelTestBase {
16
17   /**
18    * {@inheritdoc}
19    */
20   public static $modules = ['language', 'entity_test'];
21
22   /**
23    * {@inheritdoc}
24    */
25   protected function setUp() {
26     parent::setUp();
27
28     // Enable an additional language.
29     ConfigurableLanguage::createFromLangcode('de')->save();
30
31     $this->installEntitySchema('entity_test_mul');
32     $this->installEntitySchema('entity_test_mulrev');
33   }
34
35   /**
36    * Tests if entity references on fields are still correct after cloning.
37    */
38   public function testFieldEntityReferenceAfterClone() {
39     $user = $this->createUser();
40
41     // Create a test entity.
42     $entity = EntityTestMul::create([
43       'name' => $this->randomString(),
44       'user_id' => $user->id(),
45       'language' => 'en',
46     ]);
47     $translation = $entity->addTranslation('de');
48
49     // Initialize the fields on the translation objects in order to check that
50     // they are properly cloned and have a reference to the cloned entity
51     // object and not to the original one.
52     $entity->getFields();
53     $translation->getFields();
54
55     $clone = clone $translation;
56
57     $this->assertEqual($entity->getTranslationLanguages(), $clone->getTranslationLanguages(), 'The entity and its clone have the same translation languages.');
58
59     $default_langcode = $entity->getUntranslated()->language()->getId();
60     foreach (array_keys($clone->getTranslationLanguages()) as $langcode) {
61       $translation = $clone->getTranslation($langcode);
62       foreach ($translation->getFields() as $field_name => $field) {
63         if ($field->getFieldDefinition()->isTranslatable()) {
64           $args = ['%field_name' => $field_name, '%langcode' => $langcode];
65           $this->assertEqual($langcode, $field->getEntity()->language()->getId(), format_string('Translatable field %field_name on translation %langcode has correct entity reference in translation %langcode after cloning.', $args));
66           $this->assertSame($translation, $field->getEntity(), new FormattableMarkup('Translatable field %field_name on translation %langcode has correct reference to the cloned entity object.', $args));
67         }
68         else {
69           $args = ['%field_name' => $field_name, '%langcode' => $langcode, '%default_langcode' => $default_langcode];
70           $this->assertEqual($default_langcode, $field->getEntity()->language()->getId(), format_string('Non translatable field %field_name on translation %langcode has correct entity reference in the default translation %default_langcode after cloning.', $args));
71           $this->assertSame($translation->getUntranslated(), $field->getEntity(), new FormattableMarkup('Non translatable field %field_name on translation %langcode has correct reference to the cloned entity object in the default translation %default_langcode.', $args));
72         }
73       }
74     }
75   }
76
77   /**
78    * Tests that the flag for enforcing a new entity is not shared.
79    */
80   public function testEnforceIsNewOnClonedEntityTranslation() {
81     // Create a test entity.
82     $entity = EntityTestMul::create([
83       'name' => $this->randomString(),
84       'language' => 'en',
85     ]);
86     $entity->save();
87     $entity_translation = $entity->addTranslation('de');
88     $entity->save();
89
90     // The entity is not new anymore.
91     $this->assertFalse($entity_translation->isNew());
92
93     // The clone should not be new either.
94     $clone = clone $entity_translation;
95     $this->assertFalse($clone->isNew());
96
97     // After forcing the clone to be new only it should be flagged as new, but
98     // the original entity should not.
99     $clone->enforceIsNew();
100     $this->assertTrue($clone->isNew());
101     $this->assertFalse($entity_translation->isNew());
102   }
103
104   /**
105    * Tests if the entity fields are properly cloned.
106    */
107   public function testClonedEntityFields() {
108     $user = $this->createUser();
109
110     // Create a test entity.
111     $entity = EntityTestMul::create([
112       'name' => $this->randomString(),
113       'user_id' => $user->id(),
114       'language' => 'en',
115     ]);
116
117     $entity->addTranslation('de');
118     $entity->save();
119     $fields = array_keys($entity->getFieldDefinitions());
120
121     // Reload the entity, clone it and check that both entity objects reference
122     // different field instances.
123     $entity = $this->reloadEntity($entity);
124     $clone = clone $entity;
125
126     $different_references = TRUE;
127     foreach ($fields as $field_name) {
128       if ($entity->get($field_name) === $clone->get($field_name)) {
129         $different_references = FALSE;
130       }
131     }
132     $this->assertTrue($different_references, 'The entity object and the cloned entity object reference different field item list objects.');
133
134     // Reload the entity, initialize one translation, clone it and check that
135     // both entity objects reference different field instances.
136     $entity = $this->reloadEntity($entity);
137     $entity->getTranslation('de');
138     $clone = clone $entity;
139
140     $different_references = TRUE;
141     foreach ($fields as $field_name) {
142       if ($entity->get($field_name) === $clone->get($field_name)) {
143         $different_references = FALSE;
144       }
145     }
146     $this->assertTrue($different_references, 'The entity object and the cloned entity object reference different field item list objects if the entity is cloned after an entity translation has been initialized.');
147   }
148
149   /**
150    * Tests that the flag for enforcing a new revision is not shared.
151    */
152   public function testNewRevisionOnCloneEntityTranslation() {
153     // Create a test entity.
154     $entity = EntityTestMulRev::create([
155       'name' => $this->randomString(),
156       'language' => 'en',
157     ]);
158     $entity->save();
159     $entity->addTranslation('de');
160     $entity->save();
161
162     // Reload the entity as ContentEntityBase::postCreate() forces the entity to
163     // be a new revision.
164     $entity = EntityTestMulRev::load($entity->id());
165     $entity_translation = $entity->getTranslation('de');
166
167     // The entity is not set to be a new revision.
168     $this->assertFalse($entity_translation->isNewRevision());
169
170     // The clone should not be set to be a new revision either.
171     $clone = clone $entity_translation;
172     $this->assertFalse($clone->isNewRevision());
173
174     // After forcing the clone to be a new revision only it should be flagged
175     // as a new revision, but the original entity should not.
176     $clone->setNewRevision();
177     $this->assertTrue($clone->isNewRevision());
178     $this->assertFalse($entity_translation->isNewRevision());
179   }
180
181   /**
182    * Tests modifications on entity keys of a cloned entity object.
183    */
184   public function testEntityKeysModifications() {
185     // Create a test entity with a translation, which will internally trigger
186     // entity cloning for the new translation and create references for some of
187     // the entity properties.
188     $entity = EntityTestMulRev::create([
189       'name' => 'original-name',
190       'uuid' => 'original-uuid',
191       'language' => 'en',
192     ]);
193     $entity->addTranslation('de');
194     $entity->save();
195
196     // Clone the entity.
197     $clone = clone $entity;
198
199     // Alter a non-translatable and a translatable entity key fields of the
200     // cloned entity and assert that retrieving the value through the entity
201     // keys local cache will be different for the cloned and the original
202     // entity.
203     // We first have to call the ::uuid() and ::label() method on the original
204     // entity as it is going to cache the field values into the $entityKeys and
205     // $translatableEntityKeys properties of the entity object and we want to
206     // check that the cloned and the original entity aren't sharing the same
207     // reference to those local cache properties.
208     $uuid_field_name = $entity->getEntityType()->getKey('uuid');
209     $this->assertFalse($entity->getFieldDefinition($uuid_field_name)->isTranslatable());
210     $clone->$uuid_field_name->value = 'clone-uuid';
211     $this->assertEquals('original-uuid', $entity->uuid());
212     $this->assertEquals('clone-uuid', $clone->uuid());
213
214     $label_field_name = $entity->getEntityType()->getKey('label');
215     $this->assertTrue($entity->getFieldDefinition($label_field_name)->isTranslatable());
216     $clone->$label_field_name->value = 'clone-name';
217     $this->assertEquals('original-name', $entity->label());
218     $this->assertEquals('clone-name', $clone->label());
219   }
220
221   /**
222    * Tests the field values after serializing an entity and its clone.
223    */
224   public function testFieldValuesAfterSerialize() {
225     // Create a test entity with a translation, which will internally trigger
226     // entity cloning for the new translation and create references for some of
227     // the entity properties.
228     $entity = EntityTestMulRev::create([
229       'name' => 'original',
230       'language' => 'en',
231     ]);
232     $entity->addTranslation('de');
233     $entity->save();
234
235     // Clone the entity.
236     $clone = clone $entity;
237
238     // Alter the name field value of the cloned entity object.
239     $clone->setName('clone');
240
241     // Serialize the entity and the cloned object in order to destroy the field
242     // objects and put the field values into the entity property $values, so
243     // that on accessing a field again it will be newly created with the value
244     // from the $values property.
245     serialize($entity);
246     serialize($clone);
247
248     // Assert that the original and the cloned entity both have different names.
249     $this->assertEquals('original', $entity->getName());
250     $this->assertEquals('clone', $clone->getName());
251   }
252
253   /**
254    * Tests changing the default revision flag.
255    */
256   public function testDefaultRevision() {
257     // Create a test entity with a translation, which will internally trigger
258     // entity cloning for the new translation and create references for some of
259     // the entity properties.
260     $entity = EntityTestMulRev::create([
261       'name' => 'original',
262       'language' => 'en',
263     ]);
264     $entity->addTranslation('de');
265     $entity->save();
266
267     // Assert that the entity is in the default revision.
268     $this->assertTrue($entity->isDefaultRevision());
269
270     // Clone the entity and modify its default revision flag.
271     $clone = clone $entity;
272     $clone->isDefaultRevision(FALSE);
273
274     // Assert that the clone is not in default revision, but the original entity
275     // is still in the default revision.
276     $this->assertFalse($clone->isDefaultRevision());
277     $this->assertTrue($entity->isDefaultRevision());
278   }
279
280   /**
281    * Tests references of entity properties after entity cloning.
282    */
283   public function testEntityPropertiesModifications() {
284     // Create a test entity with a translation, which will internally trigger
285     // entity cloning for the new translation and create references for some of
286     // the entity properties.
287     $entity = EntityTestMulRev::create([
288       'name' => 'original',
289       'language' => 'en',
290     ]);
291     $translation = $entity->addTranslation('de');
292     $entity->save();
293
294     // Clone the entity.
295     $clone = clone $entity;
296
297     // Retrieve the entity properties.
298     $reflection = new \ReflectionClass($entity);
299     $properties = $reflection->getProperties(~\ReflectionProperty::IS_STATIC);
300     $translation_unique_properties = ['activeLangcode', 'translationInitialize', 'fieldDefinitions', 'languages', 'langcodeKey', 'defaultLangcode', 'defaultLangcodeKey', 'validated', 'validationRequired', 'entityTypeId', 'typedData', 'cacheContexts', 'cacheTags', 'cacheMaxAge', '_serviceIds'];
301
302     foreach ($properties as $property) {
303       // Modify each entity property on the clone and assert that the change is
304       // not propagated to the original entity.
305       $property->setAccessible(TRUE);
306       $property->setValue($entity, 'default-value');
307       $property->setValue($translation, 'default-value');
308       $property->setValue($clone, 'test-entity-cloning');
309       $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
310       $this->assertEquals('default-value', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
311       $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
312
313       // Modify each entity property on the translation entity object and assert
314       // that the change is propagated to the default translation entity object
315       // except for the properties that are unique for each entity translation
316       // object.
317       $property->setValue($translation, 'test-translation-cloning');
318       // Using assertEquals or assertNotEquals here is dangerous as if the
319       // assertion fails and the property for some reasons contains the entity
320       // object e.g. the "typedData" property then the property will be
321       // serialized, but this will cause exceptions because the entity is
322       // modified in a non-consistent way and ContentEntityBase::__sleep() will
323       // not be able to properly access all properties and this will cause
324       // exceptions without a proper backtrace.
325       if (in_array($property->getName(), $translation_unique_properties)) {
326         $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
327         $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
328       }
329       else {
330         $this->assertEquals('test-translation-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
331         $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
332       }
333     }
334   }
335
336 }