diff options
Diffstat (limited to 'includes')
-rw-r--r-- | includes/ajax.inc | 59 | ||||
-rw-r--r-- | includes/bootstrap.inc | 2 | ||||
-rw-r--r-- | includes/form.inc | 42 |
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); } |