TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
<?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\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Dto\Settings\EditableSetting;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\Enum\ModuleLayout;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplate;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\SetupSettingsViewMode;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\Http\ResponseFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Settings\Category;
|
||||
use TYPO3\CMS\Core\Settings\SettingDefinition;
|
||||
use TYPO3\CMS\Core\Settings\SettingsTypeRegistry;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Set\CategoryRegistry;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Site\SiteSettingsService;
|
||||
use TYPO3\CMS\Core\SysLog\Action\Setting as SettingAction;
|
||||
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
||||
use TYPO3\CMS\Core\SysLog\Type;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* View, edit, save site settings. Part of Setup module.
|
||||
*
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
readonly class SiteSettingsController
|
||||
{
|
||||
public function __construct(
|
||||
protected ComponentFactory $componentFactory,
|
||||
protected ModuleTemplateFactory $moduleTemplateFactory,
|
||||
protected SiteFinder $siteFinder,
|
||||
protected SiteSettingsService $siteSettingsService,
|
||||
protected SettingsTypeRegistry $settingsTypeRegistry,
|
||||
protected CategoryRegistry $categoryRegistry,
|
||||
protected UriBuilder $uriBuilder,
|
||||
protected PageRenderer $pageRenderer,
|
||||
protected FlashMessageService $flashMessageService,
|
||||
protected IconFactory $iconFactory,
|
||||
protected ResponseFactory $responseFactory,
|
||||
protected FormProtectionFactory $formProtectionFactory,
|
||||
) {}
|
||||
|
||||
public function editAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$moduleData = $request->getAttribute('moduleData');
|
||||
$mode = SetupSettingsViewMode::tryFrom($moduleData->get('settingsMode') ?? '') ?? SetupSettingsViewMode::BASIC;
|
||||
$moduleData->set('settingsMode', $mode->value);
|
||||
|
||||
$identifier = $request->getQueryParams()['site'] ?? null;
|
||||
if ($identifier === null) {
|
||||
throw new \RuntimeException('Site identifier to edit must be set', 1713394528);
|
||||
}
|
||||
|
||||
$returnUrl = GeneralUtility::sanitizeLocalUrl(
|
||||
(string)($request->getQueryParams()['returnUrl'] ?? ''),
|
||||
$request
|
||||
) ?: null;
|
||||
$overviewUrl = (string)$this->uriBuilder->buildUriFromRoute('site_configuration');
|
||||
|
||||
$site = $this->siteFinder->getSiteByIdentifier($identifier);
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
|
||||
$settings = $this->siteSettingsService->getUncachedSettings($site);
|
||||
$setSettings = $this->siteSettingsService->getSetSettings($site);
|
||||
|
||||
$categoryEnhancer = function (Category $category) use (&$categoryEnhancer, $settings, $setSettings): Category {
|
||||
return new Category(...[
|
||||
...get_object_vars($category),
|
||||
'label' => $this->getLanguageService()->sL($category->label),
|
||||
'description' => $category->description !== null ? $this->getLanguageService()->sL($category->description) : $category->description,
|
||||
'categories' => array_map($categoryEnhancer, $category->categories),
|
||||
'settings' => array_map(
|
||||
fn(SettingDefinition $definition): EditableSetting => new EditableSetting(
|
||||
definition: $this->resolveSettingLabels($definition),
|
||||
value: $settings->get($definition->key),
|
||||
systemDefault: $setSettings->get($definition->key),
|
||||
typeImplementation: $this->settingsTypeRegistry->get($definition->type)->getJavaScriptModule(),
|
||||
),
|
||||
$category->settings
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
$categories = array_map(
|
||||
$categoryEnhancer,
|
||||
$this->categoryRegistry->getCategories(...$site->getSets())
|
||||
);
|
||||
$hasSettings = count($categories) > 0;
|
||||
|
||||
$this->addDocHeaderBreadcrumb($view, $site);
|
||||
$this->addDocHeaderCloseAndSaveButtons($view, $returnUrl ?? $overviewUrl, $hasSettings);
|
||||
$this->addDocHeaderViewModeButton($view, $site, $mode);
|
||||
$this->addDocHeaderSiteConfigurationButton($view, $site);
|
||||
if ($hasSettings) {
|
||||
$this->addDocHeaderExportButton($view, $mode);
|
||||
}
|
||||
// Set shortcut context - reload button is added automatically
|
||||
$view->getDocHeaderComponent()->setShortcutContext(
|
||||
'site_configuration.editSettings',
|
||||
sprintf($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_sitesettings.xlf:labels.edit'), $site->getIdentifier()),
|
||||
['site' => $site->getIdentifier()]
|
||||
);
|
||||
|
||||
$view->setLayout(ModuleLayout::NORMAL);
|
||||
$view->assign('site', $site);
|
||||
$view->assign('siteTitle', $this->getSiteTitle($site));
|
||||
$view->assign('rootPageId', $site->getRootPageId());
|
||||
|
||||
$view->assign('actionUrl', (string)$this->uriBuilder->buildUriFromRoute('site_configuration.saveSettings', array_filter([
|
||||
'site' => $site->getIdentifier(),
|
||||
'returnUrl' => $returnUrl,
|
||||
])));
|
||||
$view->assign('returnUrl', $returnUrl);
|
||||
$view->assign('dumpUrl', (string)$this->uriBuilder->buildUriFromRoute('site_configuration.dumpSettings', ['site' => $site->getIdentifier()]));
|
||||
$view->assign('categories', $categories);
|
||||
$view->assign('mode', $mode);
|
||||
|
||||
$formProtection = $this->formProtectionFactory->createFromRequest($request);
|
||||
$view->assign('formToken', $formProtection->generateToken('site_configuration', 'saveSettings'));
|
||||
|
||||
return $view->renderResponse('SiteSettings/Edit');
|
||||
}
|
||||
|
||||
private function resolveSettingLabels(SettingDefinition $definition): SettingDefinition
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
return new SettingDefinition(...[
|
||||
...get_object_vars($definition),
|
||||
'label' => $languageService->sL($definition->label),
|
||||
'description' => $definition->description !== null ? $languageService->sL($definition->description) : null,
|
||||
'enum' => array_map(
|
||||
static fn(string|int|float|bool $label): string => $languageService->sL((string)$label),
|
||||
$definition->enum
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$identifier = $request->getQueryParams()['site'] ?? null;
|
||||
if ($identifier === null) {
|
||||
throw new \RuntimeException('Site identifier to edit must be set', 1713394529);
|
||||
}
|
||||
|
||||
$site = $this->siteFinder->getSiteByIdentifier($identifier);
|
||||
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$formProtection = $this->formProtectionFactory->createFromRequest($request);
|
||||
if (!$formProtection->validateToken((string)($parsedBody['formToken'] ?? ''), 'site_configuration', 'saveSettings')) {
|
||||
return $this->responseFactory
|
||||
->createResponse(400, 'Invalid request token given')
|
||||
->withHeader('Location', (string)$this->uriBuilder->buildUriFromRoute('site_configuration.editSettings', [
|
||||
'site' => $site->getIdentifier(),
|
||||
]));
|
||||
}
|
||||
|
||||
$returnUrl = GeneralUtility::sanitizeLocalUrl(
|
||||
(string)($parsedBody['returnUrl'] ?? ''),
|
||||
$request
|
||||
) ?: null;
|
||||
$overviewUrl = $this->uriBuilder->buildUriFromRoute('site_configuration');
|
||||
$CMD = $parsedBody['CMD'] ?? '';
|
||||
$isSave = $CMD === 'save' || $CMD === 'saveclose';
|
||||
$isSaveClose = $parsedBody['CMD'] === 'saveclose';
|
||||
if (!$isSave) {
|
||||
return new RedirectResponse($returnUrl ?? $overviewUrl);
|
||||
}
|
||||
|
||||
$newSettings = $this->siteSettingsService->createSettingsFromFormData($site, $parsedBody['settings'] ?? []);
|
||||
$settingsDiff = $this->siteSettingsService->computeSettingsDiff($site, $newSettings);
|
||||
$this->siteSettingsService->writeSettings($site, $settingsDiff->asArray());
|
||||
|
||||
if ($settingsDiff->changes !== [] || $settingsDiff->deletions !== []) {
|
||||
$this->getBackendUser()->writelog(
|
||||
Type::SITE,
|
||||
SettingAction::CHANGE,
|
||||
SystemLogErrorClassification::MESSAGE,
|
||||
null,
|
||||
'Site settings changed for \'%s\': %s',
|
||||
[$site->getIdentifier(), json_encode($settingsDiff)],
|
||||
'site'
|
||||
);
|
||||
|
||||
$languageService = $this->getLanguageService();
|
||||
$message = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_sitesettings.xlf:save.message.updated');
|
||||
$flashMessage = new FlashMessage($message, '', ContextualFeedbackSeverity::OK, true);
|
||||
$defaultFlashMessageQueue = $this->flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
|
||||
if ($isSaveClose) {
|
||||
return new RedirectResponse($returnUrl ?? $overviewUrl);
|
||||
}
|
||||
$editRoute = $this->uriBuilder->buildUriFromRoute('site_configuration.editSettings', array_filter([
|
||||
'site' => $site->getIdentifier(),
|
||||
'returnUrl' => $returnUrl,
|
||||
], static fn(?string $v): bool => $v !== null));
|
||||
return new RedirectResponse($editRoute);
|
||||
}
|
||||
|
||||
public function dumpAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$identifier = $request->getQueryParams()['site'] ?? null;
|
||||
if ($identifier === null) {
|
||||
throw new \RuntimeException('Site identifier to edit must be set', 1724772561);
|
||||
}
|
||||
|
||||
$site = $this->siteFinder->getSiteByIdentifier($identifier);
|
||||
$parsedBody = $request->getParsedBody();
|
||||
$specificSetting = (string)($parsedBody['specificSetting'] ?? '');
|
||||
|
||||
$minify = $specificSetting !== '' ? false : true;
|
||||
|
||||
$newSettings = $this->siteSettingsService->createSettingsFromFormData($site, $parsedBody['settings'] ?? []);
|
||||
$settingsDiff = $this->siteSettingsService->computeSettingsDiff($site, $newSettings, $minify);
|
||||
$settings = $settingsDiff->asArray();
|
||||
if ($specificSetting !== '' && isset($settings[$specificSetting])) {
|
||||
$settings = [
|
||||
$specificSetting => $settings[$specificSetting],
|
||||
];
|
||||
}
|
||||
|
||||
$yamlContents = Yaml::dump($settings, 99, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP);
|
||||
|
||||
return new JsonResponse([
|
||||
'yaml' => $yamlContents,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addDocHeaderBreadcrumb(ModuleTemplate $moduleTemplate, Site $site): void
|
||||
{
|
||||
$record = BackendUtility::getRecord('pages', $site->getRootPageId());
|
||||
$moduleTemplate->getDocHeaderComponent()->setPageBreadcrumb($record ?? []);
|
||||
}
|
||||
|
||||
protected function addDocHeaderCloseAndSaveButtons(ModuleTemplate $moduleTemplate, string $closeUrl, bool $saveEnabled): void
|
||||
{
|
||||
$moduleTemplate->addButtonToButtonBar($this->componentFactory->createCloseButton($closeUrl));
|
||||
$saveButton = $this->componentFactory->createSaveButton('sitesettings_form')
|
||||
->setName('CMD')
|
||||
->setValue('save')
|
||||
->setDisabled(!$saveEnabled);
|
||||
$moduleTemplate->addButtonToButtonBar($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
|
||||
}
|
||||
|
||||
protected function addDocHeaderViewModeButton(ModuleTemplate $moduleTemplate, Site $site, SetupSettingsViewMode $mode): void
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$viewModeButton = $this->componentFactory->createDropDownButton()
|
||||
->setLabel($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.view'))
|
||||
->setShowLabelText(true);
|
||||
|
||||
$viewModeButton->addItem(
|
||||
$this->componentFactory->createDropDownRadio()
|
||||
->setActive(($mode === SetupSettingsViewMode::BASIC))
|
||||
->setHref(
|
||||
(string)$this->uriBuilder->buildUriFromRoute(
|
||||
'site_configuration.editSettings',
|
||||
array_filter([
|
||||
'site' => $site->getIdentifier(),
|
||||
'settingsMode' => SetupSettingsViewMode::BASIC->value,
|
||||
])
|
||||
)
|
||||
)
|
||||
->setLabel($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_settingseditor.xlf:settingseditor.mode.basic'))
|
||||
->setIcon($this->iconFactory->getIcon('actions-window', IconSize::SMALL))
|
||||
);
|
||||
|
||||
$viewModeButton->addItem(
|
||||
$this->componentFactory->createDropDownRadio()
|
||||
->setActive(($mode === SetupSettingsViewMode::ADVANCED))
|
||||
->setHref(
|
||||
(string)$this->uriBuilder->buildUriFromRoute(
|
||||
'site_configuration.editSettings',
|
||||
array_filter([
|
||||
'site' => $site->getIdentifier(),
|
||||
'settingsMode' => SetupSettingsViewMode::ADVANCED->value,
|
||||
])
|
||||
)
|
||||
)
|
||||
->setLabel($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_settingseditor.xlf:settingseditor.mode.advanced'))
|
||||
->setIcon($this->iconFactory->getIcon('actions-window-cog', IconSize::SMALL))
|
||||
);
|
||||
|
||||
$moduleTemplate->addButtonToButtonBar($viewModeButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);
|
||||
}
|
||||
|
||||
protected function addDocHeaderExportButton(ModuleTemplate $moduleTemplate, SetupSettingsViewMode $mode): void
|
||||
{
|
||||
if ($mode === SetupSettingsViewMode::ADVANCED) {
|
||||
$languageService = $this->getLanguageService();
|
||||
$exportButton = $this->componentFactory->createInputButton()
|
||||
->setTitle($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_sitesettings.xlf:edit.yamlExport'))
|
||||
->setIcon($this->iconFactory->getIcon('actions-database-export', IconSize::SMALL))
|
||||
->setShowLabelText(true)
|
||||
->setName('CMD')
|
||||
->setValue('export')
|
||||
->setForm('sitesettings_form');
|
||||
$moduleTemplate->addButtonToButtonBar($exportButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addDocHeaderSiteConfigurationButton(ModuleTemplate $moduleTemplate, Site $site): void
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$exportButton = $this->componentFactory->createLinkButton()
|
||||
->setTitle($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_sitesettings.xlf:edit.editSiteConfiguration'))
|
||||
->setIcon($this->iconFactory->getIcon('actions-open', IconSize::SMALL))
|
||||
->setShowLabelText(true)
|
||||
->setHref((string)$this->uriBuilder->buildUriFromRoute('site_configuration.edit', [
|
||||
'site' => $site->getIdentifier(),
|
||||
'returnUrl' => $this->uriBuilder->buildUriFromRoute('site_configuration.editSettings', [
|
||||
'site' => $site->getIdentifier(),
|
||||
]),
|
||||
]));
|
||||
$moduleTemplate->addButtonToButtonBar($exportButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
|
||||
}
|
||||
|
||||
protected function getSiteTitle(Site $site): string
|
||||
{
|
||||
$websiteTitle = $site->getConfiguration()['websiteTitle'] ?? '';
|
||||
if ($websiteTitle !== '') {
|
||||
return $websiteTitle;
|
||||
}
|
||||
$rootPage = BackendUtility::getRecord('pages', $site->getRootPageId());
|
||||
$title = $rootPage['title'] ?? '';
|
||||
if ($title !== '') {
|
||||
return $title;
|
||||
}
|
||||
|
||||
return '(unknown)';
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user