diff --git includes/form.inc includes/form.inc index bf9ab04..cd528a5 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,37 @@ 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; + // TODO: $form['#post'] should not be used in favor of $form_state['input'] + $form['#post'] = $form_state['input']; // Now that we know we have a form, we'll process it (validating, // submitting, and handling the results returned by its submission @@ -117,7 +178,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 +207,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 +229,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. @@ -174,10 +245,7 @@ function drupal_get_form($form_id) { * 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. + * drupal_get_form(), plus the original form_state in the beginning. * @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 +253,12 @@ function drupal_get_form($form_id) { * @return * The newly built form. */ -function drupal_rebuild_form($form_id, &$form_state, $args, $form_build_id = NULL) { +function drupal_rebuild_form($form_id, &$form_state, $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); + + $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 +267,18 @@ 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); + // 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['#post'] = $form_state['input']; + $form = form_builder($form_id, $form, $form_state); return $form; } @@ -290,12 +357,14 @@ 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 = drupal_retrieve_form($form, $form_state); $form['#post'] = $form_state['values']; drupal_prepare_form($form_id, $form, $form_state); drupal_process_form($form_id, $form, $form_state); @@ -325,15 +394,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 +424,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,6 +451,19 @@ 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['#post']['form_build_id'])) { + $form['#post']['form_build_id'] = $form['#build_id']; + } + if (!isset($form['#post']['form_id'])) { + $form['#post']['form_id'] = $form_id; + } + if (!isset($form['#post']['form_token']) && isset($form['#token'])) { + $form['#post']['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. @@ -445,7 +514,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']); + } } } } @@ -568,7 +642,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; } 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' => '