diff --git a/includes/form.inc b/includes/form.inc index 5058ffdf221d967cc1f3dd9698854d53f308adcd..366c6a11594a9d73fdcf808519a726066c973d97 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -188,9 +188,8 @@ function drupal_build_form($form_id, &$form_state) { 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']. - form_set_cache($form_build_id, $original_form, NULL); + // set #cache. + form_set_cache($form_build_id, $original_form, $form_state); } } @@ -201,14 +200,14 @@ function drupal_build_form($form_id, &$form_state) { // the form will simply be re-rendered with the values still in its // fields. // - // If $form_state['storage'] or $form_state['rebuild'] have been - // set by any submit or validate handlers, however, we know that - // we're in a complex multi-part process of some sort and the form's - // workflow is NOT complete. We need to construct a fresh copy of - // the form, passing in the latest $form_state in addition to any - // other variables passed into drupal_get_form(). - - if (!empty($form_state['rebuild']) || !empty($form_state['storage'])) { + // If $form_state['storage'] or $form_state['rebuild'] has been set + // and the form has been submitted, we know that we're in a complex + // multi-part process of some sort and the form's workflow is NOT + // complete. We need to construct a fresh copy of the form, passing + // in the latest $form_state in addition to any other variables passed + // into drupal_get_form(). + + if ((!empty($form_state['storage']) || !empty($form_state['rebuild'])) && !empty($form_state['submitted']) && !form_get_errors()) { $form = drupal_rebuild_form($form_id, $form_state); } diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index 88b2614b9ca302e582bcc666b8cce96bab59fcb7..d787a850f9d7f9117557e22b700170910683f952 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -383,3 +383,69 @@ class FormAPITestCase extends DrupalWebTestCase { } } + +/** + * Test the form storage on a multistep form. + * + * The tested form puts data into the storage during the initial form + * construction. These tests verify that there are no duplicate form + * constructions, with and without manual form caching activiated. Furthermore + * when a validation error occurs, it makes sure that changed form element + * values aren't lost due to a wrong form rebuild. + */ +class FormsFormStorageTestCase extends DrupalWebTestCase { + + function getInfo() { + return array( + 'name' => t('Multistep form using form storage'), + 'description' => t('Tests a multistep form using form storage and makes sure validation and caching works right.'), + 'group' => t('Form API'), + ); + } + + function setUp() { + parent::setUp('form_test'); + } + + /** + * Tests using the form in a usual way. + */ + function testForm() { + + $user = $this->drupalCreateUser(array('access content')); + $this->drupalLogin($user); + + $this->drupalPost('form_test/form-storage', array('title' => 'new', 'value' => 'value_is_set'), 'Continue'); + $this->assertText('Form constructions: 2', t('The form has been constructed two times till now.')); + + $this->drupalPost(NULL, array(), 'Save'); + $this->assertText('Form constructions: 3', t('The form has been constructed three times till now.')); + $this->assertText('Title: new', t('The form storage has stored the values.')); + } + + /** + * Tests using the form with an activated #cache property. + */ + function testFormCached() { + $user = $this->drupalCreateUser(array('access content')); + $this->drupalLogin($user); + + $this->drupalPost('form_test/form-storage', array('title' => 'new', 'value' => 'value_is_set'), 'Continue', array('query' => 'cache=1')); + $this->assertText('Form constructions: 1', t('The form has been constructed one time till now.')); + + $this->drupalPost(NULL, array(), 'Save', array('query' => 'cache=1')); + $this->assertText('Form constructions: 2', t('The form has been constructed two times till now.')); + $this->assertText('Title: new', t('The form storage has stored the values.')); + } + + /** + * Tests validation when form storage is used. + */ + function testValidation() { + $user = $this->drupalCreateUser(array('access content')); + $this->drupalLogin($user); + + $this->drupalPost('form_test/form-storage', array('title' => '', 'value' => 'value_is_set'), 'Continue'); + $this->assertPattern('/value_is_set/', t("The input values have been kept.")); + } +} diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index dd91f6ebfda01b1f67bb0c6e639ebe22573c3437..afa08ba8965fc924cd4273a0cac0ddc7a406a229 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -58,6 +58,14 @@ function form_test_menu() { 'type' => MENU_CALLBACK, ); + $items['form_test/form-storage'] = array( + 'title' => 'Form storage test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_storage_test_form'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; } @@ -279,3 +287,81 @@ function form_test_mock_form($form_state) { function form_test_mock_form_submit($form, &$form_state) { variable_set('form_test_mock_submit', $form_state['values']['test_value']); } + +/** + * A multistep form for testing the form storage. + * + * It uses two steps for editing a virtual "thing". Any changes to it are saved + * in the form storage and have to be present during any step. By setting the + * request parameter "cache" the form can be tested with caching enabled, as + * it would be the case, if the form would contain some #ahah callbacks. + * + * @see form_storage_test_form_submit(). + */ +function form_storage_test_form(&$form_state) { + // Initialize + if (!isset($form_state['storage'])) { + if (empty($form_state['input'])) { + $_SESSION['constructions'] = 0; + } + // Put the initial thing into the storage + $form_state['storage'] = array( + 'thing' => array( + 'title' => 'none', + 'value' => '', + ), + ); + $form_state['storage'] += array('step' => 1); + } + + // Count how often the form is constructed + $_SESSION['constructions']++; + + if ($form_state['storage']['step'] == 1) { + $form['title'] = array( + '#type' => 'textfield', + '#title' => 'title', + '#default_value' => $form_state['storage']['thing']['title'], + '#required' => TRUE, + ); + $form['value'] = array( + '#type' => 'textfield', + '#title' => 'value', + '#default_value' => $form_state['storage']['thing']['value'], + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Continue', + ); + } + else { + $form['content'] = array('#value' => 'This is the second step.'); + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Save', + ); + } + + if (isset($_REQUEST['cache'])) { + // Manually activate caching, so we can test that the storage keeps working + // when it's enabled. + $form['#cache'] = TRUE; + } + + return $form; +} + +/** + * Multistep form submit callback. + */ +function form_storage_test_form_submit($form, &$form_state) { + if ($form_state['storage']['step'] == 1) { + $form_state['storage']['thing']['title'] = $form_state['values']['title']; + $form_state['storage']['thing']['value'] = $form_state['values']['value']; + } + else { + drupal_set_message("Title: ". check_plain($form_state['storage']['thing']['title'])); + } + $form_state['storage']['step']++; + drupal_set_message("Form constructions: ". $_SESSION['constructions']); +}