Commit 451e2dfc authored by catch's avatar catch
Browse files

fix: #3562319 [regression] The installer does not properly load the theme

By: phenaproxima
By: nicxvan
By: alexpott
(cherry picked from commit b6f6b221)
parent 78399e08
Loading
Loading
Loading
Loading
Loading
+7 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Extension\Exception\UnknownExtensionException;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\Requirement\RequirementSeverity;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormState;
@@ -32,7 +33,6 @@
use Drupal\Core\Site\Settings;
use Drupal\Core\StringTranslation\Translator\FileTranslation;
use Drupal\Core\StackMiddleware\ReverseProxyMiddleware;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageManagerInterface;
@@ -423,6 +423,12 @@ function install_begin_request($class_loader, &$install_state): void {
  // already initialized.
  $kernel = new InstallerKernel($environment, $class_loader, FALSE);
  $kernel::bootEnvironment();

  // Add list of all available profiles to the installation state.
  $listing = new ExtensionDiscovery($kernel->getAppRoot(), FALSE);
  $listing->setProfileDirectories([]);
  $install_state['profiles'] += $listing->scan('profile');

  $kernel->setSitePath($site_path);
  $kernel->boot();
  // Get the new version of the container from the kernel. This is now a
@@ -444,11 +450,6 @@ function install_begin_request($class_loader, &$install_state): void {
  $container->get('string_translation')
    ->addTranslator($container->get('string_translator.file_translation'));

  // Add list of all available profiles to the installation state.
  $listing = new ExtensionDiscovery($container->getParameter('app.root'));
  $listing->setProfileDirectories([]);
  $install_state['profiles'] += $listing->scan('profile');

  /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
  $module_list = \Drupal::service('extension.list.module');
  /** @var \Drupal\Core\Extension\ProfileExtensionList $profile_list */
+119 −3
Original line number Diff line number Diff line
@@ -3,6 +3,9 @@
namespace Drupal\Core\Installer;

use Drupal\Core\DrupalKernel;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\InfoParser;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -102,13 +105,126 @@ protected function attachSynthetic(ContainerInterface $container): void {
   */
  protected function getExtensions(): array {
    $extensions = parent::getExtensions() ?: [];
    if (!static::installationAttempted()) {
      return $extensions;
    }

    // Ensure that the System module is always available to the installer.
    $extensions['module']['system'] ??= 0;
    if (empty($extensions['profile']) && !empty($GLOBALS['install_state']) && ($profile = _install_select_profile($GLOBALS['install_state']))) {
      $extensions['profile'] = $profile;
      $extensions['module'][$profile] = 1000;
    if ($profile = $this->installGetProfile()) {
      $extensions['profile'] = $profile->getName();
      if (!isset($extensions['module'][$profile->getName()])) {
        $extensions['module'][$profile->getName()] = 1000;
      }
      $theme = $profile->info['distribution']['install']['theme'] ?? 'claro';
      $extensions['theme'][$theme] ??= 0;

      if ($theme !== 'claro') {
        // Need to check for base themes.
        foreach ($this->getBaseThemes($profile, $theme) as $base_theme) {
          $extensions['theme'][$base_theme] ??= 0;
        }
      }
    }
    // Ensure that the default theme is always available to the installer.
    else {
      $extensions['theme']['claro'] ??= 0;
    }
    return $extensions;
  }

  /**
   * Gets the base themes for a given theme.
   *
   * @param \Drupal\Core\Extension\Extension $profile
   *   The profile being installed.
   * @param string $theme
   *   The theme for installation.
   *
   * @return string[]
   *   A list of base themes.
   */
  private function getBaseThemes(Extension $profile, string $theme): array {
    $base_themes = [];

    // Find all the available themes.
    $listing = new ExtensionDiscovery($this->root);
    $listing->setProfileDirectories([$profile->getName() => $profile->getPath()]);
    $themes = $listing->scan('theme');

    $info_parser = new InfoParser($this->root);
    $theme_info = $info_parser->parse($themes[$theme]->getPathname());
    $base_theme = $theme_info['base theme'] ?? FALSE;

    while ($base_theme) {
      $base_themes[] = $base_theme;
      $theme_info = $info_parser->parse($themes[$base_theme]->getPathname());
      $base_theme = $theme_info['base theme'] ?? FALSE;
    }

    return $base_themes;
  }

  /**
   * Gets the profile to be installed.
   *
   * @return string|null|\Drupal\Core\Extension\Extension
   *   Returns NULL if no profile was selected or FALSE if the site has no
   *   profile, or the profile extension object with the profile info added.
   *
   * @see _install_select_profile()
   */
  private function installGetProfile(): null|false|Extension {
    global $install_state;

    $profile = NULL;

    if (empty($install_state['profiles'])) {
      throw new \RuntimeException('No profiles found.');
    }

    // If there is only one profile available it will always be the one
    // selected.
    if (count($install_state['profiles']) == 1) {
      $profile = reset($install_state['profiles']);
    }
    // If a valid profile has already been selected, return the selection.
    if (array_key_exists('profile', $install_state['parameters'])) {
      $profile = $install_state['parameters']['profile'];
      if ($profile && isset($install_state['profiles'][$profile])) {
        $profile = $install_state['profiles'][$profile];
      }
    }

    // Not using a profile.
    if ($profile === FALSE) {
      return $profile;
    }

    $info_parser = new InfoParser($this->root);

    if ($profile instanceof Extension) {
      $profile->info = $info_parser->parse($profile->getPathname());
      return $profile;
    }

    $visible_profiles = [];
    // If any of the profiles are distribution profiles, select the first one.
    foreach ($install_state['profiles'] as $profile) {
      $profile->info = $info_parser->parse($profile->getPathname());
      if (!empty($profile->info['distribution'])) {
        return $profile;
      }
      if (!isset($profile->info['hidden']) || !$profile->info['hidden']) {
        $visible_profiles[] = $profile;
      }
    }
    // If there is only one visible profile, select it.
    if (count($visible_profiles) == 1) {
      return $visible_profiles[0];
    }

    return NULL;
  }

}
+5 −0
Original line number Diff line number Diff line
name: 'Custom installer theme'
type: theme
description: 'A custom installer theme to test that the installer can be themed.'
base theme: claro
version: VERSION
+15 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Contains hooks for a custom installer theme.
 */

declare(strict_types=1);

/**
 * Implements hook_form_FORM_ID_alter().
 */
function test_installer_theme_form_install_select_language_form_alter(array &$form): void {
  $form['function_name']['#markup'] = 'Added by custom installer theme.';
}
+7 −3
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ protected function prepareEnvironment(): void {
      'distribution' => [
        'name' => 'My Distribution',
        'install' => [
          'theme' => 'claro',
          'theme' => 'test_installer_theme',
          'finish_url' => '/root-user',
        ],
      ],
@@ -56,8 +56,12 @@ protected function setUpLanguage(): void {
    $this->assertSession()->pageTextContains($this->info['distribution']['name']);
    // Verify that the distribution name is used in the site title.
    $this->assertSession()->titleEquals('Choose language | ' . $this->info['distribution']['name']);
    // Verify that the requested theme is used.
    $this->assertSession()->responseContains($this->info['distribution']['install']['theme']);
    // Verify that the requested theme is used -- its modifications to this form
    // should be visible.
    $this->assertSession()->pageTextContains('Added by custom installer theme.');
    // Verify the base theme CSS is loaded.
    $this->assertSession()->responseContains("claro/css/theme/install-page.css");

    // Verify that the "Choose profile" step does not appear.
    $this->assertSession()->pageTextNotContains('profile');

Loading