diff --git a/core/modules/migrate/config/schema/migrate.destination.schema.yml b/core/modules/migrate/config/schema/migrate.destination.schema.yml index b295741..9c657f9 100644 --- a/core/modules/migrate/config/schema/migrate.destination.schema.yml +++ b/core/modules/migrate/config/schema/migrate.destination.schema.yml @@ -16,3 +16,12 @@ migrate.destination.config: config_name: type: string label: 'Configuration name' + +migrate.destination.entity_revision:*: + type: migrate_destination + label: 'Entity revision' + mapping: + translated: + type: boolean + label: 'Whether revisions are translated' + default: false 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/src/Plugin/migrate/destination/EntityRevision.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php index 24602b6..8195831 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php @@ -70,7 +70,13 @@ protected function getEntity(Row $row, array $old_destination_id_values) { */ protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) { $entity->save(); - return array($entity->getRevisionId()); + $ids = array($entity->getRevisionId()); + + if (isset($this->configuration['translated']) && $this->configuration['translated']) { + $ids[] = $entity->language()->getId(); + } + + return $ids; } /** @@ -79,9 +85,21 @@ protected function save(ContentEntityInterface $entity, array $old_destination_i public function getIds() { if ($key = $this->getKey('revision')) { $ids[$key]['type'] = 'integer'; - return $ids; } - throw new MigrateException('This entity type does not support revisions.'); + else { + throw new MigrateException('This entity type does not support revisions.'); + } + + if (isset($this->configuration['translated']) && $this->configuration['translated']) { + if ($key = $this->getKey('langcode')) { + $ids[$key]['type'] = 'string'; + } + else { + throw new MigrateException('This entity type does not support translation.'); + } + } + + return $ids; } } diff --git a/core/modules/migrate/src/Tests/MigrateFilterExecutable.php b/core/modules/migrate/src/Tests/MigrateFilterExecutable.php new file mode 100644 index 0000000..422904e --- /dev/null +++ b/core/modules/migrate/src/Tests/MigrateFilterExecutable.php @@ -0,0 +1,46 @@ +filter = $filter; + } + + public function processRow(Row $row, array $process = NULL, $value = NULL) { + if (!call_user_func($this->filter, $this->migration, $row)) { + throw new MigrateSkipRowException("Row filtered", FALSE); + } + parent::processRow($row, $process, $value); + } + +} diff --git a/core/modules/migrate/src/Tests/MigrateTestBase.php b/core/modules/migrate/src/Tests/MigrateTestBase.php index e9012e3..23ea4e5 100644 --- a/core/modules/migrate/src/Tests/MigrateTestBase.php +++ b/core/modules/migrate/src/Tests/MigrateTestBase.php @@ -147,8 +147,10 @@ protected function prepareMigrations(array $id_mappings) { * * @param string|\Drupal\migrate\Entity\MigrationInterface $migration * The migration to execute, or its ID. + * @param callback $filter + * Predicate for source rows to migrate. */ - protected function executeMigration($migration) { + protected function executeMigration($migration, $filter = NULL) { if (is_string($migration)) { $this->migration = Migration::load($migration); } @@ -158,7 +160,15 @@ protected function executeMigration($migration) { if ($this instanceof MigrateDumpAlterInterface) { static::migrateDumpAlter($this); } - (new MigrateExecutable($this->migration, $this))->import(); + + $executable = NULL; + if ($filter) { + $executable = new MigrateFilterExecutable($this->migration, $this, NULL, $filter); + } + else { + $executable = new MigrateExecutable($this->migration, $this); + } + $executable->import(); } /** @@ -166,10 +176,14 @@ protected function executeMigration($migration) { * * @param string[] $ids * Array of migration IDs, in any order. + * @param callback $filter + * Predicate for source rows to migrate. */ - protected function executeMigrations(array $ids) { + protected function executeMigrations(array $ids, $filter = NULL) { $migrations = Migration::loadMultiple($ids); - array_walk($migrations, [$this, 'executeMigration']); + array_walk($migrations, function($id) use ($filter) { + $this->executeMigration($id, $filter); + }); } /** 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/migration_templates/d6_node_revision.yml b/core/modules/node/migration_templates/d6_node_revision.yml index e7f3b54..eaba3e9 100644 --- a/core/modules/node/migration_templates/d6_node_revision.yml +++ b/core/modules/node/migration_templates/d6_node_revision.yml @@ -39,3 +39,4 @@ process: destination: plugin: entity_revision:node + translated: true 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..53300f4 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/Node.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/Node.php @@ -18,11 +18,10 @@ * ) */ class Node extends DrupalSqlBase { - /** - * The join options between the node and the node_revisions table. + * An expression for the translation set of a node. */ - const JOIN = 'n.vid = nr.vid'; + const NODE_TRANSLATION_SET = 'CASE n.tnid WHEN 0 THEN n.nid ELSE n.tnid END'; /** * The default filter format. @@ -42,12 +41,11 @@ class Node extends DrupalSqlBase { * {@inheritdoc} */ public function query() { + $query = $this->translationQuery(); + // Select node in its last revision. - $query = $this->select('node_revisions', 'nr') - ->fields('n', array( - 'nid', + $query->fields('n', array( 'type', - 'language', 'status', 'created', 'changed', @@ -55,11 +53,9 @@ public function query() { 'promote', 'moderate', 'sticky', - 'tnid', 'translate', )) ->fields('nr', array( - 'vid', 'title', 'body', 'teaser', @@ -69,10 +65,9 @@ public function query() { )); $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('type', $this->configuration['node_type']); + $query->condition('n.type', $this->configuration['node_type']); } return $query; @@ -92,6 +87,7 @@ protected function initializeIterator() { public function fields() { $fields = array( 'nid' => $this->t('Node ID'), + 'vid' => $this->t('Revision ID'), 'type' => $this->t('Type'), 'title' => $this->t('Title'), 'body' => $this->t('Body'), @@ -106,7 +102,6 @@ public function fields() { 'sticky' => $this->t('Sticky at top of lists'), 'revision' => $this->t('Create new revision'), 'language' => $this->t('Language (fr, en, ...)'), - 'tnid' => $this->t('The translation set id for this node'), 'timestamp' => $this->t('The timestamp the latest revision of this node was created.'), ); return $fields; @@ -238,7 +233,7 @@ protected function getCckData(array $field, Row $node) { // the time being. ->isNotNull($field['field_name'] . '_' . $columns[0]) ->condition('nid', $node->getSourceProperty('nid')) - ->condition('vid', $node->getSourceProperty('vid')) + ->condition('vid', $node->getSourceProperty('field_vid')) ->execute() ->fetchAllAssoc('delta'); } @@ -256,4 +251,44 @@ public function getIds() { return $ids; } + /** + * Build a query to get the maximum vid of each translation set. + * + * @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', 'n'); + $query->fields('n', ['nid', 'language']); + + // Only yield the default node for each translation set. + $query->where('n.tnid = 0 OR n.nid = n.tnid'); + + // Claim our vid is the maximum vid of our translation set. + $query->join($this->maxVidQuery(), 'max_vid', 'n.nid = max_vid.translation_set'); + $query->fields('max_vid', ['vid']); + + // Use the real vid for finding fields. + $query->join('node_revisions', 'nr', 'n.vid = nr.vid'); + $query->addField('nr', 'vid', 'field_vid'); + + 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 8f426c0..2ce683a 100644 --- a/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php +++ b/core/modules/node/src/Plugin/migrate/source/d6/NodeRevision.php @@ -17,9 +17,9 @@ class NodeRevision extends Node { /** - * The join options between the node and the node_revisions_table. + * The join options between a node table and its translations. */ - const JOIN = 'n.nid = nr.nid AND n.vid <> nr.vid'; + const NODE_TRANSLATION_JOIN = '(n.tnid <> 0 AND n.tnid = nt.tnid) OR n.nid = nt.nid'; /** * {@inheritdoc} @@ -27,7 +27,6 @@ class NodeRevision extends Node { public function fields() { // Use all the node fields plus the vid that identifies the version. return parent::fields() + array( - 'vid' => t('The primary identifier for this version.'), 'log' => $this->t('Revision Log message'), 'timestamp' => $this->t('Revision timestamp'), ); @@ -38,8 +37,87 @@ public function fields() { */ public function getIds() { $ids['vid']['type'] = 'integer'; - $ids['vid']['alias'] = 'nr'; + $ids['vid']['alias'] = 'tvids'; + $ids['language']['type'] = 'string'; + $ids['language']['alias'] = 'tvids'; return $ids; } + /** + * Build a query to turn each revision into multiple translation rows. + * + * For each D6 revision, generate a row for each translation that existed + * at that time. + * + * Eg: If we have: + * | tnid | nid | vid | language | + * | 1 | 1 | 1 | en | + * | 1 | 2 | 2 | fr | + * | 1 | 3 | 3 | de | + * | 1 | 1 | 4 | en | + * + * + * Then we generate the following rows: + * | vid | language | tvid | + * | 1 | en | 1 | + * | 2 | en | 1 | + * | 2 | fr | 2 | + * | 3 | en | 1 | + * | 3 | fr | 2 | + * | 3 | de | 3 | + * | 4 | en | 4 | + * | 4 | fr | 2 | + * | 4 | de | 3 | + * + * @return \Drupal\Core\Database\Query\SelectInterface + * The generated query. + */ + protected function translationRevisionsQuery() { + // Find the nodes that are translations of this node. + $query = $this->select('node_revisions', 'nr'); + $query->join('node', 'n', 'n.nid = nr.nid'); + $query->join('node', 'nt', self::NODE_TRANSLATION_JOIN); + + // Find all translation revisions with lower vids than the current one. + $query->join('node_revisions', 'nrt', + 'nrt.nid = nt.nid AND nr.vid >= nrt.vid'); + + // For each vid/language pair, we only want the top translation vid. + $query->groupBy('nr.vid'); + $query->groupBy('nt.language'); + $query->addField('nr', 'vid'); + $query->addField('nt', 'language'); + $query->addExpression('MAX(nrt.vid)', 'tvid'); + + return $query; + } + + /** + * {@inheritdoc} + */ + protected function translationQuery() { + // Multiply each revision according to translationRevisionsQuery. + $query = $this->select('node_revisions', 'nr'); + $query->join($this->translationRevisionsQuery(), 'tvids', + 'nr.vid = tvids.tvid'); + + // Get fields according to this row of node_revisions. + $query->addField('nr', 'vid', 'field_vid'); + // Claim to be from a translation, see 'vid' in the table from + // translationRevisionsQuery. + $query->fields('tvids', ['vid', 'language']); + + // Get the default node for this translation set. + $query->join('node', 'nt', 'nt.nid = nr.nid'); + $query->join('node', 'n', self::NODE_TRANSLATION_JOIN); + $query->where('n.tnid = 0 OR n.tnid = n.nid'); + $query->addField('n', 'nid'); + + // Add orders, for reproducible results. + $query->orderBy('vid'); + $query->orderBy('language'); + + 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 b8ee0c2..bb817b6 100644 --- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php +++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeRevisionTest.php @@ -7,6 +7,11 @@ namespace Drupal\node\Tests\Migrate\d6; +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\migrate\Entity\MigrationInterface; +use Drupal\migrate\Row; +use Drupal\node\NodeInterface; + /** * Node content revisions migration. * @@ -19,14 +24,20 @@ class MigrateNodeRevisionTest extends MigrateNodeTestBase { */ protected function setUp() { parent::setUp(); - $this->executeMigrations(['d6_node:*', 'd6_node_revision:*']); + // The revision migrations include translations, so we need to install + // the necessary languages. + ConfigurableLanguage::createFromLangcode('en')->save(); + ConfigurableLanguage::createFromLangcode('fr')->save(); } /** * Test node revisions migration from Drupal 6 to 8. */ public function testNodeRevision() { - $node = \Drupal::entityManager()->getStorage('node')->loadRevision(2); + $this->executeMigrations(['d6_node:*', 'd6_node_revision:*']); + $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 +49,76 @@ 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()); + $this->assertTrue($node->isDefaultRevision()); + // 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); + + // The node as a whole should have both languages. + $node = $storage->load(9); + $languages = array_keys($node->getTranslationLanguages()); + sort($languages); + $this->assertIdentical(['en', 'fr'], $languages); + } + + /** + * Test partial node revision migrations from Drupal 6 to 8. + */ + public function testSomeNodeRevision() { + $storage = \Drupal::entityManager()->getStorage('node'); + + // Make sure we don't already have a revision. + $this->assertNull($storage->loadRevision(13), 'No revision before migrations'); + + $this->executeMigrations(['d6_node:*', 'd6_node_revision:*'], function(MigrationInterface $migration, Row $row) { + // Only include nodes with nid 9. + if ($row->getSourceProperty('nid') != 9) { + return FALSE; + } + // Only include revision 13. + if (strstr($migration->id(), 'node_revision') !== FALSE) { + if ($row->getSourceProperty('vid') != 13) { + return FALSE; + } + } + return TRUE; + }); + + /** @var \Drupal\node\NodeInterface $node */ + $node = $storage->loadRevision(13); + // Both languages should be present in this revision. + $this->assertTrue($node->hasTranslation('en'), 'English translation exists'); + $this->assertIdentical('The Real McCoy', $node->getTranslation('en')->getTitle()); + $this->assertTrue($node->hasTranslation('fr'), 'French translation exists'); + $this->assertIdentical('Le Vrai McCoy', $node->getTranslation('fr')->getTitle()); } } diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php index cf0c44b..104ad43 100644 --- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php +++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php @@ -12,6 +12,7 @@ use Drupal\migrate\Entity\MigrationInterface; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; /** * Node content migration. @@ -81,6 +82,14 @@ public function testNode() { $this->assertIdentical('Migrate API in Drupal 8', $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); + + // 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..0f9bc4c 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 = [ @@ -73,6 +62,42 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'uid' => 1, 'title' => 'title for revision 2 (node 2)', ], + // A translation set. + [ + 'nid' => 3, + 'type' => 'article', + 'language' => 'en', + 'status' => 1, + 'created' => 1279309100, + 'changed' => 1279309300, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 3, + 'translate' => 0, + 'vid' => 2, + 'uid' => 1, + 'title' => 'title for revision 7 (node 3)', + ], + [ + 'nid' => 4, + 'type' => 'article', + 'language' => 'fr', + 'status' => 1, + 'created' => 1279309200, + 'changed' => 1279309200, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'tnid' => 3, + 'translate' => 0, + 'vid' => 2, + 'uid' => 1, + 'title' => 'title for revision 6 (node 4)', + ], + ], 'node_revisions' => [ [ @@ -119,13 +144,46 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'format' => 1, 'timestamp' => 1279308993, ], + // Translations. + [ + 'nid' => 3, + 'vid' => 5, + 'uid' => 1, + 'title' => 'title for revision 5 (node 3)', + 'body' => 'body for revision 5 (node 3)', + 'teaser' => 'teaser for revision 5 (node 3)', + 'log' => 'log for revision 5 (node 3)', + 'format' => 1, + 'timestamp' => 1279309100, + ], + [ + 'nid' => 3, + 'vid' => 7, + 'uid' => 1, + 'title' => 'title for revision 7 (node 3)', + 'body' => 'body for revision 7 (node 3)', + 'teaser' => 'teaser for revision 7 (node 3)', + 'log' => 'log for revision 7 (node 3)', + 'format' => 1, + 'timestamp' => 1279309300, + ], + [ + 'nid' => 4, + 'vid' => 6, + 'uid' => 1, + 'title' => 'title for revision 6 (node 4)', + 'body' => 'body for revision 6 (node 4)', + 'teaser' => 'teaser for revision 6 (node 4)', + 'log' => 'log for revision 6 (node 4)', + 'format' => 1, + 'timestamp' => 1279309200, + ], ], ]; - // There are three revisions of nid 1, but the NodeRevision source ignores - // the current revision. So only two revisions will be returned here. nid 2 - // is ignored because it only has one revision (the current one). - protected $expectedResults = [ + // There are three revisions of nid 1, and one for node 2. Results are sorted + // by vid. + protected $expectedResults = [ [ // Node fields. 'nid' => 1, @@ -138,7 +196,6 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, 'translate' => 0, // Node revision fields. 'vid' => 1, @@ -149,6 +206,30 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'teaser' => 'teaser for revision 1 (node 1)', 'log' => 'log for revision 1 (node 1)', 'format' => 1, + 'field_vid' => 1, + ], + [ + 'nid' => 2, + 'type' => 'article', + 'language' => 'en', + 'status' => 1, + 'created' => 1279290908, + 'changed' => 1279308993, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 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, + 'field_vid' => 2, ], [ // Node fields. @@ -162,7 +243,6 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, 'translate' => 0, // Node revision fields. 'vid' => 3, @@ -173,6 +253,146 @@ class NodeRevisionTest extends MigrateSqlSourceTestCase { 'teaser' => 'teaser for revision 3 (node 1)', 'log' => 'log for revision 3 (node 1)', 'format' => 1, + 'field_vid' => 3, + ], + [ + 'nid' => 1, + 'type' => 'page', + 'language' => 'en', + 'status' => 1, + 'created' => 1279051598, + 'changed' => 1279051598, + 'comment' => 2, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 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, + 'field_vid' => 4, + ], + // Translations add extra revisions. + [ + 'nid' => 3, + 'type' => 'article', + 'language' => 'en', + 'status' => 1, + 'created' => 1279309100, + 'changed' => 1279309300, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'vid' => 5, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 5 (node 3)', + 'body' => 'body for revision 5 (node 3)', + 'teaser' => 'teaser for revision 5 (node 3)', + 'log' => 'log for revision 5 (node 3)', + 'format' => 1, + 'timestamp' => 1279309100, + 'field_vid' => 5, + ], + [ + 'nid' => 3, + 'type' => 'article', + 'language' => 'en', + 'status' => 1, + 'created' => 1279309100, + 'changed' => 1279309300, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'vid' => 6, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 5 (node 3)', + 'body' => 'body for revision 5 (node 3)', + 'teaser' => 'teaser for revision 5 (node 3)', + 'log' => 'log for revision 5 (node 3)', + 'format' => 1, + 'timestamp' => 1279309100, + 'field_vid' => 5, + ], + [ + 'nid' => 3, + 'type' => 'article', + 'language' => 'fr', + 'status' => 1, + 'created' => 1279309100, + 'changed' => 1279309300, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'vid' => 6, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 6 (node 4)', + 'body' => 'body for revision 6 (node 4)', + 'teaser' => 'teaser for revision 6 (node 4)', + 'log' => 'log for revision 6 (node 4)', + 'format' => 1, + 'timestamp' => 1279309200, + 'field_vid' => 6, + ], + [ + 'nid' => 3, + 'type' => 'article', + 'language' => 'en', + 'status' => 1, + 'created' => 1279309100, + 'changed' => 1279309300, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'vid' => 7, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 7 (node 3)', + 'body' => 'body for revision 7 (node 3)', + 'teaser' => 'teaser for revision 7 (node 3)', + 'log' => 'log for revision 7 (node 3)', + 'format' => 1, + 'timestamp' => 1279309300, + 'field_vid' => 7, + ], + [ + 'nid' => 3, + 'type' => 'article', + 'language' => 'fr', + 'status' => 1, + 'created' => 1279309100, + 'changed' => 1279309300, + 'comment' => 0, + 'promote' => 1, + 'moderate' => 0, + 'sticky' => 0, + 'translate' => 0, + 'vid' => 7, + 'node_uid' => 1, + 'revision_uid' => 1, + 'title' => 'title for revision 6 (node 4)', + 'body' => 'body for revision 6 (node 4)', + 'teaser' => 'teaser for revision 6 (node 4)', + 'log' => 'log for revision 6 (node 4)', + 'format' => 1, + 'timestamp' => 1279309200, + 'field_vid' => 6, ], ]; 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 13131a1..c38af09 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 @@ -41,7 +41,6 @@ class NodeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, 'translate' => 0, // Node revision fields. 'body' => 'body for node 1', @@ -65,7 +64,6 @@ class NodeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, 'translate' => 0, // Node revision fields. 'body' => 'body for node 2', @@ -88,7 +86,6 @@ class NodeTest extends MigrateSqlSourceTestCase { 'promote' => 1, 'moderate' => 0, 'sticky' => 0, - 'tnid' => 0, 'translate' => 0, // Node revision fields. 'body' => 'body for node 5', @@ -100,6 +97,28 @@ 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, + 'translate' => 0, + // Node revision fields. + 'body' => 'body for node 6', + 'teaser' => 'body for node 6', + 'log' => '', + 'timestamp' => 1279308994, + 'format' => 1, + ), ); /** @@ -150,22 +169,114 @@ protected function setUp() { 'status' => TRUE, ), ); - foreach ($this->expectedResults as $k => $row) { + $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_row = $result; + break; + } + } + + // Populate node_revisions. 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; + $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']++; } } - $this->databaseContents['node'][$k] = $row; } + array_walk($this->expectedResults, function (&$row) { $row['node_uid'] = $row['uid']; $row['revision_uid'] = $row['uid'] + 1;