Issue #1740378 by xjm, Désiré, alexpott | heyrocker: Implement renames in the import cycle.
parent
7ec9480ea9
commit
37250bb5bb
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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')) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -208,6 +210,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}
|
||||
*/
|
||||
|
@ -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],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue