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;