Problem/Motivation

The Domain Source module sets a redirect response on the the form_state object.

When a response object exists the form redirect URL is totally ignored.

Steps to reproduce

Install the Domain module and its submodule Domain Source.

Create a basic domain.

Create a new content and select the created domain as its source.

Once the domain source is selected, save the content using the Save & Edit button.

You get redirected to the content view using the source Domain URL instead of returning back to the edit form.

Proposed resolution

When setting the form state redirect URL, replace any existing response object from the form state by our own redirect response.

Here is the code from the FormSubmitter.php core file where we can see that Drupal is checking that the form state doesn't have a response already defined before creating a new redirect response using the redirect URL:

    // If no response has been set, process the form redirect.
    if (!$form_state->getResponse() && $redirect = $this->redirectForm($form_state)) {
      $form_state->setResponse($redirect);
    }

    // If there is a response was set, return it instead of continuing.
    if (($response = $form_state->getResponse()) && $response instanceof Response) {
      return $response;
    }

Issue fork save_edit-3541709

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Issue summary: View changes

mably’s picture

Issue summary: View changes
Status: Active » Needs review
mably’s picture

Issue summary: View changes
caesius’s picture

Status: Needs review » Needs work

I was hoping someone would eventually come along and say "yeah this fixes a big conflict with other contrib modules and definitely doesn't break anything else" but that never happened. The fact that the steps to reproduce require setting up a module I've never used before didn't help, so I never got around to this.

I used an AI assistant to evaluate the MR and here's what it came up with:

Root cause confirmed against Drupal core, not just the reported symptom. Core's FormSubmitter::doSubmitForm() checks getResponse() before ever calling redirectForm():

// If no response has been set, process the form redirect.
if (!$form_state->getResponse() && $redirect = $this->redirectForm($form_state)) {

save_edit_form_submit_redirect() only calls setRedirectUrl(), which sets a separate property (FormState::$redirect) that getResponse() never sees. Once Domain Source calls setResponse(), save_edit's redirect is discarded regardless of submit handler order. Switching to setResponse() directly, as the MR does, is the right general direction.

Two changes needed before this is safe to merge:

1. The MR drops the redirect guards that setRedirectUrl() got for free through FormState::getRedirect() — that method returns FALSE (suppressing the redirect) when the form is programmatically submitted, rebuilding, or has redirects explicitly disabled. setResponse() has no such guard; it's a bare setter. As written, the patch will force a redirect during an AJAX-triggered rebuild or a programmatic submitForm() call sharing this submit chain. It should check isProgrammed() / isRebuilding() / isRedirectDisabled() and bail out early, the same way core does before it would otherwise call setRedirectUrl().

2. TrustedRedirectResponse isn't needed — the target is always the entity's own edit-form route, never external. Core's own redirectForm() uses plain RedirectResponse for this same case; matching that keeps the patch consistent with core instead of implying it needs the "trusted external redirect" escape hatch.

One non-blocking observation: the fix works because save_edit's submit handler happens to run after Domain Source's when the button is cloned in save_edit_form_alter() (Domain Source's hook_form_alter() runs first under default module weights, so its handler is already present on the array save_edit copies, and save_edit's own handler is appended last). That ordering is incidental, not enforced — a third module altering the same button with a later effective position could reproduce this exact bug against save_edit itself. Not a reason to hold up this fix, but worth a code comment noting the assumption.

mably’s picture

Status: Needs work » Needs review

Thanks for the review, both points are addressed.

The handler keeps its setRedirectUrl() call and only promotes the redirect to a response when another submit handler has already set one. That promotion is gated on FormState::getRedirect(), which already returns FALSE for a programmed form, a rebuilding form and one whose redirect has been disabled, so the guards are core's own rather than a copy of them. TrustedRedirectResponse is replaced by a plain RedirectResponse.

The branch also adds a kernel test covering the redirect target, the replacement of an existing response, the destination parameter and those three no-redirect cases. It fails against 2.x and passes with the change.

One thing outside the scope of this issue: the project had no .gitlab-ci.yml, so nothing ran the test. I added one in its own commit, with a handful of small pre-existing lint fixes it turned up. Please drop that commit if you would rather handle CI separately.

AI-Generated: Yes (Claude Code was used to help write the change, the test and this comment. I reviewed all of them. The test was confirmed to fail against the unpatched code and to pass with the change.)

mably’s picture

[DELETED]

caesius’s picture

Status: Needs review » Needs work

Hi, I'd like to avoid performing any "linting" on 2.x given that Drupal 10 is EOL soon and I'd like to cut over to a 3.0.0 release not long into the new year. Feel free to add tests but please do not do any 2.x linting since it clutters commits with unrelated changes. Also, if possible, ensure that *identical* bug fixes and tests are provided for both 2.x and 3.x (or at least, functionally identical bugfix code on 3.x)

Linting and code cleanup will be handled separately at #3619151 for 3.x only.

mably’s picture

Feel free to remove anything you want from my MR.

  • caesius committed 836fbf11 on 2.x authored by mably
    Issue #3541709 by mably: Redirect skipped if a response has been set on...
caesius’s picture

Status: Needs work » Reviewed & tested by the community

  • caesius committed 88eddfd9 on 3.x
    Issue #3541709 by mably: Keep core's redirect guards and drop...
caesius’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mably’s picture

Thanks for the merge @caesius!

Status: Fixed » Closed (fixed)

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