Problem/Motivation

Install standard.

Add a page node, with an alias via the node form.

Add a second page node, with no alias.

Then add an alias for admin/config/search

Expected result:

Both aliases have either 'en' or 'und' language codes. Both aliases can be edited.

Actual result:

Aliases added via the node form have 'en' as language code - can be edited.

Aliases added via the path alias UI have 'und' as language code - cannot be edited from the node form because the langcode doesn't match.

Proposed resolution

When looking for aliases for the node form, fall back to language neutral if there is not an alias in the language

Remaining tasks

User interface changes

API changes

Data model changes

Data migration may be needed.

CommentFileSizeAuthor
#42 interdiff-37-42.txt789 byteszaporylie
#42 2511968-42.patch4.96 KBzaporylie
#37 2511968-37.patch4.93 KBzaporylie
#37 2511968-37-TEST-ONLY.patch1.91 KBzaporylie
#33 2511968-33.patch4.91 KBjhedstrom
#33 interdiff-2511968-30-33.txt1.01 KBjhedstrom
#30 2511968-30.patch4.92 KBjhedstrom
#30 2511968-30-TEST-ONLY.patch1.91 KBjhedstrom
#25 2511968-25.patch4.72 KBjhedstrom
#25 2511968-25-TEST-ONLY.patch4.61 KBjhedstrom
#25 interdiff-2511968-24-25.txt884 bytesjhedstrom
#24 2511968-24.patch4.39 KBjhedstrom
#24 interdiff-2511968-22-24.txt917 bytesjhedstrom
#22 path_alias_widget_on-2511968-20.patch4.31 KBBerdir
#20 path_alias_widget_on-2511968-20.patch4.31 KBBerdir
#16 path_alias_widget_on-2511968-16-interdiff.txt1.61 KBBerdir
#16 path_alias_widget_on-2511968-16.patch4.8 KBBerdir
#10 path_alias_widget_on-2511968-10-interdiff.txt555 bytesBerdir
#10 path_alias_widget_on-2511968-10.patch4.5 KBBerdir
#8 path_alias_widget_on-2511968-8-interdiff.txt502 bytesBerdir
#8 path_alias_widget_on-2511968-8.patch4.49 KBBerdir
#6 path_alias_widget_on-2511968-6.patch4.42 KBBerdir
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

On monolingual sites, add aliases with 'en' langcode.

OR

When looking for aliases for the node form, fall back to language neutral if there is not an alias in the language

Given that 'en' is used as the langcode of the entity itself, I think there is no reason that the path alias should not inherit that automatically.

Given that the same UI behaviour should happen for the path alias as well, always at least have one langcode available.
This could also ease the transaction into path aliases as entities in the future.

catch’s picture

catch’s picture

Title: Aliases added via the path alias UI have the wrong language code » Path alias widget on entity forms should fall back to language neutral aliases
Version: 8.0.x-dev » 8.1.x-dev
Issue summary: View changes
Status: Closed (duplicate) » Active
Related issues: +#2484411: Manual path aliases are not the same as aliases on the node form on single-language sites

Re-opening this after a discussion with @xjm @effulgentsia @alexpott @plach @swentel @berdir @amateescu

The path alias form should look for language neutral aliases since that's valid.

catch’s picture

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.

Berdir’s picture

Status: Active » Needs review
FileSize
4.42 KB
Berdir’s picture

That respects the langcode and falls back to unspecified.

Berdir’s picture

Forgot to add some assertions for the overview page.

dawehner’s picture

  1. index 239588f..b14e9b7 100644
    --- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    
    --- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    
    +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    @@ -30,6 +30,8 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
    
    @@ -30,6 +30,8 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
           ->setLabel(t('Path alias'));
         $properties['pid'] = DataDefinition::create('integer')
           ->setLabel(t('Path id'));
    +    $properties['langcode'] = DataDefinition::create('string')
    +      ->setLabel(t('Path langcode'));
         return $properties;
    

    I'm wondering whether we should provide an update path and set the langcode on there

  2. +++ b/core/modules/path/src/Tests/PathLanguageUiTest.php
    @@ -78,4 +80,28 @@ function testNonDefaultUrl() {
    +  function testNotSpecifiedNode() {
    

    nitpick: we could add a "public" here.

Berdir’s picture

1. set /where? This is a computed field type, this information not persisted anywhere anymore. It also wouldn't strictly be required, the patch works without it. But we will eventually also support loading an existing alias I hope, and then it might become useful.

2. Yeah :) I blame the existing methods which don't have this for forgetting it.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

1. set /where? This is a computed field type, this information not persisted anywhere anymore. It also wouldn't strictly be required, the patch works without it. But we will eventually also support loading an existing alias I hope, and then it might become useful.

Oh right, nevermind.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -54,7 +56,7 @@ public function postSave($update) {
-        if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
+        if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->langcode ?: $this->getLangcode())) {

@@ -67,7 +69,7 @@ public function postSave($update) {
-        \Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
+        \Drupal::service('path.alias_storage')->save('/' . $entity->urlInfo()->getInternalPath(), $this->alias, $this->langcode ?: $this->getLangcode(), $this->pid);

This change looks odd. Should \Drupal\path\Plugin\Field\FieldType\PathItem() override \Drupal\Core\Field\FieldItemBase::getLangcode() and contain this logic? Maybe getLangcode() needs to always return the parent langcode - not sure.

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

getLangcode() is the langcode of the entity. It is a public method, overriding that feels wrong.

getLangcode() follows specific rules. You get the active translation, but if the field is not translatable then it is the language of the original translation. It is never not specified unless the entity actually has not specified as the langcode.

There will be additional changes with #2689459: If you don't want to translate your URL alias, the original URL alias won't work with your translations, basically, if the field is not translatable then we will use language not specified and not the language of the original entity as the default in the widget, so that the alias is available in all languages.

It made sense to me to split that up, if you think it makes more sense to combine the two issues, then we can also do both things together. The other issue would basically do a isTranslatable() check and if not, us not specified. I'm not sure yet about changing it when saving or respecting the existing langcode, a this now does.

And I kept the getLangcode() as a BC fallback.

Yet another related issue is loading lazy loading existing aliases when accessing the data, which will mean support for REST, search api, rules and other systems which use field api/typed data in a generic way.

Back to you for feedback, feel free to set back. @catch might also have thoughts on this, as he opened the issue.

alexpott’s picture

  /**
   * Gets the langcode of the field values held in the object.
   *
   * @return string
   *   The langcode.
   */
  public function getLangcode();

Above are the docs on \Drupal\Core\Field\FieldItemInterface::getLangcode() - so whilst this is normally the same as the entity's langcode in this case it does not appear to be because PathItem has its own langcode. Tricky.

catch’s picture

Status: Reviewed & tested by the community » Needs work

Let's add comments for #12/13 since it's a bit confusing, but that looks right to me.

Berdir’s picture

Tried to document this behavior a bit but failed to come up with a concise description why we didn't override getLangcode() (which might prove that it would maybe make sense? I'm not sure..)

While thinking about this, this issue, in combination with #2689459: If you don't want to translate your URL alias, the original URL alias won't work with your translations could result in some interesting problems. Imagine a single language site, the path field is not translatable. With that other issue, we save the alias initially as unspecified. Then we add another language later and enable translations, making the field now translatable. Now we will find the existing unspecified alias when adding a translation and will update that instead of saving a new one in the translation language. We can figure out a solution in the other issue, possibly make the langcode visible to the user, so he can change it.

dawehner’s picture

  1. +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
    @@ -30,6 +30,8 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
    +    $properties['langcode'] = DataDefinition::create('string')
    +      ->setLabel(t('Path langcode'));
         return $properties;
    

    Sometimes it would be nice to document when to use this new langcode and when not.

  2. +++ b/core/modules/path/src/Plugin/Field/FieldWidget/PathWidget.php
    @@ -34,7 +34,14 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
           $path = \Drupal::service('path.alias_storage')->load($conditions);
    ...
    +          $path = \Drupal::service('path.alias_storage')->load($conditions);
    

    In case someone wants to, we could store \Drupal::service('path.alias_storage') as local variable.

  3. +++ b/core/modules/path/src/Tests/PathLanguageUiTest.php
    @@ -78,4 +80,28 @@ function testNonDefaultUrl() {
    +
    +    $this->drupalGet($node->toUrl('edit-form'));
    +    $this->assertFieldByName('path[0][alias]', $edit['alias']);
    +    $this->drupalPostForm(NULL, [], t('Save'));
    +
    +    $this->drupalGet('admin/config/search/path');
    +    $this->assertText(t('None'));
    +    $this->assertNoText(t('English'));
    

    I'm wondering whether we should test also the aliases directly, like going to the path aliases itself.

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.

jhedstrom’s picture

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

Needs a reroll as some of the tests have been moved to phpunit.

Berdir’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
4.31 KB

It's a bit more complicated than that :)

"Rebased", which is more like a rewrite as this now can and has to be in PathItem instead of the widget.

The thing now is that the duplicated load always happens on accessing the value, which is likely more often than the edit widget.

Thinking about extending load to support array values in the condition and then have the same language sorting logic as the lookup methods.

Not addressed the review yet.

Status: Needs review » Needs work

The last submitted patch, 20: path_alias_widget_on-2511968-20.patch, failed testing. View results

Berdir’s picture

Version: 8.3.x-dev » 8.4.x-dev
Status: Needs work » Needs review
FileSize
4.31 KB

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.

jhedstrom’s picture

+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -164,12 +170,17 @@ protected function ensureLoaded() {
+            $conditions['langcode'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;

This loads the first alias found of the 'und' language, since $conditions isn't previously set.

This patch resolves that, but we need further test coverage since this was previously green.

jhedstrom’s picture

Here's an updated test that demonstrates the issue with #22. The 'test only' patch is actually that patch with only the updated test, and the full patch contains the updated test and the change in #24.

The last submitted patch, 25: 2511968-25-TEST-ONLY.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

ptmkenny’s picture

Just a note: at the moment, this patch conflicts with the latest patch in 2689459. It would be great to either get this committed so 2689459 can be re-rolled or to combine the patches so that they can be used together.

jhedstrom’s picture

Status: Needs review » Needs work

The patch is also not compatible with the recent commit in #2916300: Use ComputedFieldItemListTrait for the path field type so needs a refactor.

jhedstrom’s picture

Assigned: Unassigned » jhedstrom

I'm going to work on the refactoring of the fix.

jhedstrom’s picture

Assigned: jhedstrom » Unassigned
Status: Needs work » Needs review
FileSize
1.91 KB
4.92 KB

Here's the refactored fix (no interdiff since the previous patch no longer applied).

The last submitted patch, 30: 2511968-30-TEST-ONLY.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 30: 2511968-30.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jhedstrom’s picture

Status: Needs work » Needs review
FileSize
1.01 KB
4.91 KB

This fixes coding standards. The fail in #30 looks to be unrelated/random:

Exception: Warning: apcu_store(): Unable to allocate memory for pool.

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.

WidgetsBurritos’s picture

Status: Needs review » Needs work

This has two failures on the 8.6.x branch.

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.

zaporylie’s picture

Reroll of the #33.

Wim Leers’s picture

Title: Path alias widget on entity forms should fall back to language neutral aliases » Path field should fall back to language neutral aliases (also makes this happen for the form widget!)
Issue tags: +D8MI, +API-First Initiative
+++ b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php
@@ -26,12 +27,18 @@ protected function computeValue() {
+      if ($alias === FALSE) {
+        // Fall back to non-specific language.
+        if ($this->getLangcode() !== LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+          $conditions['langcode'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
+          $alias = \Drupal::service('path.alias_storage')->load($conditions);
+        }

I believe this would also address the unexpected/confusing behavior reported against JSON:API in #3012019: Include the path / url alias and #3015566: Missing path attribute?

This approach is new as of #20, and means the issue title should probably be updated.

The last submitted patch, 37: 2511968-37-TEST-ONLY.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 37: 2511968-37.patch, failed testing. View results

zaporylie’s picture

+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -63,10 +63,15 @@ public function preSave() {
+    // If specified, rely on the langcode property for the language, so that the
+    // existing language of an alias can be kept. That could for example be
+    // unspecified even if the field/entity has a specific langcode.
+    $alias_langcode = $this->langcode ?: $this->getLangcode();

It seems like we set the the language property of the PathItem to Drupal's default in PathFieldItemList::computeValue() when we create the form, so the $this->language is always populated and it doesn't respect in which language the entity is created, hence the Test error.

zaporylie’s picture

Status: Needs work » Needs review
FileSize
4.96 KB
789 bytes

This patch fixes #41 by checking if path alias has pid value before using the langcode property.

One problem still remains - when url alias field is configured as non-translatable, whenever you add an entity translation it will create a new url alias in the source language of the entity. Ex. if I create node in english, set the path alias, then decide to add a polish translation and save a form I get two rows in the DB:

16	/node/15	/test	en
17	/node/15	/test	en

I believe it should be fixed in #2689459: If you don't want to translate your URL alias, the original URL alias won't work with your translations as it only applies to the non-translatable aliases.

Status: Needs review » Needs work

The last submitted patch, 42: 2511968-42.patch, failed testing. View results

zaporylie’s picture

Status: Needs work » Needs review

Not sure why CI decided to fail on this - all tests are green now.

googletorp’s picture

Status: Needs review » Reviewed & tested by the community

This looks good to me, would be great to get the ball rolling and improve url alias handling.

The last submitted patch, 33: 2511968-33.patch, failed testing. View results

Berdir’s picture

+++ b/core/modules/path/src/Plugin/Field/FieldType/PathFieldItemList.php
@@ -26,12 +27,18 @@ protected function computeValue() {
     $entity = $this->getEntity();
     if (!$entity->isNew()) {
-      // @todo Support loading language neutral aliases in
-      //   https://www.drupal.org/node/2511968.
-      $alias = \Drupal::service('path.alias_storage')->load([
+      $conditions = [
         'source' => '/' . $entity->toUrl()->getInternalPath(),
         'langcode' => $this->getLangcode(),
-      ]);
+      ];
+      $alias = \Drupal::service('path.alias_storage')->load($conditions);
+      if ($alias === FALSE) {
+        // Fall back to non-specific language.
+        if ($this->getLangcode() !== LanguageInterface::LANGCODE_NOT_SPECIFIED) {
+          $conditions['langcode'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;
+          $alias = \Drupal::service('path.alias_storage')->load($conditions);
+        }
+      }
 

There is still the performance aspect to consider here. I'm wondering if this could be done like the main alias lookup. Look for both language specific and neutral alias in a single load, if there is more than one alias, prefer the one with the matching language.

Wim Leers’s picture

Status: Reviewed & tested by the community » Needs work

I'm wondering if this could be done like the main alias lookup. Look for both language specific and neutral alias in a single load, if there is more than one alias, prefer the one with the matching language.

This would indeed be much better for performance.

amateescu’s picture

That would be very easy to do after #2336597: Convert path aliases to full featured entities since we'll be able to use an entity query with an OR condition on the langcode field :)

zaporylie’s picture

There is still the performance aspect to consider here. I'm wondering if this could be done like the main alias lookup. Look for both language specific and neutral alias in a single load, if there is more than one alias, prefer the one with the matching language.

Do you mean an additional method in AliasStorageInterface that combines load and lookupPathAlias together? That sounds like a BC break so I guess we would need another service for that?

#49 would be great but probably won't go into core before the next minor release?

zaporylie’s picture

I'm also not sure about the performance impact - the code doesn't seem to run in the critical path (does it?). All API additions we may wanna add are soon to be deprecated by #2336597: Convert path aliases to full featured entities. All that makes me wonder - wouldn't be enough to just leave a comment and create the followup to refactor this part once #2336597 gets in?

amateescu’s picture

Status: Needs work » Reviewed & tested by the community

Agreed with #51, I think the current patch is fine for 8.6.x given that we know that it can be improved in 8.7.x.

catch’s picture

Is there an issue open for the 8.7.x follow-up? I think it's OK for this to go in as a bugfix to both versions then optimise to a single query there.

zaporylie’s picture

  • catch committed 7758b75 on 8.7.x
    Issue #2511968 by Berdir, jhedstrom, zaporylie, catch, dawehner,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed 7758b75 to 8.7.x and cherry-picked to 8.6.x. Thanks!

  • catch committed a6005f8 on 8.6.x
    Issue #2511968 by Berdir, jhedstrom, zaporylie, catch, dawehner,...
Berdir’s picture

That was fast ;)

> I'm also not sure about the performance impact - the code doesn't seem to run in the critical path (does it?

I'm not 100% sure about that. I did just check that at least by default, it doesn't do that, but because this is an implicit load of the field, all it takes is some code somehow accessing it to trigger that.

But fine :)

I did comment in the follow-up that I disagree with @amateescu about the entity query, though :)

Status: Fixed » Closed (fixed)

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