Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.492 diff -u -p -r1.492 form.inc --- includes/form.inc 14 Sep 2010 21:42:05 -0000 1.492 +++ includes/form.inc 16 Sep 2010 02:04:57 -0000 @@ -985,36 +985,45 @@ function drupal_validate_form($form_id, } // If the session token was set by drupal_prepare_form(), ensure that it - // matches the current user's session. - if (isset($form['#token'])) { - if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { - // Setting this error will cause the form to fail validation. - form_set_error('form_token', t('This form is outdated. Reload the page and try again. Contact the site administrator if the problem persists.')); - } + // matches the current user's session. Otherwise, we can skip the entire + // form validation, as form validation already failed. + if (isset($form['#token']) && !drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { + // Setting this error will cause the form to fail validation. + form_set_error('form_token', t('This form was outdated and has been reloaded automatically. Try to submit it again. Contact the site administrator if the problem persists.')); + $reset_token = TRUE; } + else { + _form_validate($form, $form_state, $form_id); + $validated_forms[$form_id] = TRUE; - _form_validate($form, $form_state, $form_id); - $validated_forms[$form_id] = TRUE; - - // If validation errors are limited then remove any non validated form values, - // so that only values that passed validation are left for submit callbacks. - if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) { - $values = array(); - foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { - // If the section exists within $form_state['values'], even if the value - // is NULL, copy it to $values. - $section_exists = NULL; - $value = drupal_array_get_nested_value($form_state['values'], $section, $section_exists); - if ($section_exists) { - drupal_array_set_nested_value($values, $section, $value); + // If validation errors are limited then remove any non validated form values, + // so that only values that passed validation are left for submit callbacks. + if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) { + $values = array(); + foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { + // If the section exists within $form_state['values'], even if the value + // is NULL, copy it to $values. + $section_exists = NULL; + $value = drupal_array_get_nested_value($form_state['values'], $section, $section_exists); + if ($section_exists) { + drupal_array_set_nested_value($values, $section, $value); + } } + // For convenience we always make the value of the pressed button available. + if (isset($form_state['triggering_element']['#button_type'])) { + $values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; + drupal_array_set_nested_value($values, $form_state['triggering_element']['#parents'], $form_state['triggering_element']['#value']); + } + $form_state['values'] = $values; } - // For convenience we always make the value of the pressed button available. - if (isset($form_state['triggering_element']['#button_type'])) { - $values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; - drupal_array_set_nested_value($values, $form_state['triggering_element']['#parents'], $form_state['triggering_element']['#value']); - } - $form_state['values'] = $values; + } + // If the session token did not match the current user's session, reset the + // token, so the user is able to submit the form by submitting it again. + if (isset($reset_token)) { + // Setting this value will cause the form to have an up-to-date token + // when it is re-rendered, therefore allowing the new copy of the form + // to be submitted successfully. + $form['form_token']['#value'] = drupal_get_token($form['#token']); } } Index: modules/simpletest/tests/form.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v retrieving revision 1.64 diff -u -p -r1.64 form.test --- modules/simpletest/tests/form.test 13 Sep 2010 06:41:23 -0000 1.64 +++ modules/simpletest/tests/form.test 16 Sep 2010 02:21:16 -0000 @@ -343,6 +343,8 @@ class FormAlterTestCase extends DrupalWe * Test form validation handlers. */ class FormValidationTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + public static function getInfo() { return array( 'name' => 'Form validation handlers', @@ -417,6 +419,34 @@ class FormValidationTestCase extends Dru $this->assertText(t('!name field is required.', array('!name' => 'Title'))); $this->assertText('Test element is invalid'); } + + /** + * Tests re-submitting a form that contains an expired session token. + */ + function testExpiredToken() { + $this->web_user = $this->drupalCreateUser(array()); + + $this->drupalLogin($this->web_user); + $this->drupalGet('form-test/validate'); + $copy = $this->drupalGetContent(); + $url = $this->getUrl(); + $this->drupalLogout(); + + $this->drupalLogin($this->web_user); + $this->drupalSetContent($copy, $url); + + $edit = array( + 'name' => 'Drupal', + ); + $this->drupalPost(NULL, $edit, 'Save'); + $this->assertText(t('This form was outdated and has been reloaded automatically. Try to submit it again. Contact the site administrator if the problem persists.')); + $this->assertNoText(t('@label value: @value', array('@label' => 'Name', '@value' => $edit['name']))); + + // Do what it suggests us to do. + $this->drupalPost(NULL, array(), 'Save'); + $this->assertNoText(t('This form was outdated and has been reloaded automatically. Try to submit it again. Contact the site administrator if the problem persists.')); + $this->assertText(t('@label value: @value', array('@label' => 'Name', '@value' => $edit['name']))); + } } /** Index: modules/simpletest/tests/form_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v retrieving revision 1.48 diff -u -p -r1.48 form_test.module --- modules/simpletest/tests/form_test.module 27 Aug 2010 11:54:32 -0000 1.48 +++ modules/simpletest/tests/form_test.module 16 Sep 2010 02:10:32 -0000 @@ -298,6 +298,17 @@ function form_test_validate_form_validat } /** + * Form submit handler for form_test_validate_form(). + */ +function form_test_validate_form_submit(&$form, &$form_state) { + // Output the element's value from $form_state. + drupal_set_message(t('@label value: @value', array( + '@label' => $form['name']['#title'], + '@value' => $form_state['values']['name'], + ))); +} + +/** * Builds a simple form with a button triggering partial validation. */ function form_test_limit_validation_errors_form($form, &$form_state) {