TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
|
||||
use TYPO3\CMS\Core\Http\Uri;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Entity representing a site for everything on "pid=0". Mostly used in TYPO3 Backend, not really in use elsewhere.
|
||||
*/
|
||||
class NullSite implements SiteInterface
|
||||
{
|
||||
protected int $rootPageId = 0;
|
||||
|
||||
/**
|
||||
* @var SiteLanguage[]
|
||||
*/
|
||||
protected array $languages;
|
||||
|
||||
/**
|
||||
* Sets up a null site object
|
||||
*
|
||||
* @param array|null $languages site languages
|
||||
* @param Uri|null $baseEntryPoint
|
||||
*/
|
||||
public function __construct(?array $languages = null, ?Uri $baseEntryPoint = null)
|
||||
{
|
||||
if (empty($languages)) {
|
||||
// Create the default language if no language configuration is given
|
||||
$this->languages[0] = new SiteLanguage(
|
||||
0,
|
||||
'',
|
||||
new Uri('/'),
|
||||
['enabled' => true]
|
||||
);
|
||||
} else {
|
||||
foreach ($languages as $languageConfiguration) {
|
||||
$languageUid = (int)$languageConfiguration['languageId'];
|
||||
// Language configuration does not have a base defined
|
||||
// So the main site base is used (usually done for default languages)
|
||||
$this->languages[$languageUid] = new SiteLanguage(
|
||||
$languageUid,
|
||||
$languageConfiguration['locale'] ?? '',
|
||||
$baseEntryPoint ?: new Uri('/'),
|
||||
$languageConfiguration
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns always #NULL
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return '#NULL';
|
||||
}
|
||||
|
||||
/**
|
||||
* Always "/"
|
||||
*/
|
||||
public function getBase(): UriInterface
|
||||
{
|
||||
return new Uri('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Always zero
|
||||
*/
|
||||
public function getRootPageId(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available languages of this installation
|
||||
*
|
||||
* @return SiteLanguage[]
|
||||
*/
|
||||
public function getLanguages(): array
|
||||
{
|
||||
return $this->languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language of this site, given by the sys_language_uid
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getLanguageById(int $languageId): SiteLanguage
|
||||
{
|
||||
if (isset($this->languages[$languageId])) {
|
||||
return $this->languages[$languageId];
|
||||
}
|
||||
throw new \InvalidArgumentException(
|
||||
'Language ' . $languageId . ' does not exist on site ' . $this->getIdentifier() . '.',
|
||||
1522965188
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultLanguage(): SiteLanguage
|
||||
{
|
||||
return reset($this->languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes page TSconfig into account (unlike Site interface) to find
|
||||
* mod.SHARED.disableLanguages and mod.SHARED.defaultLanguageLabel
|
||||
*/
|
||||
public function getAvailableLanguages(BackendUserAuthentication $user, bool $includeAllLanguagesFlag = false, ?int $pageId = null): array
|
||||
{
|
||||
$availableLanguages = [];
|
||||
|
||||
// Check if we need to add language "-1"
|
||||
if ($includeAllLanguagesFlag && $user->checkLanguageAccess(-1)) {
|
||||
$availableLanguages[-1] = new SiteLanguage(-1, '', $this->getBase(), [
|
||||
'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages'),
|
||||
'flag' => 'flags-multiple',
|
||||
]);
|
||||
}
|
||||
$pageTs = BackendUtility::getPagesTSconfig((int)$pageId);
|
||||
$pageTs = $pageTs['mod.']['SHARED.'] ?? [];
|
||||
|
||||
$disabledLanguages = GeneralUtility::intExplode(',', (string)($pageTs['disableLanguages'] ?? ''), true);
|
||||
// Do not add the ones that are not allowed by the user
|
||||
foreach ($this->languages as $language) {
|
||||
if ($user->checkLanguageAccess($language) && !in_array($language->getLanguageId(), $disabledLanguages, true)) {
|
||||
if ($language->getLanguageId() === 0) {
|
||||
// 0: "Default" language
|
||||
$defaultLanguageLabel = 'LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage';
|
||||
$defaultLanguageLabel = $this->getLanguageService()->sL($defaultLanguageLabel);
|
||||
if (isset($pageTs['defaultLanguageLabel'])) {
|
||||
$defaultLanguageLabel = $pageTs['defaultLanguageLabel'] . ' (' . $defaultLanguageLabel . ')';
|
||||
}
|
||||
$defaultLanguageFlag = '';
|
||||
if (isset($pageTs['defaultLanguageFlag'])) {
|
||||
$defaultLanguageFlag = 'flags-' . $pageTs['defaultLanguageFlag'];
|
||||
}
|
||||
$language = new SiteLanguage(0, '', $language->getBase(), [
|
||||
'title' => $defaultLanguageLabel,
|
||||
'flag' => $defaultLanguageFlag,
|
||||
]);
|
||||
}
|
||||
$availableLanguages[$language->getLanguageId()] = $language;
|
||||
}
|
||||
}
|
||||
|
||||
return $availableLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ready-to-use error handler, to be used within the ErrorController
|
||||
*/
|
||||
public function getErrorHandler(int $statusCode): PageErrorHandlerInterface
|
||||
{
|
||||
throw new \RuntimeException('No error handler given for the status code "' . $statusCode . '".', 1522495102);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Symfony\Component\ExpressionLanguage\SyntaxError;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\FluidPageErrorHandler;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\InvalidPageErrorHandlerException;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageContentErrorHandler;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\RedirectLoginErrorHandler;
|
||||
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
|
||||
use TYPO3\CMS\Core\Http\Uri;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Routing\PageRouter;
|
||||
use TYPO3\CMS\Core\Routing\RouterInterface;
|
||||
use TYPO3\CMS\Core\Site\Set\SetError;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Entity representing a single site with available languages
|
||||
*
|
||||
* @phpstan-type LanguageRef -1|0|positive-int
|
||||
*/
|
||||
class Site implements SiteInterface
|
||||
{
|
||||
protected const ERRORHANDLER_TYPE_PAGE = 'Page';
|
||||
protected const ERRORHANDLER_TYPE_FLUID = 'Fluid';
|
||||
protected const ERRORHANDLER_TYPE_PHP = 'PHP';
|
||||
protected const ERRORHANDLER_TYPE_LOGIN_REDIRECT = 'LoginRedirect';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* @var UriInterface
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $rootPageId;
|
||||
|
||||
/**
|
||||
* Any attributes for this site
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* Raw attributes for this site
|
||||
* @var array
|
||||
*/
|
||||
protected $rawConfiguration;
|
||||
|
||||
/**
|
||||
* @var array<LanguageRef, SiteLanguage>
|
||||
*/
|
||||
protected $languages;
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
protected array $sets;
|
||||
|
||||
/**
|
||||
* @var array<string, array{error: SetError, name: string, context: string}>
|
||||
*/
|
||||
public array $invalidSets = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $errorHandlers;
|
||||
|
||||
protected SiteSettings $settings;
|
||||
|
||||
protected ?SiteTypoScript $typoscript;
|
||||
|
||||
protected ?SiteTSconfig $tsConfig;
|
||||
|
||||
/**
|
||||
* Sets up a site object, and its languages, error handlers and the settings
|
||||
*/
|
||||
public function __construct(string $identifier, int $rootPageId, array $configuration, ?SiteSettings $settings = null, ?SiteTypoScript $typoscript = null, ?SiteTSconfig $tsConfig = null)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
$this->rootPageId = $rootPageId;
|
||||
if ($settings === null) {
|
||||
// @todo deprecate null settings argument
|
||||
$settings = SiteSettings::createFromSettingsTree($configuration['settings'] ?? []);
|
||||
}
|
||||
$this->settings = $settings;
|
||||
$this->typoscript = $typoscript;
|
||||
$this->tsConfig = $tsConfig;
|
||||
$this->rawConfiguration = $configuration;
|
||||
// Merge settings back in configuration for backwards-compatibility
|
||||
$configuration['settings'] = $this->settings->getAll();
|
||||
$this->configuration = $configuration;
|
||||
$configuration['languages'] = !empty($configuration['languages']) ? $configuration['languages'] : [
|
||||
0 => [
|
||||
'languageId' => 0,
|
||||
'title' => 'Default',
|
||||
'navigationTitle' => '',
|
||||
'flag' => 'us',
|
||||
'locale' => 'en_US.UTF-8',
|
||||
],
|
||||
];
|
||||
$baseUrl = $this->resolveBaseWithVariants(
|
||||
$configuration['base'] ?? '',
|
||||
$configuration['baseVariants'] ?? null
|
||||
);
|
||||
$this->base = new Uri($this->sanitizeBaseUrl($baseUrl));
|
||||
|
||||
$this->sets = $configuration['dependencies'] ?? [];
|
||||
foreach ($configuration['languages'] as $languageConfiguration) {
|
||||
$languageUid = (int)$languageConfiguration['languageId'];
|
||||
// site language has defined its own base, this is the case most of the time.
|
||||
if (!empty($languageConfiguration['base'])) {
|
||||
$base = $this->resolveBaseWithVariants(
|
||||
$languageConfiguration['base'],
|
||||
$languageConfiguration['baseVariants'] ?? null
|
||||
);
|
||||
$base = new Uri($this->sanitizeBaseUrl($base));
|
||||
// no host given by the language-specific base, so lets prefix the main site base
|
||||
if ($base->getScheme() === '' && $base->getHost() === '') {
|
||||
$base = rtrim((string)$this->base, '/') . '/' . ltrim((string)$base, '/');
|
||||
$base = new Uri($this->sanitizeBaseUrl($base));
|
||||
}
|
||||
} else {
|
||||
// Language configuration does not have a base defined
|
||||
// So the main site base is used (usually done for default languages)
|
||||
$base = new Uri($this->sanitizeBaseUrl(rtrim((string)$this->base, '/') . '/'));
|
||||
}
|
||||
if (!empty($languageConfiguration['flag'])) {
|
||||
if ($languageConfiguration['flag'] === 'global') {
|
||||
$languageConfiguration['flag'] = 'flags-multiple';
|
||||
} elseif ($languageConfiguration['flag'] !== 'empty-empty') {
|
||||
$languageConfiguration['flag'] = 'flags-' . $languageConfiguration['flag'];
|
||||
}
|
||||
}
|
||||
$this->languages[$languageUid] = new SiteLanguage(
|
||||
$languageUid,
|
||||
$languageConfiguration['locale'],
|
||||
$base,
|
||||
$languageConfiguration
|
||||
);
|
||||
}
|
||||
foreach ($configuration['errorHandling'] ?? [] as $errorHandlingConfiguration) {
|
||||
$code = $errorHandlingConfiguration['errorCode'];
|
||||
unset($errorHandlingConfiguration['errorCode']);
|
||||
$this->errorHandlers[(int)$code] = $errorHandlingConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the base has variants, and takes the first variant which matches an expression.
|
||||
*/
|
||||
protected function resolveBaseWithVariants(string $baseUrl, ?array $baseVariants): string
|
||||
{
|
||||
if (!empty($baseVariants)) {
|
||||
$expressionLanguageResolver = GeneralUtility::makeInstance(
|
||||
Resolver::class,
|
||||
'site',
|
||||
[]
|
||||
);
|
||||
foreach ($baseVariants as $baseVariant) {
|
||||
try {
|
||||
if ((bool)$expressionLanguageResolver->evaluate($baseVariant['condition'])) {
|
||||
$baseUrl = $baseVariant['base'];
|
||||
break;
|
||||
}
|
||||
} catch (SyntaxError $e) {
|
||||
// silently fail and do not evaluate
|
||||
// no logger here, as Site is currently cached and serialized
|
||||
}
|
||||
}
|
||||
}
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the identifier of this site,
|
||||
* mainly used when maintaining / configuring sites.
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base URL of this site
|
||||
*/
|
||||
public function getBase(): UriInterface
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root page ID of this site
|
||||
*/
|
||||
public function getRootPageId(): int
|
||||
{
|
||||
return $this->rootPageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available languages of this site
|
||||
*
|
||||
* @return array<LanguageRef, SiteLanguage>
|
||||
*/
|
||||
public function getLanguages(): array
|
||||
{
|
||||
$languages = [];
|
||||
foreach ($this->languages as $languageId => $language) {
|
||||
if ($language->enabled()) {
|
||||
$languages[$languageId] = $language;
|
||||
}
|
||||
}
|
||||
return $languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns configured sets of this site
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getSets(): array
|
||||
{
|
||||
return $this->sets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available languages of this site, even the ones disabled for frontend usages
|
||||
*
|
||||
* @return array<LanguageRef, SiteLanguage>
|
||||
*/
|
||||
public function getAllLanguages(): array
|
||||
{
|
||||
return $this->languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language of this site, given by the sys_language_uid
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getLanguageById(int $languageId): SiteLanguage
|
||||
{
|
||||
if (isset($this->languages[$languageId])) {
|
||||
return $this->languages[$languageId];
|
||||
}
|
||||
// @todo: Turn this into a specific exception to avoid catching \InvalidArgumentException
|
||||
// since there is no hasLanguageById() or similar and some core places already
|
||||
// call this method and try-catch global \InvalidArgumentException, which is bad practice.
|
||||
throw new \InvalidArgumentException(
|
||||
'Language ' . $languageId . ' does not exist on site ' . $this->identifier . '.',
|
||||
1522960188
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefaultLanguage(): SiteLanguage
|
||||
{
|
||||
foreach ($this->languages as $language) {
|
||||
if ($language->isPrimary()) {
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
return reset($this->languages);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<LanguageRef, SiteLanguage>
|
||||
*/
|
||||
public function getAvailableLanguages(BackendUserAuthentication $user, bool $includeAllLanguagesFlag = false, ?int $pageId = null): array
|
||||
{
|
||||
$availableLanguages = [];
|
||||
|
||||
// Check if we need to add language "-1"
|
||||
if ($includeAllLanguagesFlag && $user->checkLanguageAccess(-1)) {
|
||||
$availableLanguages[-1] = new SiteLanguage(-1, '', $this->getBase(), [
|
||||
'title' => $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:multipleLanguages'),
|
||||
'flag' => 'flags-multiple',
|
||||
]);
|
||||
}
|
||||
|
||||
// Do not add the ones that are not allowed by the user
|
||||
foreach ($this->languages as $language) {
|
||||
if ($user->checkLanguageAccess($language)) {
|
||||
$availableLanguages[$language->getLanguageId()] = $language;
|
||||
}
|
||||
}
|
||||
|
||||
return $availableLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ready-to-use error handler, to be used within the ErrorController
|
||||
*
|
||||
* @throws PageErrorHandlerNotConfiguredException
|
||||
* @throws InvalidPageErrorHandlerException
|
||||
*/
|
||||
public function getErrorHandler(int $statusCode): PageErrorHandlerInterface
|
||||
{
|
||||
$errorHandlerConfiguration = $this->errorHandlers[$statusCode] ?? $this->errorHandlers[0] ?? null;
|
||||
switch ($errorHandlerConfiguration['errorHandler'] ?? null) {
|
||||
case self::ERRORHANDLER_TYPE_FLUID:
|
||||
return GeneralUtility::makeInstance(FluidPageErrorHandler::class, $statusCode, $errorHandlerConfiguration);
|
||||
case self::ERRORHANDLER_TYPE_PAGE:
|
||||
return GeneralUtility::makeInstance(PageContentErrorHandler::class, $statusCode, $errorHandlerConfiguration);
|
||||
case self::ERRORHANDLER_TYPE_LOGIN_REDIRECT:
|
||||
return GeneralUtility::makeInstance(RedirectLoginErrorHandler::class, $statusCode, $errorHandlerConfiguration);
|
||||
case self::ERRORHANDLER_TYPE_PHP:
|
||||
$handler = GeneralUtility::makeInstance($errorHandlerConfiguration['errorPhpClassFQCN'], $statusCode, $errorHandlerConfiguration);
|
||||
// Check if the interface is implemented
|
||||
if (!($handler instanceof PageErrorHandlerInterface)) {
|
||||
throw new InvalidPageErrorHandlerException('The configured error handler "' . (string)$errorHandlerConfiguration['errorPhpClassFQCN'] . '" for status code ' . $statusCode . ' must implement the PageErrorHandlerInterface.', 1527432330);
|
||||
}
|
||||
return $handler;
|
||||
}
|
||||
throw new PageErrorHandlerNotConfiguredException('No error handler given for the status code "' . $statusCode . '".', 1522495914);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole configuration for this site
|
||||
*/
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
public function getRawConfiguration(): array
|
||||
{
|
||||
return $this->rawConfiguration;
|
||||
}
|
||||
|
||||
public function getSettings(): SiteSettings
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function isTypoScriptRoot(): bool
|
||||
{
|
||||
return $this->sets !== [] || $this->typoscript !== null || $this->tsConfig !== null;
|
||||
}
|
||||
|
||||
public function getTypoScript(): ?SiteTypoScript
|
||||
{
|
||||
return $this->typoscript;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getTSconfig(): ?SiteTSconfig
|
||||
{
|
||||
return $this->tsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single configuration attribute
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getAttribute(string $attributeName)
|
||||
{
|
||||
if (isset($this->configuration[$attributeName])) {
|
||||
return $this->configuration[$attributeName];
|
||||
}
|
||||
throw new \InvalidArgumentException(
|
||||
'Attribute ' . $attributeName . ' does not exist on site ' . $this->identifier . '.',
|
||||
1522495954
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a site base contains "/" or "www.domain.com", it is ensured that
|
||||
* parse_url() can handle this kind of configuration properly.
|
||||
*/
|
||||
protected function sanitizeBaseUrl(string $base): string
|
||||
{
|
||||
// no protocol ("//") and the first part is no "/" (path), means that this is a domain like
|
||||
// "www.domain.com/subpage", and we want to ensure that this one then gets a "no-scheme agnostic" part
|
||||
if (!empty($base) && !str_contains($base, '//') && $base[0] !== '/') {
|
||||
// either a scheme is added, or no scheme but with domain, or a path which is not absolute
|
||||
// make the base prefixed with a slash, so it is recognized as path, not as domain
|
||||
// treat as path
|
||||
if (!str_contains($base, '.')) {
|
||||
$base = '/' . $base;
|
||||
} else {
|
||||
// treat as domain name
|
||||
$base = '//' . $base;
|
||||
}
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the applicable router for this site. This might be configurable in the future.
|
||||
*/
|
||||
public function getRouter(?Context $context = null): RouterInterface
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRouter::class, $this, $context);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
|
||||
use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
|
||||
|
||||
interface SiteInterface
|
||||
{
|
||||
/**
|
||||
* Returns the root page ID of this site
|
||||
*/
|
||||
public function getRootPageId(): int;
|
||||
|
||||
/**
|
||||
* Returns an identifier for the site / configuration
|
||||
*/
|
||||
public function getIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Returns the base URL
|
||||
*/
|
||||
public function getBase(): UriInterface;
|
||||
|
||||
/**
|
||||
* Returns all available languages of this site visible in the frontend
|
||||
*
|
||||
* @return SiteLanguage[]
|
||||
*/
|
||||
public function getLanguages(): array;
|
||||
|
||||
/**
|
||||
* Returns a language of this site, given by the sys_language_uid
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getLanguageById(int $languageId): SiteLanguage;
|
||||
|
||||
/**
|
||||
* Returns the first language that was configured. This is usually language=0
|
||||
*/
|
||||
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|null $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
|
||||
*
|
||||
* @throws PageErrorHandlerNotConfiguredException
|
||||
*/
|
||||
public function getErrorHandler(int $statusCode): PageErrorHandlerInterface;
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use TYPO3\CMS\Core\Localization\Locale;
|
||||
|
||||
/**
|
||||
* Entity representing a site_language configuration of a site object.
|
||||
*/
|
||||
class SiteLanguage
|
||||
{
|
||||
/**
|
||||
* The language id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $languageId;
|
||||
|
||||
/**
|
||||
* The language tag, like 'de', 'en', 'de-CH'.
|
||||
*/
|
||||
protected string $languageTag = '';
|
||||
|
||||
/**
|
||||
* Locale, like 'de-CH' or 'en-GB'
|
||||
*/
|
||||
protected Locale $locale;
|
||||
|
||||
/**
|
||||
* The Base URL for this language
|
||||
*
|
||||
* @var UriInterface
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* Label to be used within TYPO3 to identify the language
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Default';
|
||||
|
||||
/**
|
||||
* Label to be used within language menus
|
||||
* @var string
|
||||
*/
|
||||
protected $navigationTitle = '';
|
||||
|
||||
/**
|
||||
* Localized title of the site to be used in title tag.
|
||||
* @var string
|
||||
*/
|
||||
protected $websiteTitle = '';
|
||||
|
||||
/**
|
||||
* The flag key (like "gb" or "fr") used to be used in TYPO3's Backend.
|
||||
* @var string
|
||||
*/
|
||||
protected $flagIdentifier = '';
|
||||
|
||||
/**
|
||||
* Language tag for this language defined by RFC 1766 / 3066 for "hreflang" attribute
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hreflang = '';
|
||||
|
||||
/**
|
||||
* Prefix for TYPO3's language files. If empty, this
|
||||
* is fetched from $locale
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $typo3Language = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fallbackType = 'strict';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fallbackLanguageIds = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $enabled = true;
|
||||
|
||||
/**
|
||||
* Whether this language is the primary language of the site.
|
||||
*/
|
||||
protected bool $primary = false;
|
||||
|
||||
/**
|
||||
* Additional parameters configured for this site language
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
/**
|
||||
* SiteLanguage constructor.
|
||||
*/
|
||||
public function __construct(int $languageId, string $locale, UriInterface $base, array $configuration)
|
||||
{
|
||||
$this->languageId = $languageId;
|
||||
$this->locale = new Locale($locale);
|
||||
$this->base = $base;
|
||||
$this->configuration = $configuration;
|
||||
|
||||
if (!empty($configuration['languageTag'])) {
|
||||
$this->languageTag = $configuration['languageTag'];
|
||||
}
|
||||
if (!empty($configuration['title'])) {
|
||||
$this->title = $configuration['title'];
|
||||
}
|
||||
if (!empty($configuration['navigationTitle'])) {
|
||||
$this->navigationTitle = $configuration['navigationTitle'];
|
||||
}
|
||||
if (!empty($configuration['websiteTitle'])) {
|
||||
$this->websiteTitle = $configuration['websiteTitle'];
|
||||
}
|
||||
if (!empty($configuration['flag'])) {
|
||||
$this->flagIdentifier = $configuration['flag'];
|
||||
}
|
||||
if (!empty($configuration['typo3Language'])) {
|
||||
$this->typo3Language = $configuration['typo3Language'];
|
||||
}
|
||||
if (!empty($configuration['hreflang'])) {
|
||||
$this->hreflang = $configuration['hreflang'];
|
||||
}
|
||||
if (!empty($configuration['fallbackType'])) {
|
||||
$this->fallbackType = $configuration['fallbackType'];
|
||||
}
|
||||
if (isset($configuration['fallbacks'])) {
|
||||
$fallbackLanguageIds = $configuration['fallbacks'];
|
||||
|
||||
// It is important to distinct between "0" and "" so, empty() should not be used here
|
||||
if (is_string($fallbackLanguageIds)) {
|
||||
if ($fallbackLanguageIds !== '') {
|
||||
$fallbackLanguageIds = explode(',', $fallbackLanguageIds);
|
||||
} else {
|
||||
$fallbackLanguageIds = [];
|
||||
}
|
||||
} elseif (is_scalar($fallbackLanguageIds)) {
|
||||
$fallbackLanguageIds = [$fallbackLanguageIds];
|
||||
}
|
||||
$this->fallbackLanguageIds = array_map(intval(...), $fallbackLanguageIds);
|
||||
}
|
||||
if (isset($configuration['enabled'])) {
|
||||
$this->enabled = (bool)$configuration['enabled'];
|
||||
}
|
||||
if (!empty($configuration['primary'])) {
|
||||
$this->primary = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SiteLanguage in an array representation for e.g. the usage
|
||||
* in TypoScript.
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_merge($this->configuration, [
|
||||
'languageId' => $this->getLanguageId(),
|
||||
'languageTag' => $this->getLanguageTag(),
|
||||
// kept for backwards-compat for the time being, might change to BGP-47 format
|
||||
'locale' => $this->getLocale()->posixFormatted(),
|
||||
'base' => (string)$this->getBase(),
|
||||
'title' => $this->getTitle(),
|
||||
'websiteTitle' => $this->getWebsiteTitle(),
|
||||
'navigationTitle' => $this->getNavigationTitle(),
|
||||
'hreflang' => $this->hreflang ?: $this->locale->getName(),
|
||||
'typo3Language' => $this->getTypo3Language(),
|
||||
'flagIdentifier' => $this->getFlagIdentifier(),
|
||||
'fallbackType' => $this->getFallbackType(),
|
||||
'enabled' => $this->enabled(),
|
||||
'primary' => $this->isPrimary(),
|
||||
'fallbackLanguageIds' => $this->getFallbackLanguageIds(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function getLanguageId(): int
|
||||
{
|
||||
return $this->languageId;
|
||||
}
|
||||
|
||||
public function getLanguageTag(): string
|
||||
{
|
||||
return $this->languageTag;
|
||||
}
|
||||
|
||||
public function getLocale(): Locale
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
|
||||
public function getBase(): UriInterface
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getNavigationTitle(): string
|
||||
{
|
||||
return $this->navigationTitle ?: $this->title;
|
||||
}
|
||||
|
||||
public function getWebsiteTitle(): string
|
||||
{
|
||||
return $this->websiteTitle;
|
||||
}
|
||||
|
||||
public function getFlagIdentifier(): string
|
||||
{
|
||||
return $this->flagIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XLF label language key.
|
||||
* For locales like "en-US", this method returns "en_US" which can then be used
|
||||
* for XLF file prefixes properly.
|
||||
*/
|
||||
public function getTypo3Language(): string
|
||||
{
|
||||
if ($this->typo3Language !== '') {
|
||||
return $this->typo3Language;
|
||||
}
|
||||
$typo3Language = $this->locale->getLanguageCode();
|
||||
if ($this->locale->getCountryCode()) {
|
||||
$typo3Language .= '_' . $this->locale->getCountryCode();
|
||||
}
|
||||
return $typo3Language;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function hasCustomTypo3Language(): bool
|
||||
{
|
||||
return $this->typo3Language !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RFC 1766 / 3066 language tag for hreflang tags
|
||||
*/
|
||||
public function getHreflang(bool $fetchCustomSetting = false): string
|
||||
{
|
||||
// Ensure to check if a custom attribute is set
|
||||
if ($fetchCustomSetting) {
|
||||
return $this->hreflang;
|
||||
}
|
||||
return $this->hreflang ?: $this->locale->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the language is available in frontend usage
|
||||
*/
|
||||
public function enabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper so fluid can work with this as well.
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function isPrimary(): bool
|
||||
{
|
||||
return $this->primary;
|
||||
}
|
||||
|
||||
public function getFallbackType(): string
|
||||
{
|
||||
return $this->fallbackType;
|
||||
}
|
||||
|
||||
public function getFallbackLanguageIds(): array
|
||||
{
|
||||
return $this->fallbackLanguageIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
use TYPO3\CMS\Core\Settings\Settings;
|
||||
use TYPO3\CMS\Core\Settings\SettingsInterface;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* Entity representing all settings for a site. These settings are not overlaid
|
||||
* with TypoScript settings / constants which happens in the TypoScript Parser
|
||||
* for a specific page.
|
||||
*/
|
||||
final readonly class SiteSettings implements SettingsInterface, \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @internal to be constructed by create() or createFromSettingsTree()
|
||||
*/
|
||||
public function __construct(
|
||||
private SettingsInterface $settings,
|
||||
private array $settingsTree,
|
||||
private array $flattenedArrayValues,
|
||||
) {}
|
||||
|
||||
public function has(string $identifier): bool
|
||||
{
|
||||
return $this->settings->has($identifier) || array_key_exists($identifier, $this->settingsTree) || array_key_exists($identifier, $this->flattenedArrayValues);
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->settings->getIdentifiers() === [];
|
||||
}
|
||||
|
||||
public function get(string $identifier, mixed $defaultValue = null): mixed
|
||||
{
|
||||
if ($this->settings->has($identifier)) {
|
||||
return $this->settings->get($identifier);
|
||||
}
|
||||
return $this->flattenedArrayValues[$identifier] ?? $this->settingsTree[$identifier] ?? $defaultValue;
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->settingsTree;
|
||||
}
|
||||
|
||||
public function getMap(): array
|
||||
{
|
||||
$map = [];
|
||||
foreach ($this->settings->getIdentifiers() as $key) {
|
||||
$map[$key] = $this->settings->get($key);
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function getAllFlat(): array
|
||||
{
|
||||
return [
|
||||
...$this->flattenedArrayValues,
|
||||
...array_filter($this->getMap(), static fn(mixed $value): bool => !is_array($value)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Update jsonSerialize() to return settings map and settings tree values, or remove altogether.
|
||||
*/
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return json_encode($this->settingsTree);
|
||||
}
|
||||
|
||||
public function getIdentifiers(): array
|
||||
{
|
||||
return $this->settings->getIdentifiers();
|
||||
}
|
||||
|
||||
public static function __set_state(array $state): static
|
||||
{
|
||||
return new static(...$state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function create(SettingsInterface $settings): self
|
||||
{
|
||||
$tree = [];
|
||||
$flattenedArrayValues = [];
|
||||
foreach ($settings->getIdentifiers() as $key) {
|
||||
$value = $settings->get($key);
|
||||
$tree = ArrayUtility::setValueByPath($tree, $key, $value, '.');
|
||||
if (is_array($value)) {
|
||||
foreach (ArrayUtility::flattenPlain($value) as $flatKey => $flatValue) {
|
||||
$flattenedArrayValues[$key . '.' . $flatKey] = $flatValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new self(
|
||||
settings: $settings,
|
||||
settingsTree: $tree,
|
||||
flattenedArrayValues: $flattenedArrayValues,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function createFromSettingsTree(array $settingsTree): self
|
||||
{
|
||||
$flatSettings = $settingsTree === [] ? [] : ArrayUtility::flattenPlain($settingsTree);
|
||||
return new self(
|
||||
settings: new Settings($flatSettings),
|
||||
settingsTree: $settingsTree,
|
||||
flattenedArrayValues: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class SiteTSconfig
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $pageTSconfig = null,
|
||||
) {}
|
||||
|
||||
public static function __set_state(array $state): self
|
||||
{
|
||||
return new self(...$state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\Site\Entity;
|
||||
|
||||
final readonly class SiteTypoScript
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $setup = null,
|
||||
public ?string $constants = null,
|
||||
) {}
|
||||
|
||||
public static function __set_state(array $state): self
|
||||
{
|
||||
return new self(...$state);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user