From e42ac27706b2517f94143fbb206437603ae2b679 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Fri, 14 Mar 2014 10:49:27 +0000
Subject: [PATCH] =?UTF-8?q?Issue=20#1351352=20by=20sun,=20lucascaro,=20G?=
 =?UTF-8?q?=C3=A1bor=20Hojtsy,=20rupertj:=20Distribution=20installation=20?=
 =?UTF-8?q?profiles=20are=20no=20longer=20able=20to=20override=20the=20ear?=
 =?UTF-8?q?ly=20installer=20screens.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 core/includes/install.core.inc                | 112 ++++++++++--------
 core/includes/install.inc                     |  30 +++--
 core/includes/theme.inc                       |   6 +-
 core/includes/theme.maintenance.inc           |   7 +-
 .../Installer/DistributionProfileTest.php     |  85 +++++++++++++
 core/modules/system/system.module             |   9 +-
 6 files changed, 184 insertions(+), 65 deletions(-)
 create mode 100644 core/modules/system/lib/Drupal/system/Tests/Installer/DistributionProfileTest.php

diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 61d9feed1441..8d27e08d6766 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -205,6 +205,8 @@ function install_state_defaults() {
     'profile_info' => array(),
     // An array of available installation profiles.
     'profiles' => array(),
+    // The name of the theme to use during installation.
+    'theme' => 'seven',
     // The server URL where the interface translation files can be downloaded.
     // Tokens in the pattern will be replaced by appropriate values for the
     // required translation file.
@@ -377,17 +379,6 @@ function install_begin_request(&$install_state) {
     \Drupal::translation()->setDefaultLangcode($install_state['parameters']['langcode']);
   }
 
-  $module_handler = \Drupal::moduleHandler();
-  if (!$module_handler->moduleExists('system')) {
-    // Override the module list with a minimal set of modules.
-    $module_handler->setModuleList(array('system' => 'core/modules/system/system.module'));
-  }
-  // After setting up a custom and finite module list in a custom low-level
-  // bootstrap like here, ensure to use ModuleHandler::loadAll() so that
-  // ModuleHandler::isLoaded() returns TRUE, since that is a condition being
-  // checked by other subsystems (e.g., the theme system).
-  $module_handler->loadAll();
-
   // Add list of all available profiles to the installation state.
   $listing = new ExtensionDiscovery();
   $listing->setProfileDirectories(array());
@@ -398,6 +389,30 @@ function install_begin_request(&$install_state) {
     drupal_get_filename('profile', $name, $profile->uri);
   }
 
+  if ($profile = _install_select_profile($install_state)) {
+    $install_state['parameters']['profile'] = $profile;
+    install_load_profile($install_state);
+    if (isset($install_state['profile_info']['distribution']['install']['theme'])) {
+      $install_state['theme'] = $install_state['profile_info']['distribution']['install']['theme'];
+    }
+  }
+
+  // Override the module list with a minimal set of modules.
+  $module_handler = \Drupal::moduleHandler();
+  $module_list = $module_handler->getModuleList();
+  if (!$module_handler->moduleExists('system')) {
+    $module_list['system'] = 'core/modules/system/system.module';
+  }
+  if ($profile && !$module_handler->moduleExists($profile)) {
+    $module_list[$profile] = $install_state['profiles'][$profile]->uri;
+  }
+  $module_handler->setModuleList($module_list);
+  // After setting up a custom and finite module list in a custom low-level
+  // bootstrap like here, ensure to use ModuleHandler::loadAll() so that
+  // ModuleHandler::isLoaded() returns TRUE, since that is a condition being
+  // checked by other subsystems (e.g., the theme system).
+  $module_handler->loadAll();
+
   // Prepare for themed output. We need to run this at the beginning of the
   // page request to avoid a different theme accidentally getting set. (We also
   // need to run it even in the case of command-line installations, to prevent
@@ -639,7 +654,7 @@ function install_tasks($install_state) {
     ),
     'install_select_profile' => array(
       'display_name' => t('Choose profile'),
-      'display' => count($install_state['profiles']) != 1,
+      'display' => empty($install_state['profile_info']['distribution']['name']) && count($install_state['profiles']) != 1,
       'run' => INSTALL_TASK_RUN_IF_REACHED,
     ),
     'install_load_profile' => array(
@@ -664,7 +679,7 @@ function install_tasks($install_state) {
       'run' => INSTALL_TASK_RUN_IF_REACHED,
     ),
     'install_profile_modules' => array(
-      'display_name' => count($install_state['profiles']) == 1 ? t('Install site') : t('Installation profile'),
+      'display_name' => t('Install site'),
       'type' => 'batch',
     ),
     'install_import_translations' => array(
@@ -1220,7 +1235,7 @@ function install_select_profile(&$install_state) {
       throw new NoProfilesException(\Drupal::service('string_translation'));
     }
     // Try to automatically select a profile.
-    if ($profile = _install_select_profile($install_state['profiles'])) {
+    if ($profile = _install_select_profile($install_state)) {
       $install_state['parameters']['profile'] = $profile;
     }
     else {
@@ -1235,49 +1250,44 @@ function install_select_profile(&$install_state) {
 }
 
 /**
- * Selects an installation profile.
+ * Determines the installation profile to use in the installer.
  *
- * A profile will be selected if:
- * - Only one profile is available,
- * - A profile was submitted through \Drupal::request()->request,
- * - Exactly one of the profiles is marked as "exclusive".
- * If multiple profiles are marked as "exclusive" then no profile will be
- * selected.
+ * A profile will be selected in the following order of conditions:
  *
- * @param array $profiles
- *   An associative array of profiles with the machine-readable names as keys.
+ * 1. Only one profile is available.
+ * 2. A specific profile name is requested in installation parameters:
+ *    - for interactive installations via request query parameters.
+ *    - for non-interactive installations via install_drupal() settings.
+ * 3. A discovered profile that is a distribution.
+ *    If multiple profiles are distributions, then the first discovered profile
+ *    will be selected.
+ *
+ * @param array $install_state
+ *   The current installer state, containing a 'profiles' key, which is an
+ *   associative array of profiles with the machine-readable names as keys.
  *
  * @return
  *   The machine-readable name of the selected profile or NULL if no profile was
  *   selected.
  */
-function _install_select_profile($profiles) {
+function _install_select_profile(&$install_state) {
   // Don't need to choose profile if only one available.
-  $request_params = \Drupal::request()->request;
-  if (count($profiles) == 1) {
-    $profile = array_pop($profiles);
-    return $profile->getName();
-  }
-  elseif ($request_params->has('profile') && ($profile = $request_params->get('profile')) && isset($profiles[$profile])) {
-    return $profiles[$profile]->getName();
-  }
-  // Check for a profile marked as "exclusive" and ensure that only one
-  // profile is marked as such.
-  $exclusive_profile = NULL;
-  foreach ($profiles as $profile) {
+  if (count($install_state['profiles']) == 1) {
+    return key($install_state['profiles']);
+  }
+  if (!empty($install_state['parameters']['profile'])) {
+    $profile = $install_state['parameters']['profile'];
+    if (isset($install_state['profiles'][$profile])) {
+      return $profile;
+    }
+  }
+  // Check for a distribution profile.
+  foreach ($install_state['profiles'] as $profile) {
     $profile_info = install_profile_info($profile->getName());
-    if (!empty($profile_info['exclusive'])) {
-      if (empty($exclusive_profile)) {
-        $exclusive_profile = $profile->getName();
-      }
-      else {
-        // We found a second "exclusive" profile. There's no way to choose
-        // between them, so we ignore the property.
-        return;
-      }
+    if (!empty($profile_info['distribution'])) {
+      return $profile->getName();
     }
   }
-  return $exclusive_profile;
 }
 
 /**
@@ -1351,6 +1361,14 @@ function install_select_profile_form($form, &$form_state, $install_state) {
   return $form;
 }
 
+/**
+ * Form submission handler for install_select_profile_form().
+ */
+function install_select_profile_form_submit($form, &$form_state) {
+  global $install_state;
+  $install_state['parameters']['profile'] = $form_state['values']['profile'];
+}
+
 /**
  * Finds all .po files that are useful to the installer.
  *
@@ -1755,7 +1773,7 @@ function install_load_profile(&$install_state) {
   $profile_file = $install_state['profiles'][$profile]->uri;
   if (file_exists($profile_file)) {
     include_once DRUPAL_ROOT . '/' . $profile_file;
-    $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['langcode']);
+    $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], isset($install_state['parameters']['langcode']) ? $install_state['parameters']['langcode'] : 'en');
   }
   else {
     throw new InstallerException(t('Sorry, the profile you have chosen cannot be loaded.'));
diff --git a/core/includes/install.inc b/core/includes/install.inc
index c2a138c9375e..4b5466d43ed0 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -95,16 +95,19 @@ function drupal_load_updates() {
 function drupal_install_profile_distribution_name() {
   // During installation, the profile information is stored in the global
   // installation state (it might not be saved anywhere yet).
+  $info = array();
   if (drupal_installation_attempted()) {
     global $install_state;
-    return isset($install_state['profile_info']['distribution_name']) ? $install_state['profile_info']['distribution_name'] : 'Drupal';
+    if (isset($install_state['profile_info'])) {
+      $info = $install_state['profile_info'];
+    }
   }
   // At all other times, we load the profile via standard methods.
   else {
     $profile = drupal_get_profile();
     $info = system_get_info('module', $profile);
-    return $info['distribution_name'];
   }
+  return isset($info['distribution']['name']) ? $info['distribution']['name'] : 'Drupal';
 }
 
 /**
@@ -1046,15 +1049,19 @@ function drupal_check_module($module) {
  * Additional, less commonly-used information that can appear in a
  * profile.info.yml file but not in a normal Drupal module .info.yml file
  * includes:
- * - distribution_name: The name of the Drupal distribution that is being
- *   installed, to be shown throughout the installation process. Defaults to
- *   'Drupal'.
- * - exclusive: If the install profile is intended to be the only eligible
- *   choice in a distribution, setting exclusive = TRUE will auto-select it
- *   during installation, and the install profile selection screen will be
- *   skipped. If more than one profile is found where exclusive = TRUE then
- *   this property will have no effect and the profile selection screen will
- *   be shown as normal with all available profiles shown.
+ *
+ * - distribution: Existence of this key denotes that the installation profile
+ *   is intended to be the only eligible choice in a distribution and will be
+ *   auto-selected during installation, whereas the installation profile
+ *   selection screen will be skipped. If more than one distribution profile is
+ *   found then the first one discovered will be selected.
+ *   The following subproperties may be set:
+ *   - name: The name of the distribution that is being installed, to be shown
+ *     throughout the installation process. If omitted,
+ *     drupal_install_profile_distribution_name() defaults to 'Drupal'.
+ *   - install: Optional parameters to override the installer:
+ *     - theme: The machine name of a theme to use in the installer instead of
+ *       Drupal's default installer theme.
  *
  * Note that this function does an expensive file system scan to get info file
  * information for dependencies. If you only need information from the info
@@ -1084,7 +1091,6 @@ function install_profile_info($profile, $langcode = 'en') {
     $defaults = array(
       'dependencies' => array(),
       'description' => '',
-      'distribution_name' => 'Drupal',
       'version' => NULL,
       'hidden' => FALSE,
       'php' => DRUPAL_MINIMUM_PHP,
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index bbeb9cba7dd6..cfd84278b3d5 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2360,7 +2360,11 @@ function template_preprocess_install_page(&$variables) {
   $variables['attributes']['class'][] = 'install-page';
   // Override the site name that is displayed on the page, since Drupal is
   // still in the process of being installed.
-  $variables['site_name'] = drupal_install_profile_distribution_name();
+  $distribution_name = String::checkPlain(drupal_install_profile_distribution_name());
+  $variables['site_name'] = $distribution_name;
+  $variables['head_title_array']['name'] = $distribution_name;
+
+  $variables['head_title'] = implode(' | ', $variables['head_title_array']);
 }
 
 /**
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 21c8d0748710..429b9329e884 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -34,7 +34,12 @@ function _drupal_maintenance_theme() {
 
   // Install and update pages are treated differently to prevent theming overrides.
   if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {
-    $custom_theme = settings()->get('maintenance_theme', 'seven');
+    if (drupal_installation_attempted()) {
+      $custom_theme = $GLOBALS['install_state']['theme'];
+    }
+    else {
+      $custom_theme = settings()->get('maintenance_theme', 'seven');
+    }
   }
   else {
     // The bootstrap was not complete. So we are operating in a crippled
diff --git a/core/modules/system/lib/Drupal/system/Tests/Installer/DistributionProfileTest.php b/core/modules/system/lib/Drupal/system/Tests/Installer/DistributionProfileTest.php
new file mode 100644
index 000000000000..4e3ae04b8c61
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Installer/DistributionProfileTest.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Installer\DistributionProfileTest.
+ */
+
+namespace Drupal\system\Tests\Installer;
+
+use Drupal\simpletest\InstallerTestBase;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Tests the installer translation detection.
+ */
+class DistributionProfileTest extends InstallerTestBase {
+
+  /**
+   * The distribution profile info.
+   *
+   * @var array
+   */
+  protected $info;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Distribution installation profile test',
+      'description' => 'Tests distribution profile support.',
+      'group' => 'Installer',
+    );
+  }
+
+  protected function setUp() {
+    $this->info = array(
+      'type' => 'profile',
+      'core' => \Drupal::CORE_COMPATIBILITY,
+      'name' => 'Distribution profile',
+      'distribution' => array(
+        'name' => 'My Distribution',
+        'install' => array(
+          'theme' => 'bartik',
+        ),
+      ),
+    );
+    // File API functions are not available yet.
+    $path = $this->siteDirectory . '/profiles/mydistro';
+    mkdir($path, 0777, TRUE);
+    file_put_contents("$path/mydistro.info.yml", Yaml::dump($this->info, PHP_INT_MAX, 2));
+    file_put_contents("$path/mydistro.profile", "<?php\n");
+
+    parent::setUp();
+  }
+
+  /**
+   * Overrides InstallerTest::setUpLanguage().
+   */
+  protected function setUpLanguage() {
+    // Verify that the distribution name appears.
+    $this->assertRaw($this->info['distribution']['name']);
+    // Verify that the requested theme is used.
+    $this->assertRaw($this->info['distribution']['install']['theme']);
+    // Verify that the "Choose profile" step does not appear.
+    $this->assertNoText('profile');
+
+    parent::setUpLanguage();
+  }
+
+  /**
+   * Overrides InstallerTest::setUpProfile().
+   */
+  protected function setUpProfile() {
+    // This step is skipped, because there is a distribution profile.
+  }
+
+  /**
+   * Confirms that the installation succeeded.
+   */
+  public function testInstalled() {
+    $this->assertUrl('user/1');
+    $this->assertResponse(200);
+    // Confirm that we are logged-in after installation.
+    $this->assertText($this->root_user->getUsername());
+  }
+
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index ca908a2792dd..65a8796c9969 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1425,10 +1425,11 @@ function _system_rebuild_module_data() {
   if ($profile && isset($modules[$profile])) {
     // The installation profile is required, if it's a valid module.
     $modules[$profile]->info['required'] = TRUE;
-    // Add a default distribution name if the profile did not provide one. This
-    // matches the default value used in install_profile_info().
-    if (!isset($modules[$profile]->info['distribution_name'])) {
-      $modules[$profile]->info['distribution_name'] = 'Drupal';
+    // Add a default distribution name if the profile did not provide one.
+    // @see install_profile_info()
+    // @see drupal_install_profile_distribution_name()
+    if (!isset($modules[$profile]->info['distribution']['name'])) {
+      $modules[$profile]->info['distribution']['name'] = 'Drupal';
     }
   }
 
-- 
GitLab