diff -u b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php --- b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php @@ -7,29 +7,26 @@ namespace Drupal\config\Tests; +use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\ConfigImporter; +use Drupal\Core\Config\ConfigImporterException; +use Drupal\Core\Config\Entity\ConfigEntityStorage; use Drupal\Core\Config\StorageComparer; use Drupal\simpletest\DrupalUnitTestBase; +use Drupal\simpletest\WebTestBase; /** * Tests importing renamed configuration from files into active configuration. */ -class ConfigImportRenameTest extends DrupalUnitTestBase { - - /** - * Config Importer object used for testing. - * - * @var \Drupal\Core\Config\ConfigImporter - */ - protected $configImporter; +class ConfigImportRenameTest extends WebTestBase { /** * Modules to enable. * * @var array */ - public static $modules = array('system', 'node', 'field', 'text', 'entity'); + public static $modules = array('system', 'node', 'field', 'text', 'entity', 'config', 'config_test'); /** * {@inheritdoc} @@ -47,26 +44,8 @@ */ public function setUp() { parent::setUp(); - - $this->installSchema('system', 'config_snapshot'); - $this->installSchema('node', 'node'); - - // Set up the ConfigImporter object for testing. - $storage_comparer = new StorageComparer( - $this->container->get('config.storage.staging'), - $this->container->get('config.storage') - ); - $this->configImporter = new ConfigImporter( - $storage_comparer->createChangelist(), - $this->container->get('event_dispatcher'), - $this->container->get('config.manager'), - $this->container->get('lock'), - $this->container->get('config.typed'), - $this->container->get('module_handler'), - $this->container->get('theme_handler'), - $this->container->get('string_translation') - ); - $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); + $this->web_user = $this->drupalCreateUser(array('synchronize configuration')); + $this->drupalLogin($this->web_user); } /** @@ -78,6 +57,7 @@ 'name' => $this->randomName(), )); $content_type->save(); + $staged_type = $content_type->type; $active = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); @@ -85,23 +65,58 @@ $this->copyConfig($active, $staging); // Change the machine name of the content type. This wil rename 5 - // configuration entities: the node type, the body field instance, the - // entity form display and the entity view display. + // configuration entities: the node type, the body field instance, two + // entity form displays and the entity view display. $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), 'Content type has new name in active store.'); $this->assertFalse($active->exists($config_name), '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.'); - $this->assertEqual(4, count($this->configImporter->getUnprocessedConfiguration('rename')), 'There are 4 configuration items to rename.'); - - // If we try to do this then we fail badly because of secondary writes and - // deletes. - $this->configImporter->import(); + $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.'); + $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 = explode('::', $rename); + $this->assertText(String::format('!source_name to !target_name', array('!source_name' => $names[0], '!target_name' => $names[1]))); + // Test that the diff link is present. + $href = \Drupal::urlGenerator()->getPathFromRoute('config.diff', array('source_name' => $names[0], 'target_name' => $names[1])); + $this->assertLinkByHref($href); + $hrefs[$rename] = $href; + } + + // Ensure that the diff works. + foreach ($hrefs as $rename => $href) { + $this->drupalGet($href); + $names = explode('::', $rename); + $config_entity_type = \Drupal::service('config.manager')->getEntityTypeIdByName($names[0]); + $entity_type = \Drupal::entityManager()->getDefinition($config_entity_type); + $old_id = ConfigEntityStorage::getIDFromConfigName($names[0], $entity_type->getConfigPrefix()); + $new_id = ConfigEntityStorage::getIDFromConfigName($names[1], $entity_type->getConfigPrefix()); + $this->assertText('-' . $entity_type->getKey('id') . ': ' . $old_id); + $this->assertText('+' . $entity_type->getKey('id') . ': ' . $new_id); + } + + // Run the import. + $this->drupalPostForm(NULL, array(), t('Import all')); + $this->assertText(t('There are no configuration changes.')); + + $this->assertFalse(entity_load('node_type', $active_type), 'Content with old name no longer exists.'); + $content_type = entity_load('node_type', $staged_type); + $this->assertIdentical($staged_type, $content_type->type); } } only in patch2: unchanged: --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -100,7 +100,10 @@ public function getConfigFactory() { /** * {@inheritdoc} */ - public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $name) { + public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL) { + if (!isset($target_name)) { + $target_name = $source_name; + } // @todo Replace with code that can be autoloaded. // https://drupal.org/node/1848266 require_once __DIR__ . '/../../Component/Diff/DiffEngine.php'; @@ -111,8 +114,8 @@ public function diff(StorageInterface $source_storage, StorageInterface $target_ $dumper = new Dumper(); $dumper->setIndentation(2); - $source_data = explode("\n", $dumper->dump($source_storage->read($name), PHP_INT_MAX)); - $target_data = explode("\n", $dumper->dump($target_storage->read($name), PHP_INT_MAX)); + $source_data = explode("\n", $dumper->dump($source_storage->read($source_name), PHP_INT_MAX)); + $target_data = explode("\n", $dumper->dump($target_storage->read($target_name), PHP_INT_MAX)); // Check for new or removed files. if ($source_data === array('false')) { only in patch2: unchanged: --- a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php @@ -46,15 +46,18 @@ public function getConfigFactory(); * The storage to diff configuration from. * @param \Drupal\Core\Config\StorageInterface $target_storage * The storage to diff configuration to. - * @param string $name - * The name of the configuration object to diff. + * @param string $source_name + * The name of the configuration object in the source storage to diff. + * @param string $target_name + * (Optional) The name of the configuration object in the target storage to + * diff. If omitted then the source name is used. * * @return core/lib/Drupal/Component/Diff * A formatted string showing the difference between the two storages. * * @todo Make renderer injectable */ - public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $name); + public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL); /** * Creates a configuration snapshot following a successful import. only in patch2: unchanged: --- a/core/modules/config/config.routing.yml +++ b/core/modules/config/config.routing.yml @@ -7,9 +7,10 @@ config.sync: _permission: 'synchronize configuration' config.diff: - path: '/admin/config/development/configuration/sync/diff/{config_file}' + path: '/admin/config/development/configuration/sync/diff/{source_name}/{target_name}' defaults: _content: '\Drupal\config\Controller\ConfigController::diff' + target_name: NULL requirements: _permission: 'synchronize configuration' only in patch2: unchanged: --- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php +++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php @@ -105,15 +105,15 @@ public function downloadExport() { * @return string * Table showing a two-way diff between the active and staged configuration. */ - public function diff($config_file) { + public function diff($source_name, $target_name = NULL) { - $diff = $this->configManager->diff($this->targetStorage, $this->sourceStorage, $config_file); + $diff = $this->configManager->diff($this->targetStorage, $this->sourceStorage, $source_name, $target_name); $formatter = new \DrupalDiffFormatter(); $formatter->show_header = FALSE; $build = array(); - $build['#title'] = t('View changes of @config_file', array('@config_file' => $config_file)); + $build['#title'] = t('View changes of @config_file', array('@config_file' => $source_name)); // Add the CSS for the inline diff. $build['#attached']['css'][] = drupal_get_path('module', 'system') . '/css/system.diff.css'; only in patch2: unchanged: --- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php @@ -207,6 +207,10 @@ public function buildForm(array $form, array &$form_state) { case 'delete': $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed'); break; + + case 'rename': + $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count renamed', '@count renamed'); + break; } $form[$config_change_type]['list'] = array( '#type' => 'table', @@ -214,9 +218,18 @@ public function buildForm(array $form, array &$form_state) { ); foreach ($config_files as $config_file) { + 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])); + } + else { + $href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $config_file)); + $config_name = $config_file; + } $links['view_diff'] = array( 'title' => $this->t('View differences'), - 'href' => $this->urlGenerator->getPathFromRoute('config.diff', array('config_file' => $config_file)), + 'href' => $href, 'attributes' => array( 'class' => array('use-ajax'), 'data-accepts' => 'application/vnd.drupal-modal', @@ -226,7 +239,7 @@ public function buildForm(array $form, array &$form_state) { ), ); $form[$config_change_type]['list']['#rows'][] = array( - 'name' => $config_file, + 'name' => $config_name, 'operations' => array( 'data' => array( '#type' => 'operations', only in patch2: unchanged: --- a/core/modules/config/lib/Drupal/config/Tests/ConfigDiffTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigDiffTest.php @@ -84,6 +84,33 @@ function testDiff() { $this->assertEqual($diff->edits[1]->type, 'add', 'The second item in the diff is an add.'); $this->assertFalse($diff->edits[1]->orig, format_string("The key '%add_key' does not exist in active.", array('%add_key' => $add_key))); $this->assertEqual($diff->edits[1]->closing[0], $add_key . ': ' . $add_data, format_string("The staging value for key '%add_key' is '%add_data'.", array('%add_key' => $add_key, '%add_data' => $add_data))); + + // Test diffing a renamed config entity. + $test_entity_id = $this->randomName(); + $test_entity = entity_create('config_test', array( + 'id' => $test_entity_id, + 'label' => $this->randomName(), + )); + $test_entity->save(); + $data = $active->read('config_test.dynamic.' . $test_entity_id); + $staging->write('config_test.dynamic.' . $test_entity_id, $data); + $config_name = 'config_test.dynamic.' . $test_entity_id; + $diff = \Drupal::service('config.manager')->diff($active, $staging, $config_name, $config_name); + // Prove the fields match. + $this->assertEqual($diff->edits[0]->type, 'copy', 'The first item in the diff is a copy.'); + $this->assertEqual(count($diff->edits), 1, 'There is one item in the diff'); + + // Rename the entity. + $new_test_entity_id = $this->randomName(); + $test_entity->set('id', $new_test_entity_id); + $test_entity->save(); + + $diff = \Drupal::service('config.manager')->diff($active, $staging, 'config_test.dynamic.' . $new_test_entity_id, $config_name); + $this->assertEqual($diff->edits[0]->type, 'change', 'The second item in the diff is a copy.'); + $this->assertEqual($diff->edits[0]->orig, array('id: ' . $new_test_entity_id)); + $this->assertEqual($diff->edits[0]->closing, array('id: ' . $test_entity_id)); + $this->assertEqual($diff->edits[1]->type, 'copy', 'The second item in the diff is a copy.'); + $this->assertEqual(count($diff->edits), 2, 'There are two items in the diff'); } } \ No newline at end of file only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php @@ -0,0 +1,117 @@ + 'Configuration import rename validation', + 'description' => 'Tests validating renamed configuration in a configuration import.', + 'group' => 'Configuration', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->installSchema('system', 'config_snapshot'); + $this->installSchema('node', 'node'); + + // Set up the ConfigImporter object for testing. + $storage_comparer = new StorageComparer( + $this->container->get('config.storage.staging'), + $this->container->get('config.storage') + ); + $this->configImporter = new ConfigImporter( + $storage_comparer->createChangelist(), + $this->container->get('event_dispatcher'), + $this->container->get('config.manager'), + $this->container->get('lock'), + $this->container->get('config.typed'), + $this->container->get('module_handler'), + $this->container->get('theme_handler'), + $this->container->get('string_translation') + ); + } + + /** + * Tests configuration renaming validation. + */ + public function testRenameValidation() { + $test_entity_id = $this->randomName(); + $test_entity = entity_create('config_test', array( + 'id' => $test_entity_id, + 'label' => $this->randomName(), + )); + $test_entity->save(); + $uuid = $test_entity->uuid(); + $active = $this->container->get('config.storage'); + $staging = $this->container->get('config.storage.staging'); + $this->copyConfig($active, $staging); + $test_entity->delete(); + + // Create a content type with a matching UUID. + $content_type = entity_create('node_type', array( + 'type' => Unicode::strtolower($this->randomName(16)), + 'name' => $this->randomName(), + 'uuid' => $uuid, + )); + $content_type->save(); + + $this->configImporter->reset(); + $expected = array( + 'node.type.' . $content_type->id() . '::config_test.dynamic.' . $test_entity_id, + ); + $renames = $this->configImporter->getUnprocessedConfiguration('rename'); + $this->assertIdentical($expected, $renames); + + try { + $this->configImporter->import(); + $this->fail('Expected ConfigImporterException thrown.'); + } + catch (ConfigImporterException $e) { + $this->pass('Expected ConfigImporterException thrown.'); + $expected = array( + String::format('Entity type mismatch on rename. !old_type not equal to !new_type for existing configuration !old_name and staged configuration !new_name.', array('old_type' => 'node_type', 'new_type' => 'config_test', 'old_name' => 'node.type.' . $content_type->id(), 'new_name' => 'config_test.dynamic.' . $test_entity_id)) + ); + $this->assertIdentical($expected, $this->configImporter->getErrors()); + } + } + +}