Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.136
diff -u -F^f -r1.136 form.inc
--- includes/form.inc	21 Aug 2006 06:22:01 -0000	1.136
+++ includes/form.inc	22 Aug 2006 05:50:05 -0000
@@ -39,7 +39,9 @@
 /**
  * Retrieves a form from a builder function, passes it on for
  * processing, and renders the form or redirects to its destination
- * as appropriate.
+ * as appropriate. In multi-step form scenerios, it handles properly
+ * processing the values using the previous step's form definition,
+ * then rendering the requested step for display.
  *
  * @param $form_id
  *   The unique string identifying the desired form. If a function
@@ -53,16 +55,97 @@
  * @return
  *   The rendered form.
  */
-function drupal_get_form($form_id) {
-  $args = func_get_args();
-  $form = call_user_func_array('drupal_retrieve_form', $args);
+function drupal_get_form($form_id) { 
+  // In multi-step form scenerios, the incoming $_POST values are not
+  // necessarily intended for the current form. We need to build
+  // a copy of the previously built form for validation and processing,
+  // then go on to the one that was requested if everything works.
+
+  $form_build_id = md5(mt_rand());
+  if (isset($_POST['form_build_id']) && isset($_SESSION['form'][$_POST['form_build_id']])) {
+    // There's a previously stored multi-step form. We should handle
+    // IT first.
+    $stored = TRUE;
+    $args = $_SESSION['form'][$_POST['form_build_id']];
+    $form = call_user_func_array('drupal_retrieve_form', $args);
+  }
+  else {
+    // We're coming in fresh; build things as they would be. If the
+    // form's #multistep flag is set, store the build parameters so
+    // the same form can be reconstituted for validation.
+    $args = func_get_args();
+    $form = call_user_func_array('drupal_retrieve_form', $args);
+    if (isset($form['#multistep']) && $form['#multistep']) {
+      $_SESSION['form'][$form_build_id] = $args;
+      $_SESSION['form'][$form_build_id]['#timestamp'] = time();
+      $form['#build_id'] = $form_build_id;
+    }
+    $stored = FALSE;
+  }
+
+  // Process the form, submit it, and store any errors if necessary.
+  drupal_process_form($args[0], $form);
+
+  if ($stored && !form_get_errors()) {
+    // If it's a stored form and there were no errors, we processed the
+    // stored form successfully. Now we need to build the form that was
+    // actually requested. We always pass in the current $_POST values
+    // to the builder function, as values from one stage of a multistep
+    // form can determine how subsequent steps are displayed.
+    $args = func_get_args();
+    $args[] = $_POST['edit'];
+    $form = call_user_func_array('drupal_retrieve_form', $args);
+    unset($_SESSION['form'][$_POST['form_build_id']]);
+    if (isset($form['#multistep']) && $form['#multistep']) {
+      $_SESSION['form'][$form_build_id] = $args;
+      $_SESSION['form'][$form_build_id]['#timestamp'] = time();
+      $form['#build_id'] = $form_build_id;
+    }
+    // If we're in this part of the code, $_POST['edit'] always contains
+    // values from the previously submitted form. Unset it to avoid
+    // any accidental submission of doubled data, then process the form
+    // to prep it for rendering.
+    unset($_POST['edit']);
+    drupal_process_form($args[0], $form);
+  }
+
+  return drupal_render_form($args[0], $form);
+}
 
-  $redirect = drupal_process_form($form_id, $form);
+/**
+ * Retrieves a form using a form_id, populates it with $form_values,
+ * processes it, and returns any validation errors encountered. This
+ * function is the programmatic counterpart to drupal_get_form().
+ *
+ * @param $form_id
+ *   The unique string identifying the desired form. If a function
+ *   with that name exists, it is called to build the form array.
+ *   Modules that need to generate the same form (or very similar forms)
+ *   using different $form_ids can implement hook_forms(), which maps
+ *   different $form_id values to the proper form building function. Examples
+ *   may be found in node_forms(), search_forms(), and user_forms().
+ * @param $form_values
+ *   An array of values mirroring the values returned by a given form
+ *   when it is submitted by a user.
+ * @param ...
+ *   Any additional arguments needed by the form building function.
+ * @return
+ *   Any form validation errors encountered.
+ */
+ function drupal_execute($form_id, $form_values) {
+  $args = func_get_args();
 
-  if (isset($redirect)) {
-    drupal_redirect_form($form, $redirect);
+  $form_id = array_shift($args);
+  $form_values = array_shift($args);
+  array_unshift($args, $form_id);
+  
+  if (isset($form_values)) {
+    $form = call_user_func_array('drupal_retrieve_form', $args);
+    $form['#post']['edit'] = $form_values;
+    drupal_process_form($form_id, $form);
+    
+    return form_get_errors();
   }
-  return drupal_render_form($form_id, $form);
 }
 
 /**
@@ -95,7 +178,9 @@ function drupal_retrieve_form($form_id) 
     }
   }
   // $callback comes from a hook_forms() implementation
-  return call_user_func_array(isset($callback) ? $callback : $form_id, $args);
+  $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);
+  $form['#params'] = func_get_args();
+  return $form;
 }
 
 /**
@@ -129,6 +214,9 @@ function drupal_process_form($form_id, &
     // In that case we accept a submission without button values.
     if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
       $redirect = drupal_submit_form($form_id, $form);
+      if (!$form['#programmed']) {
+        drupal_redirect_form($form, $redirect);
+      }
     }
   }
 
@@ -160,6 +248,17 @@ function drupal_prepare_form($form_id, &
     $form['#programmed'] = TRUE;
   }
 
+  // In multi-step form scenerios, this id is used to identify
+  // a unique instance of a particular form for retrieval.
+  if (isset($form['#build_id'])) {
+    $form['form_build_id'] = array(
+      '#type' => 'hidden',
+      '#value' => $form['#build_id'],
+      '#id' => $form['#build_id'],
+      '#name' => 'form_build_id',
+    );
+  }
+
   // If $base is set, it is used in place of $form_id when constructing validation,
   // submission, and theming functions. Useful for mapping many similar or duplicate
   // forms with different $form_ids to the same processing functions.
@@ -495,14 +594,14 @@ function form_builder($form_id, $form) {
     if (!isset($form['#id'])) {
       $form['#id'] =  'edit-' . implode('-', $form['#parents']);
     }
-
     $posted = (($form['#programmed']) || (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id)));
     $edit = $posted ? $form['#post']['edit'] : array();
+
     foreach ($form['#parents'] as $parent) {
       $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
     }
     if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
-      if ($posted) {
+      if ($posted && (!$form['#programmed'] || isset($edit))) {
         switch ($form['#type']) {
           case 'checkbox':
             $form['#value'] = !empty($edit) ? $form['#return_value'] : 0;
