Currently in drupal_lookup_path() we use $language_content as a default if a language is not explictly passed. This seems a sensible choice as the (node) path alias is tied to the node language, but actually this causes a severe misbehavior: as content language is inherited from interface language, if we get it from a language provider different from URL, path aliases will stop working.

Steps to reproduce:

  1. Enable locale
  2. Add French language
  3. Enable URL and user language detection and move User on top
  4. Edit user account andt set French as preferred language
  5. Enable multilingual support for articles
  6. Create an English article with a path alias
  7. After saving the node you get a 404 page

(found while discussing #817114: Deprecate the session language detection method)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

plach’s picture

FileSize
5.78 KB

The fix is pretty obvious: use URL language as default. The attached patch provides a test that defines which the correct behavior should be.

plach’s picture

Status: Active » Needs review
Issue tags: +Quick fix

Status: Needs review » Needs work

The last submitted patch, language-855380-1.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
7.49 KB

Tests should pass now.

sun’s picture

Issue tags: -Quick fix

That's not a quick fix.

+++ modules/path/path.test	15 Jul 2010 16:24:53 -0000
@@ -242,8 +242,8 @@ class PathLanguageTestCase extends Drupa
-    $this->drupalLogin($web_user);
...
+    $this->drupalLogin($this->user);

This should be $this->web_user. I actually thought that $this->user would be a backup of the local user running simpletest.

+++ modules/path/path.test	15 Jul 2010 16:24:53 -0000
@@ -268,26 +264,26 @@ class PathLanguageTestCase extends Drupa
+    $french_alias = $this->randomName();

@@ -306,6 +302,40 @@ class PathLanguageTestCase extends Drupa
+    // Check that the French alias works.
+    $this->drupalGet("fr/$french_alias");
...
+    // Disable URL language negotiation.
...
+    // Check that the English alias still works.
+    $this->drupalGet($english_alias);
...
+    // Check that the French alias is not available.
+    $this->drupalGet($french_alias);
+    $this->assertResponse(404, t('Alias for French translation is unavailable when URL language negotiation is disabled.'));

Please bear with me. I'm a crazy old man.

1) We are testing two different URLs for the same $french_alias here. First with an additional "fr/" prefix, afterwards without. Somehow, but I've to admit that I never really checked this, I assumed that, when storing language-specific aliases, then that entire alias would be used; without path prefix, as that prefix, like the name implies, belongs to the path, not the alias...? Am I horribly mistaken?

2) This test enables both user language and URl language negotiation, preferring user language over URL language. It's configuring the user's preferred language to French. Afterwards, it disables URL language negotiation. Keeping user language negotiation enabled. Lastly, it directly visits the English node page. -- I totally thought that one of the following would happen:

a) As the user still has a preference, user language negotiation is still enabled, and there is a French translation, he gets or is redirected to the French node.

b) Basically same as a), but the test should have visited a node listing page (such as the front page), where language negotiation actually happens, so the French translation (the user's preference) actually appears.

No? Am I nuts? :)

+++ modules/path/path.test	15 Jul 2010 16:24:53 -0000
@@ -306,6 +302,40 @@ class PathLanguageTestCase extends Drupa
+    // Confirm that the alias works even changing language negotiaion options.

1) Missing "when" after "even".

2) Typo in "negotiation".

Powered by Dreditor.

plach’s picture

FileSize
7.51 KB

@sun:

1) We are testing two different URLs for the same $french_alias here. First with an additional "fr/" prefix, afterwards without. Somehow, but I've to admit that I never really checked this, I assumed that, when storing language-specific aliases, then that entire alias would be used; without path prefix, as that prefix, like the name implies, belongs to the path, not the alias...? Am I horribly mistaken?

It's correct. The alias is stored without the prefix. We check the unprefixed version because we disabled URL language one moment before. In this situation (as in D6) only english and language neutral aliases (should) keep working.

I totally thought that one of the following would happen:
a) As the user still has a preference, user language negotiation is still enabled, and there is a French translation, he gets or is redirected to the French node.

If we take into account language negotiation for paths when URL language is disabled the problem described in the OP may occur: the french language is selected for UI an content, english is selected for URLs (as no prefix is found we fallback to the default language); if we pick the content language we have no way to check that the path alias is valid: there is no path alias for french matching the english alias, so we can't get the english node and we can't get its french translation. So we have to pick URL language.

By the way, if I'm not mistaken, nowhere in core we perform redirects based on language negotiation values.

b) Basically same as a), but the test should have visited a node listing page (such as the front page), where language negotiation actually happens, so the French translation (the user's preference) actually appears.

This way we wouldn't explictly check that the french alias is not recognized (I'm using D6 behavior as reference).

andypost’s picture

subscribe

plach’s picture

FileSize
8.53 KB

Updated the url() phpdoc to match the switch to $language_url.

sun’s picture

Thanks for the thorough explanation!

+++ includes/path.inc	15 Jul 2010 18:40:31 -0000
@@ -71,7 +71,7 @@ function drupal_lookup_path($action, $pa
-  $path_language = $path_language ? $path_language : $language_content->language;
+  $path_language = $path_language ? $path_language : $language_url->language;

Given that this is a critical issue and contains quite some explanation and reasoning, it would be good to transfer the major information building blocks into a in-code comment, explaining why the fallback is $language_url and not something else, so as to prevent breaking this again in the future.

+++ modules/path/path.test	15 Jul 2010 19:06:41 -0000
@@ -306,6 +302,40 @@ class PathLanguageTestCase extends Drupa
+    // Check that the French alias is not available.
+    $this->drupalGet($french_alias);

Let's append your explanation to the comment:

"Check the unprefixed alias, because we disabled URL language negotiation above. In this situation, only English and language neutral aliases should keep working."

(although... "English"...? Did you mean the site's default language?)

Furthermore, let's additionally append:

"If URL language negotiation is disabled, then French will be the active UI and content language (the user's preference), and English the active URL language, as no prefix was found and we fall back to the site's default language. [NOTE: needs tweaking:] In this case, drupal_lookup_path() needs to use the URL language to check whether a URL alias is valid."

[TWEAK WITH THIS: if we pick the content language we have no way to check that the path alias is valid: there is no path alias for french matching the english alias, so we can't get the english node and we can't get its french translation. So we have to pick URL language.]

46 critical left. Go review some!

plach’s picture

FileSize
9.56 KB

Here it is. Hope the comments are good.

Status: Needs review » Needs work

The last submitted patch, language-855380-10.patch, failed testing.

plach’s picture

Status: Needs work » Needs review

Only comments added since tests passed and I can't see the tests failing on my box.

#10: language-855380-10.patch queued for re-testing.

plach’s picture

@sun: is #10 ok?

sun’s picture

Status: Needs review » Reviewed & tested by the community

Awesome! Much much better now. With these, no one needs to read this issue to understand what's going on.

andypost’s picture

Dries’s picture

Status: Reviewed & tested by the community » Fixed

This looks good and the code comments make it easy to grok. Great work. Committed to CVS HEAD.

Status: Fixed » Closed (fixed)

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

donquixote’s picture