diff -u b/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php --- b/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -570,7 +570,7 @@ // into account. if ($this->totalConfigurationToProcess == 0) { $this->storageComparer->reset(); - foreach (array('delete', 'create', 'rename', 'update') as $op) { + foreach (array('delete', 'create', 'update') as $op) { foreach ($this->getUnprocessedConfiguration($op) as $name) { $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op)); } @@ -587,7 +587,7 @@ // into account. if ($this->totalConfigurationToProcess == 0) { $this->storageComparer->reset(); - foreach (array('delete', 'create', 'update') as $op) { + foreach (array('delete', 'create', 'rename', 'update') as $op) { foreach ($this->getUnprocessedConfiguration($op) as $name) { $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op)); } @@ -646,7 +646,7 @@ protected function getNextConfigurationOperation() { // The order configuration operations is processed is important. Deletes // have to come first so that recreates can work. - foreach (array('delete', 'create', 'rename', 'update') as $op) { + foreach (array('delete', 'create', 'update') as $op) { $config_names = $this->getUnprocessedConfiguration($op); if (!empty($config_names)) { return array( @@ -663,7 +663,7 @@ protected function getNextConfigurationOperation() { // The order configuration operations is processed is important. Deletes // have to come first so that recreates can work. - foreach (array('delete', 'create', 'update') as $op) { + foreach (array('delete', 'create', 'rename', 'update') as $op) { $config_names = $this->getUnprocessedConfiguration($op); if (!empty($config_names)) { return array( @@ -940,7 +940,7 @@ * * @param string $rename_name * The rename configuration name, as provided by - * ConfigImporter::createRenameName(). + * \Drupal\Core\Config\StorageComparer::createRenameName(). * * @return bool * TRUE if the configuration was imported as a configuration entity. FALSE diff -u b/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php --- b/core/lib/Drupal/Core/Config/StorageComparer.php +++ b/core/lib/Drupal/Core/Config/StorageComparer.php @@ -236,20 +236,24 @@ } $renames = array(); - foreach ($this->targetData as $id => $data) { + + // Renames should be ordered so that dependencies are renamed last. This + // ensures that if there is logic in the configuration entity class to keep + // names in sync it should still work. + // @see \Drupal\node\Entity\NodeType::postSave() + foreach ($this->targetNames as $name) { + $data = $this->targetData[$name]; if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) { // Remove the item from the create list. $this->removeFromChangelist('create', $create_uuids[$data['uuid']]); // Remove the item from the delete list. - $this->removeFromChangelist('delete', $id); + $this->removeFromChangelist('delete', $name); // Create the rename name. - $renames[] = $this->createRenameName($id, $create_uuids[$data['uuid']]); + $renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]); } } - // Import the renames in reverse order, so that (e.g.) content types are - // handled before fields. - $this->addChangeList('rename', array_reverse($renames)); + $this->addChangeList('rename', $renames); } /** diff -u b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php --- b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -184,8 +184,8 @@ // Add the AJAX library to the form for dialog support. $form['#attached']['library'][] = 'core/drupal.ajax'; - foreach ($storage_comparer->getChangelist() as $config_change_type => $config_files) { - if (empty($config_files)) { + foreach ($storage_comparer->getChangelist() as $config_change_type => $config_names) { + if (empty($config_names)) { continue; } @@ -197,19 +197,19 @@ ); switch ($config_change_type) { case 'create': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new'); + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count new', '@count new'); break; case 'update': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed'); + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count changed', '@count changed'); break; case 'delete': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed'); + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count removed', '@count removed'); break; case 'rename': - $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count renamed', '@count renamed'); + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count renamed', '@count renamed'); break; } $form[$config_change_type]['list'] = array( @@ -217,15 +217,14 @@ '#header' => array('Name', 'Operations'), ); - foreach ($config_files as $config_file) { + foreach ($config_names as $config_name) { if ($config_change_type == 'rename') { - $names = explode('::', $config_file); - $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $names[0], 'target_name' => $names[1])); - $config_name = $this->t('!source_name to !target_name', array('!source_name' => $names[0], '!target_name' => $names[1])); + $names = $storage_comparer->extractRenameNames($config_name); + $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name'])); + $config_name = $this->t('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name'])); } else { - $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $config_file)); - $config_name = $config_file; + $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $config_name)); } $links['view_diff'] = array( 'title' => $this->t('View differences'), reverted: --- b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php +++ /dev/null @@ -1,125 +0,0 @@ - 'Import renamed configuration', - 'description' => 'Tests importing renamed configuration.', - 'group' => 'Configuration', - ); - } - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - $this->web_user = $this->drupalCreateUser(array('synchronize configuration')); - $this->drupalLogin($this->web_user); - } - - /** - * Tests configuration renaming. - */ - public function testConfigurationRename() { - $content_type = entity_create('node_type', array( - 'type' => Unicode::strtolower($this->randomName(16)), - 'name' => $this->randomName(), - )); - $content_type->save(); - $staged_type = $content_type->type; - $active = $this->container->get('config.storage'); - $staging = $this->container->get('config.storage.staging'); - - $config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); - // Emulate a staging operation. - $this->copyConfig($active, $staging); - - // Change the machine name of the content type. - $content_type->type = Unicode::strtolower($this->randomName(8)); - $content_type->save(); - $active_type = $content_type->type; - $renamed_config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); - $this->assertTrue($active->exists($renamed_config_name), 'The content type has the new name in the active store.'); - $this->assertFalse($active->exists($config_name), "The content type's old name does not exist active store."); - - $this->configImporter()->reset(); - $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('create')), 'There are no configuration items to create.'); - $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('delete')), 'There are no configuration items to delete.'); - $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('update')), 'There are no configuration items to update.'); - - // We expect that changing the machine name of the content type will - // rename five configuration entities: the node type, the body field - // instance, two entity form displays, and the entity view display. - $expected = array( - 'node.type.' . $active_type . '::node.type.' . $staged_type, - 'field.instance.node.' . $active_type . '.body::field.instance.node.' . $staged_type . '.body', - 'entity.view_display.node.' . $active_type . '.teaser::entity.view_display.node.' . $staged_type . '.teaser', - 'entity.view_display.node.' . $active_type . '.default::entity.view_display.node.' . $staged_type . '.default', - 'entity.form_display.node.' . $active_type . '.default::entity.form_display.node.' . $staged_type . '.default', - ); - $renames = $this->configImporter()->getUnprocessedConfiguration('rename'); - $this->assertIdentical($expected, $renames); - - $this->drupalGet('admin/config/development/configuration'); - foreach ($expected as $rename) { - $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); - $this->assertText(String::format('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name']))); - // Test that the diff link is present for each renamed item. - $href = \Drupal::urlGenerator()->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name'])); - $this->assertLinkByHref($href); - $hrefs[$rename] = $href; - } - - // Ensure that the diff works for each renamed item. - foreach ($hrefs as $rename => $href) { - $this->drupalGet($href); - $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); - $config_entity_type = \Drupal::service('config.manager')->getEntityTypeIdByName($names['old_name']); - $entity_type = \Drupal::entityManager()->getDefinition($config_entity_type); - $old_id = ConfigEntityStorage::getIDFromConfigName($names['old_name'], $entity_type->getConfigPrefix()); - $new_id = ConfigEntityStorage::getIDFromConfigName($names['new_name'], $entity_type->getConfigPrefix()); - $this->assertText('-' . $entity_type->getKey('id') . ': ' . $old_id); - $this->assertText('+' . $entity_type->getKey('id') . ': ' . $new_id); - } - - // Run the import. - $this->drupalPostForm('admin/config/development/configuration', array(), t('Import all')); - $this->assertText(t('There are no configuration changes.')); - - $this->assertFalse(entity_load('node_type', $active_type), 'The content no longer exists with the old name.'); - $content_type = entity_load('node_type', $staged_type); - $this->assertIdentical($staged_type, $content_type->type); - } - -} diff -u b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php --- b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; +use Drupal\Component\Uuid\Php; use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\ConfigImporterException; use Drupal\Core\Config\StorageComparer; @@ -123,2 +124,45 @@ + /** + * Tests configuration renaming validation for simple configuration. + */ + public function testRenameSimpleConfigValidation() { + $uuid = new Php(); + // Create a simple configuration with a UUID. + $config = \Drupal::config('config_test.new'); + $uuid_value = $uuid->generate(); + $config->set('uuid', $uuid_value)->save(); + + $active = $this->container->get('config.storage'); + $staging = $this->container->get('config.storage.staging'); + $this->copyConfig($active, $staging); + $config->delete(); + + // Create another simple configuration with the same UUID. + $config = \Drupal::config('config_test.old'); + $config->set('uuid', $uuid_value)->save(); + + // Confirm that the staged configuration is detected as a rename since the + // UUIDs match. + $this->configImporter->reset(); + $expected = array( + 'config_test.old::config_test.new' + ); + $renames = $this->configImporter->getUnprocessedConfiguration('rename'); + $this->assertIdentical($expected, $renames); + + // Try to import the configuration. We expect an exception to be thrown + // because the rename is for simple configuration. + try { + $this->configImporter->import(); + $this->fail('Expected ConfigImporterException thrown when a renamed configuration entity does not match the existing entity type.'); + } + catch (ConfigImporterException $e) { + $this->pass('Expected ConfigImporterException thrown when a renamed configuration entity does not match the existing entity type.'); + $expected = array( + String::format('Rename operation for simple configuration. Existing configuration !old_name and staged configuration !new_name.', array('old_name' => 'config_test.old', 'new_name' => 'config_test.new')) + ); + $this->assertIdentical($expected, $this->configImporter->getErrors()); + } + } + } only in patch2: unchanged: --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php @@ -0,0 +1,122 @@ + 'Import renamed node type', + 'description' => 'Tests importing renamed node type via configuration synchronisation.', + 'group' => 'Configuration', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->web_user = $this->drupalCreateUser(array('synchronize configuration')); + $this->drupalLogin($this->web_user); + } + + /** + * Tests configuration renaming. + */ + public function testConfigurationRename() { + $content_type = entity_create('node_type', array( + 'type' => Unicode::strtolower($this->randomName(16)), + 'name' => $this->randomName(), + )); + $content_type->save(); + $staged_type = $content_type->type; + $active = $this->container->get('config.storage'); + $staging = $this->container->get('config.storage.staging'); + + $config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); + // Emulate a staging operation. + $this->copyConfig($active, $staging); + + // Change the machine name of the content type. + $content_type->type = Unicode::strtolower($this->randomName(8)); + $content_type->save(); + $active_type = $content_type->type; + $renamed_config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); + $this->assertTrue($active->exists($renamed_config_name), 'The content type has the new name in the active store.'); + $this->assertFalse($active->exists($config_name), "The content type's old name does not exist active store."); + + $this->configImporter()->reset(); + $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('create')), 'There are no configuration items to create.'); + $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('delete')), 'There are no configuration items to delete.'); + $this->assertEqual(0, count($this->configImporter()->getUnprocessedConfiguration('update')), 'There are no configuration items to update.'); + + // We expect that changing the machine name of the content type will + // rename five configuration entities: the node type, the body field + // instance, two entity form displays, and the entity view display. + // @see \Drupal\node\Entity\NodeType::postSave() + $expected = array( + 'node.type.' . $active_type . '::node.type.' . $staged_type, + 'entity.form_display.node.' . $active_type . '.default::entity.form_display.node.' . $staged_type . '.default', + 'entity.view_display.node.' . $active_type . '.default::entity.view_display.node.' . $staged_type . '.default', + 'entity.view_display.node.' . $active_type . '.teaser::entity.view_display.node.' . $staged_type . '.teaser', + 'field.instance.node.' . $active_type . '.body::field.instance.node.' . $staged_type . '.body', + ); + $renames = $this->configImporter()->getUnprocessedConfiguration('rename'); + $this->assertIdentical($expected, $renames); + + $this->drupalGet('admin/config/development/configuration'); + foreach ($expected as $rename) { + $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); + $this->assertText(String::format('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name']))); + // Test that the diff link is present for each renamed item. + $href = \Drupal::urlGenerator()->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name'])); + $this->assertLinkByHref($href); + $hrefs[$rename] = $href; + } + + // Ensure that the diff works for each renamed item. + foreach ($hrefs as $rename => $href) { + $this->drupalGet($href); + $names = $this->configImporter()->getStorageComparer()->extractRenameNames($rename); + $config_entity_type = \Drupal::service('config.manager')->getEntityTypeIdByName($names['old_name']); + $entity_type = \Drupal::entityManager()->getDefinition($config_entity_type); + $old_id = ConfigEntityStorage::getIDFromConfigName($names['old_name'], $entity_type->getConfigPrefix()); + $new_id = ConfigEntityStorage::getIDFromConfigName($names['new_name'], $entity_type->getConfigPrefix()); + $this->assertText('-' . $entity_type->getKey('id') . ': ' . $old_id); + $this->assertText('+' . $entity_type->getKey('id') . ': ' . $new_id); + } + + // Run the import. + $this->drupalPostForm('admin/config/development/configuration', array(), t('Import all')); + $this->assertText(t('There are no configuration changes.')); + + $this->assertFalse(entity_load('node_type', $active_type), 'The content no longer exists with the old name.'); + $content_type = entity_load('node_type', $staged_type); + $this->assertIdentical($staged_type, $content_type->type); + } + +}