diff --git a/core/core.services.yml b/core/core.services.yml index 58270f4..98ddc9e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -296,7 +296,7 @@ services: class: Drupal\Core\Entity\ContentUninstallValidator tags: - { name: module_install.uninstall_validator } - arguments: ['@entity.manager'] + arguments: ['@entity.manager', '@string_translation'] theme_handler: class: Drupal\Core\Extension\ThemeHandler arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder_indicator'] diff --git a/core/lib/Drupal/Core/Entity/ContentUninstallValidator.php b/core/lib/Drupal/Core/Entity/ContentUninstallValidator.php index 359f158..9801b2a 100644 --- a/core/lib/Drupal/Core/Entity/ContentUninstallValidator.php +++ b/core/lib/Drupal/Core/Entity/ContentUninstallValidator.php @@ -7,8 +7,8 @@ namespace Drupal\Core\Entity; use Drupal\Core\Extension\UninstallValidatorInterface; -use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationInterface; /** * Validates module uninstall readiness based on existing content entities. @@ -22,8 +22,9 @@ class ContentUninstallValidator implements UninstallValidatorInterface { * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) { $this->entityManager = $entity_manager; + $this->stringTranslation = $string_translation; } /** diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index 9149ce9..c6d4d14 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -11,7 +11,6 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\DrupalKernelInterface; -use Drupal\Core\Extension\UninstallValidatorInterface; /** * Default implementation of the module installer. @@ -323,8 +322,10 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Use the validators and print the reason if ($reasons = $this->validateUninstall($module_list)) { - foreach ($reasons as $reason) { - drupal_set_message($reason, 'error'); + foreach ($reasons as $module_reason) { + foreach($module_reason as $reason) { + drupal_set_message($reason, 'error'); + } } // The validation failed. return FALSE; @@ -338,7 +339,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { asort($module_list); $module_list = array_keys($module_list); - // Only process modules that are enabled. A module is only disabled if it is + // Only process modules that are enabled. A module is only enabled if it is // configured as enabled. Custom or overridden module handlers might contain // the module already, which means that it might be loaded, but not // necessarily installed. @@ -497,18 +498,18 @@ protected function updateKernel($module_filenames) { */ public function validateUninstall(array $module_list) { $reasons = array(); - foreach ($module_list as $key => $module) { + foreach ($module_list as $module) { foreach ($this->uninstallValidators as $validator) { $validation_reasons = $validator->validate($module); if ($validation_reasons !== NULL) { - $reasons = array_merge($reasons, $validation_reasons); + if (!isset($reasons[$module])) { + $reasons[$module] = array(); + } + $reasons[$module] = array_merge($reasons[$module], $validation_reasons); } } } - if ($reasons) { - return $reasons; - } - return NULL; + return $reasons; } } diff --git a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php index a64a9f5..6b135ec 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php @@ -7,8 +7,6 @@ namespace Drupal\Core\Extension; -use Drupal\Core\Extension\UninstallValidatorInterface; - /** * Provides the installation of modules with creating the db schema and more. */ diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index e0a728a..871951e 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -122,6 +122,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Sort all modules by their name. uasort($uninstallable, 'system_sort_modules_by_info_name'); + $validation_reasons = $this->moduleInstaller->validateUninstall(array_keys($uninstallable)); $form['uninstall'] = array('#tree' => TRUE); foreach ($uninstallable as $module_key => $module) { @@ -138,8 +139,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { // If a validator returns reasons not to uninstall a module, // list the reasons and disable the check box. - if ($reasons = $this->moduleInstaller->validateUninstall(array($module_key))) { - $form['modules'][$module->getName()]['#validation_reasons'] = $reasons; + if (isset($validation_reasons[$module_key])) { + $form['modules'][$module->getName()]['#validation_reasons'] = $validation_reasons[$module_key]; $form['uninstall'][$module->getName()]['#disabled'] = TRUE; } // All modules which depend on this one must be uninstalled first, before diff --git a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php b/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php index 7aca3c0..709fe79 100644 --- a/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php +++ b/core/modules/system/src/Tests/Extension/ModuleHandlerTest.php @@ -216,7 +216,8 @@ function testUninstallContentDependency() { } // Create a fake dependency. - // entity_test will depend on help. + // entity_test will depend on help. This way help can not be uninstalled + // when there is test content preventing entity_test from being uninstalled. \Drupal::state()->set('module_test.dependency', 'dependency'); drupal_static_reset('system_rebuild_module_data'); @@ -224,21 +225,20 @@ function testUninstallContentDependency() { $entity = entity_create('entity_test', array('name' => $this->randomString())); $entity->save(); + // Uninstalling entity_test is not possible when there is content. $result = $this->moduleInstaller()->uninstall(array('entity_test')); $this->assertFalse($result, 'ModuleHandler::uninstall() returns FALSE.'); - // uninstalling help needs entity_test to be un-installable. + // Uninstalling help needs entity_test to be un-installable. $result = $this->moduleInstaller()->uninstall(array('help')); $this->assertFalse($result, 'ModuleHandler::uninstall() returns FALSE.'); // Deleting the entity. $entity->delete(); - $result = $this->moduleInstaller()->uninstall(array('entity_test')); - $this->assertTrue($result, 'ModuleHandler::uninstall() returns TRUE.'); - $result = $this->moduleInstaller()->uninstall(array('help')); $this->assertTrue($result, 'ModuleHandler::uninstall() returns TRUE.'); + $this->assertEqual(drupal_get_installed_schema_version('entity_test'), SCHEMA_UNINSTALLED, "entity_test module was uninstalled."); } /** diff --git a/core/modules/system/src/Tests/Module/UninstallTest.php b/core/modules/system/src/Tests/Module/UninstallTest.php index f48a8aa..fc24051 100644 --- a/core/modules/system/src/Tests/Module/UninstallTest.php +++ b/core/modules/system/src/Tests/Module/UninstallTest.php @@ -43,9 +43,22 @@ function testUserPermsUninstalled() { function testUninstallPage() { $account = $this->drupalCreateUser(array('administer modules')); $this->drupalLogin($account); + + // Create a node type. + $node_type = entity_create('node_type', array('type' => 'uninstall_blocker')); + $node_type->save(); + // Add a node to prevent node from being uninstalled + $node = entity_create('node', array('type' => 'uninstall_blocker')); + $node->save(); + $this->drupalGet('admin/modules/uninstall'); $this->assertTitle(t('Uninstall') . ' | Drupal'); + $this->assertText(\Drupal::translation()->translate('The following reasons prevents Node from being uninstalled: There is content for the entity type: Content'), 'Content prevents uninstalling node module.'); + // Delete the node to allow node to be uninstalled. + $node->delete(); + $node_type->delete(); + // Uninstall module_test. $edit = array(); $edit['uninstall[module_test]'] = TRUE; diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index cbb5fe8..197767f 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -303,20 +303,16 @@ function theme_system_modules_uninstall($variables) { // Display table. $rows = array(); foreach (Element::children($form['modules']) as $module) { + $disabled_message = ''; + // Add the modules requiring the module in question as a validation reason. if (!empty($form['modules'][$module]['#required_by'])) { - $disabled_message = format_plural(count($form['modules'][$module]['#required_by']), - 'To uninstall @module, the following module must be uninstalled first: @required_modules', - 'To uninstall @module, the following modules must be uninstalled first: @required_modules', - array('@module' => $form['modules'][$module]['#module_name'], '@required_modules' => implode(', ', $form['modules'][$module]['#required_by']))); - } - else { - $disabled_message = ''; + $form['modules'][$module]['#validation_reasons'][] = \Drupal::translation()->translate('Required by: @modules', array('@modules' => implode(', ',$form['modules'][$module]['#required_by']))); } if (!empty($form['modules'][$module]['#validation_reasons'])) { - $disabled_message = format_plural(count($form['modules'][$module]['#validation_reasons']), + $disabled_message = \Drupal::translation()->formatPlural(count($form['modules'][$module]['#validation_reasons']), 'The following reason prevents @module from being uninstalled: @reasons', 'The following reasons prevents @module from being uninstalled: @reasons', - array('@module' => $form['modules'][$module]['#module_name'], '@reasons' => implode(', ', $form['modules'][$module]['#validation_reasons']))); + array('@module' => $form['modules'][$module]['#module_name'], '@reasons' => implode('; ', $form['modules'][$module]['#validation_reasons']))); } $rows[] = array( array('data' => drupal_render($form['uninstall'][$module]), 'align' => 'center'),