diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index f23b54f..bcd57cc 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -12,6 +12,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\Entity\MigrationInterface; use Drupal\migrate\MigrateException; @@ -127,6 +128,19 @@ public function getIds() { * The row object to update from. */ protected function updateEntity(EntityInterface $entity, Row $row) { + 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); + } + $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. diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6TestBase.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6TestBase.php index 1d8f5f4..ff85842 100644 --- a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6TestBase.php +++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6TestBase.php @@ -7,6 +7,7 @@ namespace Drupal\migrate_drupal\Tests\d6; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\migrate\Entity\Migration; use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase; @@ -22,6 +23,7 @@ 'datetime', 'filter', 'image', + 'language', 'link', 'node', 'options', @@ -107,6 +109,10 @@ protected function migrateContent($include_revisions = FALSE) { $this->executeMigrations(['d6_node_settings', 'd6_node:*']); if ($include_revisions) { + // The revision migrations include translations, so we need to install + // the necessary languages. + ConfigurableLanguage::createFromLangcode('en')->save(); + ConfigurableLanguage::createFromLangcode('fr')->save(); $this->executeMigrations(['d6_node_revision:*']); } } 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 b8a4d7d..cd7bddc 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/Node.php @@ -7,6 +7,8 @@ namespace Drupal\node\Plugin\migrate\source\d6; +use Drupal\Core\Database\Query\Condition; +use Drupal\Core\Database\Query\SelectInterface; use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; @@ -75,6 +77,8 @@ public function query() { $query->condition('type', $this->configuration['node_type']); } + $this->addTranslationConditions($query); + return $query; } @@ -256,4 +260,17 @@ public function getIds() { return $ids; } + /** + * Add conditions related to translations. + * + * @param $query + */ + protected function addTranslationConditions(SelectInterface $query) { + // Do not include translations. + $condition = (new Condition('OR')) + ->where('n.tnid = n.nid') + ->condition('n.tnid', 0); + $query->condition($condition); + } + } 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 8f426c0..4a2bc01 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php @@ -7,6 +7,9 @@ namespace Drupal\node\Plugin\migrate\source\d6; +use Drupal\Core\Database\Query\SelectInterface; +use Drupal\migrate\Row; + /** * Drupal 6 node revision source from database. * @@ -19,7 +22,17 @@ class NodeRevision extends Node { /** * The join options between the node and the node_revisions_table. */ - const JOIN = 'n.nid = nr.nid AND n.vid <> nr.vid'; + const JOIN = 'n.nid = nr.nid'; + + /** + * {@inheritdoc} + */ + public function prepareRow(Row $row) { + if ($tnid = $row->getSourceProperty('tnid')) { + $row->setSourceProperty('nid', $tnid); + } + return parent::prepareRow($row); + } /** * {@inheritdoc} @@ -42,4 +55,11 @@ public function getIds() { return $ids; } + /** + * {@inheritdoc} + */ + protected function addTranslationConditions(SelectInterface $query) { + // Translations are included in the selection by default. + } + } diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php index b8ee0c2..c2ecee6 100644 --- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php +++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php @@ -7,6 +7,9 @@ namespace Drupal\node\Tests\Migrate\d6; +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\node\NodeInterface; + /** * Node content revisions migration. * @@ -19,6 +22,10 @@ class MigrateNodeRevisionTest extends MigrateNodeTestBase { */ protected function setUp() { parent::setUp(); + // The revision migrations include translations, so we need to install + // the necessary languages. + ConfigurableLanguage::createFromLangcode('en')->save(); + ConfigurableLanguage::createFromLangcode('fr')->save(); $this->executeMigrations(['d6_node:*', 'd6_node_revision:*']); } @@ -26,7 +33,9 @@ protected function setUp() { * Test node revisions migration from Drupal 6 to 8. */ public function testNodeRevision() { - $node = \Drupal::entityManager()->getStorage('node')->loadRevision(2); + $storage = \Drupal::entityManager()->getStorage('node'); + + $node = $storage->loadRevision(2); /** @var \Drupal\node\NodeInterface $node */ $this->assertIdentical('1', $node->id()); $this->assertIdentical('2', $node->getRevisionId()); @@ -38,12 +47,37 @@ public function testNodeRevision() { $this->assertIdentical('modified rev 2', $node->revision_log->value); $this->assertIdentical('1390095702', $node->getRevisionCreationTime()); - $node = \Drupal::entityManager()->getStorage('node')->loadRevision(5); + $node = $storage->loadRevision(5); $this->assertIdentical('1', $node->id()); $this->assertIdentical('body test rev 3', $node->body->value); $this->assertIdentical('1', $node->getRevisionAuthor()->id()); $this->assertIdentical('modified rev 3', $node->revision_log->value); $this->assertIdentical('1390095703', $node->getRevisionCreationTime()); + + // Revision 12 is the default revision of node 9. + $node = $storage->loadRevision(12); + $this->assertTrue($node instanceof NodeInterface); + $this->assertIdentical('9', $node->id()); + // The French translation is only introduced in rev. 13. + $this->assertTrue($node->hasTranslation('en')); + $this->assertFalse($node->hasTranslation('fr')); + $this->assertIdentical('The Real McCoy', $node->getTitle()); + $this->assertIdentical("In the original, Queen's English.", $node->body->value); + + // Revision 13 was part of node 10, which is a translation of node 9. + $node = $storage->loadRevision(13); + $this->assertTrue($node instanceof NodeInterface); + $this->assertIdentical('9', $node->id()); + // English is the node's default language, in any revision. + $this->assertIdentical('en', $node->language()->getId()); + // The English title and body did not change in this revision... + $this->assertIdentical('The Real McCoy', $node->getTitle()); + $this->assertIdentical("In the original, Queen's English.", $node->body->value); + // ...but a French translation was introduced. + $this->assertTrue($node->hasTranslation('fr')); + $node = $node->getTranslation('fr'); + $this->assertIdentical('Le Vrai McCoy', $node->getTitle()); + $this->assertIdentical("Ooh là là!", $node->body->value); } } diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php index 1957dc1..48b41b6 100644 --- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php +++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php @@ -57,6 +57,13 @@ public function testNode() { $this->assertIdentical('test rev 3', $node->body->value); $this->assertIdentical('filtered_html', $node->body->format); + $node = Node::load(9); + $this->assertTrue($node instanceof NodeInterface); + $this->assertIdentical('en', $node->langcode->value); + + // Node 10 is a translation of node 9, and should not be imported separately. + $this->assertNull(Node::load(10)); + // Test that we can re-import using the EntityContentBase destination. $title = $this->rerunMigration(); $node = Node::load(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 9e409d9..443295f 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 @@ -7,6 +7,7 @@ namespace Drupal\Tests\node\Unit\Plugin\migrate\source\d6; +use Drupal\node\Plugin\migrate\source\d6\NodeRevision; use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase; /** @@ -16,26 +17,14 @@ */ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { - const PLUGIN_CLASS = 'Drupal\node\Plugin\migrate\source\d6\NodeRevision'; + const PLUGIN_CLASS = NodeRevision::class; - // The fake Migration configuration entity. protected $migrationConfiguration = [ 'id' => 'test', - // The fake configuration for the source. 'source' => [ 'plugin' => 'd6_node_revision', 'node_type' => 'page', ], - 'sourceIds' => [ - 'vid' => [ - 'alias' => 'v', - ], - ], - 'destinationIds' => [ - 'vid' => [ - // This is where the field schema would go. - ], - ], ]; protected $databaseContents = [ @@ -123,10 +112,6 @@ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { ], ]; - // There are three revisions of nid 1; vid 4 is the current one. The - // NodeRevision plugin should capture every revision EXCEPT that one. - // nid 2 will be ignored because $this->migrationConfiguration specifies - // a particular node type. protected $expectedResults = [ [ 'nid' => 1, @@ -172,6 +157,29 @@ class NodeRevisionByNodeTypeTest extends MigrateSqlSourceTestCase { 'log' => 'log for revision 3 (node 1)', 'format' => 1, ], + [ + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + 'vid' => 4, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 4 (node 1)', + 'body' => 'body for revision 4 (node 1)', + 'teaser' => 'teaser for revision 4 (node 1)', + 'log' => 'log for revision 4 (node 1)', + 'format' => 1, + 'timestamp' => 1279051598, + ], ]; } 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 6982341..7bc7e8b 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 @@ -7,6 +7,7 @@ namespace Drupal\Tests\node\Unit\Plugin\migrate\source\d6; +use Drupal\node\Plugin\migrate\source\d6\NodeRevision; use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase; /** @@ -16,25 +17,13 @@ */ class NodeRevisionTest extends MigrateSqlSourceTestCase { - const PLUGIN_CLASS = 'Drupal\node\Plugin\migrate\source\d6\NodeRevision'; + const PLUGIN_CLASS = NodeRevision::class; - // The fake Migration configuration entity. protected $migrationConfiguration = [ 'id' => 'test', - // The fake configuration for the source. 'source' => [ 'plugin' => 'd6_node_revision', ], - 'sourceIds' => [ - 'vid' => [ - 'alias' => 'v', - ], - ], - 'destinationIds' => [ - 'vid' => [ - // This is where the field schema would go. - ], - ], ]; protected $databaseContents = [ @@ -174,6 +163,52 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'log' => 'log for revision 3 (node 1)', 'format' => 1, ], + [ + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + 'vid' => 4, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 4 (node 1)', + 'body' => 'body for revision 4 (node 1)', + 'teaser' => 'teaser for revision 4 (node 1)', + 'log' => 'log for revision 4 (node 1)', + 'format' => 1, + 'timestamp' => 1279051598, + ], + [ + 'nid' => 2, + 'type' => 'article', + 'language' => 'en', + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 0, + 'translate' => 0, + 'vid' => 2, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 2 (node 2)', + 'body' => 'body for revision 2 (node 2)', + 'teaser' => 'teaser for revision 2 (node 2)', + 'log' => 'log for revision 2 (node 2)', + 'format' => 1, + 'timestamp' => 1279308993, + ], ]; }