From a33b43386dee4b19be0f3bd2b5d4eabd54d47b21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Hojtsy?= <gabor@hojtsy.hu>
Date: Sat, 8 Dec 2007 15:15:25 +0000
Subject: [PATCH] #176003 by yched, KarenS, dvessel: put module installs into a
 batch, solving the following issues:  - possible timeouts with
 installing/enabling lots of modules at once in core  - enable install
 profiles to have more modules without fear of timeouts on install  -
 bootstrap Drupal before each module load, so previously enabled modules are
 bootstrapped  - let modules run their hook_requirements() (although actually
 calling them will be possibly fixed in another patch)

---
 includes/install.inc             |  65 +++++++++---------
 includes/module.inc              |   5 +-
 includes/theme.inc               |   2 +-
 includes/theme.maintenance.inc   |   2 +-
 install.php                      | 111 +++++++++++++++++++++++--------
 profiles/default/default.profile |   7 +-
 6 files changed, 124 insertions(+), 68 deletions(-)

diff --git a/includes/install.inc b/includes/install.inc
index 04f7939b0c56..d68185e91453 100644
--- a/includes/install.inc
+++ b/includes/install.inc
@@ -294,19 +294,41 @@ function drupal_verify_profile($profile, $locale) {
 }
 
 /**
- * Install a profile (i.e. a set of modules) from scratch.
- * The profile must be verified first using drupal_verify_profile().
+ * Calls the install function and updates the system table for a given list of
+ * modules.
  *
- * @param profile
- *   The name of the profile to install.
  * @param module_list
- *   An array of modules to install.
+ *   The modules to install.
+ */
+function drupal_install_modules($module_list = array()) {
+  array_filter($module_list, '_drupal_install_module');
+  module_enable($module_list);
+}
+
+/**
+ * Callback to install an individual profile module.
+ *
+ * Used during installation to install modules one at a time and then
+ * enable them, or to install a number of modules at one time
+ * from admin/build/modules.
  */
-function drupal_install_profile($profile, $module_list) {
-  // The system module is a special case; we can't bootstrap until it's
-  // installed, so we can't use the normal installation function.
-  $module_list = array_diff($module_list, array('system'));
+function _drupal_install_module($module) {
+  if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
+    module_load_install($module);
+    module_invoke($module, 'install');
+    $versions = drupal_get_schema_versions($module);
+    drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED);
+    return TRUE;
+  }
+}
 
+/**
+ * Callback to install the system module.
+ *
+ * Separated from the installation of other modules so core system
+ * functions can be made available while other modules are installed.
+ */
+function drupal_install_system() {
   $system_path = dirname(drupal_get_filename('module', 'system', NULL));
   require_once './'. $system_path .'/system.install';
   module_invoke('system', 'install');
@@ -315,34 +337,9 @@ function drupal_install_profile($profile, $module_list) {
   db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version) VALUES('%s', '%s', '%s', '%s', %d, %d, %d, %d)", $system_path .'/system.module', 'system', 'module', '', 1, 0, 0, $system_version);
   // Now that we've installed things properly, bootstrap the full Drupal environment
   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-
-  // Install schemas for profile and all its modules.
   module_rebuild_cache();
-  drupal_install_modules($module_list);
 }
 
-/**
- * Calls the install function and updates the system table for a given list of
- * modules.
- *
- * @param module_list
- *   The modules to install.
- */
-function drupal_install_modules($module_list = array()) {
-  $enable_modules = array();
-
-  foreach ($module_list as $module) {
-    if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
-      module_load_install($module);
-      module_invoke($module, 'install');
-      $versions = drupal_get_schema_versions($module);
-      drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED);
-      $enable_modules[] = $module;
-    }
-  }
-
-  module_enable($enable_modules);
-}
 
 /**
  * Calls the uninstall function and updates the system table for a given module.
diff --git a/includes/module.inc b/includes/module.inc
index 64087717a147..c249a417589d 100644
--- a/includes/module.inc
+++ b/includes/module.inc
@@ -261,7 +261,10 @@ function module_enable($module_list) {
   foreach ($invoke_modules as $module) {
     module_invoke($module, 'enable');
     // Check if node_access table needs rebuilding.
-    if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
+    // We check for the existence of node_access_needs_rebuild() since
+    // at install time, module_enable() could be called while node.module
+    // is not enabled yet.
+    if (function_exists('node_access_needs_rebuild') && !node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
       node_access_needs_rebuild(TRUE);
     }
   }
diff --git a/includes/theme.inc b/includes/theme.inc
index 02697358598b..45655c58ade0 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -1632,7 +1632,7 @@ function template_preprocess(&$variables, $hook) {
   $variables['is_admin']            = FALSE;
   $variables['is_front']            = FALSE;
   $variables['logged_in']           = FALSE;
-  if ($variables['db_is_active'] = db_is_active()) {
+  if ($variables['db_is_active'] = db_is_active()  && !defined('MAINTENANCE_MODE')) {
     // Check for administrators.
     if (user_access('access administration pages')) {
       $variables['is_admin'] = TRUE;
diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc
index d56a7c89dc63..94d30cf880ee 100644
--- a/includes/theme.maintenance.inc
+++ b/includes/theme.maintenance.inc
@@ -127,7 +127,7 @@ function theme_install_page($content) {
 
   // Special handling of status messages
   if (isset($messages['status'])) {
-    $warnings = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored');
+    $title = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored');
     $variables['messages'] .= '<h4>'. $title .':</h4>';
     $variables['messages'] .= theme('status_messages', 'status');
   }
diff --git a/install.php b/install.php
index 5f8bff12555b..f01dcbb0a0f5 100644
--- a/install.php
+++ b/install.php
@@ -119,18 +119,12 @@ function install_main() {
       install_change_settings($profile, $install_locale);
     }
 
-    // Perform actual installation defined in the profile.
-    drupal_install_profile($profile, $modules);
-
-    // Warn about settings.php permissions risk
-    $settings_dir = './'. conf_path();
-    $settings_file = $settings_dir .'/settings.php';
-    if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file($settings_dir, FILE_NOT_WRITABLE, 'dir')) {
-      drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, please consult the <a href="@handbook_url">on-line handbook</a>.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/getting-started')), 'error');
-    }
-    else {
-      drupal_set_message(st('All necessary changes to %dir and %file have been made. They have been set to read-only for security.', array('%dir' => $settings_dir, '%file' => $settings_file)));
-    }
+    // Install system.module.
+    drupal_install_system();
+    // Save the list of other modules to install for the 'profile-install'
+    // task. variable_set() can be used now that system.module is installed
+    // and drupal is bootstrapped.
+    variable_set('install_profile_modules', array_diff($modules, array('system')));
   }
 
   // The database is set up, turn to further tasks.
@@ -624,13 +618,43 @@ function install_tasks($profile, $task) {
 
   // Build a page for final tasks.
   if (empty($task)) {
-    variable_set('install_task', 'locale-initial-import');
-    $task = 'locale-initial-import';
+    variable_set('install_task', 'profile-install');
+    $task = 'profile-install';
   }
 
   // We are using a list of if constructs here to allow for
   // passing from one task to the other in the same request.
 
+  // Install profile modules.
+  if ($task == 'profile-install') {
+    $modules = variable_get('install_profile_modules', array());
+    $files = module_rebuild_cache();
+    variable_del('install_profile_modules');
+    $operations = array();
+    foreach ($modules as $module) {
+      $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name']));
+    }
+    $batch = array(
+      'operations' => $operations,
+      'finished' => '_install_profile_batch_finished',
+      'title' => t('Installing @drupal', array('@drupal' => drupal_install_profile_name())),
+      'error_message' => t('The installation has encountered an error.'),
+    );
+    // Start a batch, switch to 'profile-install-batch' task. We need to
+    // set the variable here, because batch_process() redirects.
+    variable_set('install_task', 'profile-install-batch');
+    batch_set($batch);
+    batch_process($url, $url);
+  }
+  // We are running a batch install of the profile's modules.
+  // This might run in multiple HTTP requests, constantly redirecting
+  // to the same address, until the batch finished callback is invoked
+  // and the task advances to 'locale-initial-import'.
+  if ($task == 'profile-install-batch') {
+    include_once 'includes/batch.inc';
+    $output = _batch_page();
+  }
+
   // Import interface translations for the enabled modules.
   if ($task == 'locale-initial-import') {
     if (!empty($install_locale) && ($install_locale != 'en')) {
@@ -652,11 +676,6 @@ function install_tasks($profile, $task) {
     // Found nothing to import or not foreign language, go to next task.
     $task = 'configure';
   }
-
-  // We are running a batch import of interface translation files.
-  // This might run in multiple HTTP requests, constantly redirecting
-  // to the same address, until the batch finished callback is invoked
-  // and the task advances to 'configure'.
   if ($task == 'locale-initial-batch') {
     include_once 'includes/batch.inc';
     include_once 'includes/locale.inc';
@@ -677,6 +696,16 @@ function install_tasks($profile, $task) {
       $output = $form;
       drupal_set_title(st('Configure site'));
 
+      // Warn about settings.php permissions risk
+      $settings_dir = './'. conf_path();
+      $settings_file = $settings_dir .'/settings.php';
+      if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file($settings_dir, FILE_NOT_WRITABLE, 'dir')) {
+        drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, please consult the <a href="@handbook_url">on-line handbook</a>.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/getting-started')), 'error');
+      }
+      else {
+        drupal_set_message(st('All necessary changes to %dir and %file have been made. They have been set to read-only for security.', array('%dir' => $settings_dir, '%file' => $settings_file)));
+      }
+
       // Add JavaScript validation.
       _user_password_dynamic_validation();
       drupal_add_js(drupal_get_path('module', 'system') .'/system.js', 'module');
@@ -748,8 +777,8 @@ function install_tasks($profile, $task) {
   // Display a 'finished' page to user.
   if ($task == 'finished') {
     drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())));
-    $output = '<p>'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'</p>';
     $messages = drupal_set_message();
+    $output = '<p>'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'</p>';
     $output .= '<p>'. (isset($messages['error']) ? st('Please review the messages above before continuing on to <a href="@url">your new site</a>.', array('@url' => url(''))) : st('You may now visit <a href="@url">your new site</a>.', array('@url' => url('')))) .'</p>';
     $task = 'done';
   }
@@ -777,6 +806,29 @@ function install_tasks($profile, $task) {
   }
 }
 
+/**
+ * Batch callback for batch installation of modules.
+ */
+function _install_module_batch($module, $module_name, &$context) {
+  _drupal_install_module($module);
+  // We enable the installed module right away, so that the module will be
+  // loaded by drupal_bootstrap in subsequent batch requests, and other
+  // modules possibly depending on it can safely perform their installation
+  // steps.
+  module_enable(array($module));
+  $context['results'][] = $module;
+  $context['message'] = 'Installed '. $module_name. ' module.';
+}
+
+/**
+ * Finished callback for the modules install batch.
+ *
+ * Advance installer task to language import.
+ */
+function _install_profile_batch_finished($success, $results) {
+  variable_set('install_task', 'locale-initial-import');
+}
+
 /**
  * Finished callback for the first locale import batch.
  *
@@ -799,7 +851,7 @@ function _install_locale_remaining_batch_finished($success, $results) {
  * The list of reserved tasks to run in the installer.
  */
 function install_reserved_tasks() {
-  return array('configure', 'locale-initial-import', 'locale-initial-batch', 'profile-finished', 'locale-remaining-batch', 'finished', 'done');
+  return array('configure', 'profile-install', 'profile-install-batch', 'locale-initial-import', 'locale-initial-batch', 'profile-finished', 'locale-remaining-batch', 'finished', 'done');
 }
 
 /**
@@ -855,21 +907,24 @@ function install_check_requirements($profile, $verify) {
 function install_task_list($active = NULL) {
   // Default list of tasks.
   $tasks = array(
-    'profile-select'       => st('Choose profile'),
-    'locale-select'        => st('Choose language'),
-    'requirements'         => st('Verify requirements'),
-    'database'             => st('Set up database'),
-    'locale-initial-batch' => st('Set up translations'),
-    'configure'            => st('Configure site'),
+    'profile-select'        => st('Choose profile'),
+    'locale-select'         => st('Choose language'),
+    'requirements'          => st('Verify requirements'),
+    'database'              => st('Set up database'),
+    'profile-install-batch' => st('Install profile'),
+    'locale-initial-batch'  => st('Set up translations'),
+    'configure'             => st('Configure site'),
   );
 
   $profiles = install_find_profiles();
   $profile = isset($_GET['profile']) && isset($profiles[$_GET['profile']]) ? $_GET['profile'] : '.';
   $locales = install_find_locales($profile);
 
-  // Remove select profile if we have only one.
+  // If we have only one profile, remove 'Choose profile'
+  // and rename 'Install profile'.
   if (count($profiles) == 1) {
     unset($tasks['profile-select']);
+    $tasks['profile-install-batch'] = st('Install site');
   }
 
   // Add tasks defined by the profile.
diff --git a/profiles/default/default.profile b/profiles/default/default.profile
index f8363e9ab813..cf227dc2600c 100644
--- a/profiles/default/default.profile
+++ b/profiles/default/default.profile
@@ -42,9 +42,10 @@ function default_profile_task_list() {
  * Perform any final installation tasks for this profile.
  *
  * The installer goes through the profile-select -> locale-select
- * -> requirements -> database -> locale-initial-batch -> configure
- * -> locale-remaining-batch -> finished -> done tasks in this order,
- * if you don't implement this function in your profile.
+ * -> requirements -> database -> profile-install-batch
+ * -> locale-initial-batch -> configure -> locale-remaining-batch
+ * -> finished -> done tasks, in this order, if you don't implement
+ * this function in your profile.
  *
  * If this function is implemented, you can have any number of
  * custom tasks to perform after 'configure', implementing a state
-- 
GitLab