diff -u b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php --- b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php @@ -120,33 +120,52 @@ } } - // Get a list of installed profiles. - $installed_profiles = \Drupal::service('profile_handler')->getProfiles(); + // Get a list of parent profiles and the main profile. + /* @var $profiles \Drupal\Core\Extension\Extension[] */ + $profiles = \Drupal::service('profile_handler')->getProfiles(); + /* @var $main_profile \Drupal\Core\Extension\Extension */ + $main_profile = end($profiles); // Ensure that all modules being uninstalled are not required by modules // that will be installed after the import. $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) && (!array_key_exists($dependent_module, $installed_profiles))) { - $module_name = $module_data[$module]->info['name']; - $dependent_module_name = $module_data[$dependent_module]->info['name']; - $config_importer->logError($this->t('Unable to uninstall the %module module since the %dependent_module module is installed.', ['%module' => $module_name, '%dependent_module' => $dependent_module_name])); + if ($module_data[$dependent_module]->status && !in_array($dependent_module, $uninstalls, TRUE)) { + if (!array_key_exists($dependent_module, $profiles)) { + $module_name = $module_data[$module]->info['name']; + $dependent_module_name = $module_data[$dependent_module]->info['name']; + $config_importer->logError($this->t('Unable to uninstall the %module module since the %dependent_module module is installed.', [ + '%module' => $module_name, + '%dependent_module' => $dependent_module_name + ])); + } } } } - // Ensure that none of the installed profiles are being uninstalled. - if ($profile_uninstalls = array_intersect_key($installed_profiles, array_flip($uninstalls))) { + if (in_array($main_profile->getName(), $uninstalls, TRUE)) { + // Ensure that the active profile is not being uninstalled. + $profile_name = $main_profile->info['name']; + $config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the main install profile.', ['%profile' => $profile_name])); + } + + $profile_names = []; + if ($profile_uninstalls = array_intersect_key($profiles, array_flip($uninstalls))) { + // Ensure that none of the parent profiles are being uninstalled. foreach ($profile_uninstalls as $profile) { - $profile_names[] = $profile->info['name']; + if ($profile->getName() !== $main_profile->getName()) { + $profile_names[] = $module_data[$profile->getName()]->info['name']; + } + } + if (!empty($profile_names)) { + $message = $this->formatPlural(count($profile_names), + 'Unable to uninstall the :profile profile since it is a parent of another installed profile.', + 'Unable to uninstall the :profile profiles since they are parents of another installed profile.', + [':profile' => implode(', ', $profile_names)] + ); + $config_importer->logError($message); } - $message = $this->formatPlural(count($profile_names), - 'Unable to uninstall the %profile profile since it is an installed profile.', - 'Unable to uninstall the %profile profiles since they are installed profiles.', - ['%profile' => implode(', ', $profile_names)] - ); - $config_importer->logError($message); } } diff -u b/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php b/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php --- b/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php +++ b/core/modules/config/src/Tests/ConfigImportInstallProfileTest.php @@ -56,7 +56,7 @@ $this->drupalPostForm('admin/config/development/configuration', [], t('Import all')); $this->assertText('The configuration cannot be imported because it failed validation for the following reasons:'); - $this->assertText('Unable to uninstall the Testing config import profile since it is an installed profile.'); + $this->assertText('Unable to uninstall the Testing config import profile since it is the main install profile.'); // Uninstall dependencies of testing_config_import. $core['module']['testing_config_import'] = 0; diff -u b/core/profiles/testing_inherited/testing_inherited.info.yml b/core/profiles/testing_inherited/testing_inherited.info.yml --- b/core/profiles/testing_inherited/testing_inherited.info.yml +++ b/core/profiles/testing_inherited/testing_inherited.info.yml @@ -15,6 +15,7 @@ dependencies: - block - config + - syslog themes: - stable only in patch2: unchanged: --- /dev/null +++ b/core/modules/config/src/Tests/ConfigImportBaseInstallProfileTest.php @@ -0,0 +1,96 @@ +webUser = $this->drupalCreateUser(['synchronize configuration']); + $this->drupalLogin($this->webUser); + $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync')); + } + + /** + * Tests config importer cannot uninstall parent install profiles and + * dependencies of parent profiles can be uninstalled. + * + * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber + */ + public function testInstallParentProfileValidation() { + $sync = $this->container->get('config.storage.sync'); + $this->copyConfig($this->container->get('config.storage'), $sync); + $core = $sync->read('core.extension'); + + // Ensure that parent profile can not be uninstalled. + unset($core['module']['testing']); + $sync->write('core.extension', $core); + + $this->drupalPostForm('admin/config/development/configuration', [], t('Import all')); + $this->assertText('The configuration cannot be imported because it failed validation for the following reasons:'); + $this->assertText('Unable to uninstall the Testing profile since it is a parent of another installed profile.'); + + // Uninstall dependencies of parent profile. + $core['module']['testing'] = 0; + unset($core['module']['dynamic_page_cache']); + $sync->write('core.extension', $core); + $sync->deleteAll('dynamic_page_cache.'); + $this->drupalPostForm('admin/config/development/configuration', [], t('Import all')); + $this->assertText('The configuration was imported successfully.'); + $this->rebuildContainer(); + $this->assertFalse(\Drupal::moduleHandler()->moduleExists('dynamic_page_cache'), 'The dynamic_page_cache module has been uninstalled.'); + } + + /** + * Tests config importer cannot uninstall sub-profiles and dependencies of + * sub-profiles can be uninstalled. + * + * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber + */ + public function testInstallSubProfileValidation() { + $sync = $this->container->get('config.storage.sync'); + $this->copyConfig($this->container->get('config.storage'), $sync); + $core = $sync->read('core.extension'); + + // Ensure install sub-profiles can not be uninstalled. + unset($core['module']['testing_inherited']); + $sync->write('core.extension', $core); + + $this->drupalPostForm('admin/config/development/configuration', [], t('Import all')); + $this->assertText('The configuration cannot be imported because it failed validation for the following reasons:'); + $this->assertText('Unable to uninstall the Testing Inherited profile since it is the main install profile.'); + + // Uninstall dependencies of main profile. + $core['module']['testing_inherited'] = 0; + unset($core['module']['syslog']); + $sync->write('core.extension', $core); + $sync->deleteAll('syslog.'); + $this->drupalPostForm('admin/config/development/configuration', [], t('Import all')); + $this->assertText('The configuration was imported successfully.'); + $this->rebuildContainer(); + $this->assertFalse(\Drupal::moduleHandler()->moduleExists('syslog'), 'The syslog module has been uninstalled.'); + } + +}