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