From ee8aa910b9db2f90bfb46ba852eaa349343d5e04 Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Mon, 21 Sep 2009 06:44:14 +0000
Subject: [PATCH] Patch #579366 by sun, litwol | chx, Dries: simplified form
 API redirection handling. I can actually understand it now. ;-).

---
 includes/ajax.inc                   |   2 +-
 includes/batch.inc                  |  27 +++-----
 includes/common.inc                 |  17 +++--
 includes/form.inc                   | 102 ++++++++++++++++++----------
 modules/field_ui/field_ui.admin.inc |   2 +-
 modules/node/node.pages.inc         |   4 +-
 modules/openid/openid.module        |   4 +-
 modules/openid/openid.pages.inc     |   2 +-
 modules/search/search.module        |   4 +-
 modules/system/system.admin.inc     |   4 +-
 modules/system/system.test          |   8 +--
 modules/taxonomy/taxonomy.admin.inc |   7 +-
 modules/user/user.module            |   2 +-
 modules/user/user.pages.inc         |  12 ++--
 14 files changed, 103 insertions(+), 94 deletions(-)

diff --git a/includes/ajax.inc b/includes/ajax.inc
index 478adf572fdf..6bfd86d77554 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -184,7 +184,7 @@ function ajax_get_form() {
   }
 
   // Since some of the submit handlers are run, redirects need to be disabled.
-  $form['#redirect'] = FALSE;
+  $form_state['no_redirect'] = TRUE;
 
   // The form needs to be processed; prepare for that by setting a few internal
   // variables.
diff --git a/includes/batch.inc b/includes/batch.inc
index b136ac99906c..5fe876f9d518 100644
--- a/includes/batch.inc
+++ b/includes/batch.inc
@@ -426,29 +426,24 @@ function _batch_finished() {
   if ($_batch['progressive']) {
     // Revert the 'destination' that was saved in batch_process().
     if (isset($_batch['destination'])) {
-      $_REQUEST['destination'] = $_batch['destination'];
+      $_GET['destination'] = $_batch['destination'];
     }
 
     // Determine the target path to redirect to.
-    if (isset($_batch['form_state']['redirect'])) {
-      $redirect = $_batch['form_state']['redirect'];
-    }
-    elseif (isset($_batch['redirect'])) {
-      $redirect = $_batch['redirect'];
-    }
-    else {
-      $redirect = $_batch['source_page'];
+    if (!isset($_batch['form_state']['redirect'])) {
+      if (isset($_batch['redirect'])) {
+        $_batch['form_state']['redirect'] = $_batch['redirect'];
+      }
+      else {
+        $_batch['form_state']['redirect'] = $_batch['source_page'];
+      }
     }
 
     // Use drupal_redirect_form() to handle the redirection logic.
-    $form = isset($batch['form']) ? $batch['form'] : array();
-    if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) {
-      drupal_redirect_form($form, $redirect);
-    }
+    drupal_redirect_form($_batch['form_state']);
 
-    // We get here if $form['#redirect'] was FALSE, or if the form is a
-    // multi-step form. We save the final $form_state value to be retrieved
-    // by drupal_get_form(), and redirect to the originating page.
+    // If no redirection happened, save the final $form_state value to be
+    // retrieved by drupal_get_form() and redirect to the originating page.
     $_SESSION['batch_form_state'] = $_batch['form_state'];
     drupal_goto($_batch['source_page']);
   }
diff --git a/includes/common.inc b/includes/common.inc
index 2fee010a4ed5..479d0952eaae 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -364,8 +364,8 @@ function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
  * @see drupal_goto()
  */
 function drupal_get_destination() {
-  if (isset($_REQUEST['destination'])) {
-    return 'destination=' . urlencode($_REQUEST['destination']);
+  if (isset($_GET['destination'])) {
+    return 'destination=' . urlencode($_GET['destination']);
   }
   else {
     // Use $_GET here to retrieve the original path in source form.
@@ -418,9 +418,8 @@ function drupal_get_destination() {
  * @see drupal_get_destination()
  */
 function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
-
-  if (isset($_REQUEST['destination'])) {
-    extract(parse_url(urldecode($_REQUEST['destination'])));
+  if (isset($_GET['destination'])) {
+    extract(parse_url(urldecode($_GET['destination'])));
   }
 
   $args = array(
@@ -471,8 +470,8 @@ function drupal_not_found() {
   watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
 
   // Keep old path for reference, and to allow forms to redirect to it.
-  if (!isset($_REQUEST['destination'])) {
-    $_REQUEST['destination'] = $_GET['q'];
+  if (!isset($_GET['destination'])) {
+    $_GET['destination'] = $_GET['q'];
   }
 
   $path = drupal_get_normal_path(variable_get('site_404', ''));
@@ -502,8 +501,8 @@ function drupal_access_denied() {
   watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
 
   // Keep old path for reference, and to allow forms to redirect to it.
-  if (!isset($_REQUEST['destination'])) {
-    $_REQUEST['destination'] = $_GET['q'];
+  if (!isset($_GET['destination'])) {
+    $_GET['destination'] = $_GET['q'];
   }
 
   $path = drupal_get_normal_path(variable_get('site_403', ''));
diff --git a/includes/form.inc b/includes/form.inc
index 30df84c54978..d92e2a7ee761 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -122,8 +122,13 @@ function drupal_get_form($form_id) {
  *     structure, which is passed on to the actual form builder function.
  *     Such forms cannot use drupal_get_form() and need to prepare $form_state
  *     on their own.
+ *   Further $form_state properties controlling the redirection behavior after
+ *   form submission may be found in drupal_redirect_form().
+ *
  * @return
  *   The rendered form or NULL, depending upon the $form_state flags that were set.
+ *
+ * @see drupal_redirect_form()
  */
 function drupal_build_form($form_id, &$form_state) {
   // Ensure some defaults; if already set they will not be overridden.
@@ -503,6 +508,8 @@ function drupal_retrieve_form($form_id, &$form_state) {
 }
 
 /**
+ * Processes a form submission.
+ *
  * This function is the heart of form API. The form gets built, validated and in
  * appropriate cases, submitted.
  *
@@ -579,20 +586,8 @@ function drupal_process_form($form_id, &$form, &$form_state) {
       // Set a flag to indicate the the form has been processed and executed.
       $form_state['executed'] = TRUE;
 
-      // The form is executed. By default, we're finished now and redirect to a
-      // new destination page. The path of the destination page can be set in
-      // $form['#redirect'] or $form_state['redirect']. If neither of the two is
-      // set, the user is redirect to the current page, which means a fresh,
-      // unpopulated copy of the form.
-      // Redirection is skipped, though, if
-      // - the form was called by drupal_form_submit(),
-      // - the form has to be rebuilt because either $form_state['rebuild'] was
-      //   set to TRUE or $form_state['storage'] was populated by a submit handler.
-      // - $form_state['no_redirect'] is set to TRUE,
-      // - $form_state['redirect'] or $form['#redirect'] is set to FALSE.
-      if (!$form_state['programmed'] && empty($form_state['rebuild']) && empty($form_state['storage']) && empty($form_state['no_redirect'])) {
-        drupal_redirect_form($form, $form_state['redirect']);
-      }
+      // Redirect the form based on values in $form_state.
+      drupal_redirect_form($form_state);
     }
   }
 }
@@ -732,33 +727,67 @@ function drupal_validate_form($form_id, $form, &$form_state) {
 }
 
 /**
- * Redirect the user to a URL after a form has been processed.
+ * Redirects the user to a URL after a form has been processed.
+ *
+ * After a form was executed, the data in $form_state controls whether the form
+ * is redirected. By default, we redirect to a new destination page. The path of
+ * the destination page can be set in $form_state['redirect']. If that is not
+ * set, the user is redirected to the current page to display a fresh,
+ * unpopulated copy of the form.
+ *
+ * There are several triggers that may prevent a redirection though:
+ * - If $form_state['redirect'] is FALSE, a form builder function or form
+ *   validation/submit handler does not want a user to be redirected, which
+ *   means that drupal_goto() is not invoked. For most forms, the redirection
+ *   logic will be the same regardless of whether $form_state['redirect'] is
+ *   undefined or FALSE. However, in case it was not defined and the current
+ *   request contains a 'destination' query string, drupal_goto() will redirect
+ *   to that given destination instead. Only setting $form_state['redirect'] to
+ *   FALSE will prevent any redirection.
+ * - If $form_state['no_redirect'] is TRUE, then the callback that originally
+ *   built the form explicitly disallows any redirection, regardless of the
+ *   redirection value in $form_state['redirect']. For example, ajax_get_form()
+ *   defines $form_state['no_redirect'] when building a form in an AJAX
+ *   callback to prevent any redirection. $form_state['no_redirect'] should NOT
+ *   be altered by form builder functions or form validation/submit handlers.
+ * - If $form_state['programmed'] is TRUE, the form submission was usually
+ *   invoked via drupal_form_submit(), so any redirection would break the script
+ *   that invoked drupal_form_submit().
+ * - If $form_state['rebuild'] is TRUE or $form_state['storage'] is populated,
+ *   the form is most probably a multi-step form and needs to be rebuilt without
+ *   redirection.
  *
- * @param $form
- *   An associative array containing the structure of the form.
- * @param $redirect
- *   An optional value containing the destination path to redirect
- *   to if none is specified by the form.
+ * @param $form_state
+ *   A keyed array containing the current state of the form.
+ *
+ * @see drupal_process_form()
+ * @see drupal_build_form()
  */
-function drupal_redirect_form($form, $redirect = NULL) {
-  $goto = NULL;
-  if (isset($redirect)) {
-    $goto = $redirect;
+function drupal_redirect_form($form_state) {
+  // Skip redirection for form submissions invoked via drupal_form_submit().
+  if (!empty($form_state['programmed'])) {
+    return;
   }
-  if ($goto !== FALSE && isset($form['#redirect'])) {
-    $goto = $form['#redirect'];
+  // Skip redirection for multi-step forms.
+  if (!empty($form_state['rebuild']) || !empty($form_state['storage'])) {
+    return;
+  }
+  // Skip redirection if it was explicitly disallowed.
+  if (!empty($form_state['no_redirect'])) {
+    return;
   }
-  if (!isset($goto) || ($goto !== FALSE)) {
-    if (isset($goto)) {
-      if (is_array($goto)) {
-        call_user_func_array('drupal_goto', $goto);
+  // Only invoke drupal_goto() if redirect value was not set to FALSE.
+  if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) {
+    if (isset($form_state['redirect'])) {
+      if (is_array($form_state['redirect'])) {
+        call_user_func_array('drupal_goto', $form_state['redirect']);
       }
       else {
         // This function can be called from the installer, which guarantees
         // that $redirect will always be a string, so catch that case here
         // and use the appropriate redirect function.
         $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto';
-        $function($goto);
+        $function($form_state['redirect']);
       }
     }
     drupal_goto($_GET['q']);
@@ -2922,12 +2951,11 @@ function batch_process($redirect = NULL, $url = NULL) {
     $batch += $process_info;
 
     if ($batch['progressive']) {
-      // Clear the way for the drupal_goto redirection to the batch processing
-      // page, by saving and unsetting the 'destination' if any, on both places
-      // drupal_goto looks for it.
-      if (isset($_REQUEST['destination'])) {
-        $batch['destination'] = $_REQUEST['destination'];
-        unset($_REQUEST['destination']);
+      // Clear the way for the drupal_goto() redirection to the batch processing
+      // page, by saving and unsetting the 'destination', if there is any.
+      if (isset($_GET['destination'])) {
+        $batch['destination'] = $_GET['destination'];
+        unset($_GET['destination']);
       }
 
       // Initiate db storage in order to get a batch id. We have to provide
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
index 6cdf699d449e..9335ecc8ae34 100644
--- a/modules/field_ui/field_ui.admin.inc
+++ b/modules/field_ui/field_ui.admin.inc
@@ -540,7 +540,7 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
 
   if ($destinations) {
     $destinations[] = urldecode(substr(drupal_get_destination(), 12));
-    unset($_REQUEST['destination']);
+    unset($_GET['destination']);
     $form_state['redirect'] = field_ui_get_destinations($destinations);
   }
 }
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index a0bbf92b0ba4..e794fd0b24aa 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -301,9 +301,9 @@ function node_form($form, &$form_state, $node) {
  */
 function node_form_delete_submit($form, &$form_state) {
   $destination = '';
-  if (isset($_REQUEST['destination'])) {
+  if (isset($_GET['destination'])) {
     $destination = drupal_get_destination();
-    unset($_REQUEST['destination']);
+    unset($_GET['destination']);
   }
   $node = $form['#node'];
   $form_state['redirect'] = array('node/' . $node->nid . '/delete', $destination);
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
index 0facb8ebde63..61edf053a774 100644
--- a/modules/openid/openid.module
+++ b/modules/openid/openid.module
@@ -445,7 +445,7 @@ function openid_authentication($response) {
       $_SESSION['openid']['values'] = $form_state['values'];
       // We'll want to redirect back to the same place.
       $destination = drupal_get_destination();
-      unset($_REQUEST['destination']);
+      unset($_GET['destination']);
       drupal_goto('user/register', $destination);
     }
     else {
@@ -463,7 +463,7 @@ function openid_authentication($response) {
       // Let other modules act on OpenID login
       module_invoke_all('openid_response', $response, $account);
     }
-    drupal_redirect_form($form, $form_state['redirect']);
+    drupal_redirect_form($form_state);
   }
   else {
     drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
diff --git a/modules/openid/openid.pages.inc b/modules/openid/openid.pages.inc
index b900c413c903..0091e33fa778 100644
--- a/modules/openid/openid.pages.inc
+++ b/modules/openid/openid.pages.inc
@@ -111,5 +111,5 @@ function openid_user_delete_form_submit($form, &$form_state) {
   if ($query) {
     drupal_set_message(t('OpenID deleted.'));
   }
-  $form_state['#redirect'] = 'user/' . $form_state['args'][0]->uid . '/openid';
+  $form_state['redirect'] = 'user/' . $form_state['args'][0]->uid . '/openid';
 }
diff --git a/modules/search/search.module b/modules/search/search.module
index 35b817d3f0bd..9d15edb5e448 100644
--- a/modules/search/search.module
+++ b/modules/search/search.module
@@ -917,8 +917,8 @@ function search_box_form_submit($form, &$form_state) {
   // functionality, so we override any static destination set in the request,
   // for example by drupal_access_denied() or drupal_not_found()
   // (see http://drupal.org/node/292565).
-  if (isset($_REQUEST['destination'])) {
-    unset($_REQUEST['destination']);
+  if (isset($_GET['destination'])) {
+    unset($_GET['destination']);
   }
 
   $form_id = $form['form_id']['#value'];
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 48806cdb41ba..2d8c81713aae 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1822,7 +1822,7 @@ function system_site_maintenance_mode() {
  * @ingroup forms
  * @see system_settings_form()
  */
-function system_clean_url_settings($form) {
+function system_clean_url_settings($form, &$form_state) {
   global $base_url;
 
   // When accessing this form using a non-clean URL, allow a re-check to make
@@ -1852,7 +1852,7 @@ function system_clean_url_settings($form) {
   else {
     drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
 
-    $form['#redirect'] = $base_url . '/admin/config/search/clean-urls';
+    $form_state['redirect'] = $base_url . '/admin/config/search/clean-urls';
     $form['clean_url_description'] = array(
       '#type' => 'markup',
       '#markup' => '<p>' . t('Use URLs like <code>example.com/user</code> instead of <code>example.com/?q=user</code>.') . ' ' . t('If you are directed to a <em>Page not found (404)</em> error after testing for clean URLs, see the <a href="@handbook">online handbook</a>.', array('@handbook' => 'http://drupal.org/node/15365')) . '</p>',
diff --git a/modules/system/system.test b/modules/system/system.test
index d6fd1a98e87b..6d1e4d14255a 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -512,20 +512,14 @@ class AdminMetaTagTestCase extends DrupalWebTestCase {
 class AccessDeniedTestCase extends DrupalWebTestCase {
   protected $admin_user;
 
-  /**
-   * Implement getInfo().
-   */
   public static function getInfo() {
     return array(
       'name' => '403 functionality',
-      'description' => "Tests page access denied functionality, including custom 403 pages.",
+      'description' => 'Tests page access denied functionality, including custom 403 pages.',
       'group' => 'System'
     );
   }
 
-  /**
-   * Implement setUp().
-   */
   function setUp() {
     parent::setUp();
 
diff --git a/modules/taxonomy/taxonomy.admin.inc b/modules/taxonomy/taxonomy.admin.inc
index ee32be4b0824..b7cd4aba60bd 100644
--- a/modules/taxonomy/taxonomy.admin.inc
+++ b/modules/taxonomy/taxonomy.admin.inc
@@ -469,10 +469,7 @@ function taxonomy_overview_terms($form, &$form_state, $vocabulary) {
       '#type' => 'submit',
       '#value' => t('Reset to alphabetical')
     );
-    $form['destination'] = array(
-      '#type' => 'hidden',
-      '#value' => $_GET['q'] . (isset($_GET['page']) ? '?page=' . $_GET['page'] : '')
-    );
+    $form_state['redirect'] = array($_GET['q'], (isset($_GET['page']) ? array('page' => $_GET['page']) : ''));
   }
 
   return $form;
@@ -780,7 +777,7 @@ function taxonomy_form_term($form, &$form_state, $vocabulary, $edit = array()) {
       '#value' => $edit['tid']);
   }
   else {
-    $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
+    $form_state['redirect'] = $_GET['q'];
   }
 
   return $form;
diff --git a/modules/user/user.module b/modules/user/user.module
index 9cb594c73dde..cf41f9d7e007 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -3078,7 +3078,7 @@ function user_register($form, &$form_state) {
     );
     // Redirect back to page which initiated the create request;
     // usually admin/people/create.
-    $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
+    $form_state['redirect'] = $_GET['q'];
   }
 
   // Create a dummy variable for pass-by-reference parameters.
diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc
index ac1115a80fac..80e00ae49f14 100644
--- a/modules/user/user.pages.inc
+++ b/modules/user/user.pages.inc
@@ -292,9 +292,9 @@ function user_profile_form_submit($form, &$form_state) {
  */
 function user_edit_cancel_submit($form, &$form_state) {
   $destination = '';
-  if (isset($_REQUEST['destination'])) {
+  if (isset($_GET['destination'])) {
     $destination = drupal_get_destination();
-    unset($_REQUEST['destination']);
+    unset($_GET['destination']);
   }
   // Note: We redirect from user/uid/edit to user/uid/cancel to make the tabs disappear.
   $form_state['redirect'] = array("user/" . $form_state['values']['_account']->uid . "/cancel", $destination);
@@ -391,9 +391,7 @@ function user_cancel_confirm_form_submit($form, &$form_state) {
   if (user_access('administer users') && empty($form_state['values']['user_cancel_confirm']) && $account->uid != $user->uid) {
     user_cancel($form_state['values'], $account->uid, $form_state['values']['user_cancel_method']);
 
-    if (!isset($_REQUEST['destination'])) {
-      $form_state['redirect'] = 'admin/people';
-    }
+    $form_state['redirect'] = 'admin/people';
   }
   else {
     // Store cancelling method and whether to notify the user in $account for
@@ -407,9 +405,7 @@ function user_cancel_confirm_form_submit($form, &$form_state) {
     drupal_set_message(t('A confirmation request to cancel your account has been sent to your e-mail address.'));
     watchdog('user', 'Sent account cancellation request to %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
 
-    if (!isset($_REQUEST['destination'])) {
-      $form_state['redirect'] = "user/$account->uid";
-    }
+    $form_state['redirect'] = "user/$account->uid";
   }
 }
 
-- 
GitLab