381 lines
17 KiB
PHP
381 lines
17 KiB
PHP
<?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'];
|
|
}
|
|
}
|