diff --git includes/form.inc includes/form.inc index bf9ab04..e0a8157 100644 --- includes/form.inc +++ includes/form.inc @@ -70,6 +70,66 @@ function drupal_get_form($form_id) { $form_state = array('storage' => NULL, 'submitted' => FALSE); $args = func_get_args(); + array_shift($args); // remove the form id + $form_state['args'] = $args; + + return drupal_build_form($form_id, $form_state); +} + +/** + * Retrieves a form from a constructor function, or from the cache if + * the form was built in a previous page-load. The form is then passesed + * on for processing, after and rendered for display if necessary. + * + * @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 constructor function. Examples + * may be found in node_forms(), search_forms(), and user_forms(). + * @param &$form_state + * The $form_state array which stores information about the form. This + * is passed as a reference so that the caller can use it to examine + * what the form did when the form is complete. + * + * Unlike drupal_get_form() where args are passed as additional arguments, + * Any arguments should be in $form_state['args']. Otherwise, this function + * is very similar to drupal_get_form(). + * + * The following parameters may be set in $form_state to affect how the + * form is rendered: + * - args: An array of arguments to pass to the form builder. + * - input: An array of input that corresponds to $_POST. + * - method: May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method + * forms do not use form IDs so are always considered to be submitted. This + * can have unexpected effects. + * - rerender: May be set to FALSE to force form not to rerender after submit. + * - no_redirect: If set to TRUE the form will NOT perform a drupal_goto even + * if a redirect is set. + * - always_process: If TRUE and the method is GET, a form_id is not necessary. + * This should *only* be used on RESTful get forms that do NOT write data, + * as this could lead to security issues. + * - must_validate: Ordinarily a form is only validated once but there are times, + * when a form is resubmitted internally and should be validated again. Setting + * this to true will force that to happen. + * @return + * The rendered form. + */ +function drupal_build_form($form_id, &$form_state) { + // Ensure that we have some defaults. + // These are defaults only; if already set they will not be overridden. + $form_state += array( + 'storage' => NULL, + 'submitted' => FALSE, + 'method' => 'post', + 'rerender' => TRUE + ); + + if (!isset($form_state['input'])) { + $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST; + } + $cacheable = FALSE; if (isset($_SESSION['batch_form_state'])) { @@ -80,36 +140,35 @@ function drupal_get_form($form_id) { unset($_SESSION['batch_form_state']); } else { - // If the incoming $_POST contains a form_build_id, we'll check the + // If the incoming input contains a form_build_id, we'll check the // cache for a copy of the form in question. If it's there, we don't // have to rebuild the form to proceed. In addition, if there is stored // form_state data from a previous step, we'll retrieve it so it can // be passed on to the form processing code. - if (isset($_POST['form_id']) && $_POST['form_id'] == $form_id && !empty($_POST['form_build_id'])) { - $form = form_get_cache($_POST['form_build_id'], $form_state); + if (isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id'])) { + $form = form_get_cache($form_state['input']['form_build_id'], $form_state); } // If the previous bit of code didn't result in a populated $form // object, we're hitting the form for the first time and we need // to build it from scratch. if (!isset($form)) { - $form_state['post'] = $_POST; - // Use a copy of the function's arguments for manipulation - $args_temp = $args; - $args_temp[0] = &$form_state; - array_unshift($args_temp, $form_id); - - $form = call_user_func_array('drupal_retrieve_form', $args_temp); + $form = drupal_retrieve_form($form_id, $form_state); $form_build_id = 'form-' . md5(uniqid(mt_rand(), TRUE)); $form['#build_id'] = $form_build_id; + + // If the form method is set to get in the form_state but not + // the form, or vice versa, fix that. + if ($form_state['method'] == 'get' && !isset($form['#method'])) { + $form['#method'] = 'get'; + } + drupal_prepare_form($form_id, $form, $form_state); // Store a copy of the unprocessed form for caching and indicate that it // is cacheable if #cache will be set. $original_form = $form; $cacheable = TRUE; - unset($form_state['post']); } - $form['#post'] = $_POST; // Now that we know we have a form, we'll process it (validating, // submitting, and handling the results returned by its submission @@ -117,7 +176,13 @@ function drupal_get_form($form_id) { // altering the $form_state variable, which is passed into them by // reference. drupal_process_form($form_id, $form, $form_state); - if ($cacheable && !empty($form['#cache'])) { + // If we were told not to redirect, but not told to re-render, return + // here. + if (!empty($form_state['executed']) && empty($form_state['rerender'])) { + return; + } + + if ($cacheable && !empty($form['#cache']) && empty($form['#no_cache'])) { // Caching is done past drupal_process_form so #process callbacks can // set #cache. By not sending the form state, we avoid storing // $form_state['storage']. @@ -140,7 +205,7 @@ function drupal_get_form($form_id) { // other variables passed into drupal_get_form(). if (!empty($form_state['rebuild']) || !empty($form_state['storage'])) { - $form = drupal_rebuild_form($form_id, $form_state, $args); + $form = drupal_rebuild_form($form_id, $form_state); } // If we haven't redirected to a new location by now, we want to @@ -162,6 +227,10 @@ function drupal_get_form($form_id) { * $form_state['clicked_button']['#array_parents'] will help you to find which * part. * + * If you are getting a form from the cache, use $form['#args'] to shift off + * the $form_id from its beginning then the resulting array can be given to + * $form_state['args']. + * * @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. @@ -172,12 +241,6 @@ function drupal_get_form($form_id) { * @param $form_state * A keyed array containing the current state of the form. Most * important is the $form_state['storage'] collection. - * @param $args - * Any additional arguments are passed on to the functions called by - * drupal_get_form(), plus the original form_state in the beginning. If you - * are getting a form from the cache, use $form['#parameters'] to shift off - * the $form_id from its beginning then the resulting array can be used as - * $arg here. * @param $form_build_id * If the AHAH callback calling this function only alters part of the form, * then pass in the existing form_build_id so we can re-cache with the same @@ -185,14 +248,8 @@ function drupal_get_form($form_id) { * @return * The newly built form. */ -function drupal_rebuild_form($form_id, &$form_state, $args, $form_build_id = NULL) { - // Remove the first argument. This is $form_id.when called from - // drupal_get_form and the original $form_state when called from some AHAH - // callback. Neither is needed. After that, put in the current state. - $args[0] = &$form_state; - // And the form_id. - array_unshift($args, $form_id); - $form = call_user_func_array('drupal_retrieve_form', $args); +function drupal_rebuild_form($form_id, &$form_state, $form_build_id = NULL) { + $form = drupal_retrieve_form($form_id, $form_state); if (!isset($form_build_id)) { // We need a new build_id for the new version of the form. @@ -201,17 +258,22 @@ function drupal_rebuild_form($form_id, &$form_state, $args, $form_build_id = NUL $form['#build_id'] = $form_build_id; drupal_prepare_form($form_id, $form, $form_state); - // Now, we cache the form structure so it can be retrieved later for - // validation. If $form_state['storage'] is populated, we'll also cache - // it so that it can be used to resume complex multi-step processes. - form_set_cache($form_build_id, $form, $form_state); + if (empty($form['#no_cache'])) { + // Now, we cache the form structure so it can be retrieved later for + // validation. If $form_state['storage'] is populated, we'll also cache + // it so that it can be used to resume complex multi-step processes. + form_set_cache($form_build_id, $form, $form_state); + } // Clear out all post data, as we don't want the previous step's // data to pollute this one and trigger validate/submit handling, // then process the form for rendering. - $_POST = array(); - $form['#post'] = array(); - drupal_process_form($form_id, $form, $form_state); + $form_state['input'] = array(); + + // Originally this called drupal_process_form, but all that happens there + // is form_builder and then submission; and the rebuilt form is not + // allowed to submit. Therefore, just do this: + $form = form_builder($form_id, $form, $form_state); return $form; } @@ -290,13 +352,16 @@ function form_set_cache($form_build_id, $form, $form_state) { * drupal_execute('story_node_form', $form_state, (object)$node); */ function drupal_execute($form_id, &$form_state) { - $args = func_get_args(); - - // Make sure $form_state is passed around by reference. - $args[1] = &$form_state; + if (!isset($form_state['args'])) { + $args = func_get_args(); + array_shift($args); + array_shift($args); + $form_state['args'] = $args; + } - $form = call_user_func_array('drupal_retrieve_form', $args); - $form['#post'] = $form_state['values']; + $form = drupal_retrieve_form($form_id, $form_state); + $form_state['input'] = $form_state['values']; + $form['#programmed'] = TRUE; drupal_prepare_form($form_id, $form, $form_state); drupal_process_form($form_id, $form, $form_state); } @@ -325,15 +390,8 @@ function drupal_retrieve_form($form_id, &$form_state) { // We save two copies of the incoming arguments: one for modules to use // when mapping form ids to constructor functions, and another to pass to - // the constructor function itself. We shift out the first argument -- the - // $form_id itself -- from the list to pass into the constructor function, - // since it's already known. - $args = func_get_args(); - $saved_args = $args; - array_shift($args); - if (isset($form_state)) { - array_shift($args); - } + // the constructor function itself. + $args = $form_state['args']; // We first check to see if there's a function named after the $form_id. // If there is, we simply pass the arguments on to it to get the form. @@ -362,18 +420,12 @@ function drupal_retrieve_form($form_id, &$form_state) { } } - array_unshift($args, NULL); - $args[0] = &$form_state; + $args = array_merge(array(&$form_state), $args); // If $callback was returned by a hook_forms() implementation, call it. // Otherwise, call the function named after the form id. $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args); - - // We store the original function arguments, rather than the final $arg - // value, so that form_alter functions can see what was originally - // passed to drupal_retrieve_form(). This allows the contents of #parameters - // to be saved and passed in at a later date to recreate the form. - $form['#parameters'] = $saved_args; + $form['#args'] = $form_state['args']; return $form; } @@ -395,10 +447,23 @@ function drupal_retrieve_form($form_id, &$form_state) { function drupal_process_form($form_id, &$form, &$form_state) { $form_state['values'] = array(); + // With $_GET, these forms are always submitted if requested. + if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) { + if (!isset($form_state['input']['form_build_id'])) { + $form_state['input']['form_build_id'] = $form['#build_id']; + } + if (!isset($form_state['input']['form_id'])) { + $form_state['input']['form_id'] = $form_id; + } + if (!isset($form_state['input']['form_token']) && isset($form['#token'])) { + $form_state['input']['form_token'] = drupal_get_token($form['#token']); + } + } + $form = form_builder($form_id, $form, $form_state); // Only process the form if it is programmed or the form_id coming // from the POST data is set and matches the current form_id. - if ((!empty($form['#programmed'])) || (!empty($form['#post']) && (isset($form['#post']['form_id']) && ($form['#post']['form_id'] == $form_id)))) { + if ((!empty($form['#programmed'])) || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) { drupal_validate_form($form_id, $form, $form_state); // form_clean_id() maintains a cache of element IDs it has seen, @@ -445,7 +510,12 @@ function drupal_process_form($form_id, &$form, &$form_state) { // however, we'll skip this and let the calling function examine // the resulting $form_state bundle itself. if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) { - drupal_redirect_form($form, $form_state['redirect']); + if (!empty($form_state['no_redirect'])) { + $form_state['executed'] = TRUE; + } + else { + drupal_redirect_form($form, $form_state['redirect']); + } } } } @@ -469,7 +539,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { global $user; $form['#type'] = 'form'; - $form['#programmed'] = isset($form['#post']); + $form['#programmed'] = isset($form['#programmed']) ? $form['#programmed'] : FALSE; if (isset($form['#build_id'])) { $form['form_build_id'] = array( @@ -568,7 +638,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { function drupal_validate_form($form_id, $form, &$form_state) { static $validated_forms = array(); - if (isset($validated_forms[$form_id])) { + if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) { return; } @@ -885,7 +955,6 @@ function form_builder($form_id, $form, &$form_state) { // Recurse through all child elements. $count = 0; foreach (element_children($form) as $key) { - $form[$key]['#post'] = $form['#post']; $form[$key]['#programmed'] = $form['#programmed']; // Don't squash an existing tree value. if (!isset($form[$key]['#tree'])) { @@ -994,15 +1063,15 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com if (!isset($form['#value']) && !array_key_exists('#value', $form)) { $function = !empty($form['#value_callback']) ? $form['#value_callback'] : 'form_type_' . $form['#type'] . '_value'; - if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form['#post']) && (isset($form['#post']['form_id']) && $form['#post']['form_id'] == $form_id))) { - $edit = $form['#post']; + if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form_state['input']) && (isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id))) { + $edit = $form_state['input']; foreach ($form['#parents'] as $parent) { $edit = isset($edit[$parent]) ? $edit[$parent] : NULL; } if (!$form['#programmed'] || isset($edit)) { // Call #type_value to set the form value; if (function_exists($function)) { - $form['#value'] = $function($form, $edit); + $form['#value'] = $function($form, $edit, $form_state); } if (!isset($form['#value']) && isset($edit)) { $form['#value'] = $edit; @@ -1032,13 +1101,13 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com // We compare the incoming values with the buttons defined in the form, // and flag the one that matches. We have to do some funky tricks to // deal with Internet Explorer's handling of single-button forms, though. - if (!empty($form['#post']) && isset($form['#executes_submit_callback'])) { + if (!empty($form_state['input']) && isset($form['#executes_submit_callback'])) { // First, accumulate a collection of buttons, divided into two bins: // those that execute full submit callbacks and those that only validate. $button_type = $form['#executes_submit_callback'] ? 'submit' : 'button'; $form_state['buttons'][$button_type][] = $form; - if (_form_button_was_clicked($form)) { + if (_form_button_was_clicked($form, $form_state)) { $form_state['submitted'] = $form_state['submitted'] || $form['#executes_submit_callback']; // In most cases, we want to use form_set_value() to manipulate @@ -1077,13 +1146,13 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com * and we'll never detect a match. That special case is handled by * _form_builder_ie_cleanup(). */ -function _form_button_was_clicked($form) { +function _form_button_was_clicked($form, &$form_state) { // First detect normal 'vanilla' button clicks. Traditionally, all // standard buttons on a form share the same name (usually 'op'), // and the specific return value is used to determine which was // clicked. This ONLY works as long as $form['#name'] puts the // value at the top level of the tree of $_POST data. - if (isset($form['#post'][$form['#name']]) && $form['#post'][$form['#name']] == $form['#value']) { + if (isset($form_state['input'][$form['#name']]) && $form_state['input'][$form['#name']] == $form['#value']) { return TRUE; } // When image buttons are clicked, browsers do NOT pass the form element @@ -1134,11 +1203,13 @@ function _form_builder_ie_cleanup($form, &$form_state) { * @param $edit * The incoming POST data to populate the form element. If this is FALSE, * the element's default value should be returned. +* @param $form_state + * A keyed array containing the current state of the form. * @return * The data that will appear in the $form_state['values'] collection * for this element. Return nothing to use the default. */ -function form_type_image_button_value($form, $edit = FALSE) { +function form_type_image_button_value($form, $edit, $form_state) { if ($edit !== FALSE) { if (!empty($edit)) { // If we're dealing with Mozilla or Opera, we're lucky. It will @@ -1151,7 +1222,7 @@ function form_type_image_button_value($form, $edit = FALSE) { // X and one for the Y coordinates on which the user clicked the // button. We'll find this element in the #post data, and search // in the same spot for its name, with '_x'. - $post = $form['#post']; + $post = $form_state['input']; foreach (split('\[', $form['#name']) as $element_name) { // chop off the ] that may exist. if (substr($element_name, -1) == ']') { @@ -1639,7 +1710,7 @@ function password_confirm_validate($form, &$form_state) { form_error($form, t('The specified passwords do not match.')); } } - elseif ($form['#required'] && !empty($form['#post'])) { + elseif ($form['#required'] && !empty($form_state['input'])) { form_error($form, t('Password field is required.')); } diff --git modules/aggregator/aggregator.admin.inc modules/aggregator/aggregator.admin.inc index 944c986..cd95a77 100644 --- modules/aggregator/aggregator.admin.inc +++ modules/aggregator/aggregator.admin.inc @@ -338,6 +338,7 @@ function aggregator_form_opml_submit($form, &$form_state) { $form_state['values']['title'] = $feed['title']; $form_state['values']['url'] = $feed['url']; + drupal_execute('aggregator_form_feed', $form_state); } diff --git modules/book/book.admin.inc modules/book/book.admin.inc index aacdb4d..81e9457 100644 --- modules/book/book.admin.inc +++ modules/book/book.admin.inc @@ -106,7 +106,7 @@ function book_admin_edit_validate($form, &$form_state) { function book_admin_edit_submit($form, &$form_state) { // Save elements in the same order as defined in post rather than the form. // This ensures parents are updated before their children, preventing orphans. - $order = array_flip(array_keys($form['#post']['table'])); + $order = array_flip(array_keys($form_state['input']['table'])); $form['table'] = array_merge($order, $form['table']); foreach (element_children($form['table']) as $key) { diff --git modules/book/book.pages.inc modules/book/book.pages.inc index 2ab3854..1c0489a 100644 --- modules/book/book.pages.inc +++ modules/book/book.pages.inc @@ -246,7 +246,6 @@ function book_form_update() { form_set_cache($_POST['form_build_id'], $form, $cached_form_state); // Build and render the new select element, then return it in JSON format. $form_state = array(); - $form['#post'] = array(); $form = form_builder($form['form_id']['#value'] , $form, $form_state); $output = drupal_render($form['book']['plid']); drupal_json(array('status' => TRUE, 'data' => $output)); diff --git modules/comment/comment.admin.inc modules/comment/comment.admin.inc index 9c6083a..b7e68c1 100644 --- modules/comment/comment.admin.inc +++ modules/comment/comment.admin.inc @@ -191,7 +191,7 @@ function theme_comment_admin_overview($form) { * @see comment_multiple_delete_confirm_submit() */ function comment_multiple_delete_confirm(&$form_state) { - $edit = $form_state['post']; + $edit = $form_state['input']; $form['comments'] = array( '#prefix' => '