2 declare(strict_types = 1);
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
48 use TYPO3Fluid\Fluid\View\ViewInterface;
81 $this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
82 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
91 public function handleRequest(ServerRequestInterface $request): ResponseInterface
95 $this->siteFinder->getAllSites(
false);
96 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule(
'TYPO3/CMS/Backend/ContextMenu');
97 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule(
'TYPO3/CMS/Backend/Modal');
98 $action = $request->getQueryParams()[
'action'] ?? $request->getParsedBody()[
'action'] ??
'overview';
100 if (!in_array($action, self::ALLOWED_ACTIONS,
true)) {
106 $result = call_user_func_array([$this, $action .
'Action'], [$request]);
107 if ($result instanceof ResponseInterface) {
110 $this->moduleTemplate->setContent($this->view->render());
111 return new HtmlResponse($this->moduleTemplate->renderContent());
121 $allSites = $this->siteFinder->getAllSites();
123 $unassignedSites = [];
124 foreach ($allSites as $identifier => $site) {
125 $rootPageId = $site->getRootPageId();
126 if (isset($pages[$rootPageId])) {
127 $pages[$rootPageId][
'siteIdentifier'] = $identifier;
128 $pages[$rootPageId][
'siteConfiguration'] = $site;
130 $unassignedSites[] = $site;
133 $this->view->assignMultiple([
135 'unassignedSites' => $unassignedSites
145 protected function editAction(ServerRequestInterface $request): void
151 $GLOBALS[
'TCA'] = array_merge(
$GLOBALS[
'TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
153 $siteIdentifier = $request->getQueryParams()[
'site'] ??
null;
154 $pageUid = (int)($request->getQueryParams()[
'pageUid'] ?? 0);
156 if (empty($siteIdentifier) && empty($pageUid)) {
157 throw new \RuntimeException(
'Either site identifier to edit a config or page uid to add new config must be set', 1521561148);
159 $isNewConfig = empty($siteIdentifier);
163 $defaultValues[
'site'][
'rootPageId'] = $pageUid;
166 $allSites = $this->siteFinder->getAllSites();
167 if (!$isNewConfig && !isset($allSites[$siteIdentifier])) {
168 throw new \RuntimeException(
'Existing config for site ' . $siteIdentifier .
' not found', 1521561226);
171 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
172 $returnUrl = $uriBuilder->buildUriFromRoute(
'site_configuration');
174 $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
175 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
176 $formDataCompilerInput = [
177 'tableName' =>
'site',
178 'vanillaUid' => $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId(),
179 'command' => $isNewConfig ?
'new' :
'edit',
180 'returnUrl' => (string)$returnUrl,
182 'siteIdentifier' => $isNewConfig ?
'' : $siteIdentifier,
184 'defaultValues' => $defaultValues,
186 $formData = $formDataCompiler->compile($formDataCompilerInput);
187 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
188 $formData[
'renderType'] =
'outerWrapContainer';
189 $formResult = $nodeFactory->create($formData)->render();
191 $formResult[
'doSaveFieldName'] =
'doSave';
192 $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
193 $formResultCompiler->mergeResult($formResult);
194 $formResultCompiler->addCssFiles();
196 $this->view->assign(
'rootPageId', $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId());
197 $this->view->assign(
'returnUrl', $returnUrl);
198 $this->view->assign(
'formEngineHtml', $formResult[
'html']);
199 $this->view->assign(
'formEngineFooter', $formResultCompiler->printNeededJSFunctions());
209 protected function saveAction(ServerRequestInterface $request): ResponseInterface
213 $GLOBALS[
'TCA'] = array_merge(
$GLOBALS[
'TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
215 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
216 $siteTca = GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca();
218 $overviewRoute = $uriBuilder->buildUriFromRoute(
'site_configuration', [
'action' =>
'overview']);
219 $parsedBody = $request->getParsedBody();
220 if (isset($parsedBody[
'closeDoc']) && (
int)$parsedBody[
'closeDoc'] === 1) {
222 return new RedirectResponse($overviewRoute);
224 $isSave = $parsedBody[
'_savedok'] ?? $parsedBody[
'doSave'] ??
false;
225 $isSaveClose = $parsedBody[
'_saveandclosedok'] ??
false;
226 if (!$isSave && !$isSaveClose) {
227 throw new \RuntimeException(
'Either save or save and close', 1520370364);
230 if (!isset($parsedBody[
'data'][
'site']) || !is_array($parsedBody[
'data'][
'site'])) {
231 throw new \RuntimeException(
'No site data or site identifier given', 1521030950);
234 $data = $parsedBody[
'data'];
236 $pageId = (int)key($data[
'site']);
237 $sysSiteRow = current($data[
'site']);
238 $siteIdentifier = $sysSiteRow[
'identifier'] ??
'';
240 $isNewConfiguration =
false;
241 $currentIdentifier =
'';
243 $currentSite = $this->siteFinder->getSiteByRootPageId($pageId);
244 $currentSiteConfiguration = $currentSite->getConfiguration();
245 $currentIdentifier = $currentSite->getIdentifier();
246 }
catch (SiteNotFoundException $e) {
247 $currentSiteConfiguration = [];
248 $isNewConfiguration =
true;
249 $pageId = (int)$parsedBody[
'rootPageId'];
252 throw new \RuntimeException(
'No root page id found', 1521719709);
258 unset($sysSiteRow[
'identifier']);
261 $newSysSiteData = [];
263 $newSysSiteData[
'rootPageId'] = $pageId;
264 foreach ($sysSiteRow as $fieldName => $fieldValue) {
265 $type = $siteTca[
'site'][
'columns'][$fieldName][
'config'][
'type'];
270 $newSysSiteData[$fieldName] = $fieldValue;
274 $newSysSiteData[$fieldName] = [];
275 $childRowIds = GeneralUtility::trimExplode(
',', $fieldValue,
true);
276 if (!isset($siteTca[
'site'][
'columns'][$fieldName][
'config'][
'foreign_table'])) {
277 throw new \RuntimeException(
'No foreign_table found for inline type', 1521555037);
279 $foreignTable = $siteTca[
'site'][
'columns'][$fieldName][
'config'][
'foreign_table'];
280 foreach ($childRowIds as $childRowId) {
282 if (!isset($data[$foreignTable][$childRowId])) {
283 if (!empty($currentSiteConfiguration[$fieldName][$childRowId])) {
285 $newSysSiteData[$fieldName][] = $currentSiteConfiguration[$fieldName][$childRowId];
288 throw new \RuntimeException(
'No data found for table ' . $foreignTable .
' with id ' . $childRowId, 1521555177);
290 $childRow = $data[$foreignTable][$childRowId];
291 foreach ($childRow as $childFieldName => $childFieldValue) {
292 if ($childFieldName ===
'pid') {
296 $type = $siteTca[$foreignTable][
'columns'][$childFieldName][
'config'][
'type'];
301 $childRowData[$childFieldName] = $childFieldValue;
304 $childRowData[$childFieldName] = (bool)$childFieldValue;
307 throw new \RuntimeException(
'TCA type ' . $type .
' not implemented in site handling', 1521555340);
310 $newSysSiteData[$fieldName][] = $childRowData;
319 $newSysSiteData[$fieldName] = (bool)$fieldValue;
323 throw new \RuntimeException(
'TCA type "' . $type .
'" is not implemented in site handling', 1521032781);
331 $newSysSiteData = array_merge($currentSiteConfiguration, $newSysSiteData);
336 if (!$isNewConfiguration && $currentIdentifier !== $siteIdentifier) {
337 $siteConfigurationManager->rename($currentIdentifier, $siteIdentifier);
339 $siteConfigurationManager->write($siteIdentifier, $newSiteConfiguration);
340 }
catch (SiteValidationErrorException $e) {
344 $saveRoute = $uriBuilder->buildUriFromRoute(
'site_configuration', [
'action' =>
'edit',
'site' => $siteIdentifier]);
346 return new RedirectResponse($overviewRoute);
348 return new RedirectResponse($saveRoute);
368 $this->siteFinder->getSiteByIdentifier($identifier);
370 $originalIdentifier = $identifier;
371 $identifier = $identifier .
'-' . str_replace(
'.',
'', uniqid((
string)mt_rand(),
true));
373 $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.message'),
377 $messageTitle = $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.title');
378 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle,
FlashMessage::WARNING,
true);
379 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
380 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
381 $defaultFlashMessageQueue->enqueue($flashMessage);
382 }
catch (SiteNotFoundException $e) {
390 $site = $this->siteFinder->getSiteByIdentifier($identifier);
391 if ($site->getRootPageId() !== $rootPageId) {
393 $origSite = $this->siteFinder->getSiteByRootPageId($rootPageId);
394 $originalIdentifier = $identifier;
395 $identifier = $origSite->getIdentifier();
397 $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.message'),
401 $messageTitle = $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.title');
402 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle,
FlashMessage::WARNING,
true);
403 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
404 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
405 $defaultFlashMessageQueue->enqueue($flashMessage);
407 }
catch (SiteNotFoundException $e) {
429 $fieldConfig =
$GLOBALS[
'TCA'][$tableName][
'columns'][$fieldName][
'config'];
431 if (!empty($fieldConfig[
'eval'])) {
432 $evalArray = GeneralUtility::trimExplode(
',', $fieldConfig[
'eval'],
true);
434 if (in_array(
'alphanum_x', $evalArray,
true)) {
435 $handledEvals[] =
'alphanum_x';
436 $fieldValue = preg_replace(
'/[^a-zA-Z0-9_-]/',
'', $fieldValue);
438 if (in_array(
'lower', $evalArray,
true)) {
439 $handledEvals[] =
'lower';
440 $fieldValue = mb_strtolower($fieldValue,
'utf-8');
442 if (in_array(
'trim', $evalArray,
true)) {
443 $handledEvals[] =
'trim';
444 $fieldValue = trim($fieldValue);
446 if (in_array(
'int', $evalArray,
true)) {
447 $handledEvals[] =
'int';
448 $fieldValue = (int)$fieldValue;
452 if (in_array(
'required', $evalArray,
true)) {
453 $handledEvals[] =
'required';
454 if (empty($fieldValue)) {
456 $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.message'),
459 $messageTitle = $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.title');
460 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle,
FlashMessage::WARNING,
true);
461 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
462 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
463 $defaultFlashMessageQueue->enqueue($flashMessage);
464 throw new SiteValidationErrorException(
465 'Field ' . $fieldName .
' is set to required, but received empty.',
470 if (!empty(array_diff($evalArray, $handledEvals))) {
471 throw new \RuntimeException(
'At least one not implemented \'eval\' in list ' . $fieldConfig[
'eval'], 1522491734);
474 if (isset($fieldConfig[
'range'][
'lower'])) {
475 $fieldValue = (int)$fieldValue < (
int)$fieldConfig[
'range'][
'lower'] ? (int)$fieldConfig[
'range'][
'lower'] : (
int)$fieldValue;
477 if (isset($fieldConfig[
'range'][
'upper'])) {
478 $fieldValue = (int)$fieldValue > (
int)$fieldConfig[
'range'][
'upper'] ? (int)$fieldConfig[
'range'][
'upper'] : (
int)$fieldValue;
495 if (isset($newSysSiteData[
'errorHandling']) && is_array($newSysSiteData[
'errorHandling'])) {
496 $uniqueCriteria = [];
498 foreach ($newSysSiteData[
'errorHandling'] as $child) {
499 if (!isset($child[
'errorCode'])) {
500 throw new \RuntimeException(
'No errorCode found', 1521788518);
502 if (!in_array((
int)$child[
'errorCode'], $uniqueCriteria,
true)) {
503 $uniqueCriteria[] = (int)$child[
'errorCode'];
504 $validChildren[] = $child;
507 $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.message'),
510 $messageTitle = $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.title');
511 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle,
FlashMessage::WARNING,
true);
512 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
513 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
514 $defaultFlashMessageQueue->enqueue($flashMessage);
517 $newSysSiteData[
'errorHandling'] = $validChildren;
521 if (!isset($newSysSiteData[
'languages']) || !is_array($newSysSiteData[
'languages']) || count($newSysSiteData[
'languages']) < 1) {
522 throw new \RuntimeException(
523 'No default language definition found. The interface does not allow this. Aborting',
527 $uniqueCriteria = [];
529 foreach ($newSysSiteData[
'languages'] as $child) {
530 if (!isset($child[
'languageId'])) {
531 throw new \RuntimeException(
'languageId not found', 1521789455);
533 if (!in_array((
int)$child[
'languageId'], $uniqueCriteria,
true)) {
534 $uniqueCriteria[] = (int)$child[
'languageId'];
535 $validChildren[] = $child;
538 $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title'),
541 $messageTitle = $languageService->sL(
'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title');
542 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle,
FlashMessage::WARNING,
true);
543 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
544 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
545 $defaultFlashMessageQueue->enqueue($flashMessage);
548 $newSysSiteData[
'languages'] = $validChildren;
550 return $newSysSiteData;
559 protected function deleteAction(ServerRequestInterface $request): ResponseInterface
561 $siteIdentifier = $request->getQueryParams()[
'site'] ??
'';
562 if (empty($siteIdentifier)) {
563 throw new \RuntimeException(
'Not site identifier given', 1521565182);
567 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
568 $overviewRoute = $uriBuilder->buildUriFromRoute(
'site_configuration', [
'action' =>
'overview']);
569 return new RedirectResponse($overviewRoute);
579 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
580 $this->view->setTemplate($templateName);
581 $this->view->setTemplateRootPaths([
'EXT:backend/Resources/Private/Templates/SiteConfiguration']);
582 $this->view->setPartialRootPaths([
'EXT:backend/Resources/Private/Partials']);
583 $this->view->setLayoutRootPaths([
'EXT:backend/Resources/Private/Layouts']);
591 $iconFactory = $this->moduleTemplate->getIconFactory();
592 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
594 $closeButton = $buttonBar->makeLinkButton()
596 ->setClasses(
't3js-editform-close')
597 ->setTitle($lang->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
598 ->setShowLabelText(
true)
600 $saveButton = $buttonBar->makeInputButton()
601 ->setTitle($lang->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
602 ->setName(
'_savedok')
604 ->setShowLabelText(
true)
605 ->setForm(
'siteConfigurationController')
607 $buttonBar->addButton($closeButton);
616 $iconFactory = $this->moduleTemplate->getIconFactory();
617 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
618 $reloadButton = $buttonBar->makeLinkButton()
619 ->setHref(GeneralUtility::getIndpEnv(
'REQUEST_URI'))
620 ->setTitle($this->
getLanguageService()->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
624 $getVars = [
'id',
'route'];
625 $shortcutButton = $buttonBar->makeShortcutButton()
626 ->setModuleName(
'site_configuration')
627 ->setGetVariables($getVars);
639 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
'pages');
640 $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
641 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, 0,
false));
642 $statement = $queryBuilder
646 $queryBuilder->expr()->eq(
'sys_language_uid', 0),
647 $queryBuilder->expr()->orX(
648 $queryBuilder->expr()->andX(
649 $queryBuilder->expr()->eq(
'pid', 0),
652 $queryBuilder->expr()->eq(
'is_siteroot', 1)
656 ->addOrderBy(
'sorting')
660 while ($row = $statement->fetch()) {
662 array_pop($row[
'rootline']);
663 $row[
'rootline'] = array_reverse($row[
'rootline']);
665 foreach ($row[
'rootline'] as &$record) {
666 $record[
'margin'] = $i++ * 20;
668 $pages[(int)$row[
'uid']] = $row;