[BUGFIX] Do not show sysfolders on pid 0 in sites module
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / SiteConfigurationController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Configuration\SiteTcaConfiguration;
21 use TYPO3\CMS\Backend\Exception\SiteValidationErrorException;
22 use TYPO3\CMS\Backend\Form\FormDataCompiler;
23 use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
24 use TYPO3\CMS\Backend\Form\FormResultCompiler;
25 use TYPO3\CMS\Backend\Form\NodeFactory;
26 use TYPO3\CMS\Backend\Routing\UriBuilder;
27 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
28 use TYPO3\CMS\Backend\Template\ModuleTemplate;
29 use TYPO3\CMS\Backend\Utility\BackendUtility;
30 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
31 use TYPO3\CMS\Core\Configuration\SiteConfiguration;
32 use TYPO3\CMS\Core\Core\Environment;
33 use TYPO3\CMS\Core\Database\ConnectionPool;
34 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
35 use TYPO3\CMS\Core\Http\HtmlResponse;
36 use TYPO3\CMS\Core\Http\RedirectResponse;
37 use TYPO3\CMS\Core\Imaging\Icon;
38 use TYPO3\CMS\Core\Localization\LanguageService;
39 use TYPO3\CMS\Core\Messaging\FlashMessage;
40 use TYPO3\CMS\Core\Messaging\FlashMessageService;
41 use TYPO3\CMS\Core\Site\Entity\Site;
42 use TYPO3\CMS\Core\Site\SiteFinder;
43 use TYPO3\CMS\Core\Utility\GeneralUtility;
44 use TYPO3\CMS\Fluid\View\StandaloneView;
45 use TYPO3\CMS\Frontend\Page\PageRepository;
46 use TYPO3Fluid\Fluid\View\ViewInterface;
47
48 /**
49 * Backend controller: The "Site management" -> "Sites" module
50 *
51 * List all site root pages, CRUD site configuration.
52 */
53 class SiteConfigurationController
54 {
55 /**
56 * @var ModuleTemplate
57 */
58 protected $moduleTemplate;
59
60 /**
61 * @var ViewInterface
62 */
63 protected $view;
64
65 /**
66 * @var SiteFinder
67 */
68 protected $siteFinder;
69
70 /**
71 * Default constructor
72 */
73 public function __construct()
74 {
75 $this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
76 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
77 }
78
79 /**
80 * Main entry method: Dispatch to other actions - those method names that end with "Action".
81 *
82 * @param ServerRequestInterface $request the current request
83 * @return ResponseInterface the response with the content
84 */
85 public function handleRequest(ServerRequestInterface $request): ResponseInterface
86 {
87 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
88 $action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'overview';
89 $this->initializeView($action);
90 $result = call_user_func_array([$this, $action . 'Action'], [$request]);
91 if ($result instanceof ResponseInterface) {
92 return $result;
93 }
94 $this->moduleTemplate->setContent($this->view->render());
95 return new HtmlResponse($this->moduleTemplate->renderContent());
96 }
97
98 /**
99 * List pages that have 'is_siteroot' flag set - those that have the globe icon in page tree.
100 * Link to Add / Edit / Delete for each.
101 */
102 protected function overviewAction(): void
103 {
104 $this->configureOverViewDocHeader();
105 $allSites = $this->siteFinder->getAllSites();
106 $pages = $this->getAllSitePages();
107 foreach ($allSites as $identifier => $site) {
108 $rootPageId = $site->getRootPageId();
109 if (isset($pages[$rootPageId])) {
110 $pages[$rootPageId]['siteIdentifier'] = $identifier;
111 $pages[$rootPageId]['siteConfiguration'] = $site;
112 }
113 }
114 $this->view->assign('pages', $pages);
115 }
116
117 /**
118 * Shows a form to create a new site configuration, or edit an existing one.
119 *
120 * @param ServerRequestInterface $request
121 * @throws \RuntimeException
122 */
123 protected function editAction(ServerRequestInterface $request): void
124 {
125 $this->configureEditViewDocHeader();
126
127 // Put sys_site and friends TCA into global TCA
128 // @todo: We might be able to get rid of that later
129 $GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
130
131 $siteIdentifier = $request->getQueryParams()['site'] ?? null;
132 $pageUid = (int)($request->getQueryParams()['pageUid'] ?? 0);
133
134 if (empty($siteIdentifier) && empty($pageUid)) {
135 throw new \RuntimeException('Either site identifier to edit a config or page uid to add new config must be set', 1521561148);
136 }
137 $isNewConfig = empty($siteIdentifier);
138
139 $defaultValues = [];
140 if ($isNewConfig) {
141 $defaultValues['sys_site']['rootPageId'] = $pageUid;
142 }
143
144 $allSites = $this->siteFinder->getAllSites();
145 if (!$isNewConfig && !isset($allSites[$siteIdentifier])) {
146 throw new \RuntimeException('Existing config for site ' . $siteIdentifier . ' not found', 1521561226);
147 }
148
149 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
150 $returnUrl = $uriBuilder->buildUriFromRoute('site_configuration');
151
152 $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
153 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
154 $formDataCompilerInput = [
155 'tableName' => 'sys_site',
156 'vanillaUid' => $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId(),
157 'command' => $isNewConfig ? 'new' : 'edit',
158 'returnUrl' => (string)$returnUrl,
159 'customData' => [
160 'siteIdentifier' => $isNewConfig ? '' : $siteIdentifier,
161 ],
162 'defaultValues' => $defaultValues,
163 ];
164 $formData = $formDataCompiler->compile($formDataCompilerInput);
165 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
166 $formData['renderType'] = 'outerWrapContainer';
167 $formResult = $nodeFactory->create($formData)->render();
168 // Needed to be set for 'onChange="reload"' and reload on type change to work
169 $formResult['doSaveFieldName'] = 'doSave';
170 $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
171 $formResultCompiler->mergeResult($formResult);
172 $formResultCompiler->addCssFiles();
173 // Always add rootPageId as additional field to have a reference for new records
174 $this->view->assign('rootPageId', $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId());
175 $this->view->assign('returnUrl', $returnUrl);
176 $this->view->assign('formEngineHtml', $formResult['html']);
177 $this->view->assign('formEngineFooter', $formResultCompiler->printNeededJSFunctions());
178 }
179
180 /**
181 * Save incoming data from editAction and redirect to overview or edit
182 *
183 * @param ServerRequestInterface $request
184 * @return ResponseInterface
185 * @throws \RuntimeException
186 */
187 protected function saveAction(ServerRequestInterface $request): ResponseInterface
188 {
189 // Put sys_site and friends TCA into global TCA
190 // @todo: We might be able to get rid of that later
191 $GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
192
193 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
194 $siteTca = GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca();
195
196 $overviewRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']);
197 $parsedBody = $request->getParsedBody();
198 if (isset($parsedBody['closeDoc']) && (int)$parsedBody['closeDoc'] === 1) {
199 // Closing means no save, just redirect to overview
200 return new RedirectResponse($overviewRoute);
201 }
202 $isSave = $parsedBody['_savedok'] ?? $parsedBody['doSave'] ?? false;
203 $isSaveClose = $parsedBody['_saveandclosedok'] ?? false;
204 if (!$isSave && !$isSaveClose) {
205 throw new \RuntimeException('Either save or save and close', 1520370364);
206 }
207
208 if (!isset($parsedBody['data']['sys_site']) || !is_array($parsedBody['data']['sys_site'])) {
209 throw new \RuntimeException('No sys_site data or sys_site identifier given', 1521030950);
210 }
211
212 $data = $parsedBody['data'];
213 // This can be NEW123 for new records
214 $pageId = (int)key($data['sys_site']);
215 $sysSiteRow = current($data['sys_site']);
216 $siteIdentifier = $sysSiteRow['identifier'] ?? '';
217
218 $isNewConfiguration = false;
219 $currentIdentifier = '';
220 try {
221 $currentSite = $this->siteFinder->getSiteByRootPageId($pageId);
222 $currentSiteConfiguration = $currentSite->getConfiguration();
223 $currentIdentifier = $currentSite->getIdentifier();
224 } catch (SiteNotFoundException $e) {
225 $isNewConfiguration = true;
226 $pageId = (int)$parsedBody['rootPageId'];
227 if (!$pageId > 0) {
228 // Early validation of rootPageId - it must always be given and greater than 0
229 throw new \RuntimeException('No root page id found', 1521719709);
230 }
231 }
232
233 // Validate site identifier and do not store or further process it
234 $siteIdentifier = $this->validateAndProcessIdentifier($isNewConfiguration, $siteIdentifier, $pageId);
235 unset($sysSiteRow['identifier']);
236
237 try {
238 $newSysSiteData = [];
239 // Hard set rootPageId: This is TCA readOnly and not transmitted by FormEngine, but is also the "uid" of the sys_site record
240 $newSysSiteData['site']['rootPageId'] = $pageId;
241 foreach ($sysSiteRow as $fieldName => $fieldValue) {
242 $type = $siteTca['sys_site']['columns'][$fieldName]['config']['type'];
243 if ($type === 'input') {
244 $fieldValue = $this->validateAndProcessValue('sys_site', $fieldName, $fieldValue);
245 $newSysSiteData['site'][$fieldName] = $fieldValue;
246 } elseif ($type === 'inline') {
247 $newSysSiteData['site'][$fieldName] = [];
248 $childRowIds = GeneralUtility::trimExplode(',', $fieldValue, true);
249 if (!isset($siteTca['sys_site']['columns'][$fieldName]['config']['foreign_table'])) {
250 throw new \RuntimeException('No foreign_table found for inline type', 1521555037);
251 }
252 $foreignTable = $siteTca['sys_site']['columns'][$fieldName]['config']['foreign_table'];
253 foreach ($childRowIds as $childRowId) {
254 $childRowData = [];
255 if (!isset($data[$foreignTable][$childRowId])) {
256 if (!empty($currentSiteConfiguration[$fieldName][$childRowId])) {
257 // A collapsed inline record: Fetch data from existing config
258 $newSysSiteData['site'][$fieldName][] = $currentSiteConfiguration[$fieldName][$childRowId];
259 continue;
260 }
261 throw new \RuntimeException('No data found for table ' . $foreignTable . ' with id ' . $childRowId, 1521555177);
262 }
263 $childRow = $data[$foreignTable][$childRowId];
264 foreach ($childRow as $childFieldName => $childFieldValue) {
265 if ($childFieldName === 'pid') {
266 // pid is added by inline by default, but not relevant for yml storage
267 continue;
268 }
269 $type = $siteTca[$foreignTable]['columns'][$childFieldName]['config']['type'];
270 if ($type === 'input') {
271 $childRowData[$childFieldName] = $childFieldValue;
272 } elseif ($type === 'select') {
273 $childRowData[$childFieldName] = $childFieldValue;
274 } else {
275 throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1521555340);
276 }
277 }
278 $newSysSiteData['site'][$fieldName][] = $childRowData;
279 }
280 } elseif ($type === 'select') {
281 $newSysSiteData['site'][$fieldName] = (int)$fieldValue;
282 } else {
283 throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1521032781);
284 }
285 }
286
287 $newSiteConfiguration = $this->validateFullStructure($newSysSiteData);
288
289 // Persist the configuration
290 $siteConfigurationManager = GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites');
291 if (!$isNewConfiguration && $currentIdentifier !== $siteIdentifier) {
292 $siteConfigurationManager->rename($currentIdentifier, $siteIdentifier);
293 }
294 $siteConfigurationManager->write($siteIdentifier, $newSiteConfiguration);
295 } catch (SiteValidationErrorException $e) {
296 // Do not store new config if a validation error is thrown, but redirect only to show a generated flash message
297 }
298
299 $saveRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'edit', 'site' => $siteIdentifier]);
300 if ($isSaveClose) {
301 return new RedirectResponse($overviewRoute);
302 }
303 return new RedirectResponse($saveRoute);
304 }
305
306 /**
307 * Validation and processing of site identifier
308 *
309 * @param bool $isNew If true, we're dealing with a new record
310 * @param string $identifier Given identifier to validate and process
311 * @param int $rootPageId Page uid this identifier is bound to
312 * @return mixed Verified / modified value
313 */
314 protected function validateAndProcessIdentifier(bool $isNew, string $identifier, int $rootPageId)
315 {
316 $languageService = $this->getLanguageService();
317 // Normal "eval" processing of field first
318 $identifier = $this->validateAndProcessValue('sys_site', 'identifier', $identifier);
319 if ($isNew) {
320 // Verify no other site with this identifier exists. If so, find a new unique name as
321 // identifier and show a flash message the identifier has been adapted
322 try {
323 $this->siteFinder->getSiteByIdentifier($identifier);
324 // Force this identifier to be unique
325 $originalIdentifier = $identifier;
326 $identifier = $identifier . '-' . str_replace('.', '', uniqid((string)mt_rand(), true));
327 $message = sprintf(
328 $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.message'),
329 $originalIdentifier,
330 $identifier
331 );
332 $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.title');
333 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
334 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
335 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
336 $defaultFlashMessageQueue->enqueue($flashMessage);
337 } catch (SiteNotFoundException $e) {
338 // Do nothing, this new identifier is ok
339 }
340 } else {
341 // If this is an existing config, the site for this identifier must have the same rootPageId, otherwise
342 // a user tried to rename a site identifier to a different site that already exists. If so, we do not rename
343 // the site and show a flash message
344 try {
345 $site = $this->siteFinder->getSiteByIdentifier($identifier);
346 if ($site->getRootPageId() !== $rootPageId) {
347 // Find original value and keep this
348 $origSite = $this->siteFinder->getSiteByRootPageId($rootPageId);
349 $originalIdentifier = $identifier;
350 $identifier = $origSite->getIdentifier();
351 $message = sprintf(
352 $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.message'),
353 $originalIdentifier,
354 $identifier
355 );
356 $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.title');
357 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
358 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
359 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
360 $defaultFlashMessageQueue->enqueue($flashMessage);
361 }
362 } catch (SiteNotFoundException $e) {
363 // User is renaming identifier which does not exist yet. That's ok
364 }
365 }
366 return $identifier;
367 }
368
369 /**
370 * Simple validation and processing method for incoming form field values.
371 *
372 * Note this does not support all TCA "eval" options but only what we really need.
373 *
374 * @param string $tableName Table name
375 * @param string $fieldName Field name
376 * @param mixed $fieldValue Incoming value from FormEngine
377 * @return mixed Verified / modified value
378 * @throws SiteValidationErrorException
379 * @throws \RuntimeException
380 */
381 protected function validateAndProcessValue(string $tableName, string $fieldName, $fieldValue)
382 {
383 $languageService = $this->getLanguageService();
384 $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
385 $handledEvals = [];
386 if (!empty($fieldConfig['eval'])) {
387 $evalArray = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
388 // Processing
389 if (in_array('alphanum_x', $evalArray, true)) {
390 $handledEvals[] = 'alphanum_x';
391 $fieldValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $fieldValue);
392 }
393 if (in_array('lower', $evalArray, true)) {
394 $handledEvals[] = 'lower';
395 $fieldValue = mb_strtolower($fieldValue, 'utf-8');
396 }
397 if (in_array('trim', $evalArray, true)) {
398 $handledEvals[] = 'trim';
399 $fieldValue = trim($fieldValue);
400 }
401 if (in_array('int', $evalArray, true)) {
402 $handledEvals[] = 'int';
403 $fieldValue = (int)$fieldValue;
404 }
405 // Validation throws - these should be handled client side already,
406 // eg. 'required' being set and receiving empty, shouldn't happen server side
407 if (in_array('required', $evalArray, true)) {
408 $handledEvals[] = 'required';
409 if (empty($fieldValue)) {
410 $message = sprintf(
411 $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.message'),
412 $fieldName
413 );
414 $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.title');
415 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
416 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
417 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
418 $defaultFlashMessageQueue->enqueue($flashMessage);
419 throw new SiteValidationErrorException(
420 'Field ' . $fieldName . ' is set to required, but received empty.',
421 1521726421
422 );
423 }
424 }
425 if (!empty(array_diff($evalArray, $handledEvals))) {
426 throw new \RuntimeException('At least one not implemented \'eval\' in list ' . $fieldConfig['eval'], 1522491734);
427 }
428 }
429 if (isset($fieldConfig['range']['lower'])) {
430 $fieldValue = (int)$fieldValue < (int)$fieldConfig['range']['lower'] ? (int)$fieldConfig['range']['lower'] : (int)$fieldValue;
431 }
432 if (isset($fieldConfig['range']['upper'])) {
433 $fieldValue = (int)$fieldValue > (int)$fieldConfig['range']['upper'] ? (int)$fieldConfig['range']['upper'] : (int)$fieldValue;
434 }
435 return $fieldValue;
436 }
437
438 /**
439 * Last sanitation method after all data has been gathered. Check integrity
440 * of full record, manipulate if possible, or throw exception if unfixable broken.
441 *
442 * @param array $newSysSiteData Incoming data
443 * @return array Updated data if needed
444 * @throws \RuntimeException
445 */
446 protected function validateFullStructure(array $newSysSiteData): array
447 {
448 $languageService = $this->getLanguageService();
449 // Verify there are not two error handlers with the same error code
450 if (isset($newSysSiteData['site']['errorHandling']) && is_array($newSysSiteData['site']['errorHandling'])) {
451 $uniqueCriteria = [];
452 $validChildren = [];
453 foreach ($newSysSiteData['site']['errorHandling'] as $child) {
454 if (!isset($child['errorCode'])) {
455 throw new \RuntimeException('No errorCode found', 1521788518);
456 }
457 if (!in_array((int)$child['errorCode'], $uniqueCriteria, true)) {
458 $uniqueCriteria[] = (int)$child['errorCode'];
459 $validChildren[] = $child;
460 } else {
461 $message = sprintf(
462 $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.message'),
463 $child['errorCode']
464 );
465 $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.title');
466 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
467 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
468 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
469 $defaultFlashMessageQueue->enqueue($flashMessage);
470 }
471 }
472 $newSysSiteData['site']['errorHandling'] = $validChildren;
473 }
474
475 // Verify there is only one inline child per sys_language record configured.
476 if (!isset($newSysSiteData['site']['languages']) || !is_array($newSysSiteData['site']['languages']) || count($newSysSiteData['site']['languages']) < 1) {
477 throw new \RuntimeException(
478 'No default language definition found. The interface does not allow this. Aborting',
479 1521789306
480 );
481 }
482 $uniqueCriteria = [];
483 $validChildren = [];
484 foreach ($newSysSiteData['site']['languages'] as $child) {
485 if (!isset($child['languageId'])) {
486 throw new \RuntimeException('languageId not found', 1521789455);
487 }
488 if (!in_array((int)$child['languageId'], $uniqueCriteria, true)) {
489 $uniqueCriteria[] = (int)$child['languageId'];
490 $validChildren[] = $child;
491 } else {
492 $message = sprintf(
493 $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title'),
494 $child['languageId']
495 );
496 $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title');
497 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
498 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
499 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
500 $defaultFlashMessageQueue->enqueue($flashMessage);
501 }
502 }
503 $newSysSiteData['site']['languages'] = $validChildren;
504
505 return $newSysSiteData;
506 }
507
508 /**
509 * Delete an existing configuration
510 *
511 * @param ServerRequestInterface $request
512 * @return ResponseInterface
513 */
514 protected function deleteAction(ServerRequestInterface $request): ResponseInterface
515 {
516 $siteIdentifier = $request->getQueryParams()['site'] ?? '';
517 if (empty($siteIdentifier)) {
518 throw new \RuntimeException('Not site identifier given', 1521565182);
519 }
520 // Verify site does exist, method throws if not
521 GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites')->delete($siteIdentifier);
522 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
523 $overviewRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']);
524 return new RedirectResponse($overviewRoute);
525 }
526
527 /**
528 * Sets up the Fluid View.
529 *
530 * @param string $templateName
531 */
532 protected function initializeView(string $templateName): void
533 {
534 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
535 $this->view->setTemplate($templateName);
536 $this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/SiteConfiguration']);
537 $this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
538 $this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
539 }
540
541 /**
542 * Create document header buttons of "edit" action
543 */
544 protected function configureEditViewDocHeader(): void
545 {
546 $iconFactory = $this->moduleTemplate->getIconFactory();
547 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
548 $lang = $this->getLanguageService();
549 $closeButton = $buttonBar->makeLinkButton()
550 ->setHref('#')
551 ->setClasses('t3js-editform-close')
552 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
553 ->setIcon($iconFactory->getIcon('actions-close', Icon::SIZE_SMALL));
554 $saveButton = $buttonBar->makeInputButton()
555 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
556 ->setName('_savedok')
557 ->setValue('1')
558 ->setForm('siteConfigurationController')
559 ->setIcon($iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL));
560 $saveAndCloseButton = $buttonBar->makeInputButton()
561 ->setName('_saveandclosedok')
562 ->setClasses('t3js-editform-submitButton')
563 ->setValue('1')
564 ->setForm('siteConfigurationController')
565 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
566 ->setIcon($iconFactory->getIcon('actions-document-save-close', Icon::SIZE_SMALL));
567 $saveSplitButton = $buttonBar->makeSplitButton();
568 $saveSplitButton->addItem($saveButton, true);
569 $saveSplitButton->addItem($saveAndCloseButton);
570 $buttonBar->addButton($closeButton);
571 $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
572 }
573
574 /**
575 * Create document header buttons of "overview" action
576 */
577 protected function configureOverViewDocHeader(): void
578 {
579 $iconFactory = $this->moduleTemplate->getIconFactory();
580 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
581 $reloadButton = $buttonBar->makeLinkButton()
582 ->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
583 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.reload'))
584 ->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
585 $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
586 if ($this->getBackendUser()->mayMakeShortcut()) {
587 $getVars = ['id', 'route'];
588 $shortcutButton = $buttonBar->makeShortcutButton()
589 ->setModuleName('site_configuration')
590 ->setGetVariables($getVars);
591 $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
592 }
593 }
594
595 /**
596 * Returns a list of pages that have 'is_siteroot' set
597 *
598 * @return array
599 */
600 protected function getAllSitePages(): array
601 {
602 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
603 $statement = $queryBuilder
604 ->select('*')
605 ->from('pages')
606 ->where(
607 $queryBuilder->expr()->eq('sys_language_uid', 0),
608 $queryBuilder->expr()->orX(
609 $queryBuilder->expr()->andX(
610 $queryBuilder->expr()->eq('pid', 0),
611 $queryBuilder->expr()->neq('doktype', PageRepository::DOKTYPE_SYSFOLDER)
612 ),
613 $queryBuilder->expr()->eq('is_siteroot', 1)
614 )
615 )
616 ->orderBy('pid')
617 ->addOrderBy('sorting')
618 ->execute();
619
620 $pages = [];
621 while ($row = $statement->fetch()) {
622 $row['rootline'] = BackendUtility::BEgetRootLine((int)$row['uid']);
623 array_pop($row['rootline']);
624 $row['rootline'] = array_reverse($row['rootline']);
625 $i = 0;
626 foreach ($row['rootline'] as &$record) {
627 $record['margin'] = $i++ * 20;
628 }
629 $pages[(int)$row['uid']] = $row;
630 }
631 return $pages;
632 }
633
634 /**
635 * @return LanguageService
636 */
637 protected function getLanguageService(): LanguageService
638 {
639 return $GLOBALS['LANG'];
640 }
641
642 /**
643 * @return BackendUserAuthentication
644 */
645 protected function getBackendUser(): BackendUserAuthentication
646 {
647 return $GLOBALS['BE_USER'];
648 }
649 }