From 31439e93ffe4ea8de1588a3494b08c06bedd3823 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Fri, 8 Mar 2024 10:10:34 +0100
Subject: [PATCH] [TASK] Separation of concerns while rendering Page module

All PageTsConfig options are now in DrawingConfiguration
and named properly.

All plain labels are now moved to Fluid directly via <f:translate>

Random id="{uniqueId}" are removed from markup, reducing
the usages to the AbstractGridObject, which hopefully
will vanish in the near future.

RecordRememberer is a singleton and injected as much as possible.

The "languageMode" is now resolved into a PageViewMode enum.

Resolves: #103345
Releases: main
Change-Id: I78f33fed409db2a1c5528e734ed19ad67aeb4e89
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83322
Reviewed-by: Nikita Hovratov <nikita.h@live.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Andreas Kienast <a.fernandez@scripting-base.de>
Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de>
Tested-by: Nikita Hovratov <nikita.h@live.de>
---
 .../Controller/PageLayoutController.php       | 41 ++++---------
 .../View/BackendLayout/ContentFetcher.php     |  4 +-
 .../BackendLayout/Grid/AbstractGridObject.php |  6 --
 .../Classes/View/BackendLayout/Grid/Grid.php  |  2 +-
 .../View/BackendLayout/Grid/GridColumn.php    | 24 ++++----
 .../BackendLayout/Grid/GridColumnItem.php     | 12 ++--
 .../BackendLayout/Grid/LanguageColumn.php     | 26 ++------
 .../View/BackendLayout/RecordRememberer.php   |  2 +-
 .../View/Drawing/BackendLayoutRenderer.php    | 13 ++--
 .../View/Drawing/DrawingConfiguration.php     | 60 +++++++++++++------
 .../Classes/View/PageLayoutContext.php        | 15 ++---
 .../backend/Classes/View/PageViewMode.php     | 35 +++++++++++
 .../PageLayout/Grid/ColumnHeader.html         |  8 +--
 .../Partials/PageLayout/LanguageColumns.html  |  8 +--
 .../Private/Partials/PageLayout/Record.html   |  6 +-
 .../Templates/PageLayout/PageLayout.html      |  2 +-
 .../Templates/PageLayout/PageModule.html      |  2 +-
 .../Drawing/BackendLayoutRendererTest.php     | 41 ++++++++++---
 .../BackendUserAuthentication.php             | 18 ++++--
 .../core/Classes/Site/Entity/NullSite.php     |  2 +-
 .../sysext/core/Classes/Site/Entity/Site.php  |  2 +-
 .../Middleware/SiteBaseRedirectResolver.php   |  2 +-
 22 files changed, 191 insertions(+), 140 deletions(-)
 create mode 100644 typo3/sysext/backend/Classes/View/PageViewMode.php

diff --git a/typo3/sysext/backend/Classes/Controller/PageLayoutController.php b/typo3/sysext/backend/Classes/Controller/PageLayoutController.php
index 99c8d3e8ebd5..61142f8ac452 100644
--- a/typo3/sysext/backend/Classes/Controller/PageLayoutController.php
+++ b/typo3/sysext/backend/Classes/Controller/PageLayoutController.php
@@ -38,6 +38,7 @@ use TYPO3\CMS\Backend\View\BackendLayoutView;
 use TYPO3\CMS\Backend\View\Drawing\BackendLayoutRenderer;
 use TYPO3\CMS\Backend\View\Drawing\DrawingConfiguration;
 use TYPO3\CMS\Backend\View\PageLayoutContext;
+use TYPO3\CMS\Backend\View\PageViewMode;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -124,7 +125,7 @@ class PageLayoutController
         }
 
         $tsConfig = BackendUtility::getPagesTSconfig($this->id);
-        $this->menuConfig($request);
+        $this->menuConfig();
         $this->currentSelectedLanguage = (int)$this->moduleData->get('language');
         $this->addJavaScriptModuleInstructions();
         $this->makeActionMenu($view, $tsConfig);
@@ -134,9 +135,7 @@ class PageLayoutController
 
         $pageLayoutContext = $this->createPageLayoutContext($request, $tsConfig);
         $mainLayoutHtml = $this->backendLayoutRenderer->drawContent($request, $pageLayoutContext);
-        $numberOfHiddenElements = $this->getNumberOfHiddenElements(
-            $pageLayoutContext->getDrawingConfiguration()->getLanguageMode()
-        );
+        $numberOfHiddenElements = $this->getNumberOfHiddenElements($pageLayoutContext->getDrawingConfiguration());
 
         $pageLocalizationRecord = $this->getLocalizedPageRecord($this->currentSelectedLanguage);
 
@@ -145,13 +144,13 @@ class PageLayoutController
         $view->assignMultiple([
             'pageId' => $this->id,
             'localizedPageId' => $pageLocalizationRecord['uid'] ?? 0,
+            'pageLayoutContext' => $pageLayoutContext,
             'infoBoxes' => $this->generateMessagesForCurrentPage($request),
             'isPageEditable' => $this->isPageEditable($this->currentSelectedLanguage),
-            'localizedPageTitle' => $this->getLocalizedPageTitle($this->currentSelectedLanguage, $this->pageinfo),
+            'localizedPageTitle' => $pageLocalizationRecord['title'] ?? $this->pageinfo['title'] ?? '',
             'eventContentHtmlTop' => $event->getHeaderContent(),
             'mainContentHtml' => $mainLayoutHtml,
             'hiddenElementsShowToggle' => ($this->getBackendUser()->check('tables_select', 'tt_content') && ($numberOfHiddenElements > 0)),
-            'hiddenElementsState' => (bool)$this->moduleData->get('showHidden'),
             'hiddenElementsCount' => $numberOfHiddenElements,
             'eventContentHtmlBottom' => $event->getFooterContent(),
         ]);
@@ -161,21 +160,18 @@ class PageLayoutController
     protected function createPageLayoutContext(ServerRequestInterface $request, array $tsConfig): PageLayoutContext
     {
         $backendLayout = $this->backendLayoutView->getBackendLayoutForPage($this->id);
-        $configuration = DrawingConfiguration::create($backendLayout, $tsConfig);
+        $viewMode = (int)$this->moduleData->get('function') === 2 ? PageViewMode::LanguageComparisonView : PageViewMode::LayoutView;
+        $configuration = DrawingConfiguration::create($backendLayout, $tsConfig, $viewMode);
         $configuration->setShowHidden((bool)$this->moduleData->get('showHidden'));
         $configuration->setLanguageColumns($this->MOD_MENU['language']);
         $configuration->setSelectedLanguageId($this->currentSelectedLanguage);
-        if ((int)$this->moduleData->get('function') === 2) {
-            $configuration->setLanguageMode(true);
-        }
-
-        return PageLayoutContext::create($this->pageinfo, $backendLayout, $request->getAttribute('site'), $configuration, $tsConfig);
+        return GeneralUtility::makeInstance(PageLayoutContext::class, $this->pageinfo, $backendLayout, $request->getAttribute('site'), $configuration, $request);
     }
 
     /**
      * Initialize menu array
      */
-    protected function menuConfig(ServerRequestInterface $request): void
+    protected function menuConfig(): void
     {
         $backendUser = $this->getBackendUser();
         $languageService = $this->getLanguageService();
@@ -462,18 +458,6 @@ class PageLayoutController
         return implode(', ', $links);
     }
 
-    protected function getLocalizedPageTitle(int $currentSelectedLanguage, array $pageInfo): string
-    {
-        if ($currentSelectedLanguage <= 0) {
-            return $pageInfo['title'];
-        }
-        $pageLocalizationRecord = $this->getLocalizedPageRecord($currentSelectedLanguage);
-        if (!is_array($pageLocalizationRecord)) {
-            return $pageInfo['title'];
-        }
-        return $pageLocalizationRecord['title'] ?? '';
-    }
-
     /**
      * Initializes the clipboard for generating paste links dynamically via JavaScript after each "+ Content" symbol
      */
@@ -686,8 +670,9 @@ class PageLayoutController
      * Returns the number of hidden elements (including those hidden by start/end times)
      * on the current page (for the current site language)
      */
-    protected function getNumberOfHiddenElements(bool $isLanguageModeActive): int
+    protected function getNumberOfHiddenElements(DrawingConfiguration $drawingConfiguration): int
     {
+        $isLanguageComparisonModeActive = $drawingConfiguration->isLanguageComparisonMode();
         $andWhere = [];
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
         $queryBuilder->getRestrictions()
@@ -712,7 +697,7 @@ class PageLayoutController
                     [-1, 0]
                 )
             );
-        } elseif ($isLanguageModeActive && $this->currentSelectedLanguage !== -1) {
+        } elseif ($isLanguageComparisonModeActive && $this->currentSelectedLanguage !== -1) {
             // Multi-language view with any translation is active -
             // consider "all languages", the default and the translation
             $queryBuilder->andWhere(
@@ -816,7 +801,7 @@ class PageLayoutController
      */
     protected function getTargetPageIfVisible(array $targetPage): array
     {
-        return !(bool)($targetPage['hidden'] ?? false) ? $targetPage : [];
+        return !($targetPage['hidden'] ?? false) ? $targetPage : [];
     }
 
     /**
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/ContentFetcher.php b/typo3/sysext/backend/Classes/View/BackendLayout/ContentFetcher.php
index 6c237bcff7df..ef1529ffbe83 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/ContentFetcher.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/ContentFetcher.php
@@ -75,7 +75,7 @@ class ContentFetcher
         $languageId = $languageId ?? $this->context->getSiteLanguage()->getLanguageId();
 
         if (empty($this->fetchedContentRecords)) {
-            $isLanguageMode = $this->context->getDrawingConfiguration()->getLanguageMode();
+            $isLanguageComparisonMode = $this->context->getDrawingConfiguration()->isLanguageComparisonMode();
             $queryBuilder = $this->getQueryBuilder();
             $result = $queryBuilder->executeQuery();
             $records = $this->getResult($result);
@@ -84,7 +84,7 @@ class ContentFetcher
                 $recordColumnNumber = (int)$record['colPos'];
                 if ($recordLanguage === -1) {
                     // Record is set to "all languages", place it according to view mode.
-                    if ($isLanguageMode) {
+                    if ($isLanguageComparisonMode) {
                         // Force the record to only be shown in default language in "Languages" view mode.
                         $recordLanguage = 0;
                     } else {
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/AbstractGridObject.php b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/AbstractGridObject.php
index 3c26eb09cfb6..7265766aedbc 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/AbstractGridObject.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/AbstractGridObject.php
@@ -22,7 +22,6 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\StringUtility;
 
 /**
  * Base class for objects which constitute a page layout grid.
@@ -46,11 +45,6 @@ abstract class AbstractGridObject
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
     }
 
-    public function getUniqueId(): string
-    {
-        return StringUtility::getUniqueId();
-    }
-
     public function getContext(): PageLayoutContext
     {
         return $this->context;
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/Grid.php b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/Grid.php
index 4d9a49d4f7af..56125a50e851 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/Grid.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/Grid.php
@@ -66,7 +66,7 @@ class Grid extends AbstractGridObject
 
     public function getSpan(): int
     {
-        if (!isset($this->rows[0]) || $this->context->getDrawingConfiguration()->getLanguageMode()) {
+        if (!isset($this->rows[0]) || $this->context->getDrawingConfiguration()->isLanguageComparisonMode()) {
             return 1;
         }
         $span = 0;
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumn.php b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumn.php
index 04db560122ce..890702d18a8b 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumn.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumn.php
@@ -114,7 +114,7 @@ class GridColumn extends AbstractGridObject
 
     public function getColSpan(): int
     {
-        if ($this->context->getDrawingConfiguration()->getLanguageMode()) {
+        if ($this->context->getDrawingConfiguration()->isLanguageComparisonMode()) {
             return 1;
         }
         return $this->colSpan;
@@ -122,7 +122,7 @@ class GridColumn extends AbstractGridObject
 
     public function getRowSpan(): int
     {
-        if ($this->context->getDrawingConfiguration()->getLanguageMode()) {
+        if ($this->context->getDrawingConfiguration()->isLanguageComparisonMode()) {
             return 1;
         }
         return $this->rowSpan;
@@ -147,15 +147,19 @@ class GridColumn extends AbstractGridObject
         }
         $pageRecord = $this->context->getPageRecord();
         if (!$this->getBackendUser()->doesUserHaveAccess($pageRecord, Permission::CONTENT_EDIT)
-            || !$this->getBackendUser()->checkLanguageAccess($this->context->getSiteLanguage()->getLanguageId())) {
+            || !$this->getBackendUser()->checkLanguageAccess($this->context->getSiteLanguage())) {
             return null;
         }
-        $pageTitleParamForAltDoc = '&recTitle=' . rawurlencode(
-            BackendUtility::getRecordTitle('pages', $pageRecord, true)
-        );
-        $editParam = '&edit[' . $this->table . '][' . implode(',', $this->getAllContainedItemUids()) . ']=edit' . $pageTitleParamForAltDoc;
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-        return $uriBuilder->buildUriFromRoute('record_edit') . $editParam . '&returnUrl=' . rawurlencode($GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri());
+        return (string)$uriBuilder->buildUriFromRoute('record_edit', [
+            'edit' => [
+                $this->table => [
+                    implode(',', $this->getAllContainedItemUids()) => 'edit',
+                ],
+            ],
+            'recTitle' => rawurlencode(BackendUtility::getRecordTitle('pages', $pageRecord, true)),
+            'returnUrl' => rawurlencode($this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri()),
+        ]);
     }
 
     public function getNewContentUrl(): string
@@ -168,7 +172,7 @@ class GridColumn extends AbstractGridObject
             'sys_language_uid' => $this->context->getSiteLanguage()->getLanguageId(),
             'colPos' => $this->getColumnNumber(),
             'uid_pid' => $pageId,
-            'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
+            'returnUrl' => $this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri(),
         ]);
     }
 
@@ -229,7 +233,7 @@ class GridColumn extends AbstractGridObject
         $pageRecord = $this->context->getPageRecord();
         return !$pageRecord['editlock']
             && $this->getBackendUser()->doesUserHaveAccess($pageRecord, Permission::CONTENT_EDIT)
-            && $this->getBackendUser()->checkLanguageAccess($this->context->getSiteLanguage()->getLanguageId());
+            && $this->getBackendUser()->checkLanguageAccess($this->context->getSiteLanguage());
     }
 
     /**
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php
index d46dc6fb53aa..4087ffc22325 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/GridColumnItem.php
@@ -125,7 +125,7 @@ class GridColumnItem extends AbstractGridObject
                         ],
                     ],
                 ],
-                'redirect' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
+                'redirect' => $this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri(),
             ]
         );
     }
@@ -278,14 +278,12 @@ class GridColumnItem extends AbstractGridObject
     public function getNewContentAfterUrl(): string
     {
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-        $pageId = $this->context->getPageId();
-
         return (string)$uriBuilder->buildUriFromRoute('new_content_element_wizard', [
-            'id' => $pageId,
+            'id' => $this->context->getPageId(),
             'sys_language_uid' => $this->context->getSiteLanguage()->getLanguageId(),
             'colPos' => $this->column->getColumnNumber(),
             'uid_pid' => -$this->record['uid'],
-            'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
+            'returnUrl' => $this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri(),
         ]);
     }
 
@@ -307,7 +305,7 @@ class GridColumnItem extends AbstractGridObject
                         ],
                     ],
                 ],
-                'redirect' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
+                'redirect' => $this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri(),
             ]
         ) . '#element-' . $this->table . '-' . $this->record['uid'];
     }
@@ -345,7 +343,7 @@ class GridColumnItem extends AbstractGridObject
                     $this->record['uid'] => 'edit',
                 ],
             ],
-            'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri() . '#element-' . $this->table . '-' . $this->record['uid'],
+            'returnUrl' => $this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri() . '#element-' . $this->table . '-' . $this->record['uid'],
         ];
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
         return $uriBuilder->buildUriFromRoute('record_edit', $urlParameters) . '#element-' . $this->table . '-' . $this->record['uid'];
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/LanguageColumn.php b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/LanguageColumn.php
index a5dcd6b39be1..5853cccdfe92 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/Grid/LanguageColumn.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/Grid/LanguageColumn.php
@@ -44,15 +44,12 @@ use TYPO3\CMS\Core\Versioning\VersionState;
  */
 class LanguageColumn extends AbstractGridObject
 {
-    protected readonly array $localizationConfiguration;
-
     public function __construct(
         protected PageLayoutContext $context,
         protected readonly Grid $grid,
         protected readonly array $translationInfo
     ) {
         parent::__construct($context);
-        $this->localizationConfiguration = BackendUtility::getPagesTSconfig($context->getPageId())['mod.']['web_layout.']['localization.'] ?? [];
     }
 
     public function getGrid(): Grid
@@ -72,7 +69,7 @@ class LanguageColumn extends AbstractGridObject
 
     public function getAllowTranslate(): bool
     {
-        return ($this->localizationConfiguration['enableTranslate'] ?? true) && !($this->getTranslationData()['hasStandAloneContent'] ?? false);
+        return $this->context->getDrawingConfiguration()->translateModeForTranslationsAllowed() && !($this->getTranslationData()['hasStandAloneContent'] ?? false);
     }
 
     public function getTranslationData(): array
@@ -82,23 +79,13 @@ class LanguageColumn extends AbstractGridObject
 
     public function getAllowTranslateCopy(): bool
     {
-        return ($this->localizationConfiguration['enableCopy'] ?? true) && !($this->getTranslationData()['hasTranslations'] ?? false);
-    }
-
-    public function getTranslatePageTitle(): string
-    {
-        return $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newPageContent_translate');
+        return $this->context->getDrawingConfiguration()->copyModeForTranslationsAllowed() && !($this->getTranslationData()['hasTranslations'] ?? false);
     }
 
     public function getAllowEditPage(): bool
     {
         return $this->getBackendUser()->check('tables_modify', 'pages')
-            && $this->getBackendUser()->checkLanguageAccess($this->context->getSiteLanguage()->getLanguageId());
-    }
-
-    public function getPageEditTitle(): string
-    {
-        return $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:edit');
+            && $this->getBackendUser()->checkLanguageAccess($this->context->getSiteLanguage());
     }
 
     public function getPageEditUrl(): string
@@ -110,7 +97,7 @@ class LanguageColumn extends AbstractGridObject
                     $pageRecordUid => 'edit',
                 ],
             ],
-            'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
+            'returnUrl' => $this->context->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri(),
         ];
         // Disallow manual adjustment of the language field for pages
         if (($languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? '') !== '') {
@@ -124,11 +111,6 @@ class LanguageColumn extends AbstractGridObject
         return VersionState::tryFrom($this->context->getPageRecord()['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER;
     }
 
-    public function getViewPageLinkTitle(): string
-    {
-        return $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage');
-    }
-
     public function getPreviewUrlAttributes(): string
     {
         $pageId = $this->context->getPageId();
diff --git a/typo3/sysext/backend/Classes/View/BackendLayout/RecordRememberer.php b/typo3/sysext/backend/Classes/View/BackendLayout/RecordRememberer.php
index d01e7c3ae9f1..19d93258cea4 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayout/RecordRememberer.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayout/RecordRememberer.php
@@ -27,7 +27,7 @@ class RecordRememberer implements SingletonInterface
     /**
      * @var int[]
      */
-    protected $rememberedUids = [];
+    protected array $rememberedUids = [];
 
     public function rememberRecords(iterable $records): void
     {
diff --git a/typo3/sysext/backend/Classes/View/Drawing/BackendLayoutRenderer.php b/typo3/sysext/backend/Classes/View/Drawing/BackendLayoutRenderer.php
index 1357c705d773..d1c01a4a34fe 100644
--- a/typo3/sysext/backend/Classes/View/Drawing/BackendLayoutRenderer.php
+++ b/typo3/sysext/backend/Classes/View/Drawing/BackendLayoutRenderer.php
@@ -18,7 +18,6 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Backend\View\Drawing;
 
 use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\View\BackendLayout\ContentFetcher;
 use TYPO3\CMS\Backend\View\BackendLayout\Grid\Grid;
 use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumn;
@@ -48,14 +47,14 @@ class BackendLayoutRenderer
 {
     public function __construct(
         protected readonly BackendViewFactory $backendViewFactory,
+        protected readonly RecordRememberer $recordRememberer
     ) {}
 
     public function getGridForPageLayoutContext(PageLayoutContext $context): Grid
     {
         $contentFetcher = GeneralUtility::makeInstance(ContentFetcher::class, $context);
         $grid = GeneralUtility::makeInstance(Grid::class, $context);
-        $recordRememberer = GeneralUtility::makeInstance(RecordRememberer::class);
-        if ($context->getDrawingConfiguration()->getLanguageMode()) {
+        if ($context->getDrawingConfiguration()->isLanguageComparisonMode()) {
             $languageId = $context->getSiteLanguage()->getLanguageId();
         } else {
             $languageId = $context->getDrawingConfiguration()->getSelectedLanguageId();
@@ -69,7 +68,7 @@ class BackendLayoutRenderer
                 $rowObject->addColumn($columnObject);
                 if (isset($column['colPos'])) {
                     $records = $contentFetcher->getContentRecordsPerColumn((int)$column['colPos'], $languageId);
-                    $recordRememberer->rememberRecords($records);
+                    $this->recordRememberer->rememberRecords($records);
                     foreach ($records as $contentRecord) {
                         $columnItem = GeneralUtility::makeInstance(GridColumnItem::class, $context, $columnObject, $contentRecord);
                         $columnObject->addItem($columnItem);
@@ -92,14 +91,12 @@ class BackendLayoutRenderer
         $view = $this->backendViewFactory->create($request);
         $view->assignMultiple([
             'context' => $pageLayoutContext,
-            'hideRestrictedColumns' => (bool)(BackendUtility::getPagesTSconfig($pageLayoutContext->getPageId())['mod.']['web_layout.']['hideRestrictedCols'] ?? false),
-            'newContentTitle' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newContentElement'),
-            'newContentTitleShort' => $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:content'),
+            'hideRestrictedColumns' => $pageLayoutContext->getDrawingConfiguration()->shouldHideRestrictedColumns(),
             'allowEditContent' => $backendUser->check('tables_modify', 'tt_content'),
             'maxTitleLength' => $backendUser->uc['titleLen'] ?? 20,
         ]);
 
-        if ($pageLayoutContext->getDrawingConfiguration()->getLanguageMode()) {
+        if ($pageLayoutContext->getDrawingConfiguration()->isLanguageComparisonMode()) {
             if ($pageLayoutContext->getDrawingConfiguration()->getDefaultLanguageBinding()) {
                 $view->assign('languageColumns', $this->getLanguageColumnsWithDefLangBindingForPageLayoutContext($pageLayoutContext));
             } else {
diff --git a/typo3/sysext/backend/Classes/View/Drawing/DrawingConfiguration.php b/typo3/sysext/backend/Classes/View/Drawing/DrawingConfiguration.php
index 7b39d6b6e7a5..01abd38e13f7 100644
--- a/typo3/sysext/backend/Classes/View/Drawing/DrawingConfiguration.php
+++ b/typo3/sysext/backend/Classes/View/Drawing/DrawingConfiguration.php
@@ -18,6 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Backend\View\Drawing;
 
 use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout;
+use TYPO3\CMS\Backend\View\PageViewMode;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -39,7 +40,7 @@ class DrawingConfiguration
     /**
      * Corresponds to web.layout.allowInconsistentLanguageHandling TSconfig property
      */
-    protected bool $allowInconsistentLanguageHandling = false;
+    protected bool $allowInconsistentLanguageHandling;
 
     /**
      * Determines whether rendering should happen with a visually aligned
@@ -47,13 +48,7 @@ class DrawingConfiguration
      * with this flag enabled, any translated versions are vertically
      * aligned so they are rendered in the same visual row as the original.
      */
-    protected bool $defaultLanguageBinding = true;
-
-    /**
-     * If TRUE, indicates that the current rendering method shows multiple
-     * languages (e.g. the "page" module is set in "Languages" mode.
-     */
-    protected bool $languageMode = false;
+    protected bool $defaultLanguageBinding;
 
     /**
      * Key => "Language ID", Value "Label of language"
@@ -72,18 +67,37 @@ class DrawingConfiguration
      */
     protected array $activeColumns = [1, 0, 2, 3];
 
-    public static function create(BackendLayout $backendLayout, array $pageTsConfig): self
+    /**
+     * Whether or not to allow the translate mode for translations
+     */
+    protected bool $allowTranslateModeForTranslations;
+
+    /**
+     * Whether or not to allow the copy mode for translations
+     */
+    protected bool $allowCopyModeForTranslations;
+
+    protected bool $shouldHideRestrictedColumns;
+
+    protected PageViewMode $pageViewMode;
+
+    public static function create(BackendLayout $backendLayout, array $pageTsConfig, PageViewMode $pageViewMode): self
     {
         $obj = new self();
+        $obj->pageViewMode = $pageViewMode;
         $obj->defaultLanguageBinding = !empty($pageTsConfig['mod.']['web_layout.']['defLangBinding']);
         $obj->allowInconsistentLanguageHandling = (bool)($pageTsConfig['mod.']['web_layout.']['allowInconsistentLanguageHandling'] ?? false);
+        $obj->shouldHideRestrictedColumns = (bool)($pageTsConfig['mod.']['web_layout.']['hideRestrictedCols'] ?? false);
         $availableColumnPositionsFromBackendLayout = array_unique($backendLayout->getColumnPositionNumbers());
         $allowedColumnPositionsByTsConfig = array_unique(GeneralUtility::intExplode(',', (string)($pageTsConfig['mod.']['SHARED.']['colPos_list'] ?? ''), true));
-        $obj->activeColumns = $availableColumnPositionsFromBackendLayout;
+        // If there is no tsConfig colPos_list, no restriction. Else create intersection of available and allowed.
         if (!empty($allowedColumnPositionsByTsConfig)) {
-            // If there is no tsConfig colPos_list, no restriction. Else create intersection of available and allowed.
             $obj->activeColumns = array_intersect($availableColumnPositionsFromBackendLayout, $allowedColumnPositionsByTsConfig);
+        } else {
+            $obj->activeColumns = $availableColumnPositionsFromBackendLayout;
         }
+        $obj->allowTranslateModeForTranslations = $pageTsConfig['mod.']['web_layout.']['localization.']['enableTranslate'] ?? true;
+        $obj->allowCopyModeForTranslations = $pageTsConfig['mod.']['web_layout.']['localization.']['enableCopy'] ?? true;
 
         return $obj;
     }
@@ -108,14 +122,9 @@ class DrawingConfiguration
         return $this->defaultLanguageBinding;
     }
 
-    public function getLanguageMode(): bool
+    public function isLanguageComparisonMode(): bool
     {
-        return $this->languageMode;
-    }
-
-    public function setLanguageMode(bool $languageMode): void
-    {
-        $this->languageMode = $languageMode;
+        return $this->pageViewMode === PageViewMode::LanguageComparisonView;
     }
 
     public function getLanguageColumns(): array
@@ -145,4 +154,19 @@ class DrawingConfiguration
     {
         return $this->activeColumns;
     }
+
+    public function translateModeForTranslationsAllowed(): bool
+    {
+        return $this->allowTranslateModeForTranslations;
+    }
+
+    public function copyModeForTranslationsAllowed(): bool
+    {
+        return $this->allowCopyModeForTranslations;
+    }
+
+    public function shouldHideRestrictedColumns(): bool
+    {
+        return $this->shouldHideRestrictedColumns;
+    }
 }
diff --git a/typo3/sysext/backend/Classes/View/PageLayoutContext.php b/typo3/sysext/backend/Classes/View/PageLayoutContext.php
index 91683c730025..b70d3e8d1b60 100644
--- a/typo3/sysext/backend/Classes/View/PageLayoutContext.php
+++ b/typo3/sysext/backend/Classes/View/PageLayoutContext.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Backend\View;
 
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout;
@@ -71,7 +72,7 @@ class PageLayoutContext
         protected readonly BackendLayout $backendLayout,
         protected readonly SiteInterface $site,
         protected readonly DrawingConfiguration $drawingConfiguration,
-        protected readonly array $tsConfig
+        protected readonly ServerRequestInterface $request
     ) {
         $this->pageId = (int)($pageRecord['uid'] ?? 0);
         $this->contentFetcher = GeneralUtility::makeInstance(ContentFetcher::class, $this);
@@ -79,11 +80,6 @@ class PageLayoutContext
         $this->siteLanguage = $this->site->getDefaultLanguage();
     }
 
-    public static function create(array $pageRecord, BackendLayout $backendLayout, SiteInterface $site, DrawingConfiguration $drawingConfiguration, array $tsConfig): self
-    {
-        return new self($pageRecord, $backendLayout, $site, $drawingConfiguration, $tsConfig);
-    }
-
     public function cloneForLanguage(SiteLanguage $language): self
     {
         $copy = clone $this;
@@ -303,7 +299,7 @@ class PageLayoutContext
                             'record_edit',
                             [
                                 'justLocalized' => 'pages:' . $this->pageId . ':' . $languageUid,
-                                'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(),
+                                'returnUrl' => $this->getCurrentRequest()->getAttribute('normalizedParams')->getRequestUri(),
                             ]
                         ),
                     ]
@@ -314,6 +310,11 @@ class PageLayoutContext
         return $options;
     }
 
+    public function getCurrentRequest(): ServerRequestInterface
+    {
+        return $this->request;
+    }
+
     public function getLocalizedPageTitle(): string
     {
         return $this->localizedPageRecord['title'] ?? $this->pageRecord['title'];
diff --git a/typo3/sysext/backend/Classes/View/PageViewMode.php b/typo3/sysext/backend/Classes/View/PageViewMode.php
new file mode 100644
index 000000000000..8e25c9f342ef
--- /dev/null
+++ b/typo3/sysext/backend/Classes/View/PageViewMode.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Backend\View;
+
+/**
+ * State how to render the page layout module.
+ * @todo: we could move the "mod.defLangBinding" option as a separate View in here as well.
+ *
+ * @internal
+ */
+enum PageViewMode
+{
+    case LayoutView;
+
+    /**
+     * Indicates that the current rendering method shows multiple
+     * languages side-by-side.
+     */
+    case LanguageComparisonView;
+}
diff --git a/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Grid/ColumnHeader.html b/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Grid/ColumnHeader.html
index 3de62ffbc83b..909948917a98 100644
--- a/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Grid/ColumnHeader.html
+++ b/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Grid/ColumnHeader.html
@@ -23,11 +23,11 @@
 </div>
 <f:format.raw>{column.beforeSectionMarkup}</f:format.raw>
 <f:if condition="{allowEditContent} && {column.contentEditable} && {column.context.allowNewContent} && {column.active}">
-    <div class="t3-page-ce t3js-page-ce" data-page="{column.context.pageId}" id="{column.uniqueId}">
-        <div class="t3-page-ce-actions t3js-page-new-ce" id="colpos-{column.columnNumber}-page-{column.context.pageId}-{column.uniqueId}">
-            <typo3-backend-new-content-element-wizard-button class="btn btn-default btn-sm" url="{column.newContentUrl}" subject="{newContentTitle}">
+    <div class="t3-page-ce t3js-page-ce" data-page="{column.context.pageId}">
+        <div class="t3-page-ce-actions t3js-page-new-ce">
+            <typo3-backend-new-content-element-wizard-button class="btn btn-default btn-sm" url="{column.newContentUrl}" subject="{f:translate(key: 'LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newContentElement')}">
                 <core:icon identifier="actions-plus" />
-                {newContentTitleShort}
+                <f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:content" />
             </typo3-backend-new-content-element-wizard-button>
         </div>
         <div class="t3-page-ce-dropzone t3js-page-ce-dropzone-available"></div>
diff --git a/typo3/sysext/backend/Resources/Private/Partials/PageLayout/LanguageColumns.html b/typo3/sysext/backend/Resources/Private/Partials/PageLayout/LanguageColumns.html
index 54e799867896..f1d9de9af455 100644
--- a/typo3/sysext/backend/Resources/Private/Partials/PageLayout/LanguageColumns.html
+++ b/typo3/sysext/backend/Resources/Private/Partials/PageLayout/LanguageColumns.html
@@ -32,18 +32,18 @@
                     <td class="t3-page-column t3-page-lang-label nowrap">
                         <div class="btn-group">
                             <f:if condition="{languageColumn.allowViewPage}">
-                                <button class="btn btn-default btn-sm" {f:if(condition: languageColumn.previewUrlAttributes, then: '{languageColumn.previewUrlAttributes -> f:format.raw()}', else: 'disabled="true"')} title="{languageColumn.viewPageLinkTitle}">
+                                <button class="btn btn-default btn-sm" {f:if(condition: languageColumn.previewUrlAttributes, then: '{languageColumn.previewUrlAttributes -> f:format.raw()}', else: 'disabled="true"')} title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage')}">
                                     <core:icon identifier="actions-view" />
                                 </button>
                             </f:if>
                             <f:if condition="{languageColumn.allowEditPage}">
-                                <a href="{languageColumn.pageEditUrl}" class="btn btn-default btn-sm" title="{languageColumn.pageEditTitle}">
+                                <a href="{languageColumn.pageEditUrl}" class="btn btn-default btn-sm" title="{f:translate(key: 'LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:edit')}">
                                     <core:icon identifier="actions-open" />
                                 </a>
                             </f:if>
                             <f:if condition="{allowEditContent} && {languageColumn.context.siteLanguage.languageId} && {languageColumn.translationData.untranslatedRecordUids}">
                                 <a href="#" class="btn btn-default btn-sm t3js-localize disabled"
-                                    title="{languageColumn.context.translatePageTitle}"
+                                    title="{f:translate(key: 'LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newPageContent_translate')}"
                                     data-page="{languageColumn.context.localizedPageRecord.title}"
                                     data-has-elements="{languageColumn.translationData.hasTranslations as integer}"
                                     data-allow-copy="{languageColumn.allowTranslateCopy as integer}"
@@ -53,7 +53,7 @@
                                     data-language-id="{languageColumn.context.siteLanguage.languageId}"
                                     data-language-name="{languageColumn.context.siteLanguage.title}">
                                     <core:icon identifier="actions-localize" />
-                                    {languageColumn.translatePageTitle}
+                                    <f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newPageContent_translate" />
                                 </a>
                             </f:if>
                         </div>
diff --git a/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Record.html b/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Record.html
index 1f812c5335cf..2d1304484f53 100644
--- a/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Record.html
+++ b/typo3/sysext/backend/Resources/Private/Partials/PageLayout/Record.html
@@ -1,6 +1,6 @@
 {f:if(condition: '{item.disabled} && {item.context.drawingConfiguration.showHidden} == 0', then: 'height: 0; position: absolute;') -> f:variable(name: 'style')}
 <div class="t3-page-ce {item.wrapperClassName} t3js-page-ce t3js-page-ce-sortable" id="element-tt_content-{item.record.uid}" data-table="tt_content" data-uid="{item.record.uid}" data-language-uid="{item.record.sys_language_uid}" style="{style}">
-    <div class="t3-page-ce-element t3-page-ce-dragitem" id="{item.uniqueId}">
+    <div class="t3-page-ce-element t3-page-ce-dragitem">
         <f:render partial="PageLayout/Record/{item.record.CType}/Header" arguments="{_all}" optional="1">
             <f:render partial="PageLayout/RecordDefault/Header" arguments="{_all}" />
         </f:render>
@@ -19,8 +19,8 @@
         </f:if>
     </div>
     <f:if condition="{allowEditContent} && {item.column.contentEditable} && {column.context.allowNewContent} && {column.active}">
-        <div class="t3-page-ce-actions t3js-page-new-ce" id="colpos-{item.column.columnNumber}-page-{item.context.pageId}-{item.column.uniqueId}">
-            <typo3-backend-new-content-element-wizard-button class="btn btn-default btn-sm" url="{item.newContentAfterUrl}" subject="{newContentTitle}">
+        <div class="t3-page-ce-actions t3js-page-new-ce">
+            <typo3-backend-new-content-element-wizard-button class="btn btn-default btn-sm" url="{item.newContentAfterUrl}" subject="{f:translate(key: 'LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newContentElement')}">
                 <core:icon identifier="actions-plus" />
                 <f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:content" />
             </typo3-backend-new-content-element-wizard-button>
diff --git a/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageLayout.html b/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageLayout.html
index 2de35153b6e4..a26e10243a9a 100644
--- a/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageLayout.html
+++ b/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageLayout.html
@@ -1,5 +1,5 @@
 {namespace be=TYPO3\CMS\Backend\ViewHelpers}
-<f:if condition="{context.drawingConfiguration.languageMode}">
+<f:if condition="{context.drawingConfiguration.languageComparisonMode}">
     <f:then>
         <f:render partial="PageLayout/LanguageColumns" arguments="{_all}" />
     </f:then>
diff --git a/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageModule.html b/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageModule.html
index a97f3c6cab31..818a0b54d8d5 100644
--- a/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageModule.html
+++ b/typo3/sysext/backend/Resources/Private/Templates/PageLayout/PageModule.html
@@ -61,7 +61,7 @@
                 class="form-check-input"
                 name="showHidden"
                 value="1"
-                {f:if(condition:'{hiddenElementsState} == 1', then:'checked="checked"')}
+                {f:if(condition:'{pageLayoutContext.drawingConfiguration.showHidden}', then:'checked="checked"')}
             />
             <label class="form-check-label" for="checkShowHidden">
                 <f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:hiddenCE" /> ({hiddenElementsCount})
diff --git a/typo3/sysext/backend/Tests/Functional/View/Drawing/BackendLayoutRendererTest.php b/typo3/sysext/backend/Tests/Functional/View/Drawing/BackendLayoutRendererTest.php
index 9b85f825287a..75083b490387 100644
--- a/typo3/sysext/backend/Tests/Functional/View/Drawing/BackendLayoutRendererTest.php
+++ b/typo3/sysext/backend/Tests/Functional/View/Drawing/BackendLayoutRendererTest.php
@@ -20,6 +20,7 @@ namespace TYPO3\CMS\Backend\Tests\Functional\View\Drawing;
 use PHPUnit\Framework\Attributes\Test;
 use PHPUnit\Framework\MockObject\MockObject;
 use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout;
+use TYPO3\CMS\Backend\View\BackendLayout\RecordRememberer;
 use TYPO3\CMS\Backend\View\BackendViewFactory;
 use TYPO3\CMS\Backend\View\Drawing\BackendLayoutRenderer;
 use TYPO3\CMS\Backend\View\PageLayoutContext;
@@ -89,7 +90,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             'rows.' => [],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(0, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
     }
 
@@ -104,7 +108,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(1, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(0, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
     }
@@ -123,7 +130,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(2, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(0, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
     }
@@ -143,7 +153,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(1, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(1, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
         foreach ($subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns() as $column) {
@@ -169,7 +182,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(1, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(2, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
         foreach ($subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns() as $column) {
@@ -199,7 +215,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(2, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(2, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
         foreach ($subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns() as $column) {
@@ -235,7 +254,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(2, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(4, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
         foreach ($subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns() as $column) {
@@ -259,7 +281,10 @@ final class BackendLayoutRendererTest extends FunctionalTestCase
             ],
         ];
         $pageLayoutContext = $this->getPageLayoutContext(1100, $configuration);
-        $subject = new BackendLayoutRenderer(new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)));
+        $subject = new BackendLayoutRenderer(
+            new BackendViewFactory($this->get(RenderingContextFactory::class), $this->get(PackageManager::class)),
+            new RecordRememberer()
+        );
         self::assertCount(1, $subject->getGridForPageLayoutContext($pageLayoutContext)->getRows());
         self::assertCount(1, $subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns());
         foreach ($subject->getGridForPageLayoutContext($pageLayoutContext)->getColumns() as $column) {
diff --git a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
index fe48c2265efb..1dafb4d0b6ba 100644
--- a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
@@ -36,6 +36,7 @@ use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\CMS\Core\Resource\Filter\FileNameFilter;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Routing\BackendEntryPointResolver;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\SysLog\Action as SystemLogGenericAction;
 use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
 use TYPO3\CMS\Core\SysLog\Type;
@@ -576,18 +577,23 @@ class BackendUserAuthentication extends AbstractUserAuthentication
     /**
      * Checking if a language value (-1, 0 and >0) is allowed to be edited by the user.
      *
-     * @param int $langValue Language value to evaluate
+     * @param int|SiteLanguage|string $langValue Language value to evaluate
      * @return bool Returns TRUE if the language value is allowed, otherwise FALSE.
      */
     public function checkLanguageAccess($langValue)
     {
         // The users language list must be non-blank - otherwise all languages are allowed.
-        if (trim($this->groupData['allowed_languages']) !== '') {
+        if (trim($this->groupData['allowed_languages']) === '') {
+            return true;
+        }
+        if ($langValue instanceof SiteLanguage) {
+            $langValue = $langValue->getLanguageId();
+        } else {
             $langValue = (int)$langValue;
-            // Language must either be explicitly allowed OR the lang Value be "-1" (all languages)
-            if ($langValue != -1 && !$this->check('allowed_languages', (string)$langValue)) {
-                return false;
-            }
+        }
+        // Language must either be explicitly allowed OR the lang Value be "-1" (all languages)
+        if ($langValue !== -1 && !$this->check('allowed_languages', (string)$langValue)) {
+            return false;
         }
         return true;
     }
diff --git a/typo3/sysext/core/Classes/Site/Entity/NullSite.php b/typo3/sysext/core/Classes/Site/Entity/NullSite.php
index 7d3a23bc0f7d..5358cee3937b 100644
--- a/typo3/sysext/core/Classes/Site/Entity/NullSite.php
+++ b/typo3/sysext/core/Classes/Site/Entity/NullSite.php
@@ -144,7 +144,7 @@ class NullSite implements SiteInterface
         $disabledLanguages = GeneralUtility::intExplode(',', (string)($pageTs['disableLanguages'] ?? ''), true);
         // Do not add the ones that are not allowed by the user
         foreach ($this->languages as $language) {
-            if ($user->checkLanguageAccess($language->getLanguageId()) && !in_array($language->getLanguageId(), $disabledLanguages, true)) {
+            if ($user->checkLanguageAccess($language) && !in_array($language->getLanguageId(), $disabledLanguages, true)) {
                 if ($language->getLanguageId() === 0) {
                     // 0: "Default" language
                     $defaultLanguageLabel = 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage';
diff --git a/typo3/sysext/core/Classes/Site/Entity/Site.php b/typo3/sysext/core/Classes/Site/Entity/Site.php
index d22a2c5f651a..0a97eda58c66 100644
--- a/typo3/sysext/core/Classes/Site/Entity/Site.php
+++ b/typo3/sysext/core/Classes/Site/Entity/Site.php
@@ -261,7 +261,7 @@ class Site implements SiteInterface
 
         // Do not add the ones that are not allowed by the user
         foreach ($this->languages as $language) {
-            if ($user->checkLanguageAccess($language->getLanguageId())) {
+            if ($user->checkLanguageAccess($language)) {
                 $availableLanguages[$language->getLanguageId()] = $language;
             }
         }
diff --git a/typo3/sysext/frontend/Classes/Middleware/SiteBaseRedirectResolver.php b/typo3/sysext/frontend/Classes/Middleware/SiteBaseRedirectResolver.php
index 41a66db9ec88..b555a914dc36 100644
--- a/typo3/sysext/frontend/Classes/Middleware/SiteBaseRedirectResolver.php
+++ b/typo3/sysext/frontend/Classes/Middleware/SiteBaseRedirectResolver.php
@@ -96,7 +96,7 @@ class SiteBaseRedirectResolver implements MiddlewareInterface
     protected function isLanguageEnabled(SiteLanguage $language, ?BackendUserAuthentication $user = null): bool
     {
         // language is hidden, check if a possible backend user is allowed to access the language
-        if ($language->enabled() || $user?->checkLanguageAccess($language->getLanguageId())) {
+        if ($language->enabled() || $user?->checkLanguageAccess($language)) {
             return true;
         }
         return false;
-- 
GitLab