X-Git-Url: http://aleph1.co.uk/gitweb/?a=blobdiff_plain;f=web%2Fcore%2Fmodules%2Fcontent_translation%2Fsrc%2FFieldTranslationSynchronizer.php;h=da62b776990b7ed42c1c59f95377c380059cf654;hb=9424afc6c1f518c301bf87a23c047d1873435d05;hp=13b805df8ac2c8c097381647b10b39b85718252a;hpb=a2bd1bf0c2c1f1a17d188f4dc0726a45494cefae;p=yaffs-website diff --git a/web/core/modules/content_translation/src/FieldTranslationSynchronizer.php b/web/core/modules/content_translation/src/FieldTranslationSynchronizer.php index 13b805df8..da62b7769 100644 --- a/web/core/modules/content_translation/src/FieldTranslationSynchronizer.php +++ b/web/core/modules/content_translation/src/FieldTranslationSynchronizer.php @@ -5,6 +5,8 @@ namespace Drupal\content_translation; use Drupal\Core\Config\Entity\ThirdPartySettingsInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; /** * Provides field translation synchronization capabilities. @@ -18,14 +20,57 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf */ protected $entityManager; + /** + * The field type plugin manager. + * + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface + */ + protected $fieldTypeManager; + /** * Constructs a FieldTranslationSynchronizer object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entityManager * The entity manager. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager + * The field type plugin manager. */ - public function __construct(EntityManagerInterface $entityManager) { + public function __construct(EntityManagerInterface $entityManager, FieldTypePluginManagerInterface $field_type_manager) { $this->entityManager = $entityManager; + $this->fieldTypeManager = $field_type_manager; + } + + /** + * {@inheritdoc} + */ + public function getFieldSynchronizedProperties(FieldDefinitionInterface $field_definition) { + $properties = []; + $settings = $this->getFieldSynchronizationSettings($field_definition); + foreach ($settings as $group => $translatable) { + if (!$translatable) { + $field_type_definition = $this->fieldTypeManager->getDefinition($field_definition->getType()); + if (!empty($field_type_definition['column_groups'][$group]['columns'])) { + $properties = array_merge($properties, $field_type_definition['column_groups'][$group]['columns']); + } + } + } + return $properties; + } + + /** + * Returns the synchronization settings for the specified field. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * A field definition. + * + * @return string[] + * An array of synchronized field property names. + */ + protected function getFieldSynchronizationSettings(FieldDefinitionInterface $field_definition) { + if ($field_definition instanceof ThirdPartySettingsInterface && $field_definition->isTranslatable()) { + return $field_definition->getThirdPartySetting('content_translation', 'translation_sync', []); + } + return []; } /** @@ -33,7 +78,6 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf */ 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 @@ -43,21 +87,55 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf } // 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()); + $entity_unchanged = $this->getOriginalEntity($entity); if ($entity->getUntranslated()->language()->getId() != $entity_unchanged->getUntranslated()->language()->getId()) { return; } + if ($entity->isNewRevision()) { + if ($entity->isDefaultTranslationAffectedOnly()) { + // If changes to untranslatable fields are configured to affect only the + // default translation, we need to skip synchronization in pending + // revisions, otherwise multiple translations would be affected. + if (!$entity->isDefaultRevision()) { + return; + } + // When this mode is enabled, changes to synchronized properties are + // allowed only in the default translation, thus we need to make sure this + // is always used as source for the synchronization process. + else { + $sync_langcode = $entity->getUntranslated()->language()->getId(); + } + } + elseif ($entity->isDefaultRevision()) { + // If a new default revision is being saved, but a newer default + // revision was created meanwhile, use any other translation as source + // for synchronization, since that will have been merged from the + // default revision. In this case the actual language does not matter as + // synchronized properties are the same for all the translations in the + // default revision. + /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */ + $default_revision = $this->entityManager + ->getStorage($entity->getEntityTypeId()) + ->load($entity->id()); + if ($default_revision->getLoadedRevisionId() !== $entity->getLoadedRevisionId()) { + $other_langcodes = array_diff_key($default_revision->getTranslationLanguages(), [$sync_langcode => FALSE]); + if ($other_langcodes) { + $sync_langcode = key($other_langcodes); + } + } + } + } + /** @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()); + $field_type_definition = $this->fieldTypeManager->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')) { + if (($translation_sync = $this->getFieldSynchronizationSettings($field_definition)) && !$items->isEmpty()) { // Retrieve all the untranslatable column groups and merge them into // single list. $groups = array_keys(array_diff($translation_sync, array_filter($translation_sync))); @@ -101,10 +179,30 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf } } + /** + * Returns the original unchanged entity to be used to detect changes. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity being changed. + * + * @return \Drupal\Core\Entity\ContentEntityInterface + * The unchanged entity. + */ + protected function getOriginalEntity(ContentEntityInterface $entity) { + if (!isset($entity->original)) { + $storage = $this->entityManager->getStorage($entity->getEntityTypeId()); + $original = $entity->isDefaultRevision() ? $storage->loadUnchanged($entity->id()) : $storage->loadRevision($entity->getLoadedRevisionId()); + } + else { + $original = $entity->original; + } + return $original; + } + /** * {@inheritdoc} */ - public function synchronizeItems(array &$values, array $unchanged_items, $sync_langcode, array $translations, array $columns) { + public function synchronizeItems(array &$values, array $unchanged_items, $sync_langcode, array $translations, array $properties) { $source_items = $values[$sync_langcode]; // Make sure we can detect any change in the source items. @@ -120,7 +218,7 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf // 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)) { + if ($item_id = $this->itemHash($items, $delta, $properties)) { $change_map[$item_id][$key][] = $delta; } } @@ -153,7 +251,7 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf $old_delta = NULL; $new_delta = NULL; - if ($item_id = $this->itemHash($source_items, $delta, $columns)) { + if ($item_id = $this->itemHash($source_items, $delta, $properties)) { if (!empty($change_map[$item_id]['old'])) { $old_delta = array_shift($change_map[$item_id]['old']); } @@ -174,9 +272,7 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf // 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; + $values[$langcode][$delta] = $this->createMergedItem($source_items[$delta], $original_field_values[$langcode][$delta], $properties); } // If the delta doesn't exist, copy from the source language. elseif ($created) { @@ -190,13 +286,37 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf // 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; + // When saving a default revision starting from a pending revision, + // we may have desynchronized field values, so we make sure that + // untranslatable properties are synchronized, even if in any other + // situation this would not be necessary. + $values[$langcode][$new_delta] = $this->createMergedItem($source_items[$new_delta], $item, $properties); } } } } } + /** + * Creates a merged item. + * + * @param array $source_item + * An item containing the untranslatable properties to be synchronized. + * @param array $target_item + * An item containing the translatable properties to be kept. + * @param string[] $properties + * An array of properties to be synchronized. + * + * @return array + * A merged item array. + */ + protected function createMergedItem(array $source_item, array $target_item, array $properties) { + $property_keys = array_flip($properties); + $item_properties_to_sync = array_intersect_key($source_item, $property_keys); + $item_properties_to_keep = array_diff_key($target_item, $property_keys); + return $item_properties_to_sync + $item_properties_to_keep; + } + /** * Computes a hash code for the specified item. * @@ -204,19 +324,19 @@ class FieldTranslationSynchronizer implements FieldTranslationSynchronizerInterf * An array of field items. * @param int $delta * The delta identifying the item to be processed. - * @param array $columns + * @param array $properties * 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) { + protected function itemHash(array $items, $delta, array $properties) { $values = []; if (isset($items[$delta])) { - foreach ($columns as $column) { - if (!empty($items[$delta][$column])) { - $value = $items[$delta][$column]; + foreach ($properties as $property) { + if (!empty($items[$delta][$property])) { + $value = $items[$delta][$property]; // 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));