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