Index: install.php
===================================================================
RCS file: /cvs/drupal/drupal/install.php,v
retrieving revision 1.185
diff -u -p -r1.185 install.php
--- install.php	19 Jul 2009 04:48:09 -0000	1.185
+++ install.php	20 Jul 2009 04:19:44 -0000
@@ -14,17 +14,171 @@ require_once DRUPAL_ROOT . '/includes/in
 define('MAINTENANCE_MODE', 'install');
 
 /**
- * The Drupal installation happens in a series of steps. We begin by verifying
- * that the current environment meets our minimum requirements. We then go
- * on to verify that settings.php is properly configured. From there we
- * connect to the configured database and verify that it meets our minimum
- * requirements. Finally we can allow the user to select an installation
- * profile and complete the installation process.
+ * Global flag to indicate that a task should not be run during the current
+ * installation request.
+ */
+define('INSTALL_SKIP_TASK', 1);
+
+/**
+ * Global flag to indicate that a task should be run on each installation
+ * request that reaches it.
+ */
+define ('INSTALL_RUN_TASK_IF_REACHED', 2);
+
+/**
+ * Global flag to indicate that a task should be run on each installation
+ * request that reaches it, until the database is set up and we are able to
+ * record the fact that it already ran.
+ */
+define('INSTALL_RUN_TASK_IF_NOT_COMPLETED', 3);
+
+/**
+ * Install Drupal either interactively or via an array of passed-in settings.
+ *
+ * The Drupal installation happens in a series of steps, which may be spread
+ * out over multiple page requests. Each request begins by trying to determine
+ * the last completed installation step (also known as a "task"), if one is
+ * available from a previous request. Control is then passed to the task
+ * handler, which processes the remaining tasks that need to be run until (a)
+ * an error is thrown, (b) a new page needs to be displayed, or (c) the
+ * installation finishes (whichever happens first).
  *
- * @param $phase
- *   The installation phase we should proceed to.
+ * @param $settings
+ *   An optional array of installation settings. Leave this empty for a normal,
+ *   interactive, browser-based installation intended to occur over multiple
+ *   page requests. Alternatively, if this is passed in, the installer will
+ *   attempt to use it to perform the installation in a single page request
+ *   (optimized for the command line) and not send any output intended for the
+ *   web browser.
+ */
+function install_drupal($settings = array()) {
+  global $install_state;
+  // Initialize the installation state with the settings that were passed in,
+  // as well as a boolean indicating whether or not this is an interactive
+  // installation.
+  $interactive = empty($settings);
+  $install_state = $settings + array('interactive' => $interactive) + install_state_defaults();
+  try {
+    // Begin the page request. This adds information about the current state of
+    // the Drupal installation to the passed-in array.
+    install_begin_request($install_state);
+    // Based on the installation state, run the remaining tasks for this page
+    // request, and collect any output.
+    $output = install_run_tasks($install_state);
+  }
+  catch (Exception $e) {
+    // When an installation error occurs, either send the error to the web
+    // browser or pass on the exception so the calling script can use it.
+    if ($install_state['interactive']) {
+      install_display_output($e->getMessage(), $install_state);
+    }
+    else {
+      throw $e;
+    }
+  }
+  // All available tasks for this page request are now complete. Interactive
+  // installations can send output to the browser or redirect the user to the
+  // next page.
+  if ($install_state['interactive']) {
+    if ($install_state['parameters_changed']) {
+      // Redirect to the correct page if the URL parameters have changed.
+      install_goto(install_redirect_url($install_state));
+    }
+    elseif (isset($output)) {
+      // Display a page only if some output is available. Otherwise it is
+      // possible that we are printing a JSON page and theme output should
+      // not be shown.
+      install_display_output($output, $install_state);
+    }
+  }
+}
+
+/**
+ * Return an array of default settings for the global installation state.
+ *
+ * The installation state is initialized with these settings at the beginning
+ * of each page request. They may evolve during the page request, but they are
+ * initialized again once the next request begins.
+ *
+ * Non-interactive Drupal installations can override some of these default
+ * settings by passing in an array to the installation script, most notably
+ * 'parameters' (which contains one-time parameters such as 'profile' and
+ * 'locale' that are normally passed in via the URL) and 'forms' (which can
+ * be used to programmatically submit forms during the installation; the keys
+ * of each element indicate the name of the installation task that the form
+ * submission is for, and the values are arrays of parameters to pass on to
+ * the form submission via drupal_form_submit()).
+ */
+function install_state_defaults() {
+  $defaults = array(
+    // The current task being processed.
+    'active_task' => NULL,
+    // The last task that was completed during the previous installation
+    // request.
+    'completed_task' => NULL,
+    // This becomes TRUE only when Drupal's system module is installed.
+    'database_tables_exist' => FALSE,
+    // An array of forms to be programmatically submitted during the
+    // installation.
+    'forms' => array(),
+    // This becomes TRUE only at the end of the installation process; certain
+    // theme functions may have access to it then.
+    'installation_finished' => FALSE,
+    // Whether or not this installation is interactive. By default this will
+    // be set to FALSE if settings are passed in to install_drupal().
+    'interactive' => TRUE,
+    // An array of available languages for the installation.
+    'locales' => array(),
+    // An array of parameters for the installation, pre-populated by the URL
+    // or by the settings passed in to install_drupal().
+    'parameters' => array(),
+    // Whether or not the parameters have changed during the current page
+    // request. For interactive installations, this will trigger a page
+    // redirect.
+    'parameters_changed' => FALSE,
+    // An array of information about the chosen installation profile. This will
+    // be filled in based on the profile's .info file.
+    'profile_info' => array(),
+    // An array of available installation profiles.
+    'profiles' => array(),
+    // An array of server variables that will be substituted into the global
+    // $_SERVER array via drupal_override_server_variables(). Used by
+    // non-interactive installations only.
+    'server' => array(),
+    // This becomes TRUE only when a valid database connection can be
+    // established.
+    'settings_verified' => FALSE,
+    // Installation tasks can set this to TRUE to force the page request to
+    // end (even if there is no themable output), in the case of an interactive
+    // installation.
+    'stop_page_request' => FALSE,
+    // Installation tasks can set this to TRUE to indicate that the task should
+    // be run again, even if it normally wouldn't be.
+    'task_not_complete' => FALSE,
+    // A list of installation tasks which have already been performed during
+    // the current page request.
+    'tasks_performed' => array(),
+  );
+  return $defaults;
+}
+
+/**
+ * Begin an installation request, modifying the installation state as needed.
+ *
+ * This function performs commands that must run at the beginning of every page
+ * request. It throws an exception if the installation should not proceed.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state. This is
+ *   modified with information gleaned from the beginning of the page request.
  */
-function install_main() {
+function install_begin_request(&$install_state) {
+  // Allow command line scripts to override server variables used by Drupal.
+  require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
+  if (!$install_state['interactive']) {
+    drupal_override_server_variables($install_state['server']);
+  }
+
   // The user agent header is used to pass a database prefix in the request when
   // running tests. However, for security reasons, it is imperative that no
   // installation be permitted using such a prefix.
@@ -33,17 +187,15 @@ function install_main() {
     exit;
   }
 
-  require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
   drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
 
   // This must go after drupal_bootstrap(), which unsets globals!
-  global $profile, $install_locale, $conf;
+  global $conf;
 
   require_once DRUPAL_ROOT . '/modules/system/system.install';
+  require_once DRUPAL_ROOT . '/includes/common.inc';
   require_once DRUPAL_ROOT . '/includes/file.inc';
-
-  // Ensure correct page headers are sent (e.g. caching)
-  drupal_page_header();
+  require_once DRUPAL_ROOT . '/includes/path.inc';
 
   // Set up $language, so t() caller functions will still work.
   drupal_language_initialize();
@@ -57,13 +209,17 @@ function install_main() {
   drupal_load('module', 'system');
   drupal_load('module', 'filter');
 
-  // Set up theme system for the maintenance page.
-  drupal_maintenance_theme();
+  // Prepare for themed output, if necessary. We need to run this at the
+  // beginning of the page request to avoid a different theme accidentally
+  // getting set.
+  if ($install_state['interactive']) {
+    drupal_maintenance_theme();
+  }
 
   // Check existing settings.php.
-  $verify = install_verify_settings();
+  $install_state['settings_verified'] = install_verify_settings();
 
-  if ($verify) {
+  if ($install_state['settings_verified']) {
     // Since we have a database connection, we use the normal cache system.
     // This is important, as the installer calls into the Drupal system for
     // the clean URL checks, so we should maintain the cache properly.
@@ -74,11 +230,8 @@ function install_main() {
     // won't be initialized until it is actually requested.
     require_once DRUPAL_ROOT . '/includes/database/database.inc';
 
-    // Check if Drupal is installed.
-    $task = install_verify_drupal();
-    if ($task == 'done') {
-      install_already_done_error();
-    }
+    // Verify the last completed task in the database, if there is one.
+    $task = install_verify_completed_task();
   }
   else {
     // Since no persistent storage is available yet, and functions that check
@@ -89,90 +242,479 @@ function install_main() {
     $conf['cache_inc'] = 'includes/cache-install.inc';
 
     $task = NULL;
+
+    // Since previous versions of Drupal stored database connection information
+    // in the 'db_url' variable, we should never let an installation proceed if
+    // this variable is defined and the settings file was not verified above
+    // (otherwise we risk installing over an existing site whose settings file
+    // has not yet been updated).
+    if (!empty($GLOBALS['db_url'])) {
+      throw new Exception(install_already_done_error());
+    }
   }
 
-  // Decide which profile to use.
-  if (!empty($_GET['profile'])) {
-    $profile = preg_replace('/[^a-zA-Z_0-9]/', '', $_GET['profile']);
+  // Modify the installation state as appropriate.
+  $install_state['completed_task'] = $task;
+  $install_state['database_tables_exist'] = !empty($task);
+
+  // Add any installation parameters passed in via the URL.
+  $install_state['parameters'] += $_GET;
+
+  // Validate certain core settings that are used throughout the installation.
+  if (!empty($install_state['parameters']['profile'])) {
+    $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']);
   }
-  elseif ($profile = install_select_profile()) {
-    install_goto("install.php?profile=$profile");
+  if (!empty($install_state['parameters']['locale'])) {
+    $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']);
   }
-  else {
-    install_no_profile_error();
+}
+
+/**
+ * Run all tasks for the current installation request.
+ *
+ * In the case of an interactive installation, all tasks will be attempted
+ * until one is reached that has output which needs to be displayed to the
+ * user, or until a page redirect is required. Otherwise, tasks will be
+ * attempted until the installation is finished.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state. This is
+ *   passed along to each task, so it can be modified if necessary.
+ * @return
+ *   HTML output from the last completed task.
+ */
+function install_run_tasks(&$install_state) {
+  do {
+    // Obtain a list of tasks to perform. The list of tasks itself can be
+    // dynamic (e.g., some might be defined by the installation profile,
+    // which is not necessarily known until the earlier tasks have run),
+    // so we regenerate the remaining tasks based on the installation state,
+    // each time through the loop.
+    $tasks_to_perform = install_tasks_to_perform($install_state);
+    // Run the first task on the list.
+    reset($tasks_to_perform);
+    $task_name = key($tasks_to_perform);
+    $task = array_shift($tasks_to_perform);
+    $install_state['active_task'] = $task_name;
+    $original_parameters = $install_state['parameters'];
+    $output = install_run_task($task, $install_state);
+    $install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters);
+    // Store this task as having been performed during the current request,
+    // and save it to the database as completed, if we need to and if the
+    // database is in a state that allows us to do so. Also mark the
+    // installation as 'done' when we have run out of tasks.
+    if (!$install_state['task_not_complete']) {
+      $install_state['tasks_performed'][] = $task_name;
+      $install_state['installation_finished'] = empty($tasks_to_perform);
+      if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_RUN_TASK_IF_NOT_COMPLETED || $install_state['installation_finished'])) {
+        drupal_install_initialize_database();
+        variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
+      }
+    }
+    // Stop when there are no tasks left. In the case of an interactive
+    // installation, also stop if we have some output to send to the browser,
+    // the URL parameters have changed, or an end to the page request was
+    // specifically called for.
+    $finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request']));
+  } while (!$finished);
+  return $output;
+}
+
+/**
+ * Run an individual installation task.
+ *
+ * @param $task
+ *   An array of information about the task to be run.
+ * @param $install_state
+ *   An array of information about the current installation state. This is
+ *   passed in by reference so that it can be modified by the task.
+ * @return
+ *   The output of the task function, if there is any.
+ */
+function install_run_task($task, &$install_state) {
+  $function = $task['function'];
+
+  if ($task['type'] == 'form') {
+    require_once DRUPAL_ROOT . '/includes/form.inc';
+    if ($install_state['interactive']) {
+      // For interactive forms, build the form and ensure that it will not
+      // redirect, since the installer handles its own redirection only after
+      // marking the form submission task complete.
+      $form_state = array(
+        'args' => array($install_state),
+        'no_redirect' => TRUE,
+      );
+      $form = drupal_build_form($function, $form_state);
+      // If a successful form submission did not occur, the form needs to be
+      // rendered, which means the task is not complete yet.
+      if (empty($form_state['executed'])) {
+        $install_state['task_not_complete'] = TRUE;
+        return drupal_render($form);
+      }
+      // Otherwise, return nothing so the next task will run in the same
+      // request.
+      return;
+    }
+    else {
+      // For non-interactive forms, submit the form programmatically with the
+      // values taken from the installation state. Throw an exception if any
+      // errors were encountered.
+      $form_state = array('values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : array());
+      drupal_form_submit($function, $form_state, $install_state);
+      $errors = form_get_errors();
+      if (!empty($errors)) {
+        throw new Exception(implode("\n", $errors));
+      }
+    }
   }
 
+  elseif ($task['type'] == 'batch') {
+    // Start a new batch based on the task function, if one is not running
+    // already.
+    $current_batch = variable_get('install_current_batch');
+    if (!$install_state['interactive'] || !$current_batch) {
+      $batch = $function($install_state);
+      if (empty($batch)) {
+        // If the task did some processing and decided no batch was necessary,
+        // there is nothing more to do here.
+        return;
+      }
+      batch_set($batch);
+      // For interactive batches, we need to store the fact that this batch
+      // task is currently running. Otherwise, we need to make sure the batch
+      // will complete in one page request.
+      if ($install_state['interactive']) {
+        variable_set('install_current_batch', $function);
+      }
+      else {
+        $batch =& batch_get();
+        $batch['progressive'] = FALSE;
+      }
+      // Process the batch. For progressive batches, this will redirect.
+      // Otherwise, the batch will complete.
+      batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
+    }
+    // If we are in the middle of processing this batch, keep sending back
+    // any output from the batch process, until the task is complete.
+    elseif ($current_batch == $function) {
+      include_once DRUPAL_ROOT . '/includes/batch.inc';
+      $output = _batch_page();
+      // The task is complete when we try to access the batch page and receive
+      // FALSE in return, since this means we are at a URL where we are no
+      // longer requesting a batch ID.
+      if ($output === FALSE) {
+        // Return nothing so the next task will run in the same request.
+        variable_del('install_current_batch');
+        return;
+      }
+      else {
+        // We need to force the page request to end if the task is not
+        // complete, since the batch API sometimes prints JSON output
+        // rather than returning a themed page.
+        $install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE;
+        return $output;
+      }
+    }
+  }
+
+  else {
+    // For normal tasks, just return the function result, whatever it is.
+    return $function($install_state);
+  }
+}
 
-  // Locale selection
-  if (!empty($_GET['locale'])) {
-    $install_locale = preg_replace('/[^a-zA-Z_0-9\-]/', '', $_GET['locale']);
+/**
+ * Return a list of tasks to perform during the current installation request.
+ *
+ * Note that the list of tasks can change based on the installation state as
+ * the page request evolves (for example, if an installation profile hasn't
+ * been selected yet, we don't yet know which profile tasks need to be run).
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   A list of tasks to be performed, with associated metadata.
+ */
+function install_tasks_to_perform($install_state) {
+  // Start with a list of all currently available tasks.
+  $tasks = install_tasks($install_state);
+  foreach ($tasks as $name => $task) {
+    // Remove any tasks that were already performed or that never should run.
+    // Also, if we started this page request with an indication of the last
+    // task that was completed, skip that task and all those that come before
+    // it, unless they are marked as always needing to run.
+    if ($task['run'] == INSTALL_SKIP_TASK || in_array($name, $install_state['tasks_performed']) || (!empty($install_state['completed_task']) && empty($completed_task_found) && $task['run'] != INSTALL_RUN_TASK_IF_REACHED)) {
+      unset($tasks[$name]);
+    }
+    if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) {
+      $completed_task_found = TRUE;
+    }
   }
-  elseif (($install_locale = install_select_locale($profile)) !== FALSE) {
-    install_goto("install.php?profile=$profile&locale=$install_locale");
+  return $tasks;
+}
+
+/**
+ * Return a list of all tasks the installer currently knows about.
+ *
+ * This function will return tasks regardless of whether or not they are
+ * intended to run on the current page request. However, the list can change
+ * based on the installation state (for example, if an installation profile
+ * hasn't been selected yet, we don't yet know which profile tasks will be
+ * available).
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   A list of tasks, with associated metadata.
+ */
+function install_tasks($install_state) {
+  // Determine whether translation import tasks will need to be performed.
+  $needs_translations = count($install_state['locales']) > 1 && !empty($install_state['parameters']['locale']) && $install_state['parameters']['locale'] != 'en';
+
+  // Start with the core installation tasks that run before handing control
+  // to the install profile.
+  $tasks = array(
+    'install_select_profile' => array(
+      'display_name' => st('Choose profile'),
+      'display' => count($install_state['profiles']) != 1,
+      'run' => INSTALL_RUN_TASK_IF_REACHED,
+    ),
+    'install_select_locale' => array(
+      'display_name' => st('Choose language'),
+      'run' => INSTALL_RUN_TASK_IF_REACHED,
+    ),
+    'install_load_profile' => array(
+      'run' => INSTALL_RUN_TASK_IF_REACHED,
+    ),
+    'install_verify_requirements' => array(
+      'display_name' => st('Verify requirements'),
+    ),
+    'install_settings_form' => array(
+      'display_name' => st('Set up database'),
+      'type' => 'form',
+      'run' => $install_state['settings_verified'] ? INSTALL_SKIP_TASK : INSTALL_RUN_TASK_IF_NOT_COMPLETED,
+    ),
+    'install_system_module' => array(
+    ),
+    'install_bootstrap_full' => array(
+      'run' => INSTALL_RUN_TASK_IF_REACHED,
+    ),
+    'install_profile_modules' => array(
+      'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'),
+      'type' => 'batch',
+    ),
+    'install_import_locales' => array(
+      'display_name' => st('Set up translations'),
+      'display' => $needs_translations,
+      'type' => 'batch',
+      'run' => $needs_translations ? INSTALL_RUN_TASK_IF_NOT_COMPLETED : INSTALL_SKIP_TASK,
+    ),
+    'install_configure_form' => array(
+      'display_name' => st('Configure site'),
+      'type' => 'form',
+    ),
+  );
+
+  // Now add any tasks defined by the installation profile.
+  if (!empty($install_state['parameters']['profile'])) {
+    $function = $install_state['parameters']['profile'] . '_profile_tasks';
+    if (function_exists($function)) {
+      $result = $function($install_state);
+      if (is_array($result)) {
+        $tasks += $result;
+      }
+    }
   }
 
-  // Load the profile.
-  require_once DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
-  $info = install_profile_info($profile, $install_locale);
+  // Finish by adding the remaining core tasks.
+  $tasks += array(
+    'install_import_locales_remaining' => array(
+      'display_name' => st('Finish translations'),
+      'display' => $needs_translations,
+      'type' => 'batch',
+      'run' => $needs_translations ? INSTALL_RUN_TASK_IF_NOT_COMPLETED : INSTALL_SKIP_TASK,
+    ),
+    'install_finished' => array(
+      'display_name' => st('Finished'),
+    ),
+  );
 
-  // Tasks come after the database is set up
-  if (!$task) {
-    global $db_url;
+  // Fill in default parameters for each task before returning the list.
+  foreach ($tasks as $task_name => &$task) {
+    $task += array(
+      'display_name' => NULL,
+      'display' => !empty($task['display_name']),
+      'type' => 'normal',
+      'run' => INSTALL_RUN_TASK_IF_NOT_COMPLETED,
+      'function' => $task_name,
+    );
+  }
+  return $tasks;
+}
 
-    if (!$verify && !empty($db_url)) {
-      // Do not install over a configured settings.php.
-      install_already_done_error();
+/**
+ * Return a list of tasks that should be displayed to the end user.
+ *
+ * The output of this function is a list suitable for sending to
+ * theme_task_list().
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   A list of tasks, with keys equal to the machine-readable task name and
+ *   values equal to the name that should be displayed.
+ *
+ * @see theme_task_list()
+ */
+function install_tasks_to_display($install_state) {
+  $displayed_tasks = array();
+  foreach (install_tasks($install_state) as $name => $task) {
+    if ($task['display']) {
+      $displayed_tasks[$name] = $task['display_name'];
     }
+  }
+  return $displayed_tasks;
+}
+
+/**
+ * Return the URL that should be redirected to during an installation request.
+ *
+ * The output of this function is suitable for sending to install_goto().
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The URL to redirect to.
+ *
+ * @see install_full_redirect_url()
+ */
+function install_redirect_url($install_state) {
+  return 'install.php?' . drupal_query_string_encode($install_state['parameters']);
+}
+
+/**
+ * Return the complete URL that should be redirected to during an installation
+ * request.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The complete URL to redirect to.
+ *
+ * @see install_redirect_url()
+ */
+function install_full_redirect_url($install_state) {
+  global $base_url;
+  return $base_url . '/' . install_redirect_url($install_state);
+}
 
+/**
+ * Display themed installer output and end the page request.
+ *
+ * Installation tasks should use drupal_set_title() to set the desired page
+ * title, but otherwise this function takes care of theming the overall page
+ * output during every step of the installation.
+ *
+ * @param $output
+ *   The content to display on the main part of the page.
+ * @param $install_state
+ *   An array of information about the current installation state.
+ */
+function install_display_output($output, $install_state) {
+  drupal_page_header();
+  // Only show the task list if there is an active task; otherwise, the page
+  // request has ended before tasks have even been started, so there is nothing
+  // meaningful to show.
+  if (isset($install_state['active_task'])) {
+    // Let the theming function know when every step of the installation has
+    // been completed.
+    $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task'];
+    drupal_add_region_content('left', theme_task_list(install_tasks_to_display($install_state), $active_task));
+  }
+  print theme($install_state['database_tables_exist'] ? 'maintenance_page' : 'install_page', $output);
+  exit;
+}
+
+/**
+ * Installation task; verify the requirements for installing Drupal.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   A themed status report, or an exception if there are requirement errors.
+ *   Otherwise, no output is returned, so that the next task can be run
+ *   in the same page request.
+ */
+function install_verify_requirements(&$install_state) {
     // Check the installation requirements for Drupal and this profile.
-    $requirements = install_check_requirements($profile, $verify);
+    $requirements = install_check_requirements($install_state);
 
     // Verify existence of all required modules.
-    $requirements += drupal_verify_profile($profile, $install_locale);
+    $requirements += drupal_verify_profile($install_state);
 
     // Check the severity of the requirements reported.
     $severity = drupal_requirements_severity($requirements);
 
     if ($severity == REQUIREMENT_ERROR) {
-      install_task_list('requirements');
-      drupal_set_title(st('Requirements problem'));
-      $status_report = theme('status_report', $requirements);
-      $status_report .= st('Please check the error messages and <a href="!url">try again</a>.', array('!url' => request_uri()));
-      print theme('install_page', $status_report);
-      exit;
-    }
-
-    // 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);
+      if ($install_state['interactive']) {
+        drupal_set_title(st('Requirements problem'));
+        $status_report = theme('status_report', $requirements);
+        $status_report .= st('Please check the error messages and <a href="!url">try again</a>.', array('!url' => request_uri()));
+        return $status_report;
+      }
+      else {
+        // Throw an exception showing all unmet requirements.
+        $failures = array();
+        foreach ($requirements as $requirement) {
+          if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
+            $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description'];
+          }
+        }
+        throw new Exception(implode("\n\n", $failures));
+      }
     }
+}
 
+/**
+ * Installation task; install the Drupal system module.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ */
+function install_system_module(&$install_state) {
     // 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.
-    $modules = $info['dependencies'];
+    // Save the list of other modules to install for the upcoming tasks.
+    // variable_set() can be used now that system.module is installed and
+    // Drupal is bootstrapped.
+    $modules = $install_state['profile_info']['dependencies'];
     variable_set('install_profile_modules', array_diff($modules, array('system')));
-  }
-
-  // The database is set up, turn to further tasks.
-  install_tasks($profile, $task);
+    $install_state['database_tables_exist'] = TRUE;
 }
 
 /**
- * Verify if Drupal is installed.
+ * Verify and return the last installation task that was completed.
+ *
+ * @return
+ *   The last completed task, if there is one. An exception is thrown if Drupal
+ *   is already installed.
  */
-function install_verify_drupal() {
-  // Read the variable manually using the @ so we don't trigger an error if it fails.
+function install_verify_completed_task() {
   try {
     if ($result = db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task')) {
-      return unserialize(db_result($result));
+      $task = unserialize(db_result($result));
     }
   }
+  // Do not trigger an error if the database query fails, since the database
+  // might not be set up yet.
   catch (Exception $e) {
   }
+  if (isset($task)) {
+    if ($task == 'done') {
+      throw new Exception(install_already_done_error());
+    }
+    return $task;
+  }
 }
 
 /**
@@ -183,16 +725,11 @@ function install_verify_settings() {
 
   // Verify existing settings (if any).
   if (!empty($databases)) {
-    // We need this because we want to run form_get_errors.
-    include_once DRUPAL_ROOT . '/includes/form.inc';
-
     $database = $databases['default']['default'];
     drupal_static_reset('conf_path');
     $settings_file = './' . conf_path(FALSE) . '/settings.php';
-
-    $form_state = array();
-    _install_settings_form_validate($database, $settings_file, $form_state);
-    if (!form_get_errors()) {
+    $errors = install_database_errors($database, $settings_file);
+    if (empty($errors)) {
       return TRUE;
     }
   }
@@ -200,37 +737,33 @@ function install_verify_settings() {
 }
 
 /**
- * Configure and rewrite settings.php.
+ * Installation task; define a form to configure and rewrite settings.php.
+ *
+ * @param $form_state
+ *   An associative array containing the current state of the form.
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The form API definition for the database configuration form.
  */
-function install_change_settings($profile = 'default', $install_locale = '') {
+function install_settings_form(&$form_state, &$install_state) {
   global $databases, $db_prefix;
+  $profile = $install_state['parameters']['profile'];
+  $install_locale = $install_state['parameters']['locale'];
 
   drupal_static_reset('conf_path');
   $conf_path = './' . conf_path(FALSE);
   $settings_file = $conf_path . '/settings.php';
   $database = isset($databases['default']['default']) ? $databases['default']['default'] : array();
 
-  // We always need this because we want to run form_get_errors.
-  include_once DRUPAL_ROOT . '/includes/form.inc';
-  install_task_list('database');
-
-  $output = drupal_render(drupal_get_form('install_settings_form', $profile, $install_locale, $settings_file, $database));
   drupal_set_title(st('Database configuration'));
-  print theme('install_page', $output);
-  exit;
-}
 
-
-/**
- * Form API array definition for install_settings.
- */
-function install_settings_form(&$form_state, $profile, $install_locale, $settings_file, $database) {
   $drivers = drupal_detect_database_types();
 
   if (!$drivers) {
-    $form['no_drivers'] = array(
-      '#markup' => st('Your web server does not appear to support any common database types. Check with your hosting provider to see if they offer any databases that <a href="@drupal-databases">Drupal supports</a>.', array('@drupal-databases' => 'http://drupal.org/node/270#database')),
-    );
+    // There is no point submitting the form if there are no database drivers
+    // at all, so throw an exception here.
+    throw new Exception(st('Your web server does not appear to support any common database types. Check with your hosting provider to see if they offer any databases that <a href="@drupal-databases">Drupal supports</a>.', array('@drupal-databases' => 'http://drupal.org/node/270#database')));
   }
   else {
     $form['basic_options'] = array(
@@ -326,8 +859,6 @@ function install_settings_form(&$form_st
     $form['errors'] = array();
     $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file);
     $form['_database'] = array('#type' => 'value');
-    $form['#action'] = "install.php?profile=$profile" . ($install_locale ? "&locale=$install_locale" : '');
-    $form['#redirect'] = FALSE;
   }
   return $form;
 }
@@ -336,54 +867,57 @@ function install_settings_form(&$form_st
  * Form API validate for install_settings form.
  */
 function install_settings_form_validate($form, &$form_state) {
-  global $db_url;
-  _install_settings_form_validate($form_state['values'], $form_state['values']['settings_file'], $form_state, $form);
+  form_set_value($form['_database'], $form_state['values'], $form_state);
+  $errors = install_database_errors($form_state['values'], $form_state['values']['settings_file']);
+  foreach ($errors as $name => $message) {
+    form_set_error($name, $message);
+  }
 }
 
 /**
- * Helper function for install_settings_validate.
+ * Check a database connection and return any errors.
  */
-function _install_settings_form_validate($database, $settings_file, &$form_state, $form = NULL) {
+function install_database_errors($database, $settings_file) {
   global $databases;
+  $errors = array();
   // Verify the table prefix
   if (!empty($database['db_prefix']) && is_string($database['db_prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['db_prefix'])) {
-    form_set_error('db_prefix', st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $database['db_prefix'])), 'error');
+    $errors['db_prefix'] = st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $database['db_prefix']));
   }
 
   if (!empty($database['port']) && !is_numeric($database['port'])) {
-    form_set_error('db_port', st('Database port must be a number.'));
+    $errors['db_port'] = st('Database port must be a number.');
   }
 
   // Check database type
   $database_types = drupal_detect_database_types();
   $driver = $database['driver'];
   if (!isset($database_types[$driver])) {
-    form_set_error('driver', st("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_name(), '%driver' => $database['driver'])));
+    $errors['driver'] = st("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_name(), '%driver' => $database['driver']));
   }
   else {
-    if (isset($form)) {
-      form_set_value($form['_database'], $database, $form_state);
-    }
     $class = "DatabaseInstaller_$driver";
     $test = new $class;
     $databases['default']['default'] = $database;
-    $return = $test->test();
-    if (!$return || $test->error) {
+    $test->test();
+    if (!empty($test->failures)) {
+      // These are generic errors, so we do not have any specific key of the
+      // database connection array to attach them to; therefore, we just
+      // put them in the error array with standard numeric keys.
+      $errors += $test->failures;
       if (!empty($test->success)) {
-        form_set_error('db_type', st('In order for Drupal to work, and to continue with the installation process, you must resolve all permission issues reported above. We were able to verify that we have permission for the following commands: %commands. For more help with configuring your database server, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.', array('%commands' => implode($test->success, ', '))));
-      }
-      else {
-        form_set_error('driver', '');
+        $errors[] = st('In order for Drupal to work, and to continue with the installation process, you must resolve all permission issues reported above. We were able to verify that we have permission for the following commands: %commands. For more help with configuring your database server, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.', array('%commands' => implode($test->success, ', ')));
       }
     }
   }
+  return $errors;
 }
 
 /**
  * Form API submit for install_settings form.
  */
 function install_settings_form_submit($form, &$form_state) {
-  global $profile, $install_locale;
+  global $install_state;
 
   $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port')));
   // Update global settings array and save
@@ -396,9 +930,12 @@ function install_settings_form_submit($f
     'required' => TRUE,
   );
   drupal_rewrite_settings($settings);
-
-  // Continue to install profile step
-  install_goto("install.php?profile=$profile" . ($install_locale ? "&locale=$install_locale" : ''));
+  // Indicate that the settings file has been verified, and check the database
+  // for the last completed task, now that we have a valid connection. This
+  // last step is important since we want to trigger an error if the new
+  // database already has Drupal installed.
+  $install_state['settings_verified'] = TRUE;
+  $install_state['completed_task'] = install_verify_completed_task();
 }
 
 /**
@@ -409,37 +946,66 @@ function install_find_profiles() {
 }
 
 /**
- * Allow admin to select which profile to install.
+ * Installation task; allow the site administrator to select which profile to
+ * install.
  *
+ * @param $install_state
+ *   An array of information about the current installation state. The chosen
+ *   profile will be added here, if it was not already selected previously, as
+ *   will a list of all available profiles.
  * @return
- *   The selected profile.
- */
-function install_select_profile() {
-  include_once DRUPAL_ROOT . '/includes/form.inc';
+ *   For interactive installations, a form allowing the profile to be selected,
+ *   if the user has a choice that needs to be made. Otherwise, an exception is
+ *   thrown if a profile cannot be chosen automatically.
+ */
+function install_select_profile(&$install_state) {
+  $install_state['profiles'] += install_find_profiles();
+  if (empty($install_state['parameters']['profile'])) {
+    // Try to find a profile.
+    $profile = _install_select_profile($install_state['profiles']);
+    if (empty($profile)) {
+      // We still don't have a profile, so display a form for selecting one.
+      // Only do this in the case of interactive installations, since this is
+      // not a real form with submit handlers (the database isn't even set up
+      // yet), rather just a convenience method for setting parameters in the
+      // URL.
+      if ($install_state['interactive']) {
+        include_once DRUPAL_ROOT . '/includes/form.inc';
+        drupal_set_title(st('Select an installation profile'));
+        return drupal_render(drupal_get_form('install_select_profile_form', $install_state['profiles']));
+      }
+      else {
+        throw new Exception(install_no_profile_error());
+      }
+    }
+    else {
+      $install_state['parameters']['profile'] = $profile;
+    }
+  }
+}
 
-  $profiles = install_find_profiles();
+/**
+ * Helper function for automatically selecting an installation profile from a
+ * list or from a selection passed in via $_POST.
+ */
+function _install_select_profile($profiles) {
+  if (sizeof($profiles) == 0) {
+    throw new Exception(install_no_profile_error());
+  }
   // Don't need to choose profile if only one available.
   if (sizeof($profiles) == 1) {
     $profile = array_pop($profiles);
-    require_once $profile->filepath;
     return $profile->name;
   }
-  elseif (sizeof($profiles) > 1) {
+  else {
     foreach ($profiles as $profile) {
       if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) {
         return $profile->name;
       }
     }
-
-    install_task_list('profile-select');
-
-    drupal_set_title(st('Select an installation profile'));
-    print theme('install_page', drupal_render(drupal_get_form('install_select_profile_form', $profiles)));
-    exit;
   }
 }
 
-
 /**
  * Form API array definition for the profile selection form.
  *
@@ -494,27 +1060,33 @@ function install_find_locales($profilena
 }
 
 /**
- * Allow admin to select which locale to use for the current profile.
+ * Installation task; allow the site administrator to select which locale to
+ * use for the current profile.
  *
+ * @param $install_state
+ *   An array of information about the current installation state. The chosen
+ *   locale will be added here, if it was not already selected previously, as
+ *   will a list of all available locales.
  * @return
- *   The selected language.
+ *   For interactive installations, a form or other page output allowing the
+ *   locale to be selected or providing information about locale selection, if
+ *   a locale has not been chosen. Otherwise, an exception is thrown if a
+ *   locale cannot be chosen automatically.
  */
-function install_select_locale($profilename) {
-  include_once DRUPAL_ROOT . '/includes/file.inc';
-  include_once DRUPAL_ROOT . '/includes/form.inc';
-
+function install_select_locale(&$install_state) {
   // Find all available locales.
+  $profilename = $install_state['parameters']['profile'];
   $locales = install_find_locales($profilename);
-
-  // If only the built-in (English) language is available,
-  // and we are using the default profile, inform the user
-  // that the installer can be localized. Otherwise we assume
-  // the user know what he is doing.
+  $install_state['locales'] += $locales;
+  if (empty($install_state['parameters']['locale'])) {
+  // If only the built-in (English) language is available, and we are using the
+  // default profile and performing an interactive installation, inform the
+  // user that the installer can be localized. Otherwise we assume the user
+  // knows what he is doing.
   if (count($locales) == 1) {
-    if ($profilename == 'default') {
-      install_task_list('locale-select');
+    if ($profilename == 'default' && $install_state['interactive']) {
       drupal_set_title(st('Choose language'));
-      if (!empty($_GET['localize'])) {
+      if (!empty($install_state['parameters']['localize'])) {
         $output = '<p>' . st('With the addition of an appropriate translation package, this installer is capable of proceeding in another language of your choice. To install and use Drupal in a language other than English:') . '</p>';
         $output .= '<ul><li>' . st('Determine if <a href="@translations" target="_blank">a translation of this Drupal version</a> is available in your language of choice. A translation is provided via a translation package; each translation package enables the display of a specific version of Drupal in a specific language. Not all languages are available for every version of Drupal.', array('@translations' => 'http://drupal.org/project/translations')) . '</li>';
         $output .= '<li>' . st('If an alternative translation package of your choice is available, download and extract its contents to your Drupal root directory.') . '</li>';
@@ -526,12 +1098,13 @@ function install_select_locale($profilen
       else {
         $output = '<ul><li><a href="install.php?profile=' . $profilename . '&amp;locale=en">' . st('Install Drupal in English') . '</a></li><li><a href="install.php?profile=' . $profilename . '&amp;localize=true">' . st('Learn how to install Drupal in other languages') . '</a></li></ul>';
       }
-      print theme('install_page', $output);
-      exit;
+      return $output;
     }
-    // One language, but not the default profile, assume
-    // the user knows what he is doing.
-    return FALSE;
+    // One language, but not the default profile or not an interactive
+    // installation. Assume the user knows what he is doing.
+    $locale = current($locales);
+    $install_state['parameters']['locale'] = $locale->name;
+    return;
   }
   else {
     // Allow profile to pre-select the language, skipping the selection.
@@ -541,7 +1114,8 @@ function install_select_locale($profilen
       if (isset($details['language'])) {
         foreach ($locales as $locale) {
           if ($details['language'] == $locale->name) {
-            return $locale->name;
+            $install_state['parameters']['locale'] = $locale->name;
+            return;
           }
         }
       }
@@ -550,17 +1124,25 @@ function install_select_locale($profilen
     if (!empty($_POST['locale'])) {
       foreach ($locales as $locale) {
         if ($_POST['locale'] == $locale->name) {
-          return $locale->name;
+          $install_state['parameters']['locale'] = $locale->name;
+          return;
         }
       }
     }
 
-    install_task_list('locale-select');
-
-    drupal_set_title(st('Choose language'));
-
-    print theme('install_page', drupal_render(drupal_get_form('install_select_locale_form', $locales)));
-    exit;
+    // We still don't have a locale, so display a form for selecting one. Only
+    // do this in the case of interactive installations, since this is not a
+    // real form with submit handlers (the database isn't even set up yet),
+    // rather just a convenience method for setting parameters in the URL.
+    if ($install_state['interactive']) {
+      drupal_set_title(st('Choose language'));
+      include_once DRUPAL_ROOT . '/includes/form.inc';
+      return drupal_render(drupal_get_form('install_select_locale_form', $locales));
+    }
+    else {
+      throw new Exception(st('Sorry, you must select a language to continue the installation.'));
+    }
+  }
   }
 }
 
@@ -592,54 +1174,66 @@ function install_select_locale_form(&$fo
 }
 
 /**
- * Show an error page when there are no profiles available.
+ * Indicate that there are no profiles available.
  */
 function install_no_profile_error() {
-  install_task_list('profile-select');
   drupal_set_title(st('No profiles available'));
-  print theme('install_page', '<p>' . st('We were unable to find any installer profiles. Installer profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.') . '</p>');
-  exit;
+  return st('We were unable to find any installer profiles. Installer profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.');
 }
 
-
 /**
- * Show an error page when Drupal has already been installed.
+ * Indicate that Drupal has already been installed.
  */
 function install_already_done_error() {
   global $base_url;
 
   drupal_set_title(st('Drupal already installed'));
-  print theme('install_page', st('<ul><li>To start over, you must empty your existing database.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url)));
-  exit;
+  return st('<ul><li>To start over, you must empty your existing database.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url));
 }
 
 /**
- * Tasks performed after the database is initialized.
- */
-function install_tasks($profile, $task) {
-  global $base_url, $install_locale;
+ * Installation task; load information about the chosen profile.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state. The loaded
+ *   profile information will be added here, or an exception will be thrown if
+ *   the profile cannot be loaded.
+ */
+function install_load_profile(&$install_state) {
+  $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile';
+  if (is_file($profile_file)) {
+    include_once $profile_file;
+    $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['locale']);
+  }
+  else {
+    throw new Exception(st('Sorry, the profile you have chosen cannot be loaded.'));
+  }
+}
 
+/**
+ * Installation task; perform a full bootstrap of Drupal.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ */
+function install_bootstrap_full(&$install_state) {
   // Bootstrap newly installed Drupal, while preserving existing messages.
   $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : '';
   drupal_install_initialize_database();
 
   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
   $_SESSION['messages'] = $messages;
+}
 
-  // URL used to direct page requests.
-  $url = $base_url . '/install.php?locale=' . $install_locale . '&profile=' . $profile;
-
-  // Build a page for final tasks.
-  if (empty($task)) {
-    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') {
+/**
+ * Installation task; install required modules via a batch process.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The batch definition.
+ */
+function install_profile_modules(&$install_state) {
     $modules = variable_get('install_profile_modules', array());
     $files = system_get_module_data();
     variable_del('install_profile_modules');
@@ -649,64 +1243,52 @@ function install_tasks($profile, $task) 
     }
     $batch = array(
       'operations' => $operations,
-      'finished' => '_install_profile_batch_finished',
       'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_name())),
       'error_message' => st('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 DRUPAL_ROOT . '/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')) {
+    return $batch;
+}
+
+/**
+ * Installation task; import languages via a batch process.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The batch definition, if there are language files to import.
+ */
+function install_import_locales(&$install_state) {
       include_once DRUPAL_ROOT . '/includes/locale.inc';
+      $install_locale = $install_state['parameters']['locale'];
       // Enable installation language as default site language.
       locale_add_language($install_locale, NULL, NULL, NULL, '', NULL, 1, TRUE);
       // Collect files to import for this language.
-      $batch = locale_batch_by_language($install_locale, '_install_locale_initial_batch_finished');
+      $batch = locale_batch_by_language($install_locale, NULL);
       if (!empty($batch)) {
         // Remember components we cover in this batch set.
         variable_set('install_locale_batch_components', $batch['#components']);
-        // Start a batch, switch to 'locale-batch' task. We need to
-        // set the variable here, because batch_process() redirects.
-        variable_set('install_task', 'locale-initial-batch');
-        batch_set($batch);
-        batch_process($url, $url);
+        return $batch;
       }
-    }
-    // Found nothing to import or not foreign language, go to next task.
-    $task = 'configure';
-  }
-  if ($task == 'locale-initial-batch') {
-    include_once DRUPAL_ROOT . '/includes/batch.inc';
-    include_once DRUPAL_ROOT . '/includes/locale.inc';
-    $output = _batch_page();
-  }
+}
 
-  if ($task == 'configure') {
+/**
+ * Installation task; configure settings for the new site.
+ *
+ * @param $form_state
+ *   An associative array containing the current state of the form.
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The form API definition for the site configuration form.
+ */
+function install_configure_form(&$form_state, &$install_state) {
     if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) {
       // Site already configured: This should never happen, means re-running
       // the installer, possibly by an attacker after the 'install_task' variable
       // got accidentally blown somewhere. Stop it now.
-      install_already_done_error();
+      throw new Exception(install_already_done_error());
     }
-    $form = drupal_render(drupal_get_form('install_configure_form', $url));
 
-    if (!variable_get('site_name', FALSE) && !variable_get('site_mail', FALSE)) {
-      // Not submitted yet: Prepare to display the form.
-      $output = $form;
       drupal_set_title(st('Configure site'));
 
       // Warn about settings.php permissions risk
@@ -738,73 +1320,47 @@ function install_tasks($profile, $task) 
       // cached schema, drupal_get_schema() will try to generate one
       // but with no loaded modules will return nothing.
       //
-      // This logically could be done during task 'done' but the clean
-      // URL check requires it now.
+      // This logically could be done during the 'install_finished' task,
+      // but the clean URL check requires it now.
       drupal_get_schema(NULL, TRUE);
-    }
-
-    else {
-      $task = 'profile';
-    }
-  }
-
-  // If found an unknown task or the 'profile' task, which is
-  // reserved for profiles, hand over the control to the profile,
-  // so it can run any number of custom tasks it defines.
-  if (!in_array($task, install_reserved_tasks())) {
-    $function = $profile . '_profile_tasks';
-    if (function_exists($function)) {
-      // The profile needs to run more code, maybe even more tasks.
-      // $task is sent through as a reference and may be changed!
-      $output = $function($task, $url);
-    }
 
-    // If the profile doesn't move on to a new task we assume
-    // that it is done.
-    if ($task == 'profile') {
-      $task = 'profile-finished';
-    }
-  }
+      // Return the form.
+      return _install_configure_form($form_state, $install_state);
+}
 
-  // Profile custom tasks are done, so let the installer regain
-  // control and proceed with importing the remaining translations.
-  if ($task == 'profile-finished') {
-    if (!empty($install_locale) && ($install_locale != 'en')) {
+/**
+ * Installation task; import remaining languages via a batch process.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   The batch definition, if there are language files to import.
+ */
+function install_import_locales_remaining(&$install_state) {
       include_once DRUPAL_ROOT . '/includes/locale.inc';
       // Collect files to import for this language. Skip components
       // already covered in the initial batch set.
-      $batch = locale_batch_by_language($install_locale, '_install_locale_remaining_batch_finished', variable_get('install_locale_batch_components', array()));
+      $batch = locale_batch_by_language($install_locale, NULL, variable_get('install_locale_batch_components', array()));
       // Remove temporary variable.
       variable_del('install_locale_batch_components');
-      if (!empty($batch)) {
-        // Start a batch, switch to 'locale-remaining-batch' task. We need to
-        // set the variable here, because batch_process() redirects.
-        variable_set('install_task', 'locale-remaining-batch');
-        batch_set($batch);
-        batch_process($url, $url);
-      }
-    }
-    // Found nothing to import or not foreign language, go to next task.
-    $task = 'finished';
-  }
-  if ($task == 'locale-remaining-batch') {
-    include_once DRUPAL_ROOT . '/includes/batch.inc';
-    include_once DRUPAL_ROOT . '/includes/locale.inc';
-    $output = _batch_page();
-  }
+      return $batch;
+}
 
-  // Display a 'finished' page to user.
-  if ($task == 'finished') {
+/**
+ * Installation task; perform final steps and display a 'finished' page.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ * @return
+ *   A message informing the user that the installation is complete.
+ */
+function install_finished(&$install_state) {
     drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())));
     $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>';
     $output .= '<p>' . st('For more information on configuring Drupal, please refer to the <a href="@help">help section</a>.', array('@help' => url('admin/help'))) . '</p>';
-    $task = 'done';
-  }
 
-  // The end of the install process. Remember profile used.
-  if ($task == 'done') {
     // Rebuild menu and registry to get content type links registered by the
     // profile, and possibly any other menu items created through the tasks.
     menu_rebuild();
@@ -816,26 +1372,18 @@ function install_tasks($profile, $task) 
     // this is a new install, not upgraded yet.
     _drupal_flush_css_js();
 
-    variable_set('install_profile', $profile);
+    // Remember the profile which was used.
+    variable_set('install_profile', $install_state['parameters']['profile']);
 
     // Cache a fully-built schema.
     drupal_get_schema(NULL, TRUE);
-  }
-
-  // Set task for user, and remember the task in the database.
-  install_task_list($task);
-  variable_set('install_task', $task);
 
   // Run cron to populate update status tables (if available) so that users
   // will be warned if they've installed an out of date Drupal version.
   // Will also trigger indexing of profile-supplied content or feeds.
   drupal_cron_run();
 
-  // Output page, if some output was required. Otherwise it is possible
-  // that we are printing a JSON page and theme output should not be there.
-  if (isset($output)) {
-    print theme('maintenance_page', $output);
-  }
+  return $output;
 }
 
 /**
@@ -853,48 +1401,16 @@ function _install_module_batch($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.
- *
- * Advance installer task to the configure screen.
- */
-function _install_locale_initial_batch_finished($success, $results) {
-  variable_set('install_task', 'configure');
-}
-
-/**
- * Finished callback for the second locale import batch.
- *
- * Advance installer task to the finished screen.
- */
-function _install_locale_remaining_batch_finished($success, $results) {
-  variable_set('install_task', 'finished');
-}
-
-/**
- * The list of reserved tasks to run in the installer.
- */
-function install_reserved_tasks() {
-  return array('configure', 'profile-install', 'profile-install-batch', 'locale-initial-import', 'locale-initial-batch', 'profile-finished', 'locale-remaining-batch', 'finished', 'done');
-}
-
-/**
  * Check installation requirements and report any errors.
  */
-function install_check_requirements($profile, $verify) {
+function install_check_requirements($install_state) {
+  $profile = $install_state['parameters']['profile'];
+
   // Check the profile requirements.
   $requirements = drupal_check_profile($profile);
 
   // If Drupal is not set up already, we need to create a settings file.
-  if (!$verify) {
+  if (!$install_state['settings_verified']) {
     $writable = FALSE;
     $conf_path = './' . conf_path(FALSE, TRUE);
     $settings_file = $conf_path . '/settings.php';
@@ -945,66 +1461,9 @@ function install_check_requirements($pro
 }
 
 /**
- * Add the installation task list to the current page.
- */
-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'),
-    '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);
-
-  // 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.
-  if ($profile) {
-    $info = install_profile_info($profile);
-    if (isset($info['tasks'])) {
-      foreach ($info['tasks'] as $task => $title) {
-        $tasks[$task] = st($title);
-      }
-    }
-  }
-
-  if (count($locales) < 2 || empty($_GET['locale']) || $_GET['locale'] == 'en') {
-    // If not required, remove translation import from the task list.
-    unset($tasks['locale-initial-batch']);
-  }
-  else {
-    // If required, add remaining translations import task.
-    $tasks += array('locale-remaining-batch' => st('Finish translations'));
-  }
-
-  // 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_add_region_content('left', theme_task_list($tasks, $active));
-}
-
-/**
  * Form API array definition for site configuration.
  */
-function install_configure_form(&$form_state, $url) {
+function _install_configure_form(&$form_state, &$install_state) {
   include_once DRUPAL_ROOT . '/includes/locale.inc';
 
   $form['site_information'] = array(
@@ -1102,13 +1561,11 @@ function install_configure_form(&$form_s
     '#value' => st('Save and continue'),
     '#weight' => 15,
   );
-  $form['#action'] = $url;
-  $form['#redirect'] = FALSE;
 
   // Allow the profile to alter this form. $form_state isn't available
   // here, but to conform to the hook_form_alter() signature, we pass
   // an empty array.
-  $hook_form_alter = $_GET['profile'] . '_form_alter';
+  $hook_form_alter = $install_state['parameters']['profile'] . '_form_alter';
   if (function_exists($hook_form_alter)) {
     $hook_form_alter($form, array(), 'install_configure');
   }
@@ -1156,8 +1613,8 @@ function install_configure_form_submit($
   $merge_data = array('init' => $form_state['values']['mail'], 'roles' => array(), 'status' => 1);
   user_save($account, array_merge($form_state['values'], $merge_data));
   // Load global $user and perform final login tasks.
-  $form_state['uid'] = 1;
-  user_login_submit(array(), $form_state);
+  $user = user_load(1);
+  user_login_finalize();
   $form_state['values'] = $form_state['old_values'];
   unset($form_state['old_values']);
   variable_set('user_email_verification', TRUE);
@@ -1171,4 +1628,4 @@ function install_configure_form_submit($
 }
 
 // Start the installer.
-install_main();
+install_drupal();
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.290
diff -u -p -r1.290 bootstrap.inc
--- includes/bootstrap.inc	19 Jul 2009 05:26:11 -0000	1.290
+++ includes/bootstrap.inc	20 Jul 2009 04:19:47 -0000
@@ -391,6 +391,48 @@ function conf_path($require_settings = T
 }
 
 /**
+ * Set appropriate server variables needed for command line scripts to work.
+ *
+ * This function can be called by command line scripts before bootstrapping
+ * Drupal, to ensure that the page loads with the desired server parameters.
+ * This may be necessary for allowing the detection of the correct settings.php
+ * file or for allowing functions such as request_uri() and ip_address() to
+ * return the expected values.
+ *
+ * @param $variables
+ *   (optional) An associative array of variables within $_SERVER that should
+ *   be replaced. If the special element 'url' is provided in this array, it
+ *   will be used to populate some of the server defaults; it should be set to
+ *   the URL of the current page request, excluding any $_GET request but
+ *   including the script name (e.g., http://www.example.com/mysite/index.php).
+ */
+function drupal_override_server_variables($variables = array()) {
+  // Set defaults based on the provided URL.
+  if (isset($variables['url'])) {
+    $url = parse_url($variables['url']);
+    unset($variables['url']);
+  }
+  else {
+    $url = array();
+  }
+  $url += array(
+    'path' => '',
+    'host' => 'localhost',
+  );
+  $defaults = array(
+    'HTTP_HOST' => $url['host'],
+    'SCRIPT_NAME' => $url['path'],
+    'REMOTE_ADDR' => '127.0.0.1',
+    'REQUEST_METHOD' => 'GET',
+    'SERVER_NAME' => NULL,
+    'SERVER_SOFTWARE' => 'PHP CLI',
+    'HTTP_USER_AGENT' => NULL,
+  );
+  // Replace elements of the $_SERVER array, as appropriate.
+  $_SERVER = $variables + $_SERVER + $defaults;
+}
+
+/**
  * Initialize PHP environment.
  */
 function drupal_environment_initialize() {
@@ -1442,6 +1484,13 @@ function drupal_maintenance_theme() {
 }
 
 /**
+ * Return TRUE if a Drupal installation is currently being attempted.
+ */
+function drupal_installation_attempted() {
+  return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install';
+}
+
+/**
  * Return the name of the localization function. Use in code that needs to
  * run both during installation and normal operation.
  */
@@ -1450,7 +1499,7 @@ function get_t() {
   // This is not converted to drupal_static because there is no point in
   // resetting this as it can not change in the course of a request.
   if (!isset($t)) {
-    $t = function_exists('install_main') ? 'st' : 't';
+    $t = drupal_installation_attempted() ? 'st' : 't';
   }
   return $t;
 }
Index: includes/cache-install.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache-install.inc,v
retrieving revision 1.2
diff -u -p -r1.2 cache-install.inc
--- includes/cache-install.inc	7 Aug 2007 08:39:35 -0000	1.2
+++ includes/cache-install.inc	20 Jul 2009 04:19:47 -0000
@@ -15,6 +15,10 @@ function cache_get($key, $table = 'cache
   return FALSE;
 }
 
+function cache_get_multiple(array &$cids, $bin = 'cache') {
+  return FALSE;
+}
+
 function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
   return;
 }
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.936
diff -u -p -r1.936 common.inc
--- includes/common.inc	19 Jul 2009 06:03:04 -0000	1.936
+++ includes/common.inc	20 Jul 2009 04:19:57 -0000
@@ -3597,15 +3597,18 @@ function drupal_cron_cleanup() {
  *   An array of file objects of the specified type.
  */
 function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) {
-  global $profile;
+  global $install_state;
   $config = conf_path();
 
   // When this function is called during Drupal's initial installation process,
   // the name of the profile that's about to be installed is stored in the global
-  // $profile variable. At all other times, the standard Drupal systems variable
+  // installation state. At all other times, the standard Drupal systems variable
   // table contains the name of the current profile, and we can call variable_get()
   // to determine what one is active.
-  if (!isset($profile)) {
+  if (isset($install_state['parameters']['profile'])) {
+    $profile = $install_state['parameters']['profile'];
+  }
+  else {
     $profile = variable_get('install_profile', 'default');
   }
   $searchdir = array($directory);
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.351
diff -u -p -r1.351 form.inc
--- includes/form.inc	18 Jul 2009 02:41:05 -0000	1.351
+++ includes/form.inc	20 Jul 2009 04:20:04 -0000
@@ -705,7 +705,11 @@ function drupal_redirect_form($form, $re
         call_user_func_array('drupal_goto', $goto);
       }
       else {
-        drupal_goto($goto);
+        // This function can be called from the installer, which guarantees
+        // that $redirect will always be a string, so catch that case here
+        // and use the appropriate redirect function.
+        $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto';
+        $function($goto);
       }
     }
     drupal_goto($_GET['q']);
Index: includes/install.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/install.inc,v
retrieving revision 1.97
diff -u -p -r1.97 install.inc
--- includes/install.inc	19 Jul 2009 04:48:10 -0000	1.97
+++ includes/install.inc	20 Jul 2009 04:20:06 -0000
@@ -165,7 +165,8 @@ function drupal_set_installed_schema_ver
  *   The name defined in the profile's _profile_details() hook.
  */
 function drupal_install_profile_name() {
-  global $profile;
+  global $install_state;
+  $profile = $install_state['parameters']['profile'];
   static $name = NULL;
 
   if (!isset($name)) {
@@ -190,7 +191,6 @@ function drupal_install_profile_name() {
  *   The auto-detected $base_url that should be configured in settings.php
  */
 function drupal_detect_baseurl($file = 'install.php') {
-  global $profile;
   $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://';
   $host = $_SERVER['SERVER_NAME'];
   $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']);
@@ -214,7 +214,7 @@ function drupal_detect_database_types() 
   // without modifying the installer.
   // Because we have no registry yet, we need to also include the install.inc
   // file for the driver explicitly.
-
+  require_once DRUPAL_ROOT . '/includes/database/database.inc';
   foreach (file_scan_directory(DRUPAL_ROOT . '/includes/database', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) {
     include_once "{$file->filepath}/install.inc";
     include_once "{$file->filepath}/database.inc";
@@ -240,7 +240,8 @@ function drupal_detect_database_types() 
 }
 
 abstract class DatabaseInstaller {
-  protected $success = array();
+  public $success = array();
+  public $failures = array();
   protected $tests = array(
     'testCreate' => array(
       'query' => 'CREATE TABLE drupal_install_test (id int NULL)',
@@ -269,7 +270,6 @@ abstract class DatabaseInstaller {
       'message' => 'Failed to drop a test table from your %name database server. We tried dropping a table with the command %query and %name reported the following error %error.',
     ),
   );
-  public $error = FALSE;
 
   protected function hasPdoDriver() {
     return in_array($this->pdoDriver, PDO::getAvailableDrivers());
@@ -310,7 +310,7 @@ abstract class DatabaseInstaller {
       $this->success[] = 'CONNECT';
     }
     catch (Exception $e) {
-      drupal_set_message(st('Failed to connect to your %name database server. %name reports the following message: <strong>%error</strong>.<ul><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li><li>Are you sure you have the correct database name?</li><li>Are you sure you have the correct username and password?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => $e->getMessage(), '%name' => $this->name())), 'error');
+      $this->failures[] = st('Failed to connect to your %name database server. %name reports the following message: <strong>%error</strong>.<ul><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li><li>Are you sure you have the correct database name?</li><li>Are you sure you have the correct username and password?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => $e->getMessage(), '%name' => $this->name()));
       return FALSE;
     }
   }
@@ -321,8 +321,7 @@ abstract class DatabaseInstaller {
       $this->success[] = $success;
     }
     catch (Exception $e) {
-      drupal_set_message(st($message, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name())), 'error');
-      $this->error = TRUE;
+      $this->failures[] = st($message, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name()));
       if ($fatal) {
         return FALSE;
       }
@@ -400,11 +399,11 @@ function drupal_rewrite_settings($settin
 
     $fp = fopen(DRUPAL_ROOT . '/' . $settings_file, 'w');
     if ($fp && fwrite($fp, $buffer) === FALSE) {
-      drupal_set_message(st('Failed to modify %settings, please verify the file permissions.', array('%settings' => $settings_file)), 'error');
+      throw new Exception(st('Failed to modify %settings, please verify the file permissions.', array('%settings' => $settings_file)));
     }
   }
   else {
-    drupal_set_message(st('Failed to open %settings, please verify the file permissions.', array('%settings' => $default_settings)), 'error');
+    throw new Exception(st('Failed to open %settings, please verify the file permissions.', array('%settings' => $default_settings)));
   }
 }
 
@@ -426,24 +425,24 @@ function drupal_get_install_files($modul
 /**
  * Verify an install profile for installation.
  *
- * @param $profile
- *   Name of install profile to verify.
- * @param $locale
- *   Name of locale used (if any).
+ * @param $install_state
+ *   An array of information about the current installation state.
  * @return
  *   The list of modules to install.
  */
-function drupal_verify_profile($profile, $locale) {
+function drupal_verify_profile($install_state) {
+  $profile = $install_state['parameters']['profile'];
+  $locale = $install_state['parameters']['locale'];
+
   include_once DRUPAL_ROOT . '/includes/file.inc';
   include_once DRUPAL_ROOT . '/includes/common.inc';
 
   $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
 
   if (!isset($profile) || !file_exists($profile_file)) {
-    install_no_profile_error();
+    throw new Exception(install_no_profile_error());
   }
-  $info = install_profile_info($profile);
-
+  $info = $install_state['profile_info'];
 
   // Get a list of modules that exist in Drupal's assorted subdirectories.
   $present_modules = array();
@@ -856,17 +855,19 @@ function install_goto($path) {
  */
 function st($string, $args = array()) {
   static $locale_strings = NULL;
-  global $profile, $install_locale;
+  global $install_state;
 
   if (!isset($locale_strings)) {
     $locale_strings = array();
-    $filename = 'profiles/' . $profile . '/translations/' . $install_locale . '.po';
+    if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) {
+    $filename = 'profiles/' . $install_state['parameters']['profile'] . '/translations/' . $install_state['parameters']['locale'] . '.po';
     if (file_exists(DRUPAL_ROOT . '/' . $filename)) {
       require_once DRUPAL_ROOT . '/includes/locale.inc';
       $file = (object) array('filepath' => $filename);
       _locale_import_read_po('mem-store', $file);
       $locale_strings = _locale_import_one_string('mem-report');
     }
+    }
   }
 
   require_once DRUPAL_ROOT . '/includes/theme.inc';
@@ -903,7 +904,7 @@ function drupal_check_profile($profile) 
   $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
 
   if (!isset($profile) || !file_exists($profile_file)) {
-    install_no_profile_error();
+    throw new Exception(install_no_profile_error());
   }
 
   $info = install_profile_info($profile);
@@ -982,8 +983,6 @@ function drupal_check_module($module) {
  * - name: The real name of the install profile for display purposes.
  * - description: A brief description of the profile.
  * - dependencies: An array of shortnames of other modules this install profile requires.
- * - tasks: An associative array of tasks and the page title of each task that need to be 
- *   completed for installation.
  *
  * Example of .info file:
  * @verbatim
@@ -1007,7 +1006,6 @@ function install_profile_info($profile, 
     // Set defaults for module info.
     $defaults = array(
       'dependencies' => array(),
-      'tasks' => array(),
       'description' => '',
       'version' => NULL,
       'php' => DRUPAL_MINIMUM_PHP,
Index: includes/database/database.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/database.inc,v
retrieving revision 1.62
diff -u -p -r1.62 database.inc
--- includes/database/database.inc	14 Jul 2009 10:22:17 -0000	1.62
+++ includes/database/database.inc	20 Jul 2009 04:20:11 -0000
@@ -2495,7 +2495,7 @@ function db_result(DatabaseStatementInte
  */
 function _db_check_install_needed() {
   global $databases;
-  if (empty($databases) && !function_exists('install_main')) {
+  if (empty($databases) && !drupal_installation_attempted()) {
     include_once DRUPAL_ROOT . '/includes/install.inc';
     install_goto('install.php');
   }
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.127
diff -u -p -r1.127 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	15 Jul 2009 02:08:41 -0000	1.127
+++ modules/simpletest/drupal_web_test_case.php	20 Jul 2009 04:20:17 -0000
@@ -1057,8 +1057,8 @@ class DrupalWebTestCase extends DrupalTe
     drupal_get_schema(NULL, TRUE);
 
     // Run default profile tasks.
-    $task = 'profile';
-    default_profile_tasks($task, '');
+    $install_state = array();
+    default_profile_site_setup($install_state);
 
     // Rebuild caches.
     node_types_rebuild();
@@ -1074,7 +1074,7 @@ class DrupalWebTestCase extends DrupalTe
 
     // Restore necessary variables.
     variable_set('install_profile', 'default');
-    variable_set('install_task', 'profile-finished');
+    variable_set('install_task', 'done');
     variable_set('clean_url', $clean_url_original);
     variable_set('site_mail', 'simpletest@example.com');
     // Set up English language.
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.49
diff -u -p -r1.49 system.api.php
--- modules/system/system.api.php	15 Jul 2009 02:08:41 -0000	1.49
+++ modules/system/system.api.php	20 Jul 2009 04:20:22 -0000
@@ -1825,126 +1825,147 @@ function hook_registry_files_alter(&$fil
   }
 }
 
-
 /**
- * Perform any final installation tasks for an installation profile.
+ * Return an array of tasks to be performed by an installation profile.
  *
- * The installer goes through the profile-select -> locale-select
- * -> 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
- * machine here to walk the user through those tasks. First time,
- * this function gets called with $task set to 'profile', and you
- * can advance to further tasks by setting $task to your tasks'
- * identifiers, used as array keys in the tasks property of the
- * profilename.info file.
- * 
- * You must avoid the reserved tasks listed in install_reserved_tasks(). 
- * 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 'profile-finished' task,
- * with which you hand control back to the installer. Each custom page
- * you return needs to provide a way to continue, such as a form
- * submission or a link. You should also set custom page titles.
- *
- * You should define the list of custom tasks you implement by
- * specifying an array of tasks in your profilename.info file, as these
- * show up in the list of tasks on the installer user interface.
- *
- * Example :
- *   task[custom_task] = My first custom task
- *   task[custom_task_2] = My second custom task
- *
- * Remember that the user will be able to reload the pages multiple
- * times, so you might want to use variable_set() and variable_get()
- * to remember your data and control further processing, if $task
- * is insufficient. Should a profile want to display a form here,
- * it can; the form should set '#redirect' to FALSE, and rely on
- * an action in the submit handler, such as variable_set(), to
- * detect submission and proceed to further tasks. See the configuration
- * form handling code in install_tasks() for an example.
- *
- * Important: Any temporary variables should be removed using
- * variable_del() before advancing to the 'profile-finished' phase.
- *
- * @param $task
- *   The current $task of the install system. When hook_profile_tasks()
- *   is first called, this is 'profile'.
- * @param $url
- *   Complete URL to be used for a link or form action on a custom page,
- *   if providing any, to allow the user to proceed with the installation.
+ * Any tasks you define here will be run, in order, after the installer has
+ * finished the site configuration step but before it has moved on to the
+ * final import of languages and the end of the installation.
+ *
+ * If this function is implemented, you can have any number of custom tasks to
+ * perform during this phase. Each task receives the global installation state
+ * variable, $install_state, as input, and has the opportunity to access or
+ * modify any of its settings. See the install_state_defaults() function in the
+ * installer for the list of $install_state settings used by Drupal core.
+ *
+ * In general, each task function you define should return content to be
+ * displayed to the end user, if you want the page request to end. You should
+ * also set custom page titles within the task function, if necessary.
+ * Alternatively, if you want to pass control to the next task without ending
+ * the page request, do not return any output.
+ *
+ * The task function is treated specially if it defines a form or requires
+ * batch processing; in that case, you should return either the form API
+ * definition or batch API array, as appropriate. See below for more
+ * information. It is important to use these APIs directly, since the
+ * installer may be run non-interactively (for example, via a command line
+ * script), all in one page request; in that case, the installer will
+ * automatically take care of submitting forms and processing batches
+ * correctly for both types of installations. You can inspect the
+ * $install_state['interactive'] boolean to see whether or not the current
+ * installation is interactive, if you need access to this information.
+ *
+ * Remember that a user installing Drupal interactively will be able to reload
+ * the pages multiple times, so you might want to use variable_set() and
+ * variable_get() to remember your data and control further processing. It is
+ * important to remove any temporary variables using variable_del() before your
+ * last task has completed and control is handed back to the installer.
  *
  * @return
- *   An optional HTML string to display to the user. Only used if you
- *   modify the $task, otherwise discarded.
+ *   A keyed array of tasks the profile will perform during the final stage of
+ *   the installation. Each key represents the name of a function (usually a
+ *   function defined by this profile, although that is not strictly required)
+ *   that is called when that task is run. The values are associative arrays
+ *   containing the following key-value pairs (all of which are optional):
+ *     - 'display_name'
+ *       The human-readable name of the task. This will be displayed to the
+ *       user while the installer is running, along with a list of other tasks
+ *       that are being run. Leave this unset to prevent the task from
+ *       appearing in the list.
+ *     - 'display'
+ *       A boolean which can be set to force the task to either display or not
+ *       display to the end user. This can be used to provide finer-grained
+ *       control over whether or not the task will display.
+ *     - 'type'
+ *       A string representing the type of task. If this is left unset or set
+ *       to 'normal', the task will be treated as a regular callback function,
+ *       which does its processing and optionally returns HTML output. If this
+ *       is set to 'batch', the task function should return a batch API
+ *       definition suitable for batch_set(), and the installer will run the
+ *       task via batch processing. Finally, if this is set to 'form', the task
+ *       function should return a standard form API definition (and separately
+ *       define validation and submit handlers, as appropriate), and the
+ *       installer will appropriately direct the user through the form
+ *       submission process.
+ *     - 'run'
+ *       A constant representing the manner in which the task will be run. If
+ *       this is left unset or set to INSTALL_RUN_TASK_IF_NOT_COMPLETED, the
+ *       task will run once during the installation of the profile, until the
+ *       installer marks it complete. If this is set to INSTALL_SKIP_TASK, the
+ *       task will not run during the current installation page request.
+ *       Finally, if this is set to INSTALL_RUN_TASK_IF_REACHED, the task will
+ *       run on each installation page request that reaches it.
+ *     - 'function'
+ *       Normally this does not need to be set, but it can be used to force the
+ *       installer to call a different function when the task is run (rather
+ *       than the function whose name is given by the array key). This could be
+ *       used, for example, to allow the same function to be called by two
+ *       different tasks.
+ *
+ * @see install_state_defaults()
  */
-function hook_profile_tasks(&$task, $url) {
-
-  // Enable some standard blocks.
-  $values = array(
-    array(
-      'module' => 'system',
-      'delta' => 'main',
-      'theme' => 'garland',
-      'status' => 1,
-      'weight' => 0,
-      'region' => 'content',
-      'pages' => '',
-      'cache' => -1,
+function hook_profile_tasks() {
+  // This is an example of a variable that your profile might set and unset
+  // throughout the tasks below, to control which tasks actually need to be
+  // processed.
+  $myprofile_needs_batch_processing = variable_get('myprofile_needs_batch_processing', FALSE);
+  $tasks = array(
+    // This is an example of a task that defines a form which the user who is
+    // installing the site will be asked to fill out. To implement this task,
+    // your profile would define a function named myprofile_data_import_form()
+    // as a normal form API callback function, with associated validation and
+    // submit handlers. In the submit handler, in addition to saving whatever
+    // other data you have collected from the user, you might also call
+    // variable_set('myprofile_needs_batch_processing', TRUE) if the user has
+    // entered data which requires that batch processing will need to occur
+    // later on.
+    'myprofile_data_import_form' => array(
+      'display_name' => st('Data import options'),
+      'type' => 'form',
     ),
-    array(
-      'module' => 'user',
-      'delta' => 'login',
-      'theme' => 'garland',
-      'status' => 1,
-      'weight' => 0,
-      'region' => 'left',
-      'pages' => '',
-      'cache' => -1,
+    // Similarly, to implement this task, your profile would define a function
+    // named myprofile_settings_form() with associated validation and submit
+    // handlers. This form might be used to collect and save additional
+    // information from the user that your profile needs. There are no extra
+    // steps required for your profile to act as a multi-step installation
+    // wizard; you can simply define as many tasks of type 'form' as you wish
+    // to execute.
+    'myprofile_settings_form' => array(
+      'display_name' => st('Additional options'),
+      'type' => 'form',
     ),
-    array(
-      'module' => 'system',
-      'delta' => 'navigation',
-      'theme' => 'garland',
-      'status' => 1,
-      'weight' => 0,
-      'region' => 'left',
-      'pages' => '',
-      'cache' => -1,
+    // This is an example of a task that performs batch operations. To
+    // implement this task, your profile would define a function named
+    // myprofile_batch_processing() which returns a batch API array definition
+    // that the installer will use to execute your batch operations. Due to the
+    // 'myprofile_needs_batch_processing' variable used here, this task will be
+    // hidden and skipped unless your profile set it to TRUE in one of the
+    // previous tasks.
+    'myprofile_batch_processing' => array(
+      'display_name' => st('Import additional data'),
+      'display' => $myprofile_needs_batch_processing,
+      'type' => 'batch',
+      'run' => $myprofile_needs_batch_processing ? INSTALL_RUN_TASK_IF_NOT_COMPLETED : INSTALL_SKIP_TASK,
     ),
-    array(
-      'module' => 'system',
-      'delta' => 'management',
-      'theme' => 'garland',
-      'status' => 1,
-      'weight' => 1,
-      'region' => 'left',
-      'pages' => '',
-      'cache' => -1,
-    ),
-    array(
-      'module' => 'system',
-      'delta' => 'help',
-      'theme' => 'garland',
-      'status' => 1,
-      'weight' => 0,
-      'region' => 'help',
-      'pages' => '',
-      'cache' => -1,
+    // This is an example of a task that will not be displayed in the list that
+    // the user sees. To implement this task, your profile would define a
+    // function named myprofile_final_site_setup(), in which additional,
+    // automated site setup operations would be performed. Since this is the
+    // last task defined by your profile, you might also use this function to
+    // call variable_del('myprofile_needs_batch_processing') and clean up the
+    // variable that was used above. You might choose to return no output from
+    // this function, if you want the user to pass to the final Drupal
+    // installation tasks uninterrupted, or you could return themed output that
+    // the user will see (for example, a confirmation page explaining that your
+    // profile's tasks are complete, with a link to reload the current page and
+    // therefore pass on to the final Drupal installation tasks when the user
+    // is ready to do so).
+    'myprofile_final_site_setup' => array(
     ),
   );
-  $query = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache'));
-  foreach ($values as $record) {
-    $query->values($record);
-  }
-  $query->execute();  
+  return $tasks; 
 }
 
-
 /**
  * @} End of "addtogroup hooks".
  */
Index: profiles/default/default.profile
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.profile,v
retrieving revision 1.55
diff -u -p -r1.55 default.profile
--- profiles/default/default.profile	15 Jul 2009 02:08:41 -0000	1.55
+++ profiles/default/default.profile	20 Jul 2009 04:20:23 -0000
@@ -4,7 +4,24 @@
 /**
  * Implement hook_profile_tasks().
  */
-function default_profile_tasks(&$task, $url) {
+function default_profile_tasks() {
+  $tasks = array(
+    'default_profile_site_setup' => array(),
+  );
+  return $tasks;
+}
+
+/**
+ * Installation task; perform actions to set up the site for this profile.
+ *
+ * This task does not return any output, meaning that control will be passed
+ * along to the next task without ending the page request.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ */
+function default_profile_site_setup(&$install_state) {
+
   // Enable some standard blocks.
   $values = array(
     array(
Index: profiles/expert/expert.profile
===================================================================
RCS file: /cvs/drupal/drupal/profiles/expert/expert.profile,v
retrieving revision 1.10
diff -u -p -r1.10 expert.profile
--- profiles/expert/expert.profile	15 Jul 2009 02:08:41 -0000	1.10
+++ profiles/expert/expert.profile	20 Jul 2009 04:20:23 -0000
@@ -4,7 +4,23 @@
 /**
  * Implement hook_profile_tasks().
  */
-function expert_profile_tasks(&$task, $url) {
+function expert_profile_tasks() {
+  $tasks = array(
+    'expert_profile_site_setup' => array(),
+  );
+  return $tasks;
+}
+
+/**
+ * Installation task; perform actions to set up the site for this profile.
+ *
+ * This task does not return any output, meaning that control will be passed
+ * along to the next task without ending the page request.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ */
+function expert_profile_site_setup(&$install_state) {
 
   // Enable some standard blocks.
   $values = array(
