diff --git a/core/modules/migrate/src/Audit/IdAuditor.php b/core/modules/migrate/src/Audit/IdAuditor.php index f482acd597..17f6ffe938 100644 --- a/core/modules/migrate/src/Audit/IdAuditor.php +++ b/core/modules/migrate/src/Audit/IdAuditor.php @@ -3,6 +3,7 @@ namespace Drupal\migrate\Audit; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\migrate\Plugin\migrate\destination\EntityContentComplete; use Drupal\migrate\Plugin\MigrationInterface; /** @@ -33,11 +34,12 @@ public function audit(MigrationInterface $migration) { throw new AuditException($migration, "ID map does not implement $interface"); } - if ($destination->getHighestId() > $id_map->getHighestId()) { + if ($destination->getHighestId() > $id_map->getHighestId() || ($destination instanceof EntityContentComplete && !$this->auditEntityComplete($migration))) { return AuditResult::fail($migration, [ $this->t('The destination system contains data which was not created by a migration.'), ]); } + return AuditResult::pass($migration); } @@ -55,4 +57,44 @@ public function auditMultiple(array $migrations) { return $conflicts; } + /** + * Audits an EntityComplete migration. + * + * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * The migration to audit. + * + * @return bool + * TRUE if the audit passes and FALSE if not. + * + * @todo Refactor in https://www.drupal.org/project/drupal/issues/3061676 or + * https://www.drupal.org/project/drupal/issues/3091004 + */ + private function auditEntityComplete(MigrationInterface $migration) { + $map_table = $migration->getIdMap()->mapTableName(); + + $database = \Drupal::database(); + if (!$database->schema()->tableExists($map_table)) { + throw new \InvalidArgumentException(); + } + + $query = $database->select($map_table, 'map') + ->fields('map', ['destid2']) + ->range(0, 1) + ->orderBy('destid2', 'DESC'); + $max = (int) $query->execute()->fetchField(); + + // Make a migration based on node_complete but with an entity_revision + // destination. + $revision_migration = $migration->getPluginDefinition(); + $revision_migration['id'] = $migration->getPluginId() . '-revision'; + $revision_migration['destination']['plugin'] = 'entity_revision:node'; + $revision_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($revision_migration); + + // Get the highest node revision ID. + $destination = $revision_migration->getDestinationPlugin(); + $highest = $destination->getHighestId(); + + return $max <= $highest; + } + } diff --git a/core/modules/migrate/src/Audit/NodeCompleteIdAuditor.php b/core/modules/migrate/src/Audit/NodeCompleteIdAuditor.php deleted file mode 100644 index a955564a78..0000000000 --- a/core/modules/migrate/src/Audit/NodeCompleteIdAuditor.php +++ /dev/null @@ -1,88 +0,0 @@ -getPluginId(); - if (preg_match('/node_complete/', $migration_id) === 1) { - $max = static::getHighestMigratedNodeRevisionId($migration); - - // Make a migration based on node_complete but with an entity_revision - // destination. - $revision_migration = $migration->getPluginDefinition(); - $revision_migration['id'] = $migration->getPluginId() . '-revision'; - $revision_migration['destination']['plugin'] = 'entity_revision:node'; - $revision_migration = $migration_plugin_manager->createStubMigration($revision_migration); - - // Get the highest node revision ID. - $destination = $revision_migration->getDestinationPlugin(); - $highest = $destination->getHighestId(); - - if ($highest > $max) { - $results[$migration_id . '-revision'] = AuditResult::fail($revision_migration, [ - t('The destination system contains data which was not created by a migration.'), - ]); - } - else { - $results[$migration_id . '-revision'] = AuditResult::pass($revision_migration); - } - } - } - return $results; - } - - /** - * Gets highest migrated node revision ID. - * - * @param \Drupal\migrate\Plugin\MigrationInterface $migration - * A node_complete migration. - * - * @return int - * The highest migrated node revision ID. - * - * @throws \InvalidArgumentException - * Thrown when the ID map table does not exist. - */ - protected static function getHighestMigratedNodeRevisionId(MigrationInterface $migration) : int { - $map_table = $migration->getIdMap()->mapTableName(); - - $database = \Drupal::database(); - if (!$database->schema()->tableExists($map_table)) { - throw new \InvalidArgumentException(); - } - - $query = $database->select($map_table, 'map') - ->fields('map', ['destid2']) - ->range(0, 1) - ->orderBy('destid2', 'DESC'); - $ids[] = $query->execute()->fetchField(); - $max = (int) (max($ids)); - - return $max; - } - -} diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php index b4baf39ef0..88e44bab65 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php @@ -8,7 +8,7 @@ use Drupal\migrate\Row; /** - * Provides a node destination for migrating the entire entity revision table. + * Provides a destination for migrating the entire entity revision table. * * @MigrateDestination( * id = "entity_complete", diff --git a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php index 52937b9a70..4c3170ba70 100644 --- a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php @@ -128,7 +128,7 @@ protected function getMigrations($database_state_key, $drupal_version) { // migration. That is, if this is a complete node migration then unset the // classic node migrations and if this is a classic node migration then // unset the complete node migrations. - $type = (new NodeMigrateType)->getNodeMigrateType([], $drupal_version); + $type = NodeMigrateType::getNodeMigrateType(\Drupal::database(), $drupal_version); switch ($type) { case NodeMigrateType::NODE_MIGRATE_TYPE_COMPLETE: $patterns = '/(d' . $drupal_version . '_node:)|(d' . $drupal_version . '_node_translation:)|(d' . $drupal_version . '_node_revision:)|(d7_node_entity_translation:)/'; @@ -206,7 +206,7 @@ protected function getFollowUpMigrationTags() { * A string representing the major branch of Drupal core (e.g. '6' for * Drupal 6.x), or FALSE if no valid version is matched. */ - protected function getLegacyDrupalVersion(Connection $connection) { + public static function getLegacyDrupalVersion(Connection $connection) { // Don't assume because a table of that name exists, that it has the columns // we're querying. Catch exceptions and report that the source database is // not Drupal. diff --git a/core/modules/migrate_drupal/src/NodeMigrateType.php b/core/modules/migrate_drupal/src/NodeMigrateType.php index ce335fd23e..94ce0dfa42 100644 --- a/core/modules/migrate_drupal/src/NodeMigrateType.php +++ b/core/modules/migrate_drupal/src/NodeMigrateType.php @@ -2,6 +2,8 @@ namespace Drupal\migrate_drupal; +use Drupal\Core\Database\Connection; + /** * Provides a class to determine the type of migration. */ @@ -25,69 +27,24 @@ final class NodeMigrateType { * The node complete migration is the default. It is not used when there * are existing tables for dN_node. * - * @param array[] $definitions - * An associative array of migrations. The array is normally keyed by - * migration ID. However, the followup migrations are keyed by - * 'entity_type__bundle'. Each value is the migration array, obtained by - * decoding the migration YAML file and enriched with some meta information - * added during discovery phase, like migration 'class', 'provider' or - * '_discovered_file_path'. - * @param string $version - * The Drupal version of the source database. + * @param \Drupal\Core\Database\Connection $connection + * The connection to the target database. + * @param string|false $version + * The Drupal version of the source database, FALSE if it cannot be + * determined. * * @return string - * Indicator of the node migration map tables in use. + * The migrate type. * * @internal - * Only to be used by migrate_drupal_migration_plugins_alter(). - */ - public function getNodeMigrateType(array $definitions, $version = NULL) { - if (!$version) { - // We need to get the version of the source database in order to check - // if the classic or complete node tables have been used in a migration. - if (isset($definitions['system_site'])) { - // Use the source plugin of the system_site migration to get the - // database connection. - $migration = $definitions['system_site']; - /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source_plugin */ - $source_plugin = \Drupal::service('plugin.manager.migration') - ->createStubMigration($migration) - ->getSourcePlugin(); - $connection = NULL; - - try { - $connection = $source_plugin->getDatabase(); - } - catch (\Exception $e) { - // @todo: do something useful. - } - $version = FALSE; - - if ($connection) { - $version = $this->getLegacyDrupalVersion($connection); - } - } - } - return $this->migrateType($version); - } - - /** - * Helper to determine what node migrate map tables have data. - * - * @param string $version - * The Drupal version of the source database. - * - * @return string - * The migrate type. */ - protected function migrateType($version) { + public static function getNodeMigrateType(Connection $connection, $version) { $migrate_type = static::NODE_MIGRATE_TYPE_COMPLETE; if ($version) { // Create the variable name, 'node_has_rows' or 'node_complete_exists' and // set it the default value, FALSE. $node_has_rows = FALSE; $node_complete_has_rows = FALSE; - $connection = \Drupal::database(); // Find out what migrate map tables have rows for the node migrations. // It is either the classic, 'dN_node', or the complete, diff --git a/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php index 5d326634fe..4146bb41d8 100644 --- a/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php @@ -4,7 +4,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\migrate\Audit\IdAuditor; -use Drupal\migrate\Audit\NodeCompleteIdAuditor; use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; /** @@ -40,8 +39,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { $translated_content_conflicts = $content_conflicts = []; $results = (new IdAuditor())->auditMultiple($migrations); - $node_complete_results = (new NodeCompleteIdAuditor())->auditMultiple($migrations); - $results += $node_complete_results; /** @var \Drupal\migrate\Audit\AuditResult $result */ foreach ($results as $result) { @@ -126,7 +123,7 @@ protected function formatConflicts(array $conflicts) { } sort($items, SORT_STRING); - return $items; + return array_unique($items); } /** diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php index 7cb4ee2743..8ca811ccb4 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php @@ -146,9 +146,8 @@ public function testMigrateUpgradeExecute() { $session->pageTextContains('WARNING: Content may be overwritten on your new site.'); $session->pageTextContains('There is conflicting content of these types:'); $session->pageTextContains('files'); - $session->pageTextContains('content item revisions'); $session->pageTextContains('There is translated content of these types:'); - $session->pageTextContains('content items'); + $session->pageTextContainsOnce('content items'); $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.')); $session->statusCodeEquals(200); diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php index 0a406c6850..4b6754487c 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php @@ -207,7 +207,7 @@ protected function assertIdConflict(WebAssert $session, array $entity_types) { $label = $entity_type_manager->getDefinition($entity_type)->getPluralLabel(); $session->pageTextContains($label); } - $session->pageTextContains('content item revisions'); + $session->pageTextContainsOnce('content items'); $session->pageTextContains('There is translated content of these types:'); } diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/NoMultilingualReviewPageTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/NoMultilingualReviewPageTestBase.php index ec51ab9f36..81f618ce6c 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/NoMultilingualReviewPageTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/NoMultilingualReviewPageTestBase.php @@ -24,8 +24,7 @@ public function testMigrateUpgradeReviewPage() { $session->pageTextContains('There is conflicting content of these types:'); $session->pageTextContains('taxonomy terms'); $session->pageTextContains('There is translated content of these types:'); - $session->pageTextContains('content item revisions'); - $session->pageTextContains('content items'); + $session->pageTextContainsOnce('content items'); $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.')); $session->statusCodeEquals(200); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index e12a9d0475..fee138a419 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -26,6 +26,7 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\language\ConfigurableLanguageInterface; +use Drupal\migrate_drupal\MigrationConfigurationTrait; use Drupal\migrate_drupal\NodeMigrateType; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; @@ -1467,10 +1468,32 @@ function node_config_translation_info_alter(&$info) { * Implements hook_migration_plugins_alter(). */ function node_migration_plugins_alter(array &$definitions) { + $version = FALSE; + $connection = \Drupal::database(); + // We need to get the version of the source database in order to check + // if the classic or complete node tables have been used in a migration. + if (isset($definitions['system_site'])) { + // Use the source plugin of the system_site migration to get the + // database connection. + $migration = $definitions['system_site']; + /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source_plugin */ + $source_plugin = \Drupal::service('plugin.manager.migration') + ->createStubMigration($migration) + ->getSourcePlugin(); + $source_connection = NULL; + + try { + $source_connection = $source_plugin->getDatabase(); + $version = MigrationConfigurationTrait::getLegacyDrupalVersion($source_connection); + } + catch (\Exception $e) { + // @todo: do something useful. + } + } // If this is complete node migration then for of migrations, except the // classic node migrations, replace the dependency on a classic node migration // with a dependency on the complete node migration. - if ((new NodeMigrateType)->getNodeMigrateType($definitions, NULL) == NodeMigrateType::NODE_MIGRATE_TYPE_COMPLETE) { + if (NodeMigrateType::getNodeMigrateType($connection, $version) === NodeMigrateType::NODE_MIGRATE_TYPE_COMPLETE) { // Anonymous function to replace classic node migration plugin IDs with the // node complete plugin ID. $replace_with_complete_migration = function (&$value) {