diff --git a/core/core.services.yml b/core/core.services.yml index 55d5b34..1a69e5a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -148,6 +148,9 @@ services: arguments: [ '%container.namespaces%' ] tags: - { name: persist } + module_installer: + class: Drupal\Core\Extension\ModuleInstaller + arguments: ['@module_handler'] plugin.manager.entity: class: Drupal\Core\Entity\EntityManager arguments: ['@container.namespaces', '@service_container'] diff --git a/core/includes/module.inc b/core/includes/module.inc index 58b8829..4e3f94f 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -206,30 +206,30 @@ function module_load_include($type, $module, $name = NULL) { * Enables or installs a given list of modules. * * @deprecated as of Drupal 8.0. Use - * Drupal::moduleHandler()->enable($module_list, $enable_dependencies = TRUE). + * Drupal::moduleInstaller()->enable($module_list, $enable_dependencies = TRUE). */ function module_enable($module_list, $enable_dependencies = TRUE) { - return Drupal::moduleHandler()->enable($module_list, $enable_dependencies); + return Drupal::moduleInstaller()->enable($module_list, $enable_dependencies); } /** * Disables a given set of modules. * * @deprecated as of Drupal 8.0. Use - * Drupal::moduleHandler()->disable($module_list, $disable_dependents = TRUE). + * Drupal::moduleInstaller()->disable($module_list, $disable_dependents = TRUE). */ function module_disable($module_list, $disable_dependents = TRUE) { - Drupal::moduleHandler()->disable($module_list, $disable_dependents); + Drupal::moduleInstaller()->disable($module_list, $disable_dependents); } /** * Uninstalls a given list of disabled modules. * * @deprecated as of Drupal 8.0. Use - * Drupal::moduleHandler()->uninstall($module_list, $uninstall_dependents = TRUE). + * Drupal::moduleInstaller()->uninstall($module_list, $uninstall_dependents = TRUE). */ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) { - return Drupal::moduleHandler()->uninstall($module_list, $uninstall_dependents); + return Drupal::moduleInstaller()->uninstall($module_list, $uninstall_dependents); } /** diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index d8a68ba..e47bcfd 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -331,13 +331,22 @@ public static function flood() { /** * Returns the module handler. * - * @return \Drupal\Core\Extension\ModuleHandler + * @return \Drupal\Core\Extension\ModuleHandlerInterface */ public static function moduleHandler() { return static::$container->get('module_handler'); } /** + * Returns the module installer. + * + * @return \Drupal\Core\Extension\ModuleInstallerInterface + */ + public static function moduleInstaller() { + return static::$container->get('module_installer'); + } + + /** * Returns the typed data manager service. * * Use the typed data manager service for creating typed data objects. diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 3a5ffbe..46ac6b1 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -8,11 +8,8 @@ namespace Drupal\Core\Extension; use Drupal\Component\Graph\Graph; -use Symfony\Component\Yaml\Parser; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\KeyValueStore\KeyValueStoreInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Class that manages enabled modules in a Drupal installation. @@ -526,368 +523,4 @@ protected function parseDependency($dependency) { return $value; } - /** - * {@inheritdoc} - */ - public function enable($module_list, $enable_dependencies = TRUE) { - if ($enable_dependencies) { - // Get all module data so we can find dependencies and sort. - $module_data = system_rebuild_module_data(); - // Create an associative array with weights as values. - $module_list = array_flip(array_values($module_list)); - - while (list($module) = each($module_list)) { - if (!isset($module_data[$module])) { - // This module is not found in the filesystem, abort. - return FALSE; - } - if ($module_data[$module]->status) { - // Skip already enabled modules. - unset($module_list[$module]); - continue; - } - $module_list[$module] = $module_data[$module]->sort; - - // Add dependencies to the list, with a placeholder weight. - // The new modules will be processed as the while loop continues. - foreach (array_keys($module_data[$module]->requires) as $dependency) { - if (!isset($module_list[$dependency])) { - $module_list[$dependency] = 0; - } - } - } - - if (!$module_list) { - // Nothing to do. All modules already enabled. - return TRUE; - } - - // Sort the module list by pre-calculated weights. - arsort($module_list); - $module_list = array_keys($module_list); - } - - // Required for module installation checks. - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - - $modules_installed = array(); - $modules_enabled = array(); - $module_config = config('system.module'); - $disabled_config = config('system.module.disabled'); - foreach ($module_list as $module) { - // Only process modules that are not already 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 or enabled. - $enabled = $module_config->get("enabled.$module") !== NULL; - if (!$enabled) { - $weight = $disabled_config->get($module); - if ($weight === NULL) { - $weight = 0; - } - $module_config - ->set("enabled.$module", $weight) - ->set('enabled', module_config_sort($module_config->get('enabled'))) - ->save(); - $disabled_config - ->clear($module) - ->save(); - - // Prepare the new module list, sorted by weight, including filenames. - // This list is used for both the ModuleHandler and DrupalKernel. It needs - // to be kept in sync between both. A DrupalKernel reboot or rebuild will - // automatically re-instantiate a new ModuleHandler that uses the new - // module list of the kernel. However, DrupalKernel does not cause any - // modules to be loaded. - // Furthermore, the currently active (fixed) module list can be different - // from the configured list of enabled modules. For all active modules not - // contained in the configured enabled modules, we assume a weight of 0. - $current_module_filenames = $this->getModuleList(); - $current_modules = array_fill_keys(array_keys($current_module_filenames), 0); - $current_modules = module_config_sort(array_merge($current_modules, $module_config->get('enabled'))); - $module_filenames = array(); - foreach ($current_modules as $name => $weight) { - if (isset($current_module_filenames[$name])) { - $filename = $current_module_filenames[$name]; - } - else { - $filename = drupal_get_filename('module', $name); - } - $module_filenames[$name] = $filename; - } - - // Update the module handler in order to load the module's code. - // This allows the module to participate in hooks and its existence to be - // discovered by other modules. - // The current ModuleHandler instance is obsolete with the kernel rebuild - // below. - $this->setModuleList($module_filenames); - $this->load($module); - module_load_install($module); - - // Flush theme info caches, since (testing) modules can implement - // hook_system_theme_info() to register additional themes. - system_list_reset(); - - // Update the kernel to include it. - // This reboots the kernel to register the module's bundle and its - // services in the service container. The $module_filenames argument is - // taken over as %container.modules% parameter, which is passed to a fresh - // ModuleHandler instance upon first retrieval. - // @todo install_begin_request() creates a container without a kernel. - if ($kernel = drupal_container()->get('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) { - $kernel->updateModules($module_filenames, $module_filenames); - } - - // Refresh the list of modules that implement bootstrap hooks. - // @see bootstrap_hooks() - _system_update_bootstrap_status(); - - // Refresh the schema to include it. - drupal_get_schema(NULL, TRUE); - // Update the theme registry to include it. - drupal_theme_rebuild(); - - // Allow modules to react prior to the installation of a module. - $this->invokeAll('modules_preinstall', array(array($module))); - - // Clear the entity info cache before importing new configuration. - entity_info_cache_clear(); - - // Now install the module if necessary. - if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { - drupal_install_schema($module); - - // Set the schema version to the number of the last update provided - // by the module. - $versions = drupal_get_schema_versions($module); - $version = $versions ? max($versions) : SCHEMA_INSTALLED; - - // Install default configuration of the module. - config_install_default_config('module', $module); - - // If the module has no current updates, but has some that were - // previously removed, set the version to the value of - // hook_update_last_removed(). - if ($last_removed = $this->invoke($module, 'update_last_removed')) { - $version = max($version, $last_removed); - } - drupal_set_installed_schema_version($module, $version); - // Allow the module to perform install tasks. - $this->invoke($module, 'install'); - // Record the fact that it was installed. - $modules_installed[] = $module; - watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO); - } - - // Allow modules to react prior to the enabling of a module. - entity_info_cache_clear(); - $this->invokeAll('modules_preenable', array(array($module))); - - // Enable the module. - $this->invoke($module, 'enable'); - - // Record the fact that it was enabled. - $modules_enabled[] = $module; - watchdog('system', '%module module enabled.', array('%module' => $module), WATCHDOG_INFO); - } - } - - // If any modules were newly installed, invoke hook_modules_installed(). - if (!empty($modules_installed)) { - $this->invokeAll('modules_installed', array($modules_installed)); - } - - // If any modules were newly enabled, invoke hook_modules_enabled(). - if (!empty($modules_enabled)) { - $this->invokeAll('modules_enabled', array($modules_enabled)); - } - - return TRUE; - } - - /** - * {@inheritdoc} - */ - function disable($module_list, $disable_dependents = TRUE) { - if ($disable_dependents) { - // Get all module data so we can find dependents and sort. - $module_data = system_rebuild_module_data(); - // Create an associative array with weights as values. - $module_list = array_flip(array_values($module_list)); - - $profile = drupal_get_profile(); - while (list($module) = each($module_list)) { - if (!isset($module_data[$module]) || !$module_data[$module]->status) { - // This module doesn't exist or is already disabled, skip it. - unset($module_list[$module]); - continue; - } - $module_list[$module] = $module_data[$module]->sort; - - // Add dependent modules to the list, with a placeholder weight. - // The new modules will be processed as the while loop continues. - foreach ($module_data[$module]->required_by as $dependent => $dependent_data) { - if (!isset($module_list[$dependent]) && $dependent != $profile) { - $module_list[$dependent] = 0; - } - } - } - - // Sort the module list by pre-calculated weights. - asort($module_list); - $module_list = array_keys($module_list); - } - - $invoke_modules = array(); - - $module_config = config('system.module'); - $disabled_config = config('system.module.disabled'); - foreach ($module_list as $module) { - // 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 or enabled. - $enabled = $module_config->get("enabled.$module") !== NULL; - if ($enabled) { - module_load_install($module); - module_invoke($module, 'disable'); - - $disabled_config - ->set($module, $module_config->get($module)) - ->save(); - $module_config - ->clear("enabled.$module") - ->save(); - - // Update the module handler to remove the module. - // The current ModuleHandler instance is obsolete with the kernel rebuild - // below. - $module_filenames = $this->getModuleList(); - unset($module_filenames[$module]); - $this->setModuleList($module_filenames); - - // Record the fact that it was disabled. - $invoke_modules[] = $module; - watchdog('system', '%module module disabled.', array('%module' => $module), WATCHDOG_INFO); - } - } - - if (!empty($invoke_modules)) { - // @todo Most of the following should happen in above loop already. - - // Refresh the system list to exclude the disabled modules. - // @todo Only needed to rebuild theme info. - // @see system_list_reset() - system_list_reset(); - - entity_info_cache_clear(); - - // Invoke hook_modules_disabled before disabling modules, - // so we can still call module hooks to get information. - $this->invokeAll('modules_disabled', array($invoke_modules)); - _system_update_bootstrap_status(); - - // Update the kernel to exclude the disabled modules. - $enabled = $this->getModuleList(); - drupal_container()->get('kernel')->updateModules($enabled, $enabled); - - // Update the theme registry to remove the newly-disabled module. - drupal_theme_rebuild(); - } - } - - /** - * {@inheritdoc} - */ - public function uninstall($module_list = array(), $uninstall_dependents = TRUE) { - if ($uninstall_dependents) { - // Get all module data so we can find dependents and sort. - $module_data = system_rebuild_module_data(); - // Create an associative array with weights as values. - $module_list = array_flip(array_values($module_list)); - - $profile = drupal_get_profile(); - while (list($module) = each($module_list)) { - if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { - // This module doesn't exist or is already uninstalled. Skip it. - unset($module_list[$module]); - continue; - } - $module_list[$module] = $module_data[$module]->sort; - - // If the module has any dependents which are not already uninstalled and - // not included in the passed-in list, abort. It is not safe to uninstall - // them automatically because uninstalling a module is a destructive - // operation. - foreach (array_keys($module_data[$module]->required_by) as $dependent) { - if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) { - return FALSE; - } - } - } - - // Sort the module list by pre-calculated weights. - asort($module_list); - $module_list = array_keys($module_list); - } - - $schema_store = \Drupal::keyValue('system.schema'); - $disabled_config = config('system.module.disabled'); - foreach ($module_list as $module) { - // Uninstall the module. - module_load_install($module); - $this->invoke($module, 'uninstall'); - drupal_uninstall_schema($module); - - // Remove all configuration belonging to the module. - config_uninstall_default_config('module', $module); - - // Remove any cache bins defined by the module. - $service_yaml_file = drupal_get_path('module', $module) . "/$module.services.yml"; - if (file_exists($service_yaml_file)) { - $parser = new Parser; - $definitions = $parser->parse(file_get_contents($service_yaml_file)); - if (isset($definitions['services'])) { - foreach ($definitions['services'] as $id => $definition) { - if (isset($definition['tags'])) { - foreach ($definition['tags'] as $tag) { - // This works for the default cache registration and even in some - // cases when a non-default "super" factory is used. That should - // be extremely rare. - if ($tag['name'] == 'cache.bin' && isset($definition['factory_service']) && isset($definition['factory_method']) && !empty($definition['arguments'])) { - try { - $factory = \Drupal::service($definition['factory_service']); - if (method_exists($factory, $definition['factory_method'])) { - $backend = call_user_func_array(array($factory, $definition['factory_method']), $definition['arguments']); - if ($backend instanceof CacheBackendInterface) { - $backend->removeBin(); - } - } - } - catch (\Exception $e) { - watchdog_exception('system', $e, 'Failed to remove cache bin defined by the service %id.', array('%id' => $id)); - } - } - } - } - } - } - } - - watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); - $schema_store->delete($module); - $disabled_config->clear($module); - } - $disabled_config->save(); - drupal_get_installed_schema_version(NULL, TRUE); - - if (!empty($module_list)) { - // Let other modules react. - $this->invokeAll('modules_uninstalled', array($module_list)); - } - - return TRUE; - } - } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 44284b9..71c5dd9 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -262,77 +262,4 @@ public function invokeAll($hook, $args = array()); */ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL); - /** - * Enables or installs a given list of modules. - * - * Definitions: - * - "Enabling" is the process of activating a module for use by Drupal. - * - "Disabling" is the process of deactivating a module. - * - "Installing" is the process of enabling it for the first time or after it - * has been uninstalled. - * - "Uninstalling" is the process of removing all traces of a module. - * - * Order of events: - * - Gather and add module dependencies to $module_list (if applicable). - * - For each module that is being enabled: - * - Install module schema and update system registries and caches. - * - If the module is being enabled for the first time or had been - * uninstalled, invoke hook_install() and add it to the list of installed - * modules. - * - Invoke hook_enable(). - * - Invoke hook_modules_installed(). - * - Invoke hook_modules_enabled(). - * - * @param $module_list - * An array of module names. - * @param $enable_dependencies - * If TRUE, dependencies will automatically be added and enabled in the - * correct order. This incurs a significant performance cost, so use FALSE - * if you know $module_list is already complete and in the correct order. - * - * @return - * FALSE if one or more dependencies are missing, TRUE otherwise. - * - * @see hook_install() - * @see hook_enable() - * @see hook_modules_installed() - * @see hook_modules_enabled() - */ - public function enable($module_list, $enable_dependencies = TRUE); - - /** - * Disables a given set of modules. - * - * @param $module_list - * An array of module names. - * @param $disable_dependents - * If TRUE, dependent modules will automatically be added and disabled in the - * correct order. This incurs a significant performance cost, so use FALSE - * if you know $module_list is already complete and in the correct order. - */ - public function disable($module_list, $disable_dependents = TRUE); - - /** - * Uninstalls a given list of disabled modules. - * - * @param array $module_list - * The modules to uninstall. It is the caller's responsibility to ensure that - * all modules in this list have already been disabled before this function - * is called. - * @param bool $uninstall_dependents - * (optional) If TRUE, the function will check that all modules which depend - * on the passed-in module list either are already uninstalled or contained in - * the list, and it will ensure that the modules are uninstalled in the - * correct order. This incurs a significant performance cost, so use FALSE if - * you know $module_list is already complete and in the correct order. - * Defaults to TRUE. - * - * @return bool - * Returns TRUE if the operation succeeds or FALSE if it aborts due to an - * unsafe condition, namely, $uninstall_dependents is TRUE and a module in - * $module_list has dependents which are not already uninstalled and not also - * included in $module_list). - */ - public function uninstall($module_list = array(), $uninstall_dependents = TRUE); - } diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php new file mode 100644 index 0000000..58e9d9b --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -0,0 +1,401 @@ +moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public function enable($module_list, $enable_dependencies = TRUE) { + if ($enable_dependencies) { + // Get all module data so we can find dependencies and sort. + $module_data = system_rebuild_module_data(); + // Create an associative array with weights as values. + $module_list = array_flip(array_values($module_list)); + + while (list($module) = each($module_list)) { + if (!isset($module_data[$module])) { + // This module is not found in the filesystem, abort. + return FALSE; + } + if ($module_data[$module]->status) { + // Skip already enabled modules. + unset($module_list[$module]); + continue; + } + $module_list[$module] = $module_data[$module]->sort; + + // Add dependencies to the list, with a placeholder weight. + // The new modules will be processed as the while loop continues. + foreach (array_keys($module_data[$module]->requires) as $dependency) { + if (!isset($module_list[$dependency])) { + $module_list[$dependency] = 0; + } + } + } + + if (!$module_list) { + // Nothing to do. All modules already enabled. + return TRUE; + } + + // Sort the module list by pre-calculated weights. + arsort($module_list); + $module_list = array_keys($module_list); + } + + // Required for module installation checks. + include_once DRUPAL_ROOT . '/core/includes/install.inc'; + + $modules_installed = array(); + $modules_enabled = array(); + $module_config = config('system.module'); + $disabled_config = config('system.module.disabled'); + foreach ($module_list as $module) { + // Only process modules that are not already 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 or enabled. + $enabled = $module_config->get("enabled.$module") !== NULL; + if (!$enabled) { + $weight = $disabled_config->get($module); + if ($weight === NULL) { + $weight = 0; + } + $module_config + ->set("enabled.$module", $weight) + ->set('enabled', module_config_sort($module_config->get('enabled'))) + ->save(); + $disabled_config + ->clear($module) + ->save(); + + // Prepare the new module list, sorted by weight, including filenames. + // This list is used for both the ModuleHandler and DrupalKernel. It needs + // to be kept in sync between both. A DrupalKernel reboot or rebuild will + // automatically re-instantiate a new ModuleHandler that uses the new + // module list of the kernel. However, DrupalKernel does not cause any + // modules to be loaded. + // Furthermore, the currently active (fixed) module list can be different + // from the configured list of enabled modules. For all active modules not + // contained in the configured enabled modules, we assume a weight of 0. + $current_module_filenames = $this->moduleHandler->getModuleList(); + $current_modules = array_fill_keys(array_keys($current_module_filenames), 0); + $current_modules = module_config_sort(array_merge($current_modules, $module_config->get('enabled'))); + $module_filenames = array(); + foreach ($current_modules as $name => $weight) { + if (isset($current_module_filenames[$name])) { + $filename = $current_module_filenames[$name]; + } + else { + $filename = drupal_get_filename('module', $name); + } + $module_filenames[$name] = $filename; + } + + // Update the module handler in order to load the module's code. + // This allows the module to participate in hooks and its existence to be + // discovered by other modules. + // The current ModuleHandler instance is obsolete with the kernel rebuild + // below. + $this->moduleHandler->setModuleList($module_filenames); + $this->moduleHandler->load($module); + module_load_install($module); + + // Flush theme info caches, since (testing) modules can implement + // hook_system_theme_info() to register additional themes. + system_list_reset(); + + // Update the kernel to include it. + // This reboots the kernel to register the module's bundle and its + // services in the service container. The $module_filenames argument is + // taken over as %container.modules% parameter, which is passed to a fresh + // ModuleHandler instance upon first retrieval. + // @todo install_begin_request() creates a container without a kernel. + if ($kernel = drupal_container()->get('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + $kernel->updateModules($module_filenames, $module_filenames); + } + + // Refresh the list of modules that implement bootstrap hooks. + // @see bootstrap_hooks() + _system_update_bootstrap_status(); + + // Refresh the schema to include it. + drupal_get_schema(NULL, TRUE); + // Update the theme registry to include it. + drupal_theme_rebuild(); + + // Allow modules to react prior to the installation of a module. + $this->moduleHandler->invokeAll('modules_preinstall', array(array($module))); + + // Clear the entity info cache before importing new configuration. + entity_info_cache_clear(); + + // Now install the module if necessary. + if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { + drupal_install_schema($module); + + // Set the schema version to the number of the last update provided + // by the module. + $versions = drupal_get_schema_versions($module); + $version = $versions ? max($versions) : SCHEMA_INSTALLED; + + // Install default configuration of the module. + config_install_default_config('module', $module); + + // If the module has no current updates, but has some that were + // previously removed, set the version to the value of + // hook_update_last_removed(). + if ($last_removed = $this->moduleHandler->invoke($module, 'update_last_removed')) { + $version = max($version, $last_removed); + } + drupal_set_installed_schema_version($module, $version); + // Allow the module to perform install tasks. + $this->moduleHandler->invoke($module, 'install'); + // Record the fact that it was installed. + $modules_installed[] = $module; + watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO); + } + + // Allow modules to react prior to the enabling of a module. + entity_info_cache_clear(); + $this->moduleHandler->invokeAll('modules_preenable', array(array($module))); + + // Enable the module. + $this->moduleHandler->invoke($module, 'enable'); + + // Record the fact that it was enabled. + $modules_enabled[] = $module; + watchdog('system', '%module module enabled.', array('%module' => $module), WATCHDOG_INFO); + } + } + + // If any modules were newly installed, invoke hook_modules_installed(). + if (!empty($modules_installed)) { + $this->moduleHandler->invokeAll('modules_installed', array($modules_installed)); + } + + // If any modules were newly enabled, invoke hook_modules_enabled(). + if (!empty($modules_enabled)) { + $this->moduleHandler->invokeAll('modules_enabled', array($modules_enabled)); + } + + return TRUE; + } + + /** + * {@inheritdoc} + */ + function disable($module_list, $disable_dependents = TRUE) { + if ($disable_dependents) { + // Get all module data so we can find dependents and sort. + $module_data = system_rebuild_module_data(); + // Create an associative array with weights as values. + $module_list = array_flip(array_values($module_list)); + + $profile = drupal_get_profile(); + while (list($module) = each($module_list)) { + if (!isset($module_data[$module]) || !$module_data[$module]->status) { + // This module doesn't exist or is already disabled, skip it. + unset($module_list[$module]); + continue; + } + $module_list[$module] = $module_data[$module]->sort; + + // Add dependent modules to the list, with a placeholder weight. + // The new modules will be processed as the while loop continues. + foreach ($module_data[$module]->required_by as $dependent => $dependent_data) { + if (!isset($module_list[$dependent]) && $dependent != $profile) { + $module_list[$dependent] = 0; + } + } + } + + // Sort the module list by pre-calculated weights. + asort($module_list); + $module_list = array_keys($module_list); + } + + $invoke_modules = array(); + + $module_config = config('system.module'); + $disabled_config = config('system.module.disabled'); + foreach ($module_list as $module) { + // 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 or enabled. + $enabled = $module_config->get("enabled.$module") !== NULL; + if ($enabled) { + module_load_install($module); + module_invoke($module, 'disable'); + + $disabled_config + ->set($module, $module_config->get($module)) + ->save(); + $module_config + ->clear("enabled.$module") + ->save(); + + // Update the module handler to remove the module. + // The current ModuleHandler instance is obsolete with the kernel rebuild + // below. + $module_filenames = $this->moduleHandler->getModuleList(); + unset($module_filenames[$module]); + $this->moduleHandler->setModuleList($module_filenames); + + // Record the fact that it was disabled. + $invoke_modules[] = $module; + watchdog('system', '%module module disabled.', array('%module' => $module), WATCHDOG_INFO); + } + } + + if (!empty($invoke_modules)) { + // @todo Most of the following should happen in above loop already. + + // Refresh the system list to exclude the disabled modules. + // @todo Only needed to rebuild theme info. + // @see system_list_reset() + system_list_reset(); + + entity_info_cache_clear(); + + // Invoke hook_modules_disabled before disabling modules, + // so we can still call module hooks to get information. + $this->moduleHandler->invokeAll('modules_disabled', array($invoke_modules)); + _system_update_bootstrap_status(); + + // Update the kernel to exclude the disabled modules. + $enabled = $this->moduleHandler->getModuleList(); + drupal_container()->get('kernel')->updateModules($enabled, $enabled); + + // Update the theme registry to remove the newly-disabled module. + drupal_theme_rebuild(); + } + } + + /** + * {@inheritdoc} + */ + public function uninstall($module_list = array(), $uninstall_dependents = TRUE) { + if ($uninstall_dependents) { + // Get all module data so we can find dependents and sort. + $module_data = system_rebuild_module_data(); + // Create an associative array with weights as values. + $module_list = array_flip(array_values($module_list)); + + $profile = drupal_get_profile(); + while (list($module) = each($module_list)) { + if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { + // This module doesn't exist or is already uninstalled. Skip it. + unset($module_list[$module]); + continue; + } + $module_list[$module] = $module_data[$module]->sort; + + // If the module has any dependents which are not already uninstalled and + // not included in the passed-in list, abort. It is not safe to uninstall + // them automatically because uninstalling a module is a destructive + // operation. + foreach (array_keys($module_data[$module]->required_by) as $dependent) { + if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) { + return FALSE; + } + } + } + + // Sort the module list by pre-calculated weights. + asort($module_list); + $module_list = array_keys($module_list); + } + + $schema_store = \Drupal::keyValue('system.schema'); + $disabled_config = config('system.module.disabled'); + foreach ($module_list as $module) { + // Uninstall the module. + module_load_install($module); + $this->moduleHandler->invoke($module, 'uninstall'); + drupal_uninstall_schema($module); + + // Remove all configuration belonging to the module. + config_uninstall_default_config('module', $module); + + // Remove any cache bins defined by the module. + $service_yaml_file = drupal_get_path('module', $module) . "/$module.services.yml"; + if (file_exists($service_yaml_file)) { + $parser = new Parser; + $definitions = $parser->parse(file_get_contents($service_yaml_file)); + if (isset($definitions['services'])) { + foreach ($definitions['services'] as $id => $definition) { + if (isset($definition['tags'])) { + foreach ($definition['tags'] as $tag) { + // This works for the default cache registration and even in some + // cases when a non-default "super" factory is used. That should + // be extremely rare. + if ($tag['name'] == 'cache.bin' && isset($definition['factory_service']) && isset($definition['factory_method']) && !empty($definition['arguments'])) { + try { + $factory = \Drupal::service($definition['factory_service']); + if (method_exists($factory, $definition['factory_method'])) { + $backend = call_user_func_array(array($factory, $definition['factory_method']), $definition['arguments']); + if ($backend instanceof CacheBackendInterface) { + $backend->removeBin(); + } + } + } + catch (\Exception $e) { + watchdog_exception('system', $e, 'Failed to remove cache bin defined by the service %id.', array('%id' => $id)); + } + } + } + } + } + } + } + + watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); + $schema_store->delete($module); + $disabled_config->clear($module); + } + $disabled_config->save(); + drupal_get_installed_schema_version(NULL, TRUE); + + if (!empty($module_list)) { + // Let other modules react. + $this->moduleHandler->invokeAll('modules_uninstalled', array($module_list)); + } + + return TRUE; + } + +} diff --git a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php new file mode 100644 index 0000000..70bb49f --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php @@ -0,0 +1,88 @@ +