Commit 6efd90c8 authored by catch's avatar catch

Issue #2140511 by alexpott, Gábor Hojtsy, swentel, babruix, ohthehugemanatee,...

Issue #2140511 by alexpott, Gábor Hojtsy, swentel, babruix, ohthehugemanatee, jessebeach, Berdir: Configuration file name collisions silently ignored for default configuration
parent 73069e05
...@@ -22,11 +22,11 @@ class ConfigInstaller implements ConfigInstallerInterface { ...@@ -22,11 +22,11 @@ class ConfigInstaller implements ConfigInstallerInterface {
protected $configFactory; protected $configFactory;
/** /**
* The active configuration storage. * The active configuration storages, keyed by collection.
* *
* @var \Drupal\Core\Config\StorageInterface * @var \Drupal\Core\Config\StorageInterface[]
*/ */
protected $activeStorage; protected $activeStorages;
/** /**
* The typed configuration manager. * The typed configuration manager.
...@@ -79,7 +79,7 @@ class ConfigInstaller implements ConfigInstallerInterface { ...@@ -79,7 +79,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
*/ */
public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) { public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) {
$this->configFactory = $config_factory; $this->configFactory = $config_factory;
$this->activeStorage = $active_storage; $this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
$this->typedConfig = $typed_config; $this->typedConfig = $typed_config;
$this->configManager = $config_manager; $this->configManager = $config_manager;
$this->eventDispatcher = $event_dispatcher; $this->eventDispatcher = $event_dispatcher;
...@@ -119,7 +119,7 @@ public function installDefaultConfig($type, $name) { ...@@ -119,7 +119,7 @@ public function installDefaultConfig($type, $name) {
$enabled_extensions[] = 'core'; $enabled_extensions[] = 'core';
foreach ($collection_info->getCollectionNames(TRUE) as $collection) { foreach ($collection_info->getCollectionNames(TRUE) as $collection) {
$config_to_install = $this->listDefaultConfigCollection($collection, $type, $name, $enabled_extensions); $config_to_install = $this->listDefaultConfigToInstall($type, $name, $collection, $enabled_extensions);
if (!empty($config_to_install)) { if (!empty($config_to_install)) {
$this->createConfiguration($collection, $config_to_install); $this->createConfiguration($collection, $config_to_install);
} }
...@@ -130,21 +130,25 @@ public function installDefaultConfig($type, $name) { ...@@ -130,21 +130,25 @@ public function installDefaultConfig($type, $name) {
} }
/** /**
* Installs default configuration for a particular collection. * Lists default configuration for an extension that is available to install.
*
* This looks in the extension's config/install directory and all of the
* currently enabled extensions config/install directories for configuration
* that begins with the extension's name.
* *
* @param string $collection
* The configuration collection to install.
* @param string $type * @param string $type
* The extension type; e.g., 'module' or 'theme'. * The extension type; e.g., 'module' or 'theme'.
* @param string $name * @param string $name
* The name of the module or theme to install default configuration for. * The name of the module or theme to install default configuration for.
* @param string $collection
* The configuration collection to install.
* @param array $enabled_extensions * @param array $enabled_extensions
* A list of all the currently enabled modules and themes. * A list of all the currently enabled modules and themes.
* *
* @return array * @return array
* The list of configuration objects to create. * The list of configuration objects to create.
*/ */
protected function listDefaultConfigCollection($collection, $type, $name, array $enabled_extensions) { protected function listDefaultConfigToInstall($type, $name, $collection, array $enabled_extensions) {
// Get all default configuration owned by this extension. // Get all default configuration owned by this extension.
$source_storage = $this->getSourceStorage($collection); $source_storage = $this->getSourceStorage($collection);
$config_to_install = $source_storage->listAll($name . '.'); $config_to_install = $source_storage->listAll($name . '.');
...@@ -155,16 +159,17 @@ protected function listDefaultConfigCollection($collection, $type, $name, array ...@@ -155,16 +159,17 @@ protected function listDefaultConfigCollection($collection, $type, $name, array
$extension_path = drupal_get_path($type, $name); $extension_path = drupal_get_path($type, $name);
if ($type !== 'core' && is_dir($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY)) { if ($type !== 'core' && is_dir($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY)) {
$default_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection); $default_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection);
$other_module_config = array_filter($default_storage->listAll(), function ($value) use ($name) { $extension_provided_config = array_filter($default_storage->listAll(), function ($config_name) use ($config_to_install, $enabled_extensions) {
return !preg_match('/^' . $name . '\./', $value); // Ensure that we have not already discovered the config to install.
}); if (in_array($config_name, $config_to_install)) {
return FALSE;
$other_module_config = array_filter($other_module_config, function ($config_name) use ($enabled_extensions) { }
// Ensure the configuration is provided by an enabled module.
$provider = Unicode::substr($config_name, 0, strpos($config_name, '.')); $provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
return in_array($provider, $enabled_extensions); return in_array($provider, $enabled_extensions);
}); });
$config_to_install = array_merge($config_to_install, $other_module_config); $config_to_install = array_merge($config_to_install, $extension_provided_config);
} }
return $config_to_install; return $config_to_install;
...@@ -190,7 +195,7 @@ protected function createConfiguration($collection, array $config_to_install) { ...@@ -190,7 +195,7 @@ protected function createConfiguration($collection, array $config_to_install) {
} }
// Remove configuration that already exists in the active storage. // Remove configuration that already exists in the active storage.
$config_to_install = array_diff($config_to_install, $this->getActiveStorage($collection)->listAll()); $config_to_install = array_diff($config_to_install, $this->getActiveStorages($collection)->listAll());
foreach ($config_to_install as $name) { foreach ($config_to_install as $name) {
// Allow config factory overriders to use a custom configuration object if // Allow config factory overriders to use a custom configuration object if
...@@ -200,7 +205,7 @@ protected function createConfiguration($collection, array $config_to_install) { ...@@ -200,7 +205,7 @@ protected function createConfiguration($collection, array $config_to_install) {
$new_config = $overrider->createConfigObject($name, $collection); $new_config = $overrider->createConfigObject($name, $collection);
} }
else { else {
$new_config = new Config($name, $this->getActiveStorage($collection), $this->eventDispatcher, $this->typedConfig); $new_config = new Config($name, $this->getActiveStorages($collection), $this->eventDispatcher, $this->typedConfig);
} }
if ($data[$name] !== FALSE) { if ($data[$name] !== FALSE) {
$new_config->setData($data[$name]); $new_config->setData($data[$name]);
...@@ -221,7 +226,7 @@ protected function createConfiguration($collection, array $config_to_install) { ...@@ -221,7 +226,7 @@ protected function createConfiguration($collection, array $config_to_install) {
->getStorage($entity_type); ->getStorage($entity_type);
// It is possible that secondary writes can occur during configuration // It is possible that secondary writes can occur during configuration
// creation. Updates of such configuration are allowed. // creation. Updates of such configuration are allowed.
if ($this->getActiveStorage($collection)->exists($name)) { if ($this->getActiveStorages($collection)->exists($name)) {
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix()); $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
$entity = $entity_storage->load($id); $entity = $entity_storage->load($id);
$entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get()); $entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
...@@ -290,7 +295,7 @@ public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECT ...@@ -290,7 +295,7 @@ public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECT
// Default to using the ExtensionInstallStorage which searches extension's // Default to using the ExtensionInstallStorage which searches extension's
// config directories for default configuration. Only include the profile // config directories for default configuration. Only include the profile
// configuration during Drupal installation. // configuration during Drupal installation.
$this->sourceStorage = new ExtensionInstallStorage($this->activeStorage, InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, drupal_installation_attempted()); $this->sourceStorage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, drupal_installation_attempted());
} }
if ($this->sourceStorage->getCollectionName() != $collection) { if ($this->sourceStorage->getCollectionName() != $collection) {
$this->sourceStorage = $this->sourceStorage->createCollection($collection); $this->sourceStorage = $this->sourceStorage->createCollection($collection);
...@@ -308,11 +313,11 @@ public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECT ...@@ -308,11 +313,11 @@ public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECT
* @return \Drupal\Core\Config\StorageInterface * @return \Drupal\Core\Config\StorageInterface
* The configuration storage that provides the default configuration. * The configuration storage that provides the default configuration.
*/ */
protected function getActiveStorage($collection = StorageInterface::DEFAULT_COLLECTION) { protected function getActiveStorages($collection = StorageInterface::DEFAULT_COLLECTION) {
if ($this->activeStorage->getCollectionName() != $collection) { if (!isset($this->activeStorages[$collection])) {
$this->activeStorage = $this->activeStorage->createCollection($collection); $this->activeStorages[$collection] = reset($this->activeStorages)->createCollection($collection);
} }
return $this->activeStorage; return $this->activeStorages[$collection];
} }
/** /**
...@@ -329,4 +334,31 @@ public function setSyncing($status) { ...@@ -329,4 +334,31 @@ public function setSyncing($status) {
public function isSyncing() { public function isSyncing() {
return $this->isSyncing; return $this->isSyncing;
} }
/**
* {@inheritdoc}
*/
public function findPreExistingConfiguration($type, $name) {
$existing_configuration = array();
// Gather information about all the supported collections.
$collection_info = $this->configManager->getConfigCollectionInfo();
// Read enabled extensions directly from configuration to avoid circular
// dependencies on ModuleHandler and ThemeHandler.
$extension_config = $this->configFactory->get('core.extension');
$enabled_extensions = array_keys((array) $extension_config->get('module'));
$enabled_extensions += array_keys((array) $extension_config->get('theme'));
// Add the extension that will be enabled to the list of enabled extensions.
$enabled_extensions[] = $name;
foreach ($collection_info->getCollectionNames(TRUE) as $collection) {
$config_to_install = $this->listDefaultConfigToInstall($type, $name, $collection, $enabled_extensions);
$active_storage = $this->getActiveStorages($collection);
foreach ($config_to_install as $config_name) {
if ($active_storage->exists($config_name)) {
$existing_configuration[$collection][] = $config_name;
}
}
}
return $existing_configuration;
}
} }
...@@ -84,4 +84,26 @@ public function setSyncing($status); ...@@ -84,4 +84,26 @@ public function setSyncing($status);
*/ */
public function isSyncing(); public function isSyncing();
/**
* Finds pre-existing configuration objects for the provided extension.
*
* Extensions can not be installed if configuration objects exist in the
* active storage with the same names. This can happen in a number of ways,
* commonly:
* - if a user has created configuration with the same name as that provided
* by the extension.
* - if the extension provides default configuration that does not depend on
* it and the extension has been uninstalled and is about to the
* reinstalled.
*
* @param string $type
* Type of extension to install.
* @param string $name
* Name of extension to install.
*
* @return array
* Array of configuration objects that already exist keyed by collection.
*/
public function findPreExistingConfiguration($type, $name);
} }
...@@ -107,6 +107,19 @@ public function getEntityTypeIdByName($name) { ...@@ -107,6 +107,19 @@ public function getEntityTypeIdByName($name) {
return key($entities); return key($entities);
} }
/**
* {@inheritdoc}
*/
public function loadConfigEntityByName($name) {
$entity_type_id = $this->getEntityTypeIdByName($name);
if ($entity_type_id) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
$id = substr($name, strlen($entity_type->getConfigPrefix()) + 1);
return $this->entityManager->getStorage($entity_type_id)->load($id);
}
return NULL;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -23,6 +23,17 @@ interface ConfigManagerInterface { ...@@ -23,6 +23,17 @@ interface ConfigManagerInterface {
*/ */
public function getEntityTypeIdByName($name); public function getEntityTypeIdByName($name);
/**
* Loads a configuration entity using the configuration name.
*
* @param string $name
* The configuration object name.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* The configuration entity or NULL if it does not exist.
*/
public function loadConfigEntityByName($name);
/** /**
* Gets the entity manager. * Gets the entity manager.
* *
......
<?php
/**
* @file
* Contains \Drupal\Core\Config\PreExistingConfigException.
*/
namespace Drupal\Core\Config;
use Drupal\Component\Utility\String;
/**
* An exception thrown if configuration with the same name already exists.
*/
class PreExistingConfigException extends ConfigException {
/**
* A list of configuration objects that already exist in active configuration.
*
* @var array
*/
protected $configObjects = [];
/**
* The name of the module that is being installed.
*
* @var string
*/
protected $extension;
/**
* Gets the list of configuration objects that already exist.
*
* @return array
* A list of configuration objects that already exist in active
* configuration keyed by collection.
*/
public function getConfigObjects() {
return $this->configObjects;
}
/**
* Gets the name of the extension that is being installed.
*
* @return string
* The name of the extension that is being installed.
*/
public function getExtension() {
return $this->extension;
}
/**
* Creates an exception for an extension and a list of configuration objects.
*
* @param $extension
* The name of the extension that is being installed.
* @param array $config_objects
* A list of configuration objects that already exist in active
* configuration, keyed by config collection.
*
* @return \Drupal\Core\Config\PreExistingConfigException
*/
public static function create($extension, array $config_objects) {
$message = String::format('Configuration objects (@config_names) provided by @extension already exist in active configuration',
array(
'@config_names' => implode(', ', static::flattenConfigObjects($config_objects)),
'@extension' => $extension
)
);
$e = new static($message);
$e->configObjects = $config_objects;
$e->extension = $extension;
return $e;
}
/**
* Flattens the config object array to a single dimensional list.
*
* @param array $config_objects
* A list of configuration objects that already exist in active
* configuration, keyed by config collection.
*
* @return array
* A list of configuration objects that have been prefixed with their
* collection.
*/
public static function flattenConfigObjects(array $config_objects) {
$flat_config_objects = array();
foreach ($config_objects as $collection => $config_names) {
$config_names = array_map(function ($config_name) use ($collection) {
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
$config_name = str_replace('.', DIRECTORY_SEPARATOR, $collection) . DIRECTORY_SEPARATOR . $config_name;
}
return $config_name;
}, $config_names);
$flat_config_objects = array_merge($flat_config_objects, $config_names);
}
return $flat_config_objects;
}
}
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DrupalKernelInterface; use Drupal\Core\DrupalKernelInterface;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\String;
...@@ -149,6 +151,18 @@ public function install(array $module_list, $enable_dependencies = TRUE) { ...@@ -149,6 +151,18 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
))); )));
} }
// Install profiles can not have config clashes. Configuration that
// has the same name as a module's configuration will be used instead.
if ($module != drupal_get_profile()) {
// Validate default configuration of this module. Bail if unable to
// install. Should not continue installing more modules because those
// may depend on this one.
$existing_configuration = $config_installer->findPreExistingConfiguration('module', $module);
if (!empty($existing_configuration)) {
throw PreExistingConfigException::create($module, $existing_configuration);
}
}
$extension_config $extension_config
->set("module.$module", 0) ->set("module.$module", 0)
->set('module', module_config_sort($extension_config->get('module'))) ->set('module', module_config_sort($extension_config->get('module')))
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigInstallerInterface; use Drupal\Core\Config\ConfigInstallerInterface;
use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Routing\RouteBuilderIndicatorInterface; use Drupal\Core\Routing\RouteBuilderIndicatorInterface;
use Drupal\Core\State\StateInterface; use Drupal\Core\State\StateInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
...@@ -255,6 +256,13 @@ public function install(array $theme_list, $install_dependencies = TRUE) { ...@@ -255,6 +256,13 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
))); )));
} }
// Validate default configuration of the theme. If there is existing
// configuration then stop installing.
$existing_configuration = $this->configInstaller->findPreExistingConfiguration('theme', $key);
if (!empty($existing_configuration)) {
throw PreExistingConfigException::create($key, $existing_configuration);
}
// The value is not used; the weight is ignored for themes currently. // The value is not used; the weight is ignored for themes currently.
$extension_config $extension_config
->set("theme.$key", 0) ->set("theme.$key", 0)
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
namespace Drupal\config\Tests; namespace Drupal\config\Tests;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\StorageInterface;
use Drupal\simpletest\KernelTestBase; use Drupal\simpletest\KernelTestBase;
/** /**
...@@ -115,6 +117,21 @@ public function testCollectionInstallationCollections() { ...@@ -115,6 +117,21 @@ public function testCollectionInstallationCollections() {
$this->assertEqual($collection, $data['collection']); $this->assertEqual($collection, $data['collection']);
} }
// Tests that clashing configuration in collections is detected.
try {
\Drupal::service('module_installer')->install(['config_collection_clash_install_test']);
$this->fail('Expected PreExistingConfigException not thrown.');
}
catch (PreExistingConfigException $e) {
$this->assertEqual($e->getExtension(), 'config_collection_clash_install_test');
$this->assertEqual($e->getConfigObjects(), [
'another_collection' => ['config_collection_install_test.test'],
'collection.test1' => ['config_collection_install_test.test'],
'collection.test2' => ['config_collection_install_test.test'],
]);
$this->assertEqual($e->getMessage(), 'Configuration objects (another_collection/config_collection_install_test.test, collection/test1/config_collection_install_test.test, collection/test2/config_collection_install_test.test) provided by config_collection_clash_install_test already exist in active configuration');
}
// Test that the we can use the config installer to install all the // Test that the we can use the config installer to install all the
// available default configuration in a particular collection for enabled // available default configuration in a particular collection for enabled
// extensions. // extensions.
......
...@@ -2,12 +2,15 @@ ...@@ -2,12 +2,15 @@
/** /**
* @file * @file
* Definition of Drupal\config\Tests\ConfigInstallTest. * Contains \Drupal\config\Tests\ConfigInstallWebTest.
*/ */
namespace Drupal\config\Tests; namespace Drupal\config\Tests;
use Drupal\Core\Config\InstallStorage; use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\PreExistingConfigException;
use Drupal\Core\Config\StorageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\simpletest\WebTestBase; use Drupal\simpletest\WebTestBase;
use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\FileStorage;
...@@ -19,12 +22,19 @@ ...@@ -19,12 +22,19 @@
*/ */
class ConfigInstallWebTest extends WebTestBase { class ConfigInstallWebTest extends WebTestBase {
/**
* The admin user used in this test.
*/
protected $adminUser;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function setUp() { protected function setUp() {
parent::setUp(); parent::setUp();
$this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes'));
// Ensure the global variable being asserted by this test does not exist; // Ensure the global variable being asserted by this test does not exist;
// a previous test executed in this request/process might have set it. // a previous test executed in this request/process might have set it.
unset($GLOBALS['hook_config_test']); unset($GLOBALS['hook_config_test']);
...@@ -82,6 +92,18 @@ function testIntegrationModuleReinstallation() { ...@@ -82,6 +92,18 @@ function testIntegrationModuleReinstallation() {
$this->assertIdentical($config_entity->get('label'), 'Customized integration config label'); $this->assertIdentical($config_entity->get('label'), 'Customized integration config label');
// Reinstall the integration module. // Reinstall the integration module.
try {
\Drupal::service('module_installer')->install(array('config_integration_test'));
$this->fail('Expected PreExistingConfigException not thrown.');
}
catch (PreExistingConfigException $e) {
$this->assertEqual($e->getExtension(), 'config_integration_test');
$this->assertEqual($e->getConfigObjects(), [StorageInterface::DEFAULT_COLLECTION => ['config_test.dynamic.config_integration_test']]);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.config_integration_test) provided by config_integration_test already exist in active configuration');
}
// Delete the configuration entity so that the install will work.
$config_entity->delete();
\Drupal::service('module_installer')->install(array('config_integration_test')); \Drupal::service('module_installer')->install(array('config_integration_test'));
// Verify the integration module's config was re-installed. // Verify the integration module's config was re-installed.
...@@ -91,10 +113,10 @@ function testIntegrationModuleReinstallation() { ...@@ -91,10 +113,10 @@ function testIntegrationModuleReinstallation() {
$this->assertIdentical($config_static->isNew(), FALSE); $this->assertIdentical($config_static->isNew(), FALSE);
$this->assertIdentical($config_static->get('foo'), 'default setting'); $this->assertIdentical($config_static->get('foo'), 'default setting');
// Verify the customized integration config still exists. // Verify the integration config is using the default.
$config_entity = $this->config($default_configuration_entity); $config_entity = \Drupal::config($default_configuration_entity);
$this->assertIdentical($config_entity->isNew(), FALSE); $this->assertIdentical($config_entity->isNew(), FALSE);
$this->assertIdentical($config_entity->get('label'), 'Customized integration config label'); $this->assertIdentical($config_entity->get('label'), 'Default integration config label');
} }
/** /**
...@@ -132,21 +154,64 @@ function testInstallProfileConfigOverwrite() { ...@@ -132,21 +154,64 @@ function testInstallProfileConfigOverwrite() {
// created from the testing install profile's system.cron.yml file. // created from the testing install profile's system.cron.yml file.
$config = $this->config($config_name); $config = $this->config($config_name);
$this->assertIdentical($config->get(), $expected_profile_data); $this->assertIdentical($config->get(), $expected_profile_data);
}
// Turn on the test module, which will attempt to replace the /**
// configuration data. This attempt to replace the active configuration * Tests pre-existing configuration detection.
// should be ignored. */
\Drupal::service('module_installer')->install(array('config_existing_default_config_test')); public function testPreExistingConfigInstall() {
$this->drupalLogin($this->adminUser);
// Verify that the test module has not been able to change the data.
$config = $this->config($config_name); // Try to install config_install_fail_test and config_test. Doing this
$this->assertIdentical($config->get(), $expected_profile_data); // will install the config_test module first because it is a dependency of
// config_install_fail_test.
// Disable and uninstall the test module. // @see \Drupal\system\Form\ModulesListForm::submitForm()
\Drupal::service('module_installer')->uninstall(array('config_existing_default_config_test')); $this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE, 'modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default</em> already exists in active configuration.');
// Verify that the data hasn't been altered by removing the test module.
$config = $this->config($config_name); // Uninstall the config_test module to test the confirm form.
$this->assertIdentical($config->get(), $expected_profile_data); $this->drupalPostForm('admin/modules/uninstall', array('uninstall[config_test]' => TRUE), t('Uninstall'));
$this->drupalPostForm(NULL, array(), t('Uninstall'));
// Try to install config_install_fail_test without selecting config_test.
// The user is shown a confirm form because the config_test module is a
// dependency.
// @see \Drupal\system\Form\ModulesListConfirmForm::submitForm()
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration'));
$this->drupalPostForm(NULL, array(), t('Continue'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default</em> already exists in active configuration.');
// Test that collection configuration clashes during a module install are
// reported correctly.
\Drupal::service('module_installer')->install(['language']);
$this->rebuildContainer();
ConfigurableLanguage::createFromLangcode('fr')->save();
\Drupal::languageManager()
->getLanguageConfigOverride('fr', 'config_test.dynamic.dotted.default')
->set('label', 'Je suis Charlie')
->save();
$this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration'));
$this->assertRaw('Unable to install Configuration install fail test, <em class="placeholder">config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default</em> already exist in active configuration.');
// Test installing a theme through the UI that has existing configuration.
// This relies on the fact the config_test has been installed and created
// the config_test.dynamic.dotted.default configuration and the translation
// override created still exists.
$this->drupalGet('admin/appearance');
$url = $this->xpath("//a[contains(@href,'config_clash_test_theme') and contains(@href,'/install?')]/@href")[0];
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertRaw('Unable to install config_clash_test_theme, <em class="placeholder">config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default</em> already exist in active configuration.');
// Test installing a theme through the API that has existing configuration.
try {
\Drupal::service('theme_handler')->install(['config_clash_test_theme']);
$this->fail('Expected PreExistingConfigException not thrown.');
}
catch (PreExistingConfigException $e) {
$this->assertEqual($e->getExtension(), 'config_clash_test_theme');
$this->assertEqual($e->getConfigObjects(), [StorageInterface::DEFAULT_COLLECTION => ['config_test.dynamic.dotted.default'], 'language.fr' => ['config_test.dynamic.dotted.default']]);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default) provided by config_clash_test_theme already exist in active configuration');
}
} }
} }