From f9c28d48f72632eaeba9e9d5ae6f6584e8b31bb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20B=C3=BCrk?= <stefan@buerk.tech>
Date: Sat, 20 Nov 2021 15:50:14 +0100
Subject: [PATCH] [BUGFIX] Respect '_language' argument for typoLink and
 LinkHandler

This patch fixes a bug where a t3 page URI with _language != 0
was resolved to a page in the default language. This caused
automatically generated redirects to translated pages to
redirect to the page in the default language.

We now use the same override handling for the '_language'
parameter (as already existed for 'L') when t3 page URIs are
resolved. Also, we ensure that both parameters are always removed
when the final URL is generated. Furthermore, this patch adds
tests to cover these cases.

Using the historical 'L' parameter still works and has been
added to be handled as language override.

Background information:

The '_language parameter' is the preferred parameter to
be used to specify the language in a t3 URI, not 'L'.

The behaviour of creating auto redirects on slug changes
was changed previously: The target was added as
LinkHandler URI (instead of the path). Along with this change,
the '_language' parameter was used to ensure the
target URL is generated in the needed language (#89327).

This introduced a problem where a redirect always redirected
to the page in default language if a t3: page URI was used with
'_language'.

Resolves: #96043
Related: #89327
Releases: master, 11.5, 10.4
Change-Id: I95bdeff4827eed3be30528c02331b9c9ff2836b1
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72242
Tested-by: core-ci <typo3@b13.com>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Sybille Peters <sypets@gmx.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Sybille Peters <sypets@gmx.de>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../Classes/Typolink/PageLinkBuilder.php      | 15 ++++--
 .../Fixtures/TypoLinkScenario.yaml            | 12 +++++
 .../SiteHandling/TypoLinkGeneratorTest.php    | 52 +++++++++++++++++++
 3 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
index a34ea386f8a5..1f72cfe8fbcb 100644
--- a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
@@ -163,9 +163,18 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
         // Disable "?id=", for pages with no site configuration, this is added later-on anyway
         unset($queryParameters['id']);
 
-        // Override language property if not being set already
-        if (isset($queryParameters['L']) && !isset($conf['language'])) {
-            $conf['language'] = (int)$queryParameters['L'];
+        // Override language property if not being set already, supporting historically 'L' and
+        // modern '_language' arguments, giving '_language' the precedence.
+        if (isset($queryParameters['_language'])) {
+            if (!isset($conf['language'])) {
+                $conf['language'] = (int)$queryParameters['_language'];
+            }
+            unset($queryParameters['_language']);
+        }
+        if (isset($queryParameters['L'])) {
+            if (!isset($conf['language'])) {
+                $conf['language'] = (int)$queryParameters['L'];
+            }
             unset($queryParameters['L']);
         }
 
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.yaml b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.yaml
index 2a8118593127..1a44af4419b2 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.yaml
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/TypoLinkScenario.yaml
@@ -23,17 +23,29 @@ entitySettings:
     languageColumnNames: ['l18n_parent', 'l10n_source']
     columnNames: {title: 'header', type: 'CType'}
     defaultValues: {hidden: 0, CType: 'text'}
+  language:
+    tableName: 'sys_language'
+    columnNames: {code: 'language_isocode'}
 
 entities:
+  language:
+    - self: {id: 1, title: 'French', code: 'fr'}
+    - self: {id: 2, title: 'Franco-Canadian', code: 'fr'}
   page:
     - self: {id: 1000, title: 'ACME Inc', type: *pageShortcut, shortcut: 'first', root: true, slug: '/'}
       children:
         - self: {id: 1100, title: 'EN: Welcome', slug: '/welcome', subtitle: 'hello-and-welcome'}
+          languageVariants:
+            - self: {id: 1101, title: 'FR: Bienvenue', language: 1, slug: '/bienvenue', subtitle: 'salut-et-bienvenue'}
+            - self: {id: 1102, title: 'FR-CA: Bienvenue', language: 2, slug: '/bienvenue', subtitle: 'salut-et-bienvenue'}
           entities:
             content:
               - self: {title: 'EN: Content Element #1'}
               - self: {title: 'EN: Content Element #2'}
         - self: {id: 1200, title: 'EN: Features', slug: '/features'}
+          languageVariants:
+            - self: {id: 1201, title: 'FR: Features', slug: '/features-fr', language: 1}
+            - self: {id: 1202, title: 'FR-CA: Features', slug: '/features-ca', language: 2}
         - self: {id: 1300, title: 'Go to TYPO3.org', type: *pageLink, slug: '/external', url: 'typo3.org' }
         - self: {id: 9911, title: '<good>', slug: '/test/good'}
         - self: {id: 9912, title: '<good a="a/" b="thing(1)">', slug: '/test/good-a-b-spaced'}
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php
index 47a0ef757671..7e0129849286 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php
@@ -200,6 +200,58 @@ class TypoLinkGeneratorTest extends AbstractTestCase
                 'user@example.org target class title &other=other',
                 '<a href="mailto:user@example.org" title="title" target="target" class="class">user@example.org</a>',
             ],
+            // check link with language parameters
+            [
+                't3://page?uid=1200&L=0',
+                '<a href="/features">EN: Features</a>',
+            ],
+            [
+                't3://page?uid=1200&_language=0',
+                '<a href="/features">EN: Features</a>',
+            ],
+            [
+                't3://page?uid=1200&L=1',
+                '<a href="https://acme.fr/features-fr">FR: Features</a>',
+            ],
+            [
+                't3://page?uid=1200&_language=1',
+                '<a href="https://acme.fr/features-fr">FR: Features</a>',
+            ],
+            [
+                't3://page?uid=1201&L=1',
+                '<a href="https://acme.fr/features-fr">FR: Features</a>',
+            ],
+            [
+                't3://page?uid=1201&_language=1',
+                '<a href="https://acme.fr/features-fr">FR: Features</a>',
+            ],
+            // localized page language overrule language arguments (new and old).
+            // This has also test coverage through SlugGeneratorTests.
+            [
+                't3://page?uid=1202&L=1',
+                '<a href="https://acme.ca/features-ca">FR-CA: Features</a>',
+            ],
+            [
+                't3://page?uid=1202&_language=1',
+                '<a href="https://acme.ca/features-ca">FR-CA: Features</a>',
+            ],
+            // check precedence order correctness if old and modern are provided
+            [
+                't3://page?uid=1200&L=2&_language=1',
+                '<a href="https://acme.fr/features-fr">FR: Features</a>',
+            ],
+            [
+                't3://page?uid=1200&_language=1&L=2',
+                '<a href="https://acme.fr/features-fr">FR: Features</a>',
+            ],
+            [
+                't3://page?uid=1200&L=1&_language=2',
+                '<a href="https://acme.ca/features-ca">FR-CA: Features</a>',
+            ],
+            [
+                't3://page?uid=1200&_language=2&L=1',
+                '<a href="https://acme.ca/features-ca">FR-CA: Features</a>',
+            ],
         ];
         return $this->keysFromTemplate($instructions, '%1$s;');
     }
-- 
GitLab