Upgraded drupal core with security updates
[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
135     // Reload the entity, initialize one translation, clone it and check that
136     // both entity objects reference different field instances.
137     $entity = $this->reloadEntity($entity);
138     $entity->getTranslation('de');
139     $clone = clone $entity;
140
141     $different_references = TRUE;
142     foreach ($fields as $field_name) {
143       if ($entity->get($field_name) === $clone->get($field_name)) {
144         $different_references = FALSE;
145       }
146     }
147     $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.');
148   }
149
150   /**
151    * Tests that the flag for enforcing a new revision is not shared.
152    */
153   public function testNewRevisionOnCloneEntityTranslation() {
154     // Create a test entity.
155     $entity = EntityTestMulRev::create([
156       'name' => $this->randomString(),
157       'language' => 'en',
158     ]);
159     $entity->save();
160     $entity->addTranslation('de');
161     $entity->save();
162
163     // Reload the entity as ContentEntityBase::postCreate() forces the entity to
164     // be a new revision.
165     $entity = EntityTestMulRev::load($entity->id());
166     $entity_translation = $entity->getTranslation('de');
167
168     // The entity is not set to be a new revision.
169     $this->assertFalse($entity_translation->isNewRevision());
170
171     // The clone should not be set to be a new revision either.
172     $clone = clone $entity_translation;
173     $this->assertFalse($clone->isNewRevision());
174
175     // After forcing the clone to be a new revision only it should be flagged
176     // as a new revision, but the original entity should not.
177     $clone->setNewRevision();
178     $this->assertTrue($clone->isNewRevision());
179     $this->assertFalse($entity_translation->isNewRevision());
180   }
181
182   /**
183    * Tests modifications on entity keys of a cloned entity object.
184    */
185   public function testEntityKeysModifications() {
186     // Create a test entity with a translation, which will internally trigger
187     // entity cloning for the new translation and create references for some of
188     // the entity properties.
189     $entity = EntityTestMulRev::create([
190       'name' => 'original-name',
191       'uuid' => 'original-uuid',
192       'language' => 'en',
193     ]);
194     $entity->addTranslation('de');
195     $entity->save();
196
197     // Clone the entity.
198     $clone = clone $entity;
199
200     // Alter a non-translatable and a translatable entity key fields of the
201     // cloned entity and assert that retrieving the value through the entity
202     // keys local cache will be different for the cloned and the original
203     // entity.
204     // We first have to call the ::uuid() and ::label() method on the original
205     // entity as it is going to cache the field values into the $entityKeys and
206     // $translatableEntityKeys properties of the entity object and we want to
207     // check that the cloned and the original entity aren't sharing the same
208     // reference to those local cache properties.
209     $uuid_field_name = $entity->getEntityType()->getKey('uuid');
210     $this->assertFalse($entity->getFieldDefinition($uuid_field_name)->isTranslatable());
211     $clone->$uuid_field_name->value = 'clone-uuid';
212     $this->assertEquals('original-uuid', $entity->uuid());
213     $this->assertEquals('clone-uuid', $clone->uuid());
214
215     $label_field_name = $entity->getEntityType()->getKey('label');
216     $this->assertTrue($entity->getFieldDefinition($label_field_name)->isTranslatable());
217     $clone->$label_field_name->value = 'clone-name';
218     $this->assertEquals('original-name', $entity->label());
219     $this->assertEquals('clone-name', $clone->label());
220   }
221
222   /**
223    * Tests the field values after serializing an entity and its clone.
224    */
225   public function testFieldValuesAfterSerialize() {
226     // Create a test entity with a translation, which will internally trigger
227     // entity cloning for the new translation and create references for some of
228     // the entity properties.
229     $entity = EntityTestMulRev::create([
230       'name' => 'original',
231       'language' => 'en',
232     ]);
233     $entity->addTranslation('de');
234     $entity->save();
235
236     // Clone the entity.
237     $clone = clone $entity;
238
239     // Alter the name field value of the cloned entity object.
240     $clone->setName('clone');
241
242     // Serialize the entity and the cloned object in order to destroy the field
243     // objects and put the field values into the entity property $values, so
244     // that on accessing a field again it will be newly created with the value
245     // from the $values property.
246     serialize($entity);
247     serialize($clone);
248
249     // Assert that the original and the cloned entity both have different names.
250     $this->assertEquals('original', $entity->getName());
251     $this->assertEquals('clone', $clone->getName());
252   }
253
254   /**
255    * Tests changing the default revision flag.
256    */
257   public function testDefaultRevision() {
258     // Create a test entity with a translation, which will internally trigger
259     // entity cloning for the new translation and create references for some of
260     // the entity properties.
261     $entity = EntityTestMulRev::create([
262       'name' => 'original',
263       'language' => 'en',
264     ]);
265     $entity->addTranslation('de');
266     $entity->save();
267
268     // Assert that the entity is in the default revision.
269     $this->assertTrue($entity->isDefaultRevision());
270
271     // Clone the entity and modify its default revision flag.
272     $clone = clone $entity;
273     $clone->isDefaultRevision(FALSE);
274
275     // Assert that the clone is not in default revision, but the original entity
276     // is still in the default revision.
277     $this->assertFalse($clone->isDefaultRevision());
278     $this->assertTrue($entity->isDefaultRevision());
279   }
280
281   /**
282    * Tests references of entity properties after entity cloning.
283    */
284   public function testEntityPropertiesModifications() {
285     // Create a test entity with a translation, which will internally trigger
286     // entity cloning for the new translation and create references for some of
287     // the entity properties.
288     $entity = EntityTestMulRev::create([
289       'name' => 'original',
290       'language' => 'en',
291     ]);
292     $translation = $entity->addTranslation('de');
293     $entity->save();
294
295     // Clone the entity.
296     $clone = clone $entity;
297
298     // Retrieve the entity properties.
299     $reflection = new \ReflectionClass($entity);
300     $properties = $reflection->getProperties(~\ReflectionProperty::IS_STATIC);
301     $translation_unique_properties = ['activeLangcode', 'translationInitialize', 'fieldDefinitions', 'languages', 'langcodeKey', 'defaultLangcode', 'defaultLangcodeKey', 'validated', 'validationRequired', 'entityTypeId', 'typedData', 'cacheContexts', 'cacheTags', 'cacheMaxAge', '_serviceIds'];
302
303     foreach ($properties as $property) {
304       // Modify each entity property on the clone and assert that the change is
305       // not propagated to the original entity.
306       $property->setAccessible(TRUE);
307       $property->setValue($entity, 'default-value');
308       $property->setValue($translation, 'default-value');
309       $property->setValue($clone, 'test-entity-cloning');
310       $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
311       $this->assertEquals('default-value', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
312       $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
313
314       // Modify each entity property on the translation entity object and assert
315       // that the change is propagated to the default translation entity object
316       // except for the properties that are unique for each entity translation
317       // object.
318       $property->setValue($translation, 'test-translation-cloning');
319       // Using assertEquals or assertNotEquals here is dangerous as if the
320       // assertion fails and the property for some reasons contains the entity
321       // object e.g. the "typedData" property then the property will be
322       // serialized, but this will cause exceptions because the entity is
323       // modified in a non-consistent way and ContentEntityBase::__sleep() will
324       // not be able to properly access all properties and this will cause
325       // exceptions without a proper backtrace.
326       if (in_array($property->getName(), $translation_unique_properties)) {
327         $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
328         $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
329       }
330       else {
331         $this->assertEquals('test-translation-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
332         $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
333       }
334     }
335   }
336
337 }