[TASK] Streamline page language linking 01/58101/7
authorOliver Hader <oliver@typo3.org>
Fri, 31 Aug 2018 09:59:17 +0000 (11:59 +0200)
committerFrans Saris <franssaris@gmail.com>
Fri, 31 Aug 2018 11:53:58 +0000 (13:53 +0200)
When having a Site configuration in order to create a page link for
a specific language new property `language` has to be used instead
of previous `additionalParams=&L=1`. Since it might not be known in
all cases whether the defined TypoScript is used inside or outside
a valid Site configuration, `language` settings now take precedence
and override legacy L-parameter.

Besides that, linking to localized pages was not possible and is
resolved to their language parent pages. The new `language` setting
will be adjusted at the same time as well.

Resolves: #86058
Releases: master
Change-Id: I9531a14f8aa5913a03fdac9fcbdaced57312c2af
Reviewed-on: https://review.typo3.org/58101
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LinkGeneratorTest.php

index 1d47edb..90aa362 100644 (file)
@@ -62,12 +62,14 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
         }
 
         // Looking up the page record to verify its existence:
-        $page = $tsfe->sys_page->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
+        $page = $this->resolvePage($tsfe->sys_page, $linkDetails, $conf, $disableGroupAccessCheck);
 
         if (empty($page)) {
             throw new UnableToLinkException('Page id "' . $linkDetails['typoLinkParameter'] . '" was not found, so "' . $linkText . '" was not linked.', 1490987336, null, $linkText);
         }
-        $language = (int)$page[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
+
+        $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null;
+        $language = (int)($page[$languageField] ?? 0);
         if ($language === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) {
             throw new UnableToLinkException('Default language of page  "' . $linkDetails['typoLinkParameter'] . '" is hidden, so "' . $linkText . '" was not linked.', 1529527301, null, $linkText);
         }
@@ -241,6 +243,47 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
     }
 
     /**
+     * Resolves page and if a translated page was found, resolves that to it
+     * language parent, adjusts `$linkDetails['pageuid']` (for hook processing)
+     * and modifies `$configuration['language']` (for language URL generation).
+     *
+     * @param PageRepository $pageRepository
+     * @param array $linkDetails
+     * @param array $configuration
+     * @param bool $disableGroupAccessCheck
+     * @return array
+     */
+    protected function resolvePage(PageRepository $pageRepository, array &$linkDetails, array &$configuration, bool $disableGroupAccessCheck): array
+    {
+        // Looking up the page record to verify its existence:
+        $page = $pageRepository->getPage($linkDetails['pageuid'], $disableGroupAccessCheck);
+
+        if (empty($page) || !is_array($page)) {
+            return [];
+        }
+
+        $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null;
+        $languageParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] ?? null;
+        $language = (int)($page[$languageField] ?? 0);
+
+        if ($language <= 0 || empty($page[$languageParentField])) {
+            return $page;
+        }
+
+        $languageParentPage = $pageRepository->getPage(
+            $page[$languageParentField],
+            $disableGroupAccessCheck
+        );
+        if (empty($languageParentPage)) {
+            return $page;
+        }
+
+        $linkDetails['pageuid'] = (int)$languageParentPage['uid'];
+        $configuration['language'] = $language;
+        return $languageParentPage;
+    }
+
+    /**
      * Create a UriInterface object when linking to a page with a site configuration
      *
      * @param array $page
@@ -315,6 +358,20 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
      */
     protected function generateUrlForPageWithoutSiteConfiguration(array $page, string $additionalQueryParams, array $conf, string $pageType, string $sectionMark, string $target, array $MPvarAcc): array
     {
+        // new 'language' property takes precedence over '&L=1' if numeric
+        // here 'additionalParams=&L={language-value}' will be overridden
+        if (MathUtility::canBeInterpretedAsInteger($conf['language'] ?? '')) {
+            $queryParameters = [];
+            parse_str($additionalQueryParams, $queryParameters);
+            $queryParameters['L'] = $conf['language'];
+            $additionalQueryParams = http_build_query(
+                $queryParameters,
+                '',
+                '&',
+                PHP_QUERY_RFC3986
+            );
+        }
+
         $tsfe = $this->getTypoScriptFrontendController();
         $enableLinksAcrossDomains = $tsfe->config['config']['typolinkEnableLinksAcrossDomains'];
         $targetDomain = '';
index 3ae1eb2..db6fd19 100644 (file)
@@ -279,8 +279,8 @@ class LinkGeneratorTest extends AbstractTestCase
             ['https://acme.us/', 1100, 1100, 0, '/?id=1100'],
             ['https://acme.us/', 1100, 1100, 1, '/?id=1100'],
             ['https://acme.us/', 1100, 1100, 2, '/?id=1100'],
-            ['https://acme.us/', 1100, 1101, 0, '/?id=1100'], // @todo Language missing
-            ['https://acme.us/', 1100, 1102, 0, '/?id=1100'], // @todo Language missing
+            ['https://acme.us/', 1100, 1101, 0, 'https://acme.fr/?id=1100'],
+            ['https://acme.us/', 1100, 1102, 0, 'https://acme.ca/?id=1100'],
             // acme.com -> products.acme.com (nested sub-site)
             ['https://acme.us/', 1100, 1300, 0, 'https://products.acme.com/?id=1300'],
             ['https://acme.us/', 1100, 1310, 0, 'https://products.acme.com/?id=1310'],
@@ -288,20 +288,20 @@ class LinkGeneratorTest extends AbstractTestCase
             ['https://acme.us/', 1100, 3100, 0, '/index.php?id=3100&L=0'],
             ['https://acme.us/', 1100, 3100, 1, '/index.php?id=3100&L=1'],
             ['https://acme.us/', 1100, 3100, 2, '/index.php?id=3100&L=2'],
-            ['https://acme.us/', 1100, 3101, 0, '/index.php?id=3101&L=0'],
-            ['https://acme.us/', 1100, 3102, 0, '/index.php?id=3102&L=0'],
+            ['https://acme.us/', 1100, 3101, 0, '/index.php?id=3100&L=1'],
+            ['https://acme.us/', 1100, 3102, 0, '/index.php?id=3100&L=2'],
             // blog.acme.com -> acme.com (different site)
             ['https://blog.acme.com/', 2100, 1100, 0, 'https://acme.us/?id=1100'],
             ['https://blog.acme.com/', 2100, 1100, 1, 'https://acme.us/?id=1100'],
             ['https://blog.acme.com/', 2100, 1100, 2, 'https://acme.us/?id=1100'],
-            ['https://blog.acme.com/', 2100, 1101, 0, 'https://acme.us/?id=1100'], // @todo Language missing
-            ['https://blog.acme.com/', 2100, 1102, 0, 'https://acme.us/?id=1100'], // @todo Language missing
+            ['https://blog.acme.com/', 2100, 1101, 0, 'https://acme.fr/?id=1100'],
+            ['https://blog.acme.com/', 2100, 1102, 0, 'https://acme.ca/?id=1100'],
             // blog.acme.com -> archive (outside site)
             ['https://blog.acme.com/', 2100, 3100, 0, '/index.php?id=3100&L=0'],
             ['https://blog.acme.com/', 2100, 3100, 1, '/index.php?id=3100&L=1'],
             ['https://blog.acme.com/', 2100, 3100, 2, '/index.php?id=3100&L=2'],
-            ['https://blog.acme.com/', 2100, 3101, 0, '/index.php?id=3101&L=0'],
-            ['https://blog.acme.com/', 2100, 3102, 0, '/index.php?id=3102&L=0'],
+            ['https://blog.acme.com/', 2100, 3101, 0, '/index.php?id=3100&L=1'],
+            ['https://blog.acme.com/', 2100, 3102, 0, '/index.php?id=3100&L=2'],
             // blog.acme.com -> products.acme.com (different sub-site)
             ['https://blog.acme.com/', 2100, 1300, 0, 'https://products.acme.com/?id=1300'],
             ['https://blog.acme.com/', 2100, 1310, 0, 'https://products.acme.com/?id=1310'],