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