[TASK] Add LanguageAspect to Contexts 24/57424/24
authorBenni Mack <benni@typo3.org>
Sun, 1 Jul 2018 12:46:04 +0000 (14:46 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Fri, 13 Jul 2018 18:39:14 +0000 (20:39 +0200)
This change adds a new Aspect called "Language" which bundles
most existing TypoScript options on frontend language fetching.
A factory allows to migrate the logic from TSFE.

Major / Important Changes:
- RootlineUtility is decoupled from PageRepository
- TSFE->sys_language_mode is not necessary anymore
- TSFE->sys_language_contentOL can be substituted by "overlayType" of Aspect
- A new PageRepository->getLanguageOverlay() bundles all other methods
- A lot of common public properties in TSFE are now deprecated

Next steps:
- Isolate calls from RootlineUtility/PageRepository more from each other and from TSFE
- Migrate PageRepository "-1" parameters to "null"
- Migrate usages PageRepository->getRootline() to RootlineUtility::__construct()
- Then deprecate PageRepository->getRootLine()
- Migrate Extbase QuerySettings to Contexts

Resolves: #85543
Releases: master
Change-Id: I8d177222a244a8d1fd66a884e9fc50b107f27e20
Reviewed-on: https://review.typo3.org/57424
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
27 files changed:
typo3/sysext/adminpanel/Classes/Service/EditToolbarService.php
typo3/sysext/core/Classes/Context/Context.php
typo3/sysext/core/Classes/Context/LanguageAspect.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/LanguageAspectFactory.php [new file with mode: 0644]
typo3/sysext/core/Classes/FrontendEditing/FrontendEditingController.php
typo3/sysext/core/Classes/Utility/RootlineUtility.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-85389-ContextAPIForConsistentDataHandling.rst
typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript
typo3/sysext/core/Tests/Unit/Utility/RootlineUtilityTest.php
typo3/sysext/extbase/Classes/Persistence/Generic/Typo3QuerySettings.php
typo3/sysext/extbase/Classes/Service/ExtensionService.php
typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php
typo3/sysext/frontend/Classes/Aspect/FileMetadataOverlayAspect.php
typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Classes/ContentObject/FilesContentObject.php
typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php
typo3/sysext/frontend/Classes/ContentObject/RecordsContentObject.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Page/PageRepository.php
typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php
typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/indexed_search/Classes/Controller/SearchController.php
typo3/sysext/indexed_search/Classes/Indexer.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php

index 8f7647b..b35d409 100644 (file)
@@ -3,9 +3,24 @@ declare(strict_types = 1);
 
 namespace TYPO3\CMS\Adminpanel\Service;
 
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\Imaging\Icon;
@@ -30,13 +45,15 @@ class EditToolbarService
      */
     public function createToolbar(): string
     {
+        /** @var LanguageAspect $languageAspect */
+        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
         $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
         $tsfe = $this->getTypoScriptFrontendController();
         //  If mod.newContentElementWizard.override is set, use that extension's create new content wizard instead:
         $moduleName = BackendUtility::getPagesTSconfig($tsfe->page['uid'])['mod.']['newContentElementWizard.']['override'] ?? 'new_content_element';
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
         $perms = $this->getBackendUser()->calcPerms($tsfe->page);
-        $langAllowed = $this->getBackendUser()->checkLanguageAccess($tsfe->sys_language_uid);
+        $langAllowed = $this->getBackendUser()->checkLanguageAccess($languageAspect->getId());
         $id = $tsfe->id;
         $returnUrl = GeneralUtility::getIndpEnv('REQUEST_URI');
         $classes = 'typo3-adminPanel-btn typo3-adminPanel-btn-default';
@@ -69,8 +86,8 @@ class EditToolbarService
                 'id' => $id,
                 'returnUrl' => $returnUrl,
             ];
-            if (!empty($tsfe->sys_language_uid)) {
-                $linkParameters['sys_language_uid'] = $tsfe->sys_language_uid;
+            if (!empty($languageAspect->getId())) {
+                $linkParameters['sys_language_uid'] = $languageAspect->getId();
             }
             $link = (string)$uriBuilder->buildUriFromRoute($moduleName, $linkParameters);
             $icon = $iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render();
@@ -156,7 +173,7 @@ class EditToolbarService
         }
 
         // Edit Page Overlay
-        if ($perms & Permission::PAGE_EDIT && $tsfe->sys_language_uid && $langAllowed) {
+        if ($perms & Permission::PAGE_EDIT && $languageAspect->getId() > 0 && $langAllowed) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                 ->getQueryBuilderForTable('pages');
             $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
@@ -170,7 +187,7 @@ class EditToolbarService
                     ),
                     $queryBuilder->expr()->eq(
                         $GLOBALS['TCA']['pages']['ctrl']['languageField'],
-                        $queryBuilder->createNamedParameter($tsfe->sys_language_uid, \PDO::PARAM_INT)
+                        $queryBuilder->createNamedParameter($languageAspect->getId(), \PDO::PARAM_INT)
                     )
                 )
                 ->setMaxResults(1)
index 1678cda..8ea437a 100644 (file)
@@ -43,6 +43,7 @@ use TYPO3\CMS\Core\SingletonInterface;
  * - visibility
  * - frontend.user
  * - backend.user
+ * - language
  */
 class Context implements SingletonInterface
 {
@@ -79,6 +80,9 @@ class Context implements SingletonInterface
         if (!$this->hasAspect('workspace')) {
             $this->setAspect('workspace', new WorkspaceAspect());
         }
+        if (!$this->hasAspect('language')) {
+            $this->setAspect('language', new LanguageAspect());
+        }
     }
 
     /**
diff --git a/typo3/sysext/core/Classes/Context/LanguageAspect.php b/typo3/sysext/core/Classes/Context/LanguageAspect.php
new file mode 100644 (file)
index 0000000..19f67c4
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+
+/**
+ * The Aspect is usually available as "language" property, and
+ * can be used to find out the "overlay"/data retrieval strategy.
+ *
+ *
+ * "id" (languageId, int)
+ * - formally known as $TSFE->sys_language_uid
+ * - the requested language of the current page (frontend)
+ * - used in menus and links to generate "links in language with this ID"
+ *
+ * "contentId" (int)
+ * - formally known as $TSFE->sys_language_content
+ * - the language of records to be fetched
+ * - if empty, "languageId" is used.
+ *
+ * "fallbackChain"
+ *  - when "fallback" go with
+ *  - depends what "contentId" value should be set
+ *  - defined in config.sys_language_mode (strict/content_fallback:4,5,stop/ignore?)
+ *  - previously known as $TSFE->sys_language_mode
+ *  - defines "contentId" based on "if the current page is available in this language"
+ *   - "strict"
+ *   - "fallback" if current page is not available, check the fallbackChain"
+ *   - "fallbackAndIgnore"
+ *
+ * "overlays"
+ * - defines which way the records should be fetched from ($TSFE->sys_language_contentOL and config.sys_language_overlay)
+ * - usually you fetch language 0 and -1, then take the "contentId" and "overlay" them
+ *    - here you have two choices
+ *          1. "on" if there is no overlay, do not render the default language records ("hideNonTranslated")
+ *          2. "mixed" - if there no overlay, just keep the default language, possibility to have mixed languages - config.sys_language_overlay = 1
+ *          3. "off" - do not do overlay, only fetch records available in the current "contentId" (see above), and do not care about overlays or fallbacks - fallbacks could be an option here, actually that is placed on top
+ *          4. "includeFloating" - on + includeRecordsWithoutDefaultTranslation
+ */
+class LanguageAspect implements AspectInterface
+{
+    /**
+     * @var int
+     */
+    protected $id = 0;
+
+    /**
+     * @var int
+     */
+    protected $contentId = 0;
+
+    /**
+     * @var array
+     */
+    protected $fallbackChain = [];
+
+    /**
+     * @var string
+     */
+    protected $overlayType;
+
+    public const OVERLAYS_OFF = 'off';  // config.sys_language_overlay = 0
+    public const OVERLAYS_MIXED = 'mixed';  // config.sys_language_overlay = 1 (keep the ones that are only available in default language)
+    public const OVERLAYS_ON = 'on';    // "hideNonTranslated"
+    public const OVERLAYS_ON_WITH_FLOATING = 'includeFloating';    // "hideNonTranslated" + records that are only available in polish
+
+    /**
+     * Create the default language
+     *
+     * @param int $id
+     * @param int|null $contentId
+     * @param string $overlayType
+     * @param array $fallbackChain
+     */
+    public function __construct(int $id = 0, int $contentId = null, string $overlayType = self::OVERLAYS_ON_WITH_FLOATING, array $fallbackChain = [])
+    {
+        $this->overlayType = $overlayType;
+        $this->id = $id;
+        $this->contentId = $contentId === null ? $this->id : $contentId;
+        $this->fallbackChain = $fallbackChain;
+    }
+
+    /**
+     * Used language overlay
+     *
+     * @return string
+     */
+    public function getOverlayType(): string
+    {
+        return $this->overlayType;
+    }
+
+    /**
+     * Returns the language ID the current page was requested,
+     * this is relevant when building menus or links to other pages.
+     *
+     * @return int
+     */
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    /**
+     * Contains the language UID of the content records that should be overlaid to would be fetched.
+     * This is especially useful when a page requested with language=4 should fall back to showing
+     * content of language=2 (see fallbackChain)
+     *
+     * @return int
+     */
+    public function getContentId(): int
+    {
+        return $this->contentId;
+    }
+
+    public function getFallbackChain(): array
+    {
+        return $this->fallbackChain;
+    }
+
+    /**
+     * Whether overlays should be done
+     *
+     * @return bool
+     */
+    public function doOverlays(): bool
+    {
+        return $this->contentId > 0 && $this->overlayType !== self::OVERLAYS_OFF;
+    }
+
+    /**
+     * Previously known as TSFE->sys_language_mode, here for compatibility reasons
+     *
+     * @return string
+     */
+    public function getLegacyLanguageMode(): string
+    {
+        if ($this->fallbackChain === ['off']) {
+            return '';
+        }
+        if (empty($this->fallbackChain)) {
+            return 'strict';
+        }
+        if ($this->fallbackChain === [-1]) {
+            return 'ignore';
+        }
+        return 'content_fallback';
+    }
+
+    /**
+     * Previously known as TSFE->sys_language_contentOL, here for compatibility reasons
+     *
+     * @return string
+     */
+    public function getLegacyOverlayType(): string
+    {
+        switch ($this->overlayType) {
+            case self::OVERLAYS_ON_WITH_FLOATING:
+            case self::OVERLAYS_ON:
+                return 'hideNonTranslated';
+            case self::OVERLAYS_MIXED:
+                return '1';
+            case self::OVERLAYS_OFF:
+            default:
+                return '0';
+        }
+    }
+
+    /**
+     * Fetch a property.
+     *
+     * @param string $name
+     * @return int|string|array
+     * @throws AspectPropertyNotFoundException
+     */
+    public function get(string $name)
+    {
+        switch ($name) {
+            case 'id':
+                return $this->id;
+            case 'contentId':
+                return $this->contentId;
+            case 'fallbackChain':
+                return $this->fallbackChain;
+            case 'overlayType':
+                return $this->overlayType;
+            case 'legacyLanguageMode':
+                return $this->getLegacyLanguageMode();
+            case 'legacyOverlayType':
+                return $this->getLegacyOverlayType();
+        }
+        throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1530448504);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Context/LanguageAspectFactory.php b/typo3/sysext/core/Classes/Context/LanguageAspectFactory.php
new file mode 100644 (file)
index 0000000..a53918b
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * A simple factory to create a language aspect.
+ */
+class LanguageAspectFactory
+{
+    /**
+     * Create a language aspect based on TypoScript settings, which we know:
+     *
+     * config.sys_language_uid
+     * config.sys_language_mode
+     * config.sys_language_overlay
+     *
+     * @param array $config
+     * @return LanguageAspect
+     */
+    public static function createFromTypoScript(array $config): LanguageAspect
+    {
+        // Get values from TypoScript, if not set before
+        $languageId = (int)$config['sys_language_uid'];
+        list($fallbackMode, $fallbackOrder) = GeneralUtility::trimExplode(';', $config['sys_language_mode']);
+
+        // Page resolving
+        switch ($fallbackMode ?? '') {
+            case 'strict':
+                $fallBackOrder = [];
+                break;
+                // Ignore anything if a page cannot be found, and resolve pageId=0 instead.
+            case 'ignore':
+                $fallBackOrder = [-1];
+                break;
+            case 'fallback':
+            case 'content_fallback':
+                if (!empty($fallbackOrder)) {
+                    $fallBackOrder = GeneralUtility::trimExplode(',', $fallbackOrder);
+                    // no strict typing explictly done here
+                    if (!in_array(0, $fallBackOrder) && !in_array('pageNotFound', $fallBackOrder)) {
+                        $fallBackOrder[] = 'pageNotFound';
+                    }
+                } else {
+                    $fallBackOrder = [0];
+                }
+                break;
+            case '':
+                $fallBackOrder = ['off'];
+                break;
+            default:
+                $fallBackOrder = [0];
+        }
+
+        // Content fetching
+        switch ((string)$config['sys_language_overlay'] ?? '') {
+            case '1':
+                $overlayType = LanguageAspect::OVERLAYS_MIXED;
+                break;
+            case '0':
+                $overlayType = LanguageAspect::OVERLAYS_OFF;
+                break;
+            case 'hideNonTranslated':
+            default:
+                $overlayType = LanguageAspect::OVERLAYS_ON_WITH_FLOATING;
+        }
+
+        return GeneralUtility::makeInstance(LanguageAspect::class, $languageId, $languageId, $overlayType, $fallBackOrder);
+    }
+
+    /**
+     * Site Languages always run with overlays + floating records.
+     *
+     * @param SiteLanguage $language
+     * @return LanguageAspect
+     */
+    public static function createFromSiteLanguage(SiteLanguage $language): LanguageAspect
+    {
+        $languageId = $language->getLanguageId();
+        $fallbackType = $language->getFallbackType();
+        if ($fallbackType === 'fallback') {
+            $fallbackOrder = $language->getFallbackLanguageIds();
+            $fallbackOrder[] = 'pageNotFound';
+        } elseif ($fallbackType === 'strict') {
+            $fallbackOrder = [];
+        } else {
+            $fallbackOrder = [0];
+        }
+
+        return GeneralUtility::makeInstance(LanguageAspect::class, $languageId, $languageId, LanguageAspect::OVERLAYS_ON_WITH_FLOATING, $fallbackOrder);
+    }
+}
index eae8d2b..80996bc 100644 (file)
@@ -15,6 +15,8 @@ namespace TYPO3\CMS\Core\FrontendEditing;
  */
 
 use TYPO3\CMS\Adminpanel\View\AdminPanelView;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction;
@@ -483,21 +485,18 @@ class FrontendEditingController
     protected function allowedToEditLanguage($table, array $currentRecord)
     {
         // If no access right to record languages, return immediately
+        /** @var LanguageAspect $languageAspect */
+        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
         if ($table === 'pages') {
-            $lang = $GLOBALS['TSFE']->sys_language_uid;
+            $lang = $languageAspect->getId();
         } elseif ($table === 'tt_content') {
-            $lang = $GLOBALS['TSFE']->sys_language_content;
+            $lang = $languageAspect->getContentId();
         } elseif ($GLOBALS['TCA'][$table]['ctrl']['languageField']) {
             $lang = $currentRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
         } else {
             $lang = -1;
         }
-        if ($GLOBALS['BE_USER']->checkLanguageAccess($lang)) {
-            $languageAccess = true;
-        } else {
-            $languageAccess = false;
-        }
-        return $languageAccess;
+        return $GLOBALS['BE_USER']->checkLanguageAccess($lang);
     }
 
     /**
index bdac257..bf5d704 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Utility;
  */
 
 use Doctrine\DBAL\DBALException;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
@@ -94,11 +95,18 @@ class RootlineUtility
     /**
      * Rootline Context
      *
-     * @var \TYPO3\CMS\Frontend\Page\PageRepository
+     * @var PageRepository
      */
     protected $pageContext;
 
     /**
+     * Query context
+     *
+     * @var Context
+     */
+    protected $context;
+
+    /**
      * @var string
      */
     protected $cacheIdentifier;
@@ -111,34 +119,27 @@ class RootlineUtility
     /**
      * @param int $uid
      * @param string $mountPointParameter
-     * @param \TYPO3\CMS\Frontend\Page\PageRepository $context
+     * @param PageRepository|Context $context - @deprecated PageRepository is used until TYPO3 v9.4, but now the third parameter should be a Context object
      * @throws \RuntimeException
      */
-    public function __construct($uid, $mountPointParameter = '', PageRepository $context = null)
+    public function __construct($uid, $mountPointParameter = '', $context = null)
     {
         $this->pageUid = (int)$uid;
         $this->mountPointParameter = trim($mountPointParameter);
-        if ($context === null) {
-            if (isset($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE']->sys_page)) {
-                $this->pageContext = $GLOBALS['TSFE']->sys_page;
-            } else {
-                $this->pageContext = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
-            }
-        } else {
+        if ($context instanceof PageRepository) {
+            trigger_error('Calling RootlineUtility with PageRepository as third parameter will be unsupported with TYPO3 v10.0. Use a Context object directly', E_USER_DEPRECATED);
             $this->pageContext = $context;
+            $this->context = GeneralUtility::makeInstance(Context::class);
+        } else {
+            if (!($context instanceof Context)) {
+                $context = GeneralUtility::makeInstance(Context::class);
+            }
+            $this->context = $context;
+            $this->pageContext = GeneralUtility::makeInstance(PageRepository::class, $context);
         }
-        $this->initializeObject();
-    }
 
-    /**
-     * Initialize a state to work with
-     *
-     * @throws \RuntimeException
-     */
-    protected function initializeObject()
-    {
-        $this->languageUid = (int)$this->pageContext->sys_language_uid;
-        $this->workspaceUid = (int)$this->pageContext->versioningWorkspaceId;
+        $this->languageUid = $this->context->getPropertyFromAspect('language', 'id', 0);
+        $this->workspaceUid = $this->context->getPropertyFromAspect('workspace', 'id', 0);
         if ($this->mountPointParameter !== '') {
             if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['enable_mount_pids']) {
                 throw new \RuntimeException('Mount-Point Pages are disabled for this installation. Cannot resolve a Rootline for a page with Mount-Points', 1343462896);
@@ -394,9 +395,7 @@ class RootlineUtility
         if ($parentUid > 0) {
             // Get rootline of (and including) parent page
             $mountPointParameter = !empty($this->parsedMountPointParameters) ? $this->mountPointParameter : '';
-            /** @var $rootline \TYPO3\CMS\Core\Utility\RootlineUtility */
-            $rootline = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Utility\RootlineUtility::class, $parentUid, $mountPointParameter, $this->pageContext);
-            $rootline = $rootline->get();
+            $rootline = GeneralUtility::makeInstance(self::class, $parentUid, $mountPointParameter, $this->context)->get();
             // retrieve cache tags of parent rootline
             foreach ($rootline as $entry) {
                 $cacheTags[] = 'pageId_' . $entry['uid'];
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst
new file mode 100644 (file)
index 0000000..260d5d6
--- /dev/null
@@ -0,0 +1,58 @@
+.. include:: ../../Includes.txt
+
+====================================================================================================
+Deprecation: #85543 - Language-related properties in TypoScriptFrontendController and PageRepository
+====================================================================================================
+
+See :issue:`85543`
+
+Description
+===========
+
+With the introduction of a LanguageAspect within the new Context API, the following public properties
+have been marked as deprecated:
+
+* :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_uid`
+* :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_content`
+* :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_contentOL`
+* :php:`TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_mode`
+* :php:`TYPO3\CMS\Frontend\Page\PageRepository->sys_language_uid`
+
+Additionally, in order to create a better abstraction, the third constructor argument of
+:php:`TYPO3\CMS\Core\Utility\RootlineUtility` now expects a :php:`Context` object instead of a :php:`PageRepository`.
+
+Impact
+======
+
+Accessing or setting one of the properties will trigger a deprecation message.
+
+Calling RootlineUtility constructor with a PageRepository as a third argument will trigger a deprecation message.
+
+
+Affected Installations
+======================
+
+Any multi-lingual TYPO3 installations with custom non-Extbase-related PHP code.
+
+
+Migration
+=========
+
+Use the new :php:`LanguageAspect` with various superior properties to access the various values.
+
+.. code-block:: php
+
+       $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language')
+       // (previously known as TSFE->sys_language_uid)
+       $languageAspect->getId();
+       // (previously known as TSFE->sys_language_content)
+       $languageAspect->getContentId();
+       // (previously known as TSFE->sys_language_contentOL)
+       $languageAspect->getLegacyOverlayType();
+       // (previously known as TSFE->sys_language_mode)
+       $languageAspect->getLegacyLanguageMode();
+
+Also, have a detailed look on what other properties the language aspect offers for creating fallback chains,
+and more sophisticated overlays.
+
+.. index:: Frontend, PHP-API, FullyScanned, ext:frontend
index b572c3e..098d8a4 100644 (file)
@@ -58,6 +58,7 @@ The new Context API replaces lots of places known for a very long time:
 * DateTimeAspect replaces :php:`$GLOBALS['SIM_EXEC_TIME']` and :php:`$GLOBALS['EXEC_TIME']`
 * VisibilityAspect replaces :php:`$GLOBALS['TSFE']->showHiddenPages` and :php:`$GLOBALS['TSFE']->showHiddenRecords`
 * WorkspaceAspect replaces :php:`$GLOBALS['BE_USER']->workspace`
+* LanguageAspect replaces various properties related to language Id, overlay and fallback logic, mostly within Frontend
 * UserAspect replaces various calls and checks on :php:`$GLOBALS['BE_USER']` and :php:`$GLOBALS['TSFE']->fe_user`
 options when only some information is needed.
 
@@ -69,6 +70,7 @@ TYPO3 Core comes with the following Aspects within the global context:
 * backend.user
 * workspace
 * visibility
+* language
 
 Usage
 =====
@@ -115,7 +117,7 @@ Further development
 ===================
 
 There will be additional aspects that will be introduced in TYPO3 Core. Also see PSR-15 middlewares shipped with TYPO3
-Frontend or Backend to see how aspects can be modified.
+Frontend or Backend to see how aspects can be modified and set.
 
 Aspects eventually will become the successor of Database Restrictions, as they contain all information
 necessary to restrict a database query.
index f930203..20a954e 100644 (file)
@@ -246,10 +246,10 @@ page {
                                title.data = page:title
                        }
                        tsfe.children {
-                               sys_language_uid.data = tsfe:sys_language_uid
-                               sys_language_mode.data = tsfe:sys_language_mode
-                               sys_language_content.data = tsfe:sys_language_content
-                               sys_language_contentOL.data = tsfe:sys_language_contentOL
+                               sys_language_uid.data = context:language:id
+                               sys_language_mode.data = context:language:legacyLanguageMode
+                               sys_language_content.data = context:language:contentId
+                               sys_language_contentOL.data = context:language:legacyOverlayType
                        }
                }
                stdWrap.postUserFunc.as = Scope
index 2ff8871..9658cf3 100644 (file)
@@ -19,6 +19,9 @@ namespace TYPO3\CMS\Core\Tests\Unit\Utility;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
 use TYPO3\CMS\Frontend\Page\PageRepository;
@@ -36,11 +39,6 @@ class RootlineUtilityTest extends UnitTestCase
     protected $subject;
 
     /**
-     * @var PageRepository|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $pageContextMock;
-
-    /**
      * @throws \ReflectionException
      * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
      */
@@ -51,11 +49,10 @@ class RootlineUtilityTest extends UnitTestCase
         $cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
         $cacheManagerProphecy->getCache('cache_rootline')->willReturn($cacheFrontendProphecy->reveal());
 
-        $this->pageContextMock = $this->createMock(PageRepository::class);
         $this->subject = $this->getAccessibleMock(
             RootlineUtility::class,
             ['enrichWithRelationFields'],
-            [1, '', $this->pageContextMock]
+            [1, '', new Context()]
         );
     }
 
@@ -87,7 +84,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function isMountedPageWithoutMountPointsReturnsFalse(): void
     {
-        $this->subject->__construct(1, '', $this->pageContextMock);
+        $this->subject->__construct(1, '', new Context());
         $this->assertFalse($this->subject->isMountedPage());
     }
 
@@ -96,7 +93,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function isMountedPageWithMatchingMountPointParameterReturnsTrue(): void
     {
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $this->assertTrue($this->subject->isMountedPage());
     }
 
@@ -105,7 +102,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function isMountedPageWithNonMatchingMountPointParameterReturnsFalse(): void
     {
-        $this->subject->__construct(1, '99-99', $this->pageContextMock);
+        $this->subject->__construct(1, '99-99', new Context());
         $this->assertFalse($this->subject->isMountedPage());
     }
 
@@ -117,7 +114,7 @@ class RootlineUtilityTest extends UnitTestCase
         $this->expectException(\RuntimeException::class);
         $this->expectExceptionCode(1343464100);
 
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -130,7 +127,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageWithMountedPageNotThrowsException(): void
     {
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $this->assertNotEmpty($this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -143,7 +140,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageWithMountedPageAddsMountedFromParameter(): void
     {
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $result = $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -158,7 +155,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageWithMountedPageAddsMountPointParameterToReturnValue(): void
     {
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $result = $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -173,7 +170,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageForMountPageIsOverlayAddsMountOLParameter(): void
     {
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $result = $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -188,7 +185,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageForMountPageIsOverlayAddsDataInformationAboutMountPage(): void
     {
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $result = $this->subject->_call('processMountedPage', ['uid' => 1], [
             'uid' => 99,
             'doktype' => PageRepository::DOKTYPE_MOUNTPOINT,
@@ -212,7 +209,7 @@ class RootlineUtilityTest extends UnitTestCase
             'mount_pid' => 1,
             'mount_pid_ol' => 0
         ];
-        $this->subject->__construct(1, '1-99', $this->pageContextMock);
+        $this->subject->__construct(1, '1-99', new Context());
         $result = $this->subject->_call('processMountedPage', ['uid' => 1], $mountPointPageData);
         $this->assertIsSubset($mountPointPageData, $result);
     }
@@ -308,14 +305,16 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function getCacheIdentifierContainsAllContextParameters(): void
     {
-        $this->pageContextMock->sys_language_uid = 8;
-        $this->pageContextMock->versioningWorkspaceId = 15;
-        $this->subject->__construct(42, '47-11', $this->pageContextMock);
+        $context = new Context();
+        $context->setAspect('workspace', new WorkspaceAspect(15));
+        $context->setAspect('language', new LanguageAspect(8, 8, LanguageAspect::OVERLAYS_OFF));
+        $this->subject->__construct(42, '47-11', $context);
         $this->assertSame('42_47-11_8_15', $this->subject->getCacheIdentifier());
-        $this->subject->__construct(42, '47-11', $this->pageContextMock);
+        $this->subject->__construct(42, '47-11', $context);
         $this->assertSame('42_47-11_8_15', $this->subject->getCacheIdentifier());
-        $this->pageContextMock->versioningWorkspaceId = 0;
-        $this->subject->__construct(42, '47-11', $this->pageContextMock);
+
+        $context->setAspect('workspace', new WorkspaceAspect(0));
+        $this->subject->__construct(42, '47-11', $context);
         $this->assertSame('42_47-11_8_0', $this->subject->getCacheIdentifier());
     }
 
@@ -332,9 +331,11 @@ class RootlineUtilityTest extends UnitTestCase
             '',
             false
         );
-        $this->pageContextMock->sys_language_uid = 8;
-        $this->pageContextMock->versioningWorkspaceId = 15;
-        $this->subject->__construct(42, '47-11,48-12', $this->pageContextMock);
+        $context = new Context();
+        $context->setAspect('workspace', new WorkspaceAspect(15));
+        $context->setAspect('language', new LanguageAspect(8, 8, LanguageAspect::OVERLAYS_OFF));
+
+        $this->subject->__construct(42, '47-11,48-12', $context);
         $this->assertTrue($cacheFrontendMock->isValidEntryIdentifier($this->subject->getCacheIdentifier()));
     }
 }
index 27e9cae..0e8a849 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
@@ -105,20 +107,16 @@ class Typo3QuerySettings implements QuerySettingsInterface
         if (TYPO3_MODE === 'BE' && $configurationManager->isFeatureEnabled('ignoreAllEnableFieldsInBe')) {
             $this->setIgnoreEnableFields(true);
         }
-
-        // TYPO3 CMS language defaults
-        $this->setLanguageUid(0);
-        $this->setLanguageMode(null);
-        $this->setLanguageOverlayMode(false);
-
-        // Set correct language uid for frontend handling
-        if (isset($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE'])) {
-            $this->setLanguageUid((int)$GLOBALS['TSFE']->sys_language_content);
-            $this->setLanguageOverlayMode($GLOBALS['TSFE']->sys_language_contentOL ?: false);
-            $this->setLanguageMode($GLOBALS['TSFE']->sys_language_mode ?: null);
-        } elseif ((int)GeneralUtility::_GP('L')) {
-            // Set language from 'L' parameter
-            $this->setLanguageUid((int)GeneralUtility::_GP('L'));
+        /** @var LanguageAspect $languageAspect */
+        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
+        $this->setLanguageUid($languageAspect->getContentId());
+        if ($languageAspect->getContentId()) {
+            $this->setLanguageOverlayMode($languageAspect->getLegacyOverlayType() ?: false);
+            $this->setLanguageMode($languageAspect->getLegacyLanguageMode() ?: null);
+        } else {
+            // Kept for backwards-compatibility
+            $this->setLanguageOverlayMode(false);
+            $this->setLanguageMode(null);
         }
     }
 
index d076044..9b148c7 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Extbase\Service;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
@@ -162,6 +163,7 @@ class ExtensionService implements \TYPO3\CMS\Core\SingletonInterface
         $pluginSignature = strtolower($extensionName . '_' . $pluginName);
         if ($frameworkConfiguration['view']['defaultPid'] === 'auto') {
             if (!array_key_exists($pluginSignature, $this->targetPidPluginCache)) {
+                $languageId = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('language', 'id', 0);
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                     ->getQueryBuilderForTable('tt_content');
                 $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
@@ -180,7 +182,7 @@ class ExtensionService implements \TYPO3\CMS\Core\SingletonInterface
                         ),
                         $queryBuilder->expr()->eq(
                             'sys_language_uid',
-                            $queryBuilder->createNamedParameter($GLOBALS['TSFE']->sys_language_uid, \PDO::PARAM_INT)
+                            $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
                         )
                     )
                     ->setMaxResults(2)
index dc67e49..52b973b 100644 (file)
@@ -1018,11 +1018,10 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
             $this->currentSiteLanguage = $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
         } else {
             $pageId = 0;
-            $languageId = 0;
+            $languageId = (int)GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('language', 'id', 0);
 
             if (TYPO3_MODE === 'FE') {
                 $pageId = $this->getTypoScriptFrontendController()->id;
-                $languageId = $this->getTypoScriptFrontendController()->sys_language_uid;
             }
 
             $fakeSiteConfiguration = [
index b313fd3..290ab41 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Frontend\Aspect;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
 /**
  * Class FileMetadataTranslationAspect
  *
@@ -38,23 +41,14 @@ class FileMetadataOverlayAspect
             return;
         }
         $overlaidMetaData = $data->getArrayCopy();
-        $this->getTsfe()->sys_page->versionOL('sys_file_metadata', $overlaidMetaData);
-        $overlaidMetaData = $this->getTsfe()->sys_page->getRecordOverlay(
+        $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
+        $pageRepository->versionOL('sys_file_metadata', $overlaidMetaData);
+        $overlaidMetaData = $pageRepository->getLanguageOverlay(
             'sys_file_metadata',
-            $overlaidMetaData,
-            $this->getTsfe()->sys_language_content,
-            $this->getTsfe()->sys_language_contentOL
+            $overlaidMetaData
         );
         if ($overlaidMetaData !== null) {
             $data->exchangeArray($overlaidMetaData);
         }
     }
-
-    /**
-     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
-     */
-    protected function getTsfe()
-    {
-        return $GLOBALS['TSFE'];
-    }
 }
index f325e58..4646716 100644 (file)
@@ -14,10 +14,12 @@ namespace TYPO3\CMS\Frontend\Category\Collection;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Page\PageRepository;
 
 /**
  * Extend category collection for the frontend, to collect related records
@@ -105,7 +107,9 @@ class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCol
 
         $queryBuilder = $this->getCollectedRecordsQueryBuilder();
         $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
-        $tsfe = self::getTypoScriptFrontendController();
+        $context = GeneralUtility::makeInstance(Context::class);
+        $pageRepository = GeneralUtility::makeInstance(PageRepository::class, $context);
+        $languageId = $context->getPropertyFromAspect('language', 'contentId', 0);
 
         // If language handling is defined for item table, add language condition
         if (isset($GLOBALS['TCA'][$this->getItemTableName()]['ctrl']['languageField'])) {
@@ -122,7 +126,7 @@ class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCol
             );
 
             // If not in default language, also consider items in current language with no original
-            if ($tsfe->sys_language_content > 0) {
+            if ($languageId > 0) {
                 $transOrigPointerField = sprintf(
                     '%s.%s',
                     $this->getItemTableName(),
@@ -134,7 +138,7 @@ class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCol
                     $queryBuilder->expr()->andX(
                         $queryBuilder->expr()->eq(
                             $languageField,
-                            $queryBuilder->createNamedParameter($tsfe->sys_language_content, \PDO::PARAM_INT)
+                            $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
                         ),
                         $queryBuilder->expr()->eq(
                             $transOrigPointerField,
@@ -152,23 +156,14 @@ class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCol
 
         while ($record = $result->fetch()) {
             // Overlay the record for workspaces
-            $tsfe->sys_page->versionOL(
+            $pageRepository->versionOL(
                 $this->getItemTableName(),
                 $record
             );
 
             // Overlay the record for translations
-            if (is_array($record) && $tsfe->sys_language_contentOL) {
-                if ($this->getItemTableName() === 'pages') {
-                    $record = $tsfe->sys_page->getPageOverlay($record);
-                } else {
-                    $record = $tsfe->sys_page->getRecordOverlay(
-                        $this->getItemTableName(),
-                        $record,
-                        $tsfe->sys_language_content,
-                        $tsfe->sys_language_contentOL
-                    );
-                }
+            if (is_array($record)) {
+                $record = $pageRepository->getLanguageOverlay($this->getItemTableName(), $record);
             }
 
             // Record may have been unset during the overlay process
@@ -179,14 +174,4 @@ class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCol
 
         return $relatedRecords;
     }
-
-    /**
-     * Gets the TSFE object.
-     *
-     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
-     */
-    protected static function getTypoScriptFrontendController()
-    {
-        return $GLOBALS['TSFE'];
-    }
 }
index 5af917b..c2c3d2e 100644 (file)
@@ -20,6 +20,7 @@ use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -6391,20 +6392,11 @@ class ContentObjectRenderer implements LoggerAwareInterface
             $tsfe->sys_page->versionOL($tableName, $row, true);
 
             // Language overlay:
-            if (is_array($row) && $tsfe->sys_language_contentOL) {
-                if ($tableName === 'pages') {
-                    $row = $tsfe->sys_page->getPageOverlay($row);
-                } else {
-                    $row = $tsfe->sys_page->getRecordOverlay(
-                        $tableName,
-                        $row,
-                        $tsfe->sys_language_content,
-                        $tsfe->sys_language_contentOL
-                    );
-                }
+            if (is_array($row)) {
+                $row = $tsfe->sys_page->getLanguageOverlay($tableName, $row);
             }
 
-            // Might be unset in the sys_language_contentOL
+            // Might be unset in the language overlay
             if (is_array($row)) {
                 $records[] = $row;
             }
@@ -6802,9 +6794,10 @@ class ContentObjectRenderer implements LoggerAwareInterface
 
         if (!empty($languageField)) {
             // The sys_language record UID of the content of the page
-            $sys_language_content = (int)$tsfe->sys_language_content;
+            /** @var LanguageAspect $languageAspect */
+            $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
 
-            if ($tsfe->sys_language_contentOL && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
+            if ($languageAspect->doOverlays() && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
                 // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
                 // OVERLAY the records with localized versions!
                 $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
@@ -6817,12 +6810,12 @@ class ContentObjectRenderer implements LoggerAwareInterface
                         $languageQuery,
                         $expressionBuilder->andX(
                             $expressionBuilder->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0),
-                            $expressionBuilder->eq($languageField, $sys_language_content)
+                            $expressionBuilder->eq($languageField, $languageAspect->getContentId())
                         )
                     );
                 }
             } else {
-                $languageQuery = $expressionBuilder->eq($languageField, $sys_language_content);
+                $languageQuery = $expressionBuilder->eq($languageField, $languageAspect->getContentId());
             }
             $constraints[] = $languageQuery;
         }
index 289ef93..59b0927 100644 (file)
@@ -189,15 +189,8 @@ class FilesContentObject extends AbstractContentObject
             $element = $pageRepository->getRawRecord($referencesForeignTable, $referencesForeignUid);
 
             $pageRepository->versionOL($referencesForeignTable, $element, true);
-            if ($referencesForeignTable === 'pages') {
-                $element = $pageRepository->getPageOverlay($element);
-            } else {
-                $element = $pageRepository->getRecordOverlay(
-                    $referencesForeignTable,
-                    $element,
-                    $GLOBALS['TSFE']->sys_language_content,
-                    $GLOBALS['TSFE']->sys_language_contentOL
-                );
+            if (is_array($element)) {
+                $element = $pageRepository->getLanguageOverlay($referencesForeignTable, $element);
             }
         }
 
index 5b40a33..9690f92 100644 (file)
@@ -15,6 +15,8 @@ namespace TYPO3\CMS\Frontend\ContentObject\Menu;
  */
 
 use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\Site\Entity\Site;
@@ -669,6 +671,8 @@ abstract class AbstractMenuContentObject
 
         $tsfe->register['languages_HMENU'] = implode(',', $languageItems);
 
+        $currentLanguageId = $this->getCurrentLanguageAspect()->getId();
+
         foreach ($languageItems as $sUid) {
             // Find overlay record:
             if ($sUid) {
@@ -682,9 +686,9 @@ abstract class AbstractMenuContentObject
                 (!$sUid || empty($lRecs)) ||
                 !$this->conf['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
             ) {
-                $iState = $tsfe->sys_language_uid == $sUid ? 'USERDEF2' : 'USERDEF1';
+                $iState = $currentLanguageId == $sUid ? 'USERDEF2' : 'USERDEF1';
             } else {
-                $iState = $tsfe->sys_language_uid == $sUid ? 'ACT' : 'NO';
+                $iState = $currentLanguageId == $sUid ? 'ACT' : 'NO';
             }
             if ($this->conf['addQueryString']) {
                 $getVars = $this->parent_cObj->getQueryArguments(
@@ -1283,13 +1287,13 @@ abstract class AbstractMenuContentObject
         ) {
             // Checks if the default language version can be shown:
             // Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
-            $tsfe = $this->getTypoScriptFrontendController();
-            $blockPage = GeneralUtility::hideIfDefaultLanguage($data['l18n_cfg']) && (!$tsfe->sys_language_uid || $tsfe->sys_language_uid && !$data['_PAGES_OVERLAY']);
+            $languageId = $this->getCurrentLanguageAspect()->getId();
+            $blockPage = GeneralUtility::hideIfDefaultLanguage($data['l18n_cfg']) && (!$languageId || $languageId && !$data['_PAGES_OVERLAY']);
             if (!$blockPage) {
                 // Checking if a page should be shown in the menu depending on whether a translation exists:
                 $tok = true;
                 // There is an alternative language active AND the current page requires a translation:
-                if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
+                if ($languageId && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
                     if (!$data['_PAGES_OVERLAY']) {
                         $tok = false;
                     }
@@ -1298,9 +1302,8 @@ abstract class AbstractMenuContentObject
                 if ($tok) {
                     // Checking if "&L" should be modified so links to non-accessible pages will not happen.
                     if ($this->conf['protectLvar']) {
-                        $languageUid = (int)$tsfe->config['config']['sys_language_uid'];
-                        if ($languageUid && ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
-                            $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageUid);
+                        if ($languageId && ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg']))) {
+                            $olRec = $tsfe->sys_page->getPageOverlay($data['uid'], $languageId);
                             if (empty($olRec)) {
                                 // If no page translation record then page can NOT be accessed in
                                 // the language pointed to by "&L" and therefore we protect the link by setting "&L=0"
@@ -1753,7 +1756,7 @@ abstract class AbstractMenuContentObject
     {
         // Check if modification is required
         if (
-            $this->getTypoScriptFrontendController()->sys_language_uid > 0
+            $this->getCurrentLanguageAspect()->getId() > 0
             && empty($page['shortcut'])
             && !empty($page['uid'])
             && !empty($page['_PAGES_OVERLAY'])
@@ -1938,6 +1941,7 @@ abstract class AbstractMenuContentObject
         $recs = $this->sys_page->getMenu($uid, 'uid,pid,doktype,mount_pid,mount_pid_ol,nav_hide,shortcut,shortcut_mode,l18n_cfg');
         $hasSubPages = false;
         $bannedUids = $this->getBannedUids();
+        $languageId = $this->getCurrentLanguageAspect()->getId();
         foreach ($recs as $theRec) {
             // no valid subpage if the document type is excluded from the menu
             if (GeneralUtility::inList($this->doktypeExcludeList, $theRec['doktype'])) {
@@ -1950,13 +1954,13 @@ abstract class AbstractMenuContentObject
             }
             // No valid subpage if the default language should be shown and the page settings
             // are excluding the visibility of the default language
-            if (!$this->getTypoScriptFrontendController()->sys_language_uid && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
+            if (!$languageId && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'])) {
                 continue;
             }
             // No valid subpage if the alternative language should be shown and the page settings
             // are requiring a valid overlay but it doesn't exists
             $hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg']);
-            if ($this->getTypoScriptFrontendController()->sys_language_uid && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
+            if ($languageId && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
                 continue;
             }
             // No valid subpage if the subpage is banned by excludeUidList
@@ -2231,8 +2235,13 @@ abstract class AbstractMenuContentObject
         $result = [];
         while ($row = $statement->fetch()) {
             $this->sys_page->versionOL('tt_content', $row);
-            if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
-                $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL);
+            if ($this->getCurrentLanguageAspect()->doOverlays() && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) {
+                $row = $this->sys_page->getRecordOverlay(
+                    'tt_content',
+                    $row,
+                    $basePageRow['_PAGES_OVERLAY_LANGUAGE'],
+                    $this->getCurrentLanguageAspect()->getOverlayType() === LanguageAspect::OVERLAYS_MIXED ? '1' : 'hideNonTranslated'
+                );
             }
             if ($this->mconf['sectionIndex.']['type'] !== 'all') {
                 $doIncludeInSectionIndex = $row['sectionIndex'] >= 1;
@@ -2292,6 +2301,11 @@ abstract class AbstractMenuContentObject
         return $GLOBALS['TSFE'];
     }
 
+    protected function getCurrentLanguageAspect(): LanguageAspect
+    {
+        return GeneralUtility::makeInstance(Context::class)->getAspect('language');
+    }
+
     /**
      * @return TimeTracker
      */
index 6140213..979060c 100644 (file)
@@ -94,12 +94,8 @@ class RecordsContentObject extends AbstractContentObject
                         // Versioning preview
                         $GLOBALS['TSFE']->sys_page->versionOL($val['table'], $row);
                         // Language overlay
-                        if (is_array($row) && $GLOBALS['TSFE']->sys_language_contentOL) {
-                            if ($val['table'] === 'pages') {
-                                $row = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
-                            } else {
-                                $row = $GLOBALS['TSFE']->sys_page->getRecordOverlay($val['table'], $row, $GLOBALS['TSFE']->sys_language_content, $GLOBALS['TSFE']->sys_language_contentOL);
-                            }
+                        if (is_array($row)) {
+                            $row = $GLOBALS['TSFE']->sys_page->getLanguageOverlay($val['table'], $row);
                         }
                     }
                     // Might be unset during the overlay process
index 74c54a5..7a0a09f 100644 (file)
@@ -26,6 +26,8 @@ use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Charset\UnknownCharsetException;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\LanguageAspect;
+use TYPO3\CMS\Core\Context\LanguageAspectFactory;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
@@ -550,12 +552,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * (master language) - but not necessarily the content which could be falling
      * back to default (see sys_language_content)
      * @var int
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0 - use LanguageAspect->getId() instead.
      */
     public $sys_language_uid = 0;
 
     /**
      * Site language mode for content fall back.
      * @var string
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0 - use LanguageAspect->getFallbackChain() instead.
      */
     public $sys_language_mode = '';
 
@@ -563,8 +567,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * Site content selection uid (can be different from sys_language_uid if content
      * is to be selected from a fall-back language. Depends on sys_language_mode)
      * @var int
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0 - use LanguageAspect->getContentId() instead.
      */
-    public $sys_language_content = 0;
+    protected $sys_language_content = 0;
 
     /**
      * Site content overlay flag; If set - and sys_language_content is > 0 - ,
@@ -574,8 +579,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * This flag is set based on TypoScript config.sys_language_overlay setting
      *
      * @var int|string
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0 - use LanguageAspect->getOverlayType() instead.
      */
-    public $sys_language_contentOL = 0;
+    protected $sys_language_contentOL = 0;
 
     /**
      * Is set to the iso code of the sys_language_content if that is properly defined
@@ -1598,12 +1604,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         if (empty($this->page) || (int)$this->page[$GLOBALS['TCA']['pages']['ctrl']['languageField']] === 0) {
             return;
         }
-        $this->sys_language_uid = (int)$this->page[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
-        $this->sys_language_content = $this->sys_language_uid;
+        $languageId = (int)$this->page[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
         $this->page = $this->sys_page->getPage($this->page[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']]);
+        $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $languageId));
         $this->id = $this->page['uid'];
         // For common best-practice reasons, this is set, however, will be optional for new routing mechanisms
-        $this->mergingWithGetVars(['L' => $this->sys_language_uid]);
+        $this->mergingWithGetVars(['L' => $languageId]);
     }
 
     /**
@@ -2605,99 +2611,97 @@ class TypoScriptFrontendController implements LoggerAwareInterface
 
         // Get values from site language
         if ($siteLanguage) {
-            $this->sys_language_uid = ($this->sys_language_content = $siteLanguage->getLanguageId());
-            $this->sys_language_mode = $siteLanguage->getFallbackType();
-            if ($this->sys_language_mode === 'fallback') {
-                $fallbackOrder = $siteLanguage->getFallbackLanguageIds();
-                $fallbackOrder[] = 'pageNotFound';
-            }
+            $languageAspect = LanguageAspectFactory::createFromSiteLanguage($siteLanguage);
         } else {
-            // Get values from TypoScript, if not set before
-            if ($this->sys_language_uid === 0) {
-                $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']);
-            }
-            list($this->sys_language_mode, $fallbackOrder) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']);
-            if (!empty($fallbackOrder)) {
-                $fallBackOrder = GeneralUtility::trimExplode(',', $fallbackOrder);
-            } else {
-                $fallBackOrder = [0];
-            }
+            $languageAspect = LanguageAspectFactory::createFromTypoScript($this->config['config'] ?? []);
         }
 
-        $this->sys_language_contentOL = $this->config['config']['sys_language_overlay'];
+        $languageId = $languageAspect->getId();
+        $languageContentId = $languageAspect->getContentId();
+
         // If sys_language_uid is set to another language than default:
-        if ($this->sys_language_uid > 0) {
+        if ($languageAspect->getId() > 0) {
             // check whether a shortcut is overwritten by a translated page
             // we can only do this now, as this is the place where we get
             // to know about translations
-            $this->checkTranslatedShortcut();
+            $this->checkTranslatedShortcut($languageAspect->getId());
             // Request the overlay record for the sys_language_uid:
-            $olRec = $this->sys_page->getPageOverlay($this->id, $this->sys_language_uid);
+            $olRec = $this->sys_page->getPageOverlay($this->id, $languageAspect->getId());
             if (empty($olRec)) {
-                // If no OL record exists and a foreign language is asked for...
-                if ($this->sys_language_uid) {
-                    // If requested translation is not available:
-                    if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
-                        $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                            $GLOBALS['TYPO3_REQUEST'],
-                            'Page is not available in the requested language.',
-                            ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE]
-                        );
-                        $this->sendResponseAndExit($response);
-                    } else {
-                        switch ((string)$this->sys_language_mode) {
-                            case 'strict':
-                                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                                    $GLOBALS['TYPO3_REQUEST'],
-                                    'Page is not available in the requested language (strict).',
-                                    ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE_STRICT_MODE]
-                                );
-                                $this->sendResponseAndExit($response);
-                                break;
-                            case 'fallback':
-                            case 'content_fallback':
-                                // Setting content uid (but leaving the sys_language_uid) when a content_fallback
-                                // value was found.
-                                foreach ($fallBackOrder ?? [] as $orderValue) {
-                                    if ($orderValue === '0' || $orderValue === 0 || $orderValue === '') {
-                                        $this->sys_language_content = 0;
-                                        break;
-                                    }
-                                    if (MathUtility::canBeInterpretedAsInteger($orderValue) && !empty($this->sys_page->getPageOverlay($this->id, (int)$orderValue))) {
-                                        $this->sys_language_content = (int)$orderValue;
-                                        break;
-                                    }
-                                    if ($orderValue === 'pageNotFound') {
-                                        // The existing fallbacks have not been found, but instead of continuing
-                                        // page rendering with default language, a "page not found" message should be shown
-                                        // instead.
-                                        $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                                            $GLOBALS['TYPO3_REQUEST'],
-                                            'Page is not available in the requested language (fallbacks did not apply).',
-                                            ['code' => PageAccessFailureReasons::LANGUAGE_AND_FALLBACKS_NOT_AVAILABLE]
-                                        );
-                                        $this->sendResponseAndExit($response);
-                                    }
+                // If requested translation is not available:
+                if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
+                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                        $GLOBALS['TYPO3_REQUEST'],
+                        'Page is not available in the requested language.',
+                        ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE]
+                    );
+                    $this->sendResponseAndExit($response);
+                } else {
+                    switch ((string)$languageAspect->getLegacyLanguageMode()) {
+                        case 'strict':
+                            $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                                $GLOBALS['TYPO3_REQUEST'],
+                                'Page is not available in the requested language (strict).',
+                                ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE_STRICT_MODE]
+                            );
+                            $this->sendResponseAndExit($response);
+                            break;
+                        case 'fallback':
+                        case 'content_fallback':
+                            // Setting content uid (but leaving the sys_language_uid) when a content_fallback
+                            // value was found.
+                            foreach ($languageAspect->getFallbackChain() ?? [] as $orderValue) {
+                                if ($orderValue === '0' || $orderValue === 0 || $orderValue === '') {
+                                    $languageContentId = 0;
+                                    break;
                                 }
-                                break;
-                            case 'ignore':
-                                $this->sys_language_content = $this->sys_language_uid;
-                                break;
-                            default:
-                                // Default is that everything defaults to the default language...
-                                $this->sys_language_uid = ($this->sys_language_content = 0);
-                        }
+                                if (MathUtility::canBeInterpretedAsInteger($orderValue) && !empty($this->sys_page->getPageOverlay($this->id, (int)$orderValue))) {
+                                    $languageContentId = (int)$orderValue;
+                                    break;
+                                }
+                                if ($orderValue === 'pageNotFound') {
+                                    // The existing fallbacks have not been found, but instead of continuing
+                                    // page rendering with default language, a "page not found" message should be shown
+                                    // instead.
+                                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                                        $GLOBALS['TYPO3_REQUEST'],
+                                        'Page is not available in the requested language (fallbacks did not apply).',
+                                        ['code' => PageAccessFailureReasons::LANGUAGE_AND_FALLBACKS_NOT_AVAILABLE]
+                                    );
+                                    $this->sendResponseAndExit($response);
+                                }
+                            }
+                            break;
+                        case 'ignore':
+                            $languageContentId = $languageAspect->getId();
+                            break;
+                        default:
+                            // Default is that everything defaults to the default language...
+                            $languageId = ($languageContentId = 0);
                     }
                 }
             } else {
                 // Setting sys_language if an overlay record was found (which it is only if a language is used)
-                $this->page = $this->sys_page->getPageOverlay($this->page, $this->sys_language_uid);
+                $this->page = $this->sys_page->getPageOverlay($this->page, $languageAspect->getId());
             }
+
+            // Define the language aspect again now
+            $languageAspect = GeneralUtility::makeInstance(
+                LanguageAspect::class,
+                $languageId,
+                $languageContentId,
+                $languageAspect->getOverlayType(),
+                $languageAspect->getFallbackChain()
+            );
         }
-        // Setting sys_language_uid inside sys-page:
-        $this->sys_page->sys_language_uid = $this->sys_language_uid;
+
+        // Set the language aspect
+        $this->context->setAspect('language', $languageAspect);
+
+        // Setting sys_language_uid inside sys-page by creating a new page repository
+        $this->sys_page = GeneralUtility::makeInstance(PageRepository::class, $this->context);
         // If default translation is not available:
-        if ((!$this->sys_language_uid || !$this->sys_language_content) && GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'])) {
+        if ((!$languageAspect->getContentId() || !$languageAspect->getId()) && GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'])) {
             $message = 'Page is not available in default language.';
             $this->logger->error($message);
             $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
@@ -2707,17 +2711,20 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             );
             $this->sendResponseAndExit($response);
         }
-        $this->updateRootLinesWithTranslations();
+
+        if ($languageAspect->getId() > 0) {
+            $this->updateRootLinesWithTranslations();
+        }
 
         // Finding the ISO code for the currently selected language
         // fetched by the sys_language record when not fetching content from the default language
         if ($siteLanguage = $this->getCurrentSiteLanguage()) {
             $this->sys_language_isocode = $siteLanguage->getTwoLetterIsoCode();
-        } elseif ($this->sys_language_content > 0) {
+        } elseif ($languageAspect->getContentId() > 0) {
             // using sys_language_content because the ISO code only (currently) affect content selection from FlexForms - which should follow "sys_language_content"
             // Set the fourth parameter to TRUE in the next two getRawRecord() calls to
             // avoid versioning overlay to be applied as it generates an SQL error
-            $sys_language_row = $this->sys_page->getRawRecord('sys_language', $this->sys_language_content, 'language_isocode,static_lang_isocode');
+            $sys_language_row = $this->sys_page->getRawRecord('sys_language', $languageAspect->getContentId(), 'language_isocode,static_lang_isocode');
             if (is_array($sys_language_row) && !empty($sys_language_row['language_isocode'])) {
                 $this->sys_language_isocode = $sys_language_row['language_isocode'];
             }
@@ -2746,10 +2753,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     protected function updateRootLinesWithTranslations()
     {
-        if ($this->sys_language_uid) {
-            $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
-            $this->tmpl->updateRootlineData($this->rootLine);
-        }
+        $this->rootLine = $this->sys_page->getRootLine($this->id, $this->MP);
+        $this->tmpl->updateRootlineData($this->rootLine);
     }
 
     /**
@@ -2789,11 +2794,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * target than the original language page.
      * If that is the case, things get corrected to follow that alternative
      * shortcut
+     * @param int $languageId
      */
-    protected function checkTranslatedShortcut()
+    protected function checkTranslatedShortcut(int $languageId)
     {
         if (!is_null($this->originalShortcutPage)) {
-            $originalShortcutPageOverlay = $this->sys_page->getPageOverlay($this->originalShortcutPage['uid'], $this->sys_language_uid);
+            $originalShortcutPageOverlay = $this->sys_page->getPageOverlay($this->originalShortcutPage['uid'], $languageId);
             if (!empty($originalShortcutPageOverlay['shortcut']) && $originalShortcutPageOverlay['shortcut'] != $this->id) {
                 // the translation of the original shortcut page has a different shortcut target!
                 // set the correct page and id
@@ -4911,10 +4917,21 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function __isset(string $propertyName)
     {
         switch ($propertyName) {
+            case 'sys_language_uid':
+                trigger_error('Property $TSFE->sys_language_uid is not in use anymore as this information is now stored within the language aspect.');
+                return isset($this->$propertyName);
+            case 'sys_language_content':
+                trigger_error('Property $TSFE->sys_language_content is not in use anymore as this information is now stored within the language aspect.');
+                return isset($this->$propertyName);
+            case 'sys_language_contentOL':
+                trigger_error('Property $TSFE->sys_language_contentOL is not in use anymore as this information is now stored within the language aspect.');
+                return isset($this->$propertyName);
+            case 'sys_language_mode':
+                trigger_error('Property $TSFE->sys_language_mode is not in use anymore as this information is now stored within the language aspect.');
+                return isset($this->$propertyName);
             case 'loginUser':
                 trigger_error('Property $TSFE->loginUser is not in use anymore as this information is now stored within the frontend.user aspect.');
                 return isset($this->$propertyName);
-                break;
             case 'gr_list':
                 trigger_error('Property $TSFE->gr_list is not in use anymore as this information is now stored within the frontend.user aspect.');
                 return isset($this->$propertyName);
@@ -4943,10 +4960,22 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function __get(string $propertyName)
     {
         switch ($propertyName) {
+            case 'sys_language_uid':
+                trigger_error('Property $TSFE->sys_language_uid is not in use anymore as this information is now stored within the language aspect.');
+                return $this->context->getPropertyFromAspect('language', 'id', 0);
+            case 'sys_language_content':
+                trigger_error('Property $TSFE->sys_language_content is not in use anymore as this information is now stored within the language aspect.');
+                return $this->context->getPropertyFromAspect('language', 'contentId', 0);
+            case 'sys_language_contentOL':
+                trigger_error('Property $TSFE->sys_language_contentOL is not in use anymore as this information is now stored within the language aspect.');
+                return $this->context->getPropertyFromAspect('language', 'legacyOverlayType', '0');
+                break;
+            case 'sys_language_mode':
+                trigger_error('Property $TSFE->sys_language_mode is not in use anymore as this information is now stored within the language aspect.');
+                return $this->context->getPropertyFromAspect('language', 'legacyLanguageMode', '');
             case 'loginUser':
                 trigger_error('Property $TSFE->loginUser is not in use anymore as this information is now stored within the frontend.user aspect.');
                 return $this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn', false);
-                break;
             case 'gr_list':
                 trigger_error('Property $TSFE->gr_list is not in use anymore as this information is now stored within the frontend.user aspect.');
                 return implode(',', $this->context->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1]));
@@ -4977,6 +5006,66 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function __set(string $propertyName, $propertyValue)
     {
         switch ($propertyName) {
+            case 'sys_language_uid':
+                trigger_error('Property $TSFE->sys_language_uid is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, (int)$propertyValue, $aspect->getContentId(), $aspect->getOverlayType(), $aspect->getFallbackChain()));
+                break;
+            case 'sys_language_content':
+                trigger_error('Property $TSFE->sys_language_content is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $aspect->getId(), (int)$propertyValue, $aspect->getOverlayType(), $aspect->getFallbackChain()));
+                break;
+            case 'sys_language_contentOL':
+                trigger_error('Property $TSFE->sys_language_contentOL is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                switch ((string)$propertyValue) {
+                    case 'hideNonTranslated':
+                        $overlayType = LanguageAspect::OVERLAYS_ON_WITH_FLOATING;
+                        break;
+                    case '1':
+                        $overlayType = LanguageAspect::OVERLAYS_MIXED;
+                        break;
+                    default:
+                        $overlayType = LanguageAspect::OVERLAYS_OFF;
+                }
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $aspect->getId(), $aspect->getContentId(), $overlayType, $aspect->getFallbackChain()));
+                break;
+            case 'sys_language_mode':
+                trigger_error('Property $TSFE->sys_language_mode is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                switch ((string)$propertyValue) {
+                    case 'strict':
+                        $fallBackOrder = [];
+                        break;
+                    // Ignore anything if a page cannot be found, and resolve pageId=0 instead.
+                    case 'ignore':
+                        $fallBackOrder = [-1];
+                        break;
+                    case 'fallback':
+                    case 'content_fallback':
+                        if (!empty($propertyValue)) {
+                            $fallBackOrder = GeneralUtility::trimExplode(',', $propertyValue);
+                            // no strict typing explictly done here
+                            if (!in_array(0, $fallBackOrder) && !in_array('pageNotFound', $fallBackOrder)) {
+                                $fallBackOrder[] = 'pageNotFound';
+                            }
+                        } else {
+                            $fallBackOrder = [0];
+                        }
+                        break;
+                    case '':
+                        $fallBackOrder = ['off'];
+                        break;
+                    default:
+                        $fallBackOrder = [0];
+                }
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $aspect->getId(), $aspect->getContentId(), $aspect->getOverlayType(), $fallBackOrder));
+                break;
             case 'loginUser':
                 trigger_error('Property $TSFE->loginUser is not in use anymore as this information is now stored within the frontend.user aspect.');
                 /** @var UserAspect $aspect */
@@ -5025,6 +5114,28 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function __unset(string $propertyName)
     {
         switch ($propertyName) {
+            case 'sys_language_uid':
+                trigger_error('Property $TSFE->sys_language_uid is not in use anymore as this information is now stored within the language aspect.');
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class));
+                break;
+            case 'sys_language_content':
+                trigger_error('Property $TSFE->sys_language_content is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $aspect->getId(), 0, $aspect->getOverlayType()));
+                break;
+            case 'sys_language_contentOL':
+                trigger_error('Property $TSFE->sys_language_contentOL is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $aspect->getId(), $aspect->getContentId(), LanguageAspect::OVERLAYS_OFF));
+                break;
+            case 'sys_language_mode':
+                trigger_error('Property $TSFE->sys_language_mode is not in use anymore as this information is now stored within the language aspect.');
+                /** @var LanguageAspect $aspect */
+                $aspect = $this->context->getAspect('language');
+                $this->context->setAspect('language', GeneralUtility::makeInstance(LanguageAspect::class, $aspect->getId(), $aspect->getContentId(), $aspect->getOverlayType(), ['off']));
+                break;
             case 'loginUser':
                 /** @var UserAspect $aspect */
                 $aspect = $this->context->getAspect('frontend.user');
index ed3ba69..64f0031 100644 (file)
@@ -18,6 +18,8 @@ use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
 use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -55,6 +57,7 @@ class PageRepository implements LoggerAwareInterface
         'workspaceCache' => 'Using $workspaceCache of class PageRepository from the outside is discouraged, as this only reflects a local runtime cache.',
         'error_getRootLine' => 'Using $error_getRootLine of class PageRepository from the outside is deprecated as this property only exists for legacy reasons.',
         'error_getRootLine_failPid' => 'Using $error_getRootLine_failPid of class PageRepository from the outside is deprecated as this property only exists for legacy reasons.',
+        'sys_language_uid' => 'Using $sys_language_uid of class PageRepository from the outside is deprecated as this information is now stored within the Context Language Aspect given by the constructor.',
     ];
 
     /**
@@ -75,8 +78,10 @@ class PageRepository implements LoggerAwareInterface
 
     /**
      * @var int
+     * @deprecated will be removed in TYPO3 v10, all occurrences should be replaced with the language->id() aspect property in TYPO3 v10.0
+     * However, the usage within the class is kept as the property could be overwritten by third-party classes
      */
-    public $sys_language_uid = 0;
+    protected $sys_language_uid = 0;
 
     /**
      * If TRUE, versioning preview of other record versions is allowed. THIS MUST
@@ -200,6 +205,7 @@ class PageRepository implements LoggerAwareInterface
         if (isset($GLOBALS['TCA']['pages'])) {
             $this->init($this->context->getPropertyFromAspect('visibility', 'includeHiddenPages'));
             $this->where_groupAccess = $this->getMultipleGroupsWhereClause('pages.fe_group', 'pages');
+            $this->sys_language_uid = (int)$this->context->getPropertyFromAspect('language', 'id', 0);
         }
     }
 
@@ -442,6 +448,42 @@ class PageRepository implements LoggerAwareInterface
     }
 
     /**
+     * Master helper method to overlay a record to a language.
+     *
+     * Be aware that for pages the languageId is taken, and for all other records the contentId.
+     * This might change through a feature switch in the future.
+     *
+     * @param string $table the name of the table, should be a TCA table with localization enabled
+     * @param array $row the current (full-fletched) record.
+     * @return array|null
+     */
+    public function getLanguageOverlay(string $table, array $row)
+    {
+        // table is not localizable, so return directly
+        if (!isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])) {
+            return $row;
+        }
+        try {
+            /** @var LanguageAspect $languageAspect */
+            $languageAspect = $this->context->getAspect('language');
+            if ($languageAspect->doOverlays()) {
+                if ($table === 'pages') {
+                    return $this->getPageOverlay($row, $languageAspect->getId());
+                }
+                return $this->getRecordOverlay(
+                    $table,
+                    $row,
+                    $languageAspect->getContentId(),
+                    $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_MIXED ? '1' : 'hideNonTranslated'
+                );
+            }
+        } catch (AspectNotFoundException $e) {
+            // no overlays
+        }
+        return $row;
+    }
+
+    /**
      * Returns the relevant page overlay record fields
      *
      * @param mixed $pageInput If $pageInput is an integer, it's the pid of the pageOverlay record and thus the page overlay record is returned. If $pageInput is an array, it's a page-record and based on this page record the language record is found and OVERLAID before the page record is returned.
@@ -1071,7 +1113,7 @@ class PageRepository implements LoggerAwareInterface
         } else {
             $ignoreMPerrors = false;
         }
-        $rootline = GeneralUtility::makeInstance(RootlineUtility::class, $uid, $MP, $this);
+        $rootline = GeneralUtility::makeInstance(RootlineUtility::class, $uid, $MP, $this->context);
         try {
             return $rootline->get();
         } catch (\RuntimeException $ex) {
index d7fee33..c47ec9f 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Page;
 
 use Prophecy\Argument;
 use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -81,8 +82,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
      */
     public function getMenuPageOverlay()
     {
-        $subject = new PageRepository();
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
 
         $rows = $subject->getMenu([2, 3], 'uid, title');
         $this->assertEquals('Attrappe 1-2-5', $rows[5]['title']);
@@ -150,8 +152,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
      */
     public function getPagesOverlayByIdSingle()
     {
-        $subject = new PageRepository();
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
         $rows = $subject->getPagesOverlay([1]);
         $this->assertInternalType('array', $rows);
         $this->assertCount(1, $rows);
@@ -169,8 +172,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
      */
     public function getPagesOverlayByIdMultiple()
     {
-        $subject = new PageRepository();
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
         $rows = $subject->getPagesOverlay([1, 5]);
         $this->assertInternalType('array', $rows);
         $this->assertCount(2, $rows);
@@ -195,8 +199,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
      */
     public function getPagesOverlayByIdMultipleSomeNotOverlaid()
     {
-        $subject = new PageRepository();
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
         $rows = $subject->getPagesOverlay([1, 4, 5, 8]);
         $this->assertInternalType('array', $rows);
         $this->assertCount(2, $rows);
@@ -220,7 +225,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
         $subject = new PageRepository();
         $origRow = $subject->getPage(1);
 
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
         $rows = $subject->getPagesOverlay([$origRow]);
         $this->assertInternalType('array', $rows);
         $this->assertCount(1, $rows);
@@ -242,7 +249,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
         $orig1 = $subject->getPage(1);
         $orig2 = $subject->getPage(5);
 
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
         $rows = $subject->getPagesOverlay([1 => $orig1, 5 => $orig2]);
         $this->assertInternalType('array', $rows);
         $this->assertCount(2, $rows);
@@ -272,7 +281,9 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
         $orig2 = $subject->getPage(7);
         $orig3 = $subject->getPage(9);
 
-        $subject->sys_language_uid = 1;
+        $subject = new PageRepository(new Context([
+            'language' => new LanguageAspect(1)
+        ]));
         $rows = $subject->getPagesOverlay([$orig1, $orig2, $orig3]);
         $this->assertInternalType('array', $rows);
         $this->assertCount(3, $rows);
index 6366503..f6fed15 100644 (file)
@@ -16,6 +16,8 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject\Menu;
 use Doctrine\DBAL\Driver\Statement;
 use Prophecy\Argument;
 use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
@@ -129,7 +131,8 @@ class AbstractMenuContentObjectTest extends UnitTestCase
 
         $this->prepareSectionIndexTest();
         $this->subject->mconf['sectionIndex.']['type'] = 'all';
-        $GLOBALS['TSFE']->sys_language_contentOL = 1;
+        $context = GeneralUtility::makeInstance(Context::class);
+        $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_MIXED));
         $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue(['_PAGES_OVERLAY_LANGUAGE' => 1]));
         $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->willReturn($statementProphet->reveal());
         $this->subject->sys_page->expects($this->once())->method('getRecordOverlay')->will($this->returnValue(['uid' => 0, 'header' => 'OVERLAID']));
index 7025f36..65e1f6f 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@@ -44,6 +45,7 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
     {
         GeneralUtility::flushInternalRuntimeCaches();
         $this->subject = $this->getAccessibleMock(TypoScriptFrontendController::class, ['dummy'], [], '', false);
+        $this->subject->_set('context', new Context());
         $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = '170928423746123078941623042360abceb12341234231';
 
         $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
index def7583..9afbacd 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\IndexedSearch\Controller;
  */
 
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
@@ -1321,7 +1322,7 @@ class SearchController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControlle
      */
     protected function linkPage($pageUid, $row = [], $markUpSwParams = [])
     {
-        $pageLanguage = $GLOBALS['TSFE']->sys_language_content;
+        $pageLanguage = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('language', 'contentId', 0);
         // Parameters for link
         $urlParameters = (array)unserialize($row['cHashParams']);
         // Add &type and &MP variable:
index 9e22af7..002b99b 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\IndexedSearch;
 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -284,7 +285,9 @@ class Indexer
             if (!$disableFrontendIndexing || $this->crawlerActive) {
                 if (!$pObj->page['no_search']) {
                     if (!$pObj->no_cache) {
-                        if ((int)$pObj->sys_language_uid === (int)$pObj->sys_language_content) {
+                        /** @var LanguageAspect $languageAspect */
+                        $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
+                        if ($languageAspect->getId() === $languageAspect->getContentId()) {
                             // Setting up internal configuration from config array:
                             $this->conf = [];
                             // Information about page for which the indexing takes place
@@ -292,7 +295,7 @@ class Indexer
                             // Page id
                             $this->conf['type'] = $pObj->type;
                             // Page type
-                            $this->conf['sys_language_uid'] = $pObj->sys_language_uid;
+                            $this->conf['sys_language_uid'] = $languageAspect->getId();
                             // sys_language UID of the language of the indexing.
                             $this->conf['MP'] = $pObj->MP;
                             // MP variable, if any (Mount Points)
@@ -337,7 +340,7 @@ class Indexer
                             $this->init();
                             $this->indexTypo3PageContent();
                         } else {
-                            $this->log_setTSlogMessage('Index page? No, ->sys_language_uid was different from sys_language_content which indicates that the page contains fall-back content and that would be falsely indexed as localized content.');
+                            $this->log_setTSlogMessage('Index page? No, languageId was different from contentId which indicates that the page contains fall-back content and that would be falsely indexed as localized content.');
                         }
                     } else {
                         $this->log_setTSlogMessage('Index page? No, page was set to "no_cache" and so cannot be indexed.');
index 218ea50..102cd9a 100644 (file)
@@ -436,6 +436,31 @@ return [
             'Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst'
         ],
     ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_uid' => [
+        'restFiles' => [
+            'Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst'
+        ],
+    ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_content' => [
+        'restFiles' => [
+            'Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst'
+        ],
+    ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_contentOL' => [
+        'restFiles' => [
+            'Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst'
+        ],
+    ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->sys_language_mode' => [
+        'restFiles' => [
+            'Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst'
+        ],
+    ],
+    'TYPO3\CMS\Frontend\Page\PageRepository->sys_language_uid' => [
+        'restFiles' => [
+            'Deprecation-85543-Language-relatedPropertiesInTypoScriptFrontendControllerAndPageRepository.rst'
+        ],
+    ],
     'TYPO3\CMS\Core\TypoScript\TemplateService->fileCache' => [
         'restFiles' => [
             'Deprecation-85445-TemplateService-getFileName.rst'