diff --git a/core/modules/field/migration_templates/d7_field.yml b/core/modules/field/migration_templates/d7_field.yml index 0693343..15079d8 100644 --- a/core/modules/field/migration_templates/d7_field.yml +++ b/core/modules/field/migration_templates/d7_field.yml @@ -35,7 +35,10 @@ process: phone: telephone text_long: text_long text_with_summary: text_with_summary - translatable: translatable + # Translatable is not migrated and the Drupal 8 default of true is used. + # If translatable is false in field storage then the field can not be + # set to translatable via the UI. + #translatable: translatable cardinality: cardinality settings: plugin: d7_field_settings diff --git a/core/modules/field/migration_templates/d7_field_instance.yml b/core/modules/field/migration_templates/d7_field_instance.yml index da4ce0a..f3518c9 100644 --- a/core/modules/field/migration_templates/d7_field_instance.yml +++ b/core/modules/field/migration_templates/d7_field_instance.yml @@ -28,6 +28,7 @@ process: source: - default_value - widget_settings + translatable: translatable destination: plugin: entity:field_config migration_dependencies: diff --git a/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php b/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php index 5d3d6cc..3da02b9 100644 --- a/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php +++ b/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php @@ -86,6 +86,25 @@ public function prepareRow(Row $row) { $field_data = unserialize($row->getSourceProperty('field_data')); $row->setSourceProperty('field_settings', $field_data['settings']); + $translatable = FALSE; + if ($row->getSourceProperty('entity_type') == 'node') { + // language_content_type_[bundle] may be + // - 0: no language support + // - 1: language assignment support + // - 2: node translation support + // - 4: entity translation support + if ($this->variableGet('language_content_type_' . $row->getSourceProperty('bundle'), 0) == 2) { + $translatable = TRUE; + } + } + else { + // This is not a node entity. Get the translatable value from the source + // field_config table. + $data = unserialize($row->getSourceProperty('field_data')); + $translatable = $data['translatable']; + } + $row->setSourceProperty('translatable', $translatable); + return parent::prepareRow($row); } diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php index 7c2115f..c50c324 100644 --- a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php +++ b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php @@ -80,8 +80,10 @@ protected function createType($id) { * The expected field type. * @param bool $is_required * Whether or not the field is required. + * @param bool $expected_translatable + * Whether or not the field is expected to be translatable. */ - protected function assertEntity($id, $expected_label, $expected_field_type, $is_required) { + protected function assertEntity($id, $expected_label, $expected_field_type, $is_required, $expected_translatable) { list ($expected_entity_type, $expected_bundle, $expected_name) = explode('.', $id); /** @var \Drupal\field\FieldConfigInterface $field */ @@ -94,6 +96,7 @@ protected function assertEntity($id, $expected_label, $expected_field_type, $is_ $this->assertIdentical($expected_name, $field->getName()); $this->assertEqual($is_required, $field->isRequired()); $this->assertIdentical($expected_entity_type . '.' . $expected_name, $field->getFieldStorageDefinition()->id()); + $this->assertSame($expected_translatable, $field->isTranslatable()); } /** @@ -113,40 +116,38 @@ protected function assertLinkFields($id, $title_setting) { * Tests migrating D7 field instances to field_config entities. */ public function testFieldInstances() { - $this->assertEntity('comment.comment_node_page.comment_body', 'Comment', 'text_long', TRUE); - $this->assertEntity('node.page.body', 'Body', 'text_with_summary', FALSE); - $this->assertEntity('comment.comment_node_article.comment_body', 'Comment', 'text_long', TRUE); - $this->assertEntity('node.article.body', 'Body', 'text_with_summary', FALSE); - $this->assertEntity('node.article.field_tags', 'Tags', 'entity_reference', FALSE); - $this->assertEntity('node.article.field_image', 'Image', 'image', FALSE); - $this->assertEntity('comment.comment_node_blog.comment_body', 'Comment', 'text_long', TRUE); - $this->assertEntity('node.blog.body', 'Body', 'text_with_summary', FALSE); - $this->assertEntity('comment.comment_node_book.comment_body', 'Comment', 'text_long', TRUE); - $this->assertEntity('node.book.body', 'Body', 'text_with_summary', FALSE); - $this->assertEntity('node.forum.taxonomy_forums', 'Forums', 'entity_reference', TRUE); - $this->assertEntity('comment.comment_node_forum.comment_body', 'Comment', 'text_long', TRUE); - $this->assertEntity('node.forum.body', 'Body', 'text_with_summary', FALSE); - $this->assertEntity('comment.comment_node_test_content_type.comment_body', 'Comment', 'text_long', TRUE); - $this->assertEntity('node.test_content_type.field_boolean', 'Boolean', 'boolean', FALSE); - $this->assertEntity('node.test_content_type.field_email', 'Email', 'email', FALSE); - $this->assertEntity('node.test_content_type.field_phone', 'Phone', 'telephone', TRUE); - $this->assertEntity('node.test_content_type.field_date', 'Date', 'datetime', FALSE); - $this->assertEntity('node.test_content_type.field_date_with_end_time', 'Date With End Time', 'datetime', FALSE); - $this->assertEntity('node.test_content_type.field_file', 'File', 'file', FALSE); - $this->assertEntity('node.test_content_type.field_float', 'Float', 'float', FALSE); - $this->assertEntity('node.test_content_type.field_images', 'Images', 'image', TRUE); - $this->assertEntity('node.test_content_type.field_integer', 'Integer', 'integer', TRUE); - $this->assertEntity('node.test_content_type.field_link', 'Link', 'link', FALSE); - $this->assertEntity('node.test_content_type.field_text_list', 'Text List', 'list_string', FALSE); - $this->assertEntity('node.test_content_type.field_integer_list', 'Integer List', 'list_integer', FALSE); - $this->assertEntity('node.test_content_type.field_long_text', 'Long text', 'text_with_summary', FALSE); - $this->assertEntity('node.test_content_type.field_term_reference', 'Term Reference', 'entity_reference', FALSE); - $this->assertEntity('node.test_content_type.field_node_entityreference', 'Node Entity Reference', 'entity_reference', FALSE); - $this->assertEntity('node.test_content_type.field_user_entityreference', 'User Entity Reference', 'entity_reference', FALSE); - $this->assertEntity('node.test_content_type.field_term_entityreference', 'Term Entity Reference', 'entity_reference', FALSE); - $this->assertEntity('node.test_content_type.field_text', 'Text', 'text', FALSE); - $this->assertEntity('comment.comment_node_test_content_type.field_integer', 'Integer', 'integer', FALSE); - $this->assertEntity('user.user.field_file', 'File', 'file', FALSE); + $this->assertEntity('comment.comment_node_page.comment_body', 'Comment', 'text_long', TRUE, FALSE); + $this->assertEntity('node.page.body', 'Body', 'text_with_summary', FALSE, FALSE); + $this->assertEntity('comment.comment_node_article.comment_body', 'Comment', 'text_long', TRUE, FALSE); + $this->assertEntity('node.article.body', 'Body', 'text_with_summary', FALSE, TRUE); + $this->assertEntity('node.article.field_tags', 'Tags', 'entity_reference', FALSE, TRUE); + $this->assertEntity('node.article.field_image', 'Image', 'image', FALSE, TRUE); + $this->assertEntity('comment.comment_node_blog.comment_body', 'Comment', 'text_long', TRUE, FALSE); + $this->assertEntity('node.blog.body', 'Body', 'text_with_summary', FALSE, TRUE); + $this->assertEntity('comment.comment_node_book.comment_body', 'Comment', 'text_long', TRUE, FALSE); + $this->assertEntity('node.book.body', 'Body', 'text_with_summary', FALSE, FALSE); + $this->assertEntity('node.forum.taxonomy_forums', 'Forums', 'entity_reference', TRUE, FALSE); + $this->assertEntity('comment.comment_node_forum.comment_body', 'Comment', 'text_long', TRUE, FALSE); + $this->assertEntity('node.forum.body', 'Body', 'text_with_summary', FALSE, FALSE); + $this->assertEntity('comment.comment_node_test_content_type.comment_body', 'Comment', 'text_long', TRUE, FALSE); + $this->assertEntity('node.test_content_type.field_boolean', 'Boolean', 'boolean', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_email', 'Email', 'email', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_phone', 'Phone', 'telephone', TRUE, FALSE); + $this->assertEntity('node.test_content_type.field_date', 'Date', 'datetime', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_date_with_end_time', 'Date With End Time', 'datetime', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_file', 'File', 'file', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_float', 'Float', 'float', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_images', 'Images', 'image', TRUE, FALSE); + $this->assertEntity('node.test_content_type.field_integer', 'Integer', 'integer', TRUE, FALSE); + $this->assertEntity('node.test_content_type.field_link', 'Link', 'link', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_text_list', 'Text List', 'list_string', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_integer_list', 'Integer List', 'list_integer', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_long_text', 'Long text', 'text_with_summary', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_term_reference', 'Term Reference', 'entity_reference', FALSE, FALSE); + $this->assertEntity('node.test_content_type.field_text', 'Text', 'text', FALSE, FALSE); + $this->assertEntity('comment.comment_node_test_content_type.field_integer', 'Integer', 'integer', FALSE, FALSE); + $this->assertEntity('user.user.field_file', 'File', 'file', FALSE, FALSE); + $this->assertLinkFields('node.test_content_type.field_link', DRUPAL_OPTIONAL); $this->assertLinkFields('node.article.field_link', DRUPAL_DISABLED); diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldTest.php index fb275c6..4a0f8cd 100644 --- a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldTest.php +++ b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldTest.php @@ -60,8 +60,6 @@ protected function assertEntity($id, $expected_type, $expected_translatable, $ex $this->assertTrue($field instanceof FieldStorageConfigInterface); $this->assertIdentical($expected_name, $field->getName()); $this->assertIdentical($expected_type, $field->getType()); - // FieldStorageConfig::$translatable is TRUE by default, so it is useful - // to test for FALSE here. $this->assertEqual($expected_translatable, $field->isTranslatable()); $this->assertIdentical($expected_entity_type, $field->getTargetEntityTypeId()); @@ -78,31 +76,31 @@ protected function assertEntity($id, $expected_type, $expected_translatable, $ex * Tests migrating D7 fields to field_storage_config entities. */ public function testFields() { - $this->assertEntity('node.body', 'text_with_summary', FALSE, 1); - $this->assertEntity('node.field_long_text', 'text_with_summary', FALSE, 1); - $this->assertEntity('comment.comment_body', 'text_long', FALSE, 1); - $this->assertEntity('node.field_file', 'file', FALSE, 1); - $this->assertEntity('user.field_file', 'file', FALSE, 1); - $this->assertEntity('node.field_float', 'float', FALSE, 1); - $this->assertEntity('node.field_image', 'image', FALSE, 1); - $this->assertEntity('node.field_images', 'image', FALSE, 1); - $this->assertEntity('node.field_integer', 'integer', FALSE, 1); - $this->assertEntity('comment.field_integer', 'integer', FALSE, 1); - $this->assertEntity('node.field_integer_list', 'list_integer', FALSE, 1); - $this->assertEntity('node.field_link', 'link', FALSE, 1); - $this->assertEntity('node.field_tags', 'entity_reference', FALSE, -1); - $this->assertEntity('node.field_term_reference', 'entity_reference', FALSE, 1); - $this->assertEntity('node.taxonomy_forums', 'entity_reference', FALSE, 1); - $this->assertEntity('node.field_text', 'text', FALSE, 1); - $this->assertEntity('node.field_text_list', 'list_string', FALSE, 3); - $this->assertEntity('node.field_boolean', 'boolean', FALSE, 1); - $this->assertEntity('node.field_email', 'email', FALSE, -1); - $this->assertEntity('node.field_phone', 'telephone', FALSE, 1); - $this->assertEntity('node.field_date', 'datetime', FALSE, 1); - $this->assertEntity('node.field_date_with_end_time', 'datetime', FALSE, 1); - $this->assertEntity('node.field_node_entityreference', 'entity_reference', FALSE, -1); - $this->assertEntity('node.field_user_entityreference', 'entity_reference', FALSE, 1); - $this->assertEntity('node.field_term_entityreference', 'entity_reference', FALSE, -1); + $this->assertEntity('node.body', 'text_with_summary', TRUE, 1); + $this->assertEntity('node.field_long_text', 'text_with_summary', TRUE, 1); + $this->assertEntity('comment.comment_body', 'text_long', TRUE, 1); + $this->assertEntity('node.field_file', 'file', TRUE, 1); + $this->assertEntity('user.field_file', 'file', TRUE, 1); + $this->assertEntity('node.field_float', 'float', TRUE, 1); + $this->assertEntity('node.field_image', 'image', TRUE, 1); + $this->assertEntity('node.field_images', 'image', TRUE, 1); + $this->assertEntity('node.field_integer', 'integer', TRUE, 1); + $this->assertEntity('comment.field_integer', 'integer', TRUE, 1); + $this->assertEntity('node.field_integer_list', 'list_integer', TRUE, 1); + $this->assertEntity('node.field_link', 'link', TRUE, 1); + $this->assertEntity('node.field_tags', 'entity_reference', TRUE, -1); + $this->assertEntity('node.field_term_reference', 'entity_reference', TRUE, 1); + $this->assertEntity('node.taxonomy_forums', 'entity_reference', TRUE, 1); + $this->assertEntity('node.field_text', 'text', TRUE, 1); + $this->assertEntity('node.field_text_list', 'list_string', TRUE, 3); + $this->assertEntity('node.field_boolean', 'boolean', TRUE, 1); + $this->assertEntity('node.field_email', 'email', TRUE, -1); + $this->assertEntity('node.field_phone', 'telephone', TRUE, 1); + $this->assertEntity('node.field_date', 'datetime', TRUE, 1); + $this->assertEntity('node.field_date_with_end_time', 'datetime', TRUE, 1); + $this->assertEntity('node.field_node_entityreference', 'entity_reference', TRUE, -1); + $this->assertEntity('node.field_user_entityreference', 'entity_reference', TRUE, 1); + $this->assertEntity('node.field_term_entityreference', 'entity_reference', TRUE, -1); // Assert that the taxonomy term reference fields are referencing the // correct entity type. @@ -123,7 +121,8 @@ public function testFields() { // Validate that the source count and processed count match up. /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ $migration = $this->getMigration('d7_field'); - $this->assertIdentical($migration->getSourcePlugin()->count(), $migration->getIdMap()->processedCount()); + $this->assertSame($migration->getSourcePlugin() + ->count(), $migration->getIdMap()->processedCount()); } } diff --git a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php index a0b8ec1..c9e93ef 100644 --- a/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php +++ b/core/modules/field/tests/src/Kernel/Plugin/migrate/source/d7/FieldInstanceTest.php @@ -94,6 +94,7 @@ public function providerSource() { ], 'description' => '', 'required' => FALSE, + 'field_data' => 'a:6:{s:12:"entity_types";a:1:{i:0;s:4:"node";}s:12:"translatable";b:0;s:8:"settings";a:0:{}s:7:"storage";a:4:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";i:1;}s:12:"foreign keys";a:1:{s:6:"format";a:2:{s:5:"table";s:13:"filter_format";s:7:"columns";a:1:{s:6:"format";s:6:"format";}}}s:7:"indexes";a:1:{s:6:"format";a:1:{i:0;s:6:"format";}}}', ], ]; diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php index a388f8e..ea4ecda 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -6141,6 +6141,30 @@ 'body_summary' => '', 'body_format' => 'filtered_html', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '4', + 'revision_id' => '4', + 'language' => 'und', + 'delta' => '0', + 'body_value' => 'is - Is that is it awesome.', + 'body_summary' => '', + 'body_format' => 'filtered_html', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '5', + 'revision_id' => '5', + 'language' => 'und', + 'delta' => '0', + 'body_value' => 'en - Is that is it awesome.', + 'body_summary' => '', + 'body_format' => 'filtered_html', +)) ->execute(); $connection->schema()->createTable('field_revision_comment_body', array( @@ -30945,6 +30969,38 @@ 'tnid' => '2', 'translate' => '0', )) +->values(array( + 'nid' => '4', + 'vid' => '4', + 'type' => 'article', + 'language' => 'is', + 'title' => 'is - The thing about Firefly', + 'uid' => '1', + 'status' => '1', + 'created' => '1478755274', + 'changed' => '1478755274', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', + 'tnid' => '4', + 'translate' => '0', +)) +->values(array( + 'nid' => '5', + 'vid' => '5', + 'type' => 'article', + 'language' => 'en', + 'title' => 'en - The thing about Firefly', + 'uid' => '1', + 'status' => '1', + 'created' => '1478755314', + 'changed' => '1478755314', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', + 'tnid' => '4', + 'translate' => '0', +)) ->execute(); $connection->schema()->createTable('node_access', array( @@ -31089,6 +31145,22 @@ 'last_comment_uid' => '1', 'comment_count' => '0', )) +->values(array( + 'nid' => '4', + 'cid' => '0', + 'last_comment_timestamp' => '1478755274', + 'last_comment_name' => NULL, + 'last_comment_uid' => '1', + 'comment_count' => '0', +)) +->values(array( + 'nid' => '5', + 'cid' => '0', + 'last_comment_timestamp' => '1478755314', + 'last_comment_name' => NULL, + 'last_comment_uid' => '1', + 'comment_count' => '0', +)) ->execute(); $connection->schema()->createTable('node_counter', array( @@ -31143,15 +31215,27 @@ ->values(array( 'nid' => '2', 'totalcount' => '1', - 'daycount' => '1', + 'daycount' => '0', 'timestamp' => '1471428059', )) ->values(array( 'nid' => '3', 'totalcount' => '1', - 'daycount' => '1', + 'daycount' => '0', 'timestamp' => '1471428153', )) +->values(array( + 'nid' => '4', + 'totalcount' => '1', + 'daycount' => '1', + 'timestamp' => '1478755275', +)) +->values(array( + 'nid' => '5', + 'totalcount' => '1', + 'daycount' => '1', + 'timestamp' => '1478755314', +)) ->execute(); $connection->schema()->createTable('node_revision', array( @@ -31272,6 +31356,30 @@ 'promote' => '1', 'sticky' => '0', )) +->values(array( + 'nid' => '4', + 'vid' => '4', + 'uid' => '1', + 'title' => 'is - The thing about Firefly', + 'log' => '', + 'timestamp' => '1478755274', + 'status' => '1', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', +)) +->values(array( + 'nid' => '5', + 'vid' => '5', + 'uid' => '1', + 'title' => 'en - The thing about Firefly', + 'log' => '', + 'timestamp' => '1478755314', + 'status' => '1', + 'comment' => '2', + 'promote' => '1', + 'sticky' => '0', +)) ->execute(); $connection->schema()->createTable('node_type', array( diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php index 90b0b61..0c12e3b 100644 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -374,6 +374,10 @@ class MigrateUpgradeForm extends ConfirmFormBase { 'source_module' => 'node', 'destination_module' => 'node', ], + 'd7_node_translation' => [ + 'source_module' => 'node', + 'destination_module' => 'node', + ], 'd7_node_title_label' => [ 'source_module' => 'node', 'destination_module' => 'node', diff --git a/core/modules/node/migration_templates/d7_node.yml b/core/modules/node/migration_templates/d7_node.yml index 381f7ce..5de3055 100644 --- a/core/modules/node/migration_templates/d7_node.yml +++ b/core/modules/node/migration_templates/d7_node.yml @@ -8,7 +8,10 @@ source: process: # If you are using this file to build a custom migration consider removing # the nid and vid fields to allow incremental migrations. - nid: nid + # In D7, 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\d7\Node::prepareRow(). + nid: tnid vid: vid langcode: plugin: default_value diff --git a/core/modules/node/migration_templates/d7_node_translation.yml b/core/modules/node/migration_templates/d7_node_translation.yml new file mode 100644 index 0000000..11dfbb7 --- /dev/null +++ b/core/modules/node/migration_templates/d7_node_translation.yml @@ -0,0 +1,42 @@ +id: d7_node_translation +label: Node translations +migration_tags: + - Drupal 7 + - translation +deriver: Drupal\node\Plugin\migrate\D7NodeDeriver +source: + plugin: d7_node + translations: true +process: + # If you are using this file to build a custom migration consider removing + # the nid field to allow incremental migrations. + 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 + revision_uid: revision_uid + revision_log: log + revision_timestamp: timestamp + content_translation_source: source_langcode +destination: + plugin: entity:node + translations: true + content_translation_update_definitions: + - node +migration_dependencies: + required: + - d7_user + - d7_node_type + - language + optional: + - d7_field_instance +provider: migrate_drupal diff --git a/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php b/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php index 3fc2682..a35c376 100644 --- a/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php +++ b/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php @@ -39,25 +39,37 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface { protected $cckPluginManager; /** + * Whether or not to include translations. + * + * @var bool + */ + protected $includeTranslations; + + /** * D7NodeDeriver constructor. * * @param string $base_plugin_id * The base plugin ID for the plugin ID. * @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager * The CCK plugin manager. + * @param bool $translations + * Whether or not to include translations. */ - public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) { + public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager, $translations) { $this->basePluginId = $base_plugin_id; $this->cckPluginManager = $cck_manager; + $this->includeTranslations = $translations; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, $base_plugin_id) { + // Translations don't make sense unless we have content_translation. return new static( $base_plugin_id, - $container->get('plugin.manager.migrate.cckfield') + $container->get('plugin.manager.migrate.cckfield'), + $container->get('module_handler')->moduleExists('content_translation') ); } @@ -65,6 +77,11 @@ public static function create(ContainerInterface $container, $base_plugin_id) { * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { + if (in_array('translation', $base_plugin_definition['migration_tags']) && !$this->includeTranslations) { + // Refuse to generate anything. + return $this->derivatives; + } + $node_types = static::getSourcePlugin('d7_node_type'); try { $node_types->checkRequirements(); @@ -105,6 +122,13 @@ public function getDerivativeDefinitions($base_plugin_definition) { $values['source']['node_type'] = $node_type; $values['destination']['default_bundle'] = $node_type; + // If this migration is based on the d7_node_revision migration or + // is for translations of nodes, it should explicitly depend on the + // corresponding d7_node variant. + if ($base_plugin_definition['id'] == ['d7_node_revision'] || in_array('translation', $base_plugin_definition['migration_tags'])) { + $values['migration_dependencies']['required'][] = 'd7_node:' . $node_type; + } + $migration = \Drupal::service('plugin.manager.migration')->createStubMigration($values); if (isset($fields[$node_type])) { foreach ($fields[$node_type] as $field_name => $info) { @@ -131,7 +155,6 @@ public function getDerivativeDefinitions($base_plugin_definition) { // MigrationPluginManager gathers up the migration definitions but we do // not actually have a Drupal 7 source database. } - return $this->derivatives; } diff --git a/core/modules/node/src/Plugin/migrate/source/d7/Node.php b/core/modules/node/src/Plugin/migrate/source/d7/Node.php index 6286d5f..85acee1 100644 --- a/core/modules/node/src/Plugin/migrate/source/d7/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d7/Node.php @@ -2,8 +2,15 @@ namespace Drupal\node\Plugin\migrate\source\d7; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity; +use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Extension\ModuleHandler; +use Drupal\Core\State\StateInterface; +use Drupal\migrate\Plugin\MigrationInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Drupal 7 node source from database. @@ -14,6 +21,35 @@ * ) */ class Node extends FieldableEntity { + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager); + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $migration, + $container->get('state'), + $container->get('entity.manager'), + $container->get('module_handler') + ); + } /** * The join options between the node and the node_revisions table. @@ -49,6 +85,14 @@ public function query() { $query->addField('nr', 'uid', 'revision_uid'); $query->innerJoin('node', 'n', static::JOIN); + // If the content_translation module is enabled, get the source langcode + // to fill the content_translation_source field. + if ($this->moduleHandler->moduleExists('content_translation')) { + $query->leftJoin('node', 'nt', 'n.tnid = nt.nid'); + $query->addField('nt', 'language', 'source_langcode'); + } + $this->handleTranslations($query); + if (isset($this->configuration['node_type'])) { $query->condition('n.type', $this->configuration['node_type']); } @@ -66,6 +110,11 @@ public function prepareRow(Row $row) { $vid = $row->getSourceProperty('vid'); $row->setSourceProperty($field, $this->getFieldValues('node', $field, $nid, $vid)); } + + // Make sure we always have a translation set. + if ($row->getSourceProperty('tnid') == 0) { + $row->setSourceProperty('tnid', $row->getSourceProperty('nid')); + } return parent::prepareRow($row); } @@ -101,4 +150,21 @@ public function getIds() { return $ids; } + /** + * Adapt our query for translations. + * + * @param \Drupal\Core\Database\Query\SelectInterface $query + * The generated query. + */ + protected function handleTranslations(SelectInterface $query) { + // Check whether or not we want translations. + if (empty($this->configuration['translations'])) { + // No translations: Yield untranslated nodes, or default translations. + $query->where('n.tnid = 0 OR n.tnid = n.nid'); + } + else { + // Translations: Yield only non-default translations. + $query->where('n.tnid <> 0 AND n.tnid <> n.nid'); + } + } } diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php new file mode 100644 index 0000000..569582d --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeDeriverTest.php @@ -0,0 +1,51 @@ +pluginManager = $this->container->get('plugin.manager.migration'); + } + + /** + * Test node translation migrations with translation disabled. + */ + public function testNoTranslations() { + // Without content_translation, there should be no translation migrations. + $migrations = $this->pluginManager->createInstances('d7_node_translation'); + $this->assertSame([], $migrations, + "No node translation migrations without content_translation"); + } + + /** + * Test node translation migrations with translation enabled. + */ + public function testTranslations() { + // With content_translation, there should be translation migrations for + // each content type. + $this->enableModules(['language', 'content_translation', 'node', 'filter']); + $migrations = $this->pluginManager->createInstances('d7_node_translation'); + $this->assertArrayHasKey('d7_node_translation:article', $migrations, + "Node translation migrations exist after content_translation installed"); + } + +} diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php index f7b98bd..8a962e7 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php @@ -13,11 +13,16 @@ */ class MigrateNodeTest extends MigrateDrupal7TestBase { + /** + * {@inheritdoc} + */ public static $modules = array( + 'content_translation', 'comment', 'datetime', 'filter', 'image', + 'language', 'link', 'node', 'taxonomy', @@ -40,15 +45,17 @@ protected function setUp() { $this->installSchema('system', ['sequences']); $this->executeMigrations([ + 'language', 'd7_user_role', 'd7_user', 'd7_node_type', + 'd7_language_content_settings', 'd7_comment_type', 'd7_taxonomy_vocabulary', 'd7_field', 'd7_field_instance', 'd7_node', - 'd7_node:article', + 'd7_node_translation', ]); } @@ -144,9 +151,31 @@ public function testNode() { $this->assertIdentical('Click Here', $node->field_link->title); $node = Node::load(2); + $this->assertSame('en', $node->langcode->value); $this->assertIdentical("...is that it's the absolute best show ever. Trust me, I would know.", $node->body->value); + $this->assertSame('The thing about Deep Space 9', $node->label()); $this->assertIdentical('internal:/', $node->field_link->uri); $this->assertIdentical('Home', $node->field_link->title); + $this->assertTrue($node->hasTranslation('is'), "Node 2 has an Icelandic translation"); + + $translation = $node->getTranslation('is'); + $this->assertSame('is', $translation->langcode->value); + $this->assertSame("is - ...is that it's the absolute best show ever. Trust me, I would know.", $translation->body->value); + $this->assertSame('is - The thing about Deep Space 9', $translation->label()); + $this->assertSame('internal:/', $translation->field_link->uri); + $this->assertSame('Home', $translation->field_link->title); + + // Test that content_translation_source is set. + $manager = $this->container->get('content_translation.manager'); + $this->assertSame('en', $manager->getTranslationMetadata($node->getTranslation('is'))->getSource()); + + // Node 3 is a translation of node 2, and should not be imported separately. + $this->assertNull(Node::load(3), "Node 3 doesn't exist in D8, it was a translation"); + + // Test that content_translation_source for a source other than English. + $node = Node::load(4); + $this->assertSame('is', $manager->getTranslationMetadata($node->getTranslation('en'))->getSource()); + } } diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php index 6b19b95..da4f57e 100644 --- a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTest.php @@ -27,7 +27,6 @@ public function providerSource() { // The source data. $tests[0]['source_data']['node'] = [ [ - // Node fields. 'nid' => 1, 'vid' => 1, 'type' => 'page', @@ -44,7 +43,6 @@ public function providerSource() { 'translate' => 0, ], [ - // Node fields. 'nid' => 2, 'vid' => 2, 'type' => 'page', @@ -61,7 +59,6 @@ public function providerSource() { 'translate' => 0, ], [ - // Node fields. 'nid' => 5, 'vid' => 5, 'type' => 'article', @@ -77,11 +74,41 @@ public function providerSource() { 'tnid' => 0, 'translate' => 0, ], - + [ + 'nid' => 6, + 'vid' => 6, + 'type' => 'article', + 'language' => 'en', + 'title' => 'node title 5', + 'uid' => 1, + 'status' => 1, + 'created' => 1279291908, + 'changed' => 1279309993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + ], + [ + 'nid' => 7, + 'vid' => 7, + 'type' => 'article', + 'language' => 'fr', + 'title' => 'fr - node title 5', + 'uid' => 1, + 'status' => 1, + 'created' => 1279292908, + 'changed' => 1279310993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + ], ]; $tests[0]['source_data']['node_revision'] = [ [ - // Node fields. 'nid' => 1, 'vid' => 1, 'uid' => 2, @@ -94,7 +121,6 @@ public function providerSource() { 'sticky' => 0, ], [ - // Node fields. 'nid' => 2, 'vid' => 2, 'uid' => 2, @@ -107,7 +133,6 @@ public function providerSource() { 'sticky' => 0, ], [ - // Node fields. 'nid' => 5, 'vid' => 5, 'uid' => 2, @@ -119,6 +144,31 @@ public function providerSource() { 'promote' => 1, 'sticky' => 0, ], + [ + 'nid' => 6, + 'vid' => 6, + 'uid' => 1, + 'title' => 'node title 5', + 'log' => '', + 'timestamp' => 1279309993, + 'status' => 1, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + + ], + [ + 'nid' => 7, + 'vid' => 7, + 'uid' => 1, + 'title' => 'fr - node title 5', + 'log' => '', + 'timestamp' => 1279310993, + 'status' => 1, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + ], ]; $tests[0]['source_data']['field_config_instance'] = [ [ @@ -131,7 +181,7 @@ public function providerSource() { 'deleted' => '0', ], [ - 'id' => '2', + 'id' => '3', 'field_id' => '2', 'field_name' => 'body', 'entity_type' => 'node', @@ -153,12 +203,59 @@ public function providerSource() { 'body_summary' => '', 'body_format' => 'filtered_html', ], + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'deleted' => '0', + 'entity_id' => '2', + 'revision_id' => '2', + 'language' => 'en', + 'delta' => '0', + 'body_value' => 'body 2', + 'body_summary' => '', + 'body_format' => 'filtered_html', + ], + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'deleted' => '0', + 'entity_id' => '5', + 'revision_id' => '5', + 'language' => 'en', + 'delta' => '0', + 'body_value' => 'body 5', + 'body_summary' => '', + 'body_format' => 'filtered_html', + ], + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'deleted' => '0', + 'entity_id' => '6', + 'revision_id' => '6', + 'language' => 'en', + 'delta' => '0', + 'body_value' => 'body 6', + 'body_summary' => '', + 'body_format' => 'filtered_html', + ], + [ + 'entity_type' => 'node', + 'bundle' => 'page', + 'deleted' => '0', + 'entity_id' => '7', + 'revision_id' => '7', + 'language' => 'fr', + 'delta' => '0', + 'body_value' => 'fr - body 6', + 'body_summary' => '', + 'body_format' => 'filtered_html', + ], ]; // The expected results. $tests[0]['expected_data'] = [ [ - // Node fields. 'nid' => 1, 'vid' => 1, 'type' => 'page', @@ -172,7 +269,7 @@ public function providerSource() { 'comment' => 2, 'promote' => 1, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 1, 'translate' => 0, 'log' => '', 'timestamp' => 1279051598, @@ -185,7 +282,6 @@ public function providerSource() { ], ], [ - // Node fields. 'nid' => 2, 'vid' => 2, 'type' => 'page', @@ -199,13 +295,19 @@ public function providerSource() { 'comment' => 0, 'promote' => 1, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 2, 'translate' => 0, 'log' => '', 'timestamp' => 1279308993, + 'body' => [ + [ + 'value' => 'body 2', + 'summary' => '', + 'format' => 'filtered_html', + ], + ], ], [ - // Node fields. 'nid' => 5, 'vid' => 5, 'type' => 'article', @@ -219,10 +321,43 @@ public function providerSource() { 'comment' => 0, 'promote' => 1, 'sticky' => 0, - 'tnid' => 0, + 'tnid' => 5, 'translate' => 0, 'log' => '', 'timestamp' => 1279308993, + 'body' => [ + [ + 'value' => 'body 5', + 'summary' => '', + 'format' => 'filtered_html', + ], + ], + ], + [ + 'nid' => 6, + 'vid' => 6, + 'type' => 'article', + 'language' => 'en', + 'title' => 'node title 5', + 'node_uid' => 1, + 'revision_uid' => 1, + 'status' => 1, + 'created' => 1279291908, + 'changed' => 1279309993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + 'log' => '', + 'timestamp' => 1279309993, + 'body' => [ + [ + 'value' => 'body 6', + 'summary' => '', + 'format' => 'filtered_html', + ], + ], ], ]; diff --git a/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTranslationTest.php b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTranslationTest.php new file mode 100644 index 0000000..e46bd35 --- /dev/null +++ b/core/modules/node/tests/src/Kernel/Plugin/migrate/source/d7/NodeTranslationTest.php @@ -0,0 +1,69 @@ + 7, + 'vid' => 7, + 'type' => 'article', + 'language' => 'fr', + 'title' => 'fr - node title 5', + 'node_uid' => 1, + 'revision_uid' => 1, + 'status' => 1, + 'created' => 1279292908, + 'changed' => 1279310993, + 'comment' => 0, + 'promote' => 1, + 'sticky' => 0, + 'tnid' => 6, + 'translate' => 0, + // Node revision fields. + 'log' => '', + 'timestamp' => 1279310993, + 'body' => [ + [ + 'value' => 'fr - body 6', + 'summary' => '', + 'format' => 'filtered_html', + ], + ], + ], + ]; + + // Do an automatic count. + $tests[0]['expected_count'] = NULL; + + // Set up source plugin configuration. + $tests[0]['configuration'] = [ + 'translations' => TRUE, + ]; + + return $tests; + } + +}