diff --git a/core/core.services.yml b/core/core.services.yml index 2b27f69..c843de8 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -289,7 +289,14 @@ services: arguments: ['@app.root', '%container.modules%', '@cache.bootstrap'] module_installer: class: Drupal\Core\Extension\ModuleInstaller + tags: + - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator } arguments: ['@app.root', '@module_handler', '@kernel'] + content_uninstall_validator: + class: Drupal\Core\Entity\ContentUninstallValidator + tags: + - { name: module_install.uninstall_validator, priority: -254 } + arguments: ['@entity.manager', '@entity.query'] 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 new file mode 100644 index 0000000..2c275cc --- /dev/null +++ b/core/lib/Drupal/Core/Entity/ContentUninstallValidator.php @@ -0,0 +1,43 @@ +entityManager = $entity_manager; + $this->queryFactory = $entity_query; + } + /** + * {@inheritdoc} + */ + public function validate($module) { + $entities = $this->entityManager->getDefinitions(); + foreach ($entities as $entity_type) { + if ($module == $entity_type->getProvider() && $this->entityManager->getStorage($entity_type->id())->hasData()) { + return FALSE; + } + } + return TRUE; + } + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index 20a6506..daae5e6 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -11,6 +11,7 @@ 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. @@ -42,6 +43,20 @@ class ModuleInstaller implements ModuleInstallerInterface { protected $root; /** + * The uninstall validators. + * + * @var \Drupal\Core\Extension\UninstallValidatorInterface[] + */ + protected $uninstallValidators; + + /** + * Modules that can be uninstalled. + * + * @var array + */ + protected $uninstallValidated; + + /** * Constructs a new ModuleInstaller instance. * * @param string $root @@ -58,6 +73,14 @@ public function __construct($root, ModuleHandlerInterface $module_handler, Drupa $this->root = $root; $this->moduleHandler = $module_handler; $this->kernel = $kernel; + $this->uninstallValidated = array(); + } + + /** + * {@inheritdoc} + */ + public function addUninstallValidator(UninstallValidatorInterface $uninstall_validator) { + $this->uninstallValidators[] = $uninstall_validator; } /** @@ -271,6 +294,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) { * {@inheritdoc} */ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { + $module_list = $this->validateUninstall($module_list); // Get all module data so we can find dependencies and sort. $module_data = system_rebuild_module_data(); $module_list = $module_list ? array_combine($module_list, $module_list) : array(); @@ -469,4 +493,25 @@ protected function updateKernel($module_filenames) { $this->moduleHandler = $container->get('module_handler'); } + /** + * {@inheritdoc} + */ + public function validateUninstall(array $module_list) { + foreach ($module_list as $key => $module) { + if (in_array($module, $this->uninstallValidated)) { + continue; + } + foreach ($this->uninstallValidators as $validator) { + if ($validator->validate($module)) { + $this->uninstallValidated[] = $module; + } + else { + unset($module_list[$key]); + } + } + } + return $module_list; + } + + } diff --git a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php index 246af4b..0d29dce 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Extension; +use Drupal\Core\Extension\UninstallValidatorInterface; + /** * Provides the installation of modules with creating the db schema and more. */ @@ -58,5 +60,22 @@ public function install(array $module_list, $enable_dependencies = TRUE); */ public function uninstall(array $module_list, $uninstall_dependents = TRUE); + /** + * Adds module uninstall validator services. + * + * @param \Drupal\Core\Extension\UninstallValidatorInterface $uninstall_validator + * The uninstall validation service to add. It is added at the end of the + * priority list (lower priority relative to existing ones). + */ + public function addUninstallValidator(UninstallValidatorInterface $uninstall_validator); + + /** + * Determines which modules can be uninstalled. + * + * @param array $module_list + * The modules to uninstall. + */ + public function validateUninstall(array $module_list); + } diff --git a/core/lib/Drupal/Core/Extension/UninstallValidatorInterface.php b/core/lib/Drupal/Core/Extension/UninstallValidatorInterface.php new file mode 100644 index 0000000..aa3a403 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/UninstallValidatorInterface.php @@ -0,0 +1,24 @@ +delete(); } + // Delete any shortcuts so the shortcut module can be uninstalled. + $shortcuts = Shortcut::loadMultiple(); + entity_delete_multiple('shortcut', array_keys($shortcuts)); + system_list_reset(); $all_modules = system_rebuild_module_data(); diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index 4285956..bc6fcf0 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -8,6 +8,7 @@ namespace Drupal\system\Form; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Extension\ModuleInstallerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface; @@ -26,6 +27,13 @@ class ModulesUninstallForm extends FormBase { protected $moduleHandler; /** + * The module installer service. + * + * @var \Drupal\Core\Extension\ModuleInstallerInterface + */ + protected $moduleInstaller; + + /** * The expirable key value store. * * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface @@ -38,6 +46,7 @@ class ModulesUninstallForm extends FormBase { public static function create(ContainerInterface $container) { return new static( $container->get('module_handler'), + $container->get('module_installer'), $container->get('keyvalue.expirable')->get('modules_uninstall') ); } @@ -47,11 +56,14 @@ public static function create(ContainerInterface $container) { * * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer + * The module installer. * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable * The key value expirable factory. */ - public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable) { + 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; } @@ -71,8 +83,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Get a list of disabled, installed modules. $modules = system_rebuild_module_data(); - $uninstallable = array_filter($modules, function ($module) use ($modules) { - return empty($modules[$module->getName()]->info['required']) && drupal_get_installed_schema_version($module->getName()) > SCHEMA_UNINSTALLED; + $modules_validated = $this->moduleInstaller->validateUninstall(array_keys($modules)); + $uninstallable = array_filter($modules, function ($module) use ($modules, $modules_validated) { + return empty($modules[$module->getName()]->info['required']) && drupal_get_installed_schema_version($module->getName()) > SCHEMA_UNINSTALLED && in_array($module->getName(), $modules_validated); }); // Include system.admin.inc so we can use the sort callbacks.