[FEATURE] Show only sys_languages in Backend which are available 72/57672/30
authorGeorg Ringer <georg.ringer@gmail.com>
Tue, 24 Jul 2018 11:00:50 +0000 (13:00 +0200)
committerBenni Mack <benni@typo3.org>
Wed, 22 Aug 2018 13:27:30 +0000 (15:27 +0200)
Instead of rendering all languages, only the ones provided by the
site module are now shown in forms of the FormEngine, in the List
module, the Info->Translation Overview and the Page module.

Fallbacks to pseudo sites are considered if no site configuration
for a page / page tree exists.

Resolves: #85164
Releases: master
Change-Id: I95b394cd3a31773e62f7e2cc40197aceb97705d8
Reviewed-on: https://review.typo3.org/57672
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
21 files changed:
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Classes/Controller/PageLayoutController.php
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseSystemLanguageRows.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Overview.html
typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioDefault.csv [new file with mode: 0644]
typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioTranslationDone.csv [new file with mode: 0644]
typo3/sysext/backend/Tests/Functional/View/PageLayoutViewTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseSystemLanguageRowsTest.php
typo3/sysext/core/Classes/Site/Entity/PseudoSite.php
typo3/sysext/core/Classes/Site/Entity/Site.php
typo3/sysext/core/Classes/Site/Entity/SiteInterface.php
typo3/sysext/core/Classes/Site/Entity/SiteLanguage.php
typo3/sysext/core/Classes/Site/PseudoSiteFinder.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-85164-LanguageRelatedMethods.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-85164-AllowToOnlyShowSiteLanguagesInBE.rst [new file with mode: 0644]
typo3/sysext/info/Classes/Controller/TranslationStatusController.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php
typo3/sysext/recordlist/Classes/Controller/RecordListController.php

index 58eee95..d05354b 100644 (file)
@@ -34,15 +34,18 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
-use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\RedirectResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\RedirectResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Site\PseudoSiteFinder;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
@@ -2219,15 +2222,15 @@ class EditDocumentController
             if ($table === 'pages') {
                 $row = BackendUtility::getRecord($table, $uid, $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']);
                 // Ensure the check is always done against the default language page
             if ($table === 'pages') {
                 $row = BackendUtility::getRecord($table, $uid, $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']);
                 // Ensure the check is always done against the default language page
-                $langRows = $this->getLanguages(
+                $availableLanguages = $this->getLanguages(
                     (int)$row[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] ?: $uid,
                     $table
                 );
             } else {
                     (int)$row[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] ?: $uid,
                     $table
                 );
             } else {
-                $langRows = $this->getLanguages((int)$pid, $table);
+                $availableLanguages = $this->getLanguages((int)$pid, $table);
             }
             // Page available in other languages than default language?
             }
             // Page available in other languages than default language?
-            if (is_array($langRows) && count($langRows) > 1) {
+            if (count($availableLanguages) > 1) {
                 $rowsByLang = [];
                 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
                 // Get record in current language
                 $rowsByLang = [];
                 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
                 // Get record in current language
@@ -2290,41 +2293,42 @@ class EditDocumentController
                     }
                     $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
                     $languageMenu->setIdentifier('_langSelector');
                     }
                     $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
                     $languageMenu->setIdentifier('_langSelector');
-                    foreach ($langRows as $lang) {
-                        if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
-                            $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
-                            // Create url for creating a localized record
-                            $addOption = true;
-                            $href = '';
-                            if ($newTranslation) {
-                                $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
-                                    'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
-                                    'returnUrl' => $this->retUrl
-                                ]);
-
-                                if (array_key_exists(0, $rowsByLang)) {
-                                    $href = BackendUtility::getLinkToDataHandlerAction(
-                                        '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
-                                        $redirectUrl
-                                    );
-                                } else {
-                                    $addOption = false;
-                                }
+                    foreach ($availableLanguages as $language) {
+                        $languageId = $language->getLanguageId();
+                        $selectorOptionLabel = $language->getTitle();
+                        // Create url for creating a localized record
+                        $addOption = true;
+                        $href = '';
+                        if (!isset($rowsByLang[$languageId])) {
+                            // Translation in this language does not exist
+                            $selectorOptionLabel .= ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
+                            $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
+                                'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $languageId,
+                                'returnUrl' => $this->retUrl
+                            ]);
+
+                            if (array_key_exists(0, $rowsByLang)) {
+                                $href = BackendUtility::getLinkToDataHandlerAction(
+                                    '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $languageId,
+                                    $redirectUrl
+                                );
                             } else {
                             } else {
-                                $href = (string)$uriBuilder->buildUriFromRoute('record_edit', [
-                                    'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
-                                    'returnUrl' => $this->retUrl
-                                ]);
+                                $addOption = false;
                             }
                             }
-                            if ($addOption) {
-                                $menuItem = $languageMenu->makeMenuItem()
-                                    ->setTitle($lang['title'] . $newTranslation)
-                                    ->setHref($href);
-                                if ((int)$lang['uid'] === $currentLanguage) {
-                                    $menuItem->setActive(true);
-                                }
-                                $languageMenu->addMenuItem($menuItem);
+                        } else {
+                            $href = (string)$uriBuilder->buildUriFromRoute('record_edit', [
+                                'edit[' . $table . '][' . $rowsByLang[$languageId]['uid'] . ']' => 'edit',
+                                'returnUrl' => $this->retUrl
+                            ]);
+                        }
+                        if ($addOption) {
+                            $menuItem = $languageMenu->makeMenuItem()
+                                ->setTitle($selectorOptionLabel)
+                                ->setHref($href);
+                            if ($languageId === $currentLanguage) {
+                                $menuItem->setActive(true);
                             }
                             }
+                            $languageMenu->addMenuItem($menuItem);
                         }
                     }
                     $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
                         }
                     }
                     $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
@@ -2414,88 +2418,51 @@ class EditDocumentController
     }
 
     /**
     }
 
     /**
-     * Returns sys_language records available for record translations on given page.
+     * Returns languages  available for record translations on given page.
      *
      * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
      *                hidden. If set to another value, the query will select all sys_language records that has a
      *                translation record on that page (and is not hidden, unless you are admin user)
      * @param string $table For pages we want all languages, for other records the languages of the page translations
      *
      * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
      *                hidden. If set to another value, the query will select all sys_language records that has a
      *                translation record on that page (and is not hidden, unless you are admin user)
      * @param string $table For pages we want all languages, for other records the languages of the page translations
-     * @return array Language records including faked record for default language
+     * @return SiteLanguage[] Language
      */
      */
-    protected function getLanguages(int $id, string $table = ''): array
+    protected function getLanguages(int $id, string $table): array
     {
     {
-        $languageService = $this->getLanguageService();
-        $modPageTsConfig = BackendUtility::getPagesTSconfig($id)['mod.']['SHARED.'] ?? [];
-        // Fallback non sprite-configuration
-        if (preg_match('/\\.gif$/', $modPageTsConfig['defaultLanguageFlag'] ?? '')) {
-            $modPageTsConfig['defaultLanguageFlag'] = str_replace(
-                '.gif',
-                '',
-                $modPageTsConfig['defaultLanguageFlag']
-            );
-        }
-        $languages = [
-            0 => [
-                'uid' => 0,
-                'pid' => 0,
-                'hidden' => 0,
-                'title' => $modPageTsConfig['defaultLanguageLabel'] !== ''
-                        ? $modPageTsConfig['defaultLanguageLabel'] . ' (' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage') . ')'
-                        : $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage'),
-                'flag' => $modPageTsConfig['defaultLanguageFlag']
-            ]
-        ];
-
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_language');
-
-        $queryBuilder->select('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
-            ->from('sys_language', 's')
-            ->groupBy('s.uid', 's.pid', 's.hidden', 's.title', 's.flag', 's.sorting')
-            ->orderBy('s.sorting');
-
-        if ($id) {
-            $queryBuilder->getRestrictions()
-                ->removeAll()
+        try {
+            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($id);
+        } catch (SiteNotFoundException $e) {
+            // Check for a pseudo site
+            $site = GeneralUtility::makeInstance(PseudoSiteFinder::class)->getSiteByPageId($id);
+        }
+
+        // Fetch the current translations of this page, to only show the ones where there is a page translation
+        $allLanguages = $site->getAvailableLanguages($this->getBackendUser(), false, $id);
+        if ($table !== 'pages' && $id > 0) {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+            $queryBuilder->getRestrictions()->removeAll()
                 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
                 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
                 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
                 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
-
-            if (!$this->getBackendUser()->isAdmin()) {
-                $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
+            $statement = $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
+                ->from('pages')
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
+                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                    )
+                )
+                ->execute();
+            $availableLanguages = [
+                0 => $allLanguages[0]
+            ];
+            while ($row = $statement->fetch()) {
+                $languageId = (int)$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
+                if (isset($allLanguages[$languageId])) {
+                    $availableLanguages[$languageId] = $allLanguages[$languageId];
+                }
             }
             }
-
-            $this->joinPagesTranslationsForActiveLanguage($queryBuilder, $table, $id);
-        }
-
-        $result = $queryBuilder->execute();
-        while ($row = $result->fetch()) {
-            $languages[$row['uid']] = $row;
-        }
-
-        return $languages;
-    }
-
-    /**
-     * @param QueryBuilder $queryBuilder
-     * @param string $table
-     * @param int $id
-     */
-    public function joinPagesTranslationsForActiveLanguage(QueryBuilder $queryBuilder, string $table, int $id)
-    {
-        // Add join with pages translations to only show active languages
-        if ($table !== 'pages') {
-            $queryBuilder->from('pages', 'o')
-                         ->where(
-                             $queryBuilder->expr()->eq(
-                                 'o.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'],
-                                 $queryBuilder->quoteIdentifier('s.uid')
-                             ),
-                             $queryBuilder->expr()->eq(
-                                 'o.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
-                                 $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
-                             )
-                         );
+            return $availableLanguages;
         }
         }
+        return $allLanguages;
     }
 
     /**
     }
 
     /**
index bb2f43c..2b652bb 100644 (file)
@@ -31,13 +31,13 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
-use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -374,6 +374,10 @@ class PageLayoutController
         $parsedBody = $request->getParsedBody();
         $queryParams = $request->getQueryParams();
 
         $parsedBody = $request->getParsedBody();
         $queryParams = $request->getQueryParams();
 
+        /** @var SiteInterface $currentSite */
+        $currentSite = $request->getAttribute('site');
+        $availableLanguages = $currentSite->getAvailableLanguages($this->getBackendUser(), false, $this->id);
+
         $lang = $this->getLanguageService();
         // MENU-ITEMS:
         $this->MOD_MENU = [
         $lang = $this->getLanguageService();
         // MENU-ITEMS:
         $this->MOD_MENU = [
@@ -391,83 +395,35 @@ class PageLayoutController
         $this->modSharedTSconfig['properties'] = $pageTsConfig['mod.']['SHARED.'] ?? [];
         $this->modTSconfig['properties'] = $pageTsConfig['mod.']['web_layout.'] ?? [];
 
         $this->modSharedTSconfig['properties'] = $pageTsConfig['mod.']['SHARED.'] ?? [];
         $this->modTSconfig['properties'] = $pageTsConfig['mod.']['web_layout.'] ?? [];
 
-        // First, select all localized page records on the current page. Each represents a possibility for a language on the page. Add these to language selector.
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
-        $queryBuilder->getRestrictions()->removeAll();
+        // First, select all localized page records on the current page.
+        // Each represents a possibility for a language on the page. Add these to language selector.
         if ($this->id) {
         if ($this->id) {
-            $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
-                ->from('sys_language')
-                ->join(
-                    'sys_language',
-                    'pages',
-                    'pages',
-                    $queryBuilder->expr()->eq(
-                        'sys_language.uid',
-                        $queryBuilder->quoteIdentifier('pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'])
-                    )
-                )
+            // Compile language data for pid != 0 only. The language drop-down is not shown on pid 0
+            // since pid 0 can't be localized.
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+            $queryBuilder->getRestrictions()->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+            $statement = $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
+                ->from('pages')
                 ->where(
                     $queryBuilder->expr()->eq(
                 ->where(
                     $queryBuilder->expr()->eq(
-                        'pages.deleted',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->eq(
-                        'pages.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
+                        $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
                         $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
                         $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->orX(
-                        $queryBuilder->expr()->gte(
-                            'pages.t3ver_state',
-                            $queryBuilder->createNamedParameter(
-                                (string)new VersionState(VersionState::DEFAULT_STATE),
-                                \PDO::PARAM_INT
-                            )
-                        ),
-                        $queryBuilder->expr()->eq(
-                            'pages.t3ver_wsid',
-                            $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT)
-                        )
-                    )
-                )
-                ->groupBy(
-                    'pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'],
-                    'sys_language.uid',
-                    'sys_language.pid',
-                    'sys_language.tstamp',
-                    'sys_language.hidden',
-                    'sys_language.title',
-                    'sys_language.language_isocode',
-                    'sys_language.static_lang_isocode',
-                    'sys_language.flag',
-                    'sys_language.sorting'
-                )
-                ->orderBy('sys_language.sorting');
-            if (!$this->getBackendUser()->isAdmin()) {
-                $queryBuilder->andWhere(
-                    $queryBuilder->expr()->eq(
-                        'sys_language.hidden',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
                     )
                     )
-                );
+                )->execute();
+            while ($pageTranslation = $statement->fetch()) {
+                $languageId = $pageTranslation[$GLOBALS['TCA']['pages']['ctrl']['languageField']];
+                if (isset($availableLanguages[$languageId])) {
+                    $this->MOD_MENU['language'][$languageId] = $availableLanguages[$languageId]->getTitle();
+                }
             }
             }
-            $statement = $queryBuilder->execute();
-        } else {
-            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
-            $statement = $queryBuilder->select('uid', 'title')
-                ->from('sys_language')
-                ->orderBy('sorting')
-                ->execute();
-        }
-        while ($lRow = $statement->fetch()) {
-            if ($this->getBackendUser()->checkLanguageAccess($lRow['uid'])) {
-                $this->MOD_MENU['language'][$lRow['uid']] = $lRow['title'];
+            // Override the label
+            if (isset($availableLanguages[0])) {
+                $this->MOD_MENU['language'][0] = $availableLanguages[0]->getTitle();
             }
         }
             }
         }
-        // Setting alternative default label:
-        if ((!empty($this->modSharedTSconfig['properties']['defaultLanguageLabel']) || !empty($this->modTSconfig['properties']['defaultLanguageLabel'])) && isset($this->MOD_MENU['language'][0])) {
-            $this->MOD_MENU['language'][0] = $this->modTSconfig['properties']['defaultLanguageLabel'] ? $this->modTSconfig['properties']['defaultLanguageLabel'] : $this->modSharedTSconfig['properties']['defaultLanguageLabel'];
-        }
-        // Initialize the avaiable actions
+        // Initialize the available actions
         $actions = $this->initActions();
         // Clean up settings
         $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $parsedBody['SET'] ?? $queryParams['SET'] ?? [], $this->moduleName);
         $actions = $this->initActions();
         // Clean up settings
         $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $parsedBody['SET'] ?? $queryParams['SET'] ?? [], $this->moduleName);
index 423e174..0116b7f 100644 (file)
@@ -15,10 +15,13 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider;
  */
 
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
  */
 
 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
-use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Site\PseudoSiteFinder;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -35,61 +38,31 @@ class DatabaseSystemLanguageRows implements FormDataProviderInterface
      */
     public function addData(array $result)
     {
      */
     public function addData(array $result)
     {
-        $languageService = $this->getLanguageService();
-
-        $pageTs = $result['pageTsConfig'];
-        $defaultLanguageLabel = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage');
-        if (isset($pageTs['mod.']['SHARED.']['defaultLanguageLabel'])) {
-            $defaultLanguageLabel = $pageTs['mod.']['SHARED.']['defaultLanguageLabel'] . ' (' . $languageService->sL($defaultLanguageLabel) . ')';
-        }
-        $defaultLanguageFlag = 'empty-empty';
-        if (isset($pageTs['mod.']['SHARED.']['defaultLanguageFlag'])) {
-            $defaultLanguageFlag = 'flags-' . $pageTs['mod.']['SHARED.']['defaultLanguageFlag'];
+        $pageIdDefaultLanguage = $result['defaultLanguagePageRow']['uid'] ?? $result['effectivePid'];
+        try {
+            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageIdDefaultLanguage);
+        } catch (SiteNotFoundException $e) {
+            // Check for a pseudo site
+            $site = GeneralUtility::makeInstance(PseudoSiteFinder::class)->getSiteByPageId($pageIdDefaultLanguage);
         }
         }
+        $languages = $site->getAvailableLanguages($this->getBackendUser(), true, $pageIdDefaultLanguage);
 
 
-        $languageRows = [
-            -1 => [
-                // -1: "All" languages
-                'uid' => -1,
-                'title' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages'),
-                // Same as for 0, but iso is used in flex form context only and duplication handled there
-                // @todo: Maybe drop this if flex form language handling is extracted?
-                'iso' => 'DEF',
-                'flagIconIdentifier' => 'flags-multiple',
-            ],
-            0 => [
-                // 0: "Default" language
-                'uid' => 0,
-                'title' => $defaultLanguageLabel,
-                // Default "DEF" is a fallback preparation for flex form iso codes "lDEF"
-                // @todo: Maybe drop this if flex form language handling is extracted?
-                'iso' => 'DEF',
-                'flagIconIdentifier' => $defaultLanguageFlag,
-            ],
-        ];
-
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_language');
-
-        $queryBuilder->getRestrictions()->removeAll();
-
-        $queryResult = $queryBuilder
-            ->select('uid', 'title', 'language_isocode', 'flag')
-            ->from('sys_language')
-            ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)))
-            ->orderBy('sorting')
-            ->execute();
-
-        while ($row = $queryResult->fetch()) {
-            $uid = $row['uid'];
-            $languageRows[$uid] = [
-                'uid' => $uid,
-                'title' => $row['title'],
-                'flagIconIdentifier' => 'flags-' . $row['flag'],
-            ];
-            if (!empty($row['language_isocode'])) {
-                $languageRows[$uid]['iso'] = $row['language_isocode'];
+        $languageRows = [];
+        foreach ($languages as $language) {
+            $languageId = $language->getLanguageId();
+            if ($languageId > 0) {
+                $iso = $language->getTwoLetterIsoCode();
             } else {
             } else {
+                $iso = 'DEF';
+            }
+            $languageRows[$languageId] = [
+                'uid' => $languageId,
+                'title' => $language->getTitle(),
+                'iso' => $iso,
+                'flagIconIdentifier' => $language->getFlagIdentifier()
+            ];
+
+            if (empty($iso)) {
                 // No iso code could be found. This is currently possible in the system but discouraged.
                 // So, code within FormEngine has to be suited to work with an empty iso code. However,
                 // it may impact certain multi language scenarios, so we add a flash message hinting for
                 // No iso code could be found. This is currently possible in the system but discouraged.
                 // So, code within FormEngine has to be suited to work with an empty iso code. However,
                 // it may impact certain multi language scenarios, so we add a flash message hinting for
@@ -99,35 +72,40 @@ class DatabaseSystemLanguageRows implements FormDataProviderInterface
                 // @todo: This could be relaxed again if flex form language handling is extracted,
                 // @todo: since the rest of the FormEngine code does not rely on iso code?
                 $message = sprintf(
                 // @todo: This could be relaxed again if flex form language handling is extracted,
                 // @todo: since the rest of the FormEngine code does not rely on iso code?
                 $message = sprintf(
-                    $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.missingLanguageIsocode'),
-                    $row['title'],
-                    $uid
+                    $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:error.missingLanguageIsocode'),
+                    $language->getTwoLetterIsoCode(),
+                    $languageId
                 );
                 /** @var FlashMessage $flashMessage */
                 $flashMessage = GeneralUtility::makeInstance(
                 );
                 /** @var FlashMessage $flashMessage */
                 $flashMessage = GeneralUtility::makeInstance(
-                        FlashMessage::class,
-                        $message,
-                        '',
-                        FlashMessage::ERROR
+                    FlashMessage::class,
+                    $message,
+                    '',
+                    FlashMessage::ERROR
                 );
                 /** @var $flashMessageService FlashMessageService */
                 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
                 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
                 $defaultFlashMessageQueue->enqueue($flashMessage);
                 );
                 /** @var $flashMessageService FlashMessageService */
                 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
                 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
                 $defaultFlashMessageQueue->enqueue($flashMessage);
-                $languageRows[$uid]['iso'] = '';
             }
         }
             }
         }
-
         $result['systemLanguageRows'] = $languageRows;
         $result['systemLanguageRows'] = $languageRows;
-
         return $result;
     }
 
     /**
      * @return LanguageService
      */
         return $result;
     }
 
     /**
      * @return LanguageService
      */
-    protected function getLanguageService()
+    protected function getLanguageService(): LanguageService
     {
         return $GLOBALS['LANG'];
     }
     {
         return $GLOBALS['LANG'];
     }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
 }
index 85da88f..018d6a9 100644 (file)
@@ -31,8 +31,8 @@ use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
-use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
@@ -41,6 +41,10 @@ use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Service\FlexFormService;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Site\PseudoSiteFinder;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -484,10 +488,18 @@ class PageLayoutView implements LoggerAwareInterface
      * Contains sys language icons and titles
      *
      * @var array
      * Contains sys language icons and titles
      *
      * @var array
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use site languages instead.
      */
     public $languageIconTitles = [];
 
     /**
      */
     public $languageIconTitles = [];
 
     /**
+     * Contains site languages for this page ID
+     *
+     * @var SiteLanguage[]
+     */
+    protected $siteLanguages = [];
+
+    /**
      * Script URL
      *
      * @var string
      * Script URL
      *
      * @var string
@@ -511,6 +523,7 @@ class PageLayoutView implements LoggerAwareInterface
 
     /**
      * @var TranslationConfigurationProvider
 
     /**
      * @var TranslationConfigurationProvider
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
      */
     public $translateTools;
 
      */
     public $translateTools;
 
@@ -630,7 +643,8 @@ class PageLayoutView implements LoggerAwareInterface
             $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
         }
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
             $this->fixedL = $GLOBALS['BE_USER']->uc['titleLen'];
         }
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-        $this->getTranslateTools();
+        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Remove this instance along with the property.
+        $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
         $this->determineScriptUrl();
         $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
         $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
         $this->determineScriptUrl();
         $this->localizationController = GeneralUtility::makeInstance(LocalizationController::class);
         $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
@@ -836,7 +850,6 @@ class PageLayoutView implements LoggerAwareInterface
      */
     public function getTable_tt_content($id)
     {
      */
     public function getTable_tt_content($id)
     {
-        $backendUser = $this->getBackendUser();
         $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getConnectionForTable('tt_content')
             ->getExpressionBuilder();
         $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getConnectionForTable('tt_content')
             ->getExpressionBuilder();
@@ -2062,7 +2075,9 @@ class PageLayoutView implements LoggerAwareInterface
         $allowDragAndDrop = $this->isDragAndDropAllowed($row);
         $additionalIcons = [];
         $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
         $allowDragAndDrop = $this->isDragAndDropAllowed($row);
         $additionalIcons = [];
         $additionalIcons[] = $this->getIcon('tt_content', $row) . ' ';
-        $additionalIcons[] = $langMode ? $this->languageFlag($row['sys_language_uid'], false) : '';
+        if ($langMode && isset($this->siteLanguages[(int)$row['sys_language_uid']])) {
+            $additionalIcons[] = $this->renderLanguageFlag($this->siteLanguages[(int)$row['sys_language_uid']]);
+        }
         // Get record locking status:
         if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
             $additionalIcons[] = '<a href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">'
         // Get record locking status:
         if ($lockInfo = BackendUtility::isRecordLocked('tt_content', $row['uid'])) {
             $additionalIcons[] = '<a href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">'
@@ -2454,7 +2469,7 @@ class PageLayoutView implements LoggerAwareInterface
      *
      * @param int $id Page id where to create the element.
      * @param int $colPos Preset: Column position value
      *
      * @param int $id Page id where to create the element.
      * @param int $colPos Preset: Column position value
-     * @param int $sys_language Preset: Sys langauge value
+     * @param int $sys_language Preset: Sys language value
      * @return string String for onclick attribute.
      * @see getTable_tt_content()
      */
      * @return string String for onclick attribute.
      * @see getTable_tt_content()
      */
@@ -2518,134 +2533,63 @@ class PageLayoutView implements LoggerAwareInterface
      */
     public function languageSelector($id)
     {
      */
     public function languageSelector($id)
     {
-        if ($this->getBackendUser()->check('tables_modify', 'pages')) {
-            // First, select all
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
-            $queryBuilder->getRestrictions()->removeAll();
-            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
-            $statement = $queryBuilder->select('uid', 'title')
-                ->from('sys_language')
-                ->orderBy('sorting')
-                ->execute();
-            $availableTranslations = [];
-            while ($row = $statement->fetch()) {
-                if ($this->getBackendUser()->checkLanguageAccess($row['uid'])) {
-                    $availableTranslations[(int)$row['uid']] = $row['title'];
-                }
+        if (!$this->getBackendUser()->check('tables_modify', 'pages')) {
+            return '';
+        }
+        $id = (int)$id;
+
+        // First, select all languages that are available for the current user
+        $availableTranslations = [];
+        foreach ($this->siteLanguages as $language) {
+            if ($language->getLanguageId() === 0) {
+                continue;
             }
             }
-            // Then, subtract the languages which are already on the page:
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
-            $queryBuilder->getRestrictions()->removeAll();
-            $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
-                ->from('sys_language')
-                ->join(
-                    'sys_language',
-                    'pages',
-                    'pages',
-                    $queryBuilder->expr()->eq('sys_language.uid', $queryBuilder->quoteIdentifier('pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField']))
-                )
-                ->where(
-                    $queryBuilder->expr()->eq(
-                        'pages.deleted',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->eq(
-                        'pages.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
-                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->orX(
-                        $queryBuilder->expr()->gte(
-                            'pages.t3ver_state',
-                            $queryBuilder->createNamedParameter(
-                                (string)new VersionState(VersionState::DEFAULT_STATE),
-                                \PDO::PARAM_INT
-                            )
-                        ),
-                        $queryBuilder->expr()->eq(
-                            'pages.t3ver_wsid',
-                            $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT)
-                        )
-                    )
-                )
-                ->groupBy(
-                    'pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'],
-                    'sys_language.uid',
-                    'sys_language.pid',
-                    'sys_language.tstamp',
-                    'sys_language.hidden',
-                    'sys_language.title',
-                    'sys_language.language_isocode',
-                    'sys_language.static_lang_isocode',
-                    'sys_language.flag',
-                    'sys_language.sorting'
+            $availableTranslations[$language->getLanguageId()] = $language->getTitle();
+        }
+
+        // Then, subtract the languages which are already on the page:
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $queryBuilder->select('uid', $GLOBALS['TCA']['pages']['ctrl']['languageField'])
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
+                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
                 )
                 )
-                ->orderBy('sys_language.sorting');
-            if (!$this->getBackendUser()->isAdmin()) {
-                $queryBuilder->andWhere(
-                    $queryBuilder->expr()->eq(
-                        'sys_language.hidden',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                    )
-                );
-            }
-            $statement = $queryBuilder->execute();
-            while ($row = $statement->fetch()) {
-                unset($availableTranslations[(int)$row['uid']]);
-            }
-            // Remove disallowed languages
-            if (!empty($availableTranslations)
-                && !$this->getBackendUser()->isAdmin()
-                && $this->getBackendUser()->groupData['allowed_languages'] !== ''
-            ) {
-                $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
-                if (!empty($allowed_languages)) {
-                    foreach ($availableTranslations as $key => $value) {
-                        if (!isset($allowed_languages[$key]) && $key != 0) {
-                            unset($availableTranslations[$key]);
-                        }
-                    }
-                }
-            }
-            // Remove disabled languages
-            $disableLanguages = GeneralUtility::trimExplode(
-                ',',
-                BackendUtility::getPagesTSconfig($id)['mod.']['SHARED.']['disableLanguages'] ?? '',
-                true
             );
             );
-            if (!empty($availableTranslations) && !empty($disableLanguages)) {
-                foreach ($disableLanguages as $language) {
-                    if ($language != 0 && isset($availableTranslations[$language])) {
-                        unset($availableTranslations[$language]);
-                    }
-                }
-            }
-            // If any languages are left, make selector:
-            if (!empty($availableTranslations)) {
-                $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->getLL('new_language')) . '</option>';
-                foreach ($availableTranslations as $languageUid => $languageTitle) {
-                    // Build localize command URL to DataHandler (tce_db)
-                    // which redirects to FormEngine (record_edit)
-                    // which, when finished editing should return back to the current page (returnUrl)
-                    $parameters = [
-                        'justLocalized' => 'pages:' . $id . ':' . $languageUid,
-                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
-                    ];
-                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-                    $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
-                    $targetUrl = BackendUtility::getLinkToDataHandlerAction(
-                        '&cmd[pages][' . $id . '][localize]=' . $languageUid,
-                        $redirectUrl
-                    );
-
-                    $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
-                }
+        $statement = $queryBuilder->execute();
+        while ($row = $statement->fetch()) {
+            unset($availableTranslations[(int)$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]]);
+        }
+        // If any languages are left, make selector:
+        if (!empty($availableTranslations)) {
+            $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->getLL('new_language')) . '</option>';
+            foreach ($availableTranslations as $languageUid => $languageTitle) {
+                // Build localize command URL to DataHandler (tce_db)
+                // which redirects to FormEngine (record_edit)
+                // which, when finished editing should return back to the current page (returnUrl)
+                $parameters = [
+                    'justLocalized' => 'pages:' . $id . ':' . $languageUid,
+                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
+                ];
+                $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+                $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
+                $targetUrl = BackendUtility::getLinkToDataHandlerAction(
+                    '&cmd[pages][' . $id . '][localize]=' . $languageUid,
+                    $redirectUrl
+                );
 
 
-                return '<div class="form-inline form-inline-spaced">'
-                    . '<div class="form-group">'
-                    . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
-                    . $output
-                    . '</select></div></div>';
+                $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
             }
             }
+
+            return '<div class="form-inline form-inline-spaced">'
+                . '<div class="form-group">'
+                . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
+                . $output
+                . '</select></div></div>';
         }
         return '';
     }
         }
         return '';
     }
@@ -2951,7 +2895,7 @@ class PageLayoutView implements LoggerAwareInterface
      * @param int $language
      * @return bool
      */
      * @param int $language
      * @return bool
      */
-    protected function checkIfTranslationsExistInLanguage(array $contentElements, $language)
+    protected function checkIfTranslationsExistInLanguage(array $contentElements, int $language)
     {
         // If in default language, you may always create new entries
         // Also, you may override this strict behavior via user TS Config
     {
         // If in default language, you may always create new entries
         // Also, you may override this strict behavior via user TS Config
@@ -2985,10 +2929,11 @@ class PageLayoutView implements LoggerAwareInterface
                 && $this->languageHasTranslationsCache[$language]['hasTranslations']
             ) {
                 $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
                 && $this->languageHasTranslationsCache[$language]['hasTranslations']
             ) {
                 $this->languageHasTranslationsCache[$language]['mode'] = 'mixed';
+                $siteLanguage = $this->siteLanguages[$language];
                 $message = GeneralUtility::makeInstance(
                     FlashMessage::class,
                 $message = GeneralUtility::makeInstance(
                     FlashMessage::class,
-                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $this->languageIconTitles[$language]['title']),
-                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $this->languageIconTitles[$language]['title']),
+                    sprintf($this->getLanguageService()->getLL('staleTranslationWarning'), $siteLanguage->getTitle()),
+                    sprintf($this->getLanguageService()->getLL('staleTranslationWarningTitle'), $siteLanguage->getTitle()),
                     FlashMessage::WARNING
                 );
                 $service = GeneralUtility::makeInstance(FlashMessageService::class);
                     FlashMessage::WARNING
                 );
                 $service = GeneralUtility::makeInstance(FlashMessageService::class);
@@ -3036,7 +2981,8 @@ class PageLayoutView implements LoggerAwareInterface
      */
     public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
     {
      */
     public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
     {
-        $backendUser = $this->getBackendUserAuthentication();
+        $this->resolveSiteLanguages((int)$id);
+        $backendUser = $this->getBackendUser();
         // Setting internal variables:
         // sets the parent id
         $this->id = (int)$id;
         // Setting internal variables:
         // sets the parent id
         $this->id = (int)$id;
@@ -3124,7 +3070,7 @@ class PageLayoutView implements LoggerAwareInterface
         $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
         $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
 
         $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
         $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
 
-        $backendUser = $this->getBackendUserAuthentication();
+        $backendUser = $this->getBackendUser();
 
         // pre-process tables and add sorting instructions
         $tableNames = array_flip(array_keys($GLOBALS['TCA']));
 
         // pre-process tables and add sorting instructions
         $tableNames = array_flip(array_keys($GLOBALS['TCA']));
@@ -3226,8 +3172,6 @@ class PageLayoutView implements LoggerAwareInterface
      */
     public function getSearchBox($formFields = true)
     {
      */
     public function getSearchBox($formFields = true)
     {
-        /** @var $iconFactory IconFactory */
-        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
         $lang = $this->getLanguageService();
         // Setting form-elements, if applicable:
         $formElements = ['', ''];
         $lang = $this->getLanguageService();
         // Setting form-elements, if applicable:
         $formElements = ['', ''];
@@ -3302,7 +3246,7 @@ class PageLayoutView implements LoggerAwareInterface
                                         <button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars(
                 $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search')
             ) . '">
                                         <button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars(
                 $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search')
             ) . '">
-                                            ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render(
+                                            ' . $this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render(
             ) . ' ' . htmlspecialchars(
                 $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.search')
             ) . '
             ) . ' ' . htmlspecialchars(
                 $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.search')
             ) . '
@@ -3323,7 +3267,7 @@ class PageLayoutView implements LoggerAwareInterface
      */
     public function setDispFields()
     {
      */
     public function setDispFields()
     {
-        $backendUser = $this->getBackendUserAuthentication();
+        $backendUser = $this->getBackendUser();
         // Getting from session:
         $dispFields = $backendUser->getModuleData('list/displayFields');
         // If fields has been inputted, then set those as the value and push it to session variable:
         // Getting from session:
         $dispFields = $backendUser->getModuleData('list/displayFields');
         // If fields has been inputted, then set those as the value and push it to session variable:
@@ -3734,7 +3678,7 @@ class PageLayoutView implements LoggerAwareInterface
             case 'edit':
                 // If the listed table is 'pages' we have to request the permission settings for each page:
                 if ($table === 'pages') {
             case 'edit':
                 // If the listed table is 'pages' we have to request the permission settings for each page:
                 if ($table === 'pages') {
-                    $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(
+                    $localCalcPerms = $this->getBackendUser()->calcPerms(
                         BackendUtility::getRecord('pages', $row['uid'])
                     );
                     $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
                         BackendUtility::getRecord('pages', $row['uid'])
                     );
                     $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
@@ -3883,7 +3827,7 @@ class PageLayoutView implements LoggerAwareInterface
      */
     public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
     {
      */
     public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
     {
-        $backendUser = $this->getBackendUserAuthentication();
+        $backendUser = $this->getBackendUser();
         // Init fieldlist array:
         $fieldListArr = [];
         // Check table:
         // Init fieldlist array:
         $fieldListArr = [];
         // Check table:
@@ -4060,7 +4004,7 @@ class PageLayoutView implements LoggerAwareInterface
      */
     protected function getSearchableWebmounts($id, $depth, $perms_clause)
     {
      */
     protected function getSearchableWebmounts($id, $depth, $perms_clause)
     {
-        $backendUser = $this->getBackendUserAuthentication();
+        $backendUser = $this->getBackendUser();
         /** @var PageTreeView $tree */
         $tree = GeneralUtility::makeInstance(PageTreeView::class);
         $tree->init('AND ' . $perms_clause);
         /** @var PageTreeView $tree */
         $tree = GeneralUtility::makeInstance(PageTreeView::class);
         $tree->init('AND ' . $perms_clause);
@@ -4146,14 +4090,6 @@ class PageLayoutView implements LoggerAwareInterface
     }
 
     /**
     }
 
     /**
-     * @return BackendUserAuthentication
-     */
-    protected function getBackendUserAuthentication()
-    {
-        return $GLOBALS['BE_USER'];
-    }
-
-    /**
      * Sets the script url depending on being a module or script request
      */
     protected function determineScriptUrl()
      * Sets the script url depending on being a module or script request
      */
     protected function determineScriptUrl()
@@ -4375,7 +4311,7 @@ class PageLayoutView implements LoggerAwareInterface
     }
 
     /**
     }
 
     /**
-     * Initializes page languages and icons
+     * Initializes page languages
      */
     public function initializeLanguages()
     {
      */
     public function initializeLanguages()
     {
@@ -4407,8 +4343,13 @@ class PageLayoutView implements LoggerAwareInterface
         while ($row = $result->fetch()) {
             $this->pageOverlays[$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]] = $row;
         }
         while ($row = $result->fetch()) {
             $this->pageOverlays[$row[$GLOBALS['TCA']['pages']['ctrl']['languageField']]] = $row;
         }
-
-        $this->languageIconTitles = $this->getTranslateTools()->getSystemLanguages($this->id);
+        // @deprecated $this->languageIconTitles can be removed in TYPO3 v10.0.
+        foreach ($this->siteLanguages as $language) {
+            $this->languageIconTitles[$language->getLanguageId()] = [
+                'title' => $language->getTitle(),
+                'flagIcon' => $language->getFlagIdentifier()
+            ];
+        }
     }
 
     /**
     }
 
     /**
@@ -4417,9 +4358,11 @@ class PageLayoutView implements LoggerAwareInterface
      * @param int $sys_language_uid Sys language uid
      * @param bool $addAsAdditionalText If set to true, only the flag is returned
      * @return string Language icon
      * @param int $sys_language_uid Sys language uid
      * @param bool $addAsAdditionalText If set to true, only the flag is returned
      * @return string Language icon
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. Use Site Languages instead.
      */
     public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
     {
      */
     public function languageFlag($sys_language_uid, $addAsAdditionalText = true)
     {
+        trigger_error('This method will be removed in TYPO3 v10.', E_USER_DEPRECATED);
         $out = '';
         $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
         if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
         $out = '';
         $title = htmlspecialchars($this->languageIconTitles[$sys_language_uid]['title']);
         if ($this->languageIconTitles[$sys_language_uid]['flagIcon']) {
@@ -4437,16 +4380,39 @@ class PageLayoutView implements LoggerAwareInterface
     }
 
     /**
     }
 
     /**
-     * Gets an instance of TranslationConfigurationProvider
+     * Renders the language flag and language title, but only if a icon is given, otherwise just the language
+     *
+     * @param SiteLanguage $language
+     * @return string
+     */
+    protected function renderLanguageFlag(SiteLanguage $language)
+    {
+        $title = htmlspecialchars($language->getTitle());
+        if ($language->getFlagIdentifier()) {
+            $icon = $this->iconFactory->getIcon(
+                $language->getFlagIdentifier(),
+                Icon::SIZE_SMALL
+            )->render();
+            return '<span title="' . $title . '">' . $icon . '</span>&nbsp;' . $title;
+        }
+        return $title;
+    }
+
+    /**
+     * Fetch the site language objects for the given $pageId and store it in $this->siteLanguages
      *
      *
-     * @return TranslationConfigurationProvider
+     * @param int $pageId
+     * @throws SiteNotFoundException
      */
      */
-    protected function getTranslateTools()
+    protected function resolveSiteLanguages(int $pageId)
     {
     {
-        if (!isset($this->translateTools)) {
-            $this->translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
+        try {
+            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
+        } catch (SiteNotFoundException $e) {
+            // Check for a pseudo site
+            $site = GeneralUtility::makeInstance(PseudoSiteFinder::class)->getSiteByPageId($pageId);
         }
         }
-        return $this->translateTools;
+        $this->siteLanguages = $site->getAvailableLanguages($this->getBackendUser(), false, $pageId);
     }
 
     /**
     }
 
     /**
index 3ad92fd..f50c6ae 100644 (file)
@@ -59,7 +59,7 @@
                                                                        </tr>
                                                                <f:for each="{page.siteConfiguration.languages}" as="siteLanguage">
                                                                        <tr>
                                                                        </tr>
                                                                <f:for each="{page.siteConfiguration.languages}" as="siteLanguage">
                                                                        <tr>
-                                                                               <td><core:icon identifier="flags-{siteLanguage.flagIdentifier}" /> {siteLanguage.title}</td>
+                                                                               <td><core:icon identifier="{siteLanguage.flagIdentifier}" /> {siteLanguage.title}</td>
                                                                                <td><a href="{siteLanguage.base}" target="_blank">{siteLanguage.base}</a></td>
                                                                        </tr>
                                                                </f:for>
                                                                                <td><a href="{siteLanguage.base}" target="_blank">{siteLanguage.base}</a></td>
                                                                        </tr>
                                                                </f:for>
diff --git a/typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioDefault.csv b/typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioDefault.csv
new file mode 100644 (file)
index 0000000..82efb2e
--- /dev/null
@@ -0,0 +1,3 @@
+"pages",,,,,,,,,
+,uid,pid,sys_language_uid,deleted,l10n_parent,t3ver_state,t3ver_wsid,title,l10n_source
+,17,0,0,0,0,0,0,Home,0
diff --git a/typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioTranslationDone.csv b/typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioTranslationDone.csv
new file mode 100644 (file)
index 0000000..5342411
--- /dev/null
@@ -0,0 +1,4 @@
+"pages",,,,,,,,,
+,uid,pid,sys_language_uid,deleted,l10n_parent,t3ver_state,t3ver_wsid,title,l10n_source
+,17,0,0,0,0,0,0,Home,0
+,2,0,3,0,17,0,0,"[Translate to polish] Home",17
diff --git a/typo3/sysext/backend/Tests/Functional/View/PageLayoutViewTest.php b/typo3/sysext/backend/Tests/Functional/View/PageLayoutViewTest.php
new file mode 100644 (file)
index 0000000..94afeda
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Backend\Tests\Functional\View;
+
+/*
+ * 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\View\PageLayoutView;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class PageLayoutViewTest extends FunctionalTestCase
+{
+    /**
+     * @var PageLayoutView|AccessibleObjectInterface
+     */
+    private $subject;
+
+    /**
+     * Sets up this test case.
+     */
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        $this->setUpBackendUserFromFixture(1);
+        Bootstrap::initializeLanguageObject();
+
+        $this->subject = $this->getAccessibleMock(PageLayoutView::class, ['dummy']);
+        $this->subject->_set('siteLanguages', [
+            0 => new SiteLanguage(0, '', '/', [
+                'title' => 'default',
+            ]),
+            1 => new SiteLanguage(1, '', '/', [
+                'title' => 'german',
+            ]),
+            2 => new SiteLanguage(2, '', '/', [
+                'title' => 'french',
+            ]),
+            3 => new SiteLanguage(3, '', '/', [
+                'title' => 'polish',
+            ]),
+        ]);
+    }
+
+    /**
+     * @test
+     */
+    public function languageSelectorShowsAllAvailableLanguagesForTranslation()
+    {
+        $this->importCSVDataSet(ORIGINAL_ROOT . 'typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioDefault.csv');
+
+        $result = $this->subject->languageSelector(17);
+
+        $matches = [];
+
+        preg_match_all('/<option value=.+<\/option>/', $result, $matches);
+        $resultingOptions = GeneralUtility::trimExplode('</option>', $matches[0][0], true);
+        $this->assertCount(4, $resultingOptions);
+        // first entry is the empty option
+        $this->assertStringEndsWith('german', $resultingOptions[1]);
+        $this->assertStringEndsWith('french', $resultingOptions[2]);
+        $this->assertStringEndsWith('polish', $resultingOptions[3]);
+    }
+
+    /**
+     * @test
+     */
+    public function languageSelectorDoesNotOfferLanguageIfTranslationHasBeenDoneAlready()
+    {
+        $this->importCSVDataSet(ORIGINAL_ROOT . 'typo3/sysext/backend/Tests/Functional/View/Fixtures/LanguageSelectorScenarioTranslationDone.csv');
+        $result = $this->subject->languageSelector(17);
+
+        $matches = [];
+
+        preg_match_all('/<option value=.+<\/option>/', $result, $matches);
+        $resultingOptions = GeneralUtility::trimExplode('</option>', $matches[0][0], true);
+        $this->assertCount(3, $resultingOptions);
+        // first entry is the empty option
+        $this->assertStringEndsWith('german', $resultingOptions[1]);
+        $this->assertStringEndsWith('french', $resultingOptions[2]);
+    }
+}
index 204ef47..95ae6c7 100644 (file)
@@ -14,18 +14,13 @@ namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
  * The TYPO3 project - inspiring people to share!
  */
 
  * The TYPO3 project - inspiring people to share!
  */
 
-use Doctrine\DBAL\Driver\Statement;
 use Prophecy\Argument;
 use Prophecy\Argument;
-use Prophecy\Prophecy\ObjectProphecy;
 use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows;
 use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
-use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
@@ -35,349 +30,65 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 class DatabaseSystemLanguageRowsTest extends UnitTestCase
 {
     /**
 class DatabaseSystemLanguageRowsTest extends UnitTestCase
 {
     /**
-     * @var DatabaseSystemLanguageRows
+     * @test
      */
      */
-    protected $subject;
-
-    protected function setUp()
+    public function addDataSetsDefaultLanguageAndAllEntries()
     {
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
         $languageService->sL(Argument::cetera())->willReturnArgument(0);
     {
         $languageService = $this->prophesize(LanguageService::class);
         $GLOBALS['LANG'] = $languageService->reveal();
         $languageService->sL(Argument::cetera())->willReturnArgument(0);
-        $this->subject = new DatabaseSystemLanguageRows();
-    }
-
-    protected function tearDown()
-    {
-        GeneralUtility::purgeInstances();
-        parent::tearDown();
-    }
-
-    /**
-     * @test
-     */
-    public function addDataSetsDefaultLanguageAndAllEntries()
-    {
-        $input = [
-            'pageTsConfig' => [],
-        ];
-        $expected = [
-            'pageTsConfig' => [],
-            'systemLanguageRows' => [
-                -1 => [
-                    'uid' => -1,
-                    'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages',
-                    'iso' => 'DEF',
-                    'flagIconIdentifier' => 'flags-multiple',
-                ],
-                0 => [
-                    'uid' => 0,
-                    'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage',
-                    'iso' => 'DEF',
-                    'flagIconIdentifier' => 'empty-empty',
-                ],
-            ],
+        $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
+        $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
+
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
+        $siteProphecy = $this->prophesize(Site::class);
+        $siteFinderProphecy->getSiteByPageId(42)->willReturn($siteProphecy->reveal());
+        $siteLanguageMinusOne = $this->prophesize(SiteLanguage::class);
+        $siteLanguageMinusOne->getLanguageId()->willReturn(-1);
+        $siteLanguageMinusOne->getTitle()->willReturn('All');
+        $siteLanguageMinusOne->getFlagIdentifier()->willReturn('flags-multiple');
+        $siteLanguageZero = $this->prophesize(SiteLanguage::class);
+        $siteLanguageZero->getLanguageId()->willReturn(0);
+        $siteLanguageZero->getTitle()->willReturn('English');
+        $siteLanguageZero->getFlagIdentifier()->willReturn('empty-empty');
+        $siteLanguageOne = $this->prophesize(SiteLanguage::class);
+        $siteLanguageOne->getLanguageId()->willReturn(1);
+        $siteLanguageOne->getTitle()->willReturn('Dutch');
+        $siteLanguageOne->getFlagIdentifier()->willReturn('flag-nl');
+        $siteLanguageOne->getTwoLetterIsoCode()->willReturn('NL');
+        $siteLanguages = [
+            $siteLanguageMinusOne->reveal(),
+            $siteLanguageZero->reveal(),
+            $siteLanguageOne->reveal(),
         ];
         ];
-
-        // Prophecies and revelations for a lot of the database stack classes
-        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
-        $queryBuilderRevelation = $queryBuilderProphecy->reveal();
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
-        $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
-        $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
-        $statementProphecy = $this->prophesize(Statement::class);
-
-        // Register connection pool revelation in framework, this is the entry point used by the system during tests
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-
-        // Simulate method call flow on database objects and verify correct query is built
-        $connectionPoolProphecy->getQueryBuilderForTable('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->select('uid', 'title', 'language_isocode', 'flag')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->from('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->orderBy('sorting')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
-        $expressionBuilderProphecy->eq('pid', 0)->shouldBeCalled()->willReturn('pid = 0');
-        $queryBuilderProphecy->where('pid = 0')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->createNamedParameter(Argument::cetera())->willReturnArgument(0);
-        $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
-        $statementProphecy->fetch()->shouldBeCalledTimes(1)->willReturn(false);
-
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
-    /**
-     * @test
-     */
-    public function addDataSetsDefaultLanguageTitleFromPageTsConfig()
-    {
-        $input = [
-            'pageTsConfig' => [
-                'mod.' => [
-                    'SHARED.' => [
-                        'defaultLanguageLabel' => 'foo',
-                    ],
-                ]
-            ],
-        ];
-
-        // Prophecies and revelations for a lot of the database stack classes
-        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
-        $queryBuilderRevelation = $queryBuilderProphecy->reveal();
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
-        $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
-        $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
-        $statementProphecy = $this->prophesize(Statement::class);
-
-        // Register connection pool revelation in framework, this is the entry point used by the system during tests
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-
-        // Simulate method call flow on database objects and verify correct query is built
-        $connectionPoolProphecy->getQueryBuilderForTable('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->select('uid', 'title', 'language_isocode', 'flag')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->from('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->orderBy('sorting')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
-        $expressionBuilderProphecy->eq('pid', 0)->shouldBeCalled()->willReturn('pid = 0');
-        $queryBuilderProphecy->where('pid = 0')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->createNamedParameter(Argument::cetera())->willReturnArgument(0);
-        $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
-        $statementProphecy->fetch()->shouldBeCalledTimes(1)->willReturn(false);
-
-        $expected = $input;
-        $expected['systemLanguageRows'] = [
-            -1 => [
-                'uid' => -1,
-                'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages',
-                'iso' => 'DEF',
-                'flagIconIdentifier' => 'flags-multiple',
-            ],
-            0 => [
-                'uid' => 0,
-                'title' => 'foo (LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage)',
-                'iso' => 'DEF',
-                'flagIconIdentifier' => 'empty-empty',
-            ],
-        ];
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
-    /**
-     * @test
-     */
-    public function addDataSetsDefaultLanguageFlagFromPageTsConfig()
-    {
-        $input = [
-            'pageTsConfig' => [
-                'mod.' => [
-                    'SHARED.' => [
-                        'defaultLanguageFlag' => 'uk',
-                    ],
-                ]
-            ],
-        ];
-
-        // Prophecies and revelations for a lot of the database stack classes
-        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
-        $queryBuilderRevelation = $queryBuilderProphecy->reveal();
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
-        $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
-        $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
-        $statementProphecy = $this->prophesize(Statement::class);
-
-        // Register connection pool revelation in framework, this is the entry point used by the system during tests
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-
-        // Simulate method call flow on database objects and verify correct query is built
-        $connectionPoolProphecy->getQueryBuilderForTable('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->select('uid', 'title', 'language_isocode', 'flag')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->from('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->orderBy('sorting')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
-        $expressionBuilderProphecy->eq('pid', 0)->shouldBeCalled()->willReturn('pid = 0');
-        $queryBuilderProphecy->where('pid = 0')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->createNamedParameter(Argument::cetera())->willReturnArgument(0);
-        $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
-        $statementProphecy->fetch()->shouldBeCalledTimes(1)->willReturn(false);
-
-        $expected = $input;
-        $expected['systemLanguageRows'] = [
-            -1 => [
-                'uid' => -1,
-                'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages',
-                'iso' => 'DEF',
-                'flagIconIdentifier' => 'flags-multiple',
-            ],
-            0 => [
-                'uid' => 0,
-                'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage',
-                'iso' => 'DEF',
-                'flagIconIdentifier' => 'flags-uk',
-            ],
-        ];
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
-    /**
-     * @test
-     */
-    public function addDataResolvesLanguageIsocodeFromDatabaseField()
-    {
-        $aDatabaseResultRow = [
-            'uid' => 3,
-            'title' => 'french',
-            'language_isocode' => 'fr',
-            'static_lang_isocode' => '',
-            'flag' => 'fr',
-        ];
-
-        // Prophecies and revelations for a lot of the database stack classes
-        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
-        $queryBuilderRevelation = $queryBuilderProphecy->reveal();
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
-        $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
-        $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
-        $statementProphecy = $this->prophesize(Statement::class);
-
-        // Register connection pool revelation in framework, this is the entry point used by the system during tests
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-
-        // Simulate method call flow on database objects and verify correct query is built
-        $connectionPoolProphecy->getQueryBuilderForTable('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->orderBy('sorting')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->select('uid', 'title', 'language_isocode', 'flag')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->from('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
-        $expressionBuilderProphecy->eq('pid', 0)->shouldBeCalled()->willReturn('pid = 0');
-        $queryBuilderProphecy->where('pid = 0')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->createNamedParameter(Argument::cetera())->willReturnArgument(0);
-        $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
-
-        $statementProphecy->fetch()->shouldBeCalledTimes(2)->willReturn($aDatabaseResultRow, false);
-
+        $siteProphecy->getAvailableLanguages(Argument::cetera())->willReturn($siteLanguages);
         $input = [
             'pageTsConfig' => [],
         $input = [
             'pageTsConfig' => [],
+            'effectivePid' => 42,
         ];
         ];
-
         $expected = [
         $expected = [
-            'pageTsConfig' => [],
             'systemLanguageRows' => [
                 -1 => [
                     'uid' => -1,
             'systemLanguageRows' => [
                 -1 => [
                     'uid' => -1,
-                    'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages',
+                    'title' => 'All',
                     'iso' => 'DEF',
                     'flagIconIdentifier' => 'flags-multiple',
                 ],
                 0 => [
                     'uid' => 0,
                     'iso' => 'DEF',
                     'flagIconIdentifier' => 'flags-multiple',
                 ],
                 0 => [
                     'uid' => 0,
-                    'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage',
+                    'title' => 'English',
                     'iso' => 'DEF',
                     'flagIconIdentifier' => 'empty-empty',
                 ],
                     'iso' => 'DEF',
                     'flagIconIdentifier' => 'empty-empty',
                 ],
-                3 => [
-                    'uid' => 3,
-                    'title' => 'french',
-                    'flagIconIdentifier' => 'flags-fr',
-                    'iso' => 'fr',
-                ],
-            ],
-        ];
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
-    /**
-     * @test
-     */
-    public function addDataAddFlashMessageWithMissingIsoCode()
-    {
-        $aDatabaseResultRow = [
-            'uid' => 3,
-            'title' => 'french',
-            'language_isocode' => '',
-            'static_lang_isocode' => '',
-            'flag' => 'fr',
-        ];
-
-        // Prophecies and revelations for a lot of the database stack classes
-        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
-        $queryBuilderRevelation = $queryBuilderProphecy->reveal();
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
-        $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
-        $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
-        $statementProphecy = $this->prophesize(Statement::class);
-
-        // Register connection pool revelation in framework, this is the entry point used by the system during tests
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-
-        // Simulate method call flow on database objects and verify correct query is built
-        $connectionPoolProphecy->getQueryBuilderForTable('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
-        $queryBuilderProphecy->select('uid', 'title', 'language_isocode', 'flag')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->from('sys_language')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->orderBy('sorting')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
-        $expressionBuilderProphecy->eq('pid', 0)->shouldBeCalled()->willReturn('pid = 0');
-        $queryBuilderProphecy->where('pid = 0')->shouldBeCalled()->willReturn($queryBuilderRevelation);
-        $queryBuilderProphecy->createNamedParameter(Argument::cetera())->willReturnArgument(0);
-        $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
-
-        $statementProphecy->fetch()->shouldBeCalledTimes(2)->willReturn($aDatabaseResultRow, false);
-
-        $input = [
-            'pageTsConfig' => [],
-        ];
-
-        // Needed for backendUtility::getRecord()
-        $GLOBALS['TCA']['static_languages'] = [ 'foo' ];
-        $expected = [
-            'pageTsConfig' => [],
-            'systemLanguageRows' => [
-                -1 => [
-                    'uid' => -1,
-                    'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages',
-                    'iso' => 'DEF',
-                    'flagIconIdentifier' => 'flags-multiple',
-                ],
-                0 => [
-                    'uid' => 0,
-                    'title' => 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage',
-                    'iso' => 'DEF',
-                    'flagIconIdentifier' => 'empty-empty',
-                ],
-                3 => [
-                    'uid' => 3,
-                    'title' => 'french',
-                    'flagIconIdentifier' => 'flags-fr',
-                    'iso' => '',
-                ],
+                1 => [
+                    'uid' => 1,
+                    'title' => 'Dutch',
+                    'iso' => 'NL',
+                    'flagIconIdentifier' => 'flag-nl',
+                ]
             ],
         ];
             ],
         ];
-
-        /** @var FlashMessage|ObjectProphecy $flashMessage */
-        $flashMessage = $this->prophesize(FlashMessage::class);
-        GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
-        /** @var FlashMessageService|ObjectProphecy $flashMessageService */
-        $flashMessageService = $this->prophesize(FlashMessageService::class);
-        GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
-        /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
-        $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
-        $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
-
-        $flashMessageQueue->enqueue($flashMessage)->shouldBeCalled();
-
-        $this->assertSame($expected, $this->subject->addData($input));
+        $this->assertSame(array_merge($input, $expected), (new DatabaseSystemLanguageRows())->addData($input));
     }
 }
     }
 }
index 4e06a52..b862e92 100644 (file)
@@ -20,6 +20,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
 use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entity representing a site with legacy configuration (sys_domain) and all available languages in the system (sys_language)
 
 /**
  * Entity representing a site with legacy configuration (sys_domain) and all available languages in the system (sys_language)
@@ -148,15 +149,20 @@ class PseudoSite implements SiteInterface
     }
 
     /**
     }
 
     /**
-     * Fetch the available languages for a specific backend user, used in various places in Backend and Frontend
-     * when a Backend User is authenticated.
+     * @inheritdoc
+     */
+    public function getDefaultLanguage(): SiteLanguage
+    {
+        return reset($this->languages);
+    }
+
+    /**
+     * This takes pageTSconfig into account (unlike Site interface) to find
+     * mod.SHARED.disableLanguages and mod.SHARED.defaultLanguageLabel
      *
      *
-     * @param BackendUserAuthentication $user
-     * @param int $pageId
-     * @param bool $includeAllLanguagesFlag whether to include "-1" into the list, useful for some backend outputs
-     * @return array
+     * @inheritdoc
      */
      */
-    public function getAvailableLanguages(BackendUserAuthentication $user, int $pageId, bool $includeAllLanguagesFlag = false)
+    public function getAvailableLanguages(BackendUserAuthentication $user, bool $includeAllLanguagesFlag = false, int $pageId = null): array
     {
         $availableLanguages = [];
 
     {
         $availableLanguages = [];
 
@@ -167,23 +173,26 @@ class PseudoSite implements SiteInterface
                 'flag' => 'flag-multiple'
             ]);
         }
                 'flag' => 'flag-multiple'
             ]);
         }
+        $pageTs = BackendUtility::getPagesTSconfig($pageId);
+        $pageTs = $pageTs['mod.']['SHARED.'] ?? [];
 
 
+        $disabledLanguages = GeneralUtility::intExplode(',', $pageTs['disableLanguages'] ?? '', true);
         // Do not add the ones that are not allowed by the user
         foreach ($this->languages as $language) {
         // 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->getLanguageId()) && !in_array($language->getLanguageId(), $disabledLanguages, true)) {
                 if ($language->getLanguageId() === 0) {
                 if ($language->getLanguageId() === 0) {
-                    $pageTs = BackendUtility::getPagesTSconfig($pageId);
                     // 0: "Default" language
                     $defaultLanguageLabel = 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage';
                     // 0: "Default" language
                     $defaultLanguageLabel = 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage';
-                    if (isset($pageTs['mod.']['SHARED.']['defaultLanguageLabel'])) {
-                        $defaultLanguageLabel = $pageTs['mod.']['SHARED.']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL($defaultLanguageLabel) . ')';
+                    $defaultLanguageLabel = $this->getLanguageService()->sL($defaultLanguageLabel);
+                    if (isset($pageTs['defaultLanguageLabel'])) {
+                        $defaultLanguageLabel = $pageTs['defaultLanguageLabel'] . ' (' . $defaultLanguageLabel . ')';
                     }
                     }
-                    $defaultLanguageFlag = 'empty-empty';
-                    if (isset($pageTs['mod.']['SHARED.']['defaultLanguageFlag'])) {
-                        $defaultLanguageFlag = 'flags-' . $pageTs['mod.']['SHARED.']['defaultLanguageFlag'];
+                    $defaultLanguageFlag = '';
+                    if (isset($pageTs['defaultLanguageFlag'])) {
+                        $defaultLanguageFlag = 'flags-' . $pageTs['defaultLanguageFlag'];
                     }
                     $language = new SiteLanguage(0, '', $language->getBase(), [
                     }
                     $language = new SiteLanguage(0, '', $language->getBase(), [
-                        'title' => $this->getLanguageService()->sL($defaultLanguageLabel),
+                        'title' => $defaultLanguageLabel,
                         'flag' => $defaultLanguageFlag,
                     ]);
                 }
                         'flag' => $defaultLanguageFlag,
                     ]);
                 }
index 2ab87df..8d78a79 100644 (file)
@@ -108,6 +108,13 @@ class Site implements SiteInterface
                 // So the main site base is used (usually done for default languages)
                 $base = $this->sanitizeBaseUrl(rtrim($this->base, '/') . '/');
             }
                 // So the main site base is used (usually done for default languages)
                 $base = $this->sanitizeBaseUrl(rtrim($this->base, '/') . '/');
             }
+            if (!empty($languageConfiguration['flag'])) {
+                if ($languageConfiguration['flag'] === 'global') {
+                    $languageConfiguration['flag'] = 'flag-multiple';
+                } elseif ($languageConfiguration['flag'] !== 'empty-empty') {
+                    $languageConfiguration['flag'] = 'flags-' . $languageConfiguration['flag'];
+                }
+            }
             $this->languages[$languageUid] = new SiteLanguage(
                 $languageUid,
                 $languageConfiguration['locale'],
             $this->languages[$languageUid] = new SiteLanguage(
                 $languageUid,
                 $languageConfiguration['locale'],
@@ -198,15 +205,17 @@ class Site implements SiteInterface
     }
 
     /**
     }
 
     /**
-     * Fetch the available languages for a specific backend user, used in various places in Backend and Frontend
-     * when a Backend User is authenticated.
-     *
-     * @param BackendUserAuthentication $user
-     * @param int $pageId
-     * @param bool $includeAllLanguagesFlag
-     * @return array
+     * @inheritdoc
+     */
+    public function getDefaultLanguage(): SiteLanguage
+    {
+        return reset($this->languages);
+    }
+
+    /**
+     * @inheritdoc
      */
      */
-    public function getAvailableLanguages(BackendUserAuthentication $user, int $pageId, bool $includeAllLanguagesFlag = false)
+    public function getAvailableLanguages(BackendUserAuthentication $user, bool $includeAllLanguagesFlag = false, int $pageId = null): array
     {
         $availableLanguages = [];
 
     {
         $availableLanguages = [];
 
index d86cddf..4a9a5cd 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\Site\Entity;
  * The TYPO3 project - inspiring people to share!
  */
 
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
 
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
 
@@ -45,6 +46,24 @@ interface SiteInterface
     public function getLanguageById(int $languageId): SiteLanguage;
 
     /**
     public function getLanguageById(int $languageId): SiteLanguage;
 
     /**
+     * Returns the first language that was configured. This is usually language=0
+     *
+     * @return SiteLanguage
+     */
+    public function getDefaultLanguage(): SiteLanguage;
+
+    /**
+     * Fetch the available languages for a specific backend user, used in various places in Backend and Frontend
+     * when a Backend User is authenticated.
+     *
+     * @param BackendUserAuthentication $user the authenticated backend user to check access rights
+     * @param bool $includeAllLanguagesFlag whether "-1" should be included in the values or not.
+     * @param int $pageId usually used for resolving additional information from PageTS, only used for pseudo-sites. uid of the default language row!
+     * @return SiteLanguage[]
+     */
+    public function getAvailableLanguages(BackendUserAuthentication $user, bool $includeAllLanguagesFlag = false, int $pageId = null): array;
+
+    /**
      * Returns a ready-to-use error handler, to be used within the ErrorController
      *
      * @param int $statusCode
      * Returns a ready-to-use error handler, to be used within the ErrorController
      *
      * @param int $statusCode
index 2ad4b89..fb93e0f 100644 (file)
@@ -58,7 +58,7 @@ class SiteLanguage
      * The flag key (like "gb" or "fr") used to be used in TYPO3's Backend.
      * @var string
      */
      * The flag key (like "gb" or "fr") used to be used in TYPO3's Backend.
      * @var string
      */
-    protected $flagIdentifier = 'us';
+    protected $flagIdentifier = '';
 
     /**
      * The iso code for this language (two letter) ISO-639-1
 
     /**
      * The iso code for this language (two letter) ISO-639-1
index 4a888f7..471f2e9 100644 (file)
@@ -121,9 +121,9 @@ class PseudoSiteFinder implements SingletonInterface
     {
         $languageRecords = [
             0 => [
     {
         $languageRecords = [
             0 => [
-                'uid' => 0,
+                'languageId' => 0,
                 'title' => 'Default',
                 'title' => 'Default',
-                'flag' => 'empty-empty',
+                'flag' => '',
             ],
         ];
 
             ],
         ];
 
@@ -137,7 +137,7 @@ class PseudoSiteFinder implements SingletonInterface
         while ($row = $statement->fetch()) {
             $uid = $row['uid'];
             $languageRecords[$uid] = [
         while ($row = $statement->fetch()) {
             $uid = $row['uid'];
             $languageRecords[$uid] = [
-                'uid' => $uid,
+                'languageId' => $uid,
                 'title' => $row['title'],
                 'iso' => $row['language_isocode'] ?? '',
                 'flag' => 'flags-' . $row['flag'],
                 'title' => $row['title'],
                 'iso' => $row['language_isocode'] ?? '',
                 'flag' => 'flags-' . $row['flag'],
index 21a8108..363ee23 100644 (file)
@@ -413,6 +413,7 @@ return [
                     \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class => [
                         'depends' => [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class,
                     \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class => [
                         'depends' => [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseDefaultLanguagePageRow::class,
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class,
                         ],
                     ],
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class,
                         ],
                     ],
@@ -582,11 +583,16 @@ return [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEditRow::class,
                         ],
                     ],
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEditRow::class,
                         ],
                     ],
-                    \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class => [
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseDefaultLanguagePageRow::class => [
                         'depends' => [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow::class,
                         ],
                     ],
                         'depends' => [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow::class,
                         ],
                     ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseDefaultLanguagePageRow::class,
+                        ],
+                    ],
                     \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck::class => [
                         'depends' => [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
                     \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck::class => [
                         'depends' => [
                             \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
@@ -640,6 +646,7 @@ return [
                     ],
                     \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class => [
                         'depends' => [
                     ],
                     \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class => [
                         'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseDefaultLanguagePageRow::class,
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class,
                         ],
                     ],
                             \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class,
                         ],
                     ],
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85164-LanguageRelatedMethods.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85164-LanguageRelatedMethods.rst
new file mode 100644 (file)
index 0000000..5c325e1
--- /dev/null
@@ -0,0 +1,46 @@
+.. include:: ../../Includes.txt
+
+==============================================
+Deprecation: #85164 - Language related methods
+==============================================
+
+See :issue:`85164`
+
+Description
+===========
+
+Various methods related to site language handling have been deprecated:
+
+* :php:`TYPO3\CMS\Info\Controller\TranslationStatusController->getSystemLanguages()`
+* :php:`TYPO3\CMS\Backend\View\PageLayoutView->languageFlag()`
+
+These properties have been deprecated:
+
+* :php:`TYPO3\CMS\Backend\View\PageLayoutView->languageIconTitles`
+* :php:`TYPO3\CMS\Backend\View\PageLayoutView->translateTools`
+
+
+Impact
+======
+
+Calling one of the above methods logs deprecation error level messages.
+
+
+Affected Installations
+======================
+
+Instances with extensions calling one of the above methods.
+
+
+Migration
+=========
+
+Above calls can often be substituted using the :php:`Site` object that is always
+initialized during core bootstrap. In backend HTTP use cases, the object can be retrieved
+using code like this::
+
+    $currentSite = $request->getAttribute('site');
+    $availableLanguages = $currentSite->getAvailableLanguages($this->getBackendUser(), false, $this->id);
+
+
+.. index:: PHP-API, FullyScanned
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-85164-AllowToOnlyShowSiteLanguagesInBE.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-85164-AllowToOnlyShowSiteLanguagesInBE.rst
new file mode 100644 (file)
index 0000000..4865234
--- /dev/null
@@ -0,0 +1,25 @@
+.. include:: ../../Includes.txt
+
+===========================================================
+Important: #85164 - Allow to only show site languages in BE
+===========================================================
+
+See :issue:`85164`
+
+Description
+===========
+
+When the backend shows list of available languages - for instance in the page module
+language selector, when editing records and in the list module - the list of languages
+is now restricted to those defined by the site module.
+
+If there are for instance five language records in the system, but a site configures
+only three of them for a page tree, only those three are considered when rendering
+language drop downs.
+
+In case no site configuration has been created for a tree, all language records are shown. In
+this case the Page TSconfig options :php:`mod.SHARED.defaultLanguageFlag`,
+:php:`mod.SHARED.defaultLanguageLabel` and :php:`mod.SHARED.disableLanguages` settings
+are also considered - those are obsolete if a site configuration exists.
+
+.. index:: Backend, ext:backend
index 5579edf..eae7fa4 100644 (file)
@@ -22,6 +22,9 @@ use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteInterface;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -36,11 +39,17 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
     protected $iconFactory;
 
     /**
     protected $iconFactory;
 
     /**
+     * @var SiteLanguage[]
+     */
+    protected $siteLanguages;
+
+    /**
      * Construct for initialize class variables
      */
     public function __construct()
     {
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
      * Construct for initialize class variables
      */
     public function __construct()
     {
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $this->initializeSiteLanguages();
     }
 
     /**
     }
 
     /**
@@ -62,12 +71,13 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
             ]
         ];
         // Languages:
             ]
         ];
         // Languages:
-        $lang = $this->getSystemLanguages();
-        $menuArray['lang'] = [
-            0 => '[All]'
-        ];
-        foreach ($lang as $langRec) {
-            $menuArray['lang'][$langRec['uid']] = $langRec['title'];
+        $menuArray['lang'] = [];
+        foreach ($this->siteLanguages as $language) {
+            if ($language->getLanguageId() === 0) {
+                $menuArray['lang'][0] = '[All]';
+            } else {
+                $menuArray['lang'][$language->getLanguageId()] = $language->getTitle();
+            }
         }
         return $menuArray;
     }
         }
         return $menuArray;
     }
@@ -125,8 +135,6 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
     public function renderL10nTable(&$tree)
     {
         $lang = $this->getLanguageService();
     public function renderL10nTable(&$tree)
     {
         $lang = $this->getLanguageService();
-        // System languages retrieved:
-        $languages = $this->getSystemLanguages();
         // Title length:
         $titleLen = $this->getBackendUser()->uc['titleLen'];
         // Put together the TREE:
         // Title length:
         $titleLen = $this->getBackendUser()->uc['titleLen'];
         // Put together the TREE:
@@ -195,18 +203,16 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
             $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
                     'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
                 ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], 0) . '</td>';
             $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
                     'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
                 ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], 0) . '</td>';
-            $disableLanguages = GeneralUtility::trimExplode(
-                ',',
-                BackendUtility::getPagesTSconfig($data['row']['uid'])['mod.']['SHARED.']['disableLanguages'] ?? '',
-                true
-            );
             // Traverse system languages:
             // Traverse system languages:
-            foreach ($languages as $langRow) {
-                if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === (int)$langRow['uid']) {
-                    $row = $this->getLangStatus($data['row']['uid'], $langRow['uid']);
-                    $info = '';
+            foreach ($this->siteLanguages as $siteLanguage) {
+                $languageId = $siteLanguage->getLanguageId();
+                if ($languageId === 0) {
+                    continue;
+                }
+                if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === $languageId) {
+                    $row = $this->getLangStatus($data['row']['uid'], $languageId);
                     if (is_array($row)) {
                     if (is_array($row)) {
-                        $langRecUids[$langRow['uid']][] = $row['uid'];
+                        $langRecUids[$languageId][] = $row['uid'];
                         $status = $row['_HIDDEN'] ? (GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '') : 'success';
                         $icon = $this->iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL)->render();
                         $info = $icon . htmlspecialchars(
                         $status = $row['_HIDDEN'] ? (GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '') : 'success';
                         $icon = $this->iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL)->render();
                         $info = $icon . htmlspecialchars(
@@ -218,7 +224,7 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
                             ) . '</div>' : '');
                         $tCells[] = '<td class="' . $status . ' col-border-left">' .
                             '<a href="#" onclick="' . htmlspecialchars(
                             ) . '</div>' : '');
                         $tCells[] = '<td class="' . $status . ' col-border-left">' .
                             '<a href="#" onclick="' . htmlspecialchars(
-                                'top.loadEditId(' . (int)$data['row']['uid'] . ',"&SET[language]=' . $langRow['uid'] . '"); return false;'
+                                'top.loadEditId(' . (int)$data['row']['uid'] . ',"&SET[language]=' . $languageId . '"); return false;'
                             ) . '" title="' . $lang->sL(
                                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageLang'
                             ) . '">' . $info . '</a></td>';
                             ) . '" title="' . $lang->sL(
                                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageLang'
                             ) . '">' . $info . '</a></td>';
@@ -232,7 +238,7 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
                             ],
                             'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
                         ]);
                             ],
                             'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
                         ]);
-                        $info = str_replace('###LANG_UID###', $langRow['uid'], $viewPageLink);
+                        $info = str_replace('###LANG_UID###', $languageId, $viewPageLink);
                         $info .= '<a href="' . htmlspecialchars($editUrl)
                             . '" class="btn btn-default" title="' . $lang->sL(
                                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editLanguageOverlayRecord'
                         $info .= '<a href="' . htmlspecialchars($editUrl)
                             . '" class="btn btn-default" title="' . $lang->sL(
                                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editLanguageOverlayRecord'
@@ -240,25 +246,19 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
                         $tCells[] = '<td class="' . $status . '"><div class="btn-group">' . $info . '</div></td>';
                         $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
                                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
                         $tCells[] = '<td class="' . $status . '"><div class="btn-group">' . $info . '</div></td>';
                         $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
                                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
-                            ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], $langRow['uid']) . '</td>';
+                            ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], $languageId) . '</td>';
                     } else {
                     } else {
-                        if (in_array($langRow['uid'], $disableLanguages)) {
-                            // Language has been disabled for this page
-                            $status = 'danger';
-                            $info = '';
-                        } else {
-                            $status = GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '';
-                            $info = '<div class="btn-group"><label class="btn btn-default btn-checkbox">';
-                            $info .= '<input type="checkbox" data-lang="' . (int)$langRow['uid'] . '" name="newOL[' . $langRow['uid'] . '][' . $data['row']['uid'] . ']" value="1" />';
-                            $info .= '<span class="t3-icon fa"></span></label></div>';
-                            $newOL_js[$langRow['uid']] .=
-                                ' +(document.webinfoForm['
-                                . GeneralUtility::quoteJSvalue('newOL[' . $langRow['uid'] . '][' . $data['row']['uid'] . ']')
-                                . '].checked ? '
-                                . GeneralUtility::quoteJSvalue('&edit[pages][' . $data['row']['uid'] . ']=new')
-                                . ' : \'\')'
-                            ;
-                        }
+                        $status = GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '';
+                        $info = '<div class="btn-group"><label class="btn btn-default btn-checkbox">';
+                        $info .= '<input type="checkbox" data-lang="' . $languageId . '" name="newOL[' . $languageId . '][' . $data['row']['uid'] . ']" value="1" />';
+                        $info .= '<span class="t3-icon fa"></span></label></div>';
+                        $newOL_js[$languageId] .=
+                            ' +(document.webinfoForm['
+                            . GeneralUtility::quoteJSvalue('newOL[' . $languageId . '][' . $data['row']['uid'] . ']')
+                            . '].checked ? '
+                            . GeneralUtility::quoteJSvalue('&edit[pages][' . $data['row']['uid'] . ']=new')
+                            . ' : \'\')'
+                        ;
                         $tCells[] = '<td class="' . $status . ' col-border-left">&nbsp;</td>';
                         $tCells[] = '<td class="' . $status . '">&nbsp;</td>';
                         $tCells[] = '<td class="' . $status . '">' . $info . '</td>';
                         $tCells[] = '<td class="' . $status . ' col-border-left">&nbsp;</td>';
                         $tCells[] = '<td class="' . $status . '">&nbsp;</td>';
                         $tCells[] = '<td class="' . $status . '">' . $info . '</td>';
@@ -294,16 +294,20 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
         $tCells[] = '<td class="col-border-left" colspan="2">' . $lang->sL(
                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_default'
             ) . '&nbsp;' . $editIco . '</td>';
         $tCells[] = '<td class="col-border-left" colspan="2">' . $lang->sL(
                 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_default'
             ) . '&nbsp;' . $editIco . '</td>';
-        foreach ($languages as $langRow) {
-            if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === (int)$langRow['uid']) {
+        foreach ($this->siteLanguages as $siteLanguage) {
+            $languageId = $siteLanguage->getLanguageId();
+            if ($languageId === 0) {
+                continue;
+            }
+            if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === $languageId) {
                 // Title:
                 // Title:
-                $tCells[] = '<td class="col-border-left">' . htmlspecialchars($langRow['title']) . '</td>';
+                $tCells[] = '<td class="col-border-left">' . htmlspecialchars($siteLanguage->getTitle()) . '</td>';
                 // Edit language overlay records:
                 // Edit language overlay records:
-                if (is_array($langRecUids[$langRow['uid']])) {
+                if (is_array($langRecUids[$languageId])) {
                     $editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
                         'edit' => [
                             'pages' => [
                     $editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
                         'edit' => [
                             'pages' => [
-                                implode(',', $langRecUids[$langRow['uid']]) => 'edit'
+                                implode(',', $langRecUids[$languageId]) => 'edit'
                             ]
                         ],
                         'columnsOnly' => 'title,nav_title,hidden',
                             ]
                         ],
                         'columnsOnly' => 'title,nav_title,hidden',
@@ -317,15 +321,15 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
                     $editButton = '';
                 }
                 // Create new overlay records:
                     $editButton = '';
                 }
                 // Create new overlay records:
-                $params = '&columnsOnly=title,hidden,sys_language_uid&overrideVals[pages][sys_language_uid]=' . $langRow['uid'];
+                $params = '&columnsOnly=title,hidden,sys_language_uid&overrideVals[pages][sys_language_uid]=' . $languageId;
                 $onClick = BackendUtility::editOnClick($params);
                 $onClick = BackendUtility::editOnClick($params);
-                if (!empty($newOL_js[$langRow['uid']])) {
+                if (!empty($newOL_js[$languageId])) {
                     $onClickArray = explode('?', $onClick, 2);
                     $lastElement = array_pop($onClickArray);
                     $onClickArray = explode('?', $onClick, 2);
                     $lastElement = array_pop($onClickArray);
-                    $onClickArray[] = '\'' . $newOL_js[$langRow['uid']] . ' + \'&' . $lastElement;
+                    $onClickArray[] = '\'' . $newOL_js[$languageId] . ' + \'&' . $lastElement;
                     $onClick = implode('?', $onClickArray);
                 }
                     $onClick = implode('?', $onClickArray);
                 }
-                $newButton = '<a href="#" class="btn btn-default disabled t3js-language-new-' . (int)$langRow['uid'] . '" onclick="' . htmlspecialchars($onClick)
+                $newButton = '<a href="#" class="btn btn-default disabled t3js-language-new-' . $languageId . '" onclick="' . htmlspecialchars($onClick)
                     . '" title="' . $lang->sL(
                         'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_getlangsta_createNewTranslationHeaders'
                     ) . '">' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . '</a>';
                     . '" title="' . $lang->sL(
                         'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_getlangsta_createNewTranslationHeaders'
                     ) . '">' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . '</a>';
@@ -355,9 +359,11 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
      * Selects all system languages (from sys_language)
      *
      * @return array System language records in an array.
      * Selects all system languages (from sys_language)
      *
      * @return array System language records in an array.
+     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10
      */
     public function getSystemLanguages()
     {
      */
     public function getSystemLanguages()
     {
+        trigger_error('This method will be removed in TYPO3 v10.', E_USER_DEPRECATED);
         if (!$this->getBackendUser()->isAdmin() && $this->getBackendUser()->groupData['allowed_languages'] !== '') {
             $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
         }
         if (!$this->getBackendUser()->isAdmin() && $this->getBackendUser()->groupData['allowed_languages'] !== '') {
             $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
         }
@@ -433,7 +439,7 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
     public function getContentElementCount($pageId, $sysLang)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
     public function getContentElementCount($pageId, $sysLang)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('pages');
+            ->getQueryBuilderForTable('tt_content');
         $queryBuilder->getRestrictions()
             ->removeAll()
             ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
         $queryBuilder->getRestrictions()
             ->removeAll()
             ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
@@ -477,4 +483,15 @@ class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunc
     {
         return $GLOBALS['BE_USER'];
     }
     {
         return $GLOBALS['BE_USER'];
     }
+
+    /**
+     * Since the AbstractFunctionModule cannot access the current request yet, we'll do it "old school"
+     * to fetch the Site based on the current ID.
+     */
+    protected function initializeSiteLanguages()
+    {
+        /** @var SiteInterface $currentSite */
+        $currentSite = $GLOBALS['TYPO3_REQUEST']->getAttribute('site');
+        $this->siteLanguages = $currentSite->getAvailableLanguages($this->getBackendUser(), false, (int)$this->pObj->id);
+    }
 }
 }
index 492144b..a45fdab 100644 (file)
@@ -2977,4 +2977,18 @@ return [
             'Deprecation-84375-ProtectedMethodsAndPropertiesInPageLayoutController.rst',
         ],
     ],
             'Deprecation-84375-ProtectedMethodsAndPropertiesInPageLayoutController.rst',
         ],
     ],
+    'TYPO3\CMS\Info\Controller\TranslationStatusController->getSystemLanguages' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-85164-LanguageRelatedMethods.rst'
+        ],
+    ],
+    'TYPO3\CMS\Backend\View\PageLayoutView->languageFlag' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 2,
+        'restFiles' => [
+            'Deprecation-85164-LanguageRelatedMethods.rst'
+        ],
+    ],
 ];
 ];
index 37d7636..1c8dce1 100644 (file)
@@ -471,4 +471,14 @@ return [
             'Deprecation-85445-TemplateService-getFileName.rst'
         ],
     ],
             'Deprecation-85445-TemplateService-getFileName.rst'
         ],
     ],
+    'TYPO3\CMS\Backend\View\PageLayoutView->languageIconTitles' => [
+        'restFiles' => [
+            'Deprecation-85164-LanguageRelatedMethods.rst'
+        ],
+    ],
+    'TYPO3\CMS\Backend\View\PageLayoutView->translateTools' => [
+        'restFiles' => [
+            'Deprecation-85164-LanguageRelatedMethods.rst'
+        ],
+    ],
 ];
 ];
index d941c18..3ef038f 100644 (file)
@@ -24,7 +24,8 @@ use TYPO3\CMS\Backend\Template\ModuleTemplate;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
@@ -33,10 +34,11 @@ use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Site\Entity\SiteInterface;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
 
 /**
 use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
 
 /**
@@ -193,6 +195,16 @@ class RecordListController
     protected $moduleTemplate;
 
     /**
     protected $moduleTemplate;
 
     /**
+     * @var SiteInterface
+     */
+    protected $site;
+
+    /**
+     * @var SiteLanguage[]
+     */
+    protected $siteLanguages = [];
+
+    /**
      * Constructor
      */
     public function __construct()
      * Constructor
      */
     public function __construct()
@@ -582,6 +594,8 @@ class RecordListController
      */
     public function mainAction(ServerRequestInterface $request): ResponseInterface
     {
      */
     public function mainAction(ServerRequestInterface $request): ResponseInterface
     {
+        $this->site = $request->getAttribute('site');
+        $this->siteLanguages = $this->site->getAvailableLanguages($this->getBackendUserAuthentication(), false, (int)$this->id);
         BackendUtility::lockRecords();
         $GLOBALS['SOBE'] = $this;
         $this->init();
         BackendUtility::lockRecords();
         $GLOBALS['SOBE'] = $this;
         $this->init();
@@ -601,136 +615,61 @@ class RecordListController
      */
     protected function languageSelector(int $id): string
     {
      */
     protected function languageSelector(int $id): string
     {
-        if ($this->getBackendUserAuthentication()->check('tables_modify', 'pages')) {
-            // First, select all
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
-            $queryBuilder->getRestrictions()->removeAll();
-            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
-            $statement = $queryBuilder->select('uid', 'title')
-                ->from('sys_language')
-                ->orderBy('sorting')
-                ->execute();
-            $availableTranslations = [];
-            while ($row = $statement->fetch()) {
-                if ($this->getBackendUserAuthentication()->checkLanguageAccess($row['uid'])) {
-                    $availableTranslations[(int)$row['uid']] = $row['title'];
-                }
+        if (!$this->getBackendUserAuthentication()->check('tables_modify', 'pages')) {
+            return '';
+        }
+        $availableTranslations = [];
+        foreach ($this->siteLanguages as $siteLanguage) {
+            if ($siteLanguage->getLanguageId() === 0) {
+                continue;
             }
             }
-            // Then, subtract the languages which are already on the page:
-            $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
-            $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
-            $queryBuilder->getRestrictions()->removeAll();
-            $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
-                ->from('sys_language')
-                ->join(
-                    'sys_language',
-                    'pages',
-                    'pages',
-                    $queryBuilder->expr()->eq('sys_language.uid', $queryBuilder->quoteIdentifier('pages.' . $languageField))
-                )
-                ->where(
-                    $queryBuilder->expr()->eq(
-                        'pages.deleted',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->eq(
-                        'pages.' . $localizationParentField,
-                        $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->orX(
-                        $queryBuilder->expr()->gte(
-                            'pages.t3ver_state',
-                            $queryBuilder->createNamedParameter(
-                                (string)new VersionState(VersionState::DEFAULT_STATE),
-                                \PDO::PARAM_INT
-                            )
-                        ),
-                        $queryBuilder->expr()->eq(
-                            'pages.t3ver_wsid',
-                            $queryBuilder->createNamedParameter($this->getBackendUserAuthentication()->workspace, \PDO::PARAM_INT)
-                        )
-                    )
-                )
-                ->groupBy(
-                    'pages.' . $languageField,
-                    'sys_language.uid',
-                    'sys_language.pid',
-                    'sys_language.tstamp',
-                    'sys_language.hidden',
-                    'sys_language.title',
-                    'sys_language.language_isocode',
-                    'sys_language.static_lang_isocode',
-                    'sys_language.flag',
-                    'sys_language.sorting'
+            $availableTranslations[$siteLanguage->getLanguageId()] = $siteLanguage->getTitle();
+        }
+        // Then, subtract the languages which are already on the page:
+        $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
+        $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $statement = $queryBuilder->select('uid', $languageField)
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $localizationParentField,
+                    $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
                 )
                 )
-                ->orderBy('sys_language.sorting');
-            if (!$this->getBackendUserAuthentication()->isAdmin()) {
-                $queryBuilder->andWhere(
-                    $queryBuilder->expr()->eq(
-                        'sys_language.hidden',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                    )
+            )
+            ->execute();
+        while ($pageTranslation = $statement->fetch()) {
+            unset($availableTranslations[(int)$pageTranslation[$languageField]]);
+        }
+        // If any languages are left, make selector:
+        if (!empty($availableTranslations)) {
+            $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:new_language')) . '</option>';
+            foreach ($availableTranslations as $languageUid => $languageTitle) {
+                // Build localize command URL to DataHandler (tce_db)
+                // which redirects to FormEngine (record_edit)
+                // which, when finished editing should return back to the current page (returnUrl)
+                $parameters = [
+                    'justLocalized' => 'pages:' . $id . ':' . $languageUid,
+                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
+                ];
+                $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+                $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
+                $targetUrl = BackendUtility::getLinkToDataHandlerAction(
+                    '&cmd[pages][' . $id . '][localize]=' . $languageUid,
+                    $redirectUrl
                 );
                 );
-            }
-            $statement = $queryBuilder->execute();
-            while ($row = $statement->fetch()) {
-                unset($availableTranslations[(int)$row['uid']]);
-            }
-            // Remove disallowed languages
-            if (!empty($availableTranslations)
-                && !$this->getBackendUserAuthentication()->isAdmin()
-                && $this->getBackendUserAuthentication()->groupData['allowed_languages'] !== ''
-            ) {
-                $allowed_languages = array_flip(explode(',', $this->getBackendUserAuthentication()->groupData['allowed_languages']));
-                if (!empty($allowed_languages)) {
-                    foreach ($availableTranslations as $key => $value) {
-                        if (!isset($allowed_languages[$key]) && $key != 0) {
-                            unset($availableTranslations[$key]);
-                        }
-                    }
-                }
-            }
-            // Remove disabled languages
-            $disableLanguages = GeneralUtility::trimExplode(
-                ',',
-                BackendUtility::getPagesTSconfig($id)['mod.']['SHARED.']['disableLanguages'] ?? '',
-                true
-            );
-            if (!empty($availableTranslations) && !empty($disableLanguages)) {
-                foreach ($disableLanguages as $language) {
-                    if ($language != 0 && isset($availableTranslations[$language])) {
-                        unset($availableTranslations[$language]);
-                    }
-                }
-            }
-            // If any languages are left, make selector:
-            if (!empty($availableTranslations)) {
-                $output = '<option value="">' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:new_language')) . '</option>';
-                foreach ($availableTranslations as $languageUid => $languageTitle) {
-                    // Build localize command URL to DataHandler (tce_db)
-                    // which redirects to FormEngine (record_edit)
-                    // which, when finished editing should return back to the current page (returnUrl)
-                    $parameters = [
-                        'justLocalized' => 'pages:' . $id . ':' . $languageUid,
-                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
-                    ];
-                    $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-                    $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $parameters);
-                    $targetUrl = BackendUtility::getLinkToDataHandlerAction(
-                        '&cmd[pages][' . $id . '][localize]=' . $languageUid,
-                        $redirectUrl
-                    );
-
-                    $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
-                }
 
 
-                return '<div class="form-inline form-inline-spaced">'
-                    . '<div class="form-group">'
-                    . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
-                    . $output
-                    . '</select></div></div>';
+                $output .= '<option value="' . htmlspecialchars($targetUrl) . '">' . htmlspecialchars($languageTitle) . '</option>';
             }
             }
+
+            return '<div class="form-inline form-inline-spaced">'
+                . '<div class="form-group">'
+                . '<select class="form-control input-sm" name="createNewLanguage" onchange="window.location.href=this.options[this.selectedIndex].value">'
+                . $output
+                . '</select></div></div>';
         }
         return '';
     }
         }
         return '';
     }