diff -u b/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php --- b/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -185,9 +185,19 @@ // Ensure the form ID is prepared. $form_id = $this->getFormId($form_id, $form_state); + // Inform the form about the request that's building it. + $request = $this->requestStack->getCurrentRequest(); + $form_state->setRequest($request); + + // Initialize the form's user input. The user input should include only the + // input meant to be treated as part of what is submitted to the form, so + // we base it on the form's method rather than the request's method. For + // example, when someone does a GET request for + // /node/add/article?destination=foo, which is a form that expects its + // submission method to be POST, the user input during the GET request + // should be initialized to empty rather than to ['destination' => 'foo']. $input = $form_state->getUserInput(); if (!isset($input)) { - $request = $this->requestStack->getCurrentRequest(); $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all(); $form_state->setUserInput($input); } @@ -314,9 +324,12 @@ public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) { $form = $this->retrieveForm($form_id, $form_state); - // Persisting $form_state is a side-effect disallowed during a "safe" HTTP - // method (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1). - if (!$form_state->isMethodSafe()) { + // Only GET and POST are valid form methods. If the form receives its input + // via POST, then $form_state must be persisted when it is rebuilt between + // submissions. If the form receives its input via GET, then persisting + // state is forbidden by $form_state->setCached(), and the form must use + // the URL itself to transfer its state across steps. + if ($form_state->isMethodType('POST')) { $form_state->setCached(); } diff -u b/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php --- b/core/lib/Drupal/Core/Form/FormState.php +++ b/core/lib/Drupal/Core/Form/FormState.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Url; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** @@ -105,6 +106,24 @@ protected $rebuild = FALSE; /** + * The request for which the form is being built of processed. + * + * This is used to determine information about the request, such as its + * method, which can be different from $method, because $method represents + * what method to use for submitting the form, whereas $request->getMethod() + * is the method of the current request, which is usually GET for the initial + * build of the form. + * + * This should not be used for determining the form's input, because that is + * provided in $input. + * + * This property is uncacheable. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** * Used when a form needs to return some kind of a * \Symfony\Component\HttpFoundation\Response object, e.g., a * \Symfony\Component\HttpFoundation\BinaryFileResponse when triggering a @@ -477,8 +496,8 @@ public function setCached($cache = TRUE) { // Persisting $form_state is a side-effect disallowed during a "safe" HTTP // method (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1). - if ($cache && $this->isMethodSafe()) { - throw new \LogicException(sprintf('Form state caching on %s requests is not allowed.', $this->method)); + if ($cache && $this->request->isMethodSafe()) { + throw new \LogicException(sprintf('Form state caching on %s requests is not allowed.', $this->request->getMethod())); } $this->cache = (bool) $cache; @@ -578,14 +597,6 @@ /** * {@inheritdoc} */ - public function isMethodSafe() { - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html. - return $this->isMethodType('GET') || $this->isMethodType('HEAD') || $this->isMethodType('TRACE') || $this->isMethodType('OPTIONS'); - } - - /** - * {@inheritdoc} - */ public function setValidationEnforced($must_validate = TRUE) { $this->must_validate = (bool) $must_validate; return $this; @@ -1006,6 +1017,14 @@ /** * {@inheritdoc} */ + public function setRequest(Request $request) { + $this->request = $request; + return $this; + } + + /** + * {@inheritdoc} + */ public function setResponse(Response $response) { $this->response = $response; return $this; diff -u b/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php --- b/core/lib/Drupal/Core/Form/FormStateInterface.php +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Form; use Drupal\Core\Url; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** @@ -96,6 +97,16 @@ public function setFormState(array $form_state_additions); /** + * Sets the request for which the form is being built or processed. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request for which the form is being built or processed. + * + * @return $this + */ + public function setRequest(Request $request); + + /** * Sets a response for this form. * * If a response is set, it will be used during processing and returned @@ -760,18 +771,6 @@ public function isMethodType($method_type); /** - * Returns whether the HTTP method is safe, so either GET or HREAD. - * - * Safe is meant in the way that it is not supposed to change any state. - * - * @return bool - * TRUE if the HTTP form method is safe. - * - * @see \Drupal\Core\Form\FormState::$method - */ - public function isMethodSafe(); - - /** * Enforces that validation is run. * * @param bool $must_validate