TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Configuration;
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Convenience wrapper for backend user configuration
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BackendUserConfiguration
|
||||
{
|
||||
/**
|
||||
* @var BackendUserAuthentication
|
||||
*/
|
||||
protected $backendUser;
|
||||
|
||||
/**
|
||||
* @param BackendUserAuthentication|null $backendUser
|
||||
*/
|
||||
public function __construct(?BackendUserAuthentication $backendUser = null)
|
||||
{
|
||||
$this->backendUser = $backendUser ?: $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific user setting
|
||||
*
|
||||
* @param string $key Identifier, allows also dotted notation for subarrays
|
||||
* @return mixed Value associated
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
return (str_contains($key, '.')) ? $this->getFromDottedNotation($key) : $this->backendUser->uc[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user settings
|
||||
*
|
||||
* @return mixed all values, usually a multi-dimensional array
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $this->backendUser->uc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user settings by key/value pair
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set(string $key, $value): void
|
||||
{
|
||||
if (str_contains($key, '.')) {
|
||||
$this->setFromDottedNotation($key, $value);
|
||||
} else {
|
||||
$this->backendUser->uc[$key] = $value;
|
||||
}
|
||||
|
||||
$this->backendUser->writeUC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to a Comma-separated list
|
||||
* stored in $key of user settings
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addToList(string $key, $value): void
|
||||
{
|
||||
$list = $this->get($key);
|
||||
|
||||
if (!isset($list)) {
|
||||
$list = $value;
|
||||
} elseif (!GeneralUtility::inList($list, $value)) {
|
||||
$list .= ',' . $value;
|
||||
}
|
||||
|
||||
$this->set($key, $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from a Comma-separated list
|
||||
* stored in $key of user settings
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function removeFromList(string $key, $value): void
|
||||
{
|
||||
$list = $this->get($key);
|
||||
|
||||
if (GeneralUtility::inList($list, $value)) {
|
||||
$list = GeneralUtility::trimExplode(',', $list, true);
|
||||
$list = ArrayUtility::removeArrayEntryByValue($list, $value);
|
||||
$this->set($key, implode(',', $list));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the user settings to the default
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->backendUser->resetUC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a key in user settings
|
||||
*/
|
||||
public function unsetOption(string $key): void
|
||||
{
|
||||
if (isset($this->backendUser->uc[$key])) {
|
||||
unset($this->backendUser->uc[$key]);
|
||||
$this->backendUser->writeUC();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the subarray from dotted notation
|
||||
*
|
||||
* @param string $key Dotted notation of subkeys like moduleData.module1.general.checked
|
||||
* @return mixed value of the settings
|
||||
*/
|
||||
protected function getFromDottedNotation(string $key)
|
||||
{
|
||||
$subkeys = GeneralUtility::trimExplode('.', $key);
|
||||
$configuration = $this->backendUser->uc;
|
||||
|
||||
foreach ($subkeys as $subkey) {
|
||||
if (isset($configuration[$subkey])) {
|
||||
$configuration = &$configuration[$subkey];
|
||||
} else {
|
||||
$configuration = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a key written in dotted notation
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function setFromDottedNotation(string $key, $value): void
|
||||
{
|
||||
$subkeys = GeneralUtility::trimExplode('.', $key, true);
|
||||
$lastKey = $subkeys[count($subkeys) - 1];
|
||||
$configuration = &$this->backendUser->uc;
|
||||
|
||||
foreach ($subkeys as $subkey) {
|
||||
if ($subkey === $lastKey) {
|
||||
$configuration[$subkey] = $value;
|
||||
} else {
|
||||
$configuration = &$configuration[$subkey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Configuration;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use TYPO3\CMS\Core\Configuration\Tca\TcaMigration;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Helper class for the backend "Sites" module
|
||||
*
|
||||
* Load Site configuration TCA from ext:*Configuration/SiteConfiguration
|
||||
* and ext:*Configuration/SiteConfiguration/Overrides
|
||||
*
|
||||
* @internal This class is a specific Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SiteTcaConfiguration
|
||||
{
|
||||
/**
|
||||
* Returns a "fake TCA" array that is syntactically identical to
|
||||
* "normal" TCA, but is not available as $GLOBALS['TCA']. During
|
||||
* configuration loading time, the target array is available as
|
||||
* $GLOBALS['SiteConfiguration'] within the Overrides files.
|
||||
*
|
||||
* It is not possible to use ExtensionManagementUtility methods.
|
||||
*/
|
||||
public function getTca(): array
|
||||
{
|
||||
$GLOBALS['SiteConfiguration'] = [];
|
||||
$activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
|
||||
// First load "full table" files from Configuration/SiteConfiguration
|
||||
$finder = (new Finder())->files()->depth(0)->name('*.php');
|
||||
$hasDirectoryEntries = false;
|
||||
foreach ($activePackages as $package) {
|
||||
try {
|
||||
$finder->in($package->getPackagePath() . 'Configuration/SiteConfiguration');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// No such directory in this package
|
||||
continue;
|
||||
}
|
||||
$hasDirectoryEntries = true;
|
||||
}
|
||||
if ($hasDirectoryEntries) {
|
||||
foreach ($finder as $fileInfo) {
|
||||
$GLOBALS['SiteConfiguration'][substr($fileInfo->getBasename(), 0, -4)] = require $fileInfo->getPathname();
|
||||
}
|
||||
}
|
||||
// Execute override files from Configuration/SiteConfiguration/Overrides
|
||||
$finder = (new Finder())->files()->depth(0)->name('*.php');
|
||||
$hasDirectoryEntries = false;
|
||||
foreach ($activePackages as $package) {
|
||||
try {
|
||||
$finder->in($package->getPackagePath() . 'Configuration/SiteConfiguration/Overrides');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// No such directory in this package
|
||||
continue;
|
||||
}
|
||||
$hasDirectoryEntries = true;
|
||||
}
|
||||
if ($hasDirectoryEntries) {
|
||||
foreach ($finder as $fileInfo) {
|
||||
require $fileInfo->getPathname();
|
||||
}
|
||||
}
|
||||
$result = $GLOBALS['SiteConfiguration'];
|
||||
unset($GLOBALS['SiteConfiguration']);
|
||||
$tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
|
||||
$tcaProcessingResult = $tcaMigration->migrate($result);
|
||||
$messages = $tcaProcessingResult->getMessages();
|
||||
if (!empty($messages)) {
|
||||
$context = 'Automatic TCA migration done during bootstrap of Site TCA Configuration.'
|
||||
. ' Please adapt TCA accordingly, these migrations will be removed.'
|
||||
. ' Please adapt these areas:';
|
||||
array_unshift($messages, $context);
|
||||
trigger_error(implode(LF, $messages), E_USER_DEPRECATED);
|
||||
}
|
||||
return $tcaProcessingResult->getTca();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Configuration\TCA;
|
||||
|
||||
use TYPO3\CMS\Core\Country\Country;
|
||||
use TYPO3\CMS\Core\Country\CountryFilter;
|
||||
use TYPO3\CMS\Core\Country\CountryProvider;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class provides items processor functions for the usage in TCA definition
|
||||
* @internal
|
||||
*/
|
||||
class ItemsProcessorFunctions
|
||||
{
|
||||
/**
|
||||
* Return languages found in already existing site configurations,
|
||||
* sorted by their value. In case the same language is used with
|
||||
* different titles, they will be added to the items label field.
|
||||
* Additionally, a placeholder value is added to allow the creation
|
||||
* of new site languages.
|
||||
*/
|
||||
public function populateAvailableLanguagesFromSites(array &$fieldDefinition): void
|
||||
{
|
||||
foreach (GeneralUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) {
|
||||
foreach ($site->getAllLanguages() as $languageId => $language) {
|
||||
if (!isset($fieldDefinition['items'][$languageId])) {
|
||||
$fieldDefinition['items'][$languageId] = [
|
||||
'label' => $language->getTitle(),
|
||||
'value' => $languageId,
|
||||
'icon' => $language->getFlagIdentifier(),
|
||||
'tempTitles' => [],
|
||||
];
|
||||
} elseif ($fieldDefinition['items'][$languageId]['label'] !== $language->getTitle()) {
|
||||
// Temporarily store different titles
|
||||
$fieldDefinition['items'][$languageId]['tempTitles'][] = $language->getTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($fieldDefinition['items'][0])) {
|
||||
// Since TcaSiteLanguage has a special behaviour, enforcing the
|
||||
// default language ("0") to be always added to the site configuration,
|
||||
// we have to add it to the available items, in case it is not already
|
||||
// present. This only happens for the first ever created site configuration.
|
||||
$fieldDefinition['items'][] = ['label' => 'Default', 'value' => 0, 'icon' => '', 'tempTitles' => []];
|
||||
}
|
||||
|
||||
ksort($fieldDefinition['items']);
|
||||
|
||||
// Build the final language label
|
||||
foreach ($fieldDefinition['items'] as &$language) {
|
||||
$language['label'] .= ' [' . $language['value'] . ']';
|
||||
if ($language['tempTitles'] !== []) {
|
||||
$language['label'] .= ' (' . implode(',', array_unique($language['tempTitles'])) . ')';
|
||||
// Unset the temporary title "storage"
|
||||
unset($language['tempTitles']);
|
||||
}
|
||||
}
|
||||
unset($language);
|
||||
|
||||
// Add PHP_INT_MAX as last - placeholder - value to allow creation of new records
|
||||
// with the "Create new" button, which is usually not possible in "selector" mode.
|
||||
// Note: The placeholder will never be displayed in the selector.
|
||||
$fieldDefinition['items'] = array_values(
|
||||
array_merge($fieldDefinition['items'], [['label' => 'Placeholder', 'value' => PHP_INT_MAX]])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return language items for use in site_languages.fallbacks
|
||||
*/
|
||||
public function populateFallbackLanguages(array &$fieldDefinition): void
|
||||
{
|
||||
foreach (GeneralUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) {
|
||||
foreach ($site->getAllLanguages() as $languageId => $language) {
|
||||
if (isset($fieldDefinition['row']['languageId'][0])
|
||||
&& (int)$fieldDefinition['row']['languageId'][0] === $languageId
|
||||
) {
|
||||
// Skip current language id
|
||||
continue;
|
||||
}
|
||||
if (!isset($fieldDefinition['items'][$languageId])) {
|
||||
$fieldDefinition['items'][$languageId] = [
|
||||
'label' => $language->getTitle(),
|
||||
'value' => $languageId,
|
||||
'icon' => $language->getFlagIdentifier(),
|
||||
'tempTitles' => [],
|
||||
];
|
||||
} elseif ($fieldDefinition['items'][$languageId]['label'] !== $language->getTitle()) {
|
||||
// Temporarily store different titles
|
||||
$fieldDefinition['items'][$languageId]['tempTitles'][] = $language->getTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
ksort($fieldDefinition['items']);
|
||||
|
||||
// Build the final language label
|
||||
foreach ($fieldDefinition['items'] as &$language) {
|
||||
if ($language['tempTitles'] !== []) {
|
||||
$language['label'] .= ' (' . implode(',', array_unique($language['tempTitles'])) . ')';
|
||||
// Unset the temporary title "storage"
|
||||
unset($language['tempTitles']);
|
||||
}
|
||||
}
|
||||
unset($language);
|
||||
|
||||
$fieldDefinition['items'] = array_values($fieldDefinition['items']);
|
||||
}
|
||||
|
||||
public function populateFlags(array &$fieldConfiguration): void
|
||||
{
|
||||
$filter = (new CountryFilter())->setExcludeCountries(['um']);
|
||||
$countries = GeneralUtility::makeInstance(CountryProvider::class)->getFiltered($filter);
|
||||
/** @var Country $country */
|
||||
foreach ($countries as $country) {
|
||||
$code = strtolower($country->getAlpha2IsoCode());
|
||||
$fieldConfiguration['items'][] = [
|
||||
'label' => $country->getName(),
|
||||
'value' => $code,
|
||||
'icon' => 'flags-' . $code,
|
||||
'group' => 'countries',
|
||||
];
|
||||
}
|
||||
// Additional country variants
|
||||
$variants = ['ca-qc', 'es-ct', 'es-ga', 'gb-eng', 'gb-nir', 'gb-sct', 'gb-wls'];
|
||||
foreach ($variants as $variant) {
|
||||
$split = explode('-', $variant);
|
||||
$base = $countries[strtoupper($split[0])];
|
||||
$fieldConfiguration['items'][] = [
|
||||
'label' => sprintf('%s - %s', $base->getName(), strtoupper($split[1])),
|
||||
'value' => $variant,
|
||||
'icon' => 'flags-' . $variant,
|
||||
'group' => 'countries',
|
||||
];
|
||||
}
|
||||
|
||||
$colors = ['black', 'white', 'blue', 'indigo', 'purple', 'pink', 'orange', 'yellow', 'green', 'teal', 'cyan', 'rainbow'];
|
||||
foreach ($colors as $color) {
|
||||
$fieldConfiguration['items'][] = [
|
||||
'label' => $color,
|
||||
'value' => $color,
|
||||
'icon' => 'flags-' . $color,
|
||||
'group' => 'colors',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Configuration\TCA;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Localization\Locales;
|
||||
|
||||
/**
|
||||
* This class provides user functions for the usage in TCA definition
|
||||
* @internal
|
||||
*/
|
||||
class UserFunctions
|
||||
{
|
||||
/**
|
||||
* Used to build the IRRE title of a site language element
|
||||
*/
|
||||
public function getSiteLanguageTitle(array &$parameters): void
|
||||
{
|
||||
$record = $parameters['row'];
|
||||
$languageId = (int)($record['languageId'][0] ?? 0);
|
||||
|
||||
if ($languageId === PHP_INT_MAX && str_starts_with((string)($record['uid'] ?? ''), 'NEW')) {
|
||||
// If we deal with a new record, created via "Create new" (indicated by the PHP_INT_MAX placeholder),
|
||||
// we use a label as record title, until the real values, especially the language ID, are calculated.
|
||||
$parameters['title'] = '[' . $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site.languages.new') . ']';
|
||||
return;
|
||||
}
|
||||
|
||||
$primaryValue = $record['primary'] ?? null;
|
||||
$isPrimary = false;
|
||||
if (is_array($primaryValue)) {
|
||||
$isPrimary = !empty($primaryValue[0]);
|
||||
} elseif ($primaryValue !== null) {
|
||||
$isPrimary = (bool)$primaryValue;
|
||||
}
|
||||
if (!$isPrimary) {
|
||||
$isPrimary = ((int)($record['languageId'][0] ?? -1) === 0);
|
||||
}
|
||||
$parameters['title'] = sprintf(
|
||||
'%s %s [%d] (%s) Base: %s%s',
|
||||
$record['enabled'] ? '' : '[' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:disabled') . ']',
|
||||
$record['title'],
|
||||
$languageId,
|
||||
$record['locale'],
|
||||
$record['base'],
|
||||
$isPrimary ? ' ★' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to build the IRRE title of a site route element
|
||||
*/
|
||||
public function getRouteTitle(array &$parameters): void
|
||||
{
|
||||
$record = $parameters['row'];
|
||||
if (($record['type'][0] ?? false) === 'uri') {
|
||||
$parameters['title'] = sprintf(
|
||||
'%s %s %s',
|
||||
$record['route'],
|
||||
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:site.routes.irreHeader.redirectsTo'),
|
||||
$record['source'] ?: '[' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:undefined') . ']'
|
||||
);
|
||||
} else {
|
||||
$parameters['title'] = $record['route'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to build the IRRE title of a site error handling element
|
||||
*/
|
||||
public function getErrorHandlingTitle(array &$parameters): void
|
||||
{
|
||||
$record = $parameters['row'];
|
||||
$format = '%s: %s';
|
||||
$arguments = [$record['errorCode']];
|
||||
switch ($record['errorHandler'][0] ?? false) {
|
||||
case 'Fluid':
|
||||
$arguments[] = $record['errorFluidTemplate'];
|
||||
break;
|
||||
case 'Page':
|
||||
$arguments[] = $record['errorContentSource'];
|
||||
break;
|
||||
case 'PHP':
|
||||
$arguments[] = $record['errorPhpClassFQCN'];
|
||||
break;
|
||||
default:
|
||||
$arguments[] = $record['errorHandler'][0] ?? '';
|
||||
}
|
||||
$parameters['title'] = sprintf($format, ...$arguments);
|
||||
}
|
||||
|
||||
public static function getAllSystemLocales(): array
|
||||
{
|
||||
$locales = [];
|
||||
foreach (Locales::getAllSystemLocales() as $locale) {
|
||||
$locales[] = ['label' => $locale, 'value' => $locale];
|
||||
}
|
||||
return $locales;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Site\Entity\NullSite;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains translation tools
|
||||
*
|
||||
* @phpstan-type LanguageRef -1|0|positive-int
|
||||
* @internal The whole class is subject to be removed, fetch all language info from the current site object.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class TranslationConfigurationProvider
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
private FrontendInterface $runtimeCache,
|
||||
private SiteFinder $siteFinder,
|
||||
private ConnectionPool $connectionPool,
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns array of languages given for a specific site (or "nullSite" if on page=0)
|
||||
* The property flagIcon returns a string <flags-xx>.
|
||||
*
|
||||
* @param int $pageId Page id (used to get TSconfig configuration setting flag and label for default language)
|
||||
* @return array<LanguageRef, array{uid: int, title: string, ISOcode: string, flagIcon: string}> Array with languages
|
||||
*/
|
||||
public function getSystemLanguages(int $pageId = 0): array
|
||||
{
|
||||
$cacheKey = 'system-language-cache-page-uid-' . $pageId;
|
||||
if ($this->runtimeCache->has($cacheKey)) {
|
||||
return $this->runtimeCache->get($cacheKey);
|
||||
}
|
||||
$allSystemLanguages = [];
|
||||
if ($pageId === 0) {
|
||||
// Used for e.g. filelist, where there is no site selected.
|
||||
// This also means that there is no "-1" (All Languages) selectable.
|
||||
// Languages are consolidated across all sites with unique titles.
|
||||
$sites = $this->siteFinder->getAllSites();
|
||||
foreach ($sites as $site) {
|
||||
$this->addSiteLanguagesToConsolidatedList(
|
||||
$allSystemLanguages,
|
||||
$site->getAvailableLanguages($this->getBackendUserAuthentication()),
|
||||
);
|
||||
}
|
||||
$this->computeSystemLanguagesTitleAndFlag($allSystemLanguages, true);
|
||||
} else {
|
||||
try {
|
||||
$site = $this->siteFinder->getSiteByPageId($pageId);
|
||||
} catch (SiteNotFoundException) {
|
||||
$site = new NullSite();
|
||||
}
|
||||
$siteLanguages = $site->getAvailableLanguages($this->getBackendUserAuthentication(), true);
|
||||
if (!isset($siteLanguages[0])) {
|
||||
$siteLanguages[0] = $site->getDefaultLanguage();
|
||||
}
|
||||
$this->addSiteLanguagesToConsolidatedList($allSystemLanguages, $siteLanguages);
|
||||
$this->computeSystemLanguagesTitleAndFlag($allSystemLanguages);
|
||||
}
|
||||
ksort($allSystemLanguages);
|
||||
$this->runtimeCache->set($cacheKey, $allSystemLanguages);
|
||||
return $allSystemLanguages;
|
||||
}
|
||||
|
||||
protected function addSiteLanguagesToConsolidatedList(array &$allSystemLanguages, array $languagesOfSpecificSite): void
|
||||
{
|
||||
foreach ($languagesOfSpecificSite as $language) {
|
||||
$languageId = $language->getLanguageId();
|
||||
$allSystemLanguages[$languageId] ??= [
|
||||
'uid' => $languageId,
|
||||
'titlesMap' => [],
|
||||
'flagsMap' => [],
|
||||
];
|
||||
$allSystemLanguages[$languageId]['titlesMap'][$language->getTitle()] = true;
|
||||
$allSystemLanguages[$languageId]['flagsMap'][$language->getFlagIdentifier()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function computeSystemLanguagesTitleAndFlag(array &$allSystemLanguages, bool $showIdInTitle = false): void
|
||||
{
|
||||
foreach ($allSystemLanguages as &$language) {
|
||||
$language['title'] = implode(', ', array_keys($language['titlesMap']));
|
||||
if ($language['uid'] === 0 && count($language['titlesMap']) > 1) {
|
||||
// "Default" label for language 0 with multiple titles.
|
||||
$language['title'] = $this->getLanguageService()->translate('LGL.defaultLanguage', 'core.general');
|
||||
}
|
||||
if ($showIdInTitle) {
|
||||
$language['title'] .= ' [' . $language['uid'] . ']';
|
||||
}
|
||||
|
||||
$language['flagIcon'] = array_key_first($language['flagsMap']);
|
||||
if (count($language['titlesMap']) > 1 || count($language['flagsMap']) > 1) {
|
||||
$language['flagIcon'] = 'flags-multiple';
|
||||
}
|
||||
|
||||
unset($language['titlesMap'], $language['flagsMap']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about translation for an element
|
||||
* Will overlay workspace version of record too!
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param int $uid Record uid
|
||||
* @param int $languageUid Language uid. If 0, then all languages are selected.
|
||||
* @param array|null $row The record to be translated
|
||||
* @param array|string $selFieldList Select fields for the query which fetches the translations of the current record
|
||||
* @return array|string Array with information or error message as a string.
|
||||
*/
|
||||
public function translationInfo($table, $uid, $languageUid = 0, ?array $row = null, $selFieldList = ''): array|string
|
||||
{
|
||||
if (!$this->tcaSchemaFactory->has($table) || !$uid) {
|
||||
return 'No table "' . $table . '" or no UID value';
|
||||
}
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
if (!$schema->isLanguageAware()) {
|
||||
return 'Translation is not supported for this table!';
|
||||
}
|
||||
if ($row === null) {
|
||||
$row = BackendUtility::getRecordWSOL($table, $uid);
|
||||
}
|
||||
if (!is_array($row)) {
|
||||
return 'Record "' . $table . '_' . $uid . '" was not found';
|
||||
}
|
||||
$languageCapability = $schema->getCapability(TcaSchemaCapability::Language);
|
||||
$languageFieldName = $languageCapability->getLanguageField()->getName();
|
||||
$translationOriginPointerFieldName = $languageCapability->getTranslationOriginPointerField()->getName();
|
||||
if ($row[$languageFieldName] > 0) {
|
||||
return 'Record "' . $table . '_' . $uid . '" seems to be a translation already (has a language value "' . $row[$languageFieldName] . '", relation to record "' . $row[$translationOriginPointerFieldName] . '")';
|
||||
}
|
||||
if ($row[$translationOriginPointerFieldName] != 0) {
|
||||
return 'Record "' . $table . '_' . $uid . '" seems to be a translation already (has a relation to record "' . $row[$translationOriginPointerFieldName] . '")';
|
||||
}
|
||||
// Look for translations of this record, index by language field value:
|
||||
if (!empty($selFieldList)) {
|
||||
if (is_array($selFieldList)) {
|
||||
$selectFields = $selFieldList;
|
||||
} else {
|
||||
$selectFields = GeneralUtility::trimExplode(',', $selFieldList);
|
||||
}
|
||||
} else {
|
||||
$selectFields = ['uid', $languageFieldName];
|
||||
}
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
|
||||
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUserAuthentication()->workspace));
|
||||
$queryBuilder
|
||||
->select(...$selectFields)
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
$translationOriginPointerFieldName,
|
||||
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter(
|
||||
$row['pid'],
|
||||
Connection::PARAM_INT
|
||||
)
|
||||
)
|
||||
);
|
||||
if (!$languageUid) {
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->gt(
|
||||
$languageFieldName,
|
||||
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$queryBuilder
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq(
|
||||
$languageFieldName,
|
||||
$queryBuilder->createNamedParameter($languageUid, Connection::PARAM_INT)
|
||||
)
|
||||
);
|
||||
}
|
||||
$translationRecords = $queryBuilder->executeQuery()->fetchAllAssociative();
|
||||
|
||||
$translations = [];
|
||||
$translationsErrors = [];
|
||||
foreach ($translationRecords as $translationRecord) {
|
||||
if (!isset($translations[$translationRecord[$languageFieldName]])) {
|
||||
$translations[$translationRecord[$languageFieldName]] = $translationRecord;
|
||||
} else {
|
||||
$translationsErrors[$translationRecord[$languageFieldName]][] = $translationRecord;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'table' => $table,
|
||||
'uid' => $uid,
|
||||
'CType' => $row['CType'] ?? '',
|
||||
'sys_language_uid' => $row[$languageFieldName] ?? null,
|
||||
'translations' => $translations,
|
||||
'excessive_translations' => $translationsErrors,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getBackendUserAuthentication(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user