From 258c653f56ce7dd7dddcbe7c3a46c678dcb79b36 Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Thu, 10 May 2007 19:55:24 +0000 Subject: - Patch #141637 by merlinofchaos, gabor, et al: provide a site config form at the end of install to collect data, plus allow profiles to modify and add more. --- CHANGELOG.txt | 4 + install.php | 323 ++++++++++++++++++++++++++++++++------- modules/node/node.module | 4 - modules/system/system.css | 7 + modules/system/system.install | 8 + modules/system/system.js | 32 ++++ modules/user/user.module | 6 +- profiles/default/default.profile | 44 +++++- 8 files changed, 363 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1d3a737ad..876f45322 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -32,6 +32,10 @@ Drupal 6.0, xxxx-xx-xx (development version) * Used the Garland theme for the installation and maintenance pages. * Added theme preprocess functions for themes that are templates - Refactored update.php to a generic batch API to be able to run time consuming operations in multiple subsequent HTTP requests +- Installer: + * Themed the installer with the Garland theme. + * Added form to provide initial site information during installation. + * Added ability to provide extra installation steps programmatically. Drupal 5.0, 2007-01-15 ---------------------- diff --git a/install.php b/install.php index fe01c1f2d..453fed165 100644 --- a/install.php +++ b/install.php @@ -27,16 +27,19 @@ function install_main() { // Check existing settings.php. $verify = install_verify_settings(); - // Drupal may already be installed. if ($verify) { // Establish a connection to the database. require_once './includes/database.inc'; db_set_active(); // Check if Drupal is installed. - if (install_verify_drupal()) { + $task = install_verify_drupal(); + if ($task == 'done') { install_already_done_error(); } } + else { + $task = NULL; + } // Load module basics (needed for hook invokes). include_once './includes/module.inc'; @@ -57,6 +60,9 @@ function install_main() { install_no_profile_error(); } + // Load the profile. + require_once "./profiles/$profile/$profile.profile"; + // Locale selection if (!empty($_GET['locale'])) { $install_locale = preg_replace('/[^a-zA-Z_0-9]/', '', $_GET['locale']); @@ -65,46 +71,49 @@ function install_main() { install_goto("install.php?profile=$profile&locale=$install_locale"); } - // Load the profile. - require_once "./profiles/$profile/$profile.profile"; - - // Check the installation requirements for Drupal and this profile. - install_check_requirements($profile); + // Tasks come after the database is set up + if (!$task) { + // Check the installation requirements for Drupal and this profile. + install_check_requirements($profile); - // Change the settings.php information if verification failed earlier. - // Note: will trigger a redirect if database credentials change. - if (!$verify) { - install_change_settings($profile, $install_locale); - } + // Verify existence of all required modules. + $modules = drupal_verify_profile($profile, $install_locale); + if (!$modules) { + install_missing_modules_error($profile); + } - // Verify existence of all required modules. - $modules = drupal_verify_profile($profile, $install_locale); - if (!$modules) { - install_missing_modules_error($profile); - } + // Change the settings.php information if verification failed earlier. + // Note: will trigger a redirect if database credentials change. + if (!$verify) { + install_change_settings($profile, $install_locale); + } - // Perform actual installation defined in the profile. - drupal_install_profile($profile, $modules); + // Perform actual installation defined in the profile. + drupal_install_profile($profile, $modules); - // Warn about settings.php permissions risk - $settings_file = './'. conf_path() .'/settings.php'; - if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE)) { - drupal_set_message(st('All necessary changes to %file have been made, so you should now remove write permissions to this file. Failure to remove write permissions to this file is a security risk.', array('%file' => $settings_file)), 'error'); - } - else { - drupal_set_message(st('All necessary changes to %file have been made. It has been set to read-only for security.', array('%file' => $settings_file))); + // Warn about settings.php permissions risk + $settings_file = './'. conf_path() .'/settings.php'; + if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE)) { + drupal_set_message(st('All necessary changes to %file have been made, so you should now remove write permissions to this file. Failure to remove write permissions to this file is a security risk.', array('%file' => $settings_file)), 'error'); + } + else { + drupal_set_message(st('All necessary changes to %file have been made. It has been set to read-only for security.', array('%file' => $settings_file))); + } } - // Show end page. - install_complete($profile); + // The database is set up, turn to further tasks. + install_tasks($profile, $task); } /** * Verify if Drupal is installed. */ function install_verify_drupal() { - $result = @db_query("SELECT name FROM {system} WHERE name = 'system'"); - return $result && db_result($result) == 'system'; + // Read the variable manually using the @ so we don't trigger an error if it fails. + $result = @db_query("SELECT value FROM {variable} WHERE name = 'install_task'"); + if ($result) { + return unserialize(db_result($result)); + } } /** @@ -114,7 +123,7 @@ function install_verify_settings() { global $db_prefix, $db_type, $db_url; // Verify existing settings (if any). - if ($_SERVER['REQUEST_METHOD'] == 'GET' && !empty($db_url)) { + if (!empty($db_url)) { // We need this because we want to run form_get_errors. include_once './includes/form.inc'; @@ -548,47 +557,90 @@ function install_missing_modules_error($profile) { global $base_url; drupal_maintenance_theme(); - install_task_list('install'); + install_task_list('requirements'); drupal_set_title(st('Modules missing')); print theme('install_page', '

'. st('One or more required modules are missing. Please check the error messages and try again.', array('!url' => "install.php?profile=$profile")) .'

'); exit; } /** - * Page displayed when the installation is complete. Called from install.php. + * Tasks performed after the database is initialized. Called from install.php. */ -function install_complete($profile) { +function install_tasks($profile, $task) { global $base_url; $output = ''; - // Store install profile for later use. - variable_set('install_profile', $profile); // Bootstrap newly installed Drupal, while preserving existing messages. $messages = $_SESSION['messages']; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); $_SESSION['messages'] = $messages; - // Build final page. + // Build a page for a final task. drupal_maintenance_theme(); - install_task_list(); - 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())) .'

'; - - // Show profile finalization info. - $function = $profile .'_profile_final'; - if (function_exists($function)) { - // More steps required - $profile_message = $function(); + if (empty($task)) { + variable_set('install_task', 'configure'); + $task = 'configure'; } - menu_rebuild(); - // If the profile returned a welcome message, use that instead of default. - if (isset($profile_message)) { - $output .= $profile_message; + if ($task == 'configure') { + drupal_set_title(st('Configure site')); + menu_rebuild(); + + // We break the form up so we can tell when it's been successfully + // submitted. + $form = drupal_retrieve_form('install_configure_form'); + + // In order to find out if the form was successfully submitted or not, + // we do a little song and dance to set the form to 'programmed' and check + // to make sure this is really the form being submitted. It'd better be. + if ($_POST && $_POST['form_id'] == 'install_configure_form') { + $form['#programmed'] = TRUE; + $form['#post'] = $_POST; + } + + if (!drupal_process_form('install_configure_form', $form)) { + $output = drupal_render_form('install_configure_form', $form); + install_task_list('configure'); + } } - else { - // No more steps - $output .= '

'. (drupal_set_message() ? 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('')))) .'

'; + + // If we have no output, then install.php is done and now we turn to + // our profile to run it's own tasks. + if (empty($output)) { + // Profile might define more tasks. + $function = $profile .'_profile_final'; + if (function_exists($function)) { + // More tasks are required by this profile. + // $task is sent through as a reference and may be changed! + $output = $function($task); + } + + // Safety: if the profile doesn't do anything, catch it. + if ($task == 'configure') { + $task = 'finished'; + } + + // Display default 'finished' page to user. A custom finished page + // can be displayed by skipping this step and going to 'done' directly. + if ($task == 'finished') { + drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name()))); + $page = '

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

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

'. (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('')))) .'

'; + $output = $page; + $task = 'done'; + } + + // The end of the install process. Remember profile used. + if ($task == 'done') { + variable_set('install_profile', $profile); + } + + // Set task for user, and remember the task in the database. + install_task_list($task); + variable_set('install_task', $task); + } // Output page. print theme('maintenance_page', $output); @@ -628,20 +680,179 @@ function install_task_list($active = NULL) { 'locale' => st('Choose language'), 'requirements' => st('Verify requirements'), 'database' => st('Database setup'), - 'install' => st('Installation'), + 'configure' => st('Configure site'), ); + $profiles = install_find_profiles(); // Remove profiles if only one profile exists. - if (count(install_find_profiles()) == 1) { + if (count($profiles) == 1) { unset($tasks['profile']); } // Remove locale if no install profiles use them. - if (count(install_find_locales('.')) == 1) { + $profile = isset($_GET['profile']) && isset($profiles[$_GET['profile']]) ? $_GET['profile'] : '.'; + if (count(install_find_locales($profile)) == 1) { unset($tasks['locale']); } + if ($profile) { + $function = $profile .'_profile_task_list'; + if (function_exists($function)) { + $result = $function(); + if (is_array($result)) { + $tasks += $result; + } + } + } + + // Add finished step as the last task. + $tasks += array('finished' => st('Finished')); + + // Let the theming function know that 'finished' and 'done' + // include everything, so every step is completed. + if (in_array($active, array('finished', 'done'))) { + $active = NULL; + } drupal_set_content('left', theme_task_list($tasks, $active)); } +function install_configure_form() { + // This is necessary to add the task to the $_GET args so the install + // system will know that it is done and we've taken over. + + $form['intro'] = array( + '#value' => st('To configure your web site, please provide the following information.'), + '#weight' => -10, + ); + $form['site_information'] = array( + '#type' => 'fieldset', + '#title' => st('Site information'), + '#collapsible' => FALSE, + ); + $form['site_information']['site_name'] = array( + '#type' => 'textfield', + '#title' => st('Site name'), + '#default_value' => variable_get('site_name', 'Drupal'), + '#required' => TRUE, + '#weight' => -20, + ); + $form['site_information']['site_mail'] = array( + '#type' => 'textfield', + '#title' => st('Site e-mail address'), + '#default_value' => variable_get('site_mail', ini_get('sendmail_from')), + '#description' => st('A valid e-mail address to be used as the "From" address by the auto-mailer during registration, new password requests, notifications, etc. To lessen the likelihood of e-mail being marked as spam, this e-mail address should use the same domain as the website.'), + '#required' => TRUE, + '#weight' => -15, + ); + $form['admin_account'] = array( + '#type' => 'fieldset', + '#title' => st('Administrator account'), + '#collapsible' => FALSE, + ); + $form['admin_account']['account']['#tree'] = TRUE; + $form['admin_account']['markup'] = array( + '#value' => '

'. st('The administrator account has complete access to the site; it will automatically be granted all permissions and can perform any administrative activity. This will be the only account that can perform certain activities, so keep its credentials safe.') .'

', + '#weight' => -10, + ); + + $form['admin_account']['account']['name'] = array('#type' => 'textfield', + '#title' => st('Username'), + '#maxlength' => USERNAME_MAX_LENGTH, + '#description' => st('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'), + '#required' => TRUE, + '#weight' => -10, + ); + + $form['admin_account']['account']['mail'] = array('#type' => 'textfield', + '#title' => st('E-mail address'), + '#maxlength' => EMAIL_MAX_LENGTH, + '#description' => st('All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'), + '#required' => TRUE, + '#weight' => -5, + ); + $form['admin_account']['account']['pass'] = array( + '#type' => 'password_confirm', + '#required' => TRUE, + '#size' => 25, + '#weight' => 0, + ); + + $zones = _system_zonelist(); + + $form['server_settings'] = array( + '#type' => 'fieldset', + '#title' => st('Server settings'), + '#collapsible' => FALSE, + ); + $form['server_settings']['date_default_timezone'] = array( + '#type' => 'select', + '#title' => st('Default time zone'), + '#default_value' => variable_get('date_default_timezone', 0), + '#options' => $zones, + '#description' => st('By default, dates in this site will be displayed in the chosen time zone.'), + '#weight' => 5, + ); + + drupal_add_js(drupal_get_path('module', 'system') .'/system.js', 'module'); + drupal_add_js(array('cleanURL' => array('success' => st('Your server has been successfully tested to support this feature.'), 'failure' => st('Your system configuration does not currently support this feature. The handbook page on Clean URLs has additional troubleshooting information.'), 'testing' => st('Testing clean URLs...'))), 'setting'); + drupal_add_js(' +// Global Killswitch +if (Drupal.jsEnabled) { + $(document).ready(function() { + Drupal.cleanURLsInstallCheck(); + Drupal.installDefaultTimezone(); + }); +}', 'inline'); + + $form['server_settings']['clean_url'] = array( + '#type' => 'radios', + '#title' => st('Clean URLs'), + '#default_value' => variable_get('clean_url', 0), + '#options' => array(st('Disabled'), st('Enabled')), + '#description' => st('This option makes Drupal emit "clean" URLs (i.e. without ?q= in the URL).'), + '#disabled' => TRUE, + '#prefix' => '
', + '#suffix' => '
', + '#weight' => 10, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => st('Submit'), + '#weight' => 15, + ); + $hook_form_alter = $_GET['profile'] .'_form_alter'; + if (function_exists($hook_form_alter)) { + $form = $hook_form_alter($form, 'install_configure'); + } + return $form; +} + +function install_configure_form_validate($form_id, $form_values, $form) { + if ($error = user_validate_name($form_values['account']['name'])) { + form_error($form['admin_account']['account']['name'], $error); + } + if ($error = user_validate_mail($form_values['account']['mail'])) { + form_error($form['admin_account']['account']['mail'], $error); + } + if ($error = user_validate_mail($form_values['site_mail'])) { + form_error($form['site_information']['site_mail'], $error); + } +} + +function install_configure_form_submit($form_id, $form_values) { + variable_set('site_name', $form_values['site_name']); + variable_set('site_mail', $form_values['site_mail']); + variable_set('date_default_timezone', $form_values['date_default_timezone']); + // Turn this off temporarily so that we can pass a password through. + variable_set('user_email_verification', FALSE); + user_register_submit('user_register', $form_values['account']); + variable_set('user_email_verification', TRUE); + if (isset($form_values['clean_url'])) { + variable_set('clean_url', $form_values['clean_url']); + } + + return 'finished'; +} + install_main(); diff --git a/modules/node/node.module b/modules/node/node.module index fcba35b7d..c96606a94 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -2448,10 +2448,6 @@ function node_page_default() { $default_message = t('

Welcome to your new Drupal website!

Please follow these steps to set up and start using your website:

'); $default_message .= '
    '; - - if (!$admin) { - $default_message .= '
  1. '. t('Create your administrator account To begin, create the first account. This account will have full administration rights and will allow you to configure your website.', array('@register' => url('user/register'))) .'
  2. '; - } $default_message .= '
  3. '. t('Configure your website Once logged in, visit the administration section, where you can customize and configure all aspects of your website.', array('@admin' => url('admin'), '@config' => url('admin/settings'))) .'
  4. '; $default_message .= '
  5. '. t('Enable additional functionality Next, visit the module list and enable features which suit your specific needs. You can find additional modules in the Drupal modules download section.', array('@modules' => url('admin/build/modules'), '@download_modules' => 'http://drupal.org/project/modules')) .'
  6. '; $default_message .= '
  7. '. t('Customize your website design To change the "look and feel" of your website, visit the themes section. You may choose from one of the included themes or download additional themes from the Drupal themes download section.', array('@themes' => url('admin/build/themes'), '@download_themes' => 'http://drupal.org/project/themes')) .'
  8. '; diff --git a/modules/system/system.css b/modules/system/system.css index 2485269bc..ebe41c55a 100644 --- a/modules/system/system.css +++ b/modules/system/system.css @@ -431,3 +431,10 @@ tr.selected td { thead div.sticky-header { background: #fff; } + +/* +** Installation clean URLs +*/ +#clean-url.install { + display: none; +} diff --git a/modules/system/system.install b/modules/system/system.install index 62e582d64..b1fda9b22 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -3873,6 +3873,14 @@ function system_update_6013() { system_theme_data(); } +/** + * Record that the installer is done, so it is not + * possible to run the installer on upgraded sites. + */ +function system_update_6014() { + variable_set('install_task', 'done'); +} + /** * @} End of "defgroup updates-5.x-to-6.x" diff --git a/modules/system/system.js b/modules/system/system.js index a77c7ac99..0c88c9f06 100644 --- a/modules/system/system.js +++ b/modules/system/system.js @@ -24,3 +24,35 @@ Drupal.cleanURLsSettingsCheck = function() { } }}); } + +/** + * Internal function to check using Ajax if clean URLs can be enabled on the + * install page. + * + * This function is not used to verify whether or not clean URLs + * are currently enabled. + */ +Drupal.cleanURLsInstallCheck = function() { + var pathname = location.pathname +""; + var url = pathname.replace(/\/[^\/]*?$/, '/') +"node"; + $("#clean-url .description").append('
    '+ Drupal.settings.cleanURL.testing +"
    "); + $("#clean-url.install").css("display", "block"); + $.ajax({url: location.protocol +"//"+ location.host + url, type: "GET", data: " ", complete: function(response) { + $("#testing").toggle(); + if (response.status == 200) { + // Check was successful. + $("#clean-url input.form-radio").attr("disabled", ""); + $("#clean-url .description span").append('
    '+ Drupal.settings.cleanURL.success +"
    "); + $("#clean-url input.form-radio").attr("checked", 1); + } + else { + // Check failed. + $("#clean-url .description span").append('
    '+ Drupal.settings.cleanURL.failure +"
    "); + } + }}); +} + +Drupal.installDefaultTimezone = function() { + var offset = new Date().getTimezoneOffset() * -60; + $("#edit-date-default-timezone").val(offset); +} diff --git a/modules/user/user.module b/modules/user/user.module index d4ddc76e7..ff3ee7b33 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1374,8 +1374,10 @@ function user_register_submit($form_id, $form_values) { // The first user may login immediately, and receives a customized welcome e-mail. if ($account->uid == 1) { - drupal_mail('user-register-admin', $mail, t('Drupal user account details for !s', array('!s' => $name)), strtr(t("!username,\n\nYou may now login to !uri using the following username and password:\n\n username: !username\n password: !password\n\n!edit_uri\n\n--drupal"), $variables), $from); - drupal_set_message(t('

    Welcome to Drupal. You are user #1, which gives you full and immediate access. All future registrants will receive their passwords via e-mail, so please make sure your website e-mail address is set properly under the general settings on the site information settings page.

    Your password is %pass. You may change your password below.

    ', array('%pass' => $pass, '@settings' => url('admin/settings/site-information')))); + drupal_set_message(t('

    Welcome to Drupal. You are now logged in as user #1, which gives you full control over your website.

    ')); + if (variable_get('user_email_verification', TRUE)) { + drupal_set_message(t('

    Your password is %pass. You may change your password below.

    ', array('%pass' => $pass))); + } user_authenticate($account->name, trim($pass)); return 'user/1/edit'; diff --git a/profiles/default/default.profile b/profiles/default/default.profile index d80e15d6e..361d1f9ee 100644 --- a/profiles/default/default.profile +++ b/profiles/default/default.profile @@ -24,14 +24,49 @@ function default_profile_details() { ); } +/** + * Return a list of tasks that this profile supports. + * + * @return + * A keyed array of tasks the profile will perform during the _final stage. + */ +function default_profile_task_list() { +} + /** * Perform any final installation tasks for this profile. * + * You can implement a state machine here to walk the user through + * more tasks, by setting $task to something other then the reserved + * 'configure', 'finished' and 'done' values. The installer goes + * through the configure-finished-done tasks in this order, if you + * don't modify $task. If you implement your custom tasks, this + * function will get called in every HTTP request (for form + * processing, printing your information screens and so on) until + * you advance to the 'finished' or 'done' tasks. Once ready with + * your profile's tasks, set $task to 'finished' and optionally + * return a final message to be included on the default final + * install page. Alternatively you can set $task to 'done' and + * return a completely custom finished page. In both cases, you + * hand the control back to the installer. + * + * Should a profile want to display a form here, it can; it should set + * the task using variable_set('install_task', 'new_task') and use + * the form technique used in install_tasks() rather than using + * drupal_get_form(). + * + * @param $task + * The current $task of the install system. When hook_profile_final() + * is first called, this is 'configure' (the last built-in task of + * the Drupal installer). + * * @return - * An optional HTML string to display to the user on the final installation - * screen. + * An optional HTML string to display to the user. Used as part of the + * completed page if $task is set to 'finished', or used to display a + * complete page in all other cases. */ -function default_profile_final() { +function default_profile_final(&$task) { + // Insert default user-defined node types into the database. For a complete // list of available node type attributes, refer to the node type API // documentation at: http://api.drupal.org/api/HEAD/function/hook_node_info. @@ -73,4 +108,7 @@ function default_profile_final() { $theme_settings = variable_get('theme_settings', array()); $theme_settings['toggle_node_info_page'] = FALSE; variable_set('theme_settings', $theme_settings); + + // Let the installer know we're finished: + $task = 'finished'; } -- cgit v1.2.3