From a33b43386dee4b19be0f3bd2b5d4eabd54d47b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Hojtsy?= Date: Sat, 8 Dec 2007 15:15:25 +0000 Subject: #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 04f7939b0..d68185e91 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 64087717a..c249a4175 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 026973585..45655c58a 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 d56a7c89d..94d30cf88 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'] .= '

'. $title .':

'; $variables['messages'] .= theme('status_messages', 'status'); } diff --git a/install.php b/install.php index 5f8bff125..f01dcbb0a 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 on-line handbook.', 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 on-line handbook.', 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 @@ if (Drupal.jsEnabled) { // Display a 'finished' page to user. if ($task == 'finished') { drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name()))); - $output = '

'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'

'; $messages = drupal_set_message(); + $output = '

'. st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) .'

'; $output .= '

'. (isset($messages['error']) ? st('Please review the messages above before continuing on to your new site.', array('@url' => url(''))) : st('You may now visit your new site.', array('@url' => url('')))) .'

'; $task = 'done'; } @@ -777,6 +806,29 @@ if (Drupal.jsEnabled) { } } +/** + * 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 f8363e9ab..cf227dc26 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 -- cgit v1.2.3