Problem/Motivation

If you add a language-unspecified alias for a page that already has an alias in the default language, the language-unspecified alias will sporadically appear around the site.

Steps to reproduce

use Drupal\Core\Language\LanguageInterface;

// Generate paths to test with.
$source = '/' . uniqid('alias_test');
$base = '/' . uniqid('alias_test') . '_';

/** @var \Drupal\Core\Path\AliasStorageInterface $storage */
$storage = \Drupal::service('path.alias_storage');

// Save a couple of aliases.
$storage->save($source, $base . 'en', 'en');
$storage->save($source, $base . 'und', LanguageInterface::LANGCODE_NOT_SPECIFIED);

// Search for an individual alias. Should find the en alias we added above.
$found_one = $storage->lookupPathAlias($source, 'en');
print "$found_one\n";

// Search for multiple aliases. Should also find the en alias from above.
$found_multi = $storage->preloadPathAlias([$source], 'en');
print "$found_multi[$source]\n";
// Woops, it found the und alias instead!

// Cleanup.
$storage->delete(['source' => $source]);

Proposed resolution

Change the sort order of the query prepared in \Drupal\path_alias\AliasRepository::preloadPathAlias so that the in:: addLanguageFallback applies its sorts in the correct order too.

This will also require changing slightly how the results are fetched from the query by this method.

Fix is in

MR 5250

All others MRs can be closed

Remaining tasks

None that I'm aware of.

User interface changes

URL paths should now be consistent, rather than depend on the unpredictable nature of what methods were called first, what was cached etc. So this might introduce some change for path aliases on some sites, but should do so in a consistent way, fixing the essentially non-deterministic way it was working before.

API changes

None.

Data model changes

None

Issue fork drupal-2745755

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:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

vasi created an issue. See original summary.

vasi’s picture

It looks like we don't have any tests for preloadPathAlias, so I wrote some. These fail, for two reasons:

  • The priority issue mentioned above
  • preloadPathAlias() doesn't check for an empty list of paths
vasi’s picture

Status: Active » Needs review
vasi’s picture

Here's a patch that gets the new tests passing. I think it was just a swapped greater-than sign.

The last submitted patch, 2: 2745755-tests.patch, failed testing.

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

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now 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.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now 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.

vasi’s picture

In addition to just making links sometimes look wrong, this is currently breaking blocks on our site. The request_path block condition ends up comparing against the und path instead of the language-specific path, which is not expected.

vasi’s picture

Fixed the comment so it correctly explains the new code.

mvc’s picture

Status: Needs review » Reviewed & tested by the community

In particular, the behaviour now will be unexpected if url_alias.source is the same for two paths, which is a legitimate thing to do on multilingual sites.

This looks good except that calling if (!empty($preloaded)) is redundant after adding empty($preloaded) above for performance.

mvc’s picture

Status: Reviewed & tested by the community » Needs work

Fixing status

vasi’s picture

On reflection, Drupal's current behaviour when $preloaded is empty is not relevant to this issue. It was unexpected to me that it would return all paths, but that's not entirely crazy, so I backed out that part of my patch, and adjusted the tests to match. I also added to the phpDoc so that the behaviour is specified, and others won't be surprised.

vasi’s picture

Status: Needs work » Needs review
mvc’s picture

Status: Needs review » Reviewed & tested by the community
Wim Leers’s picture

Status: Reviewed & tested by the community » Needs work
  1. --- a/core/lib/Drupal/Core/Path/AliasStorage.php
    +++ b/core/lib/Drupal/Core/Path/AliasStorage.php
    

    This is modifying AliasStorage.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php
    @@ -216,4 +217,67 @@ public function testWhitelist() {
    +  public function testPreloadPathAlias() {
    

    This is expanding the functional test coverage.

  3. But the patch is not updating the unit test coverage in \Drupal\KernelTests\Core\Path\AliasStorageTest.
vasi’s picture

Status: Needs work » Needs review
FileSize
13.88 KB
7.87 KB

Thanks! Added a pretty extensive test.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

Impressive!

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Baltimore2017

Discussed this at Baltimore. We figured that changing the order does not only change the result order of the specific vs. general alias but also the preferred alias if there are multiple aliases for the same path. So this could result in a backwards incompatible change (other than for the bug being fixed).

vasi’s picture

Status: Needs work » Needs review
FileSize
14.05 KB
2.19 KB

After some thought, I've realized doesn't change the preferred alias if there are multiple aliases for the same path. We have $select->orderBy('pid', 'ASC');, so the latest-added alias in the appropriate language will always be preferred, both with and without this patch.

Added some test data to show that this is the case.

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

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now 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.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now 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.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now 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.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.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.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). 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.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now 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.

efrainh’s picture

Status: Needs review » Fixed

The patch on #19 was already merged:

When upgrading from 8.7.6 to 8.8 I got this message:

https://www.drupal.org/files/issues/2745755-19.patch (AliasStorage::preloadPathAlias() incorrectly prioritizes UND aliases; https://www.drupal.org/project/drupal/issues/2745755)
Could not apply patch! Skipping. The error was: Cannot apply patch https://www.drupal.org/files/issues/2745755-19.patch

Reviewing the code it already contains the patch so we don't need this patch in 8.8!!.

james.williams’s picture

Status: Fixed » Needs work
Issue tags: +Needs reroll

@efrainh no, the changes in the latest patch is not already in Drupal core, the patch just doesn't apply any more because the affected files changed. There is now a Drupal\Core\Path\AliasStorage::addLanguageFallback() method in D8.8, which is where the change would be needed instead. In D9.1 though, that class has been almost completely gutted, and the fallback change would need applying to Drupal\path_alias\AliasRepository::addLanguageFallback(). The tests would no doubt need changing up accordingly too.

So I'm adding the re-roll tag too, but I suspect this will not be a trivial re-roll.

james.williams’s picture

Version: 9.1.x-dev » 10.0.x-dev

As Gábor pointed out, this change might fix a bug, but would be a BC break. So I think it now has to wait for D10 :-/

Steven Jones’s picture

Version: 10.0.x-dev » 11.x-dev
Issue tags: -Needs reroll

Coming here from #3135440 where language hierarchy needs to solve basically the same problem/bug that core has, but for multiple languages instead of two.

Moving the target version to 11, and will open a issue fork to work on this.

Steven Jones’s picture

So as mentioned in #27 the code has moved on a bit, so I've focused on bringing over the tests from #16 that tested out \Drupal\path_alias\AliasRepository::preloadPathAlias.

And then I've implemented a fix.

The real problem here is that preloadPathAlias processes the result set and prefers aliases at the end, whereas all the other methods use the first row in the result set.

This is explained when setting the sort order of the base_table.id field:

    // We order by ID ASC so that fetchAllKeyed() returns the most recently
    // created alias for each source. Subsequent queries using fetchField() must
    // use ID DESC to have the same effect.
    $select->orderBy('base_table.id', 'ASC');

But this change in ordering is never made in addLanguageFallback so I've introduced a badly named parameter that'll reverse the ordering when appropriate.

Steven Jones’s picture

Status: Needs work » Needs review
Steven Jones’s picture

...and I've just read that I'm supposed to be using patches, unless I explain why I'd like to change over to MRs:

It's been 7 years since the last patch, and the world has moved on, MRs are more/faster/better. Hope that's enough!

Steven Jones’s picture

There we go, as expected the MR tests pass, but the tests-only version fails.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record

Thanks for reviving this one. Think we will need a change record for the new parameter, examples are always good too in the CR.

Searching http://grep.xnddx.ru/search?text=addLanguageFallback&filename=

https://www.drupal.org/project/variants or https://www.drupal.org/project/language_hierarchy may be doing something with this. Lightly took a look.

Steven Jones’s picture

@smustgrave thanks, do you happen to have an example of a CR for documenting adding a new parameter to a protected method?

As for language hierarchy, yes, we're all over this in #3135440: LH limits to 1 alias when preloading multiple aliases :)

smustgrave’s picture

This is a pretty basic one https://www.drupal.org/node/3372008

Steven Jones’s picture

Status: Needs work » Needs review

I was writing out the change notice, when I thought that actually this all seems really ugly: that for one method that wants the results in a different order we need to thread this parameter around etc. Seems a lot like tail wagging the dog.
Then I realised that it was only because preloadPathAlias wants to use fetchAllKeyed that we need to do this at all.

If this method does a fraction more work, then everything can be nice and consistent and actually all the queries generated by the \Drupal\path_alias\AliasRepository class can actually have the same order by clauses, meaning that everything is consistent and easier to reason about anyway.

So...I've added that approach to the MR, which now means there's no API change.

I'm also not sure if I agree with @Gábor Hojtsy in #18 that this change is now* a BC break, since, at the moment, depending on your configuration results are simply inconsistent. If preloadPathAlias is called, you'll get one set of aliases, but if you were to call lookupBySystemPath then you might get a different one for the same set of data.
Core has no control over the order and use of these functions really, so I don't think you can consider this weird bug a feature that can't be fixed in a minor or even patch release.

*: Also when @Gábor Hojtsy wrote #18 the patch on this issue was essentially inverting the sort to shift the problem to the methods that only seek to return on alias, so yeah, that would have been the same bug, but a different presentation.

Setting this back to Needs review to get some more feedback on this change of tack.

smustgrave’s picture

Not sure I can speak on it but if a new solution is taken the issue summary will need ot be updated to reflect that.

Steven Jones’s picture

Issue summary: View changes
Issue tags: -Needs change record
smustgrave’s picture

So circled back to this one

1) Drupal\Tests\path_alias\Kernel\AliasTest::testPreloadPathAlias
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
     '/und/src' => '/und/alias'
     '/en/src' => '/en/alias'
-    '/en-und/src' => '/en-und/en'
+    '/en-und/src' => '/en-und/und'
     '/en-xx-lolspeak/src' => '/en-xx-lolspeak/en-dup'
-    '/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/en'
+    '/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/und'
 )

Test-only feature shows the issue.

Hiding patches as fix is in MR, also highlighted it in the IS.

Looking at the change itself nothing seems glaring so going to mark it.

  • catch committed e829c1e3 on 10.2.x
    Issue #2745755 by vasi, Steven Jones, smustgrave, mvc, Wim Leers, james....

  • catch committed 94e37fd5 on 11.x
    Issue #2745755 by vasi, Steven Jones, smustgrave, mvc, Wim Leers, james....
catch’s picture

Version: 11.x-dev » 10.2.x-dev
Status: Reviewed & tested by the community » Fixed

Committed/pushed to 11.x and cherry-picked to 10.2.x, thanks!

I deleted the change record because it was for a different approach to the final one.

Status: Fixed » Closed (fixed)

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