Problem/Motivation

  • Add a field to a View where you can type in a path and render it as a link, and then type in a path that includes token (e.g., {{nid}}). Or, import the views.view.test_dropbutton.yml View, which has that (the "Custom text" field).
  • Enable the Language module, add a language to the site, and then go to that View's page with a language prefix. E.g., if using the test_dropbutton View, go to http://example.com/en/test-dropbutton.
  • Notice that the link (e.g., the "Custom text" link) takes you to a URL that no longer has that language prefix.
  • The problem is that FieldPluginBase::renderAsLink() calls Url::fromUri('user-path:...') on the tokenized path, and while tokenized, that doesn't match a route, so falls back to 'base:', which does not apply language prefixing (since you don't want language prefixing for unrouted paths). Then tokens are replaced, but we're already in the base: scheme, so too late.

Proposed resolution

Run the path (technically a URI at the point in the code) through the path validator to convert to a routable Url() object.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because what should be routable, and language prefixed, isn't.
Prioritized changes Bugfixes are considered prioritized changes.
Disruption No disruption, improved user experience for those running multilingual sites.

Comments

mpdonadio’s picture

Status: Active » Needs review
StatusFileSize
new985 bytes

Happened to have this file open. What about something like this?

See if the path is routable with PathValidator, otherwise use it as-is? This is kinda for discussion, as I can't 100% remember which of those variables has the path w/o the scheme.

Status: Needs review » Needs work

The last submitted patch, 1: 2426399-01.patch, failed testing.

mpdonadio’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests
StatusFileSize
new1.14 KB

This patch fixes the problem described above when I reproduced the situation in the summary (import .views.view.test_dropbutton.yml, and enable Language).

However, we need to add coverage to FieldPluginBaseTest to cover this situation.

mpdonadio’s picture

We may want to wait for #2426447: Views no longer supports {{ something }} as twig placeholder for a path, only {{something}} to get in before working on the tests, as that issue adds coverage for tokenized paths, which we can then build off of.

mpdonadio’s picture

StatusFileSize
new5.26 KB
new4.94 KB

This test kinda feels like cheating, but it passes locally for me.

mpdonadio’s picture

StatusFileSize
new5.25 KB
new1.61 KB

Minor cleanup to fix some coding standard things.

wim leers’s picture

Some advanced trickery going on here! :D

  1. +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
    @@ -1468,8 +1476,19 @@ protected function renderAsLink($alter, $text, $tokens) {
    +    // otherise use is as-is.
    

    s/otherise/otherwise/
    s/is/it/
    :)

  2. +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
    @@ -1468,8 +1476,19 @@ protected function renderAsLink($alter, $text, $tokens) {
    +    if ($i = strpos($path, 'base:') === 0) {
    

    Wow — we're being brave — relying on PHP's operator precedence :D

  3. +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
    @@ -1468,8 +1476,19 @@ protected function renderAsLink($alter, $text, $tokens) {
    +      if (!$final_url = $this->getPathValidator()->getUrlIfValidWithoutAccessCheck(substr($url['path'], 5))) {
    +        $final_url = CoreUrl::fromUri($path, $options);
    +      }
    +      else {
    +        $final_url->setOptions($options);
    +      }
    +    }
    +    else {
    +      $final_url = CoreUrl::fromUri($path, $options);
    

    Wow!

    I think this can use some additional comments. At least speaking for myself, this looks too confusing/black magic-ish.

    (I kind of understand why it's necessary given the IS, but future readers of this code will say "lolwut" in unison I think :D)

mpdonadio’s picture

StatusFileSize
new5.72 KB
new1.38 KB
new251.51 KB

Thanks for the review Wim.

#1 taken care of by the rewrite in #3.

I can assure you that #2 was by accident. I don't have the operator precedence chart memorized :) Added parens to make it more readable.

Any sufficiently advanced technology is indistinguishable from magic. -- Clarke

Added big comment block to account for #3.

Also attached screenshot showing a view-source of the test-dropbutton view that shows the language prefix on the custom text link, and that it matches the path prefix on all the other links in that row.

wim leers’s picture

I can assure you that #2 was by accident. I don't have the operator precedence chart memorized :)

:D

  1. +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
    @@ -1476,9 +1476,15 @@ protected function renderAsLink($alter, $text, $tokens) {
    +    // current Url object may be unroutable and use the 'base:' scheme,
    

    Why "may"? Isn't this *always* the case in case of a token replacement?

  2. +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
    @@ -1476,9 +1476,15 @@ protected function renderAsLink($alter, $text, $tokens) {
    +    // extract the path portion and run it throught the path validator service
    

    s/portion/component/

    But this is not actually extracting the path component, it also includes querystring and fragment, if any.

    So URI reference is probably more accurate.

    OTOH, maybe just parse_url() the path component only? That'd be clearer.

mpdonadio’s picture

Issue tags: -Needs tests
StatusFileSize
new5.79 KB
new1.9 KB

#9-1, no. views.view.files.yml has a path defined for a page display with a Twig token in it (that is the view path that started the chain reaction of Url() related issues). Right before token replacement, the URI string in FieldPluginBase::renderAsLink is 'route:view.files.page_2;arg_0={{fid}}', and ends up being treated as a routable URI through the $final_url logic (I double checked this with debug).

#9-2. Took another stab at that comment, and changed the logic to use parse_url(); that will be less fragile.

fabianx’s picture

+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1468,8 +1476,25 @@ protected function renderAsLink($alter, $text, $tokens) {
+      }
+      else {
+        $final_url->setOptions($options);
+      }
+    }
+    else {
...
+    }

The following is simpler and should do the same:

$final_url = FALSE;

if (parse_url($path, PHP_URL_SCHEME) === 'base') {
  if ($final_url = $this->getPathValidator()->getUrlIfValidWithoutAccessCheck(parse_url($path, PHP_URL_PATH))) {
    $final_url->setOptions($options);
  }
}

// If it was not yet set, get it fromUri
if (!$final_url) {
  $final_url = CoreUrl::fromUri($path, $options);
}

All that said, views should not hide this and 'cheat'.

While still strongly discouraged, there seems to be a need for a generic helper function to turn a path - potentially from base: to a full path - even in core.

Display Suite has the same problem. (with the exact same use case of using tokens)

And panels will probably, too.

We surely don't want to force all of them to find and re-implement this magic code.

mpdonadio’s picture

Assigned: Unassigned » mpdonadio
Status: Needs review » Needs work

Thanks for the review @Fabianx; that will likely read better. I'll double check it when I can locate my development laptop...

`Url::fromUserInput()` will take a path and run the pathValidator on it to get a routable Url object if possible via `Url::fromUserPathUri()` (see #2426181: Add a Url::fromUserInput() wrapper method for generating URLs from user-entered paths). The difference here is that as part of #2418139: Add a toUriString method to Url class and add a route: scheme and #2404603: Add proper support for Url objects in FieldPluginBase::renderAsLink(), so we can remove EntityInterface::getSystemPath(), `FieldPluginBase::renderAsLink()` works with the URI string and not a path, since token replacement may need to happen on on the route arguments and/or query options and not just the path component.

I am setting this back to Needs Work, though, as I want to see if the final version of #2426181: Add a Url::fromUserInput() wrapper method for generating URLs from user-entered paths will require any logic changes here.

mpdonadio’s picture

Assigned: mpdonadio » Unassigned
Status: Needs work » Needs review
StatusFileSize
new5.79 KB
new1.36 KB

Simplified some of the logic per #11.

#2426181: Add a Url::fromUserInput() wrapper method for generating URLs from user-entered paths didn't affect this one way or another.

mpdonadio’s picture

StatusFileSize
new2.84 KB
new8.63 KB
new2.84 KB

Added a test to FieldDropButtonTest to reproduce the one described in the issue summary.

The last submitted patch, 14: 2426399-test-only.patch, failed testing.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Patch looks good to me. Thanks for the explanation, has a test, therefore RTBC.

dawehner’s picture

+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1472,8 +1480,26 @@ protected function renderAsLink($alter, $text, $tokens) {
+    // We now have a heavily processed path as a URI string and set of options.
+    // If the original path was user entered, or had token replacement done to
+    // it, the current Url object may be unroutable and use the 'base:' scheme,
+    // which means outbound path processors won't run (e.g., path prefixing
+    // won't occur).  So, check for to see if we have a 'base:' URI, and then
+    // extract the path component and run it throught the path validator service
+    // to try to turn it into a routable Url object, so output path processors
+    // will run.
+    $final_url = FALSE;
+    if (parse_url($path, PHP_URL_SCHEME) === 'base') {
+      if ($final_url = $this->getPathValidator()->getUrlIfValidWithoutAccessCheck(parse_url($path, PHP_URL_PATH))) {
+        $final_url->setOptions($options);
+      }
+    }

Mh I try to understand it. So it would have been possible that we end up with base:node/1 right? In that case running the path validator is fine, but the documentation kinda talks about path processing. In that case the UnroutedUrlAssembler has a flag to force path processing to run.

mpdonadio’s picture

@dawehner, in this view, we are getting `base:node/1` as a URI. What would you have the documentation say for this?

xjm’s picture

mpdonadio’s picture

Issue summary: View changes

Updated IS; added Beta eval.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

So should we be using the UnroutedUrlAssembler here?

mpdonadio’s picture

I am not sure why we would want UnroutedUrlAssembler, as that more or less is the problem.

If we are ending up with 'base:node/1' as the final URI after token replacement, then we have a valid, routable path and should pass a routable Url() object to the link generator. Otherwise 'node/1' gets treated as an unrouted path, and gets sent off to the UnroutedUrlAssembler().

?

dawehner’s picture

+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1472,8 +1480,26 @@ protected function renderAsLink($alter, $text, $tokens) {
+    // We now have a heavily processed path as a URI string and set of options.
+    // If the original path was user entered, or had token replacement done to
+    // it, the current Url object may be unroutable and use the 'base:' scheme,
+    // which means outbound path processors won't run (e.g., path prefixing
+    // won't occur).  So, check for to see if we have a 'base:' URI, and then
+    // extract the path component and run it throught the path validator service
+    // to try to turn it into a routable Url object, so output path processors
+    // will run.
+    $final_url = FALSE;
+    if (parse_url($path, PHP_URL_SCHEME) === 'base') {
+      if ($final_url = $this->getPathValidator()->getUrlIfValidWithoutAccessCheck(parse_url($path, PHP_URL_PATH))) {
+        $final_url->setOptions($options);
+      }
+    }
...
+    // If it was not yet set, use the Uri as-is and convert it into a Url object
+    // with ::fromUri()
+    if (!$final_url) {
+      $final_url = CoreUrl::fromUri($path, $options);
+    }
 

I'm curious whether we should just expand the options to set ['path_processing'] = TRUE. Don't we then ge the basic same result?

mpdonadio’s picture

StatusFileSize
new6.81 KB
new2.56 KB

OK, here is the simple idea from #23. The patch is a clean checkout from HEAD with that option added + the new test. Passes locally for me.

Status: Needs review » Needs work

The last submitted patch, 24: 2426399-24.patch, failed testing.

mpdonadio’s picture

Status: Needs work » Needs review
StatusFileSize
new6.81 KB
new2.56 KB

Let's pretend that didn't happen.

dawehner’s picture

I kinda like the way how it solved now, but on the other hand yeah the unbound URL assembler was never intended to always generate path aliases.

dawehner’s picture

I kinda like the way how it solved now, but on the other hand yeah the unbound URL assembler was never intended to always generate path aliases.

Status: Needs review » Needs work

The last submitted patch, 26: 2426399-26.patch, failed testing.

mpdonadio’s picture

Blerg. I forgot about all of the mocking that is needed in FieldPluginBaseTest, which would need to be addressed. So, which option do we want, real routes or unrouted paths + outbound processing? If the later, I'll fix the unit test.

mpdonadio queued 14: 2426399-14.patch for re-testing.

mpdonadio queued 26: 2426399-26.patch for re-testing.

The last submitted patch, 26: 2426399-26.patch, failed testing.

gábor hojtsy’s picture

Issue tags: +sprint, +language-base
gábor hojtsy’s picture

Issue tags: -sprint

No work unfortunately for a long time, removing sprint tag :/

mpdonadio’s picture

If someone above my pay grade choose a direction (unrouted w/ path processing in #26 vs routes in #14), I'll wrap this up.

Personally, I think #14 is the best option.

kikoalonsob’s picture

FYI, Error still occurs in RC2.

wim leers’s picture

Status: Needs work » Postponed (maintainer needs more info)

Is this still broken?

mpdonadio’s picture

Status: Postponed (maintainer needs more info) » Needs review
StatusFileSize
new2.87 KB

Quick hand re-roll of test-only patch to see what happens, but we should check that it is still a valid test (mainly, that the class names haven't changed).

Status: Needs review » Needs work

The last submitted patch, 39: 2426399-test-only.patch, failed testing.

mpdonadio’s picture

Looks like this is still an issue. Existing test passes. New one fails.

We need to pick

- #14: use the path validator to convert the post-token / post-processed path into a URL object, which will then get all of the related URL goodness
- #26: update the url generation in view plugin so that outbound path processing runs, regardless of whether the path is routable or not

Personally, I think the approach is #14 is better.

jibran’s picture

I think we should prefer #26 as per @dawehner's choice.

jonhattan’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Needs work » Needs review
StatusFileSize
new6.83 KB

Both #14 (use path validator) and #26 (run outbound path processing) are outdated. Rerrolled #26.

mpdonadio’s picture

Version: 8.1.x-dev » 8.0.x-dev

This is a bug, so this should be 8.0.x eligible. I was going to work on this at DCNJ, esp since @dawehner will be there, too.

Status: Needs review » Needs work

The last submitted patch, 43: 2426399-43.patch, failed testing.

mpdonadio’s picture

Assigned: Unassigned » mpdonadio

Looking at this now. Concerned that FieldDropButtonTest fails now, which should be fixed by the patch.

mpdonadio’s picture

Status: Needs work » Postponed

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Status: Postponed » Postponed (maintainer needs more info)

There has been no activity here for 9 years.

Can someone confirm that this is still a proeblem?

If we don't receive additional information to help with the issue, it may be closed after three months.

Thanks!

smustgrave’s picture

Issue tags: +Bug Smash Initiative

Wanted to bump this one more time

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)

Since there's been no follow up going to close out. If still an issue please re-open

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.