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