diff -u b/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php --- b/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -344,8 +344,7 @@ $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 extensions that will be enabled to the list of enabled - // extensions. + // 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->listDefaultConfigCollection($collection, $type, $name, $enabled_extensions); diff -u b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php --- b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigManagerInterface.php @@ -24,7 +24,7 @@ public function getEntityTypeIdByName($name); /** - * Loads a configuration entity from the configuration name. + * Loads a configuration entity using the configuration name. * * @param string $name * The configuration object name. diff -u b/core/lib/Drupal/Core/Config/PreExistingConfigException.php b/core/lib/Drupal/Core/Config/PreExistingConfigException.php --- b/core/lib/Drupal/Core/Config/PreExistingConfigException.php +++ b/core/lib/Drupal/Core/Config/PreExistingConfigException.php @@ -28,46 +28,75 @@ */ - protected $module; + protected $extension; /** * Gets the list of configuration objects that already exist. * * @return array * A list of configuration objects that already exist in active - * configuration. + * configuration keyed by collection. */ public function getConfigObjects() { return $this->configObjects; } /** - * Gets the name of the module that is being installed. + * Gets the name of the extension that is being installed. * * @return string - * The name of the module that is being installed. + * The name of the extension that is being installed. */ - public function getModule() { - return $this->module; + public function getExtension() { + return $this->extension; } /** - * Creates an exception for a module name and a list of configuration objects. + * Creates an exception for an extension and a list of configuration objects. * - * @param $module - * The name of the module that is being installed. + * @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. + * configuration, keyed by config collection. * * @return \Drupal\Core\Config\PreExistingConfigException */ - public static function create($module, array $config_objects) { + 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(', ', $config_objects), '@extension' => $module) + array( + '@config_names' => implode(', ', static::flattenConfigObjects($config_objects)), + '@extension' => $extension + ) ); $e = new static($message); $e->configObjects = $config_objects; - $e->module = $module; + $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; + } + } diff -u b/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php --- b/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -151,15 +151,15 @@ ))); } - // Install profiles can not have config clashes as if they contain - // configuration with the same name as a module it will override it. + // 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[StorageInterface::DEFAULT_COLLECTION]); + throw PreExistingConfigException::create($module, $existing_configuration); } } diff -u b/core/modules/config/src/Tests/ConfigInstallWebTest.php b/core/modules/config/src/Tests/ConfigInstallWebTest.php --- b/core/modules/config/src/Tests/ConfigInstallWebTest.php +++ b/core/modules/config/src/Tests/ConfigInstallWebTest.php @@ -9,6 +9,8 @@ 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\Core\Config\FileStorage; @@ -31,7 +33,7 @@ protected function setUp() { parent::setUp(); - $this->adminUser = $this->drupalCreateUser(array('administer modules')); + $this->adminUser = $this->drupalCreateUser(array('administer modules', 'administer themes')); // Ensure the global variable being asserted by this test does not exist; // a previous test executed in this request/process might have set it. @@ -95,6 +97,8 @@ $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'); } @@ -175,45 +179,51 @@ $this->drupalLogin($this->adminUser); - // 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. + // Try to install config_install_fail_test and config_test. Doing this + // will install the config_test module first because it is a dependency of + // config_install_fail_test. // @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'))); + $this->assertRaw('Unable to install Configuration install fail test, config_test.dynamic.dotted.default already exists in active configuration.'); // 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. + // Try to install config_install_fail_test without selecting config_test. + // The user is shown a confirm form becuase 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(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'))); + $this->assertRaw('Unable to install Configuration install fail test, config_test.dynamic.dotted.default already exists in active configuration.'); - // Update the container so we get an environment with config_test installed. + // Test that collection configuration clashes are reported correctly. + \Drupal::service('module_installer')->install(['language']); $this->rebuildContainer(); - $config_entity = \Drupal::entityManager()->getStorage('config_test')->load('dotted.default'); + ConfigurableLanguage::createFromLangcode('fr')->save(); + \Drupal::languageManager() + ->getLanguageConfigOverride('fr', 'config_test.dynamic.dotted.default') + ->set('label', 'Je suis Charlie') + ->save(); - // 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. 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()); + $this->assertRaw('Unable to install Configuration install fail test, config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default already exist in active configuration.'); + + // Test installing a theme through UI that has existing configuration. + $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, config_test.dynamic.dotted.default, language/fr/config_test.dynamic.dotted.default already exist in active configuration.'); + + // Theme install through the API. + 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'); + } } } diff -u b/core/modules/config/src/Tests/ConfigOtherModuleTest.php b/core/modules/config/src/Tests/ConfigOtherModuleTest.php --- b/core/modules/config/src/Tests/ConfigOtherModuleTest.php +++ b/core/modules/config/src/Tests/ConfigOtherModuleTest.php @@ -8,6 +8,7 @@ namespace Drupal\config\Tests; use Drupal\Core\Config\PreExistingConfigException; +use Drupal\Core\Config\StorageInterface; use Drupal\simpletest\WebTestBase; /** @@ -67,7 +68,8 @@ } catch (PreExistingConfigException $e) { $this->pass($msg); - $this->assertTrue(strpos($e->getMessage(), 'config_test.dynamic.other_module_test provided by config_other_module_config_test') !== FALSE, 'The exception message contains "config_test.dynamic.other_module_test provided by config_other_module_config_test".'); + $this->assertEqual($e->getExtension(), 'config_other_module_config_test'); + $this->assertEqual($e->getConfigObjects(), [StorageInterface::DEFAULT_COLLECTION => ['config_test.dynamic.other_module_test']]); } } reverted: --- b/core/modules/config/tests/config_install_fail_dependency_test/config_install_fail_dependency_test.info.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: 'Configuration install fail dependency test' -type: module -package: Testing -version: VERSION -core: 8.x -dependencies: - - config_install_fail_test reverted: --- b/core/modules/system/src/Controller/ConfigClashController.php +++ /dev/null @@ -1,176 +0,0 @@ -keyValueExpirable = $key_value_expirable; - $this->configManager = $config_manager; - $this->configInstaller = $config_installer; - $this->urlGenerator = $url_generator; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('keyvalue.expirable')->get('module_list'), - $container->get('config.manager'), - $container->get('config.installer'), - $container->get('url_generator') - ); - } - - /** - * Creates a report for modules. - * - * @see \Drupal\system\Form\ModulesListForm::submitForm() - * - * @return array - * The report as a render array. - */ - public function moduleReport() { - $account = $this->currentUser()->id(); - $modules = $this->keyValueExpirable->get($account); - - // Redirect to the modules list page if the key value store is empty. - if (!$modules) { - return new RedirectResponse($this->urlGenerator->generate('system.modules_list', array(), TRUE)); - } - - $report = array( - 'description' => array( - '#prefix' => '

', - '#suffix' => '

', - '#markup' => $this->t('You must delete or rename the following configuration to install @module.', array('@module' => implode(', ', $modules['install']))) - ), - ) + $this->report('module', array_keys($modules['install'])); - - return $report; - } - - /** - * Generates a report on pre-existing configuration for a list of extensions. - * - * @param string $type - * The type of extension being installed. Either 'theme' or 'module'. - * @param array $extensions - * The list of extensions that are being installed. - * - * @return array - * The report as a render array. - */ - protected function report($type, array $extensions) { - // Check if we have any pre-existing configuration. - $existing_configuration = array(); - foreach ($extensions as $extension) { - $existing_configuration = array_merge_recursive($this->configInstaller->findPreExistingConfiguration($type, $extension), $existing_configuration); - } - - $report['config_clashes'] = array( - '#theme' => 'item_list', - '#items' => array(), - '#empty' => $this->t('No configuration clashes detected.'), - '#weight' => 10, - ); - - if (count($existing_configuration)) { - foreach ($existing_configuration as $collection => $config_names) { - foreach ($config_names as $config_name) { - $entity = $this->configManager->loadConfigEntityByName($config_name); - if ($entity) { - $text = $entity->getEntityType()->getLabel() . ': ' . $entity->label(); - if ($entity->hasLinkTemplate('edit-form')) { - $report['config_clashes']['#items'][] = array( - '#type' => 'link', - '#title' => $text, - ) + $entity->urlInfo('edit-form')->toRenderArray(); - } - else { - $report['config_clashes']['#items'][] = $text; - } - } - else { - // @todo - $report['config_clashes']['#items'][] = $config_name; - } - } - } - } - - return $report; - } - -} diff -u b/core/modules/system/src/Form/ModulesListConfirmForm.php b/core/modules/system/src/Form/ModulesListConfirmForm.php --- b/core/modules/system/src/Form/ModulesListConfirmForm.php +++ b/core/modules/system/src/Form/ModulesListConfirmForm.php @@ -8,7 +8,6 @@ namespace Drupal\system\Form; use Drupal\Core\Config\PreExistingConfigException; -use Drupal\Core\Config\ConfigInstallerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\ConfirmFormBase; @@ -51,13 +50,6 @@ protected $moduleInstaller; /** - * The configuration installer. - * - * @var \Drupal\Core\Config\ConfigInstallerInterface - */ - protected $configInstaller; - - /** * Constructs a ModulesListConfirmForm object. * * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler @@ -66,14 +58,11 @@ * The module installer. * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable * The key value expirable factory. - * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer - * The configuration installer. */ - public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ConfigInstallerInterface $config_installer) { + public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable) { $this->moduleHandler = $module_handler; $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; - $this->configInstaller = $config_installer; } /** @@ -83,8 +72,7 @@ return new static( $container->get('module_handler'), $container->get('module_installer'), - $container->get('keyvalue.expirable')->get('module_list'), - $container->get('config.installer') + $container->get('keyvalue.expirable')->get('module_list') ); } @@ -164,19 +152,6 @@ // Gets list of modules prior to install process. $before = $this->moduleHandler->getModuleList(); - // Check if we have any pre-existing configuration. - $existing_configuration = array(); - foreach (array_keys($this->modules['install']) as $module) { - $existing_configuration = array_merge_recursive($this->configInstaller->findPreExistingConfiguration('module', $module), $existing_configuration); - } - if (count($existing_configuration)) { - $account = $this->currentUser()->id(); - $this->keyValueExpirable->setWithExpire($account, $this->modules, 60); - // Redirect to the system config clash page. - $form_state->setRedirect('system.modules_config_clash'); - return; - } - // Install the given modules. if (!empty($this->modules['install'])) { // Don't catch the exception that this can throw for missing dependencies: @@ -187,13 +162,19 @@ $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()]) - )); + $config_objects = $e->flattenConfigObjects($e->getConfigObjects()); + drupal_set_message( + $this->formatPlural( + count($config_objects), + 'Unable to install @extension, %config_names already exists in active configuration.', + 'Unable to install @extension, %config_names already exist in active configuration.', + array( + '%config_names' => implode(', ', $config_objects), + '@extension' => $this->modules['install'][$e->getExtension()] + )), + 'error' + ); + return; } } diff -u b/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php --- b/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -9,7 +9,6 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; -use Drupal\Core\Config\ConfigInstallerInterface; use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Controller\TitleResolverInterface; use Drupal\Core\Access\AccessManagerInterface; @@ -103,13 +102,6 @@ protected $moduleInstaller; /** - * The configuration installer. - * - * @var \Drupal\Core\Config\ConfigInstallerInterface - */ - protected $configInstaller; - - /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { @@ -123,8 +115,7 @@ $container->get('current_route_match'), $container->get('title_resolver'), $container->get('router.route_provider'), - $container->get('plugin.manager.menu.link'), - $container->get('config.installer') + $container->get('plugin.manager.menu.link') ); } @@ -151,10 +142,8 @@ * The route provider. * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager * The menu link manager. - * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer - * The configuration installer. */ - public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, RouteProviderInterface $route_provider, MenuLinkManagerInterface $menu_link_manager, ConfigInstallerInterface $config_installer) { + public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, RouteProviderInterface $route_provider, MenuLinkManagerInterface $menu_link_manager) { $this->moduleHandler = $module_handler; $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; @@ -165,7 +154,6 @@ $this->titleResolver = $title_resolver; $this->routeProvider = $route_provider; $this->menuLinkManager = $menu_link_manager; - $this->configInstaller = $config_installer; } /** @@ -509,19 +497,6 @@ // Retrieve a list of modules to install and their dependencies. $modules = $this->buildModuleList($form_state); - // Check if we have any pre-existing configuration. - $existing_configuration = array(); - foreach (array_keys($modules['install']) as $module) { - $existing_configuration = array_merge_recursive($this->configInstaller->findPreExistingConfiguration('module', $module), $existing_configuration); - } - if (count($existing_configuration)) { - $account = $this->currentUser()->id(); - $this->keyValueExpirable->setWithExpire($account, $modules, 60); - // Redirect to the system config clash page. - $form_state->setRedirect('system.modules_config_clash'); - return; - } - // Check if we have to install any dependencies. If there is one or more // dependencies that are not installed yet, redirect to the confirmation // form. @@ -547,13 +522,19 @@ $this->moduleInstaller->install(array_keys($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' => $modules['install'][$e->getModule()]) - )); + $config_objects = $e->flattenConfigObjects($e->getConfigObjects()); + drupal_set_message( + $this->formatPlural( + count($config_objects), + 'Unable to install @extension, %config_names already exists in active configuration.', + 'Unable to install @extension, %config_names already exist in active configuration.', + array( + '%config_names' => implode(', ', $config_objects), + '@extension' => $modules['install'][$e->getExtension()] + )), + 'error' + ); + return; } } diff -u b/core/modules/system/system.module b/core/modules/system/system.module --- b/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -146,8 +146,6 @@ case 'system.status': return '

' . t("Here you can find a short overview of your site's parameters as well as any problems detected with your installation. It may be useful to copy and paste this information into support requests filed on drupal.org's support forums and project issue queues. Before filing a support request, ensure that your web server meets the system requirements.", array('@system-requirements' => 'http://drupal.org/requirements')) . '

'; - case 'system.modules_config_clash': - return '

' . t('Here you can find a list of configuration that already exists. You need to either rename it or delete it before installing modules which have configuration with the same name.') . '

'; } } reverted: --- b/core/modules/system/system.routing.yml +++ a/core/modules/system/system.routing.yml @@ -285,14 +285,6 @@ _permission: 'administer themes' _csrf_token: 'TRUE' -system.modules_config_clash: - path: 'admin/modules/config-clash' - defaults: - _controller: 'Drupal\system\Controller\ConfigClashController::moduleReport' - _title: 'Configuration clash' - requirements: - _permission: 'administer modules' - system.status: path: '/admin/reports/status' defaults: only in patch2: unchanged: --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -13,6 +13,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigInstallerInterface; use Drupal\Core\Config\ConfigManagerInterface; +use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Routing\RouteBuilderIndicatorInterface; use Drupal\Core\State\StateInterface; use Psr\Log\LoggerInterface; @@ -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. $extension_config ->set("theme.$key", 0) only in patch2: unchanged: --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -7,6 +7,8 @@ namespace Drupal\config\Tests; +use Drupal\Core\Config\PreExistingConfigException; +use Drupal\Core\Config\StorageInterface; use Drupal\simpletest\KernelTestBase; /** @@ -115,6 +117,21 @@ public function testCollectionInstallationCollections() { $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 // available default configuration in a particular collection for enabled // extensions. only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_clash_test_theme/config/install/config_test.dynamic.dotted.default.yml @@ -0,0 +1,7 @@ +# Clashes with default configuration provided by the config_test module. +id: dotted.default +label: 'Config install fail' +weight: 0 +protected_property: Default +# Intentionally commented out to verify default status behavior. +# status: 1 only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_clash_test_theme/config/install/language/fr/config_test.dynamic.dotted.default.yml @@ -0,0 +1,2 @@ +# Clashes with default configuration provided by the config_test module. +label: 'Je suis' only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_clash_test_theme/config_clash_test_theme.info.yml @@ -0,0 +1,11 @@ +# +name: 'Test theme for configuration clash detection' +type: theme +description: 'Test theme for configuration clash detection' +version: VERSION +base theme: classy +core: 8.x +regions: + content: Content + left: Left + right: Right only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_collection_clash_install_test/config/install/another_collection/config_collection_install_test.test.yml @@ -0,0 +1 @@ +collection: another_collection \ No newline at end of file only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_collection_clash_install_test/config/install/collection/test1/config_collection_install_test.test.yml @@ -0,0 +1 @@ +collection: collection.test1 \ No newline at end of file only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_collection_clash_install_test/config/install/collection/test2/config_collection_install_test.test.yml @@ -0,0 +1 @@ +collection: collection.test2 \ No newline at end of file only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_collection_clash_install_test/config/install/entity/config_test.dynamic.dotted.default.yml @@ -0,0 +1 @@ +label: entity only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_collection_clash_install_test/config_collection_clash_install_test.info.yml @@ -0,0 +1,9 @@ +# This should contain a copy of the configuration from the +# config_collection_install_test module. +name: 'Config collection clash test module' +type: module +package: Testing +version: VERSION +core: 8.x +dependencies: + - config_collection_install_test only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/tests/config_install_fail_test/config/install/language/fr/config_test.dynamic.dotted.default.yml @@ -0,0 +1,2 @@ +# Clashes with default configuration provided by the config_test module. +label: 'Je suis' only in patch2: unchanged: --- a/core/modules/system/src/Controller/ThemeController.php +++ b/core/modules/system/src/Controller/ThemeController.php @@ -7,6 +7,7 @@ namespace Drupal\system\Controller; +use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Routing\RouteBuilderIndicatorInterface; @@ -115,12 +116,28 @@ public function install(Request $request) { $theme = $request->get('theme'); if (isset($theme)) { - if ($this->themeHandler->install(array($theme))) { - $themes = $this->themeHandler->listInfo(); - drupal_set_message($this->t('The %theme theme has been installed.', array('%theme' => $themes[$theme]->info['name']))); + try { + if ($this->themeHandler->install(array($theme))) { + $themes = $this->themeHandler->listInfo(); + drupal_set_message($this->t('The %theme theme has been installed.', array('%theme' => $themes[$theme]->info['name']))); + } + else { + drupal_set_message($this->t('The %theme theme was not found.', array('%theme' => $theme)), 'error'); + } } - else { - drupal_set_message($this->t('The %theme theme was not found.', array('%theme' => $theme)), 'error'); + catch (PreExistingConfigException $e) { + $config_objects = $e->flattenConfigObjects($e->getConfigObjects()); + drupal_set_message( + $this->formatPlural( + count($config_objects), + 'Unable to install @extension, %config_names already exists in active configuration.', + 'Unable to install @extension, %config_names already exist in active configuration.', + array( + '%config_names' => implode(', ', $config_objects), + '@extension' => $theme, + )), + 'error' + ); } return $this->redirect('system.themes_page');