summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/ajax.inc59
-rw-r--r--includes/bootstrap.inc2
-rw-r--r--includes/form.inc42
3 files changed, 93 insertions, 10 deletions
diff --git a/includes/ajax.inc b/includes/ajax.inc
index ab0111ced..8446bf891 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -308,10 +308,11 @@ function ajax_render($commands = array()) {
* pulls the form info from $_POST.
*
* @return
- * An array containing the $form and $form_state. Use the list() function
- * to break these apart:
+ * An array containing the $form, $form_state, $form_id, $form_build_id and an
+ * initial list of Ajax $commands. Use the list() function to break these
+ * apart:
* @code
- * list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
+ * list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
* @endcode
*/
function ajax_get_form() {
@@ -331,6 +332,17 @@ function ajax_get_form() {
drupal_exit();
}
+ // When a page level cache is enabled, the form-build id might have been
+ // replaced from within form_get_cache. If this is the case, it is also
+ // necessary to update it in the browser by issuing an appropriate Ajax
+ // command.
+ $commands = array();
+ if (isset($form['#build_id_old']) && $form['#build_id_old'] != $form['#build_id']) {
+ // If the form build ID has changed, issue an Ajax command to update it.
+ $commands[] = ajax_command_update_build_id($form);
+ $form_build_id = $form['#build_id'];
+ }
+
// Since some of the submit handlers are run, redirects need to be disabled.
$form_state['no_redirect'] = TRUE;
@@ -345,7 +357,7 @@ function ajax_get_form() {
$form_state['input'] = $_POST;
$form_id = $form['#form_id'];
- return array($form, $form_state, $form_id, $form_build_id);
+ return array($form, $form_state, $form_id, $form_build_id, $commands);
}
/**
@@ -366,7 +378,7 @@ function ajax_get_form() {
* @see system_menu()
*/
function ajax_form_callback() {
- list($form, $form_state) = ajax_get_form();
+ list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// We need to return the part of the form (or some other content) that needs
@@ -379,7 +391,19 @@ function ajax_form_callback() {
$callback = $form_state['triggering_element']['#ajax']['callback'];
}
if (!empty($callback) && function_exists($callback)) {
- return $callback($form, $form_state);
+ $result = $callback($form, $form_state);
+
+ if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) {
+ // Turn the response into a #type=ajax array if it isn't one already.
+ $result = array(
+ '#type' => 'ajax',
+ '#commands' => ajax_prepare_response($result),
+ );
+ }
+
+ $result['#commands'] = array_merge($commands, $result['#commands']);
+
+ return $result;
}
}
@@ -1210,3 +1234,26 @@ function ajax_command_restripe($selector) {
'selector' => $selector,
);
}
+
+/**
+ * Creates a Drupal Ajax 'update_build_id' command.
+ *
+ * This command updates the value of a hidden form_build_id input element on a
+ * form. It requires the form passed in to have keys for both the old build ID
+ * in #build_id_old and the new build ID in #build_id.
+ *
+ * The primary use case for this Ajax command is to serve a new build ID to a
+ * form served from the cache to an anonymous user, preventing one anonymous
+ * user from accessing the form state of another anonymous users on Ajax enabled
+ * forms.
+ *
+ * @param $form
+ * The form array representing the form whose build ID should be updated.
+ */
+function ajax_command_update_build_id($form) {
+ return array(
+ 'command' => 'updateBuildId',
+ 'old' => $form['#build_id_old'],
+ 'new' => $form['#build_id'],
+ );
+}
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index e5de2d180..4cc39142e 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.26');
+define('VERSION', '7.27');
/**
* Core API compatibility.
diff --git a/includes/form.inc b/includes/form.inc
index 4e467bab3..fd80e09bb 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -168,6 +168,12 @@ function drupal_get_form($form_id) {
* processed.
* - base_form_id: Identification for a base form, as declared in a
* hook_forms() implementation.
+ * - immutable: If this flag is set to TRUE, a new form build id is
+ * generated when the form is loaded from the cache. If it is subsequently
+ * saved to the cache again, it will have another cache id and therefore
+ * the original form and form-state will remain unaltered. This is
+ * important when page caching is enabled in order to prevent form state
+ * from leaking between anonymous users.
* - rebuild_info: Internal. Similar to 'build_info', but pertaining to
* drupal_rebuild_form().
* - rebuild: Normally, after the entire form processing is completed and
@@ -459,16 +465,24 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
$form = drupal_retrieve_form($form_id, $form_state);
// If only parts of the form will be returned to the browser (e.g., Ajax or
- // RIA clients), re-use the old #build_id to not require client-side code to
- // manually update the hidden 'build_id' input element.
+ // RIA clients), or if the form already had a new build ID regenerated when it
+ // was retrieved from the form cache, reuse the existing #build_id.
// Otherwise, a new #build_id is generated, to not clobber the previous
// build's data in the form cache; also allowing the user to go back to an
// earlier build, make changes, and re-submit.
// @see drupal_prepare_form()
- if (isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id'])) {
+ $enforce_old_build_id = isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id']);
+ $old_form_is_mutable_copy = isset($old_form['#build_id_old']);
+ if ($enforce_old_build_id || $old_form_is_mutable_copy) {
$form['#build_id'] = $old_form['#build_id'];
+ if ($old_form_is_mutable_copy) {
+ $form['#build_id_old'] = $old_form['#build_id_old'];
+ }
}
else {
+ if (isset($old_form['#build_id'])) {
+ $form['#build_id_old'] = $old_form['#build_id'];
+ }
$form['#build_id'] = 'form-' . drupal_random_key();
}
@@ -523,6 +537,15 @@ function form_get_cache($form_build_id, &$form_state) {
}
}
}
+ // Generate a new #build_id if the cached form was rendered on a cacheable
+ // page.
+ if (!empty($form_state['build_info']['immutable'])) {
+ $form['#build_id_old'] = $form['#build_id'];
+ $form['#build_id'] = 'form-' . drupal_random_key();
+ $form['form_build_id']['#value'] = $form['#build_id'];
+ $form['form_build_id']['#id'] = $form['#build_id'];
+ unset($form_state['build_info']['immutable']);
+ }
return $form;
}
}
@@ -535,15 +558,28 @@ function form_set_cache($form_build_id, $form, $form_state) {
// 6 hours cache life time for forms should be plenty.
$expire = 21600;
+ // Ensure that the form build_id embedded in the form structure is the same as
+ // the one passed in as a parameter. This is an additional safety measure to
+ // prevent legacy code operating directly with form_get_cache and
+ // form_set_cache from accidentally overwriting immutable form state.
+ if ($form['#build_id'] != $form_build_id) {
+ watchdog('form', 'Form build-id mismatch detected while attempting to store a form in the cache.', array(), WATCHDOG_ERROR);
+ return;
+ }
+
// Cache form structure.
if (isset($form)) {
if ($GLOBALS['user']->uid) {
$form['#cache_token'] = drupal_get_token();
}
+ unset($form['#build_id_old']);
cache_set('form_' . $form_build_id, $form, 'cache_form', REQUEST_TIME + $expire);
}
// Cache form state.
+ if (variable_get('cache', 0) && drupal_page_is_cacheable()) {
+ $form_state['build_info']['immutable'] = TRUE;
+ }
if ($data = array_diff_key($form_state, array_flip(form_state_keys_no_cache()))) {
cache_set('form_state_' . $form_build_id, $data, 'cache_form', REQUEST_TIME + $expire);
}