diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 2ccbf94..7729302 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -11,6 +11,7 @@ use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Component\Utility\String; use Drupal\Core\Config\Entity\ImportableEntityStorageInterface; +use Drupal\Core\Config\ConfigEvents; use Drupal\Core\DependencyInjection\DependencySerialization; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Lock\LockBackendInterface; @@ -263,12 +264,12 @@ protected function getEmptyExtensionsProcessedList() { * * @param array $ops * The operations to check for changes. Defaults to all operations, i.e. - * array('delete', 'create', 'update'). + * array('delete', 'create', 'update', 'rename'). * * @return bool * TRUE if there are changes to process and FALSE if not. */ - public function hasUnprocessedConfigurationChanges($ops = array('delete', 'create', 'update')) { + public function hasUnprocessedConfigurationChanges($ops = array('delete', 'create', 'rename', 'update')) { foreach ($ops as $op) { if (count($this->getUnprocessedConfiguration($op))) { return TRUE; @@ -291,7 +292,7 @@ public function getProcessedConfiguration() { * Sets a change as processed. * * @param string $op - * The change operation performed, either delete, create or update. + * The change operation performed, either delete, create, rename or update. * @param string $name * The name of the configuration processed. */ @@ -304,7 +305,7 @@ protected function setProcessedConfiguration($op, $name) { * * @param string $op * The change operation to get the unprocessed list for, either delete, - * create or update. + * create, rename or update. * * @return array * An array of configuration names. @@ -569,7 +570,7 @@ public function processConfigurations(array &$context) { // 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)); } @@ -645,7 +646,7 @@ protected function getNextExtensionOperation() { 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( @@ -668,6 +669,21 @@ protected function getNextConfigurationOperation() { */ public function validate() { if (!$this->validated) { + // Validate renames. + foreach ($this->getUnprocessedConfiguration('rename') as $name) { + $names = explode('::', $name); + $old_name = $names[0]; + $new_name = $names[1]; + $old_entity_type_id = $this->configManager->getEntityTypeIdByName($old_name); + $new_entity_type_id = $this->configManager->getEntityTypeIdByName($new_name); + if ($old_entity_type_id != $new_entity_type_id) { + $this->logError($this->t('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' => $old_entity_type_id, 'new_type' => $new_entity_type_id, 'old_name' => $old_name, 'new_name' => $new_name))); + } + // Has to be a configuration entity. + if (!$old_entity_type_id) { + $this->logError($this->t('Rename operation for simple configuration. Existing configuration !old_name and staged configuration !new_name.', array('old_name' => $old_name, 'new_name' => $new_name))); + } + } $this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this)); if (count($this->getErrors())) { throw new ConfigImporterException('There were errors validating the config synchronization.'); @@ -770,6 +786,21 @@ protected function processExtension($type, $op, $name) { * TRUE is to continue processing, FALSE otherwise. */ protected function checkOp($op, $name) { + if ($op == 'rename') { + $names = explode('::', $name); + $new_name = $names[1]; + $target_exists = $this->storageComparer->getTargetStorage()->exists($new_name); + if ($target_exists) { + // The rename has already occurred as the result of a secondary + // configuration write. Change the operation into an update. This is the + // desired since renames often have to occur together. For example, + // renaming a node type has to rename field instances and entity + // displays. + $this->storageComparer->moveRenameToUpdate($name); + return FALSE; + } + return TRUE; + } $target_exists = $this->storageComparer->getTargetStorage()->exists($name); switch ($op) { case 'delete': @@ -845,7 +876,7 @@ protected function importConfig($op, $name) { * * @param string $op * The change operation to get the unprocessed list for, either delete, - * create or update. + * create, rename or update. * @param string $name * The name of the configuration to process. * @@ -858,6 +889,10 @@ protected function importConfig($op, $name) { * otherwise. */ protected function importInvokeOwner($op, $name) { + // Renames are handled separately. + if ($op == 'rename') { + return $this->importInvokeRename($name); + } // Validate the configuration object name before importing it. // Config::validateName($name); if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) { @@ -883,6 +918,46 @@ protected function importInvokeOwner($op, $name) { $this->setProcessedConfiguration($op, $name); return TRUE; } + return FALSE; + } + + /** + * Imports a configuration entity rename. + * + * @param string $name + * The rename configuration name. A special case because both the old name + * and new name are required to perform a rename. The string $name is the + * old name and new name concatenated with '::'. + * + * @return bool + * TRUE if the configuration was imported as a configuration entity. FALSE + * otherwise. + */ + protected function importInvokeRename($name) { + $names = explode('::', $name); + $old_name = $names[0]; + $new_name = $names[1]; + $entity_type_id = $this->configManager->getEntityTypeIdByName($old_name); + $old_config = new Config($old_name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager); + if ($old_data = $this->storageComparer->getTargetStorage()->read($old_name)) { + $old_config->initWithData($old_data); + } + + $data = $this->storageComparer->getSourceStorage()->read($new_name); + $new_config = new Config($new_name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager); + if ($data !== FALSE) { + $new_config->setData($data); + } + + $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type_id); + // Call to the configuration entity's storage to handle the configuration + // change. + if (!($entity_storage instanceof ImportableEntityStorageInterface)) { + throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type))); + } + $entity_storage->importRename($old_name, $new_config, $old_config); + $this->setProcessedConfiguration('rename', $name); + return TRUE; } /** diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index 6fd55c9..5a869d3 100644 --- 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')) { diff --git a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php index 4da9474..5168638 100644 --- 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. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 126275a..1fd73e7 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -455,4 +455,19 @@ public function importDelete($name, Config $new_config, Config $old_config) { return TRUE; } + /** + * {@inheritdoc} + */ + public function importRename($old_name, Config $new_config, Config $old_config) { + $id = static::getIDFromConfigName($old_name, $this->entityType->getConfigPrefix()); + $entity = $this->load($id); + $entity->setSyncing(TRUE); + $data = $new_config->get(); + foreach ($data as $key => $value) { + $entity->set($key, $value); + } + $entity->save(); + return TRUE; + } + } diff --git a/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php b/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php index 4eae41e..c09fd2a 100644 --- a/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php @@ -56,4 +56,16 @@ public function importUpdate($name, Config $new_config, Config $old_config); */ public function importDelete($name, Config $new_config, Config $old_config); + /** + * Rename entities upon synchronizing configuration changes. + * + * @param string $old_name + * The original name of the configuration object. + * @param \Drupal\Core\Config\Config $new_config + * A configuration object containing the new configuration data. + * @param \Drupal\Core\Config\Config $old_config + * A configuration object containing the old configuration data. + */ + public function importRename($old_name, Config $new_config, Config $old_config); + } diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php index b30db17..604c880 100644 --- a/core/lib/Drupal/Core/Config/StorageComparer.php +++ b/core/lib/Drupal/Core/Config/StorageComparer.php @@ -100,6 +100,7 @@ public function getEmptyChangelist() { 'create' => array(), 'update' => array(), 'delete' => array(), + 'rename' => array() ); } @@ -117,7 +118,7 @@ public function getChangelist($op = NULL) { * Adds changes to the changelist. * * @param string $op - * The change operation performed. Either delete, create or update. + * The change operation performed. Either delete, create, rename or update. * @param array $changes * Array of changes to add to the changelist. * @param array $sort_order @@ -147,6 +148,7 @@ public function createChangelist() { $this->addChangelistCreate(); $this->addChangelistUpdate(); $this->addChangelistDelete(); + $this->addChangelistRename(); $this->sourceData = NULL; $this->targetData = NULL; return $this; @@ -209,6 +211,71 @@ protected function addChangelistUpdate() { } /** + * Creates the rename changelist. + * + * The list of renames is created from the different source and target names + * with same uuid, these changes will be removed from the create and delete + * lists. + */ + protected function addChangelistRename() { + // Renames will be present in both create and delete lists. + $create_list = $this->getChangelist('create'); + $delete_list = $this->getChangelist('delete'); + if (empty($create_list) || empty($delete_list)) { + return; + } + + $create_uuids = array(); + foreach ($this->sourceData as $id => $data) { + if (isset($data['uuid']) && in_array($id, $create_list)) { + $create_uuids[$data['uuid']] = $id; + } + } + if (empty($create_uuids)) { + return; + } + + $renames = array(); + foreach ($this->targetData as $id => $data) { + if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) { + // Remove from create list. + $this->removeFromChangelist('create', $create_uuids[$data['uuid']]); + // Remove from delete list. + $this->removeFromChangelist('delete', $id); + // Create rename operation. + $renames[] = $id . '::' . $create_uuids[$data['uuid']]; + } + } + + // Import in the order that will do content types before fields. + $this->addChangeList('rename', array_reverse($renames)); + } + + /** + * Removes the entry from the given operation changelist for the given name. + * + * @param string $op + * The changelist to act on. Either delete, create, rename or update. + * @param string $name + * The name of the configuration to remove. + */ + protected function removeFromChangelist($op, $name) { + $key = array_search($name, $this->changelist[$op]); + if ($key !== FALSE) { + unset($this->changelist[$op][$key]); + } + } + + /** + * {@inheritdoc} + */ + public function moveRenameToUpdate($rename) { + $names = explode('::', $rename); + $this->removeFromChangelist('rename', $rename); + $this->addChangeList('update', array($names[1]), $this->sourceNames); + } + + /** * {@inheritdoc} */ public function reset() { @@ -220,7 +287,7 @@ public function reset() { /** * {@inheritdoc} */ - public function hasChanges($ops = array('delete', 'create', 'update')) { + public function hasChanges($ops = array('delete', 'create', 'update', 'rename')) { foreach ($ops as $op) { if (!empty($this->changelist[$op])) { return TRUE; diff --git a/core/lib/Drupal/Core/Config/StorageComparerInterface.php b/core/lib/Drupal/Core/Config/StorageComparerInterface.php index e8c597d..c96fe8d 100644 --- a/core/lib/Drupal/Core/Config/StorageComparerInterface.php +++ b/core/lib/Drupal/Core/Config/StorageComparerInterface.php @@ -80,4 +80,12 @@ public function hasChanges($ops = array('delete', 'create', 'update')); */ public function validateSiteUuid(); + /** + * Move a rename operation to an update. + * + * @param string $rename + * The rename old_config_name::new_config_name + */ + public function moveRenameToUpdate($rename); + } diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml index bd0d5c5..f2a944a 100644 --- 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' diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index c79b8cb..dd7a0c3 100644 --- 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'; diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php index a505a6c..48f509c 100644 --- 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', diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigDiffTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigDiffTest.php index 57e0553..e4e15d0 100644 --- 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 diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php new file mode 100644 index 0000000..e3cfbfb --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php @@ -0,0 +1,122 @@ + '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 testRenamed() { + $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(); + $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, 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.'); + $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); + } + +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php new file mode 100644 index 0000000..f8bf817 --- /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()); + } + } + +}