diff --git a/core/lib/Drupal/Core/Config/PreExistingConfigException.php b/core/lib/Drupal/Core/Config/PreExistingConfigException.php index d34a7ad..84a85fb 100644 --- a/core/lib/Drupal/Core/Config/PreExistingConfigException.php +++ b/core/lib/Drupal/Core/Config/PreExistingConfigException.php @@ -7,8 +7,67 @@ 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 $module; + + /** + * Gets the list of configuration objects that already exist. + * + * @return array + * A list of configuration objects that already exist in active + * configuration. + */ + public function getConfigObjects() { + return $this->configObjects; + } + + /** + * Gets the name of the module that is being installed. + * + * @return string + * The name of the module that is being installed. + */ + public function getModule() { + return $this->module; + } + + /** + * Creates an exception for a module name and a list of configuration objects. + * + * @param $module + * The name of the module that is being installed. + * @param array $config_objects + * A list of configuration objects that already exist in active + * configuration. + * + * @return \Drupal\Core\Config\PreExistingConfigException + */ + public static function create($module, array $config_objects) { + $message = String::format('Configuration objects (@config_names) provided by @extension already exist in active configuration', + array('@config_names' => implode(', ', $config_objects), '@extension' => $module) + ); + $e = new static($message); + $e->configObjects = $config_objects; + $e->module = $module; + return $e; + } + } diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index a84bad8..a3b8345 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -159,9 +159,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) { // may depend on this one. $existing_configuration = $config_installer->findPreExistingConfiguration('module', $module); if (!empty($existing_configuration)) { - throw new PreExistingConfigException(String::format('Configuration @config_names provided by @extension already exist in active configuration', - array('@config_names' => implode(',', $existing_configuration[StorageInterface::DEFAULT_COLLECTION]), '@extension' => $module) - )); + throw PreExistingConfigException::create($module, $existing_configuration[StorageInterface::DEFAULT_COLLECTION]); } } diff --git a/core/modules/config/src/Tests/ConfigInstallWebTest.php b/core/modules/config/src/Tests/ConfigInstallWebTest.php index 5639a1b..cc3cd66 100644 --- a/core/modules/config/src/Tests/ConfigInstallWebTest.php +++ b/core/modules/config/src/Tests/ConfigInstallWebTest.php @@ -25,7 +25,6 @@ class ConfigInstallWebTest extends WebTestBase { */ protected $adminUser; - /** * {@inheritdoc} */ @@ -96,7 +95,7 @@ function testIntegrationModuleReinstallation() { $this->fail('Expected PreExistingConfigException not thrown.'); } catch (PreExistingConfigException $e) { - $this->assertEqual($e->getMessage(), 'Configuration config_test.dynamic.config_integration_test provided by config_integration_test already exist in active configuration'); + $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. @@ -109,7 +108,7 @@ function testIntegrationModuleReinstallation() { $config_static = $this->config($default_config); $this->assertIdentical($config_static->isNew(), FALSE); $this->assertIdentical($config_static->get('foo'), 'default setting'); - + // Verify the integration config is using the default. $config_entity = \Drupal::config($default_configuration_entity); $this->assertIdentical($config_entity->isNew(), FALSE); @@ -175,19 +174,46 @@ function testInstallProfileConfigOverwrite() { public function testPreExistingConfigInstall() { $this->drupalLogin($this->adminUser); - // Try to install config_install_fail_test without config_test enabled. + // Try to install config_install_fail_test and config_test. This installs + // the config_test module first because it is a dependency and then detects + // a config clash. + // @see \Drupal\system\Form\ModulesListForm::submitForm() + $this->drupalPostForm('admin/modules', array('modules[Testing][config_test][enable]' => TRUE, 'modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration')); + $this->assertRaw(t('The default configuration %config_names provided by @extension already exists in active configuration.', + array('%config_names' => implode(', ', ['config_test.dynamic.dotted.default']), '@extension' => 'Configuration install fail test'))); + + // Uninstall the config_test module to test the confirm form. + $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 config_test enabled. This + // installs the config_test module because it is a dependency and then + // detects a config clash. Unlike the first module install this uses the + // confirm form. + // @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 can only work if config_install_fail_test depends on config_test - $this->assertText('Configuration config_test.dynamic.dotted.default provided by config_install_fail_test already exist in active configuration'); + $this->assertRaw(t('The default configuration %config_names provided by @extension already exists in active configuration.', + array('%config_names' => implode(', ', ['config_test.dynamic.dotted.default']), '@extension' => 'Configuration install fail test'))); + + // Update the container so we get an environment with config_test installed. + $this->rebuildContainer(); + $config_entity = \Drupal::entityManager()->getStorage('config_test')->load('dotted.default'); - // Try to install config_install_fail_test. config_test was enabled in the - // previous submission. + // Try to install config_install_fail_test. Now we are able to detect a + // configuration clash before installation so the user will be taken to... + // The config_test module was enabled in the previous submission. $this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_test][enable]' => TRUE), t('Save configuration')); + $this->assertUrl('admin/modules/config-clash'); $this->assertText('You must delete or rename the following configuration to install Configuration install fail test.'); + $this->assertLinkByHref($config_entity->url()); - // Try to install config_install_fail_dependency_test. + // Try to install config_install_fail_dependency_test. This fails because + // it depends on config_install_fail_test and we can detect the + // configuration clash before beginning the installation. $this->drupalPostForm('admin/modules', array('modules[Testing][config_install_fail_dependency_test][enable]' => TRUE), t('Save configuration')); + $this->assertUrl('admin/modules/config-clash'); $this->assertText('You must delete or rename the following configuration to install Configuration install fail dependency test, Configuration install fail test.'); + $this->assertLinkByHref($config_entity->url()); } } diff --git a/core/modules/system/src/Form/ModulesListConfirmForm.php b/core/modules/system/src/Form/ModulesListConfirmForm.php index 93c64f9..d7234f4 100644 --- a/core/modules/system/src/Form/ModulesListConfirmForm.php +++ b/core/modules/system/src/Form/ModulesListConfirmForm.php @@ -183,7 +183,18 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // the form doesn't allow modules with unmet dependencies, so the only way // this can happen is if the filesystem changed between form display and // submit, in which case the user has bigger problems. - $this->moduleInstaller->install(array_keys($this->modules['install'])); + try { + $this->moduleInstaller->install(array_keys($this->modules['install'])); + } + catch (PreExistingConfigException $e) { + $config_objects = $e->getConfigObjects(); + drupal_set_message($this->formatPlural( + count($config_objects), + 'The default configuration %config_names provided by @extension already exists in active configuration.', + 'The default configurations %config_names provided by @extension already exist in active configuration.', + array('%config_names' => implode(', ', $config_objects), '@extension' => $this->modules['install'][$e->getModule()]) + )); + } } // Gets module list after install process, flushes caches and displays a diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index 9ee1f2b..8347543 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -546,8 +546,14 @@ public function submitForm(array &$form, FormStateInterface $form_state) { try { $this->moduleInstaller->install(array_keys($modules['install'])); } - catch (PreExistingConfigException $exception) { - drupal_set_message($exception->getMessage()); + catch (PreExistingConfigException $e) { + $config_objects = $e->getConfigObjects(); + drupal_set_message($this->formatPlural( + count($config_objects), + 'The default configuration %config_names provided by @extension already exists in active configuration.', + 'The default configurations %config_names provided by @extension already exist in active configuration.', + array('%config_names' => implode(', ', $config_objects), '@extension' => $modules['install'][$e->getModule()]) + )); } }