Problem
Since #3575227 moved DomainSourcePathProcessor from priority 90 to 310, it now runs before the language path processor (priority 100). This means $options['language'] is not yet populated when DomainSourcePathProcessor::processOutbound() executes.
When the entity is loaded by ID from route parameters (e.g. during route normalization via Url::fromRoute('<current>')), the processor gets the default translation. Without $options['language'], it cannot switch to the correct translation. If the default translation has a different domain_source than the current translation, this incorrectly triggers a cross-domain rewrite, causing a 301 redirect to the wrong domain.
Steps to reproduce
- Enable
domain,domain_source,domain_path,redirect, andlanguagemodules - Create two domains (Domain 1 and Domain 2)
- Create a node with:
- Default (EN) translation:
domain_source= Domain 1 - French translation:
domain_source= Domain 2
- Default (EN) translation:
- Visit the French translation URL on Domain 2
- The redirect module's route normalizer generates the canonical URL, which incorrectly resolves to Domain 1 (from the default translation)
- Result: 301 redirect to Domain 1 with the wrong alias → 404
Root cause
In DomainSourcePathProcessor::processOutbound(), the translation resolution at lines 182–193 only runs when $options['language'] is set. At priority 310, the language path processor (priority 100) has not yet populated this option. The entity stays as the default translation, and getSourceDomain() returns the wrong domain.
Fix
When $options['language'] is not available and the site is multilingual, fall back to LanguageManagerInterface::getCurrentLanguage(TYPE_CONTENT) to determine the current content language. This was the effective behavior before the priority change (when the language processor ran first at 100, then DomainSourcePathProcessor at 90).
A kernel test is included that reproduces the bug (fails without the fix, passes with it).
Issue fork domain-3575489
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
Comment #2
mably commentedComment #4
mably commentedComment #5
mably commentedComment #7
mably commentedComment #9
idebr commentedAn alternative implementation is to add a second Outbound processor to domain_source at priority: 90 that sets the
$options['domain']to the correct translated one based on the$options['language']provided by the language negotiation plugin. It is not as clean as providing all logic in a single processor, but it does allow the default language negotiation plugins to apply their logicComment #10
mably commentedI thought the domain had to be determined before calling the domain_path path processor...
And the domain comes potentially from a translation of the content as the domain source field is translatable. So we need the language before that.
Unless I miss something.
Comment #11
mably commentedActually we will have the same problem with
DomainPathAliasProcessor(fromdomain_path) that also expects the$options['language']to be defined...Looks like we went a bit too fast on that one.
It still works because
DomainPathAliasManagerdoes this ingetAliasByPathAndDomain:Comment #12
mably commentedAliasManagerdefaults toTYPE_URL:May be we should do the same in
DomainPathAliasManagerandDomainSourcePathProcessor.What do you think @idebr?
Comment #13
idebr commentedHmm, I would expect the target language to be determined before resolving the path alias.
Disclaimer: I don't manage any projects with both Domain and multi-lingual, so I have to do a deep dive into the current implementation in Core for some insight
Comment #14
mably commented@idebr an idea of how to fix that problem once and for all?
Here is another related issue: #2973694: Outbound path processor does not have access to language and then redirects
Should we increase the priority of the PathProcessorLanguage for outbound instead?
Comment #15
mably commentedComment #16
mably commentedActually the only language negotiation outbound processor that sets the
$options['language']seems to be theLanguageNegotiationUrlone.https://git.drupalcode.org/project/drupal/-/blob/11.3.3/core/modules/lan...
So using
TYPE_URLprobably gives the same result.