diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 26a1c3d..78078ab 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -528,15 +528,11 @@ public function processForm($form_id, &$form, &$form_state) { // With GET, these forms are always submitted if requested. if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) { - if (!isset($form_state['input']['form_build_id'])) { - $form_state['input']['form_build_id'] = $form['#build_id']; - } if (!isset($form_state['input']['form_id'])) { $form_state['input']['form_id'] = $form_id; } - if (!isset($form_state['input']['form_token']) && isset($form['#token'])) { - $form_state['input']['form_token'] = $this->csrfToken->get($form['#token']); - } + $form_state['no_cache'] = TRUE; + unset($form['#token']); } // self::doBuildForm() finishes building the form by calling element @@ -664,6 +660,22 @@ public function prepareForm($form_id, &$form, &$form_state) { if ($form_state['method'] == 'get' && !isset($form['#method'])) { $form['#method'] = 'get'; } + elseif (!isset($form['#method'])) { + $form['#method'] = 'post'; + } + // Prevent an undefined index error. + if (!isset($form['#token'])) { + $form['#token'] = NULL; + } + // For certain forms like a search for or exposed filters, we want to make + // a clean GET request without hidden values as query parameters. + if ($form['#method'] == 'get' && ($form['#token'] === FALSE || !empty($form_state['always_process']))) { + $clean_get = TRUE; + $form['#token'] = FALSE; + } + else { + $clean_get = FALSE; + } // Generate a new #build_id for this form, if none has been set already. // The form_build_id is used as key to cache a particular build of the form. @@ -674,16 +686,19 @@ public function prepareForm($form_id, &$form, &$form_state) { if (!isset($form['#build_id'])) { $form['#build_id'] = 'form-' . Crypt::randomBytesBase64(); } - $form['form_build_id'] = array( - '#type' => 'hidden', - '#value' => $form['#build_id'], - '#id' => $form['#build_id'], - '#name' => 'form_build_id', - // Form processing and validation requires this value, so ensure the - // submitted form value appears literally, regardless of custom #tree - // and #parents being set elsewhere. - '#parents' => array('form_build_id'), - ); + // If we are making a clean GET request, omit this hidden element. + if (!$clean_get) { + $form['form_build_id'] = array( + '#type' => 'hidden', + '#value' => $form['#build_id'], + '#id' => $form['#build_id'], + '#name' => 'form_build_id', + // Form processing and validation requires this value, so ensure the + // submitted form value appears literally, regardless of custom #tree + // and #parents being set elsewhere. + '#parents' => array('form_build_id'), + ); + } // Add a token, based on either #token or form_id, to any form displayed to // authenticated users. This ensures that any submitted form was actually @@ -696,7 +711,7 @@ public function prepareForm($form_id, &$form, &$form_state) { if ($user && $user->isAuthenticated() && !$form_state['programmed']) { // Form constructors may explicitly set #token to FALSE when cross site // request forgery is irrelevant to the form, such as search forms. - if (isset($form['#token']) && $form['#token'] === FALSE) { + if ($form['#token'] === FALSE) { unset($form['#token']); } // Otherwise, generate a public token based on the form id. @@ -714,7 +729,8 @@ public function prepareForm($form_id, &$form, &$form_state) { } } - if (isset($form_id)) { + // If we are making a clean GET request, omit this hidden element. + if (isset($form_id) && !$clean_get) { $form['form_id'] = array( '#type' => 'hidden', '#value' => $form_id, diff --git a/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php b/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php index ae3da03..fcb6cab 100644 --- a/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php +++ b/core/modules/views/lib/Drupal/views/Form/ViewsExposedForm.php @@ -116,6 +116,10 @@ public function buildForm(array $form, array &$form_state) { $form['#action'] = url($view->display_handler->getUrl()); $form['#theme'] = $view->buildThemeFunctions('views_exposed_form'); $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . String::checkPlain($view->storage->id()) . '-' . String::checkPlain($display['id'])); + + // Since the exposed form is a GET form, we don't want it to send a wide + // variety of information. + $form['#token'] = FALSE; // $form['#attributes']['class'] = array('views-exposed-form'); /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */ diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 944c0c9..3abd329 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -879,18 +879,6 @@ function views_pre_render_views_form_views_form($element) { } /** - * Implement hook_form_alter for the exposed form. - * - * Since the exposed form is a GET form, we don't want it to send a wide - * variety of information. - */ -function views_form_views_exposed_form_alter(&$form, &$form_state) { - $form['form_build_id']['#access'] = FALSE; - $form['form_token']['#access'] = FALSE; - $form['form_id']['#access'] = FALSE; -} - -/** * Implements hook_query_TAG_alter(). * * This is the hook_query_alter() for queries tagged by Views and is used to diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index 487f4c8..843c109 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -125,6 +125,165 @@ public function testGetFormIdWithBaseForm() { } /** + * Tests the prepareForm() method with a post request. + * + * You can explicitly remove form token checking by using #token = FALSE. + */ + public function testPrepareFormPost() { + // The user is anonymous, so the form_token is not emitted. + $account = clone $this->account; + $account->expects($this->any()) + ->method('isAuthenticated') + ->will($this->returnValue(FALSE)); + $this->formBuilder->setCurrentUser($account); + $form = array(); + $form_state = $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + $this->formBuilder->prepareForm('my_module_form_id', $form, $form_state); + + $this->assertEquals('post', $form['#method']); + $this->assertTrue(isset($form['form_build_id'])); + $this->assertTrue(isset($form['form_id'])); + $this->assertFalse(isset($form['form_token'])); + + // Now test with a mock authenticated user account. The form_token should + // now be present and all three hidden elements render by default, so + // the #access key is not set. + $account = clone $this->account; + $account->expects($this->any()) + ->method('isAuthenticated') + ->will($this->returnValue(TRUE)); + $this->formBuilder->setCurrentUser($account); + + $form = array(); + $form_state = $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + $this->formBuilder->prepareForm('my_module_form_id', $form, $form_state); + + $this->assertEquals('post', $form['#method']); + $this->assertTrue(isset($form['form_build_id'])); + $this->assertFalse(isset($form['form_build_id']['#access'])); + $this->assertTrue(isset($form['form_id'])); + $this->assertFalse(isset($form['form_id']['#access'])); + $this->assertTrue(isset($form['form_token'])); + $this->assertFalse(isset($form['form_token']['#access'])); + + // Make sure the form ID and build ID are still set even with no token, + // still with an authenticated user. + $form = array(); + $form_state = $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + // Opt out of the token checking. + $form['#token'] = FALSE; + $this->formBuilder->prepareForm('my_module_form_id', $form, $form_state); + + $this->assertEquals('post', $form['#method']); + $this->assertTrue(isset($form['form_build_id'])); + $this->assertTrue(isset($form['form_id'])); + $this->assertFalse(isset($form['form_token'])); + } + + /** + * Tests the prepareForm() method with a get request with token checking. + */ + public function testPrepareFormGetWithTokenChecking() { + // The user is anonymous, so the form_token is not emitted. + $account = clone $this->account; + $account->expects($this->any()) + ->method('isAuthenticated') + ->will($this->returnValue(FALSE)); + $form = array(); + $form_state = array( + 'method' => 'get', + ); + $form_state += $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + $this->formBuilder->prepareForm('my_module_form_id', $form, $form_state); + + $this->assertEquals('get', $form['#method']); + $this->assertTrue(isset($form['form_build_id'])); + $this->assertTrue(isset($form['form_id'])); + $this->assertFalse(isset($form['form_token'])); + + // Now test with a mock authenticated user account. The form_token should + // now be present and all three hidden elements render by default, so + // the #access key is not set. + $account = clone $this->account; + $account->expects($this->any()) + ->method('isAuthenticated') + ->will($this->returnValue(TRUE)); + $this->formBuilder->setCurrentUser($account); + + $form = array(); + $form_state = array( + 'method' => 'get', + ); + $form_state += $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + $this->formBuilder->prepareForm('my_module_form_id2', $form, $form_state); + + $this->assertEquals('get', $form['#method']); + $this->assertTrue(isset($form['form_build_id'])); + $this->assertFalse(isset($form['form_build_id']['#access'])); + $this->assertTrue(isset($form['form_id'])); + $this->assertFalse(isset($form['form_id']['#access'])); + $this->assertTrue(isset($form['form_token'])); + $this->assertFalse(isset($form['form_token']['#access'])); + } + + /** + * Tests the prepareForm() method with a get request without token checking. + * + * You can explicitly remove form token checking by using #token = FALSE or + * by setting $form_state['always_process'] = TRUE when using get. + */ + public function testPrepareFormGetWithoutTokenChecking() { + $form = array(); + $form_state = array( + 'method' => 'get', + ); + $form_state += $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + // Opt out of the token checking. + $form['#token'] = FALSE; + + $this->formBuilder->prepareForm('my_module_form_id', $form, $form_state); + + $this->assertEquals('get', $form['#method']); + $this->assertFalse(isset($form['form_build_id'])); + $this->assertFalse(isset($form['form_id'])); + $this->assertFalse(isset($form['form_token'])); + + // Second variant. + $form_state = $this->formBuilder->getFormStateDefaults(); + $form = (new TestForm())->buildForm($form, $form_state); + // Opt out of the token checking. + $form['#token'] = FALSE; + $form['#method'] = 'get'; + + $this->formBuilder->prepareForm('my_module_form_id2', $form, $form_state); + + $this->assertEquals('get', $form['#method']); + $this->assertFalse(isset($form['form_build_id'])); + $this->assertFalse(isset($form['form_id'])); + $this->assertFalse(isset($form['form_token'])); + + // Third variant. + $form_state = array( + 'method' => 'get', + 'always_process' => TRUE, + ); + $form = (new TestForm())->buildForm($form, $form_state); + + $this->formBuilder->prepareForm('my_module_form_id3', $form, $form_state); + + $this->assertEquals('get', $form['#method']); + $this->assertFalse(isset($form['form_build_id'])); + $this->assertFalse(isset($form['form_id'])); + $this->assertFalse(isset($form['form_token'])); + } + + /** * Tests the redirectForm() method when a redirect is expected. * * @param array $form_state