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 9005ef2..4a706bf 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -34,6 +34,11 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface { /** + * Column name of hashed source id values. + */ + const SOURCE_IDS_HASH = 'source_ids_hash'; + + /** * An event dispatcher instance to use for map events. * * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface @@ -172,6 +177,34 @@ public static function create(ContainerInterface $container, array $configuratio } /** + * Retrieves the hash of the source identifier values. + * + * It is public only for testing purposes. + * + * @param array $source_id_values + * The source identifiers + * + * @return string + * An hash containing the hashed values of the source identifiers. + */ + public function getSourceIDsHash(array $source_id_values) { + // When looking up the destination ID we require an array with both the + // source key and value, e.g. ['nid' => 41]. In this case, $source_id_values + // need to be ordered the same order as $this->sourceIdFields(). + // However, the Migration process plugin doesn't currently have a way to get + // the source key so we presume the values have been passed through in the + // correct order. + if (!isset($source_id_values[0])) { + $source_id_values_keyed = []; + foreach ($this->sourceIdFields() as $field_name => $source_id) { + $source_id_values_keyed[] = $source_id_values[$field_name]; + } + $source_id_values = $source_id_values_keyed; + } + return hash('sha256', serialize(array_map('strval', $source_id_values))); + } + + /** * The source ID fields. * * @return array @@ -285,28 +318,25 @@ protected function ensureTables() { // and map from the source field names to the map/msg field names. $count = 1; $source_id_schema = array(); - $pks = array(); foreach ($this->migration->getSourcePlugin()->getIds() as $id_definition) { $mapkey = 'sourceid' . $count++; $source_id_schema[$mapkey] = $this->getFieldSchema($id_definition); $source_id_schema[$mapkey]['not null'] = TRUE; - - // With InnoDB, utf8mb4-based primary keys can't be over 191 characters. - // Use ASCII-based primary keys instead. - if (isset($source_id_schema[$mapkey]['type']) && $source_id_schema[$mapkey]['type'] == 'varchar') { - $source_id_schema[$mapkey]['type'] = 'varchar_ascii'; - } - $pks[] = $mapkey; } - $fields = $source_id_schema; + $source_ids_hash[static::SOURCE_IDS_HASH] = array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'Hash of source ids. Used as primary key', + ); + $fields = $source_ids_hash + $source_id_schema; // Add destination identifiers to map table. // @todo How do we discover the destination schema? $count = 1; foreach ($this->migration->getDestinationPlugin()->getIds() as $id_definition) { - // Allow dest identifier fields to be NULL (for IGNORED/FAILED - // cases). + // Allow dest identifier fields to be NULL (for IGNORED/FAILED cases). $mapkey = 'destid' . $count++; $fields[$mapkey] = $this->getFieldSchema($id_definition); $fields[$mapkey]['not null'] = FALSE; @@ -343,10 +373,8 @@ protected function ensureTables() { $schema = array( 'description' => 'Mappings from source identifier value(s) to destination identifier value(s).', 'fields' => $fields, + 'primary key' => array(static::SOURCE_IDS_HASH), ); - if ($pks) { - $schema['primary key'] = $pks; - } $this->getDatabase()->schema()->createTable($this->mapTableName, $schema); // Now do the message table. @@ -357,7 +385,7 @@ protected function ensureTables() { 'unsigned' => TRUE, 'not null' => TRUE, ); - $fields += $source_id_schema; + $fields += $source_ids_hash; $fields['level'] = array( 'type' => 'int', @@ -375,9 +403,6 @@ protected function ensureTables() { 'fields' => $fields, 'primary key' => array('msgid'), ); - if ($pks) { - $schema['indexes']['sourcekey'] = $pks; - } $this->getDatabase()->schema()->createTable($this->messageTableName(), $schema); } } @@ -406,6 +431,14 @@ protected function ensureTables() { ) ); } + if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, static::SOURCE_IDS_HASH)) { + $this->getDatabase()->schema()->addField($this->mapTableName, static::SOURCE_IDS_HASH, array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'Hash of source ids. Used as primary key', + )); + } } } @@ -435,9 +468,7 @@ protected function getFieldSchema(array $id_definition) { public function getRowBySource(array $source_id_values) { $query = $this->getDatabase()->select($this->mapTableName(), 'map') ->fields('map'); - foreach ($this->sourceIdFields() as $field_name => $source_id) { - $query->condition("map.$source_id", $source_id_values[$field_name], '='); - } + $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); $result = $query->execute(); return $result->fetchAssoc(); } @@ -497,14 +528,7 @@ public function lookupDestinationId(array $source_id_values) { $query = $this->getDatabase()->select($this->mapTableName(), 'map') ->fields('map', $this->destinationIdFields()); - // When looking up the destination ID we require an array with both the - // source key and value, e.g. ['nid' => 41]. However, the Migration process - // plugin doesn't currently have a way to get the source key so we presume - // the values have been passed through in the correct order. - $have_keys = !isset($source_id_values[0]); - foreach ($this->sourceIdFields() as $field_name => $source_id) { - $query->condition("map.$source_id", $have_keys ? $source_id_values[$field_name] : array_shift($source_id_values), '='); - } + $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); $result = $query->execute(); $destination_id = $result->fetchAssoc(); return array_values($destination_id ?: array()); @@ -517,19 +541,23 @@ public function saveIdMapping(Row $row, array $destination_id_values, $source_ro // Construct the source key. $source_id_values = $row->getSourceIdValues(); // Construct the source key and initialize to empty variable keys. - $keys = array(); + $fields = []; foreach ($this->sourceIdFields() as $field_name => $key_name) { - // A NULL key value will fail. + // A NULL key value is usually an indication of a problem. if (!isset($source_id_values[$field_name])) { - $this->message->display(t( - 'Could not save to map table due to NULL value for key field @field', + $this->message->display($this->t( + 'Did not save to map table due to NULL value for key field @field', array('@field' => $field_name)), 'error'); return; } - $keys[$key_name] = $source_id_values[$field_name]; + $fields[$key_name] = $source_id_values[$field_name]; + } + + if (!$fields) { + return; } - $fields = array( + $fields += array( 'source_row_status' => (int) $source_row_status, 'rollback_action' => (int) $rollback_action, 'hash' => $row->getHash(), @@ -545,14 +573,13 @@ public function saveIdMapping(Row $row, array $destination_id_values, $source_ro if ($this->migration->get('trackLastImported')) { $fields['last_imported'] = time(); } - if ($keys) { - // Notify anyone listening of the map row we're about to save. - $this->eventDispatcher->dispatch(MigrateEvents::MAP_SAVE, new MigrateMapSaveEvent($this, $keys + $fields)); - $this->getDatabase()->merge($this->mapTableName()) - ->key($keys) - ->fields($fields) - ->execute(); - } + $keys = [static::SOURCE_IDS_HASH => $this->getSourceIDsHash($source_id_values)]; + // Notify anyone listening of the map row we're about to save. + $this->eventDispatcher->dispatch(MigrateEvents::MAP_SAVE, new MigrateMapSaveEvent($this, $fields)); + $this->getDatabase()->merge($this->mapTableName()) + ->key($keys) + ->fields($fields) + ->execute(); } /** @@ -564,8 +591,8 @@ public function saveMessage(array $source_id_values, $message, $level = Migratio if (!isset($source_id_values[$field_name])) { return; } - $fields[$source_id] = $source_id_values[$field_name]; } + $fields[static::SOURCE_IDS_HASH] = $this->getSourceIDsHash($source_id_values); $fields['level'] = $level; $fields['message'] = $message; $this->getDatabase()->insert($this->messageTableName()) @@ -583,10 +610,8 @@ public function saveMessage(array $source_id_values, $message, $level = Migratio public function getMessageIterator(array $source_id_values = [], $level = NULL) { $query = $this->getDatabase()->select($this->messageTableName(), 'msg') ->fields('msg'); - foreach ($this->sourceIdFields() as $field_name => $source_id) { - if (isset($source_id_values[$field_name])) { - $query->condition($source_id, $source_id_values[$field_name]); - } + if ($source_id_values) { + $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); } if ($level) { @@ -672,22 +697,16 @@ public function delete(array $source_id_values, $messages_only = FALSE) { if (empty($source_id_values)) { throw new MigrateException('Without source identifier values it is impossible to find the row to delete.'); } - if (!$messages_only) { - $map_query = $this->getDatabase()->delete($this->mapTableName()); - } - $message_query = $this->getDatabase()->delete($this->messageTableName()); - foreach ($this->sourceIdFields() as $field_name => $source_id) { - if (!$messages_only) { - $map_query->condition($source_id, $source_id_values[$field_name]); - } - $message_query->condition($source_id, $source_id_values[$field_name]); - } if (!$messages_only) { + $map_query = $this->getDatabase()->delete($this->mapTableName()); + $map_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); // Notify anyone listening of the map row we're about to delete. $this->eventDispatcher->dispatch(MigrateEvents::MAP_DELETE, new MigrateMapDeleteEvent($this, $source_id_values)); $map_query->execute(); } + $message_query = $this->getDatabase()->delete($this->messageTableName()); + $message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); $message_query->execute(); } @@ -705,11 +724,8 @@ public function deleteDestination(array $destination_id_values) { // Notify anyone listening of the map row we're about to delete. $this->eventDispatcher->dispatch(MigrateEvents::MAP_DELETE, new MigrateMapDeleteEvent($this, $source_id_values)); $map_query->execute(); - $count = 1; - foreach ($this->sourceIdFields() as $field_name => $source_id) { - $message_query->condition($source_id, $source_id_values[$field_name]); - $count++; - } + + $message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); $message_query->execute(); } } @@ -762,6 +778,7 @@ public function rewind() { } $this->result = $this->getDatabase()->select($this->mapTableName(), 'map') ->fields('map', $fields) + ->orderBy('destid1') ->execute(); $this->next(); } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php index 80992ee..59a6aba 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php @@ -29,6 +29,21 @@ class MigrateSqlIdMapEnsureTablesTest extends MigrateTestCase { * Tests the ensureTables method when the tables do not exist. */ public function testEnsureTablesNotExist() { + $fields['source_ids_hash'] = Array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => 1, + 'description' => 'Hash of source ids. Used as primary key' + ); + $fields['sourceid1'] = array( + 'type' => 'int', + 'not null' => TRUE, + ); + $fields['destid1'] = array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ); $fields['source_row_status'] = array( 'type' => 'int', 'size' => 'tiny', @@ -58,19 +73,10 @@ public function testEnsureTablesNotExist() { 'not null' => FALSE, 'description' => 'Hash of source row data, for detecting changes', ); - $fields['sourceid1'] = array( - 'type' => 'int', - 'not null' => TRUE, - ); - $fields['destid1'] = array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => FALSE, - ); $map_table_schema = array( 'description' => 'Mappings from source identifier value(s) to destination identifier value(s).', 'fields' => $fields, - 'primary key' => array('sourceid1'), + 'primary key' => array('source_ids_hash'), ); $schema = $this->getMockBuilder('Drupal\Core\Database\Schema') ->disableOriginalConstructor() @@ -89,9 +95,11 @@ public function testEnsureTablesNotExist() { 'unsigned' => TRUE, 'not null' => TRUE, ); - $fields['sourceid1'] = array( - 'type' => 'int', - 'not null' => TRUE, + $fields['source_ids_hash'] = Array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => 1, + 'description' => 'Hash of source ids. Used as primary key' ); $fields['level'] = array( 'type' => 'int', @@ -109,7 +117,6 @@ public function testEnsureTablesNotExist() { 'fields' => $fields, 'primary key' => array('msgid'), ); - $table_schema['indexes']['sourcekey'] = array('sourceid1'); $schema->expects($this->at(2)) ->method('tableExists') @@ -162,7 +169,20 @@ public function testEnsureTablesExist() { $schema->expects($this->at(4)) ->method('addField') ->with('migrate_map_sql_idmap_test', 'hash', $field_schema); - $schema->expects($this->exactly(5)) + $schema->expects($this->at(5)) + ->method('fieldExists') + ->with('migrate_map_sql_idmap_test', 'source_ids_hash') + ->will($this->returnValue(FALSE)); + $field_schema = array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'Hash of source ids. Used as primary key', + ); + $schema->expects($this->at(6)) + ->method('addField') + ->with('migrate_map_sql_idmap_test', 'source_ids_hash', $field_schema); + $schema->expects($this->exactly(7)) ->method($this->anything()); $this->runEnsureTablesTest($schema); } diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index 424406f..48be830 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -171,6 +171,7 @@ public function testSaveIdMapping() { $expected_result = [ [ 'sourceid1' => 'source_value', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source), 'destid1' => 2, ] + $this->idMapDefaults(), ]; @@ -182,6 +183,7 @@ public function testSaveIdMapping() { $id_map->saveIdMapping($row, ['destination_id_property' => 3]); $expected_result[] = [ 'sourceid1' => 'source_value_1', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source), 'destid1' => 3, ] + $this->idMapDefaults(); $this->queryResultTest($this->getIdMapContents(), $expected_result); @@ -239,6 +241,7 @@ public function testGetRowsNeedingUpdate() { $id_map->saveIdMapping($row, $destination, $status); $expected_results[] = [ 'sourceid1' => 'source_value_' . $status, + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source), 'destid1' => 'destination_value_' . $status, 'source_row_status' => $status, 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE, @@ -296,20 +299,26 @@ public function testMessageCount() { */ public function testMessageSave() { $message = 'Hello world.'; - $expected_results = [ + $original_values = [ 1 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR], 2 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING], 3 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE], 4 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL], ]; + $expected_results = [ + '7ad742edb7e866caa78ced1e4455d2e9cbd8adb2074e7c323d21b4e67732e755' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR], + '2d3ec2b0c547e819346e6ae03f881fd9f5c978ff3cbe29dfb807d40735e53703' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING], + '12a042f72cad9a2a8c7715df0c7695d762975f0687d87f5d480725dae1432a6f' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE], + 'd9d1fd27a2447ace48f47a2e9ff649673f67b446d9381a7963c949fc083f8791' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL], + ]; $id_map = $this->getIdMap(); - foreach ($expected_results as $key => $expected_result) { - $id_map->saveMessage(['source_id_property' => $key], $message, $expected_result['level']); + foreach ($original_values as $key => $original_value) { + $id_map->saveMessage(['source_id_property' => $key], $message, $original_value['level']); } foreach ($id_map->getMessageIterator() as $message_row) { - $key = $message_row->sourceid1; + $key = $message_row->source_ids_hash; $this->assertEquals($expected_results[$key]['message'], $message_row->message); $this->assertEquals($expected_results[$key]['level'], $message_row->level); } @@ -344,12 +353,14 @@ public function testGetRowBySource() { $row = [ 'sourceid1' => 'source_id_value_1', 'sourceid2' => 'source_id_value_2', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']), 'destid1' => 'destination_id_value_1', ] + $this->idMapDefaults(); $this->saveMap($row); $row = [ 'sourceid1' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4']), 'destid1' => 'destination_id_value_2', ] + $this->idMapDefaults(); $this->saveMap($row); @@ -413,6 +424,7 @@ public function testLookupDestinationIdMapping($num_source_fields, $num_destinat $expected_result[] = "destination_id_value_$i"; $this->destinationIds["destination_id_property_$i"] = []; } + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_id_values); $this->saveMap($row); $id_map = $this->getIdMap(); // Test for a valid hit. @@ -430,12 +442,14 @@ public function testGetRowByDestination() { $row = [ 'sourceid1' => 'source_id_value_1', 'sourceid2' => 'source_id_value_2', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']), 'destid1' => 'destination_id_value_1', ] + $this->idMapDefaults(); $this->saveMap($row); $row = [ 'sourceid1' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3']), 'destid1' => 'destination_id_value_2', ] + $this->idMapDefaults(); $this->saveMap($row); @@ -487,9 +501,11 @@ public function testLookupSourceIDMapping($num_source_fields, $num_destination_f $this->sourceIds = []; $this->destinationIds = []; $row = $this->idMapDefaults(); + $source_ids_values = []; $expected_result = []; for ($i = 1; $i <= $num_source_fields; $i++) { $row["sourceid$i"] = "source_id_value_$i"; + $source_ids_values = [$row["sourceid$i"]]; $expected_result["source_id_property_$i"] = "source_id_value_$i"; $this->sourceIds["source_id_property_$i"] = []; } @@ -501,6 +517,7 @@ public function testLookupSourceIDMapping($num_source_fields, $num_destination_f $nonexistent_id_values["destination_id_property_$i"] = "nonexistent_destination_id_value_$i"; $this->destinationIds["destination_id_property_$i"] = []; } + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_ids_values); $this->saveMap($row); $id_map = $this->getIdMap(); // Test for a valid hit. @@ -607,6 +624,7 @@ public function testUpdateCount($num_update_rows) { for ($i = 0; $i < 5; $i++) { $row = $this->idMapDefaults(); $row['sourceid1'] = "source_id_value_$i"; + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]); $row['destid1'] = "destination_id_value_$i"; $row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED; $this->saveMap($row); @@ -614,6 +632,7 @@ public function testUpdateCount($num_update_rows) { for (; $i < 5 + $num_update_rows; $i++) { $row = $this->idMapDefaults(); $row['sourceid1'] = "source_id_value_$i"; + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]); $row['destid1'] = "destination_id_value_$i"; $row['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE; $this->saveMap($row); @@ -653,6 +672,7 @@ public function testErrorCount($num_error_rows) { for ($i = 0; $i < 5; $i++) { $row = $this->idMapDefaults(); $row['sourceid1'] = "source_id_value_$i"; + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]); $row['destid1'] = "destination_id_value_$i"; $row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED; $this->saveMap($row); @@ -660,6 +680,7 @@ public function testErrorCount($num_error_rows) { for (; $i < 5 + $num_error_rows; $i++) { $row = $this->idMapDefaults(); $row['sourceid1'] = "source_id_value_$i"; + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]); $row['destid1'] = "destination_id_value_$i"; $row['source_row_status'] = MigrateIdMapInterface::STATUS_FAILED; $this->saveMap($row); @@ -687,6 +708,7 @@ public function testSetUpdate() { $id_map->saveIdMapping($row, $destination, $status); $expected_results[] = [ 'sourceid1' => 'source_value_' . $status, + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source), 'destid1' => 'destination_value_' . $status, 'source_row_status' => $status, 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE, @@ -815,6 +837,7 @@ public function testIterators() { for ($i = 0; $i < 3; $i++) { $row = $this->idMapDefaults(); $row['sourceid1'] = "source_id_value_$i"; + $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]); $row['destid1'] = "destination_id_value_$i"; $row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED; $expected_results[serialize(['sourceid1' => $row['sourceid1']])] = ['destid1' => $row['destid1']]; diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php index 1957dc1..b25c270 100644 --- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php +++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php @@ -99,6 +99,8 @@ protected function rerunMigration($new_row = []) { $default_connection = \Drupal::database(); $default_connection->truncate($table_name)->execute(); if ($new_row) { + $hash = $migration->getIdMap()->getSourceIDsHash(['nid' => $new_row['sourceid1']]); + $new_row['source_ids_hash'] = $hash; $default_connection->insert($table_name) ->fields($new_row) ->execute(); diff --git a/core/modules/shortcut/src/Plugin/migrate/source/d7/Shortcut.php b/core/modules/shortcut/src/Plugin/migrate/source/d7/Shortcut.php index 4b89bfa..f9a68fb 100644 --- a/core/modules/shortcut/src/Plugin/migrate/source/d7/Shortcut.php +++ b/core/modules/shortcut/src/Plugin/migrate/source/d7/Shortcut.php @@ -25,7 +25,8 @@ public function query() { return $this->select('menu_links', 'ml') ->fields('ml', array('mlid', 'menu_name', 'link_path', 'link_title', 'weight')) ->condition('hidden', '0') - ->condition('menu_name', 'shortcut-set-%', 'LIKE'); + ->condition('menu_name', 'shortcut-set-%', 'LIKE') + ->orderBy('mlid'); } /**