Problem/Motivation
Blocks #2364157: Replace most existing _url calls with Url objects
$base_url is not a valid option in UrlGenerator::generateFromRoute(). Drupal 7 has this option in url(), and domain-based language negotiation relies on it.
Proposed resolution
Update UrlGenerator::generateFromRoute to support this option.
Remaining tasks
Update code in Update UrlGenerator::generateFromRouteUpdate docblockUpdate coverage in Drupal\Tests\Core\Routing\UrlGeneratorTest()
Task | Novice task? | Contributor instructions | Complete? |
---|---|---|---|
(after reroll) Create a patch (and interdiff) to address #24 | Instructions | Yes | |
Reroll the patch if it no longer applies. | Instructions | Yes | |
Add automated tests (see #22) | Instructions | Yes |
User interface changes
None.
API changes
None.
Beta phase evaluation
Issue category | Bug because the language selection block is currently unusable in HEAD |
---|---|
Issue priority | Critical |
Disruption | No disruption, there is no API change. |
Original report by @mpdonadio
The links in the Language Switcher block are incorrect for domain based negotiation.
Steps to reproduce:
Spin up drupal-8.0.x from HEAD
Login
Enable Languages
Install Italian from admin/config/regional/language
Set to domain based negotiation, and configure URLs from admin/config/regional/language/detection/url
Place Language Switcher block in a region from admin/structure/block; enable both languages.
Go back to the main site.
Hover over English link in the Language Switcher, see it is correct.
Hover over Italian link, see it is the same as the English one. Snap!
I actually think this is a general problem with generating urls from route names when you specify the language, as I am working on another issue and am having problems with code like
$languages = $this->container->get('language_manager')->getLanguages();
$italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it'], 'absolute' => TRUE])->toString();
ignoring the language option, but I cannot confirm that, so I am starting with this report.
I will work on this if someone can point me in the right direction.
Comment | File | Size | Author |
---|---|---|---|
#37 | 2369225-test-only.patch | 3.4 KB | mpdonadio |
#37 | interdiff-35-37.patch | 886 bytes | mpdonadio |
#37 | add-2369225-37.patch | 8.53 KB | mpdonadio |
#35 | interdiff-31-35.txt | 3.03 KB | mpdonadio |
#35 | add-2369225-35.patch | 8.5 KB | mpdonadio |
Comments
Comment #1
Gábor Hojtsycore/modules/language/src/Tests/LanguageUrlRewritingTest.php has sort of unit tests (it needs to build a request environment itself because the test system may not receive whatever domains, so it needs to simulate an environment) for domain + port based link generation using url().
core/modules/language/src/HttpKernel/PathProcessorLanguage.php is where the inbound (from request) and outbound (to output) URL handling is for languages; you should look at the outbound processing and why it does not work
Comment #2
mpdonadioI'll start digging into this base on those leads.
Comment #3
mpdonadioOK, patch is attached. Added WSCCI as this just got complicated:
Domain based language negotiation sets $options['base_url] as part of Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::processOutbound(), which gets passed off for URL generation.
Drupal\Core\Url does not have a valid $option for setting the $base_url. $options['base_url] will be ignored, and the $base_url will be determined by the UrlGenerator() by the context (cf, Drupal\Core\Routing\UrlGenerator::generateFromRoute).
The patch addresses this portion. If this is acceptable, I will adjust the docblocks to add this option back in.
Comment #4
dawehnerIt would be great to expand the test coverage in
Drupal\Tests\Core\Routing\UrlGeneratorTest
Comment #5
mpdonadioPer a conversation with @dawehner, this should be added in as the D7 url() function has this parameter.
I will fill this out with proper test coverage, update docs, etc, to have a proper patch for review.
Comment #6
mpdonadioDocs and test coverage.
Comment #7
mfbThere is some code in the generateFromRoute() method that doesn't really make sense to me:
Doesn't base_url include the scheme, host and port?
Comment #8
dawehner@mfb
Nope, $base_url is
$this->context->getBaseUrl()
which is something else thanglobal $base_url;
Comment #9
dawehner+1 for that so far.
Comment #10
mfbRight. But this patch has
$base_url = isset($options['base_url']) ? $options['base_url'] : $this->context->getBaseUrl();
$options['base_url'] includes the scheme, host, port.
$this->context->getBaseUrl() is the base path.
If $base_url is set to a full URL as found in $options['base_url'] then combining that with scheme, host, port down at the bottom of the function doesn't work.
Contrib modules (and custom modules) do need a way to alter all aspects of the URL, including scheme and domain (e.g. Domain Access module modifying the domain, or Secure Login module forcibly rewriting a URL to HTTPS with mixed-mode sessions disabled) so I think it's correct to allow the base_url to be altered. but the code in this method doesn't allow it - it decides what the scheme and host will be and slaps the base_url on the end of that.
Comment #11
mfbSome sample problems with this patch applied, and two languages enabled, with domain URL language detection:
Clicking on Log out redirects me to http://d8.localhttp//d8.local/
Clicking the Uninstall button at the module uninstall page redirects me to http://d8.localhttp//d8.local/admin/modules/uninstall/confirm
Running tests redirects me to http://d8.localhttp//d8.local/admin/config/development/testing/results/1
Comment #12
mpdonadioActually, this is a bug, and a weird one that results in a happy accident in some cases, and sad problems in others.
When LanguageNegotiationUrl::processOutbound() runs with domain negotiation, it sets both $options['base_url'] and $options['absolute'], eg
Since $absolute has already been pulled out at the top of UrlGenerator::generateFromRoute(), before outbound processing, it is a happy accident and the function calls the `return $base_url . $path . $fragment;` line. Apparently, the cases that @mfb points out are the sad problems. If I apply this on top of the patch from #2364157: Replace most existing _url calls with Url objects, then those tests also fail with the double-concatenated base.
Since anything that implements OutboundPathProcessorInterface could potentially set $options['absolute'] = TRUE, then that line needs to be moved to be after we have done all that.
This cause more quirks. I think this patch is the best option. I think it addressees everything that @mfb saw.
@dawehner, this approach will also get us much closer to finishing up #2364157: Replace most existing _url calls with Url objects.
Comment #13
dawehnerCan you explain why we need this here? At least a comment about the regular expression should be necessary. It sounds though not like a good idea, to be honest.
Comment #14
mpdonadioReworked logic to eliminate regex and added comments; added logic and test coverage to support mixed-mode sessions w/ base_url (generateFromPath() has this).
Comment #15
mfbThanks, this resolves the issue I noticed.
One question: Is it necessary to say that base URL is "Only used internally, to modify the base URL when a language dependent URL requires so." I realize this is how it was documented in Drupal 7, but I believe modifying base URL here may be the only way for contrib or custom modules to modify the base URL (to use a different hostname, domain or scheme)?
Comment #16
mpdonadioThat language was copied over from `UrlGeneratorInterface::generateFromPath`, which nearly one-to-one from the Drupal 7 language, and is the starting point.
Anything that implements one of the path processors can potentially mess with the options, and setting the base_url is a possibility. In core, the domain-based language negotiation is the only(?) thing that does this. I suspect that an origin-pull CDN module would work this way in Drupal 8 (though I haven't put too much thought into it. I think. This may be better:
Comment #17
mfbmaybe to be more clear something like
?
Comment #18
mpdonadioOk, updates both places to use that language, but didn't use the abbreviation for ESL reasons.
Comment #19
disasm CreditAttribution: disasm commentedShould the regexp specify to only str_replace if it matches http or https at the beginning of the string? I can't think of a scenario where http:// or https:// would ever occur anywhere but the beginning of a url, but it might be safer to specify ^http instead of http.
Comment #20
mpdonadiostr_replace() doesn't do regular expressions. We are avoiding them in this context for performance reasons.
Comment #21
mfbThis looks good to me, setting to RTBC
Comment #22
alexpottFrom the issue summary this is a bug. Do we need to add some assertions to LanguageUILanguageNegotiationTest::testLanguageDomain() or create a new test along the lines of the issue summary:
Comment #23
dawehnerGiven that this blocks #2364157: Replace most existing _url calls with Url objects at the moment, this issue should be considered to be a critical one.
Comment #24
mfbApparently mixedModeSessions has been removed (as a fan of HTTPS everywhere, yay!) so setting to Needs work.
Comment #25
YesCT CreditAttribution: YesCT commentedComment #26
mpdonadioJust the reroll; conflicts were limited to the docblocks.
Comment #28
mpdonadioUpdates for mixed-mode sessions being gone. #22 has not been addressed yet; this just gets back a passing patch.
Comment #29
mpdonadioComment #30
mpdonadioOK, LanguageSwitchingTest seemed the best place to add a new test, as other tests to the language switcher block are there. Used XPath testing to be double sure the test checks the proper links.
Comment #31
mpdonadioMissed a merge in a comment.
Comment #32
andypostJust a nitpics,
why rebuild needed here? no data changed
should be a namespaced
remove else
Comment #33
andypostand
assertText()
should uset()
Comment #34
mpdonadioRe #32-1, everything before the block placement in that test was copied verbatim from LanguageUILanguageNegotiationTest::testLanguageDomain(), which has the untranslated assertions and the container rebuilds. I'll fix, though, in the test I added.
Comment #35
mpdonadio#32 and #33 are addressed. One container rebuild is necessary; moved to proper place and added comment. Also removed the use of the global and used the current Request object instead, as I think that one is slated to be removed.
Comment #36
znerol CreditAttribution: znerol commentedCan you also post a patch which only contains the test (XX-test-only.patch)? The intention of such a patch is to demonstrate the bug and the effectiveness of the test (by means of failing test assertions). See #2378703-4: Port denial of service fixes from SA-CORE-2014-006 to Drupal 8 for an example.
Maybe use the same documentation for both methods, it looks like the second one was copied before the first one was modified.
The rest of the patch looks RTBC to me.
Comment #37
mpdonadio#36 has been addressed.
Added a test-only patch. Shows the exact fail against HEAD for me locally (lines 215 and 223).
Comment #38
znerol CreditAttribution: znerol commentedApplied #35 on a local instance and manually verified that the language selection block renders the links properly. As per #36 this is RTBC, provided that #37 turns green.
Comment #39
znerol CreditAttribution: znerol commentedComment #42
mpdonadioResetting status from #38, as the actual patch passed.
Comment #43
catchCommitted/pushed to 8.0.x, thanks!
Comment #46
mpdonadioCleaning up the issues that I am assigned to...