3 namespace Drupal\Tests\content_translation\Functional;
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\field\Entity\FieldConfig;
7 use Drupal\field\Entity\FieldStorageConfig;
8 use Drupal\file\Entity\File;
9 use Drupal\Tests\TestFileCreationTrait;
12 * Tests the field synchronization behavior for the image field.
14 * @group content_translation
16 class ContentTranslationSyncImageTest extends ContentTranslationTestBase {
18 use TestFileCreationTrait {
19 getTestFiles as drupalGetTestFiles;
23 * The cardinality of the image field.
27 protected $cardinality;
30 * The test image files.
41 public static $modules = ['language', 'content_translation', 'entity_test', 'image', 'field_ui'];
43 protected function setUp() {
45 $this->files = $this->drupalGetTestFiles('image');
49 * Creates the test image field.
51 protected function setupTestFields() {
52 $this->fieldName = 'field_test_et_ui_image';
53 $this->cardinality = 3;
55 FieldStorageConfig::create([
56 'field_name' => $this->fieldName,
57 'entity_type' => $this->entityTypeId,
59 'cardinality' => $this->cardinality,
63 'entity_type' => $this->entityTypeId,
64 'field_name' => $this->fieldName,
65 'bundle' => $this->entityTypeId,
66 'label' => 'Test translatable image field',
67 'third_party_settings' => [
68 'content_translation' => [
69 'translation_sync' => [
82 protected function getEditorPermissions() {
83 // Every entity-type-specific test needs to define these.
84 return ['administer entity_test_mul fields', 'administer languages', 'administer content translation'];
88 * Tests image field field synchronization.
90 public function testImageFieldSync() {
91 // Check that the alt and title fields are enabled for the image field.
92 $this->drupalLogin($this->editor);
93 $this->drupalGet('entity_test_mul/structure/' . $this->entityTypeId . '/fields/' . $this->entityTypeId . '.' . $this->entityTypeId . '.' . $this->fieldName);
94 $this->assertFieldChecked('edit-third-party-settings-content-translation-translation-sync-alt');
95 $this->assertFieldChecked('edit-third-party-settings-content-translation-translation-sync-title');
97 'third_party_settings[content_translation][translation_sync][alt]' => FALSE,
98 'third_party_settings[content_translation][translation_sync][title]' => FALSE,
100 $this->drupalPostForm(NULL, $edit, t('Save settings'));
102 // Check that the content translation settings page reflects the changes
103 // performed in the field edit page.
104 $this->drupalGet('admin/config/regional/content-language');
105 $this->assertNoFieldChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-alt');
106 $this->assertNoFieldChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-title');
108 'settings[entity_test_mul][entity_test_mul][fields][field_test_et_ui_image]' => TRUE,
109 'settings[entity_test_mul][entity_test_mul][columns][field_test_et_ui_image][alt]' => TRUE,
110 'settings[entity_test_mul][entity_test_mul][columns][field_test_et_ui_image][title]' => TRUE,
112 $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
113 $errors = $this->xpath('//div[contains(@class, "messages--error")]');
114 $this->assertFalse($errors, 'Settings correctly stored.');
115 $this->assertFieldChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-alt');
116 $this->assertFieldChecked('edit-settings-entity-test-mul-entity-test-mul-columns-field-test-et-ui-image-title');
117 $this->drupalLogin($this->translator);
119 $default_langcode = $this->langcodes[0];
120 $langcode = $this->langcodes[1];
122 // Populate the test entity with some random initial values.
124 'name' => $this->randomMachineName(),
125 'user_id' => mt_rand(1, 128),
126 'langcode' => $default_langcode,
128 $entity = entity_create($this->entityTypeId, $values);
130 // Create some file entities from the generated test files and store them.
132 for ($delta = 0; $delta < $this->cardinality; $delta++) {
133 // For the default language use the same order for files and field items.
136 // Create the file entity for the image being processed and record its
139 'uri' => $this->files[$index]->uri,
140 'uid' => \Drupal::currentUser()->id(),
141 'status' => FILE_STATUS_PERMANENT,
143 $file = File::create($field_values);
146 $this->files[$index]->fid = $fid;
148 // Generate the item for the current image file entity and attach it to
152 'alt' => $default_langcode . '_' . $fid . '_' . $this->randomMachineName(),
153 'title' => $default_langcode . '_' . $fid . '_' . $this->randomMachineName(),
155 $entity->{$this->fieldName}[] = $item;
157 // Store the generated values keying them by fid for easier lookup.
158 $values[$default_langcode][$fid] = $item;
160 $entity = $this->saveEntity($entity);
162 // Create some field translations for the test image field. The translated
163 // items will be one less than the original values to check that only the
164 // translated ones will be preserved. In fact we want the same fids and
165 // items order for both languages.
166 $translation = $entity->addTranslation($langcode);
167 for ($delta = 0; $delta < $this->cardinality - 1; $delta++) {
168 // Simulate a field reordering: items are shifted of one position ahead.
169 // The modulo operator ensures we start from the beginning after reaching
170 // the maximum allowed delta.
171 $index = ($delta + 1) % $this->cardinality;
173 // Generate the item for the current image file entity and attach it to
175 $fid = $this->files[$index]->fid;
178 'alt' => $langcode . '_' . $fid . '_' . $this->randomMachineName(),
179 'title' => $langcode . '_' . $fid . '_' . $this->randomMachineName(),
181 $translation->{$this->fieldName}[] = $item;
183 // Again store the generated values keying them by fid for easier lookup.
184 $values[$langcode][$fid] = $item;
187 // Perform synchronization: the translation language is used as source,
188 // while the default language is used as target.
189 $this->manager->getTranslationMetadata($translation)->setSource($default_langcode);
190 $entity = $this->saveEntity($translation);
191 $translation = $entity->getTranslation($langcode);
193 // Check that one value has been dropped from the original values.
194 $assert = count($entity->{$this->fieldName}) == 2;
195 $this->assertTrue($assert, 'One item correctly removed from the synchronized field values.');
197 // Check that fids have been synchronized and translatable column values
198 // have been retained.
200 foreach ($entity->{$this->fieldName} as $delta => $item) {
201 $value = $values[$default_langcode][$item->target_id];
202 $source_item = $translation->{$this->fieldName}->get($delta);
203 $assert = $item->target_id == $source_item->target_id && $item->alt == $value['alt'] && $item->title == $value['title'];
204 $this->assertTrue($assert, format_string('Field item @fid has been successfully synchronized.', ['@fid' => $item->target_id]));
205 $fids[$item->target_id] = TRUE;
208 // Check that the dropped value is the right one.
209 $removed_fid = $this->files[0]->fid;
210 $this->assertTrue(!isset($fids[$removed_fid]), format_string('Field item @fid has been correctly removed.', ['@fid' => $removed_fid]));
212 // Add back an item for the dropped value and perform synchronization again.
213 $values[$langcode][$removed_fid] = [
214 'target_id' => $removed_fid,
215 'alt' => $langcode . '_' . $removed_fid . '_' . $this->randomMachineName(),
216 'title' => $langcode . '_' . $removed_fid . '_' . $this->randomMachineName(),
218 $translation->{$this->fieldName}->setValue(array_values($values[$langcode]));
219 $entity = $this->saveEntity($translation);
220 $translation = $entity->getTranslation($langcode);
222 // Check that the value has been added to the default language.
223 $assert = count($entity->{$this->fieldName}->getValue()) == 3;
224 $this->assertTrue($assert, 'One item correctly added to the synchronized field values.');
226 foreach ($entity->{$this->fieldName} as $delta => $item) {
227 // When adding an item its value is copied over all the target languages,
228 // thus in this case the source language needs to be used to check the
229 // values instead of the target one.
230 $fid_langcode = $item->target_id != $removed_fid ? $default_langcode : $langcode;
231 $value = $values[$fid_langcode][$item->target_id];
232 $source_item = $translation->{$this->fieldName}->get($delta);
233 $assert = $item->target_id == $source_item->target_id && $item->alt == $value['alt'] && $item->title == $value['title'];
234 $this->assertTrue($assert, format_string('Field item @fid has been successfully synchronized.', ['@fid' => $item->target_id]));
239 * Saves the passed entity and reloads it, enabling compatibility mode.
241 * @param \Drupal\Core\Entity\EntityInterface $entity
242 * The entity to be saved.
244 * @return \Drupal\Core\Entity\EntityInterface
247 protected function saveEntity(EntityInterface $entity) {
249 $entity = entity_test_mul_load($entity->id(), TRUE);