Verified Commit 94a65fff authored by Andrei Mateescu's avatar Andrei Mateescu
Browse files

task: #3594334 Add a module and theme install command

By: moshe weitzman
By: nicxvan
By: mradcliffe
By: ressa
By: catch
By: berdir
parent 3c5ba5ef
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Command\Exception;

/**
 * Thrown when a user declines an interactive console confirmation prompt.
 */
class UserAbortException extends \RuntimeException {

  public function __construct(string $message = 'Operation cancelled by user.', int $code = 0, ?\Throwable $previous = NULL) {
    parent::__construct($message, $code, $previous);
  }

}
+49 −20
Original line number Diff line number Diff line
@@ -97,24 +97,25 @@ public function __construct(
  /**
   * {@inheritdoc}
   */
  public function install(array $module_list, $enable_dependencies = TRUE) {
    $extension_config = \Drupal::configFactory()->getEditable('core.extension');
  public function getModulesToInstall(array $module_list, bool $enable_dependencies = TRUE): array {
    $installed_modules = \Drupal::configFactory()->get('core.extension')->get('module') ?: [];

    // Remove any modules that are already installed.
    $installed_modules = $extension_config->get('module') ?: [];
    // Only process currently uninstalled modules.
    $module_list = array_diff($module_list, array_keys($installed_modules));

    $module_list = array_values(array_diff($module_list, array_keys($installed_modules)));
    if (empty($module_list)) {
      // Nothing to do. All modules already installed.
      return TRUE;
      return [];
    }

    // Get all module data so we can find dependencies and sort and find the
    // core requirements. The module list needs to be reset so that it can
    // re-scan and include any new modules that may have been added directly
    // into the filesystem.
    // Get all module data so we can find dependencies and sort. The module
    // list needs to be reset so that it can re-scan and include any new
    // modules that may have been added directly into the filesystem.
    $module_data = \Drupal::service('extension.list.module')->reset()->getList();

    // Ensure all of the requested modules actually exist.
    if ($missing_modules = array_diff_key(array_flip($module_list), $module_data)) {
      throw new MissingDependencyException(sprintf('Unable to install modules %s due to missing modules %s.', implode(', ', $module_list), implode(', ', array_keys($missing_modules))));
    }

    foreach ($module_list as $module) {
      if (!empty($module_data[$module]->info['core_incompatible'])) {
        throw new MissingDependencyException("Unable to install modules: module '$module' is incompatible with this version of Drupal core.");
@@ -122,17 +123,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
      if ($module_data[$module]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
        throw new ObsoleteExtensionException("Unable to install modules: module '$module' is obsolete.");
      }
      if ($module_data[$module]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
        // phpcs:ignore Drupal.Semantics.FunctionTriggerError
        @trigger_error("The module '$module' is deprecated. See " . $module_data[$module]->info['lifecycle_link'], E_USER_DEPRECATED);
      }
    }

    if ($enable_dependencies) {
      $module_list = array_combine($module_list, $module_list);
      if ($missing_modules = array_diff_key($module_list, $module_data)) {
        // One or more of the given modules doesn't exist.
        throw new MissingDependencyException(sprintf('Unable to install modules %s due to missing modules %s.', implode(', ', $module_list), implode(', ', $missing_modules)));
      }

      // Add dependencies to the list. The new modules will be processed as
      // the foreach loop continues.
@@ -163,6 +157,41 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
      $module_list = array_keys($module_list);
    }

    return array_values($module_list);
  }

  /**
   * {@inheritdoc}
   */
  public function install(array $module_list, $enable_dependencies = TRUE) {
    $extension_config = \Drupal::configFactory()->getEditable('core.extension');
    $installed_modules = $extension_config->get('module') ?: [];

    // The modules the caller explicitly asked for that are not yet installed.
    // Dependency resolution happens in ::getModulesToInstall() below; the
    // deprecation notice is intentionally only emitted for these requested
    // modules and not for dependencies pulled in automatically.
    $requested_modules = array_diff($module_list, array_keys($installed_modules));

    // Resolve the full, dependency-sorted list of modules to install. This
    // also validates that the modules exist and are compatible.
    $module_list = $this->getModulesToInstall($module_list, $enable_dependencies);

    if (empty($module_list)) {
      // Nothing to do. All modules already installed.
      return TRUE;
    }

    // ::getModulesToInstall() reset the extension list, so reuse it here
    // without paying the cost of another scan.
    $module_data = \Drupal::service('extension.list.module')->getList();
    foreach ($requested_modules as $module) {
      if (isset($module_data[$module]) && $module_data[$module]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
        // phpcs:ignore Drupal.Semantics.FunctionTriggerError
        @trigger_error("The module '$module' is deprecated. See " . $module_data[$module]->info['lifecycle_link'], E_USER_DEPRECATED);
      }
    }

    // Required for module installation checks.
    include_once $this->root . '/core/includes/install.inc';

+24 −0
Original line number Diff line number Diff line
@@ -50,6 +50,30 @@ interface ModuleInstallerInterface {
   */
  public function install(array $module_list, $enable_dependencies = TRUE);

  /**
   * Determines the full set of modules corresponding to the given list.
   *
   * @param string[] $module_list
   *   An array of module machine names.
   * @param bool $enable_dependencies
   *   (optional) If TRUE, dependencies are resolved and added to the returned
   *   list in the correct install order. If FALSE, only the given modules that
   *   are not yet installed are returned.
   *
   * @return string[]
   *   The machine names of the modules that are not yet installed, including
   *   resolved dependencies, ordered the way ::install() would install them.
   *   Already installed modules are omitted.
   *
   * @throws \Drupal\Core\Extension\MissingDependencyException
   *   Thrown when a requested module, or a dependency of one, can not be found
   *   or is incompatible with this version of Drupal core.
   *
   * @throws \Drupal\Core\Extension\Exception\ObsoleteExtensionException
   *   Thrown when a requested module is obsolete.
   */
  public function getModulesToInstall(array $module_list, bool $enable_dependencies = TRUE): array;

  /**
   * Uninstalls a given list of modules.
   *
+41 −12
Original line number Diff line number Diff line
@@ -47,15 +47,19 @@ public function __construct(
  /**
   * {@inheritdoc}
   */
  public function install(array $theme_list, $install_dependencies = TRUE) {
    $extension_config = $this->configFactory->getEditable('core.extension');
  public function getThemesToInstall(array $theme_list, bool $install_dependencies = TRUE): array {
    if (!$install_dependencies) {
      // Without dependency resolution the caller is responsible for providing a
      // complete list; the install loop still skips already-installed themes.
      return array_values($theme_list);
    }

    $extension_config = $this->configFactory->get('core.extension');
    $theme_data = $this->themeExtensionList->reset()->getList();
    $installed_themes = $extension_config->get('theme') ?: [];
    $installed_modules = $extension_config->get('module') ?: [];

    if ($install_dependencies) {
      $theme_list = array_combine($theme_list, $theme_list);
    $theme_list = array_combine(array_values($theme_list), array_values($theme_list));

    if ($missing = array_diff_key($theme_list, $theme_data)) {
      // One or more of the given themes doesn't exist.
@@ -65,7 +69,7 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
    // Only process themes that are not installed currently.
    if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
      // Nothing to do. All themes already installed.
        return TRUE;
      return [];
    }

    $module_list = $this->moduleExtensionList->getList();
@@ -82,11 +86,6 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
      // machine names keys that are not in $installed_modules keys.
      $unmet_module_dependencies = array_diff_key($module_dependencies, $installed_modules);

        if ($theme_data[$theme]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
          // phpcs:ignore Drupal.Semantics.FunctionTriggerError
          @trigger_error("The theme '$theme' is deprecated. See " . $theme_data[$theme]->info['lifecycle_link'], E_USER_DEPRECATED);
        }

      // Prevent themes with unmet module dependencies from being installed.
      if (!empty($unmet_module_dependencies)) {
        $unmet_module_dependencies_list = implode(', ', array_keys($unmet_module_dependencies));
@@ -105,7 +104,7 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
      foreach (array_keys($theme_dependencies) as $dependency) {
        if (!isset($theme_data[$dependency])) {
          // The dependency does not exist.
            return FALSE;
          throw new UnknownExtensionException("Unable to install theme: '$theme' is missing its theme dependency '$dependency'.");
        }

        // Skip already installed themes.
@@ -122,7 +121,37 @@ public function install(array $theme_list, $install_dependencies = TRUE) {

    // Sort the theme list by their weights (reverse).
    arsort($theme_list);
      $theme_list = array_keys($theme_list);
    return array_keys($theme_list);
  }

  /**
   * {@inheritdoc}
   */
  public function install(array $theme_list, $install_dependencies = TRUE) {
    $extension_config = $this->configFactory->getEditable('core.extension');

    $theme_data = $this->themeExtensionList->reset()->getList();
    $installed_themes = $extension_config->get('theme') ?: [];

    if ($install_dependencies) {
      // Themes explicitly requested that are not yet installed. Deprecation
      // notices are emitted only for these, not for resolved dependencies.
      $requested_themes = array_diff(array_values($theme_list), array_keys($installed_themes));

      // Resolve the full, dependency-sorted list of themes to install. This
      // also validates that the themes and their dependencies are installable.
      $theme_list = $this->getThemesToInstall($theme_list);
      if (empty($theme_list)) {
        // Nothing to do. All themes already installed.
        return TRUE;
      }

      foreach ($requested_themes as $theme) {
        if (isset($theme_data[$theme]) && $theme_data[$theme]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
          // phpcs:ignore Drupal.Semantics.FunctionTriggerError
          @trigger_error("The theme '$theme' is deprecated. See " . $theme_data[$theme]->info['lifecycle_link'], E_USER_DEPRECATED);
        }
      }
    }

    $themes_installed = [];
+24 −0
Original line number Diff line number Diff line
@@ -35,6 +35,30 @@ interface ThemeInstallerInterface {
   */
  public function install(array $theme_list, $install_dependencies = TRUE);

  /**
   * Determines the full set of themes corresponding to the given list.
   *
   * @param string[] $theme_list
   *   An array of theme machine names.
   * @param bool $install_dependencies
   *   (optional) If TRUE, dependencies are resolved and added to the returned
   *   list in the correct install order. If FALSE, the given list is returned
   *   unchanged.
   *
   * @return string[]
   *   The machine names of the themes to install, including any resolved theme
   *   dependencies, ordered the way ::install() would install them.
   *   Already installed themes are omitted.
   *
   * @throws \Drupal\Core\Extension\Exception\UnknownExtensionException
   *   Thrown when a requested theme, or a theme dependency of one, does not
   *   exist.
   *
   * @throws \Drupal\Core\Extension\MissingDependencyException
   *   Thrown when a theme has an unmet or incompatible module dependency.
   */
  public function getThemesToInstall(array $theme_list, bool $install_dependencies = TRUE): array;

  /**
   * Uninstalls a given list of themes.
   *
Loading