summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2014-04-16 21:58:17 (GMT)
committerwebchick2014-04-16 22:00:29 (GMT)
commit37250bb5bbe045bdde3522a11f1a520a1754bd95 (patch)
tree41298eb79f9db1874f5c8cd9c539adfd47c12952
parent7ec9480ea98ea78d03602b1537837f84c0ce4d44 (diff)
Issue #1740378 by xjm, Désiré, alexpott | heyrocker: Implement renames in the import cycle.
-rw-r--r--core/lib/Drupal/Core/Config/ConfigImporter.php85
-rw-r--r--core/lib/Drupal/Core/Config/ConfigManager.php9
-rw-r--r--core/lib/Drupal/Core/Config/ConfigManagerInterface.php9
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php15
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php12
-rw-r--r--core/lib/Drupal/Core/Config/StorageComparer.php107
-rw-r--r--core/lib/Drupal/Core/Config/StorageComparerInterface.php26
-rw-r--r--core/modules/config/config.routing.yml3
-rw-r--r--core/modules/config/lib/Drupal/config/Controller/ConfigController.php6
-rw-r--r--core/modules/config/lib/Drupal/config/Form/ConfigSync.php28
-rw-r--r--core/modules/config/lib/Drupal/config/Tests/ConfigDiffTest.php29
-rw-r--r--core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php168
-rw-r--r--core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php122
13 files changed, 591 insertions, 28 deletions
diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index 6f1998f..e81b29b 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\ModuleHandlerInterface;
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 @@ class ConfigImporter extends DependencySerialization {
*
* @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 @@ class ConfigImporter extends DependencySerialization {
* 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 @@ class ConfigImporter extends DependencySerialization {
*
* @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.
@@ -586,7 +587,7 @@ class ConfigImporter extends DependencySerialization {
// 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));
}
@@ -662,7 +663,7 @@ class ConfigImporter extends DependencySerialization {
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(
@@ -685,6 +686,19 @@ class ConfigImporter extends DependencySerialization {
*/
public function validate() {
if (!$this->validated) {
+ // Validate renames.
+ foreach ($this->getUnprocessedConfiguration('rename') as $name) {
+ $names = $this->storageComparer->extractRenameNames($name);
+ $old_entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']);
+ $new_entity_type_id = $this->configManager->getEntityTypeIdByName($names['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' => $names['old_name'], 'new_name' => $names['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' => $names['old_name'], 'new_name' => $names['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.');
@@ -787,6 +801,20 @@ class ConfigImporter extends DependencySerialization {
* TRUE is to continue processing, FALSE otherwise.
*/
protected function checkOp($op, $name) {
+ if ($op == 'rename') {
+ $names = $this->storageComparer->extractRenameNames($name);
+ $target_exists = $this->storageComparer->getTargetStorage()->exists($names['new_name']);
+ if ($target_exists) {
+ // If the 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 behavior since renames often
+ // have to occur together. For example, renaming a node type must
+ // also result in renaming its field instances and entity displays.
+ $this->storageComparer->moveRenameToUpdate($name);
+ return FALSE;
+ }
+ return TRUE;
+ }
$target_exists = $this->storageComparer->getTargetStorage()->exists($name);
switch ($op) {
case 'delete':
@@ -862,7 +890,7 @@ class ConfigImporter extends DependencySerialization {
*
* @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.
*
@@ -875,6 +903,10 @@ class ConfigImporter extends DependencySerialization {
* 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)) {
@@ -900,6 +932,45 @@ class ConfigImporter extends DependencySerialization {
$this->setProcessedConfiguration($op, $name);
return TRUE;
}
+ return FALSE;
+ }
+
+ /**
+ * Imports a configuration entity rename.
+ *
+ * @param string $rename_name
+ * The rename configuration name, as provided by
+ * \Drupal\Core\Config\StorageComparer::createRenameName().
+ *
+ * @return bool
+ * TRUE if the configuration was imported as a configuration entity. FALSE
+ * otherwise.
+ *
+ * @see \Drupal\Core\Config\ConfigImporter::createRenameName()
+ */
+ protected function importInvokeRename($rename_name) {
+ $names = $this->storageComparer->extractRenameNames($rename_name);
+ $entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']);
+ $old_config = new Config($names['old_name'], $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
+ if ($old_data = $this->storageComparer->getTargetStorage()->read($names['old_name'])) {
+ $old_config->initWithData($old_data);
+ }
+
+ $data = $this->storageComparer->getSourceStorage()->read($names['new_name']);
+ $new_config = new Config($names['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_id)));
+ }
+ $entity_storage->importRename($names['old_name'], $new_config, $old_config);
+ $this->setProcessedConfiguration('rename', $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 @@ class ConfigManager implements ConfigManagerInterface {
/**
* {@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 @@ class ConfigManager implements ConfigManagerInterface {
$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..1d35637 100644
--- a/core/lib/Drupal/Core/Config/ConfigManagerInterface.php
+++ b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php
@@ -46,15 +46,18 @@ interface ConfigManagerInterface {
* 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.
+ * If omitted, 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 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
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..a12f770 100644
--- a/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php
@@ -56,4 +56,16 @@ interface ImportableEntityStorageInterface {
*/
public function importDelete($name, Config $new_config, Config $old_config);
+ /**
+ * Renames 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..8a6b9f1 100644
--- a/core/lib/Drupal/Core/Config/StorageComparer.php
+++ b/core/lib/Drupal/Core/Config/StorageComparer.php
@@ -100,6 +100,7 @@ class StorageComparer implements StorageComparerInterface {
'create' => array(),
'update' => array(),
'delete' => array(),
+ 'rename' => array(),
);
}
@@ -117,7 +118,7 @@ class StorageComparer implements StorageComparerInterface {
* 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 @@ class StorageComparer implements StorageComparerInterface {
$this->addChangelistCreate();
$this->addChangelistUpdate();
$this->addChangelistDelete();
+ $this->addChangelistRename();
$this->sourceData = NULL;
$this->targetData = NULL;
return $this;
@@ -209,6 +211,80 @@ class StorageComparer implements StorageComparerInterface {
}
/**
+ * 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 the 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();
+
+ // 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 will still work. $this->targetNames is in the desired
+ // order due to the use of configuration dependencies in
+ // \Drupal\Core\Config\StorageComparer::getAndSortConfigData().
+ // Node type is a good example of a configuration entity that renames other
+ // configuration when it is renamed.
+ // @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', $name);
+ // Create the rename name.
+ $renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]);
+ }
+ }
+
+ $this->addChangeList('rename', $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 = $this->extractRenameNames($rename);
+ $this->removeFromChangelist('rename', $rename);
+ $this->addChangeList('update', array($names['new_name']), $this->sourceNames);
+ }
+
+ /**
* {@inheritdoc}
*/
public function reset() {
@@ -220,7 +296,7 @@ class StorageComparer implements StorageComparerInterface {
/**
* {@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;
@@ -249,4 +325,31 @@ class StorageComparer implements StorageComparerInterface {
$this->sourceNames = $dependency_manager->setData($this->sourceData)->sortAll();
}
+ /**
+ * Creates a rename name from the old and new names for the object.
+ *
+ * @param string $old_name
+ * The old configuration object name.
+ * @param string $new_name
+ * The new configuration object name.
+ *
+ * @return string
+ * The configuration change name that encodes both the old and the new name.
+ *
+ * @see \Drupal\Core\Config\StorageComparerInterface::extractRenameNames()
+ */
+ protected function createRenameName($name1, $name2) {
+ return $name1 . '::' . $name2;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function extractRenameNames($name) {
+ $names = explode('::', $name, 2);
+ return array(
+ 'old_name' => $names[0],
+ 'new_name' => $names[1],
+ );
+ }
}
diff --git a/core/lib/Drupal/Core/Config/StorageComparerInterface.php b/core/lib/Drupal/Core/Config/StorageComparerInterface.php
index e8c597d..f85a8aa 100644
--- a/core/lib/Drupal/Core/Config/StorageComparerInterface.php
+++ b/core/lib/Drupal/Core/Config/StorageComparerInterface.php
@@ -80,4 +80,30 @@ interface StorageComparerInterface {
*/
public function validateSiteUuid();
+ /**
+ * Moves a rename operation to an update.
+ *
+ * @param string $rename
+ * The rename name, as provided by ConfigImporter::createRenameName().
+ *
+ * @see \Drupal\Core\Config\ConfigImporter::createRenameName()
+ */
+ public function moveRenameToUpdate($rename);
+
+ /**
+ * Extracts old and new configuration names from a configuration change name.
+ *
+ * @param string $name
+ * The configuration change name, as provided by
+ * ConfigImporter::createRenameName().
+ *
+ * @return array
+ * An associative array of configuration names. The array keys are
+ * 'old_name' and and 'new_name' representing the old and name configuration
+ * object names during a rename operation.
+ *
+ * @see \Drupal\Core\Config\StorageComparer::createRenameNames()
+ */
+ public function extractRenameNames($name);
+
}
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 @@ class ConfigController implements ContainerInjectionInterface {
* @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..19f417f 100644
--- a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
@@ -184,8 +184,8 @@ class ConfigSync extends FormBase {
// 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,15 +197,19 @@ class ConfigSync extends FormBase {
);
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_names), '@count renamed', '@count renamed');
break;
}
$form[$config_change_type]['list'] = array(
@@ -213,10 +217,18 @@ class ConfigSync extends FormBase {
'#header' => array('Name', 'Operations'),
);
- foreach ($config_files as $config_file) {
+ foreach ($config_names as $config_name) {
+ if ($config_change_type == 'rename') {
+ $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_name));
+ }
$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 +238,7 @@ class ConfigSync extends FormBase {
),
);
$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..6f3b3de 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 @@ class ConfigDiffTest extends DrupalUnitTestBase {
$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/ConfigImportRenameValidationTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php
new file mode 100644
index 0000000..a538c12
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Tests\ConfigImportRenameValidationTest.
+ */
+
+namespace Drupal\config\Tests;
+
+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;
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests validating renamed configuration in a configuration import.
+ */
+class ConfigImportRenameValidationTest extends DrupalUnitTestBase {
+
+ /**
+ * Config Importer object used for testing.
+ *
+ * @var \Drupal\Core\Config\ConfigImporter
+ */
+ protected $configImporter;
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('system', 'node', 'field', 'text', 'entity', 'config_test');
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => '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() {
+ // Create a test entity.
+ $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();
+
+ // Stage the test entity and then delete it from the active storage.
+ $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 in the active storage.
+ $content_type = entity_create('node_type', array(
+ 'type' => Unicode::strtolower($this->randomName(16)),
+ 'name' => $this->randomName(),
+ 'uuid' => $uuid,
+ ));
+ $content_type->save();
+
+ // Confirm that the staged configuration is detected as a rename since the
+ // UUIDs match.
+ $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 to import the configuration. We expect an exception to be thrown
+ // because the staged entity is of a different type.
+ 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('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());
+ }
+ }
+
+ /**
+ * 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 simple configuration is renamed.');
+ }
+ catch (ConfigImporterException $e) {
+ $this->pass('Expected ConfigImporterException thrown when simple configuration is renamed.');
+ $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());
+ }
+ }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php
new file mode 100644
index 0000000..bd2ba66
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypeRenameConfigImportTest.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\NodeTypeRenameConfigImportTest.
+ */
+
+namespace Drupal\node\Tests;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests importing renamed node type via configuration synchronisation.
+ */
+class NodeTypeRenameConfigImportTest extends WebTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('node', 'text', 'config');
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => '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);
+ }
+
+}