8185182516c7bd4b7942f7f7bf529690d68a879e
[yaffs-website] / web / core / modules / content_translation / tests / src / Kernel / ContentTranslationFieldSyncRevisionTest.php
1 <?php
2
3 namespace Drupal\Tests\content_translation\Kernel;
4
5 use Drupal\Core\Entity\ContentEntityInterface;
6 use Drupal\Core\Entity\EntityConstraintViolationListInterface;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\entity_test\Entity\EntityTestMulRev;
9 use Drupal\field\Entity\FieldConfig;
10 use Drupal\field\Entity\FieldStorageConfig;
11 use Drupal\file\Entity\File;
12 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
13 use Drupal\language\Entity\ConfigurableLanguage;
14 use Drupal\Tests\TestFileCreationTrait;
15 use Drupal\user\Entity\User;
16
17 /**
18  * Tests the field synchronization logic when revisions are involved.
19  *
20  * @group content_translation
21  */
22 class ContentTranslationFieldSyncRevisionTest extends EntityKernelTestBase {
23
24   use TestFileCreationTrait;
25
26   /**
27    * {@inheritdoc}
28    */
29   public static $modules = ['file', 'image', 'language', 'content_translation', 'simpletest', 'content_translation_test'];
30
31   /**
32    * The synchronized field name.
33    *
34    * @var string
35    */
36   protected $fieldName = 'sync_field';
37
38   /**
39    * The content translation manager.
40    *
41    * @var \Drupal\content_translation\ContentTranslationManagerInterface|\Drupal\content_translation\BundleTranslationSettingsInterface
42    */
43   protected $contentTranslationManager;
44
45   /**
46    * The test entity storage.
47    *
48    * @var \Drupal\Core\Entity\ContentEntityStorageInterface
49    */
50   protected $storage;
51
52   /**
53    * {@inheritdoc}
54    */
55   protected function setUp() {
56     parent::setUp();
57
58     $entity_type_id = 'entity_test_mulrev';
59     $this->installEntitySchema($entity_type_id);
60     $this->installEntitySchema('file');
61     $this->installSchema('file', ['file_usage']);
62
63     ConfigurableLanguage::createFromLangcode('it')->save();
64     ConfigurableLanguage::createFromLangcode('fr')->save();
65
66     /** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
67     $field_storage_config = FieldStorageConfig::create([
68       'field_name' => $this->fieldName,
69       'type' => 'image',
70       'entity_type' => $entity_type_id,
71       'cardinality' => 1,
72       'translatable' => 1,
73     ]);
74     $field_storage_config->save();
75
76     $field_config = FieldConfig::create([
77       'entity_type' => $entity_type_id,
78       'field_name' => $this->fieldName,
79       'bundle' => $entity_type_id,
80       'label' => 'Synchronized field',
81       'translatable' => 1,
82     ]);
83     $field_config->save();
84
85     $property_settings = [
86       'alt' => 'alt',
87       'title' => 'title',
88       'file' => 0,
89     ];
90     $field_config->setThirdPartySetting('content_translation', 'translation_sync', $property_settings);
91     $field_config->save();
92
93     $this->entityManager->clearCachedDefinitions();
94
95     $this->contentTranslationManager = $this->container->get('content_translation.manager');
96     $this->contentTranslationManager->setEnabled($entity_type_id, $entity_type_id, TRUE);
97
98     $this->storage = $this->entityManager->getStorage($entity_type_id);
99
100     foreach ($this->getTestFiles('image') as $file) {
101       $entity = File::create((array) $file + ['status' => 1]);
102       $entity->save();
103     }
104
105     $this->state->set('content_translation.entity_access.file', ['view' => TRUE]);
106
107     $account = User::create([
108       'name' => $this->randomMachineName(),
109       'status' => 1,
110     ]);
111     $account->save();
112   }
113
114   /**
115    * Checks that field synchronization works as expected with revisions.
116    *
117    * @covers \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraintValidator::create
118    * @covers \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraintValidator::validate
119    * @covers \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraintValidator::hasSynchronizedPropertyChanges
120    * @covers \Drupal\content_translation\FieldTranslationSynchronizer::getFieldSynchronizedProperties
121    * @covers \Drupal\content_translation\FieldTranslationSynchronizer::synchronizeFields
122    * @covers \Drupal\content_translation\FieldTranslationSynchronizer::synchronizeItems
123    */
124   public function testFieldSynchronizationAndValidation() {
125     // Test that when untranslatable field widgets are displayed, synchronized
126     // field properties can be changed only in default revisions.
127     $this->setUntranslatableFieldWidgetsDisplay(TRUE);
128     $entity = $this->saveNewEntity();
129     $entity_id = $entity->id();
130     $this->assertLatestRevisionFieldValues($entity_id, [1, 1, 1, 'Alt 1 EN']);
131
132     /** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
133     $en_revision = $this->createRevision($entity, FALSE);
134     $en_revision->get($this->fieldName)->target_id = 2;
135     $violations = $en_revision->validate();
136     $this->assertViolations($violations);
137
138     $it_translation = $entity->addTranslation('it', $entity->toArray());
139     /** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
140     $it_revision = $this->createRevision($it_translation, FALSE);
141     $metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
142     $metadata->setSource('en');
143     $it_revision->get($this->fieldName)->target_id = 2;
144     $it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
145     $violations = $it_revision->validate();
146     $this->assertViolations($violations);
147     $it_revision->isDefaultRevision(TRUE);
148     $violations = $it_revision->validate();
149     $this->assertEmpty($violations);
150     $this->storage->save($it_revision);
151     $this->assertLatestRevisionFieldValues($entity_id, [2, 2, 2, 'Alt 1 EN', 'Alt 2 IT']);
152
153     $en_revision = $this->createRevision($en_revision, FALSE);
154     $en_revision->get($this->fieldName)->alt = 'Alt 3 EN';
155     $violations = $en_revision->validate();
156     $this->assertEmpty($violations);
157     $this->storage->save($en_revision);
158     $this->assertLatestRevisionFieldValues($entity_id, [3, 2, 2, 'Alt 3 EN', 'Alt 2 IT']);
159
160     $it_revision = $this->createRevision($it_revision, FALSE);
161     $it_revision->get($this->fieldName)->alt = 'Alt 4 IT';
162     $violations = $it_revision->validate();
163     $this->assertEmpty($violations);
164     $this->storage->save($it_revision);
165     $this->assertLatestRevisionFieldValues($entity_id, [4, 2, 2, 'Alt 1 EN', 'Alt 4 IT']);
166
167     $en_revision = $this->createRevision($en_revision);
168     $en_revision->get($this->fieldName)->alt = 'Alt 5 EN';
169     $violations = $en_revision->validate();
170     $this->assertEmpty($violations);
171     $this->storage->save($en_revision);
172     $this->assertLatestRevisionFieldValues($entity_id, [5, 2, 2, 'Alt 5 EN', 'Alt 2 IT']);
173
174     $en_revision = $this->createRevision($en_revision);
175     $en_revision->get($this->fieldName)->target_id = 6;
176     $en_revision->get($this->fieldName)->alt = 'Alt 6 EN';
177     $violations = $en_revision->validate();
178     $this->assertEmpty($violations);
179     $this->storage->save($en_revision);
180     $this->assertLatestRevisionFieldValues($entity_id, [6, 6, 6, 'Alt 6 EN', 'Alt 2 IT']);
181
182     $it_revision = $this->createRevision($it_revision);
183     $it_revision->get($this->fieldName)->alt = 'Alt 7 IT';
184     $violations = $it_revision->validate();
185     $this->assertEmpty($violations);
186     $this->storage->save($it_revision);
187     $this->assertLatestRevisionFieldValues($entity_id, [7, 6, 6, 'Alt 6 EN', 'Alt 7 IT']);
188
189     // Test that when untranslatable field widgets are hidden, synchronized
190     // field properties can be changed only when editing the default
191     // translation. This may lead to temporarily desynchronized values, when
192     // saving a pending revision for the default translation that changes a
193     // synchronized property (see revision 11).
194     $this->setUntranslatableFieldWidgetsDisplay(FALSE);
195     $entity = $this->saveNewEntity();
196     $entity_id = $entity->id();
197     $this->assertLatestRevisionFieldValues($entity_id, [8, 1, 1, 'Alt 1 EN']);
198
199     /** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
200     $en_revision = $this->createRevision($entity, FALSE);
201     $en_revision->get($this->fieldName)->target_id = 2;
202     $en_revision->get($this->fieldName)->alt = 'Alt 2 EN';
203     $violations = $en_revision->validate();
204     $this->assertEmpty($violations);
205     $this->storage->save($en_revision);
206     $this->assertLatestRevisionFieldValues($entity_id, [9, 2, 2, 'Alt 2 EN']);
207
208     $it_translation = $entity->addTranslation('it', $entity->toArray());
209     /** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
210     $it_revision = $this->createRevision($it_translation, FALSE);
211     $metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
212     $metadata->setSource('en');
213     $it_revision->get($this->fieldName)->target_id = 3;
214     $violations = $it_revision->validate();
215     $this->assertViolations($violations);
216     $it_revision->isDefaultRevision(TRUE);
217     $violations = $it_revision->validate();
218     $this->assertViolations($violations);
219
220     $it_revision = $this->createRevision($it_translation);
221     $metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
222     $metadata->setSource('en');
223     $it_revision->get($this->fieldName)->alt = 'Alt 3 IT';
224     $violations = $it_revision->validate();
225     $this->assertEmpty($violations);
226     $this->storage->save($it_revision);
227     $this->assertLatestRevisionFieldValues($entity_id, [10, 1, 1, 'Alt 1 EN', 'Alt 3 IT']);
228
229     $en_revision = $this->createRevision($en_revision, FALSE);
230     $en_revision->get($this->fieldName)->alt = 'Alt 4 EN';
231     $violations = $en_revision->validate();
232     $this->assertEmpty($violations);
233     $this->storage->save($en_revision);
234     $this->assertLatestRevisionFieldValues($entity_id, [11, 2, 1, 'Alt 4 EN', 'Alt 3 IT']);
235
236     $it_revision = $this->createRevision($it_revision, FALSE);
237     $it_revision->get($this->fieldName)->alt = 'Alt 5 IT';
238     $violations = $it_revision->validate();
239     $this->assertEmpty($violations);
240     $this->storage->save($it_revision);
241     $this->assertLatestRevisionFieldValues($entity_id, [12, 1, 1, 'Alt 1 EN', 'Alt 5 IT']);
242
243     $en_revision = $this->createRevision($en_revision);
244     $en_revision->get($this->fieldName)->target_id = 6;
245     $en_revision->get($this->fieldName)->alt = 'Alt 6 EN';
246     $violations = $en_revision->validate();
247     $this->assertEmpty($violations);
248     $this->storage->save($en_revision);
249     $this->assertLatestRevisionFieldValues($entity_id, [13, 6, 6, 'Alt 6 EN', 'Alt 3 IT']);
250
251     $it_revision = $this->createRevision($it_revision);
252     $it_revision->get($this->fieldName)->target_id = 7;
253     $violations = $it_revision->validate();
254     $this->assertViolations($violations);
255
256     $it_revision = $this->createRevision($it_revision);
257     $it_revision->get($this->fieldName)->alt = 'Alt 7 IT';
258     $violations = $it_revision->validate();
259     $this->assertEmpty($violations);
260     $this->storage->save($it_revision);
261     $this->assertLatestRevisionFieldValues($entity_id, [14, 6, 6, 'Alt 6 EN', 'Alt 7 IT']);
262
263     // Test that creating a default revision starting from a pending revision
264     // having changes to synchronized properties, without introducing new
265     // changes works properly.
266     $this->setUntranslatableFieldWidgetsDisplay(FALSE);
267     $entity = $this->saveNewEntity();
268     $entity_id = $entity->id();
269     $this->assertLatestRevisionFieldValues($entity_id, [15, 1, 1, 'Alt 1 EN']);
270
271     $it_translation = $entity->addTranslation('it', $entity->toArray());
272     /** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
273     $it_revision = $this->createRevision($it_translation);
274     $metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
275     $metadata->setSource('en');
276     $it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
277     $violations = $it_revision->validate();
278     $this->assertEmpty($violations);
279     $this->storage->save($it_revision);
280     $this->assertLatestRevisionFieldValues($entity_id, [16, 1, 1, 'Alt 1 EN', 'Alt 2 IT']);
281
282     /** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
283     $en_revision = $this->createRevision($entity);
284     $en_revision->get($this->fieldName)->target_id = 3;
285     $en_revision->get($this->fieldName)->alt = 'Alt 3 EN';
286     $violations = $en_revision->validate();
287     $this->assertEmpty($violations);
288     $this->storage->save($en_revision);
289     $this->assertLatestRevisionFieldValues($entity_id, [17, 3, 3, 'Alt 3 EN', 'Alt 2 IT']);
290
291     $en_revision = $this->createRevision($entity, FALSE);
292     $en_revision->get($this->fieldName)->target_id = 4;
293     $en_revision->get($this->fieldName)->alt = 'Alt 4 EN';
294     $violations = $en_revision->validate();
295     $this->assertEmpty($violations);
296     $this->storage->save($en_revision);
297     $this->assertLatestRevisionFieldValues($entity_id, [18, 4, 3, 'Alt 4 EN', 'Alt 2 IT']);
298
299     $en_revision = $this->createRevision($entity);
300     $violations = $en_revision->validate();
301     $this->assertEmpty($violations);
302     $this->storage->save($en_revision);
303     $this->assertLatestRevisionFieldValues($entity_id, [19, 4, 4, 'Alt 4 EN', 'Alt 2 IT']);
304
305     $it_revision = $this->createRevision($it_revision);
306     $it_revision->get($this->fieldName)->alt = 'Alt 6 IT';
307     $violations = $it_revision->validate();
308     $this->assertEmpty($violations);
309     $this->storage->save($it_revision);
310     $this->assertLatestRevisionFieldValues($entity_id, [20, 4, 4, 'Alt 4 EN', 'Alt 6 IT']);
311
312     // Check that we are not allowed to perform changes to multiple translations
313     // in pending revisions when synchronized properties are involved.
314     $this->setUntranslatableFieldWidgetsDisplay(FALSE);
315     $entity = $this->saveNewEntity();
316     $entity_id = $entity->id();
317     $this->assertLatestRevisionFieldValues($entity_id, [21, 1, 1, 'Alt 1 EN']);
318
319     $it_translation = $entity->addTranslation('it', $entity->toArray());
320     /** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
321     $it_revision = $this->createRevision($it_translation);
322     $metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
323     $metadata->setSource('en');
324     $it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
325     $violations = $it_revision->validate();
326     $this->assertEmpty($violations);
327     $this->storage->save($it_revision);
328     $this->assertLatestRevisionFieldValues($entity_id, [22, 1, 1, 'Alt 1 EN', 'Alt 2 IT']);
329
330     $en_revision = $this->createRevision($entity, FALSE);
331     $en_revision->get($this->fieldName)->target_id = 2;
332     $en_revision->getTranslation('it')->get($this->fieldName)->alt = 'Alt 3 IT';
333     $violations = $en_revision->validate();
334     $this->assertViolations($violations);
335
336     // Test that when saving a new default revision starting from a pending
337     // revision, outdated synchronized properties do not override more recent
338     // ones.
339     $this->setUntranslatableFieldWidgetsDisplay(TRUE);
340     $entity = $this->saveNewEntity();
341     $entity_id = $entity->id();
342     $this->assertLatestRevisionFieldValues($entity_id, [23, 1, 1, 'Alt 1 EN']);
343
344     $it_translation = $entity->addTranslation('it', $entity->toArray());
345     /** @var \Drupal\Core\Entity\ContentEntityInterface $it_revision */
346     $it_revision = $this->createRevision($it_translation, FALSE);
347     $metadata = $this->contentTranslationManager->getTranslationMetadata($it_revision);
348     $metadata->setSource('en');
349     $it_revision->get($this->fieldName)->alt = 'Alt 2 IT';
350     $violations = $it_revision->validate();
351     $this->assertEmpty($violations);
352     $this->storage->save($it_revision);
353     $this->assertLatestRevisionFieldValues($entity_id, [24, 1, 1, 'Alt 1 EN', 'Alt 2 IT']);
354
355     /** @var \Drupal\Core\Entity\ContentEntityInterface $en_revision */
356     $en_revision = $this->createRevision($entity);
357     $en_revision->get($this->fieldName)->target_id = 3;
358     $en_revision->get($this->fieldName)->alt = 'Alt 3 EN';
359     $violations = $en_revision->validate();
360     $this->assertEmpty($violations);
361     $this->storage->save($en_revision);
362     $this->assertLatestRevisionFieldValues($entity_id, [25, 3, 3, 'Alt 3 EN', 'Alt 2 IT']);
363
364     $it_revision = $this->createRevision($it_revision);
365     $it_revision->get($this->fieldName)->alt = 'Alt 4 IT';
366     $violations = $it_revision->validate();
367     $this->assertEmpty($violations);
368     $this->storage->save($it_revision);
369     $this->assertLatestRevisionFieldValues($entity_id, [26, 3, 3, 'Alt 3 EN', 'Alt 4 IT']);
370   }
371
372   /**
373    * Sets untranslatable field widgets' display status.
374    *
375    * @param bool $display
376    *   Whether untranslatable field widgets should be displayed.
377    */
378   protected function setUntranslatableFieldWidgetsDisplay($display) {
379     $entity_type_id = $this->storage->getEntityTypeId();
380     $settings = ['untranslatable_fields_hide' => !$display];
381     $this->contentTranslationManager->setBundleTranslationSettings($entity_type_id, $entity_type_id, $settings);
382     /** @var \Drupal\Core\Entity\EntityTypeBundleInfo $bundle_info */
383     $bundle_info = $this->container->get('entity_type.bundle.info');
384     $bundle_info->clearCachedBundles();
385   }
386
387   /**
388    * @return \Drupal\Core\Entity\ContentEntityInterface
389    */
390   protected function saveNewEntity() {
391     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
392     $entity = EntityTestMulRev::create([
393       'uid' => 1,
394       'langcode' => 'en',
395       $this->fieldName => [
396         'target_id' => 1,
397         'alt' => 'Alt 1 EN',
398       ],
399     ]);
400     $metadata = $this->contentTranslationManager->getTranslationMetadata($entity);
401     $metadata->setSource(LanguageInterface::LANGCODE_NOT_SPECIFIED);
402     $violations = $entity->validate();
403     $this->assertEmpty($violations);
404     $this->storage->save($entity);
405     return $entity;
406   }
407
408   /**
409    * Creates a new revision starting from the latest translation-affecting one.
410    *
411    * @param \Drupal\Core\Entity\ContentEntityInterface $translation
412    *   The translation to be revisioned.
413    * @param bool $default
414    *   (optional) Whether the new revision should be marked as default. Defaults
415    *   to TRUE.
416    *
417    * @return \Drupal\Core\Entity\ContentEntityInterface
418    *   An entity revision object.
419    */
420   protected function createRevision(ContentEntityInterface $translation, $default = TRUE) {
421     if (!$translation->isNewTranslation()) {
422       $langcode = $translation->language()->getId();
423       $revision_id = $this->storage->getLatestTranslationAffectedRevisionId($translation->id(), $langcode);
424       /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
425       $revision = $this->storage->loadRevision($revision_id);
426       $translation = $revision->getTranslation($langcode);
427     }
428     /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
429     $revision = $this->storage->createRevision($translation, $default);
430     return $revision;
431   }
432
433   /**
434    * Asserts that the expected violations were found.
435    *
436    * @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
437    *   A list of violations.
438    */
439   protected function assertViolations(EntityConstraintViolationListInterface $violations) {
440     $entity_type_id = $this->storage->getEntityTypeId();
441     $settings = $this->contentTranslationManager->getBundleTranslationSettings($entity_type_id, $entity_type_id);
442     $message = !empty($settings['untranslatable_fields_hide']) ?
443       'Non-translatable field elements can only be changed when updating the original language.' :
444       'Non-translatable field elements can only be changed when updating the current revision.';
445
446     $list = [];
447     foreach ($violations as $violation) {
448       if ((string) $violation->getMessage() === $message) {
449         $list[] = $violation;
450       }
451     }
452     $this->assertCount(1, $list);
453   }
454
455   /**
456    * Asserts that the latest revision has the expected field values.
457    *
458    * @param $entity_id
459    *   The entity ID.
460    * @param array $expected_values
461    *   An array of expected values in the following order:
462    *   - revision ID
463    *   - target ID (en)
464    *   - target ID (it)
465    *   - alt (en)
466    *   - alt (it)
467    */
468   protected function assertLatestRevisionFieldValues($entity_id, array $expected_values) {
469     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
470     $entity = $this->storage->loadRevision($this->storage->getLatestRevisionId($entity_id));
471     @list($revision_id, $target_id_en, $target_id_it, $alt_en, $alt_it) = $expected_values;
472     $this->assertEquals($revision_id, $entity->getRevisionId());
473     $this->assertEquals($target_id_en, $entity->get($this->fieldName)->target_id);
474     $this->assertEquals($alt_en, $entity->get($this->fieldName)->alt);
475     if ($entity->hasTranslation('it')) {
476       $it_translation = $entity->getTranslation('it');
477       $this->assertEquals($target_id_it, $it_translation->get($this->fieldName)->target_id);
478       $this->assertEquals($alt_it, $it_translation->get($this->fieldName)->alt);
479     }
480   }
481
482 }