Mercurial > hg > isophonics-drupal-site
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/content_translation/src/FieldTranslationSynchronizer.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,234 @@ +<?php + +namespace Drupal\content_translation; + +use Drupal\Core\Config\Entity\ThirdPartySettingsInterface; +use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityManagerInterface; + +/** + * Provides field translation synchronization capabilities. + */ +class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterface { + + /** + * The entity manager to use to load unchanged entities. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * Constructs a FieldTranslationSynchronizer object. + * + * @param \Drupal\Core\Entity\EntityManagerInterface $entityManager + * The entity manager. + */ + public function __construct(EntityManagerInterface $entityManager) { + $this->entityManager = $entityManager; + } + + /** + * {@inheritdoc} + */ + public function synchronizeFields(ContentEntityInterface $entity, $sync_langcode, $original_langcode = NULL) { + $translations = $entity->getTranslationLanguages(); + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + + // If we have no information about what to sync to, if we are creating a new + // entity, if we have no translations for the current entity and we are not + // creating one, then there is nothing to synchronize. + if (empty($sync_langcode) || $entity->isNew() || count($translations) < 2) { + return; + } + + // If the entity language is being changed there is nothing to synchronize. + $entity_type = $entity->getEntityTypeId(); + $entity_unchanged = isset($entity->original) ? $entity->original : $this->entityManager->getStorage($entity_type)->loadUnchanged($entity->id()); + if ($entity->getUntranslated()->language()->getId() != $entity_unchanged->getUntranslated()->language()->getId()) { + return; + } + + /** @var \Drupal\Core\Field\FieldItemListInterface $items */ + foreach ($entity as $field_name => $items) { + $field_definition = $items->getFieldDefinition(); + $field_type_definition = $field_type_manager->getDefinition($field_definition->getType()); + $column_groups = $field_type_definition['column_groups']; + + // Sync if the field is translatable, not empty, and the synchronization + // setting is enabled. + if ($field_definition instanceof ThirdPartySettingsInterface && $field_definition->isTranslatable() && !$items->isEmpty() && $translation_sync = $field_definition->getThirdPartySetting('content_translation', 'translation_sync')) { + // Retrieve all the untranslatable column groups and merge them into + // single list. + $groups = array_keys(array_diff($translation_sync, array_filter($translation_sync))); + + // If a group was selected has the require_all_groups_for_translation + // flag set, there are no untranslatable columns. This is done because + // the UI adds Javascript that disables the other checkboxes, so their + // values are not saved. + foreach (array_filter($translation_sync) as $group) { + if (!empty($column_groups[$group]['require_all_groups_for_translation'])) { + $groups = []; + break; + } + } + if (!empty($groups)) { + $columns = []; + foreach ($groups as $group) { + $info = $column_groups[$group]; + // A missing 'columns' key indicates we have a single-column group. + $columns = array_merge($columns, isset($info['columns']) ? $info['columns'] : [$group]); + } + if (!empty($columns)) { + $values = []; + foreach ($translations as $langcode => $language) { + $values[$langcode] = $entity->getTranslation($langcode)->get($field_name)->getValue(); + } + + // If a translation is being created, the original values should be + // used as the unchanged items. In fact there are no unchanged items + // to check against. + $langcode = $original_langcode ?: $sync_langcode; + $unchanged_items = $entity_unchanged->getTranslation($langcode)->get($field_name)->getValue(); + $this->synchronizeItems($values, $unchanged_items, $sync_langcode, array_keys($translations), $columns); + + foreach ($translations as $langcode => $language) { + $entity->getTranslation($langcode)->get($field_name)->setValue($values[$langcode]); + } + } + } + } + } + } + + /** + * {@inheritdoc} + */ + public function synchronizeItems(array &$values, array $unchanged_items, $sync_langcode, array $translations, array $columns) { + $source_items = $values[$sync_langcode]; + + // Make sure we can detect any change in the source items. + $change_map = []; + + // By picking the maximum size between updated and unchanged items, we make + // sure to process also removed items. + $total = max([count($source_items), count($unchanged_items)]); + + // As a first step we build a map of the deltas corresponding to the column + // values to be synchronized. Recording both the old values and the new + // values will allow us to detect any change in the order of the new items + // for each column. + for ($delta = 0; $delta < $total; $delta++) { + foreach (['old' => $unchanged_items, 'new' => $source_items] as $key => $items) { + if ($item_id = $this->itemHash($items, $delta, $columns)) { + $change_map[$item_id][$key][] = $delta; + } + } + } + + // Backup field values and the change map. + $original_field_values = $values; + $original_change_map = $change_map; + + // Reset field values so that no spurious one is stored. Source values must + // be preserved in any case. + $values = [$sync_langcode => $source_items]; + + // Update field translations. + foreach ($translations as $langcode) { + + // We need to synchronize only values different from the source ones. + if ($langcode != $sync_langcode) { + // Reinitialize the change map as it is emptied while processing each + // language. + $change_map = $original_change_map; + + // By using the maximum cardinality we ensure to process removed items. + for ($delta = 0; $delta < $total; $delta++) { + // By inspecting the map we built before we can tell whether a value + // has been created or removed. A changed value will be interpreted as + // a new value, in fact it did not exist before. + $created = TRUE; + $removed = TRUE; + $old_delta = NULL; + $new_delta = NULL; + + if ($item_id = $this->itemHash($source_items, $delta, $columns)) { + if (!empty($change_map[$item_id]['old'])) { + $old_delta = array_shift($change_map[$item_id]['old']); + } + if (!empty($change_map[$item_id]['new'])) { + $new_delta = array_shift($change_map[$item_id]['new']); + } + $created = $created && !isset($old_delta); + $removed = $removed && !isset($new_delta); + } + + // If an item has been removed we do not store its translations. + if ($removed) { + continue; + } + // If a synchronized column has changed or has been created from + // scratch we need to replace the values for this language as a + // combination of the values that need to be synced from the source + // items and the other columns from the existing values. This only + // works if the delta exists in the language. + elseif ($created && !empty($original_field_values[$langcode][$delta])) { + $item_columns_to_sync = array_intersect_key($source_items[$delta], array_flip($columns)); + $item_columns_to_keep = array_diff_key($original_field_values[$langcode][$delta], array_flip($columns)); + $values[$langcode][$delta] = $item_columns_to_sync + $item_columns_to_keep; + } + // If the delta doesn't exist, copy from the source language. + elseif ($created) { + $values[$langcode][$delta] = $source_items[$delta]; + } + // Otherwise the current item might have been reordered. + elseif (isset($old_delta) && isset($new_delta)) { + // If for any reason the old value is not defined for the current + // language we fall back to the new source value, this way we ensure + // the new values are at least propagated to all the translations. + // If the value has only been reordered we just move the old one in + // the new position. + $item = isset($original_field_values[$langcode][$old_delta]) ? $original_field_values[$langcode][$old_delta] : $source_items[$new_delta]; + $values[$langcode][$new_delta] = $item; + } + } + } + } + } + + /** + * Computes a hash code for the specified item. + * + * @param array $items + * An array of field items. + * @param int $delta + * The delta identifying the item to be processed. + * @param array $columns + * An array of column names to be synchronized. + * + * @returns string + * A hash code that can be used to identify the item. + */ + protected function itemHash(array $items, $delta, array $columns) { + $values = []; + + if (isset($items[$delta])) { + foreach ($columns as $column) { + if (!empty($items[$delta][$column])) { + $value = $items[$delta][$column]; + // String and integer values are by far the most common item values, + // thus we special-case them to improve performance. + $values[] = is_string($value) || is_int($value) ? $value : hash('sha256', serialize($value)); + } + else { + // Explicitly track also empty values. + $values[] = ''; + } + } + } + + return implode('.', $values); + } + +}