Allow form redirects to ignore ?destination query parameter

Created on 7 March 2018, over 6 years ago
Updated 29 July 2023, 11 months ago

Problem/Motivation

In #2767857: Add destination to edit, delete, enable, disable links in entity list builders โ†’ we added destination query parameters to config entity lists and operations in Views. This unearthed the fact that some of our forms are not destination query compatible - we broke the image style user interface unexpectedly. See #2940165: [regression] Cannot add effects to image style via the UI โ†’ .

The problem is that the destination param is set to win regardless of what a user does in the form. This means that any Drupal form can be redirected to somewhere using it.

As discussed in #2950759: Revert 2767857 โ†’ , right now, it is necessary to alter the global request object to disable a destination query arg based redirect override. That's complicated to understand and we shouldn't be doing something like that.

Older related issues are:
#579366: How many ways are there to redirect after form submission? โ†’
#1627654: drupal_redirect_form() should state that it is overridden by a destination in the request โ†’
#2325463: Destination URL breaks preview โ†’

Proposed resolution

We need a way to set a higher priority redirect on a form. So the developer can say actually regardless of the destination parameter go here because obeying the redirect makes no sense. The fix however shouldn't be tied to the forms system so the patch at the moment adds a new IgnoreDestinationRedirectReponse and the RedirectResponseSubscriber checks and doesn't apply the ?destination override if it is.

However since 90% of the redirects we are interested are from Forms the patch also adds helpers to FormState to make redirects behave like this.
FormStateInterface::getIgnoreDestination()
FormStateInterface::ignoreDestination($status)

Remaining tasks

Add more tests. One is currently provided by core/modules/image/src/Tests/ImageAdminStylesTest.php which proves this fixes #2940165: [regression] Cannot add effects to image style via the UI โ†’ whilst bringing back the nice consistent UI behaviour of #2767857: Add destination to edit, delete, enable, disable links in entity list builders โ†’ for Image Styles.

User interface changes

API changes

Data model changes

๐Ÿ› Bug report
Status

Fixed

Version

11.0 ๐Ÿ”ฅ

Component
Request processingย  โ†’

Last updated about 10 hours ago

No maintainer
Created by

๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom alexpott ๐Ÿ‡ช๐Ÿ‡บ๐ŸŒ

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • ๐Ÿ‡น๐Ÿ‡ทTurkey emircanerkul Turkey

    #10 works well. But we can use this in EntityForm while waiting for a real solution accepted

    $this->requestStack->getCurrentRequest()->query->remove('destination');

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    ๐Ÿ“Œ Revisit the redirect to 'add block' form in the 'add block content' form Fixed appears to be blocked by this as well.

  • Status changed to Needs review about 1 year ago
  • last update about 1 year ago
    29,553 pass
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Tried starting a 11.x branch but no luck so patch it is

    I started from MR 2678

    Updated the RedirectTest to check the path not destination (since it was ignored)
    Updated typehinting for D10
    Updated trigger_error message, still needs a change record

    I undid the change for core/modules/image/src/ImageStyleListBuilder.php as I wasn't sure it was needed? If it is then the solution needs to be looked at because currently when adding an image style
    1. The url is messed up with the destination parameter now appearing.
    2. When saving an image style instead of going back to the individual style you get redirected to the whole styles page.

    Had issues with the interdiff so just made a diff.

  • last update about 1 year ago
    29,553 pass
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Or this approach where I add the changes back to ImageStyleListBuilder but in RedirectResponseSubscriber I don't add the destination paramter for item 55.1

    Which also solved 55.2

  • Status changed to Needs work 11 months ago
  • ๐Ÿ‡ซ๐Ÿ‡ฎFinland lauriii Finland
    1. +++ b/core/core.services.yml
      --- a/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
      +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
      
      +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
      @@ -59,12 +66,21 @@ public function checkRedirectUrl(ResponseEvent $event) {
      +        else {
      +          $target_url = $response->getTargetUrl();
      +          $query_string = parse_url($target_url, PHP_URL_QUERY) ?: '';
      +          parse_str($query_string, $query);
      +          $target_url = substr($target_url, 0, strpos($target_url, '?')) . '?' . http_build_query($query);
      +          $response->setTargetUrl($target_url);
      

      Why do we need this ๐Ÿค”

    2. +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
      @@ -25,6 +25,13 @@ class RedirectResponseSubscriber implements EventSubscriberInterface {
      +  protected $ignoreDestination = FALSE;
      

      We can use property type declaration ๐Ÿ˜‡

    3. +++ b/core/lib/Drupal/Core/Form/FormState.php
      @@ -136,6 +136,13 @@ class FormState implements FormStateInterface {
      +  protected $ignoreDestination = FALSE;
      

      We could use property type declaration here too ๐Ÿ˜‡

    4. +++ b/core/lib/Drupal/Core/Form/FormStateDecoratorBase.php
      --- a/core/lib/Drupal/Core/Form/FormStateInterface.php
      +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
      
      +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
      @@ -158,6 +158,26 @@ public function setRedirectUrl(Url $url);
      +  public function ignoreDestination(bool $status = TRUE);
      

      Wondering if this should be setIgnoreDestination given that this can be used for disabling this behavior too?

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    #57.1 = I'm trying to remember but lets see what happens without it.
    #57.2 = added type
    #57.3 = added type
    #57.4 = changed all function calls to setIgnoreDestination

  • last update 11 months ago
    29,813 pass, 2 fail
  • Status changed to Needs review 11 months ago
  • last update 11 months ago
    29,815 pass
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Added back that snippet from 57.1

    Seems to grab the URL minus the destination.

    Also started the CR https://www.drupal.org/node/3375113 โ†’

  • Status changed to Needs work 11 months ago
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10

    Updated issue credits

    1. +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
      @@ -59,12 +66,21 @@ public function checkRedirectUrl(ResponseEvent $event) {
      +          $target_url = $response->getTargetUrl();
      +          $query_string = parse_url($target_url, PHP_URL_QUERY) ?: '';
      +          parse_str($query_string, $query);
      +          $target_url = substr($target_url, 0, strpos($target_url, '?')) . '?' . http_build_query($query);
      +          $response->setTargetUrl($target_url);
      

      Can we get a comment explaining what this is doing? It seems to parse the query, and then un-parse it and re-set it on the same url?

    2. +++ b/core/lib/Drupal/Core/Form/FormSubmitter.php
      @@ -34,10 +42,17 @@ class FormSubmitter implements FormSubmitterInterface {
      +  public function __construct(RequestStack $request_stack, UrlGeneratorInterface $url_generator, RedirectResponseSubscriber $redirect_response_subscriber = NULL) {
      

      shouldn't this be ?RedirectResponseSubscriber

  • Assigned to smustgrave
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Will work on this tomorrow. If I donโ€™t get to in 2 days anyone can have at it.

    Assigning to myself as a submaintainer

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Lets try this.

  • last update 11 months ago
    29,815 pass, 2 fail
  • Status changed to Needs review 11 months ago
  • last update 11 months ago
    29,822 pass
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Address #60

  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    Updated our patch for this issue during a 10.1 upgrade and something has broken with a mix of $form_state->setRedirect() and setIgnoreDestination() - now the form doesn't redirect at all, instead the page refreshes with an empty query string.

  • Status changed to Needs work 11 months ago
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    Debugging through it, somehow $target_url is becoming literally a ? after this line:

    $target_url = substr($target_url, 0, strpos($target_url, '?')) . '?' . http_build_query($query);

    Where before the line, target_url is http://127.0.0.1:8080/admin/content/service-status-messages/560/scheduled-transitions

    and $query is empty.

    I guess we need test coverage for this and only do this if $target_url contains a ?

  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    Weird cause this test case should be testing exactly that

        // Test basic redirection, ignoring the 'destination' query parameter.
        $options['query']['destination'] = $this->randomMachineName();
        $edit = [
          'redirection' => TRUE,
          'destination' => $this->randomMachineName(),
          'ignore_destination' => TRUE,
        ];
        $this->drupalGet($path, $options);
        $this->submitForm($edit, 'Submit');
        $this->assertSession()->addressEquals($path);
    
  • Assigned to acbramley
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    Addressing this bug, looks like it was first introduced in #55

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    The snippet was added in the MR to retain the destination in the url

  • Status changed to Needs review 11 months ago
  • last update 11 months ago
    29,825 pass, 2 fail
  • last update 11 months ago
    29,827 pass
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    Fixed, also added coverage for when the redirect url itself contains a query param.

  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    @smustgrave no, the MR still had the correct check in the test https://git.drupalcode.org/project/drupal/-/merge_requests/2678/diffs#62...

  • Status changed to RTBC 11 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Was referring to $target_url = substr($target_url, 0, strpos($target_url, '?')) . '?' . http_build_query($query);

    But #69 is all green and still working.

    Would be nice to get this in as it's a blocker for a block_content redirect.

  • 7:48
    4:46
    Running
  • Status changed to Needs review 11 months ago
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10
    +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php
    @@ -59,12 +66,25 @@ public function checkRedirectUrl(ResponseEvent $event) {
    +          // The 'destination' parameter should remain in the query but be
    +          // unused by the target_url.
    +          $target_url = $response->getTargetUrl();
    +          if (str_contains($target_url, '?')) {
    +            $query_string = parse_url($target_url, PHP_URL_QUERY) ?: '';
    +            parse_str($query_string, $query);
    +            $target_url = substr($target_url, 0, strpos($target_url, '?')) . '?' . http_build_query($query);
    +          }
    +          $response->setTargetUrl($target_url);
    

    Sorry if I'm being daft here, but I don't understand what this code is doing.

    It seems to
    a) get the query
    b) if it contains a ? parse the query
    c) recreate the same query

    https://3v4l.org/XYAX5 - the body of queryManip is copy pasted from here

  • last update 11 months ago
    29,879 pass
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    Confirming #57, #60 and #74 - this code is redundant.

    What it looks to be trying to achieve is what's described in #11 and #44 which while it might be nice, is IMO out of scope of this issue. If people want this functionality (retaining the destination query param on the redirected URL) then please file a follow up. That way we can discuss specific use cases and have a more targeted discussion.

  • Status changed to RTBC 11 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Awesome!

  • creating a new IgnoreDestinationRedirectResponse class and modifying the RedirectResponseSubscriber accordingly.
    1. Create the IgnoreDestinationRedirectResponse Class:
    Create a new class that extends the Symfony\Component\HttpFoundation\RedirectResponse class. This class will allow you to ignore the ?destination parameter in certain cases.

    2. Implement the FormStateInterface Helpers:
    In Drupal, you can use FormStateInterface to handle form-related functionality. Add the following methods to the FormStateInterface:
    <?php
    use Drupal\Core\Form\FormStateInterface;

    interface FormStateInterface extends \ArrayAccess {

    // ...

    /**
    * Gets the status of whether to ignore the ?destination query parameter in redirects.
    *
    * @return bool
    * TRUE to ignore ?destination, FALSE otherwise.
    */
    public function getIgnoreDestination();

    /**
    * Sets the status of whether to ignore the ?destination query parameter in redirects.
    *
    * @param bool $status
    * TRUE to ignore ?destination, FALSE otherwise.
    *
    * @return $this
    */
    public function ignoreDestination($status);
    }

    3. Implement RedirectResponseSubscriber:
    Modify the RedirectResponseSubscriber to check if the redirect should ignore the ?destination parameter based on the IgnoreDestinationRedirectResponse and FormStateInterface settings.

    4. Use the New Helpers in Forms:
    Now you can use the new FormStateInterface methods in your forms to control whether the redirect should ignore the ?destination parameter or not. For example:
    <?php
    use Drupal\Core\Form\FormStateInterface;

    public function submitForm(array &$form, FormStateInterface $form_state) {
    // Your form submission logic here.

    // Use the ignoreDestination() method to set whether to ignore ?destination or not.
    $form_state->ignoreDestination(TRUE);

    // Redirect to a specific page without considering ?destination.
    $form_state->setRedirect('your.route.name');
    }

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States Webbeh Georgia, USA
  • Status changed to Needs work 11 months ago
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10
    1. +++ b/core/lib/Drupal/Core/Form/FormSubmitter.php
      @@ -27,6 +28,13 @@ class FormSubmitter implements FormSubmitterInterface {
      +  protected $redirectResponseSubscriber;
      

      We can typehint this

    2. +++ b/core/lib/Drupal/Core/Form/FormSubmitter.php
      @@ -34,10 +42,17 @@ class FormSubmitter implements FormSubmitterInterface {
      +      @trigger_error('Calling ' . __CLASS__ . '::__construct() without the $redirect_response_subscriber argument is deprecated in drupal:10.2.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/XXXX', E_USER_DEPRECATED);
      

      This needs an actual change record URL

  • Status changed to Needs review 11 months ago
  • last update 11 months ago
    29,884 pass
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia acbramley

    CR added https://www.drupal.org/node/3377297 โ†’

    Type and comment updated.

  • Status changed to RTBC 11 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Feedback addressed

  • Status changed to Fixed 11 months ago
  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10

    Committed 92abbb5 and pushed to 11.x. Thanks!

    Thanks all, we got there in the end.

    Publishing both change records.

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.69.0 2024