diff --git a/core/modules/book/tests/src/Kernel/Migrate/d6/MigrateBookTest.php b/core/modules/book/tests/src/Kernel/Migrate/d6/MigrateBookTest.php index ffcc3fe..2e69972 100644 --- a/core/modules/book/tests/src/Kernel/Migrate/d6/MigrateBookTest.php +++ b/core/modules/book/tests/src/Kernel/Migrate/d6/MigrateBookTest.php @@ -15,7 +15,7 @@ class MigrateBookTest extends MigrateDrupal6TestBase { /** * {@inheritdoc} */ - public static $modules = ['book']; + public static $modules = ['language', 'book']; /** * {@inheritdoc} diff --git a/core/modules/comment/tests/src/Kernel/Migrate/d6/MigrateCommentTest.php b/core/modules/comment/tests/src/Kernel/Migrate/d6/MigrateCommentTest.php index 5330e94..9c2af9a 100644 --- a/core/modules/comment/tests/src/Kernel/Migrate/d6/MigrateCommentTest.php +++ b/core/modules/comment/tests/src/Kernel/Migrate/d6/MigrateCommentTest.php @@ -17,7 +17,7 @@ class MigrateCommentTest extends MigrateDrupal6TestBase { /** * {@inheritdoc} */ - public static $modules = ['comment']; + public static $modules = ['language', 'comment']; /** * {@inheritdoc} @@ -25,6 +25,7 @@ class MigrateCommentTest extends MigrateDrupal6TestBase { protected function setUp() { parent::setUp(); + $this->installSchema('node', ['node_access']); $this->installEntitySchema('node'); $this->installEntitySchema('comment'); $this->installSchema('comment', ['comment_entity_statistics']); diff --git a/core/modules/file/tests/src/Kernel/Migrate/d6/MigrateUploadTest.php b/core/modules/file/tests/src/Kernel/Migrate/d6/MigrateUploadTest.php index 463ce02..0de0007 100644 --- a/core/modules/file/tests/src/Kernel/Migrate/d6/MigrateUploadTest.php +++ b/core/modules/file/tests/src/Kernel/Migrate/d6/MigrateUploadTest.php @@ -16,6 +16,11 @@ class MigrateUploadTest extends MigrateDrupal6TestBase { /** * {@inheritdoc} */ + public static $modules = ['language']; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); diff --git a/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php b/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php index b4cf30b..18a95e6 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/DestinationBase.php @@ -94,8 +94,10 @@ public function supportsRollback() { * * @param array $id_map * The map row data for the item. + * @param int $update_action + * The rollback action to take if we are updating an existing item. */ - protected function setRollbackAction(array $id_map) { + protected function setRollbackAction(array $id_map, $update_action = MigrateIdMapInterface::ROLLBACK_PRESERVE) { // If the entity we're updating was previously migrated by us, preserve the // existing rollback action. if (isset($id_map['sourceid1'])) { @@ -104,7 +106,7 @@ protected function setRollbackAction(array $id_map) { // Otherwise, we're updating an entity which already existed on the // destination and want to make sure we do not delete it on rollback. else { - $this->rollbackAction = MigrateIdMapInterface::ROLLBACK_PRESERVE; + $this->rollbackAction = $update_action; } } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/Entity.php b/core/modules/migrate/src/Plugin/migrate/destination/Entity.php index 55c59fc..a534851 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/Entity.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/Entity.php @@ -109,7 +109,8 @@ public function fields(MigrationInterface $migration = NULL) { protected function getEntity(Row $row, array $old_destination_id_values) { $entity_id = reset($old_destination_id_values) ?: $this->getEntityId($row); if (!empty($entity_id) && ($entity = $this->storage->load($entity_id))) { - $this->updateEntity($entity, $row); + // Allow updateEntity() to change the entity. + $entity = $this->updateEntity($entity, $row) ?: $entity; } else { // Stubs might need some required fields filled in. diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php index 0cc00d6..86ed601 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityConfigBase.php @@ -68,6 +68,9 @@ public function getIds() { * The entity to update. * @param \Drupal\migrate\Row $row * The row object to update from. + * + * @return NULL|\Drupal\Core\Entity\EntityInterface + * An updated entity, or NULL if it's the same as the one passed in. */ protected function updateEntity(EntityInterface $entity, Row $row) { foreach ($row->getRawDestination() as $property => $value) { @@ -75,6 +78,7 @@ protected function updateEntity(EntityInterface $entity, Row $row) { } $this->setRollbackAction($row->getIdMap()); + return $entity; } /** diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index 6a2fb98..ff22336 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Core\TypedData\TranslatableInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateException; @@ -85,7 +86,12 @@ public function import(Row $row, array $old_destination_id_values = array()) { if (!$entity) { throw new MigrateException('Unable to get entity'); } - return $this->save($entity, $old_destination_id_values); + + $ids = $this->save($entity, $old_destination_id_values); + if (!empty($this->configuration['translations'])) { + $ids[] = $entity->language()->getId(); + } + return $ids; } /** @@ -105,14 +111,36 @@ protected function save(ContentEntityInterface $entity, array $old_destination_i } /** - * {@inheritdoc} + * Get the base IDs of this entity, not including dynamically determined IDs. + * + * @return array + * An array of IDs. */ - public function getIds() { + protected function baseIds() { $id_key = $this->getKey('id'); $ids[$id_key]['type'] = 'integer'; return $ids; } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids = $this->baseIds(); + + if (!empty($this->configuration['translations'])) { + if ($key = $this->getKey('langcode')) { + $ids[$key]['type'] = 'string'; + } + else { + throw new MigrateException('This entity type does not support translation.'); + } + } + + return $ids; + } + /** * Updates an entity with the new values from row. * @@ -122,6 +150,24 @@ public function getIds() { * The row object to update from. */ protected function updateEntity(EntityInterface $entity, Row $row) { + // By default, an update will be preserved. + $rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE; + + // Make sure we have the right translation. + if ($entity instanceof TranslatableInterface) { + $property = $this->storage->getEntityType()->getKey('langcode'); + if ($row->hasDestinationProperty($property)) { + $language = $row->getDestinationProperty($property); + if (!$entity->hasTranslation($language)) { + $entity->addTranslation($language); + + // We're adding a translation, so delete it on rollback. + $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE; + } + $entity = $entity->getTranslation($language); + } + } + // If the migration has specified a list of properties to be overwritten, // clone the row with an empty set of destination values, and re-add only // the specified properties. @@ -140,7 +186,10 @@ protected function updateEntity(EntityInterface $entity, Row $row) { } } - $this->setRollbackAction($row->getIdMap()); + $this->setRollbackAction($row->getIdMap(), $rollback_action); + + // We might have a different (translated) entity, so return it. + return $entity; } /** @@ -185,4 +234,32 @@ protected function processStubRow(Row $row) { } } + /** + * {@inheritdoc} + */ + public function rollback(array $destination_identifier) { + if (empty($this->configuration['translations'])) { + parent::rollback($destination_identifier); + } + else { + // Attempt to remove the translation. + $entity = $this->storage->load(reset($destination_identifier)); + if ($entity && $entity instanceof TranslatableInterface) { + if ($key = $this->getKey('langcode')) { + if (isset($destination_identifier[$key])) { + $langcode = $destination_identifier[$key]; + if ($entity->hasTranslation($langcode)) { + // Make sure we don't remove the default translation. + $translation = $entity->getTranslation($langcode); + if (!$translation->isDefaultTranslation()) { + $entity->removeTranslation($langcode); + $entity->save(); + } + } + } + } + } + } + } + } diff --git a/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml new file mode 100644 index 0000000..065d512 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrate_external_translated_test.info.yml @@ -0,0 +1,8 @@ +name: 'Migration external translated test' +type: module +package: Testing +version: VERSION +core: 8.x +dependencies: + - node + - migrate diff --git a/core/modules/migrate/tests/modules/migrate_external_translated_test/migrations/migrate.migration.external_translated_test_node.yml b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrations/migrate.migration.external_translated_test_node.yml new file mode 100644 index 0000000..f643b60 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrations/migrate.migration.external_translated_test_node.yml @@ -0,0 +1,19 @@ +id: external_translated_test_node +label: External translated content +source: + plugin: migrate_external_translated_test + default_lang: true + constants: + type: external_test +process: + type: constants/type + title: title + langcode: + plugin: static_map + source: lang + map: + English: en + French: fr + Spanish: es +destination: + plugin: entity:node diff --git a/core/modules/migrate/tests/modules/migrate_external_translated_test/migrations/migrate.migration.external_translated_test_node_translation.yml b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrations/migrate.migration.external_translated_test_node_translation.yml new file mode 100644 index 0000000..ff29084 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_external_translated_test/migrations/migrate.migration.external_translated_test_node_translation.yml @@ -0,0 +1,27 @@ +id: external_translated_test_node_translation +label: External translated content translations +source: + plugin: migrate_external_translated_test + default_lang: false + constants: + type: external_test +process: + nid: + plugin: migration + source: name + migration: external_translated_test_node + type: constants/type + title: title + langcode: + plugin: static_map + source: lang + map: + English: en + French: fr + Spanish: es +destination: + plugin: entity:node + translations: true +migration_dependencies: + required: + - external_translated_test_node diff --git a/core/modules/migrate/tests/modules/migrate_external_translated_test/src/Plugin/migrate/source/MigrateExternalTranslatedTestSource.php b/core/modules/migrate/tests/modules/migrate_external_translated_test/src/Plugin/migrate/source/MigrateExternalTranslatedTestSource.php new file mode 100644 index 0000000..ceda6d8 --- /dev/null +++ b/core/modules/migrate/tests/modules/migrate_external_translated_test/src/Plugin/migrate/source/MigrateExternalTranslatedTestSource.php @@ -0,0 +1,77 @@ + 'cat', 'title' => 'Cat', 'lang' => 'English'], + ['name' => 'cat', 'title' => 'Chat', 'lang' => 'French'], + ['name' => 'cat', 'title' => 'Gato', 'lang' => 'Spanish'], + ['name' => 'dog', 'title' => 'Dog', 'lang' => 'English'], + ['name' => 'dog', 'title' => 'Chien', 'lang' => 'French'], + ['name' => 'monkey', 'title' => 'Monkey', 'lang' => 'English'], + ]; + + /** + * {@inheritdoc} + */ + public function fields() { + return [ + 'name' => $this->t('Unique name'), + 'title' => $this->t('Title'), + 'lang' => $this->t('Language'), + ]; + } + + /** + * {@inheritdoc} + */ + public function __toString() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids['name']['type'] = 'string'; + if (!$this->configuration['default_lang']) { + $ids['lang']['type'] = 'string'; + } + return $ids; + } + + /** + * {@inheritdoc} + */ + protected function initializeIterator() { + $data = []; + + // Keep the rows with the right languages. + $want_default = $this->configuration['default_lang']; + foreach ($this->import as $row) { + $is_english = $row['lang'] == 'English'; + if ($want_default == $is_english) { + $data[] = $row; + } + } + + return new \ArrayIterator($data); + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php new file mode 100644 index 0000000..352b660 --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/MigrateEntityContentBaseTest.php @@ -0,0 +1,159 @@ +installEntitySchema('entity_test_mul'); + + ConfigurableLanguage::createFromLangcode('en')->save(); + ConfigurableLanguage::createFromLangcode('fr')->save(); + + $this->storage = $this->container->get('entity.manager')->getStorage('entity_test_mul'); + } + + /** + * Check the existing translations of an entity. + * + * @param int $id + * The entity ID. + * @param string $default + * The expected default translation language code. + * @param string[] $others + * The expected other translation language codes. + */ + protected function assertTranslations($id, $default, $others = []) { + $entity = $this->storage->load($id); + $this->assertTrue($entity, "Entity exists"); + $this->assertEquals($default, $entity->language()->getId(), "Entity default translation"); + $translations = array_keys($entity->getTranslationLanguages(FALSE)); + sort($others); + sort($translations); + $this->assertEquals($others, $translations, "Entity translations"); + } + + /** + * Create the destination plugin to test. + * + * @param array $configuration + * The plugin configuration. + */ + protected function createDestination(array $configuration) { + $this->destination = new EntityContentBase( + $configuration, + 'fake_plugin_id', + [], + $this->getMock(MigrationInterface::class), + $this->storage, + [], + $this->container->get('entity.manager'), + $this->container->get('plugin.manager.field.field_type') + ); + } + + /** + * Test importing and rolling back translated entities. + */ + public function testTranslated() { + // Create a destination. + $this->createDestination(['translations' => TRUE]); + + // Create some pre-existing entities. + $this->storage->create(['id' => 1, 'langcode' => 'en'])->save(); + $this->storage->create(['id' => 2, 'langcode' => 'fr'])->save(); + $translated = $this->storage->create(['id' => 3, 'langcode' => 'en']); + $translated->save(); + $translated->addTranslation('fr')->save(); + + // Pre-assert that things are as expected. + $this->assertTranslations(1, 'en'); + $this->assertTranslations(2, 'fr'); + $this->assertTranslations(3, 'en', ['fr']); + $this->assertFalse($this->storage->load(4)); + + $destination_rows = [ + // Existing default translation. + ['id' => 1, 'langcode' => 'en', 'action' => MigrateIdMapInterface::ROLLBACK_PRESERVE], + // New translation. + ['id' => 2, 'langcode' => 'en', 'action' => MigrateIdMapInterface::ROLLBACK_DELETE], + // Existing non-default translation. + ['id' => 3, 'langcode' => 'fr', 'action' => MigrateIdMapInterface::ROLLBACK_PRESERVE], + // Brand new row. + ['id' => 4, 'langcode' => 'fr', 'action' => MigrateIdMapInterface::ROLLBACK_DELETE], + ]; + $rollback_actions = []; + + // Import some rows. + foreach ($destination_rows as $idx => $destination_row) { + $row = new Row([], []); + foreach ($destination_row as $key => $value) { + $row->setDestinationProperty($key, $value); + } + $this->destination->import($row); + + // Check that the rollback action is correct, and save it. + $this->assertEquals($destination_row['action'], $this->destination->rollbackAction()); + $rollback_actions[$idx] = $this->destination->rollbackAction(); + } + + $this->assertTranslations(1, 'en'); + $this->assertTranslations(2, 'fr', ['en']); + $this->assertTranslations(3, 'en', ['fr']); + $this->assertTranslations(4, 'fr'); + + // Rollback the rows. + foreach ($destination_rows as $idx => $destination_row) { + if ($rollback_actions[$idx] == MigrateIdMapInterface::ROLLBACK_DELETE) { + $this->destination->rollback($destination_row); + } + } + + // No change, update of existing translation. + $this->assertTranslations(1, 'en'); + // Remove added translation. + $this->assertTranslations(2, 'fr'); + // No change, update of existing translation. + $this->assertTranslations(3, 'en', ['fr']); + // No change, can't remove default translation. + $this->assertTranslations(4, 'fr'); + } + +} diff --git a/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php b/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php new file mode 100644 index 0000000..3eaa8ff --- /dev/null +++ b/core/modules/migrate/tests/src/Kernel/MigrateExternalTranslatedTest.php @@ -0,0 +1,92 @@ +installSchema('system', ['sequences']); + $this->installSchema('node', array('node_access')); + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + + // Create some languages. + ConfigurableLanguage::createFromLangcode('en')->save(); + ConfigurableLanguage::createFromLangcode('fr')->save(); + ConfigurableLanguage::createFromLangcode('es')->save(); + + // Create a content type. + NodeType::create([ + 'type' => 'external_test', + 'name' => 'Test node type', + ])->save(); + } + + /** + * Test importing and rolling back our data. + */ + public function testMigrations() { + /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ + $storage = $this->container->get('entity.manager')->getStorage('node'); + $this->assertEquals(0, count($storage->loadMultiple())); + + // Run the migrations. + $migration_ids = ['external_translated_test_node', 'external_translated_test_node_translation']; + $this->executeMigrations($migration_ids); + $this->assertEquals(3, count($storage->loadMultiple())); + + $node = $storage->load(1); + $this->assertEquals('en', $node->language()->getId()); + $this->assertEquals('Cat', $node->title->value); + $this->assertEquals('Chat', $node->getTranslation('fr')->title->value); + $this->assertEquals('Gato', $node->getTranslation('es')->title->value); + + $node = $storage->load(2); + $this->assertEquals('en', $node->language()->getId()); + $this->assertEquals('Dog', $node->title->value); + $this->assertEquals('Chien', $node->getTranslation('fr')->title->value); + $this->assertFalse($node->hasTranslation('es')); + + $node = $storage->load(3); + $this->assertEquals('en', $node->language()->getId()); + $this->assertEquals('Monkey', $node->title->value); + $this->assertFalse($node->hasTranslation('fr')); + $this->assertFalse($node->hasTranslation('es')); + + $this->assertNull($storage->load(4)); + + // Roll back the migrations. + foreach ($migration_ids as $migration_id) { + $migration = $this->getMigration($migration_id); + $executable = new MigrateExecutable($migration, $this); + $executable->rollback(); + } + + $this->assertEquals(0, count($storage->loadMultiple())); + } + +} diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal6.php b/core/modules/migrate_drupal/tests/fixtures/drupal6.php index 1ca67a5..9a55470 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal6.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal6.php @@ -34727,7 +34727,7 @@ 'access_callback' => 'user_access', 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', 'page_callback' => 'drupal_get_form', - 'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:2:"20";s:6:"custom";s:1:"0";s:8:"modified";s:1:"0";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}', + 'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"0";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}', 'fit' => '15', 'number_parts' => '4', 'tab_parent' => '', @@ -34749,7 +34749,7 @@ 'access_callback' => 'user_access', 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', 'page_callback' => 'drupal_get_form', - 'page_arguments' => 'a:2:{i:0;s:24:"node_type_delete_confirm";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:2:"20";s:6:"custom";s:1:"0";s:8:"modified";s:1:"0";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}', + 'page_arguments' => 'a:2:{i:0;s:24:"node_type_delete_confirm";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"0";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}', 'fit' => '31', 'number_parts' => '5', 'tab_parent' => '', @@ -34859,7 +34859,7 @@ 'access_callback' => 'user_access', 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', 'page_callback' => 'drupal_get_form', - 'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:2:"20";s:6:"custom";s:1:"0";s:8:"modified";s:1:"0";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}', + 'page_arguments' => 'a:2:{i:0;s:14:"node_type_form";i:1;O:8:"stdClass":14:{s:4:"type";s:7:"company";s:4:"name";s:7:"Company";s:6:"module";s:4:"node";s:11:"description";s:17:"Company node type";s:4:"help";s:0:"";s:9:"has_title";s:1:"1";s:11:"title_label";s:4:"Name";s:8:"has_body";s:1:"1";s:10:"body_label";s:11:"Description";s:14:"min_word_count";s:1:"0";s:6:"custom";s:1:"0";s:8:"modified";s:1:"1";s:6:"locked";s:1:"0";s:9:"orig_type";s:7:"company";}}', 'fit' => '31', 'number_parts' => '5', 'tab_parent' => 'admin/content/node-type/company', @@ -41338,18 +41338,35 @@ ->values(array( 'nid' => '9', 'vid' => '12', - 'type' => 'story', - 'language' => '', - 'title' => 'Once upon a time', + 'type' => 'article', + 'language' => 'en', + 'title' => 'The Real McCoy', 'uid' => '1', 'status' => '1', - 'created' => '1444671588', - 'changed' => '1444671588', + 'created' => '1444238800', + 'changed' => '1444238808', 'comment' => '2', 'promote' => '1', 'moderate' => '0', 'sticky' => '0', - 'tnid' => '0', + 'tnid' => '9', + 'translate' => '0', +)) +->values(array( + 'nid' => '10', + 'vid' => '13', + 'type' => 'article', + 'language' => 'fr', + 'title' => 'Le Vrai McCoy', + 'uid' => '1', + 'status' => '1', + 'created' => '1444239050', + 'changed' => '1444239050', + 'comment' => '2', + 'promote' => '1', + 'moderate' => '0', + 'sticky' => '0', + 'tnid' => '9', 'translate' => '0', )) ->execute(); @@ -41497,7 +41514,14 @@ )) ->values(array( 'nid' => '9', - 'last_comment_timestamp' => '1444671588', + 'last_comment_timestamp' => '1444238800', + 'last_comment_name' => NULL, + 'last_comment_uid' => '1', + 'comment_count' => '0', +)) +->values(array( + 'nid' => '10', + 'last_comment_timestamp' => '1444239050', 'last_comment_name' => NULL, 'last_comment_uid' => '1', 'comment_count' => '0', @@ -41738,11 +41762,22 @@ 'nid' => '9', 'vid' => '12', 'uid' => '1', - 'title' => 'Once upon a time', - 'body' => 'Come on kid, go to sleep.', - 'teaser' => 'Come on kid, go to sleep.', + 'title' => 'The Real McCoy', + 'body' => "In the original, Queen's English.", + 'teaser' => "In the original, Queen's English.", + 'log' => '', + 'timestamp' => '1444238808', + 'format' => '1', +)) +->values(array( + 'nid' => '10', + 'vid' => '13', + 'uid' => '1', + 'title' => 'Le Vrai McCoy', + 'body' => 'Ooh là là!', + 'teaser' => 'Ooh là là!', 'log' => '', - 'timestamp' => '1444671588', + 'timestamp' => '1444239050', 'format' => '1', )) ->execute(); @@ -41879,9 +41914,9 @@ 'title_label' => 'Name', 'has_body' => '1', 'body_label' => 'Description', - 'min_word_count' => '20', + 'min_word_count' => '0', 'custom' => '0', - 'modified' => '0', + 'modified' => '1', 'locked' => '0', 'orig_type' => 'company', )) @@ -44828,12 +44863,16 @@ 'value' => 's:1:"2";', )) ->values(array( + 'name' => 'comment_company', + 'value' => 's:1:"2";', +)) +->values(array( 'name' => 'comment_controls_article', 'value' => 'i:3;', )) ->values(array( 'name' => 'comment_controls_company', - 'value' => 'i:3;', + 'value' => 's:1:"3";', )) ->values(array( 'name' => 'comment_controls_employee', @@ -44873,7 +44912,7 @@ )) ->values(array( 'name' => 'comment_default_mode_company', - 'value' => 'i:4;', + 'value' => 's:1:"4";', )) ->values(array( 'name' => 'comment_default_mode_employee', @@ -44913,7 +44952,7 @@ )) ->values(array( 'name' => 'comment_default_order_company', - 'value' => 'i:1;', + 'value' => 's:1:"1";', )) ->values(array( 'name' => 'comment_default_order_employee', @@ -44953,7 +44992,7 @@ )) ->values(array( 'name' => 'comment_default_per_page_company', - 'value' => 'i:50;', + 'value' => 's:2:"50";', )) ->values(array( 'name' => 'comment_default_per_page_employee', @@ -44993,7 +45032,7 @@ )) ->values(array( 'name' => 'comment_form_location_company', - 'value' => 'i:0;', + 'value' => 's:1:"0";', )) ->values(array( 'name' => 'comment_form_location_employee', @@ -45037,7 +45076,7 @@ )) ->values(array( 'name' => 'comment_preview_company', - 'value' => 'i:1;', + 'value' => 's:1:"1";', )) ->values(array( 'name' => 'comment_preview_employee', @@ -45081,7 +45120,7 @@ )) ->values(array( 'name' => 'comment_subject_field_company', - 'value' => 'i:1;', + 'value' => 's:1:"1";', )) ->values(array( 'name' => 'comment_subject_field_employee', @@ -45444,6 +45483,10 @@ 'value' => 's:5:"never";', )) ->values(array( + 'name' => 'event_nodeapi_company', + 'value' => 's:5:"never";', +)) +->values(array( 'name' => 'event_nodeapi_event', 'value' => 's:3:"all";', )) @@ -45505,7 +45548,11 @@ )) ->values(array( 'name' => 'form_build_id_article', - 'value' => 's:48:"form-mXZfFJxcCFGB80PPYtNOuwYbho6-xKTvrRLb3TAMkic";', + 'value' => 's:48:"form-t2zKJflpBD4rpYoGQH33ckjjWAYdo5lF3Hl1O_YnWyE";', +)) +->values(array( + 'name' => 'form_build_id_company', + 'value' => 's:48:"form-jFw2agRukPxjG5dG-N6joZLyoxXmCoxTzua0HUciqK0";', )) ->values(array( 'name' => 'forum_block_num_0', @@ -45600,6 +45647,10 @@ 'value' => 'a:1:{i:0;s:6:"status";}', )) ->values(array( + 'name' => 'node_options_company', + 'value' => 'a:2:{i:0;s:6:"status";i:1;s:7:"promote";}', +)) +->values(array( 'name' => 'node_options_forum', 'value' => 'a:1:{i:0;s:6:"status";}', )) @@ -45796,6 +45847,10 @@ 'value' => 'b:0;', )) ->values(array( + 'name' => 'upload_company', + 'value' => 's:1:"1";', +)) +->values(array( 'name' => 'upload_page', 'value' => 'b:1;', )) diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6TestBase.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6TestBase.php index 761e718..f52164e 100644 --- a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6TestBase.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6TestBase.php @@ -86,11 +86,12 @@ protected function migrateFields() { * If TRUE, migrates node revisions. */ protected function migrateContent($include_revisions = FALSE) { + $this->executeMigrations(['language']); $this->migrateUsers(FALSE); $this->migrateFields(); $this->installEntitySchema('node'); - $this->executeMigrations(['d6_node_settings', 'd6_node']); + $this->executeMigrations(['d6_node_settings', 'd6_node', 'd6_node_translation']); if ($include_revisions) { $this->executeMigrations(['d6_node_revision']); diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php index 8ef5f48..f4f63ae 100644 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -290,6 +290,10 @@ class MigrateUpgradeForm extends ConfirmFormBase { 'source_module' => 'node', 'destination_module' => 'node', ], + 'd6_node_translation' => [ + 'source_module' => 'node', + 'destination_module' => 'node', + ], 'd6_node_revision' => [ 'source_module' => 'node', 'destination_module' => 'node', diff --git a/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php index 7a2d2c2..2c02041 100644 --- a/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php @@ -30,7 +30,7 @@ * * @var array */ - public static $modules = ['migrate_drupal_ui', 'telephone']; + public static $modules = ['language', 'migrate_drupal_ui', 'telephone']; /** * {@inheritdoc} diff --git a/core/modules/migrate_drupal_ui/src/Tests/d6/MigrateUpgrade6Test.php b/core/modules/migrate_drupal_ui/src/Tests/d6/MigrateUpgrade6Test.php index ea0b588..bbd5e97 100644 --- a/core/modules/migrate_drupal_ui/src/Tests/d6/MigrateUpgrade6Test.php +++ b/core/modules/migrate_drupal_ui/src/Tests/d6/MigrateUpgrade6Test.php @@ -40,8 +40,9 @@ protected function getEntityCounts() { 'comment' => 3, 'comment_type' => 2, 'contact_form' => 5, + 'configurable_language' => 5, 'editor' => 2, - 'field_config' => 62, + 'field_config' => 63, 'field_storage_config' => 43, 'file' => 7, 'filter_format' => 7, @@ -57,7 +58,7 @@ protected function getEntityCounts() { 'menu' => 8, 'taxonomy_term' => 6, 'taxonomy_vocabulary' => 6, - 'tour' => 1, + 'tour' => 4, 'user' => 7, 'user_role' => 6, 'menu_link_content' => 4, diff --git a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php index 1d3ce61..615f30b 100644 --- a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php @@ -39,6 +39,8 @@ protected function getEntityCounts() { 'block_content_type' => 1, 'comment' => 1, 'comment_type' => 7, + // Module 'language' comes with 'en', 'und', 'zxx'. Migration adds 'is'. + 'configurable_language' => 4, 'contact_form' => 3, 'editor' => 2, 'field_config' => 41, @@ -57,7 +59,7 @@ protected function getEntityCounts() { 'menu' => 10, 'taxonomy_term' => 18, 'taxonomy_vocabulary' => 3, - 'tour' => 1, + 'tour' => 4, 'user' => 3, 'user_role' => 4, 'menu_link_content' => 9, diff --git a/core/modules/node/migration_templates/d6_node.yml b/core/modules/node/migration_templates/d6_node.yml index 82571b8..414f751 100644 --- a/core/modules/node/migration_templates/d6_node.yml +++ b/core/modules/node/migration_templates/d6_node.yml @@ -6,7 +6,10 @@ deriver: Drupal\node\Plugin\migrate\D6NodeDeriver source: plugin: d6_node process: - nid: nid + # In D6, nodes always have a tnid, but it's zero for untranslated nodes. + # We normalize it to equal the nid in that case. + # @see \Drupal\node\Plugin\migrate\source\d6\Node::prepareRow(). + nid: tnid vid: vid type: type langcode: diff --git a/core/modules/node/migration_templates/d6_node_translation.yml b/core/modules/node/migration_templates/d6_node_translation.yml new file mode 100644 index 0000000..b9d279f --- /dev/null +++ b/core/modules/node/migration_templates/d6_node_translation.yml @@ -0,0 +1,51 @@ +id: d6_node_translation +label: Node translations +migration_tags: + - Drupal 6 +deriver: Drupal\node\Plugin\migrate\D6NodeDeriver +source: + plugin: d6_node + translations: true +process: + nid: tnid + type: type + langcode: + plugin: default_value + source: language + default_value: "und" + title: title + uid: node_uid + status: status + created: created + changed: changed + promote: promote + sticky: sticky + 'body/format': + plugin: migration + migration: d6_filter_format + source: format + 'body/value': body + 'body/summary': teaser + revision_uid: revision_uid + revision_log: log + revision_timestamp: timestamp + +# unmapped d6 fields. +# tnid +# translate +# moderate +# comment + +destination: + plugin: entity:node + translations: true +migration_dependencies: + required: + - d6_user + - d6_node_type + - d6_node_settings + - d6_filter_format + optional: + - d6_field_instance_widget_settings + - d6_field_formatter_settings + - d6_upload_field_instance diff --git a/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php b/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php index d586efd..d83eb17 100644 --- a/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php +++ b/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php @@ -99,9 +99,10 @@ public function getDerivativeDefinitions($base_plugin_definition) { ]); $values['source']['node_type'] = $node_type; - // If this migration is based on the d6_node_revision migration, it - // should explicitly depend on the corresponding d6_node variant. - if ($base_plugin_definition['id'] == 'd6_node_revision') { + // If this migration is based on the d6_node_revision migration, or + // is for translations of nodes, it should explicitly depend on the + // corresponding d6_node variant. + if (in_array($base_plugin_definition['id'], ['d6_node_revision', 'd6_node_translation'])) { $values['migration_dependencies']['required'][] = 'd6_node:' . $node_type; } diff --git a/core/modules/node/src/Plugin/migrate/source/d6/Node.php b/core/modules/node/src/Plugin/migrate/source/d6/Node.php index 1ed96f9..ccefb2d 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/Node.php @@ -15,6 +15,11 @@ class Node extends DrupalSqlBase { /** + * An expression for the translation set of a node. + */ + const NODE_TRANSLATION_SET = 'CASE n.tnid WHEN 0 THEN n.nid ELSE n.tnid END'; + + /** * The join options between the node and the node_revisions table. */ const JOIN = 'n.vid = nr.vid'; @@ -37,9 +42,9 @@ class Node extends DrupalSqlBase { * {@inheritdoc} */ public function query() { - // Select node in its last revision. - $query = $this->select('node_revisions', 'nr') - ->fields('n', array( + $query = $this->translationQuery(); + + $query->fields('n', array( 'nid', 'type', 'language', @@ -54,17 +59,16 @@ public function query() { 'translate', )) ->fields('nr', array( - 'vid', 'title', 'body', 'teaser', 'log', 'timestamp', 'format', + 'vid', )); $query->addField('n', 'uid', 'node_uid'); $query->addField('nr', 'uid', 'revision_uid'); - $query->innerJoin('node', 'n', static::JOIN); if (isset($this->configuration['node_type'])) { $query->condition('n.type', $this->configuration['node_type']); @@ -123,6 +127,11 @@ public function prepareRow(Row $row) { } } + // Make sure we always have a translation set. + if ($row->getSourceProperty('tnid') == 0) { + $row->setSourceProperty('tnid', $row->getSourceProperty('nid')); + } + return parent::prepareRow($row); } @@ -251,4 +260,46 @@ public function getIds() { return $ids; } + /** + * Query to get the max vid of the translation set in the node table. + * + * @return \Drupal\Core\Database\Query\SelectInterface + * The generated query. + */ + protected function maxVidQuery() { + $subquery = $this->select('node', 'n'); + $subquery->addExpression(self::NODE_TRANSLATION_SET, 'translation_set'); + $subquery->groupBy('translation_set'); + $subquery->addExpression('MAX(vid)', 'vid'); + return $subquery; + } + + /** + * Build a query that yields the rows we want to migrate. + * + * It should have a node table 'n', and a node_revision table 'nr'. + * + * @return \Drupal\Core\Database\Query\SelectInterface + * The generated query. + */ + protected function translationQuery() { + $query = $this->select('node_revisions', 'nr'); + $query->innerJoin('node', 'n', static::JOIN); + + // Claim our vid is the maximum vid of our translation set. + // Otherwise the revision the node is assigned in D8 may be confusing. + $query->join($this->maxVidQuery(), 'max_vid', + 'max_vid.translation_set IN (n.nid, n.tnid)'); + $query->fields('max_vid', ['vid']); + + // Are we yielding original nodes, or translations? + if (empty($this->configuration['translations'])) { + $query->where('n.tnid = 0 OR n.tnid = n.nid'); + } + else { + $query->where('n.tnid <> 0 AND n.tnid <> n.nid'); + } + + return $query; + } } diff --git a/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php b/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php index 41e7a18..ee0f9b8 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php @@ -37,4 +37,13 @@ public function getIds() { return $ids; } + /** + * {@inheritdoc} + */ + protected function translationQuery() { + $query = $this->select('node_revisions', 'nr'); + $query->innerJoin('node', 'n', static::JOIN); + return $query; + } + } diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php index 05be99f..0311841 100644 --- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php +++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php @@ -15,7 +15,7 @@ class MigrateNodeRevisionTest extends MigrateNodeTestBase { */ protected function setUp() { parent::setUp(); - $this->executeMigrations(['d6_node', 'd6_node_revision']); + $this->executeMigrations(['language', 'd6_node', 'd6_node_revision']); } /** diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php index 7f07d11..e7896cf 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\Database; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; use Drupal\Tests\file\Kernel\Migrate\d6\FileMigrationTestTrait; /** @@ -23,7 +24,7 @@ protected function setUp() { parent::setUp(); $this->setUpMigratedFiles(); $this->installSchema('file', ['file_usage']); - $this->executeMigrations(['d6_node']); + $this->executeMigrations(['language', 'd6_node', 'd6_node_translation']); } /** @@ -79,6 +80,16 @@ public function testNode() { $this->assertIdentical('Drupal Groups', $node->field_test_link->title); $this->assertIdentical([], $node->field_test_link->options['attributes']); + // Test that translations are working. + $node = Node::load(9); + $this->assertTrue($node instanceof NodeInterface); + $this->assertIdentical('en', $node->langcode->value); + $this->assertIdentical('The Real McCoy', $node->title->value); + $this->assertTrue($node->hasTranslation('fr')); + + // Node 10 is a translation of node 9, and should not be imported separately. + $this->assertNull(Node::load(10)); + // Rerun migration with invalid link attributes and a different URL and // title. If only the attributes are changed the error does not occur. Database::getConnection('default', 'migrate') diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTestBase.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTestBase.php index e225003..ca21ad7 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTestBase.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeTestBase.php @@ -13,6 +13,11 @@ /** * {@inheritdoc} */ + public static $modules = ['language']; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php index 0c31810..6a308ff 100644 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php +++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeByNodeTypeTest.php @@ -40,7 +40,7 @@ class NodeByNodeTypeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, // Node revision fields. 'body' => 'body for node 1', @@ -64,7 +64,7 @@ class NodeByNodeTypeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 2, 'translate' => 0, // Node revision fields. 'body' => 'body for node 2', diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php index 8c78be7..d548ff3 100644 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php +++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionByNodeTypeTest.php @@ -134,7 +134,7 @@ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, 'vid' => 1, 'node_uid' => 1, @@ -156,7 +156,7 @@ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, 'vid' => 3, 'node_uid' => 1, diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php index b1c23f8..752ed9b 100644 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php +++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeRevisionTest.php @@ -133,7 +133,7 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, // Node revision fields. 'vid' => 1, @@ -157,7 +157,7 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, // Node revision fields. 'vid' => 3, diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php index 79f2bac..18b7413 100644 --- a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php +++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTest.php @@ -2,16 +2,12 @@ namespace Drupal\Tests\node\Unit\Plugin\migrate\source\d6; -use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase; - /** * Tests D6 node source plugin. * * @group node */ -class NodeTest extends MigrateSqlSourceTestCase { - - const PLUGIN_CLASS = 'Drupal\node\Plugin\migrate\source\d6\Node'; +class NodeTest extends NodeTestBase { protected $migrationConfiguration = array( 'id' => 'test', @@ -36,7 +32,7 @@ class NodeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, // Node revision fields. 'body' => 'body for node 1', @@ -60,7 +56,7 @@ class NodeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 2, 'translate' => 0, // Node revision fields. 'body' => 'body for node 2', @@ -83,7 +79,7 @@ class NodeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 5, 'translate' => 0, // Node revision fields. 'body' => 'body for node 5', @@ -95,79 +91,29 @@ class NodeTest extends MigrateSqlSourceTestCase { array('value' => '3.14159'), ), ), + array( + 'nid' => 6, + 'vid' => 7, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 6', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290909, + 'changed' => 1279308994, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 6', + 'teaser' => 'body for node 6', + 'log' => '', + 'timestamp' => 1279308994, + 'format' => 1, + ), ); - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['content_node_field'] = array( - array( - 'field_name' => 'field_test_four', - 'type' => 'number_float', - 'global_settings' => 'a:0:{}', - 'required' => '0', - 'multiple' => '0', - 'db_storage' => '1', - 'module' => 'number', - 'db_columns' => 'a:1:{s:5:"value";a:3:{s:4:"type";s:5:"float";s:8:"not null";b:0;s:8:"sortable";b:1;}}', - 'active' => '1', - 'locked' => '0', - ), - ); - $this->databaseContents['content_node_field_instance'] = array( - array( - 'field_name' => 'field_test_four', - 'type_name' => 'story', - 'weight' => '3', - 'label' => 'Float Field', - 'widget_type' => 'number', - 'widget_settings' => 'a:0:{}', - 'display_settings' => 'a:0:{}', - 'description' => 'An example float field.', - 'widget_module' => 'number', - 'widget_active' => '1', - ), - ); - $this->databaseContents['content_type_story'] = array( - array( - 'nid' => 5, - 'vid' => 5, - 'uid' => 5, - 'field_test_four_value' => '3.14159', - ), - ); - $this->databaseContents['system'] = array( - array( - 'type' => 'module', - 'name' => 'content', - 'schema_version' => 6001, - 'status' => TRUE, - ), - ); - foreach ($this->expectedResults as $k => $row) { - foreach (array('nid', 'vid', 'title', 'uid', 'body', 'teaser', 'format', 'timestamp', 'log') as $field) { - $this->databaseContents['node_revisions'][$k][$field] = $row[$field]; - switch ($field) { - case 'nid': case 'vid': - break; - case 'uid': - $this->databaseContents['node_revisions'][$k]['uid']++; - break; - default: - unset($row[$field]); - break; - } - } - $this->databaseContents['node'][$k] = $row; - } - array_walk($this->expectedResults, function (&$row) { - $row['node_uid'] = $row['uid']; - $row['revision_uid'] = $row['uid'] + 1; - unset($row['uid']); - }); - - parent::setUp(); - } - } diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php new file mode 100644 index 0000000..8850fc4 --- /dev/null +++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTestBase.php @@ -0,0 +1,179 @@ +databaseContents['content_node_field'] = array( + array( + 'field_name' => 'field_test_four', + 'type' => 'number_float', + 'global_settings' => 'a:0:{}', + 'required' => '0', + 'multiple' => '0', + 'db_storage' => '1', + 'module' => 'number', + 'db_columns' => 'a:1:{s:5:"value";a:3:{s:4:"type";s:5:"float";s:8:"not null";b:0;s:8:"sortable";b:1;}}', + 'active' => '1', + 'locked' => '0', + ), + ); + $this->databaseContents['content_node_field_instance'] = array( + array( + 'field_name' => 'field_test_four', + 'type_name' => 'story', + 'weight' => '3', + 'label' => 'Float Field', + 'widget_type' => 'number', + 'widget_settings' => 'a:0:{}', + 'display_settings' => 'a:0:{}', + 'description' => 'An example float field.', + 'widget_module' => 'number', + 'widget_active' => '1', + ), + ); + $this->databaseContents['content_type_story'] = array( + array( + 'nid' => 5, + 'vid' => 5, + 'uid' => 5, + 'field_test_four_value' => '3.14159', + ), + ); + $this->databaseContents['system'] = array( + array( + 'type' => 'module', + 'name' => 'content', + 'schema_version' => 6001, + 'status' => TRUE, + ), + ); + $this->databaseContents['node'] = [ + [ + 'nid' => 1, + 'vid' => 1, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 1', + 'uid' => 1, + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 0, + ], + [ + 'nid' => 2, + 'vid' => 2, + 'type' => 'page', + 'language' => 'en', + 'title' => 'node title 2', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 0, + ], + [ + 'nid' => 5, + 'vid' => 5, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 5', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 0, + ], + [ + 'nid' => 6, + 'vid' => 6, + 'type' => 'story', + 'language' => 'en', + 'title' => 'node title 6', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290909, + 'changed' => 1279308994, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 6, + ], + [ + 'nid' => 7, + 'vid' => 7, + 'type' => 'story', + 'language' => 'fr', + 'title' => 'node title 7', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290910, + 'changed' => 1279308995, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'tnid' => 6, + ], + ]; + + foreach ($this->databaseContents['node'] as $k => $row) { + // Find the equivalent row from expected results. + $result_row = NULL; + foreach ($this->expectedResults as $result) { + if (in_array($result['nid'], [$row['nid'], $row['tnid']]) && $result['language'] == $row['language']) { + $result_row = $result; + break; + } + } + + // Populate node_revisions. + foreach (array('nid', 'vid', 'title', 'uid', 'body', 'teaser', 'format', 'timestamp', 'log') as $field) { + $value = isset($row[$field]) ? $row[$field] : $result_row[$field]; + $this->databaseContents['node_revisions'][$k][$field] = $value; + if ($field == 'uid') { + $this->databaseContents['node_revisions'][$k]['uid']++; + } + } + } + + array_walk($this->expectedResults, function (&$row) { + $row['node_uid'] = $row['uid']; + $row['revision_uid'] = $row['uid'] + 1; + unset($row['uid']); + }); + + parent::setUp(); + } + +} diff --git a/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php new file mode 100644 index 0000000..da0b167 --- /dev/null +++ b/core/modules/node/tests/src/Unit/Plugin/migrate/source/d6/NodeTranslationTest.php @@ -0,0 +1,46 @@ + 'test', + 'source' => array( + 'plugin' => 'd6_node', + 'translations' => TRUE, + ), + ); + + protected $expectedResults = array( + array( + 'nid' => 7, + 'vid' => 7, + 'type' => 'story', + 'language' => 'fr', + 'title' => 'node title 7', + 'uid' => 1, + 'status' => 1, + 'created' => 1279290910, + 'changed' => 1279308995, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 7', + 'teaser' => 'body for node 7', + 'log' => '', + 'timestamp' => 1279308995, + 'format' => 1, + ), + ); + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeRevisionTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeRevisionTest.php index ff2b514..ac9f881 100644 --- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeRevisionTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeRevisionTest.php @@ -14,7 +14,7 @@ class MigrateTermNodeRevisionTest extends MigrateDrupal6TestBase { /** * {@inheritdoc} */ - public static $modules = ['taxonomy']; + public static $modules = ['language', 'taxonomy']; /** * {@inheritdoc} diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeTest.php index 718c35d..3e13a55 100644 --- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTermNodeTest.php @@ -15,7 +15,7 @@ class MigrateTermNodeTest extends MigrateDrupal6TestBase { /** * {@inheritdoc} */ - public static $modules = ['taxonomy']; + public static $modules = ['language', 'taxonomy']; /** * {@inheritdoc} @@ -55,7 +55,7 @@ public function testTermNode() { public function testSkipNonExistentNode() { // Node 2 is migrated by d6_node__story, but we need to pretend that it // failed, so record that in the map table. - $this->mockFailure('d6_node:story', ['nid' => 2]); + $this->mockFailure('d6_node:story', ['nid' => 2, 'language' => 'en']); // d6_term_node__2 should skip over node 2 (a.k.a. revision 3) because, // according to the map table, it failed. diff --git a/core/modules/user/migration_templates/d7_user.yml b/core/modules/user/migration_templates/d7_user.yml index 12147f8..5e9dcc7 100644 --- a/core/modules/user/migration_templates/d7_user.yml +++ b/core/modules/user/migration_templates/d7_user.yml @@ -15,7 +15,6 @@ process: login: login status: status timezone: timezone - langcode: language preferred_langcode: language preferred_admin_langcode: language init: init diff --git a/core/modules/user/src/Plugin/migrate/User.php b/core/modules/user/src/Plugin/migrate/User.php index 986b58d..65d5c2f 100644 --- a/core/modules/user/src/Plugin/migrate/User.php +++ b/core/modules/user/src/Plugin/migrate/User.php @@ -2,13 +2,80 @@ namespace Drupal\user\Plugin\migrate; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\migrate\Exception\RequirementsException; +use Drupal\migrate\Plugin\MigrateDestinationPluginManager; +use Drupal\migrate\Plugin\MigratePluginManager; use Drupal\migrate\Plugin\Migration; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Plugin class for Drupal 7 user migrations dealing with fields and profiles. */ class User extends Migration { + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * Constructs a user migration. + * + * @param array $configuration + * Plugin configuration. + * @param string $plugin_id + * The plugin ID. + * @param mixed $plugin_definition + * The plugin definition. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager. + * @param \Drupal\migrate\Plugin\MigratePluginManager $source_plugin_manager + * The source migration plugin manager. + * @param \Drupal\migrate\Plugin\MigratePluginManager $process_plugin_manager + * The process migration plugin manager. + * @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager + * The destination migration plugin manager. + * @param \Drupal\migrate\Plugin\MigratePluginManager $idmap_plugin_manager + * The ID map migration plugin manager. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $source_plugin_manager, MigratePluginManager $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManager $idmap_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration_plugin_manager, $source_plugin_manager, $process_plugin_manager, $destination_plugin_manager, $idmap_plugin_manager); + $this->moduleHandler = $module_handler; + $this->languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.migration'), + $container->get('plugin.manager.migrate.source'), + $container->get('plugin.manager.migrate.process'), + $container->get('plugin.manager.migrate.destination'), + $container->get('plugin.manager.migrate.id_map'), + $container->get('module_handler'), + $container->get('language_manager') + ); + } /** * Flag indicating whether the CCK data has been filled already. @@ -28,7 +95,7 @@ public function getProcess() { 'ignore_map' => TRUE, ] + $this->source; $definition['destination']['plugin'] = 'null'; - if (\Drupal::moduleHandler()->moduleExists('field')) { + if ($this->moduleHandler->moduleExists('field')) { $definition['source']['plugin'] = 'd7_field_instance'; $field_migration = $this->migrationPluginManager->createStubMigration($definition); foreach ($field_migration->getSourcePlugin() as $row) { @@ -50,6 +117,15 @@ public function getProcess() { // The checkRequirements() call will fail when the profile module does // not exist on the source site. } + + // Use the default language if none is provided. + $this->process['langcode'] = [ + 'source' => 'language', + 'plugin' => 'default_value', + 'strict' => FALSE, + 'default_value' => $this->languageManager->getDefaultLanguage()->getId(), + ]; + } return parent::getProcess(); } diff --git a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php index 2e0965d..15b4801 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php @@ -54,6 +54,8 @@ protected function setUp() { * Whether or not the account is blocked. * @param string $langcode * The user account's language code. + * @param string $preferred_langcode + * The user's preferred language. * @param string $init * The user's initial email address. * @param string[] $roles @@ -61,7 +63,7 @@ protected function setUp() { * @param bool $has_picture * Whether the user is expected to have a picture attached. */ - protected function assertEntity($id, $label, $mail, $password, $access, $login, $blocked, $langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID], $has_picture = FALSE) { + protected function assertEntity($id, $label, $mail, $password, $access, $login, $blocked, $langcode, $preferred_langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID], $has_picture = FALSE) { /** @var \Drupal\user\UserInterface $user */ $user = User::load($id); $this->assertTrue($user instanceof UserInterface); @@ -74,8 +76,8 @@ protected function assertEntity($id, $label, $mail, $password, $access, $login, // user preferred language is not configured on the site. We just want to // test if the value was imported correctly. $this->assertIdentical($langcode, $user->langcode->value); - $this->assertIdentical($langcode, $user->preferred_langcode->value); - $this->assertIdentical($langcode, $user->preferred_admin_langcode->value); + $this->assertIdentical($preferred_langcode, $user->preferred_langcode->value); + $this->assertIdentical($preferred_langcode, $user->preferred_admin_langcode->value); $this->assertIdentical($init, $user->getInitialEmail()); $this->assertIdentical($roles, $user->getRoles()); $this->assertIdentical($has_picture, !$user->user_picture->isEmpty()); @@ -87,7 +89,8 @@ protected function assertEntity($id, $label, $mail, $password, $access, $login, */ public function testUser() { $password = '$S$DGFZUE.FhrXbe4y52eC7p0ZVRGD/gOPtVctDlmC89qkujnBokAlJ'; - $this->assertEntity(2, 'Odo', 'odo@local.host', $password, '0', '0', FALSE, '', 'odo@local.host'); + $this->assertEquals('en', \Drupal::languageManager()->getDefaultLanguage()->getId()); + $this->assertEntity(2, 'Odo', 'odo@local.host', $password, '0', '0', FALSE, 'en', '', 'odo@local.host'); // Ensure that the user can authenticate. $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password'));