diff --git a/core/modules/aggregator/migration_templates/d6_aggregator_feed.yml b/core/modules/aggregator/migration_templates/d6_aggregator_feed.yml index cad1553..105d09b 100644 --- a/core/modules/aggregator/migration_templates/d6_aggregator_feed.yml +++ b/core/modules/aggregator/migration_templates/d6_aggregator_feed.yml @@ -2,6 +2,7 @@ id: d6_aggregator_feed label: Aggregator feeds migration_tags: - Drupal 6 +audit_ids: true source: plugin: aggregator_feed process: diff --git a/core/modules/aggregator/migration_templates/d6_aggregator_item.yml b/core/modules/aggregator/migration_templates/d6_aggregator_item.yml index e14dbd6..022a780 100644 --- a/core/modules/aggregator/migration_templates/d6_aggregator_item.yml +++ b/core/modules/aggregator/migration_templates/d6_aggregator_item.yml @@ -2,6 +2,7 @@ id: d6_aggregator_item label: Aggregator items migration_tags: - Drupal 6 +audit_ids: true source: plugin: aggregator_item process: diff --git a/core/modules/aggregator/migration_templates/d7_aggregator_feed.yml b/core/modules/aggregator/migration_templates/d7_aggregator_feed.yml index 5dbeb25..9a038b7 100644 --- a/core/modules/aggregator/migration_templates/d7_aggregator_feed.yml +++ b/core/modules/aggregator/migration_templates/d7_aggregator_feed.yml @@ -2,6 +2,7 @@ id: d7_aggregator_feed label: Aggregator feeds migration_tags: - Drupal 7 +audit_ids: true source: plugin: aggregator_feed process: diff --git a/core/modules/aggregator/migration_templates/d7_aggregator_item.yml b/core/modules/aggregator/migration_templates/d7_aggregator_item.yml index 054ba43..ac76a8c 100644 --- a/core/modules/aggregator/migration_templates/d7_aggregator_item.yml +++ b/core/modules/aggregator/migration_templates/d7_aggregator_item.yml @@ -2,6 +2,7 @@ id: d7_aggregator_item label: Aggregator items migration_tags: - Drupal 7 +audit_ids: true source: plugin: aggregator_item process: diff --git a/core/modules/ban/migration_templates/d7_blocked_ips.yml b/core/modules/ban/migration_templates/d7_blocked_ips.yml index 0122dc6..9197cf2 100644 --- a/core/modules/ban/migration_templates/d7_blocked_ips.yml +++ b/core/modules/ban/migration_templates/d7_blocked_ips.yml @@ -2,6 +2,7 @@ id: d7_blocked_ips label: Blocked IPs migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_blocked_ips process: diff --git a/core/modules/block_content/migration_templates/d6_custom_block.yml b/core/modules/block_content/migration_templates/d6_custom_block.yml index 55fbcb5..d2826c7 100644 --- a/core/modules/block_content/migration_templates/d6_custom_block.yml +++ b/core/modules/block_content/migration_templates/d6_custom_block.yml @@ -2,6 +2,7 @@ id: d6_custom_block label: Custom blocks migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_box process: diff --git a/core/modules/block_content/migration_templates/d7_custom_block.yml b/core/modules/block_content/migration_templates/d7_custom_block.yml index ca06cf0..1089ca3 100644 --- a/core/modules/block_content/migration_templates/d7_custom_block.yml +++ b/core/modules/block_content/migration_templates/d7_custom_block.yml @@ -2,6 +2,7 @@ id: d7_custom_block label: Custom blocks migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_block_custom process: diff --git a/core/modules/book/migration_templates/d6_book.yml b/core/modules/book/migration_templates/d6_book.yml index 94d7a8f..f075c39 100644 --- a/core/modules/book/migration_templates/d6_book.yml +++ b/core/modules/book/migration_templates/d6_book.yml @@ -2,6 +2,7 @@ id: d6_book label: Books migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_book process: diff --git a/core/modules/comment/migration_templates/d6_comment.yml b/core/modules/comment/migration_templates/d6_comment.yml index 06820d4..4add74b 100644 --- a/core/modules/comment/migration_templates/d6_comment.yml +++ b/core/modules/comment/migration_templates/d6_comment.yml @@ -2,6 +2,7 @@ id: d6_comment label: Comments migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_comment constants: diff --git a/core/modules/comment/migration_templates/d7_comment.yml b/core/modules/comment/migration_templates/d7_comment.yml index 94a2884..c020ab2 100644 --- a/core/modules/comment/migration_templates/d7_comment.yml +++ b/core/modules/comment/migration_templates/d7_comment.yml @@ -2,6 +2,7 @@ id: d7_comment label: Comments migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_comment constants: diff --git a/core/modules/file/migration_templates/d6_file.yml b/core/modules/file/migration_templates/d6_file.yml index 8371d45..8589978 100644 --- a/core/modules/file/migration_templates/d6_file.yml +++ b/core/modules/file/migration_templates/d6_file.yml @@ -4,6 +4,7 @@ id: d6_file label: Files migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_file constants: diff --git a/core/modules/file/migration_templates/d6_upload.yml b/core/modules/file/migration_templates/d6_upload.yml index a497099..0942e10 100644 --- a/core/modules/file/migration_templates/d6_upload.yml +++ b/core/modules/file/migration_templates/d6_upload.yml @@ -2,6 +2,7 @@ id: d6_upload label: File uploads migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_upload process: diff --git a/core/modules/file/migration_templates/d7_file.yml b/core/modules/file/migration_templates/d7_file.yml index 3fee046..8bc2147 100644 --- a/core/modules/file/migration_templates/d7_file.yml +++ b/core/modules/file/migration_templates/d7_file.yml @@ -4,6 +4,7 @@ id: d7_file label: Files migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_file scheme: public diff --git a/core/modules/file/migration_templates/d7_file_private.yml b/core/modules/file/migration_templates/d7_file_private.yml index 9c6b8e2..d2e7c89 100644 --- a/core/modules/file/migration_templates/d7_file_private.yml +++ b/core/modules/file/migration_templates/d7_file_private.yml @@ -2,6 +2,7 @@ id: d7_file_private label: Files migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_file scheme: private diff --git a/core/modules/menu_link_content/migration_templates/d6_menu_links.yml b/core/modules/menu_link_content/migration_templates/d6_menu_links.yml index 2c8ad4a..2a79d35 100644 --- a/core/modules/menu_link_content/migration_templates/d6_menu_links.yml +++ b/core/modules/menu_link_content/migration_templates/d6_menu_links.yml @@ -2,6 +2,7 @@ id: d6_menu_links label: Menu links migration_tags: - Drupal 6 +audit_ids: true source: plugin: menu_link process: diff --git a/core/modules/menu_link_content/migration_templates/d7_menu_links.yml b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml index 200a792..854a056 100644 --- a/core/modules/menu_link_content/migration_templates/d7_menu_links.yml +++ b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml @@ -2,6 +2,7 @@ id: d7_menu_links label: Menu links migration_tags: - Drupal 7 +audit_ids: true source: plugin: menu_link constants: diff --git a/core/modules/migrate/migrate.services.yml b/core/modules/migrate/migrate.services.yml index 1a4f64d..1b95191 100644 --- a/core/modules/migrate/migrate.services.yml +++ b/core/modules/migrate/migrate.services.yml @@ -30,3 +30,5 @@ services: plugin.manager.migration: class: Drupal\migrate\Plugin\MigrationPluginManager arguments: ['@module_handler', '@cache.discovery_migration', '@language_manager'] + migrate.id_auditor: + class: Drupal\migrate\MigrateIdAuditor diff --git a/core/modules/migrate/src/MigrateIdAuditor.php b/core/modules/migrate/src/MigrateIdAuditor.php new file mode 100644 index 0000000..03a8fe9 --- /dev/null +++ b/core/modules/migrate/src/MigrateIdAuditor.php @@ -0,0 +1,36 @@ +getDestinationPlugin(); + if ($destination instanceof MigrateDestinationAuditInterface) { + if ($destination->hasIdConflicts($migration->getIdMap())) { + $entity_type = $destination->getEntityType(); + $conflicts[$entity_type->id()] = $entity_type->getLabel(); + } + } + } + return $conflicts; + } + +} diff --git a/core/modules/migrate/src/Plugin/MigrateDestinationAuditInterface.php b/core/modules/migrate/src/Plugin/MigrateDestinationAuditInterface.php new file mode 100644 index 0000000..9592679 --- /dev/null +++ b/core/modules/migrate/src/Plugin/MigrateDestinationAuditInterface.php @@ -0,0 +1,30 @@ +getSettings(); } + /** + * Gets the name of the field containing the IDs that should be audited. + * + * @return string + * The field name. + */ + protected function getIdAuditField() { + return $this->getKey('id'); + } + + /** + * Gets the highest ID that exists for this destination. + * + * This is not the highest ID that has been migrated, but the highest ID that + * exists in the destination, eg: highest node ID on the site. + * + * @return int + * The highest ID found. If no IDs are found, or if the concept of a highest + * ID is not meaningful, zero should be returned. + */ + protected function getHighestDestinationId() { + $query = $this->storage->getQuery() + ->sort($this->getIdAuditField()) + ->range(0, 1); + $found = $query->execute(); + return (int) reset($found); + } + + /** + * {@inheritdoc} + */ + public function hasIdConflicts(MigrateIdMapInterface $id_map) { + if (!empty($this->migration->getPluginDefinition()['audit_ids'])) { + if ($id_map instanceof MigrateIdMapAuditInterface) { + if ($this->getHighestDestinationId() > $id_map->getHighestMigratedId($this->getIdAuditField())) { + return TRUE; + } + } + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getEntityType() { + return $this->storage->getEntityType(); + } + } diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php index b0db476..e452795 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php @@ -78,4 +78,11 @@ public function getIds() { throw new MigrateException('This entity type does not support revisions.'); } + /** + * {@inheritdoc} + */ + public function getIdAuditField() { + return $this->getKey('revision'); + } + } diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 27b7569..1c12a0a 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -6,11 +6,14 @@ use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginBase; +use Drupal\migrate\Plugin\MigrateIdMapAuditInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Event\MigrateIdMapMessageEvent; use Drupal\migrate\MigrateException; use Drupal\migrate\MigrateMessageInterface; use Drupal\migrate\Plugin\MigrateIdMapInterface; +use Drupal\migrate\Plugin\MigrationPluginManager; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Drupal\migrate\Row; use Drupal\migrate\Event\MigrateEvents; use Drupal\migrate\Event\MigrateMapSaveEvent; @@ -26,7 +29,7 @@ * * @PluginID("sql") */ -class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface { +class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface, MigrateIdMapAuditInterface { /** * Column name of hashed source id values. @@ -41,6 +44,13 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP protected $eventDispatcher; /** + * The migration plugin manager to have access to the list of migrations. + * + * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface + */ + protected $migrationPluginManager; + + /** * The migration map table name. * * @var string @@ -151,11 +161,16 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP * The configuration for the plugin. * @param \Drupal\migrate\Plugin\MigrationInterface $migration * The migration to do. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher + * The event dispatcher. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher, MigrationPluginManagerInterface $migration_plugin_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->migration = $migration; $this->eventDispatcher = $event_dispatcher; + $this->migrationPluginManager = $migration_plugin_manager; } /** @@ -167,7 +182,8 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $migration, - $container->get('event_dispatcher') + $container->get('event_dispatcher'), + $container->get('plugin.manager.migration') ); } @@ -923,4 +939,48 @@ public function valid() { return $this->currentRow !== FALSE; } + /** + * {@inheritdoc} + */ + public function getHighestMigratedId($field) { + $sql_field = $this->destinationIdFields()[$field]; + $migration_id = $this->migration->id(); + + // List of mapping tables to look in for the highest ID. + $map_tables = [ + $migration_id => $this->mapTableName(), + ]; + + // If there's a bundle, it means we have a derived migration and we need to + // find all the mapping tables from the related derived migrations. + if ($base_id = substr($migration_id, 0, strpos($migration_id, ':'))) { + $migrations = $this->migrationPluginManager->getDefinitions(); + foreach ($migrations as $migration_id => $migration) { + if ($migration['id'] == $base_id) { + // Get this derived migration's mapping table and add it to the list + // of mapping tables to look in for the highest ID. + $stub = $this->migrationPluginManager->createInstance($migration_id); + $map_tables[$migration_id] = $stub->getIdMap()->mapTableName(); + } + } + } + + // Look in all the derived migrations' mapping tables for their highest ID. + $ids = []; + foreach ($map_tables as $map_table) { + if (!$this->getDatabase()->schema()->tableExists($map_table)) { + return 0; + } + + $query = $this->getDatabase()->select($map_table, 'map') + ->fields('map', [$sql_field]) + ->orderBy($sql_field, 'DESC') + ->range(0, 1); + $ids[] = $query->execute()->fetchField(); + } + + // Return the highest of the highest IDs. + return max($ids); + } + } diff --git a/core/modules/migrate/tests/modules/migrate_high_water_test/migration_templates/migrate.migration.high_water_test.yml b/core/modules/migrate/tests/modules/migrate_high_water_test/migration_templates/migrate.migration.high_water_test.yml index 6a86577..73f32a8 100644 --- a/core/modules/migrate/tests/modules/migrate_high_water_test/migration_templates/migrate.migration.high_water_test.yml +++ b/core/modules/migrate/tests/modules/migrate_high_water_test/migration_templates/migrate.migration.high_water_test.yml @@ -1,5 +1,6 @@ id: high_water_test label: High water test. +audit_ids: true source: plugin: high_water_test high_water_property: diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php index 3c3cba8..269728d 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php @@ -232,7 +232,8 @@ protected function runEnsureTablesTest($schema) { ->willReturn($plugin); /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */ $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); - $map = new TestSqlIdMap($database, [], 'sql', [], $migration, $event_dispatcher); + $migration_plugin_manager = $this->getMock('Drupal\migrate\Plugin\MigrationPluginManagerInterface'); + $map = new TestSqlIdMap($database, [], 'sql', [], $migration, $event_dispatcher, $migration_plugin_manager); $map->getDatabase(); } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index 2ad2b3d..c636b09 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -111,8 +111,9 @@ protected function getIdMap() { ->method('getDestinationPlugin') ->willReturn($plugin); $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $migration_plugin_manager = $this->getMock('Drupal\migrate\Plugin\MigrationPluginManagerInterface'); - $id_map = new TestSqlIdMap($this->database, [], 'sql', [], $migration, $event_dispatcher); + $id_map = new TestSqlIdMap($this->database, [], 'sql', [], $migration, $event_dispatcher, $migration_plugin_manager); $migration ->method('getIdMap') ->willReturn($id_map); diff --git a/core/modules/migrate/tests/src/Unit/TestSqlIdMap.php b/core/modules/migrate/tests/src/Unit/TestSqlIdMap.php index 6d33338..f3ee60a 100644 --- a/core/modules/migrate/tests/src/Unit/TestSqlIdMap.php +++ b/core/modules/migrate/tests/src/Unit/TestSqlIdMap.php @@ -6,6 +6,7 @@ use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\migrate\id_map\Sql; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -28,10 +29,12 @@ class TestSqlIdMap extends Sql implements \Iterator { * The migration to do. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher service. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager. */ - public function __construct(Connection $database, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) { + public function __construct(Connection $database, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher, MigrationPluginManagerInterface $migration_plugin_manager) { $this->database = $database; - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $event_dispatcher); + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $event_dispatcher, $migration_plugin_manager); } /** diff --git a/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml index e23d90d..0bddcf4 100644 --- a/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml +++ b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml @@ -3,6 +3,7 @@ label: User migration migration_tags: - Drupal 6 - Drupal 7 +audit_ids: true source: plugin: d6_user process: diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php new file mode 100644 index 0000000..92f1ee7 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php @@ -0,0 +1,102 @@ +coreModuleListDataProvider()); + parent::setUp(); + + // Install required schemas. + $this->installEntitySchema('aggregator_item'); + $this->installEntitySchema('aggregator_feed'); + $this->installEntitySchema('block_content'); + $this->installEntitySchema('comment'); + $this->installEntitySchema('file'); + $this->installEntitySchema('menu_link_content'); + $this->installEntitySchema('node'); + $this->installEntitySchema('taxonomy_term'); + $this->installSchema('dblog', ['watchdog']); + $this->installSchema('tracker', ['tracker_node', 'tracker_user']); + } + + /** + * Tests all migrations with no ID conflicts. + */ + public function testAllMigrationsWithNoConflicts() { + $plugin_manager = $this->container->get('plugin.manager.migration'); + + // Get all migration definitions. + $migrations = []; + foreach ($plugin_manager->getDefinitions() as $migration_id => $migration) { + $migrations[$migration_id] = $this->getMigration($migration_id); + } + + // Audit the IDs of all migrations. There should be no conflicts since no + // content has been created. + $conflicts = $this->container->get('migrate.id_auditor')->auditIds($migrations); + $this->assertTrue(empty($conflicts)); + } + + /** + * Tests the node migration with ID conflicts. + */ + public function testMigrationWithIdConflicts() { + // Create a node of type page. + Node::create(['type' => 'page', 'title' => 'foo'])->save(); + + // Audit the IDs of the d6_node migrations for the page & article node type. + // There should be a conflict since there's a new node that wasn't created + // from a previous migration. + $conflicts = $this->container->get('migrate.id_auditor')->auditIds([ + $this->getMigration('d6_node:article'), + $this->getMigration('d6_node:page'), + ]); + $this->assertSame(['node'], array_keys($conflicts)); + } + + /** + * Tests multiple migrations to the same destination with no ID conflicts. + */ + public function testMultipleMigrationWithoutIdConflicts() { + // Create a node of type page. + Node::create(['type' => 'page', 'title' => 'foo'])->save(); + + // Insert data in the d6_node:page migration mappping table to simulate a + // previously migrated node. + $table_name = $this->getMigration('d6_node:page')->getIdMap()->mapTableName(); + $this->container->get('database')->insert($table_name) + ->fields([ + 'source_ids_hash' => 1, + 'sourceid1' => 1, + 'destid1' => 1, + ]) + ->execute(); + + // Audit the IDs of the d6_node migrations for the page & article node type. + // There should be no conflicts since the highest destination ID should be + // equal to the highest migrated ID, as found in the aggregated mapping + // tables of the two node migrations. + $conflicts = $this->container->get('migrate.id_auditor')->auditIds([ + $this->getMigration('d6_node:page'), + $this->getMigration('d6_node:article'), + ]); + $this->assertTrue(empty($conflicts)); + } + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php index c87247e..9c169e5 100644 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -8,6 +8,7 @@ use Drupal\Core\Render\RendererInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Url; +use Drupal\migrate\MigrateIdAuditor; use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Drupal\migrate_drupal_ui\Batch\MigrateUpgradeImportBatch; use Drupal\migrate_drupal\MigrationConfigurationTrait; @@ -713,6 +714,13 @@ class MigrateUpgradeForm extends ConfirmFormBase { protected $pluginManager; /** + * The ID conflict auditor. + * + * @var \Drupal\migrate\MigrateIdAuditor $idAuditor + */ + protected $idAuditor; + + /** * Constructs the MigrateUpgradeForm. * * @param \Drupal\Core\State\StateInterface $state @@ -723,12 +731,15 @@ class MigrateUpgradeForm extends ConfirmFormBase { * The renderer service. * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $plugin_manager * The migration plugin manager. + * @param \Drupal\migrate\MigrateIdAuditor $id_auditor + * The ID conflict auditor. */ - public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, RendererInterface $renderer, MigrationPluginManagerInterface $plugin_manager) { + public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, RendererInterface $renderer, MigrationPluginManagerInterface $plugin_manager, MigrateIdAuditor $id_auditor) { $this->state = $state; $this->dateFormatter = $date_formatter; $this->renderer = $renderer; $this->pluginManager = $plugin_manager; + $this->idAuditor = $id_auditor; } /** @@ -739,7 +750,8 @@ public static function create(ContainerInterface $container) { $container->get('state'), $container->get('date.formatter'), $container->get('renderer'), - $container->get('plugin.manager.migration') + $container->get('plugin.manager.migration'), + $container->get('migrate.id_auditor') ); } @@ -762,6 +774,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { case 'credentials': return $this->buildCredentialForm($form, $form_state); + case 'confirm_id_conflicts': + return $this->buildIdConflictForm($form, $form_state); + case 'confirm': return $this->buildConfirmForm($form, $form_state); @@ -1082,6 +1097,60 @@ public function validateCredentialForm(array &$form, FormStateInterface $form_st */ public function submitCredentialForm(array &$form, FormStateInterface $form_state) { // Indicate the next step is confirmation. + $form_state->set('step', 'confirm_id_conflicts'); + $form_state->setRebuild(); + } + + /** + * Confirmation form for ID conflicts. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form structure. + */ + public function buildIdConflictForm(array &$form, FormStateInterface $form_state) { + // Check if there are conflicts. If none, just skip this form! + $migration_ids = array_keys($form_state->get('migrations')); + $migrations = $this->pluginManager->createInstances($migration_ids); + $conflicts = $this->idAuditor->auditIds($migrations); + if (empty($conflicts)) { + $form_state->set('step', 'confirm'); + return $this->buildForm($form, $form_state); + } + + $form = parent::buildForm($form, $form_state); + $form['actions']['submit']['#submit'] = ['::submitConfirmIdConflictForm']; + $form['actions']['submit']['#value'] = $this->t('I acknowledge I may lose data, continue anyway.'); + + $form['warning'] = [ + '#type' => 'markup', + '#markup' => '

' . $this->t('Entities may be overwritten') . '

' . + '

' . $this->t('Upgrades work on brand new sites, or sites with previously upgraded data. However, it looks like you have content in your site that is unknown to the migrate system; perhaps manually added. These new entities may be overwritten if you run this upgrade. For more information, see the online handbook entry for handling migration conflicts..', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '

', + ]; + + sort($conflicts); + $form['type_list'] = [ + '#title' => $this->t('These entities are of the following types:'), + '#theme' => 'item_list', + '#items' => $conflicts, + ]; + + return $form; + } + + /** + * Submission handler for the confirmation form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitConfirmIdConflictForm(array &$form, FormStateInterface $form_state) { $form_state->set('step', 'confirm'); $form_state->setRebuild(); } 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 9741a0b..c3a64d4 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php @@ -147,6 +147,7 @@ public function testMigrateUpgrade() { $this->drupalPostForm(NULL, $edits, t('Review upgrade')); $this->assertResponse(200); $this->assertText('Are you sure?'); + $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data, continue anyway.')); $this->drupalPostForm(NULL, [], t('Perform upgrade')); $this->assertText(t('Congratulations, you upgraded Drupal!')); diff --git a/core/modules/node/migration_templates/d6_node.yml b/core/modules/node/migration_templates/d6_node.yml index 56d0459..1bcae10 100644 --- a/core/modules/node/migration_templates/d6_node.yml +++ b/core/modules/node/migration_templates/d6_node.yml @@ -3,6 +3,7 @@ label: Nodes migration_tags: - Drupal 6 deriver: Drupal\node\Plugin\migrate\D6NodeDeriver +audit_ids: true source: plugin: d6_node process: diff --git a/core/modules/node/migration_templates/d6_node_revision.yml b/core/modules/node/migration_templates/d6_node_revision.yml index f4ff301..ec44d58 100644 --- a/core/modules/node/migration_templates/d6_node_revision.yml +++ b/core/modules/node/migration_templates/d6_node_revision.yml @@ -3,6 +3,7 @@ label: Node revisions migration_tags: - Drupal 6 deriver: Drupal\node\Plugin\migrate\D6NodeDeriver +audit_ids: true source: plugin: d6_node_revision process: diff --git a/core/modules/node/migration_templates/d6_node_translation.yml b/core/modules/node/migration_templates/d6_node_translation.yml index 3eb06e8..0f6579e 100644 --- a/core/modules/node/migration_templates/d6_node_translation.yml +++ b/core/modules/node/migration_templates/d6_node_translation.yml @@ -3,6 +3,7 @@ label: Node translations migration_tags: - Drupal 6 deriver: Drupal\node\Plugin\migrate\D6NodeDeriver +audit_ids: true source: plugin: d6_node translations: true diff --git a/core/modules/node/migration_templates/d7_node.yml b/core/modules/node/migration_templates/d7_node.yml index 5de3055..824662f 100644 --- a/core/modules/node/migration_templates/d7_node.yml +++ b/core/modules/node/migration_templates/d7_node.yml @@ -3,6 +3,7 @@ label: Nodes migration_tags: - Drupal 7 deriver: Drupal\node\Plugin\migrate\D7NodeDeriver +audit_ids: true source: plugin: d7_node process: diff --git a/core/modules/node/migration_templates/d7_node_revision.yml b/core/modules/node/migration_templates/d7_node_revision.yml index c6081ef..420fe58 100644 --- a/core/modules/node/migration_templates/d7_node_revision.yml +++ b/core/modules/node/migration_templates/d7_node_revision.yml @@ -3,6 +3,7 @@ label: Node revisions migration_tags: - Drupal 7 deriver: Drupal\node\Plugin\migrate\D7NodeDeriver +audit_ids: true source: plugin: d7_node_revision process: diff --git a/core/modules/node/migration_templates/d7_node_translation.yml b/core/modules/node/migration_templates/d7_node_translation.yml index 11dfbb7..5a073d7 100644 --- a/core/modules/node/migration_templates/d7_node_translation.yml +++ b/core/modules/node/migration_templates/d7_node_translation.yml @@ -4,6 +4,7 @@ migration_tags: - Drupal 7 - translation deriver: Drupal\node\Plugin\migrate\D7NodeDeriver +audit_ids: true source: plugin: d7_node translations: true diff --git a/core/modules/path/migration_templates/d6_url_alias.yml b/core/modules/path/migration_templates/d6_url_alias.yml index fcf2036..f9c604c 100644 --- a/core/modules/path/migration_templates/d6_url_alias.yml +++ b/core/modules/path/migration_templates/d6_url_alias.yml @@ -2,6 +2,7 @@ id: d6_url_alias label: URL aliases migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_url_alias constants: diff --git a/core/modules/path/migration_templates/d7_url_alias.yml b/core/modules/path/migration_templates/d7_url_alias.yml index dffaf46..12a985c 100644 --- a/core/modules/path/migration_templates/d7_url_alias.yml +++ b/core/modules/path/migration_templates/d7_url_alias.yml @@ -2,6 +2,7 @@ id: d7_url_alias label: URL aliases migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_url_alias constants: diff --git a/core/modules/shortcut/migration_templates/d7_shortcut.yml b/core/modules/shortcut/migration_templates/d7_shortcut.yml index dac9354..a597780 100644 --- a/core/modules/shortcut/migration_templates/d7_shortcut.yml +++ b/core/modules/shortcut/migration_templates/d7_shortcut.yml @@ -2,6 +2,7 @@ id: d7_shortcut label: Shortcut links migration_tags: - Drupal 7 +audit_ids: false source: plugin: d7_shortcut constants: diff --git a/core/modules/shortcut/migration_templates/d7_shortcut_set_users.yml b/core/modules/shortcut/migration_templates/d7_shortcut_set_users.yml index d0eb219..9726732 100644 --- a/core/modules/shortcut/migration_templates/d7_shortcut_set_users.yml +++ b/core/modules/shortcut/migration_templates/d7_shortcut_set_users.yml @@ -2,6 +2,7 @@ id: d7_shortcut_set_users label: Shortcut set user mapping migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_shortcut_set_users process: diff --git a/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml b/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml index e3c3e3d..7ed88a7 100644 --- a/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml +++ b/core/modules/taxonomy/migration_templates/d6_taxonomy_term.yml @@ -2,6 +2,7 @@ id: d6_taxonomy_term label: Taxonomy terms migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_taxonomy_term process: diff --git a/core/modules/taxonomy/migration_templates/d6_taxonomy_term_translation.yml b/core/modules/taxonomy/migration_templates/d6_taxonomy_term_translation.yml index 11af8cd..473ab23 100644 --- a/core/modules/taxonomy/migration_templates/d6_taxonomy_term_translation.yml +++ b/core/modules/taxonomy/migration_templates/d6_taxonomy_term_translation.yml @@ -2,6 +2,7 @@ id: d6_taxonomy_term_translation label: Taxonomy terms migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_taxonomy_term translations: true diff --git a/core/modules/taxonomy/migration_templates/d6_taxonomy_vocabulary_translation.yml b/core/modules/taxonomy/migration_templates/d6_taxonomy_vocabulary_translation.yml index dbb1793..2d143d0 100644 --- a/core/modules/taxonomy/migration_templates/d6_taxonomy_vocabulary_translation.yml +++ b/core/modules/taxonomy/migration_templates/d6_taxonomy_vocabulary_translation.yml @@ -2,6 +2,7 @@ id: d6_taxonomy_vocabulary_translation label: Taxonomy vocabularies migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_taxonomy_vocabulary_translation process: diff --git a/core/modules/taxonomy/migration_templates/d6_term_node.yml b/core/modules/taxonomy/migration_templates/d6_term_node.yml index 846334d..bda7d58 100644 --- a/core/modules/taxonomy/migration_templates/d6_term_node.yml +++ b/core/modules/taxonomy/migration_templates/d6_term_node.yml @@ -3,6 +3,7 @@ label: Term/node relationships migration_tags: - Drupal 6 deriver: Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver +audit_ids: true source: plugin: d6_term_node process: diff --git a/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml b/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml index 91c8362..24da88f 100644 --- a/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml +++ b/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml @@ -3,6 +3,7 @@ label: Term/node relationship revisions migration_tags: - Drupal 6 deriver: Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver +audit_ids: true source: plugin: d6_term_node_revision process: diff --git a/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml b/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml index 99004df..e518ead 100644 --- a/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml +++ b/core/modules/taxonomy/migration_templates/d7_taxonomy_term.yml @@ -3,6 +3,7 @@ label: Taxonomy terms migration_tags: - Drupal 7 deriver: Drupal\taxonomy\Plugin\migrate\D7TaxonomyTermDeriver +audit_ids: true source: plugin: d7_taxonomy_term process: diff --git a/core/modules/tracker/migration_templates/d7_tracker_node.yml b/core/modules/tracker/migration_templates/d7_tracker_node.yml index 02645d7..2bf3b07 100644 --- a/core/modules/tracker/migration_templates/d7_tracker_node.yml +++ b/core/modules/tracker/migration_templates/d7_tracker_node.yml @@ -2,6 +2,7 @@ id: d7_tracker_node label: Tracker node migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_tracker_node process: diff --git a/core/modules/tracker/migration_templates/d7_tracker_user.yml b/core/modules/tracker/migration_templates/d7_tracker_user.yml index ae3c51d..776e1c7 100644 --- a/core/modules/tracker/migration_templates/d7_tracker_user.yml +++ b/core/modules/tracker/migration_templates/d7_tracker_user.yml @@ -2,6 +2,7 @@ id: d7_tracker_user label: Tracker user migration_tags: - Drupal 7 +audit_ids: true source: plugin: d7_tracker_user process: diff --git a/core/modules/user/migration_templates/d6_profile_values.yml b/core/modules/user/migration_templates/d6_profile_values.yml index 5530ca5..8b0d4b0 100644 --- a/core/modules/user/migration_templates/d6_profile_values.yml +++ b/core/modules/user/migration_templates/d6_profile_values.yml @@ -3,6 +3,7 @@ label: Profile values class: Drupal\user\Plugin\migrate\ProfileValues migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_profile_field_values process: diff --git a/core/modules/user/migration_templates/d6_user.yml b/core/modules/user/migration_templates/d6_user.yml index d58607b..034ce22 100644 --- a/core/modules/user/migration_templates/d6_user.yml +++ b/core/modules/user/migration_templates/d6_user.yml @@ -2,6 +2,7 @@ id: d6_user label: User accounts migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_user process: diff --git a/core/modules/user/migration_templates/d6_user_contact_settings.yml b/core/modules/user/migration_templates/d6_user_contact_settings.yml index 0d9a228..3fc3c3f 100644 --- a/core/modules/user/migration_templates/d6_user_contact_settings.yml +++ b/core/modules/user/migration_templates/d6_user_contact_settings.yml @@ -2,6 +2,7 @@ id: d6_user_contact_settings label: User contact settings migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_user constants: diff --git a/core/modules/user/migration_templates/d6_user_picture_file.yml b/core/modules/user/migration_templates/d6_user_picture_file.yml index 3518d7a..93a6e5e 100644 --- a/core/modules/user/migration_templates/d6_user_picture_file.yml +++ b/core/modules/user/migration_templates/d6_user_picture_file.yml @@ -2,6 +2,7 @@ id: d6_user_picture_file label: User pictures migration_tags: - Drupal 6 +audit_ids: true source: plugin: d6_user_picture_file constants: diff --git a/core/modules/user/migration_templates/d7_user.yml b/core/modules/user/migration_templates/d7_user.yml index ae52384..9babed3 100644 --- a/core/modules/user/migration_templates/d7_user.yml +++ b/core/modules/user/migration_templates/d7_user.yml @@ -3,6 +3,7 @@ label: User accounts migration_tags: - Drupal 7 class: Drupal\user\Plugin\migrate\User +audit_ids: true source: plugin: d7_user process: diff --git a/core/modules/user/src/Plugin/migrate/destination/EntityUser.php b/core/modules/user/src/Plugin/migrate/destination/EntityUser.php index 7c2c81a..33d9e54 100644 --- a/core/modules/user/src/Plugin/migrate/destination/EntityUser.php +++ b/core/modules/user/src/Plugin/migrate/destination/EntityUser.php @@ -127,4 +127,18 @@ protected function processStubRow(Row $row) { } } + /** + * {@inheritdoc} + */ + public function getHighestDestinationId() { + $found = parent::getHighestDestinationId(); + + // Every Drupal site must have a user with UID of 1 and it's normal for + // migrations to overwrite this user. + if ($found == 1) { + return 0; + } + return $found; + } + }