Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[yaffs-website] / web / core / modules / system / tests / src / Functional / Form / StorageTest.php
1 <?php
2
3 namespace Drupal\Tests\system\Functional\Form;
4
5 use Drupal\Core\Database\Database;
6 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
7 use Drupal\Tests\BrowserTestBase;
8
9 /**
10  * Tests a multistep form using form storage and makes sure validation and
11  * caching works right.
12  *
13  * The tested form puts data into the storage during the initial form
14  * construction. These tests verify that there are no duplicate form
15  * constructions, with and without manual form caching activated. Furthermore
16  * when a validation error occurs, it makes sure that changed form element
17  * values are not lost due to a wrong form rebuild.
18  *
19  * @group Form
20  */
21 class StorageTest extends BrowserTestBase {
22
23   /**
24    * Modules to enable.
25    *
26    * @var array
27    */
28   public static $modules = ['form_test', 'dblog'];
29
30   /**
31    * {@inheritdoc}
32    */
33   protected function setUp() {
34     parent::setUp();
35
36     $this->drupalLogin($this->drupalCreateUser());
37   }
38
39   /**
40    * Tests using the form in a usual way.
41    */
42   public function testForm() {
43     $this->drupalGet('form_test/form-storage');
44
45     $assert_session = $this->assertSession();
46     $assert_session->pageTextContains('Form constructions: 1');
47
48     $edit = ['title' => 'new', 'value' => 'value_is_set'];
49
50     // Use form rebuilding triggered by a submit button.
51     $this->drupalPostForm(NULL, $edit, 'Continue submit');
52     $assert_session->pageTextContains('Form constructions: 2');
53     $assert_session->pageTextContains('Form constructions: 3');
54
55     // Reset the form to the values of the storage, using a form rebuild
56     // triggered by button of type button.
57     $this->drupalPostForm(NULL, ['title' => 'changed'], 'Reset');
58     $assert_session->fieldValueEquals('title', 'new');
59     // After rebuilding, the form has been cached.
60     $assert_session->pageTextContains('Form constructions: 4');
61
62     $this->drupalPostForm(NULL, $edit, 'Save');
63     $assert_session->pageTextContains('Form constructions: 4');
64     $assert_session->pageTextContains('Title: new', 'The form storage has stored the values.');
65   }
66
67   /**
68    * Tests using the form after calling $form_state->setCached().
69    */
70   public function testFormCached() {
71     $this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1]]);
72     $this->assertSession()->pageTextContains('Form constructions: 1');
73
74     $edit = ['title' => 'new', 'value' => 'value_is_set'];
75
76     // Use form rebuilding triggered by a submit button.
77     $this->drupalPostForm(NULL, $edit, 'Continue submit');
78     // The first one is for the building of the form.
79     $this->assertSession()->pageTextContains('Form constructions: 2');
80     // The second one is for the rebuilding of the form.
81     $this->assertSession()->pageTextContains('Form constructions: 3');
82
83     // Reset the form to the values of the storage, using a form rebuild
84     // triggered by button of type button.
85     $this->drupalPostForm(NULL, ['title' => 'changed'], 'Reset');
86     $this->assertSession()->fieldValueEquals('title', 'new');
87     $this->assertSession()->pageTextContains('Form constructions: 4');
88
89     $this->drupalPostForm(NULL, $edit, 'Save');
90     $this->assertSession()->pageTextContains('Form constructions: 4');
91     $this->assertSession()->pageTextContains('Title: new', 'The form storage has stored the values.');
92   }
93
94   /**
95    * Tests validation when form storage is used.
96    */
97   public function testValidation() {
98     $this->drupalPostForm('form_test/form-storage', ['title' => '', 'value' => 'value_is_set'], 'Continue submit');
99     $this->assertPattern('/value_is_set/', 'The input values have been kept.');
100   }
101
102   /**
103    * Tests updating cached form storage during form validation.
104    *
105    * If form caching is enabled and a form stores data in the form storage, then
106    * the form storage also has to be updated in case of a validation error in
107    * the form. This test re-uses the existing form for multi-step tests, but
108    * triggers a special #element_validate handler to update the form storage
109    * during form validation, while another, required element in the form
110    * triggers a form validation error.
111    */
112   public function testCachedFormStorageValidation() {
113     // Request the form with 'cache' query parameter to enable form caching.
114     $this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1]]);
115
116     // Skip step 1 of the multi-step form, since the first step copies over
117     // 'title' into form storage, but we want to verify that changes in the form
118     // storage are updated in the cache during form validation.
119     $edit = ['title' => 'foo'];
120     $this->drupalPostForm(NULL, $edit, 'Continue submit');
121
122     // In step 2, trigger a validation error for the required 'title' field, and
123     // post the special 'change_title' value for the 'value' field, which
124     // conditionally invokes the #element_validate handler to update the form
125     // storage.
126     $edit = ['title' => '', 'value' => 'change_title'];
127     $this->drupalPostForm(NULL, $edit, 'Save');
128
129     // At this point, the form storage should contain updated values, but we do
130     // not see them, because the form has not been rebuilt yet due to the
131     // validation error. Post again and verify that the rebuilt form contains
132     // the values of the updated form storage.
133     $this->drupalPostForm(NULL, ['title' => 'foo', 'value' => 'bar'], 'Save');
134     $this->assertSession()->pageTextContains("The thing has been changed.", 'The altered form storage value was updated in cache and taken over.');
135   }
136
137   /**
138    * Verifies that form build-id is regenerated when loading an immutable form
139    * from the cache.
140    */
141   public function testImmutableForm() {
142     // Request the form with 'cache' query parameter to enable form caching.
143     $this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1, 'immutable' => 1]]);
144     $buildIdFields = $this->xpath('//input[@name="form_build_id"]');
145     $this->assertEquals(count($buildIdFields), 1, 'One form build id field on the page');
146     $buildId = $buildIdFields[0]->getValue();
147
148     // Trigger validation error by submitting an empty title.
149     $edit = ['title' => ''];
150     $this->drupalPostForm(NULL, $edit, 'Continue submit');
151
152     // Verify that the build-id did change.
153     $this->assertSession()->hiddenFieldValueNotEquals('form_build_id', $buildId);
154
155     // Retrieve the new build-id.
156     $buildIdFields = $this->xpath('//input[@name="form_build_id"]');
157     $this->assertEquals(count($buildIdFields), 1, 'One form build id field on the page');
158     $buildId = (string) $buildIdFields[0]->getValue();
159
160     // Trigger validation error by again submitting an empty title.
161     $edit = ['title' => ''];
162     $this->drupalPostForm(NULL, $edit, 'Continue submit');
163
164     // Verify that the build-id does not change the second time.
165     $this->assertSession()->hiddenFieldValueEquals('form_build_id', $buildId);
166   }
167
168   /**
169    * Verify that existing contrib code cannot overwrite immutable form state.
170    */
171   public function testImmutableFormLegacyProtection() {
172     $this->drupalGet('form_test/form-storage', ['query' => ['cache' => 1, 'immutable' => 1]]);
173     $build_id_fields = $this->xpath('//input[@name="form_build_id"]');
174     $this->assertEquals(count($build_id_fields), 1, 'One form build id field on the page');
175     $build_id = $build_id_fields[0]->getValue();
176
177     // Try to poison the form cache.
178     $response = $this->drupalGet('form-test/form-storage-legacy/' . $build_id, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With: XMLHttpRequest']);
179     $original = json_decode($response, TRUE);
180
181     $this->assertEquals($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
182     $this->assertNotEquals($original['form']['#build_id'], $build_id, 'New build_id was generated');
183
184     // Assert that a watchdog message was logged by
185     // \Drupal::formBuilder()->setCache().
186     $status = (bool) Database::getConnection()->queryRange('SELECT 1 FROM {watchdog} WHERE message = :message', 0, 1, [':message' => 'Form build-id mismatch detected while attempting to store a form in the cache.']);
187     $this->assertTrue($status, 'A watchdog message was logged by \Drupal::formBuilder()->setCache');
188
189     // Ensure that the form state was not poisoned by the preceding call.
190     $response = $this->drupalGet('form-test/form-storage-legacy/' . $build_id, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With: XMLHttpRequest']);
191     $original = json_decode($response, TRUE);
192     $this->assertEquals($original['form']['#build_id_old'], $build_id, 'Original build_id was recorded');
193     $this->assertNotEquals($original['form']['#build_id'], $build_id, 'New build_id was generated');
194     $this->assertTrue(empty($original['form']['#poisoned']), 'Original form structure was preserved');
195     $this->assertTrue(empty($original['form_state']['poisoned']), 'Original form state was preserved');
196   }
197
198 }