diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php index abd6d6d..132559c 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php @@ -58,27 +58,61 @@ public function onConfigImporterValidate(ConfigImporterEvent $event) { } $config_importer = $event->getConfigImporter(); if ($config_importer->getStorageComparer()->getSourceStorage()->exists('core.extension')) { - // Modules are already validated in the ConfigImporter. - // @see \Drupal\Core\Config\ConfigImporter::createExtensionChangelist() - $this->validateThemeInstalls($config_importer); + $this->validateModules($config_importer); + $this->validateThemes($config_importer); $this->validateDependencies($config_importer); } } /** - * Validates all themes being installed during a configuration import exist. + * Validates module installations and uninstallations. * * @param \Drupal\Core\Config\ConfigImporter $config_importer * The configuration importer. */ - protected function validateThemeInstalls(ConfigImporter $config_importer) { + protected function validateModules($config_importer) { + $core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension'); + // Get a list of modules with dependency weights as values. + $module_data = system_rebuild_module_data(); + $nonexistent_modules = array_diff(array_keys($core_extension['module']), array_keys($module_data)); + foreach ($nonexistent_modules as $module) { + $config_importer->logError($this->t('Unable to install module %module since it does not exist.', array('%module' => $module))); + } + + // Ensure that module dependencies are respected. + $uninstalls = $config_importer->getExtensionChangelist('module', 'uninstall'); + foreach ($uninstalls as $module) { + foreach (array_keys($module_data[$module]->required_by) as $dependent_module) { + if ($module_data[$dependent_module]->status && !in_array($dependent_module, $uninstalls, TRUE)) { + $config_importer->logError($this->t('Unable to uninstall module %module since %dependent_module is installed.', array('%module' => $module, '%dependent_module' => $dependent_module))); + } + } + } + } + + /** + * Validates theme installations and uninstallations. + * + * @param \Drupal\Core\Config\ConfigImporter $config_importer + * The configuration importer. + */ + protected function validateThemes(ConfigImporter $config_importer) { // Get all themes including those that are not installed. - $themes = $this->themeHandler->rebuildThemeData(); + $theme_data = $this->themeHandler->rebuildThemeData(); foreach ($config_importer->getExtensionChangelist('theme', 'install') as $theme) { - if (!isset($themes[$theme])) { + if (!isset($theme_data[$theme])) { $config_importer->logError($this->t('Unable to install theme %theme since it does not exist.', array('%theme' => $theme))); } } + + $uninstalls = $config_importer->getExtensionChangelist('theme', 'uninstall'); + foreach ($uninstalls as $theme) { + foreach (array_keys($theme_data[$theme]->required_by) as $dependent_theme) { + if ($theme_data[$dependent_theme]->status && !in_array($dependent_theme, $uninstalls, TRUE)) { + $config_importer->logError($this->t('Unable to uninstall theme %theme since %dependent_theme is installed.', array('%theme' => $theme, '%dependent_theme' => $dependent_theme))); + } + } + } } /** diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php index b97218b..7624f0f 100644 --- a/core/modules/config/src/Form/ConfigSync.php +++ b/core/modules/config/src/Form/ConfigSync.php @@ -340,7 +340,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } catch (ConfigImporterException $e) { // There are validation errors. - drupal_set_message($this->t('The configuration synchronization failed validation.')); + drupal_set_message($this->t('The configuration synchronization failed validation.'), 'error'); foreach ($config_importer->getErrors() as $message) { drupal_set_message($message, 'error'); } diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php index ba44925..ed0558b 100644 --- a/core/modules/config/src/Tests/ConfigImportUITest.php +++ b/core/modules/config/src/Tests/ConfigImportUITest.php @@ -453,4 +453,35 @@ public function testEntityBundleDelete() { $this->assertNoText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id()))); } + /** + * Tests config importer cannot uninstall extensions which are depended on. + * + * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber + */ + public function testExtensionValidation() { + \Drupal::service('module_installer')->install(['node']); + \Drupal::service('theme_handler')->install(['bartik']); + $this->rebuildContainer(); + + $staging = $this->container->get('config.storage.staging'); + $this->copyConfig($this->container->get('config.storage'), $staging); + $core = $staging->read('core.extension'); + // Node depends on text. + unset($core['module']['text']); + // Bartik depends on classy. + unset($core['theme']['classy']); + // This module does not exist. + $core['module']['does_not_exist'] = 0; + // This theme does not exist. + $core['theme']['does_not_exist'] = 0; + $staging->write('core.extension', $core); + + $this->drupalPostForm('admin/config/development/configuration', array(), t('Import all')); + $this->assertText('The configuration synchronization failed validation.'); + $this->assertText('Unable to uninstall module text since node is installed.'); + $this->assertText('Unable to uninstall theme classy since bartik is installed.'); + $this->assertText('Unable to install module does_not_exist since it does not exist.'); + $this->assertText('Unable to install theme does_not_exist since it does not exist.'); + } + }