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