Mercurial > hg > isophonics-drupal-site
comparison core/modules/content_translation/src/FieldTranslationSynchronizer.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\content_translation; | |
4 | |
5 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface; | |
6 use Drupal\Core\Entity\ContentEntityInterface; | |
7 use Drupal\Core\Entity\EntityManagerInterface; | |
8 | |
9 /** | |
10 * Provides field translation synchronization capabilities. | |
11 */ | |
12 class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterface { | |
13 | |
14 /** | |
15 * The entity manager to use to load unchanged entities. | |
16 * | |
17 * @var \Drupal\Core\Entity\EntityManagerInterface | |
18 */ | |
19 protected $entityManager; | |
20 | |
21 /** | |
22 * Constructs a FieldTranslationSynchronizer object. | |
23 * | |
24 * @param \Drupal\Core\Entity\EntityManagerInterface $entityManager | |
25 * The entity manager. | |
26 */ | |
27 public function __construct(EntityManagerInterface $entityManager) { | |
28 $this->entityManager = $entityManager; | |
29 } | |
30 | |
31 /** | |
32 * {@inheritdoc} | |
33 */ | |
34 public function synchronizeFields(ContentEntityInterface $entity, $sync_langcode, $original_langcode = NULL) { | |
35 $translations = $entity->getTranslationLanguages(); | |
36 $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); | |
37 | |
38 // If we have no information about what to sync to, if we are creating a new | |
39 // entity, if we have no translations for the current entity and we are not | |
40 // creating one, then there is nothing to synchronize. | |
41 if (empty($sync_langcode) || $entity->isNew() || count($translations) < 2) { | |
42 return; | |
43 } | |
44 | |
45 // If the entity language is being changed there is nothing to synchronize. | |
46 $entity_type = $entity->getEntityTypeId(); | |
47 $entity_unchanged = isset($entity->original) ? $entity->original : $this->entityManager->getStorage($entity_type)->loadUnchanged($entity->id()); | |
48 if ($entity->getUntranslated()->language()->getId() != $entity_unchanged->getUntranslated()->language()->getId()) { | |
49 return; | |
50 } | |
51 | |
52 /** @var \Drupal\Core\Field\FieldItemListInterface $items */ | |
53 foreach ($entity as $field_name => $items) { | |
54 $field_definition = $items->getFieldDefinition(); | |
55 $field_type_definition = $field_type_manager->getDefinition($field_definition->getType()); | |
56 $column_groups = $field_type_definition['column_groups']; | |
57 | |
58 // Sync if the field is translatable, not empty, and the synchronization | |
59 // setting is enabled. | |
60 if ($field_definition instanceof ThirdPartySettingsInterface && $field_definition->isTranslatable() && !$items->isEmpty() && $translation_sync = $field_definition->getThirdPartySetting('content_translation', 'translation_sync')) { | |
61 // Retrieve all the untranslatable column groups and merge them into | |
62 // single list. | |
63 $groups = array_keys(array_diff($translation_sync, array_filter($translation_sync))); | |
64 | |
65 // If a group was selected has the require_all_groups_for_translation | |
66 // flag set, there are no untranslatable columns. This is done because | |
67 // the UI adds Javascript that disables the other checkboxes, so their | |
68 // values are not saved. | |
69 foreach (array_filter($translation_sync) as $group) { | |
70 if (!empty($column_groups[$group]['require_all_groups_for_translation'])) { | |
71 $groups = []; | |
72 break; | |
73 } | |
74 } | |
75 if (!empty($groups)) { | |
76 $columns = []; | |
77 foreach ($groups as $group) { | |
78 $info = $column_groups[$group]; | |
79 // A missing 'columns' key indicates we have a single-column group. | |
80 $columns = array_merge($columns, isset($info['columns']) ? $info['columns'] : [$group]); | |
81 } | |
82 if (!empty($columns)) { | |
83 $values = []; | |
84 foreach ($translations as $langcode => $language) { | |
85 $values[$langcode] = $entity->getTranslation($langcode)->get($field_name)->getValue(); | |
86 } | |
87 | |
88 // If a translation is being created, the original values should be | |
89 // used as the unchanged items. In fact there are no unchanged items | |
90 // to check against. | |
91 $langcode = $original_langcode ?: $sync_langcode; | |
92 $unchanged_items = $entity_unchanged->getTranslation($langcode)->get($field_name)->getValue(); | |
93 $this->synchronizeItems($values, $unchanged_items, $sync_langcode, array_keys($translations), $columns); | |
94 | |
95 foreach ($translations as $langcode => $language) { | |
96 $entity->getTranslation($langcode)->get($field_name)->setValue($values[$langcode]); | |
97 } | |
98 } | |
99 } | |
100 } | |
101 } | |
102 } | |
103 | |
104 /** | |
105 * {@inheritdoc} | |
106 */ | |
107 public function synchronizeItems(array &$values, array $unchanged_items, $sync_langcode, array $translations, array $columns) { | |
108 $source_items = $values[$sync_langcode]; | |
109 | |
110 // Make sure we can detect any change in the source items. | |
111 $change_map = []; | |
112 | |
113 // By picking the maximum size between updated and unchanged items, we make | |
114 // sure to process also removed items. | |
115 $total = max([count($source_items), count($unchanged_items)]); | |
116 | |
117 // As a first step we build a map of the deltas corresponding to the column | |
118 // values to be synchronized. Recording both the old values and the new | |
119 // values will allow us to detect any change in the order of the new items | |
120 // for each column. | |
121 for ($delta = 0; $delta < $total; $delta++) { | |
122 foreach (['old' => $unchanged_items, 'new' => $source_items] as $key => $items) { | |
123 if ($item_id = $this->itemHash($items, $delta, $columns)) { | |
124 $change_map[$item_id][$key][] = $delta; | |
125 } | |
126 } | |
127 } | |
128 | |
129 // Backup field values and the change map. | |
130 $original_field_values = $values; | |
131 $original_change_map = $change_map; | |
132 | |
133 // Reset field values so that no spurious one is stored. Source values must | |
134 // be preserved in any case. | |
135 $values = [$sync_langcode => $source_items]; | |
136 | |
137 // Update field translations. | |
138 foreach ($translations as $langcode) { | |
139 | |
140 // We need to synchronize only values different from the source ones. | |
141 if ($langcode != $sync_langcode) { | |
142 // Reinitialize the change map as it is emptied while processing each | |
143 // language. | |
144 $change_map = $original_change_map; | |
145 | |
146 // By using the maximum cardinality we ensure to process removed items. | |
147 for ($delta = 0; $delta < $total; $delta++) { | |
148 // By inspecting the map we built before we can tell whether a value | |
149 // has been created or removed. A changed value will be interpreted as | |
150 // a new value, in fact it did not exist before. | |
151 $created = TRUE; | |
152 $removed = TRUE; | |
153 $old_delta = NULL; | |
154 $new_delta = NULL; | |
155 | |
156 if ($item_id = $this->itemHash($source_items, $delta, $columns)) { | |
157 if (!empty($change_map[$item_id]['old'])) { | |
158 $old_delta = array_shift($change_map[$item_id]['old']); | |
159 } | |
160 if (!empty($change_map[$item_id]['new'])) { | |
161 $new_delta = array_shift($change_map[$item_id]['new']); | |
162 } | |
163 $created = $created && !isset($old_delta); | |
164 $removed = $removed && !isset($new_delta); | |
165 } | |
166 | |
167 // If an item has been removed we do not store its translations. | |
168 if ($removed) { | |
169 continue; | |
170 } | |
171 // If a synchronized column has changed or has been created from | |
172 // scratch we need to replace the values for this language as a | |
173 // combination of the values that need to be synced from the source | |
174 // items and the other columns from the existing values. This only | |
175 // works if the delta exists in the language. | |
176 elseif ($created && !empty($original_field_values[$langcode][$delta])) { | |
177 $item_columns_to_sync = array_intersect_key($source_items[$delta], array_flip($columns)); | |
178 $item_columns_to_keep = array_diff_key($original_field_values[$langcode][$delta], array_flip($columns)); | |
179 $values[$langcode][$delta] = $item_columns_to_sync + $item_columns_to_keep; | |
180 } | |
181 // If the delta doesn't exist, copy from the source language. | |
182 elseif ($created) { | |
183 $values[$langcode][$delta] = $source_items[$delta]; | |
184 } | |
185 // Otherwise the current item might have been reordered. | |
186 elseif (isset($old_delta) && isset($new_delta)) { | |
187 // If for any reason the old value is not defined for the current | |
188 // language we fall back to the new source value, this way we ensure | |
189 // the new values are at least propagated to all the translations. | |
190 // If the value has only been reordered we just move the old one in | |
191 // the new position. | |
192 $item = isset($original_field_values[$langcode][$old_delta]) ? $original_field_values[$langcode][$old_delta] : $source_items[$new_delta]; | |
193 $values[$langcode][$new_delta] = $item; | |
194 } | |
195 } | |
196 } | |
197 } | |
198 } | |
199 | |
200 /** | |
201 * Computes a hash code for the specified item. | |
202 * | |
203 * @param array $items | |
204 * An array of field items. | |
205 * @param int $delta | |
206 * The delta identifying the item to be processed. | |
207 * @param array $columns | |
208 * An array of column names to be synchronized. | |
209 * | |
210 * @returns string | |
211 * A hash code that can be used to identify the item. | |
212 */ | |
213 protected function itemHash(array $items, $delta, array $columns) { | |
214 $values = []; | |
215 | |
216 if (isset($items[$delta])) { | |
217 foreach ($columns as $column) { | |
218 if (!empty($items[$delta][$column])) { | |
219 $value = $items[$delta][$column]; | |
220 // String and integer values are by far the most common item values, | |
221 // thus we special-case them to improve performance. | |
222 $values[] = is_string($value) || is_int($value) ? $value : hash('sha256', serialize($value)); | |
223 } | |
224 else { | |
225 // Explicitly track also empty values. | |
226 $values[] = ''; | |
227 } | |
228 } | |
229 } | |
230 | |
231 return implode('.', $values); | |
232 } | |
233 | |
234 } |