diff -u b/core/modules/config_translation/migration_templates/d6_i18n_user_profile_field_instance.yml b/core/modules/config_translation/migration_templates/d6_i18n_user_profile_field_instance.yml --- b/core/modules/config_translation/migration_templates/d6_i18n_user_profile_field_instance.yml +++ b/core/modules/config_translation/migration_templates/d6_i18n_user_profile_field_instance.yml @@ -22,6 +22,7 @@ translation: translation destination: plugin: entity:field_config + translations: true migration_dependencies: required: - user_profile_field diff -u b/core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nUserProfileFieldInstanceTest.php b/core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nUserProfileFieldInstanceTest.php --- b/core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nUserProfileFieldInstanceTest.php +++ b/core/modules/config_translation/tests/src/Kernel/Migrate/d6/MigrateI18nUserProfileFieldInstanceTest.php @@ -21,17 +21,17 @@ */ protected function setUp() { parent::setUp(); - $this->executeMigrations([ - 'user_profile_field', - 'user_profile_field_instance', - 'd6_i18n_user_profile_field_instance', - ]); } /** * Tests migration of translated user profile fields. */ public function testUserProfileFields() { + $this->executeMigrations([ + 'user_profile_field', + 'user_profile_field_instance', + 'd6_i18n_user_profile_field_instance', + ]); $language_manager = $this->container->get('language_manager'); $config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.user.user.profile_love_migrations'); reverted: --- b/core/modules/config_translation/tests/src/Plugin/migrate/source/d6/I18nProfileFieldTest.php +++ /dev/null @@ -1,79 +0,0 @@ - [ - [ - 'fid' => 42, - 'title' => 'I love migrations', - 'name' => 'profile_love_migrations', - ], - ], - 'i18n_strings' => [ - [ - 'lid' => 10, - 'objectid' => 'profile_love_migrations', - 'type' => 'field', - 'property' => 'title', - ], - [ - 'lid' => 11, - 'objectid' => 'profile_love_migrations', - 'type' => 'field', - 'property' => 'explanation' - ] - ], - 'locales_target' => [ - [ - 'lid' => 10, - 'translation' => "J'aime les migrations.", - 'language' => 'fr', - ], - [ - 'lid' => 11, - 'translation' => 'Si vous cochez cette case, vous aimez les migrations.', - 'language' => 'fr', - ], - ], - ]; - $test[0]['expected_results'] = [ - [ - 'property' => 'title', - 'translation' => "J'aime les migrations.", - 'language' => 'fr', - 'fid' => '42', - 'name' => 'profile_love_migrations', - ], - [ - 'property' => 'explanation', - 'translation' => 'Si vous cochez cette case, vous aimez les migrations.', - 'language' => 'fr', - 'fid' => '42', - 'name' => 'profile_love_migrations', - ], - ]; - return $test; - } - -} diff -u b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php --- b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php @@ -7,7 +7,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\language\ConfigurableLanguageManagerInterface; +use Drupal\language\ConfigurableLanguageManager; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\MigrateIdMapInterface; @@ -33,7 +33,7 @@ */ protected $languageManager; - /** + /** * The configuration factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface; @@ -49,7 +49,7 @@ * The plugin_id for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. - * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * @param \Drupal\migrate\Plugin\MigrationInterface $migration * The migration. * @param \Drupal\Core\Entity\EntityStorageInterface $storage * The storage for this entity type. @@ -60,7 +60,7 @@ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration factory. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles); $this->languageManager = $language_manager; $this->configFactory = $config_factory; @@ -102,13 +102,26 @@ } } $entity = $this->getEntity($row, $old_destination_id_values); - $entity->save(); + // Translations are already saved in updateEntity by configuration override. + if (!$this->isTranslationDestination()) { + $entity->save(); + } if (count($ids) > 1) { // This can only be a config entity, content entities have their ID key // and that's it. $return = array(); foreach ($id_keys as $id_key) { - $return[] = $entity->get($id_key); + if ($this->isTranslationDestination()) { + if (($id_key == 'langcode')) { + $return[] = $row->getDestinationProperty($id_key); + } + else { + $return[] = $entity->get($id_key); + } + } + else { + $return[] = $entity->get($id_key); + } } return $return; } @@ -116,11 +129,24 @@ } /** + * Get whether this destination is for translations. + * + * @return bool + * Whether this destination is for translations. + */ + protected function isTranslationDestination() { + return !empty($this->configuration['translations']); + } + + /** * {@inheritdoc} */ public function getIds() { $id_key = $this->getKey('id'); $ids[$id_key]['type'] = 'string'; + if ($this->isTranslationDestination()) { + $ids['langcode']['type'] = 'string'; + } return $ids; } @@ -136,7 +162,7 @@ // This is a translation if the language in the active config matches the // language of this row. $translation = FALSE; - if ($row->hasDestinationProperty('langcode') && $this->languageManager instanceof ConfigurableLanguageManagerInterface) { + if ($row->hasDestinationProperty('langcode') && $this->languageManager instanceof ConfigurableLanguageManager) { $config = $entity->getConfigDependencyName(); $langcode = $this->configFactory->get('langcode'); if ($langcode != $row->getDestinationProperty('langcode')) { @@ -153,8 +179,8 @@ foreach ($row->getRawDestination() as $property => $value) { $this->updateEntityProperty($entity, explode(Row::PROPERTY_SEPARATOR, $property), $value); } + $this->setRollbackAction($row->getIdMap()); } - $this->setRollbackAction($row->getIdMap()); } /** @@ -195,7 +221,36 @@ foreach ($ids as $id) { + if ($this->isTranslationDestination() && $id == 'langcode') { + continue; + } $id_values[] = $row->getDestinationProperty($id); } return implode('.', $id_values); } + /** + * {@inheritdoc} + */ + public function rollback(array $destination_identifier) { + if ($this->isTranslationDestination()) { + // The entity id does not include the langcode. + $id_values = array(); + foreach ($destination_identifier as $key => $value) { + if ($this->isTranslationDestination() && $key == 'langcode') { + continue; + } + $id_values[] = $value; + } + $entity_id = implode('.', $id_values); + $language = $destination_identifier['langcode']; + + $config = $this->storage->load($entity_id)->getConfigDependencyName(); + $config_override = $this->languageManager->getLanguageConfigOverride($language, $config); + // Rollback the translation. + $config_override->delete(); + } + else { + parent::rollback($destination_identifier); + } + } + } only in patch2: unchanged: --- /dev/null +++ b/core/modules/config_translation/tests/src/Kernel/Plugin/migrate/source/d6/I18nProfileFieldTest.php @@ -0,0 +1,79 @@ + [ + [ + 'fid' => 42, + 'title' => 'I love migrations', + 'name' => 'profile_love_migrations', + ], + ], + 'i18n_strings' => [ + [ + 'lid' => 10, + 'objectid' => 'profile_love_migrations', + 'type' => 'field', + 'property' => 'title', + ], + [ + 'lid' => 11, + 'objectid' => 'profile_love_migrations', + 'type' => 'field', + 'property' => 'explanation' + ] + ], + 'locales_target' => [ + [ + 'lid' => 10, + 'translation' => "J'aime les migrations.", + 'language' => 'fr', + ], + [ + 'lid' => 11, + 'translation' => 'Si vous cochez cette case, vous aimez les migrations.', + 'language' => 'fr', + ], + ], + ]; + $test[0]['expected_results'] = [ + [ + 'property' => 'title', + 'translation' => "J'aime les migrations.", + 'language' => 'fr', + 'fid' => '42', + 'name' => 'profile_love_migrations', + ], + [ + 'property' => 'explanation', + 'translation' => 'Si vous cochez cette case, vous aimez les migrations.', + 'language' => 'fr', + 'fid' => '42', + 'name' => 'profile_love_migrations', + ], + ]; + return $test; + } + +} only in patch2: unchanged: --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php @@ -18,15 +18,10 @@ public function getIds() { $ids['entity_type']['type'] = 'string'; $ids['bundle']['type'] = 'string'; $ids['field_name']['type'] = 'string'; + if ($this->isTranslationDestination()) { + $ids['langcode']['type'] = 'string'; + } return $ids; } - /** - * {@inheritdoc} - */ - public function rollback(array $destination_identifier) { - $destination_identifier = implode('.', $destination_identifier); - parent::rollback(array($destination_identifier)); - } - } only in patch2: unchanged: --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityConfigRollbackTest.php @@ -0,0 +1,171 @@ +installEntitySchema('taxonomy_vocabulary'); + $this->installEntitySchema('taxonomy_term'); + $this->installConfig(['taxonomy']); + } + + /** + * Tests rolling back configuration entity translations. + */ + public function testConfigEntityRollback() { + // We use vocabularies to demonstrate importing and rolling back + // configuration entities with translations. First, import vocabularies. + $vocabulary_data_rows = [ + ['id' => '1', 'name' => 'categories', 'weight' => '2'], + ['id' => '2', 'name' => 'tags', 'weight' => '1'], + ]; + $ids = ['id' => ['type' => 'integer']]; + $definition = [ + 'id' => 'vocabularies', + 'migration_tags' => ['Import and rollback test'], + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $vocabulary_data_rows, + 'ids' => $ids, + ], + 'process' => [ + 'vid' => 'id', + 'name' => 'name', + 'weight' => 'weight', + ], + 'destination' => ['plugin' => 'entity:taxonomy_vocabulary'], + ]; + + /** @var \Drupal\migrate\Plugin\Migration $vocabulary_migration */ + $vocabulary_migration = \Drupal::service('plugin.manager.migration') + ->createStubMigration($definition); + $vocabulary_id_map = $vocabulary_migration->getIdMap(); + + $this->assertTrue($vocabulary_migration->getDestinationPlugin() + ->supportsRollback()); + + // Import and validate vocabulary config entities were created. + $vocabulary_executable = new MigrateExecutable($vocabulary_migration, $this); + $vocabulary_executable->import(); + foreach ($vocabulary_data_rows as $row) { + /** @var Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load($row['id']); + $this->assertTrue($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); + $this->assertNotNull($map_row['destid1']); + } + + // Second, import translations of the vocabulary name property. + $vocabulary_i18n_data_rows = [ + [ + 'id' => '1', + 'name' => '1', + 'language' => 'fr', + 'property' => 'name', + 'translation' => 'fr - categories' + ], + [ + 'id' => '2', + 'name' => '2', + 'language' => 'fr', + 'property' => 'name', + 'translation' => 'fr - tags' + ], + ]; + $ids = [ + 'id' => ['type' => 'integer'], + 'language' => ['type' => 'string'], + ]; + $definition = [ + 'id' => 'i18n_vocabularies', + 'migration_tags' => ['Import and rollback test'], + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $vocabulary_i18n_data_rows, + 'ids' => $ids, + 'constants' => [ + 'name' => 'name', + ] + ], + 'process' => [ + 'vid' => 'id', + 'langcode' => 'language', + 'property' => 'constants/name', + 'translation' => 'translation', + ], + 'destination' => [ + 'plugin' => 'entity:taxonomy_vocabulary', + 'translations' => 'true', + ], + ]; + + $vocabulary_i18n__migration = \Drupal::service('plugin.manager.migration') + ->createStubMigration($definition); + $vocabulary_i18n_id_map = $vocabulary_i18n__migration->getIdMap(); + + $this->assertTrue($vocabulary_i18n__migration->getDestinationPlugin() + ->supportsRollback()); + + // Import and validate vocabulary config entities were created. + $vocabulary_i18n_executable = new MigrateExecutable($vocabulary_i18n__migration, $this); + $vocabulary_i18n_executable->import(); + + $language_manager = \Drupal::service('language_manager'); + foreach ($vocabulary_i18n_data_rows as $row) { + $langcode = $row['language']; + $id = 'taxonomy.vocabulary.' . $row['id']; + /** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */ + $config_translation = $language_manager->getLanguageConfigOverride($langcode, $id); + $this->assertSame($row['translation'], $config_translation->get('name')); + $map_row = $vocabulary_i18n_id_map->getRowBySource(['id' => $row['id'], 'language' => $row['language']]); + $this->assertNotNull($map_row['destid1']); + } + + // Perform the rollback and confirm the translation was deleted and the map + // table row removed. + $vocabulary_i18n_executable->rollback(); + foreach ($vocabulary_i18n_data_rows as $row) { + $langcode = $row['language']; + $id = 'taxonomy.vocabulary.' . $row['id']; + /** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */ + $config_translation = $language_manager->getLanguageConfigOverride($langcode, $id); + $this->assertNull($config_translation->get('name')); + $map_row = $vocabulary_i18n_id_map->getRowBySource(['id' => $row['id'], 'language' => $row['language']]); + $this->assertFalse($map_row); + } + + // Conform the Original vocabulary still exists. + foreach ($vocabulary_data_rows as $row) { + /** @var Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load($row['id']); + $this->assertTrue($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); + $this->assertNotNull($map_row['destid1']); + } + + } + +} only in patch2: unchanged: --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -642,6 +642,10 @@ class MigrateUpgradeForm extends ConfirmFormBase { 'source_module' => 'profile', 'destination_module' => 'user', ], + 'd6_i18n_user_profile_field_instance' => [ + 'source_module' => 'i18n', + 'destination_module' => 'user', + ], ]; /**