Commit 650d5d7b authored by Benni Mack's avatar Benni Mack Committed by Christian Kuhn
Browse files

[FEATURE] Use database field be_users.lang for UI language

Historically (even before TYPO3 3.3.0), the UI language (the language of
the TYPO3 Backend of a user) is stored in ->uc['lang'], which is only
filled from the database field be_users.lang when a user logs in the
first time.
be_user.lang is/was used for admins when creating users to set a default
language when the user first logs in for the first time, but is never
used afterwards.

However, using "uc[lang]" in various places does not make life easier
because uc always needs to be unpacked (e.g. for sending bulk mails
to users, or changing languages for users as admins, listing
users' preferred languages in list module).

For this reason, be_users.lang now defines the users' UI language,
however uc[lang] is synced on each login of a user now, if the language
has changed.

In addition, the TCA for be_users.lang is now handled via an
itemsProcFunc, making uncached requests a tiny bit faster as Locales are
not populated during TCA creation, and allows for more features such as
dynamically showing available/downloaded languages in a be_users
FormEngine field.

The Setup module now updates be_users.lang instead of uc[lang],
which makes it easier in the future to use native TCA for rendering
the setup module fields.

An upgrade wizard migrates all "uc[lang]" values into "be_users.lang"
and ensures that empty values are never used, instead
"default" (= English) is added to all user records to have an explicit
value (also for new users).

Resolves: #93663
Releases: master
Change-Id: Icb8e07f3fabb1f3022f3adcbdce6dc9efd790302
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68192


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 5db2a9dc
......@@ -127,7 +127,7 @@ class PasswordReset implements LoggerAwareInterface
}
$queryBuilder = $this->getPreparedQueryBuilder();
$users = $queryBuilder
->select('uid', 'email', 'username', 'realName', 'uc', 'lang')
->select('uid', 'email', 'username', 'realName', 'lang')
->from('be_users')
->andWhere(
$queryBuilder->expr()->eq('email', $queryBuilder->createNamedParameter($emailAddress))
......@@ -188,7 +188,6 @@ class PasswordReset implements LoggerAwareInterface
*/
protected function sendResetEmail(ServerRequestInterface $request, Context $context, array $user, string $emailAddress): void
{
$uc = unserialize($user['uc'] ?? '', ['allowed_classes' => false]);
$resetLink = $this->generateResetLinkForUser($context, (int)$user['uid'], (string)$user['email']);
$emailObject = GeneralUtility::makeInstance(FluidEmail::class);
$emailObject
......@@ -196,7 +195,7 @@ class PasswordReset implements LoggerAwareInterface
->setRequest($request)
->assign('name', $user['realName'])
->assign('email', $user['email'])
->assign('language', $uc['lang'] ?? $user['lang'] ?: 'default')
->assign('language', $user['lang'] ?: 'default')
->assign('resetLink', $resetLink)
->setTemplate('PasswordReset/ResetRequested');
......
......@@ -46,6 +46,7 @@ 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\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Page\PageRenderer;
......@@ -560,6 +561,22 @@ class EditDocumentController
if ($this->doSave === true) {
$tce->process_datamap();
$tce->process_cmdmap();
// Update the module menu for the current backend user, as they updated their UI language
$currentUserId = (int)($beUser->user[$beUser->userid_column] ?? 0);
if ($currentUserId
&& (string)($this->data['be_users'][$currentUserId]['lang'] ?? '') !== ''
&& $this->data['be_users'][$currentUserId]['lang'] !== $beUser->user['lang']
) {
$newLanguageKey = $this->data['be_users'][$currentUserId]['lang'];
// Update the current backend user language as well
$beUser->user['lang'] = $newLanguageKey;
// Re-create LANG to have the current request updated the translated page as well
$this->getLanguageService()->init($newLanguageKey);
$this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
BackendUtility::setUpdateSignal('updateModuleMenu');
BackendUtility::setUpdateSignal('updateTopbar');
}
}
// If pages are being edited, we set an instruction about updating the page tree after this operation.
if ($tce->pagetreeNeedsRefresh
......
......@@ -114,7 +114,7 @@ class EmailLoginNotification implements LoggerAwareInterface
->assignMultiple([
'user' => $user->user,
'prefix' => $subjectPrefix,
'language' => $user->uc['lang'] ?? 'default',
'language' => ($user->user['lang'] ?? '') ?: 'default',
'headline' => $headline
]);
try {
......
......@@ -2277,8 +2277,9 @@ TCAdefaults.sys_note.email = ' . $this->user['email'];
$this->overrideUC();
$updated = true;
}
// Setting default lang from be_user record.
if (!isset($this->uc['lang'])) {
// Setting default lang from be_user record, also update for backwards-compatibility
// @deprecated This will be removed in TYPO3 v12
if (!isset($this->uc['lang']) || $this->uc['lang'] !== $this->user['lang']) {
$this->uc['lang'] = $this->user['lang'];
$updated = true;
}
......
......@@ -112,7 +112,7 @@ class LanguageService
/**
* Initializes the language to fetch XLF labels for.
* $languageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
* $languageService->init($GLOBALS['BE_USER']->uc['lang']);
* $languageService->init($GLOBALS['BE_USER']->user['lang']);
*
* @throws \RuntimeException
* @param string $languageKey The language key (two character string from backend users profile)
......@@ -370,8 +370,8 @@ class LanguageService
public static function createFromUserPreferences(?AbstractUserAuthentication $user): self
{
if ($user && ($user->uc['lang'] ?? false)) {
return static::create($user->uc['lang']);
if ($user && ($user->user['lang'] ?: false)) {
return static::create($user->user['lang']);
}
return static::create('default');
}
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Localization;
use TYPO3\CMS\Core\Core\Environment;
/**
* Provides ItemProcFunc fields for special population of available TYPO3 system languages
* @internal
*/
class TcaSystemLanguageCollector
{
private Locales $locales;
public function __construct(Locales $locales)
{
$this->locales = $locales;
}
/**
* Populate languages and group by available languages of the Language packs
*/
public function populateAvailableSystemLanguagesForBackend(array &$fieldInformation): void
{
$languageItems = $this->locales->getLanguages();
unset($languageItems['default']);
asort($languageItems);
$installedLanguages = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [];
$availableLanguages = [];
$unavailableLanguages = [];
foreach ($languageItems as $typo3Language => $name) {
$available = in_array($typo3Language, $installedLanguages, true) || is_dir(Environment::getLabelsPath() . '/' . $typo3Language);
if ($available) {
$availableLanguages[] = [$name, $typo3Language, '', 'installed'];
} else {
$unavailableLanguages[] = [$name, $typo3Language, '', 'unavailable'];
}
}
// Ensure ordering of the items
$fieldInformation['items'] = array_merge($fieldInformation['items'], $availableLanguages);
$fieldInformation['items'] = array_merge($fieldInformation['items'], $unavailableLanguages);
}
}
......@@ -68,6 +68,9 @@ services:
TYPO3\CMS\Core\Controller\FileDumpController:
public: true
TYPO3\CMS\Core\Localization\TcaSystemLanguageCollector:
public: true
TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry:
public: true
......
<?php
defined('TYPO3') or die();
// Populate available languages
/** @var \TYPO3\CMS\Core\Localization\Locales $locales */
$locales = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\Locales::class);
$languageItems = $locales->getLanguages();
unset($languageItems['default']);
asort($languageItems);
foreach ($languageItems as $locale => $name) {
$GLOBALS['TCA']['be_users']['columns']['lang']['config']['items'][] = [$name, $locale];
}
......@@ -286,8 +286,14 @@ return [
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'itemsProcFunc' => 'TYPO3\\CMS\\Core\\Localization\\TcaSystemLanguageCollector->populateAvailableSystemLanguagesForBackend',
'default' => 'default',
'items' => [
['English', '']
['English', 'default']
],
'itemGroups' => [
'installed' => 'LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:be_users.languageItemGroups.installed',
'unavailable' => 'LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:be_users.languageItemGroups.unavailable',
]
]
],
......
.. include:: ../../Includes.txt
========================================================================
Feature: #93663 - Backend Users preferred UI language stored as DB field
========================================================================
See :issue:`93663`
Description
===========
In previous TYPO3 versions, Administrators could create new backend
users and select from the list of all supported TYPO3-internal
languages for their Backend Language (= all labels from
XLIFF files). This information was stored in the database field
"be_users.lang" and was only used on the first log in of a user
into TYPO3 Backend.
The backend users themselves could use the User settings module
to change the UI language to their preferred language, based on the
available language packs in the system.
This information was then stored in the users' "uc" (user configuration),
an arbitrary settings field.
This approach - built over 18 years ago without any significant
changes - had several downsides:
* The database field "be_users.lang" was not really needed
* Administrators did not see available language packs when changing
* Administrators could only change an Editors' preferred language by switching to the user (Switch User Mode).
* Administrators could not filter / sort editors to see what languages the users had chosen
* Fetching the users' preferred language always meant to fetch the whole "uc" information and unpack it.
* The preferred language was only selected if the user had logged in for the first time to initialize the "uc" values.
Instead, TYPO3 now keeps the current language preference in the
database field "be_users.lang", allowing both editors and administrators
to access the same value for fetching this information.
Impact
======
When the user changes their language in the user settings module,
the database record gets updated, and it is clear where this information is
stored. It is now the same logic when an Administrator updates the Editor's
record via FormEngine.
The value is now always filled, and if English is chosen, the value
is set to "default" (instead of an empty value).
An upgrade wizard migrates existing "uc" values into the database
fields. The "uc" entry "user->uc[lang]" is kept in sync for
backwards-compatibility.
.. index:: Backend, JavaScript, ext:backend
......@@ -133,7 +133,16 @@
<source>Directory: Delete recursively (rm -Rf)</source>
</trans-unit>
<trans-unit id="be_users.lang" resname="be_users.lang">
<source>Default Language</source>
<source>User Interface Language</source>
</trans-unit>
<trans-unit id="be_users.languageItemGroups.default" resname="be_users.languageItemGroups.default">
<source>Default</source>
</trans-unit>
<trans-unit id="be_users.languageItemGroups.installed" resname="be_users.languageItemGroups.installed">
<source>Installed Languages</source>
</trans-unit>
<trans-unit id="be_users.languageItemGroups.unavailable" resname="be_users.languageItemGroups.unavailable">
<source>Unavailable Languages</source>
</trans-unit>
<trans-unit id="be_users.tabs.rights" resname="be_users.tabs.rights">
<source>Access Rights</source>
......
......@@ -44,7 +44,7 @@ CREATE TABLE be_users (
password varchar(100) DEFAULT '' NOT NULL,
admin tinyint(4) unsigned DEFAULT '0' NOT NULL,
usergroup text,
lang varchar(6) DEFAULT '' NOT NULL,
lang varchar(10) DEFAULT 'default' NOT NULL,
email varchar(255) DEFAULT '' NOT NULL,
db_mountpoints text,
options tinyint(4) unsigned DEFAULT '0' NOT NULL,
......
......@@ -210,8 +210,8 @@ class LocalizationUtility
$languageKeys['alternativeLanguageKeys'][] = $language;
}
}
} elseif (!empty($GLOBALS['BE_USER']->uc['lang'])) {
$languageKeys['languageKey'] = $GLOBALS['BE_USER']->uc['lang'];
} elseif (!empty($GLOBALS['BE_USER']->user['lang'])) {
$languageKeys['languageKey'] = $GLOBALS['BE_USER']->user['lang'];
} elseif (!empty(static::getLanguageService()->lang)) {
$languageKeys['languageKey'] = static::getLanguageService()->lang;
}
......
......@@ -319,7 +319,7 @@ class LocalizationUtilityTest extends UnitTestCase
$backendUserAuthenticationProphecy = $this->prophesize(BackendUserAuthentication::class);
$GLOBALS['BE_USER'] = $backendUserAuthenticationProphecy->reveal();
$backendUserAuthenticationProphecy->uc = [
$backendUserAuthenticationProphecy->user = [
'lang' => $languageKey,
];
$GLOBALS['LANG'] = $this->LOCAL_LANG;
......
......@@ -601,8 +601,8 @@ class TranslationService implements SingletonInterface
}
}
}
} elseif (!empty($GLOBALS['BE_USER']->uc['lang'])) {
$this->languageKey = $GLOBALS['BE_USER']->uc['lang'];
} elseif (!empty($GLOBALS['BE_USER']->user['lang'])) {
$this->languageKey = $GLOBALS['BE_USER']->user['lang'];
} elseif (!empty($this->getLanguageService()->lang)) {
$this->languageKey = $this->getLanguageService()->lang;
}
......
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Install\Updates;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
*/
class BackendUserLanguageMigration implements UpgradeWizardInterface
{
private const TABLE_NAME = 'be_users';
public function getIdentifier(): string
{
return 'backendUserLanguage';
}
public function getTitle(): string
{
return 'Migrate backend users\' selected UI languages to new format.';
}
public function getDescription(): string
{
return 'Backend users now keep their preferred UI language for TYPO3 Backend in its own database field. This updates all backend user records.';
}
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
public function updateNecessary(): bool
{
return $this->hasRecordsToUpdate();
}
public function executeUpdate(): bool
{
$connection = $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME);
foreach ($this->getRecordsToUpdate() as $record) {
$currentDatabaseFieldValue = (string)$record['lang'] ?? '';
$uc = unserialize($user['uc'] ?? '', ['allowed_classes' => false]);
// Check if the user has a preference set, otherwise use the default from the database field
// however, "default" is now explicitly set.
$selectedLanguage = $uc['lang'] ?? $currentDatabaseFieldValue;
if ($selectedLanguage === '') {
$selectedLanguage = 'default';
}
// Everything set already in the DB field, so this can be skipped
if ($selectedLanguage === $currentDatabaseFieldValue) {
continue;
}
$connection->update(
self::TABLE_NAME,
['lang' => $selectedLanguage],
['uid' => (int)$record['uid']]
);
}
return true;
}
protected function hasRecordsToUpdate(): bool
{
$queryBuilder = $this->getPreparedQueryBuilder();
return (bool)$queryBuilder
->count('uid')
->execute()
->fetchColumn();
}
protected function getRecordsToUpdate(): array
{
return $this->getPreparedQueryBuilder()->select(...['uid', 'uc', 'lang'])->execute()->fetchAll();
}
protected function getPreparedQueryBuilder(): QueryBuilder
{
$queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME);
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder->from(self::TABLE_NAME);
return $queryBuilder;
}
protected function getConnectionPool(): ConnectionPool
{
return GeneralUtility::makeInstance(ConnectionPool::class);
}
}
......@@ -19,6 +19,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['databaseRows
// v10->v11 wizards below this line
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['legacyCollectionsExtension']
= \TYPO3\CMS\Install\Updates\CollectionsExtractionUpdate::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendUserLanguage']
= \TYPO3\CMS\Install\Updates\BackendUserLanguageMigration::class;
$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
$icons = [
......
......@@ -396,7 +396,8 @@ class RichTextElement extends AbstractFormElement
// Set the UI language of the editor if not hard-coded by the existing configuration
if (empty($configuration['language'])) {
$configuration['language'] = $this->getBackendUser()->uc['lang'] ?: ($this->getBackendUser()->user['lang'] ?: 'en');
$userLang = (string)($this->getBackendUser()->user['lang'] ?: 'en');
$configuration['language'] = $userLang === 'default' ? 'en' : $userLang;
}
$configuration['contentsLanguage'] = $this->getLanguageIsoCodeOfContent();
......
......@@ -35,7 +35,6 @@ 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\Localization\Locales;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Page\PageRenderer;
......@@ -225,7 +224,7 @@ class SetupModuleController
$save_before = md5(serialize($backendUser->uc));
// PUT SETTINGS into the ->uc array:
// Reload left frame when switching BE language
if (isset($d['lang']) && $d['lang'] !== $backendUser->uc['lang']) {
if (isset($d['lang']) && $d['lang'] !== $backendUser->user['lang']) {
$this->languageUpdate = true;
}
// Reload pagetree if the title length is changed
......@@ -548,6 +547,9 @@ class SetupModuleController
' /></div>';
$label = '';
break;
case 'language':
$html = $this->renderLanguageSelect();
break;
case 'select':
if ($config['itemsProcFunc']) {
$html = GeneralUtility::callUserFunction($config['itemsProcFunc'], $config, $this);
......@@ -704,46 +706,44 @@ class SetupModuleController
*
* @return string Complete select as HTML string or warning box if something went wrong.
*/
public function renderLanguageSelect()
protected function renderLanguageSelect()
{
$tcaConfig = $GLOBALS['TCA']['be_users']['columns']['lang']['config'];
$items = $tcaConfig['items'];
$itemsProcFunc = [
'items' => &$items
];
GeneralUtility::callUserFunction($tcaConfig['itemsProcFunc'], $itemsProcFunc);
$backendUser = $this->getBackendUser();
$language = $this->getLanguageService();
$languageOptions = [];
// Compile the languages dropdown
$langDefault = htmlspecialchars($language->getLL('lang_default'));
$languageOptions[$langDefault] = '<option value=""' . ($backendUser->uc['lang'] === '' ? ' selected="selected"' : '') . '>' . $langDefault . '</option>';
if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'])) {
// get all labels in default language as well
$defaultLanguageLabelService = LanguageService::create('default');
$defaultLanguageLabelService->includeLLFile('EXT:setup/Resources/Private/Language/locallang.xlf');
// Traverse the number of languages
$locales = GeneralUtility::makeInstance(Locales::class);
$languages = $locales->getLanguages();
foreach ($languages as $locale => $name) {
if ($locale !== 'default') {
$defaultName = $defaultLanguageLabelService->getLL('lang_') ?: $name;
$localizedName = htmlspecialchars($language->getLL('lang_' . $locale));
if ($localizedName === '') {
$localizedName = htmlspecialchars($name);
}
$localLabel = ' - [' . htmlspecialchars($defaultName) . ']';
$available = in_array($locale, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'], true) || is_dir(Environment::getLabelsPath() . '/' . $locale);
if ($available) {
$languageOptions[$defaultName] = '<option value="' . $locale . '"' . ($backendUser->uc['lang'] === $locale ? ' selected="selected"' : '') . '>' . $localizedName . $localLabel . '</option>';
}
$currentSelectedLanguage = (string)($backendUser->user['lang'] ?? 'default');
$languageService = $this->getLanguageService();
$content = '';
// get all labels in default language as well
$defaultLanguageLabelService = LanguageService::create('default');
$defaultLanguageLabelService->includeLLFile('EXT:setup/Resources/Private/Language/locallang.xlf');
foreach ($items as $item) {
$languageCode = $item[1];
$name = $item[0];
$available = in_array($languageCode, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'], true) || is_dir(Environment::getLabelsPath() . '/' . $languageCode);
if ($available || $languageCode === 'default') {
$localizedName = htmlspecialchars($languageService->getLL('lang_' . $languageCode) ?: $name);
$defaultName = $defaultLanguageLabelService->getLL('lang_' . $languageCode);
if ($defaultName === $localizedName || $defaultName === '') {
$defaultName = $languageCode;
}
if ($defaultName !== $languageCode) {
$defaultName .= ' - ' . $languageCode;
}
$localLabel = ' [' . htmlspecialchars($defaultName) . ']';
$content .= '<option value="' . $languageCode . '"' . ($currentSelectedLanguage === $languageCode ? ' selected="selected"' : '') . '>' . $localizedName . $localLabel . '</option>';
}
}
ksort($languageOptions);
$languageCode = '
<select aria-labelledby="label_lang" id="field_lang" name="data[lang]" class="form-select">' . implode('', $languageOptions) . '
</select>';
if ($backendUser->uc['lang'] && !@is_dir(Environment::getLabelsPath() . '/' . $backendUser->uc['lang'])) {
$languageUnavailableWarning = htmlspecialchars(sprintf($language->getLL('languageUnavailable'), $language->getLL('lang_' . $backendUser->uc['lang']))) . '&nbsp;&nbsp;<br />&nbsp;&nbsp;' . htmlspecialchars($language->getLL('languageUnavailable.' . ($backendUser->isAdmin() ? 'admin' : 'user')));
$languageCode = '<br /><span class="label label-danger">' . $languageUnavailableWarning . '</span><br /><br />' . $languageCode;
$content = '<select aria-labelledby="label_lang" id="field_lang" name="data[be_users][lang]" class="form-select">' . $content . '</select>';
if ($currentSelectedLanguage !== 'default' && !@is_dir(Environment::getLabelsPath() . '/' . $currentSelectedLanguage)) {
$languageUnavailableWarning = htmlspecialchars(sprintf($languageService->getLL('languageUnavailable'), $languageService->getLL('lang_' . $currentSelectedLanguage))) . '&nbsp;&nbsp;<br />&nbsp;&nbsp;' . htmlspecialchars($languageService->getLL('languageUnavailable.' . ($backendUser->isAdmin() ? 'admin' : 'user')));
$content = '<br /><span class="label label-danger">' . $languageUnavailableWarning . '</span><br /><br />' . $content;
}
return $languageCode;
return $content;
}
/**
......
......@@ -66,8 +66,8 @@ $GLOBALS['TYPO3_USER_SETTINGS'] = [
'allowed' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
],
'lang' => [
'type' => 'select',
'itemsProcFunc' => \TYPO3\CMS\Setup\Controller\SetupModuleController::class . '->renderLanguageSelect',
'type' => 'language',
'table' => 'be_users',