=== modified file 'includes/form.inc' --- includes/form.inc +++ includes/form.inc @@ -17,17 +17,16 @@ * $output = drupal_get_form('user_register'); * * Forms can also be built and submitted programmatically without any user input - * by populating $form['#post']['edit'] with values to be submitted. For example: + * using the drupal_execute() function. For example: * * // register a new user - * $form = drupal_retrieve_form('user_register'); - * $form['#post']['edit']['name'] = 'robo-user'; - * $form['#post']['edit']['mail'] = 'robouser@example.com'; - * $form['#post']['edit']['pass'] = 'password'; - * drupal_process_form('user_register', $form); + * $values['name'] = 'robo-user'; + * $values['mail'] = 'robouser@example.com'; + * $values['pass'] = 'password'; + * $errors = drupal_execute('user_register', $values); * - * Calling form_get_errors() will list any validation errors that prevented the - * form from being submitted. + * drupal_execute() returns an array containing any validation errors + * encountered during processing. * * For information on the format of the structured arrays used to define forms, * and more detailed explanations of the Form API workflow, see the reference at @@ -39,7 +38,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,11 +54,95 @@ * @return * The rendered form. */ -function drupal_get_form($form_id) { +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; + $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; + $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); +} + +/** + * 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(); - $form = call_user_func_array('drupal_retrieve_form', $args); - drupal_process_form($form_id, $form); - return drupal_render_form($form_id, $form); + + $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(); + } } /** @@ -90,7 +175,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['#parameters'] = func_get_args(); + return $form; } /** @@ -158,6 +245,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. @@ -500,38 +598,40 @@ function form_builder($form_id, $form) { foreach ($form['#parents'] as $parent) { $edit = isset($edit[$parent]) ? $edit[$parent] : NULL; } - switch ($form['#type']) { - case 'checkbox': - $form['#value'] = !empty($edit) ? $form['#return_value'] : 0; - break; - case 'select': - if (isset($form['#multiple']) && $form['#multiple']) { - if (isset($edit) && is_array($edit)) { - $form['#value'] = drupal_map_assoc($edit); + if (!$form['#programmed'] || isset($edit)) { + switch ($form['#type']) { + case 'checkbox': + $form['#value'] = !empty($edit) ? $form['#return_value'] : 0; + break; + case 'select': + if (isset($form['#multiple']) && $form['#multiple']) { + if (isset($edit) && is_array($edit)) { + $form['#value'] = drupal_map_assoc($edit); + } + else { + $form['#value'] = array(); + } } - else { - $form['#value'] = array(); + elseif (isset($edit)) { + $form['#value'] = $edit; } - } - elseif (isset($edit)) { - $form['#value'] = $edit; - } - break; - case 'textfield': - if (isset($edit)) { - // Equate $edit to the form value to ensure it's marked for validation - $edit = str_replace(array("\r", "\n"), '', $edit); - $form['#value'] = $edit; - } - break; - default: - if (isset($edit)) { - $form['#value'] = $edit; - } - } - // Mark all posted values for validation - if ((isset($form['#value']) && $form['#value'] === $edit) || (isset($form['#required']) && $form['#required'])) { - $form['#needs_validation'] = TRUE; + break; + case 'textfield': + if (isset($edit)) { + // Equate $edit to the form value to ensure it's marked for validation + $edit = str_replace(array("\r", "\n"), '', $edit); + $form['#value'] = $edit; + } + break; + default: + if (isset($edit)) { + $form['#value'] = $edit; + } + } + // Mark all posted values for validation + if ((isset($form['#value']) && $form['#value'] === $edit) || (isset($form['#required']) && $form['#required'])) { + $form['#needs_validation'] = TRUE; + } } } if (!isset($form['#value'])) {