| Total Complexity | 118 |
| Total Lines | 764 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like SiteConfigurationController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SiteConfigurationController, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 63 | class SiteConfigurationController |
||
| 64 | { |
||
| 65 | protected const ALLOWED_ACTIONS = ['overview', 'edit', 'save', 'delete']; |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @var ModuleTemplate |
||
| 69 | */ |
||
| 70 | protected $moduleTemplate; |
||
| 71 | |||
| 72 | /** |
||
| 73 | * @var ViewInterface |
||
| 74 | */ |
||
| 75 | protected $view; |
||
| 76 | |||
| 77 | protected SiteFinder $siteFinder; |
||
| 78 | protected IconFactory $iconFactory; |
||
| 79 | protected PageRenderer $pageRenderer; |
||
| 80 | protected UriBuilder $uriBuilder; |
||
| 81 | protected ModuleTemplateFactory $moduleTemplateFactory; |
||
| 82 | |||
| 83 | public function __construct( |
||
| 95 | } |
||
| 96 | |||
| 97 | /** |
||
| 98 | * Main entry method: Dispatch to other actions - those method names that end with "Action". |
||
| 99 | * |
||
| 100 | * @param ServerRequestInterface $request the current request |
||
| 101 | * @return ResponseInterface the response with the content |
||
| 102 | */ |
||
| 103 | public function handleRequest(ServerRequestInterface $request): ResponseInterface |
||
| 104 | { |
||
| 105 | $this->moduleTemplate = $this->moduleTemplateFactory->create($request); |
||
| 106 | // forcing uncached sites will re-initialize `SiteFinder` |
||
| 107 | // which is used later by FormEngine (implicit behavior) |
||
| 108 | $this->siteFinder->getAllSites(false); |
||
| 109 | $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu'); |
||
| 110 | $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal'); |
||
| 111 | $action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'overview'; |
||
| 112 | |||
| 113 | if (!in_array($action, self::ALLOWED_ACTIONS, true)) { |
||
| 114 | return new HtmlResponse('Action not allowed', 400); |
||
| 115 | } |
||
| 116 | |||
| 117 | $this->initializeView($action); |
||
| 118 | |||
| 119 | $result = $this->{$action . 'Action'}($request); |
||
| 120 | if ($result instanceof ResponseInterface) { |
||
| 121 | return $result; |
||
| 122 | } |
||
| 123 | $this->moduleTemplate->setContent($this->view->render()); |
||
| 124 | return new HtmlResponse($this->moduleTemplate->renderContent()); |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * List pages that have 'is_siteroot' flag set - those that have the globe icon in page tree. |
||
| 129 | * Link to Add / Edit / Delete for each. |
||
| 130 | * |
||
| 131 | * @param ServerRequestInterface $request |
||
| 132 | */ |
||
| 133 | protected function overviewAction(ServerRequestInterface $request): void |
||
| 134 | { |
||
| 135 | $this->configureOverViewDocHeader($request->getAttribute('normalizedParams')->getRequestUri()); |
||
| 136 | $allSites = $this->siteFinder->getAllSites(); |
||
| 137 | $pages = $this->getAllSitePages(); |
||
| 138 | $unassignedSites = []; |
||
| 139 | foreach ($allSites as $identifier => $site) { |
||
| 140 | $rootPageId = $site->getRootPageId(); |
||
| 141 | if (isset($pages[$rootPageId])) { |
||
| 142 | $pages[$rootPageId]['siteIdentifier'] = $identifier; |
||
| 143 | $pages[$rootPageId]['siteConfiguration'] = $site; |
||
| 144 | } else { |
||
| 145 | $unassignedSites[] = $site; |
||
| 146 | } |
||
| 147 | } |
||
| 148 | |||
| 149 | $this->moduleTemplate->setTitle( |
||
| 150 | $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf:mlang_tabs_tab') |
||
| 151 | ); |
||
| 152 | $this->view->assignMultiple([ |
||
| 153 | 'pages' => $pages, |
||
| 154 | 'unassignedSites' => $unassignedSites, |
||
| 155 | 'duplicatedEntryPoints' => $this->getDuplicatedEntryPoints($allSites, $pages), |
||
| 156 | ]); |
||
| 157 | } |
||
| 158 | |||
| 159 | /** |
||
| 160 | * Shows a form to create a new site configuration, or edit an existing one. |
||
| 161 | * |
||
| 162 | * @param ServerRequestInterface $request |
||
| 163 | * @throws \RuntimeException |
||
| 164 | */ |
||
| 165 | protected function editAction(ServerRequestInterface $request): void |
||
| 223 | ); |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Save incoming data from editAction and redirect to overview or edit |
||
| 228 | * |
||
| 229 | * @param ServerRequestInterface $request |
||
| 230 | * @return ResponseInterface |
||
| 231 | * @throws \RuntimeException |
||
| 232 | */ |
||
| 233 | protected function saveAction(ServerRequestInterface $request): ResponseInterface |
||
| 234 | { |
||
| 235 | // Put site and friends TCA into global TCA |
||
| 236 | // @todo: We might be able to get rid of that later |
||
| 237 | $GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca()); |
||
| 238 | |||
| 239 | $siteTca = GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca(); |
||
| 240 | |||
| 241 | $overviewRoute = $this->uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']); |
||
| 242 | $parsedBody = $request->getParsedBody(); |
||
| 243 | if (isset($parsedBody['closeDoc']) && (int)$parsedBody['closeDoc'] === 1) { |
||
| 244 | // Closing means no save, just redirect to overview |
||
| 245 | return new RedirectResponse($overviewRoute); |
||
| 246 | } |
||
| 247 | $isSave = $parsedBody['_savedok'] ?? $parsedBody['doSave'] ?? false; |
||
| 248 | $isSaveClose = $parsedBody['_saveandclosedok'] ?? false; |
||
| 249 | if (!$isSave && !$isSaveClose) { |
||
| 250 | throw new \RuntimeException('Either save or save and close', 1520370364); |
||
| 251 | } |
||
| 252 | |||
| 253 | if (!isset($parsedBody['data']['site']) || !is_array($parsedBody['data']['site'])) { |
||
| 254 | throw new \RuntimeException('No site data or site identifier given', 1521030950); |
||
| 255 | } |
||
| 256 | |||
| 257 | $data = $parsedBody['data']; |
||
| 258 | // This can be NEW123 for new records |
||
| 259 | $pageId = (int)key($data['site']); |
||
| 260 | $sysSiteRow = current($data['site']); |
||
| 261 | $siteIdentifier = $sysSiteRow['identifier'] ?? ''; |
||
| 262 | |||
| 263 | $isNewConfiguration = false; |
||
| 264 | $currentIdentifier = ''; |
||
| 265 | try { |
||
| 266 | $currentSite = $this->siteFinder->getSiteByRootPageId($pageId); |
||
| 267 | $currentSiteConfiguration = $currentSite->getConfiguration(); |
||
| 268 | $currentIdentifier = $currentSite->getIdentifier(); |
||
| 269 | } catch (SiteNotFoundException $e) { |
||
| 270 | $currentSiteConfiguration = []; |
||
| 271 | $isNewConfiguration = true; |
||
| 272 | $pageId = (int)$parsedBody['rootPageId']; |
||
| 273 | if (!$pageId > 0) { |
||
| 274 | // Early validation of rootPageId - it must always be given and greater than 0 |
||
| 275 | throw new \RuntimeException('No root page id found', 1521719709); |
||
| 276 | } |
||
| 277 | } |
||
| 278 | |||
| 279 | // Validate site identifier and do not store or further process it |
||
| 280 | $siteIdentifier = $this->validateAndProcessIdentifier($isNewConfiguration, $siteIdentifier, $pageId); |
||
| 281 | unset($sysSiteRow['identifier']); |
||
| 282 | |||
| 283 | try { |
||
| 284 | $newSysSiteData = []; |
||
| 285 | // Hard set rootPageId: This is TCA readOnly and not transmitted by FormEngine, but is also the "uid" of the site record |
||
| 286 | $newSysSiteData['rootPageId'] = $pageId; |
||
| 287 | foreach ($sysSiteRow as $fieldName => $fieldValue) { |
||
| 288 | $type = $siteTca['site']['columns'][$fieldName]['config']['type']; |
||
| 289 | switch ($type) { |
||
| 290 | case 'input': |
||
| 291 | case 'text': |
||
| 292 | $fieldValue = $this->validateAndProcessValue('site', $fieldName, $fieldValue); |
||
| 293 | $newSysSiteData[$fieldName] = $fieldValue; |
||
| 294 | break; |
||
| 295 | |||
| 296 | case 'inline': |
||
| 297 | $newSysSiteData[$fieldName] = []; |
||
| 298 | $childRowIds = GeneralUtility::trimExplode(',', $fieldValue, true); |
||
| 299 | if (!isset($siteTca['site']['columns'][$fieldName]['config']['foreign_table'])) { |
||
| 300 | throw new \RuntimeException('No foreign_table found for inline type', 1521555037); |
||
| 301 | } |
||
| 302 | $foreignTable = $siteTca['site']['columns'][$fieldName]['config']['foreign_table']; |
||
| 303 | foreach ($childRowIds as $childRowId) { |
||
| 304 | $childRowData = []; |
||
| 305 | if (!isset($data[$foreignTable][$childRowId])) { |
||
| 306 | if (!empty($currentSiteConfiguration[$fieldName][$childRowId])) { |
||
| 307 | // A collapsed inline record: Fetch data from existing config |
||
| 308 | $newSysSiteData[$fieldName][] = $currentSiteConfiguration[$fieldName][$childRowId]; |
||
| 309 | continue; |
||
| 310 | } |
||
| 311 | throw new \RuntimeException('No data found for table ' . $foreignTable . ' with id ' . $childRowId, 1521555177); |
||
| 312 | } |
||
| 313 | $childRow = $data[$foreignTable][$childRowId]; |
||
| 314 | foreach ($childRow as $childFieldName => $childFieldValue) { |
||
| 315 | if ($childFieldName === 'pid') { |
||
| 316 | // pid is added by inline by default, but not relevant for yml storage |
||
| 317 | continue; |
||
| 318 | } |
||
| 319 | $type = $siteTca[$foreignTable]['columns'][$childFieldName]['config']['type']; |
||
| 320 | switch ($type) { |
||
| 321 | case 'input': |
||
| 322 | case 'select': |
||
| 323 | case 'text': |
||
| 324 | $childRowData[$childFieldName] = $childFieldValue; |
||
| 325 | break; |
||
| 326 | case 'check': |
||
| 327 | $childRowData[$childFieldName] = (bool)$childFieldValue; |
||
| 328 | break; |
||
| 329 | default: |
||
| 330 | throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1521555340); |
||
| 331 | } |
||
| 332 | } |
||
| 333 | $newSysSiteData[$fieldName][] = $childRowData; |
||
| 334 | } |
||
| 335 | break; |
||
| 336 | |||
| 337 | case 'siteLanguage': |
||
| 338 | if (!isset($siteTca['site_language'])) { |
||
| 339 | throw new \RuntimeException('Required foreign table site_language does not exist', 1624286811); |
||
| 340 | } |
||
| 341 | if (!isset($siteTca['site_language']['columns']['languageId']) |
||
| 342 | || ($siteTca['site_language']['columns']['languageId']['config']['type'] ?? '') !== 'select' |
||
| 343 | ) { |
||
| 344 | throw new \RuntimeException( |
||
| 345 | 'Required foreign field languageId does not exist or is not of type select', |
||
| 346 | 1624286812 |
||
| 347 | ); |
||
| 348 | } |
||
| 349 | $newSysSiteData[$fieldName] = []; |
||
| 350 | $lastLanguageId = $this->getLastLanguageId(); |
||
| 351 | foreach (GeneralUtility::trimExplode(',', $fieldValue, true) as $childRowId) { |
||
| 352 | if (!isset($data['site_language'][$childRowId])) { |
||
| 353 | if (!empty($currentSiteConfiguration[$fieldName][$childRowId])) { |
||
| 354 | $newSysSiteData[$fieldName][] = $currentSiteConfiguration[$fieldName][$childRowId]; |
||
| 355 | continue; |
||
| 356 | } |
||
| 357 | throw new \RuntimeException('No data found for table site_language with id ' . $childRowId, 1624286813); |
||
| 358 | } |
||
| 359 | $childRowData = []; |
||
| 360 | foreach ($data['site_language'][$childRowId] ?? [] as $childFieldName => $childFieldValue) { |
||
| 361 | if ($childFieldName === 'pid') { |
||
| 362 | // pid is added by default, but not relevant for yml storage |
||
| 363 | continue; |
||
| 364 | } |
||
| 365 | if ($childFieldName === 'languageId' |
||
| 366 | && (int)$childFieldValue === PHP_INT_MAX |
||
| 367 | && StringUtility::beginsWith($childRowId, 'NEW') |
||
| 368 | ) { |
||
| 369 | // In case we deal with a new site language, whose "languageID" field is |
||
| 370 | // set to the PHP_INT_MAX placeholder, the next available language ID has |
||
| 371 | // to be used (auto-increment). |
||
| 372 | $childRowData[$childFieldName] = ++$lastLanguageId; |
||
| 373 | continue; |
||
| 374 | } |
||
| 375 | $type = $siteTca['site_language']['columns'][$childFieldName]['config']['type']; |
||
| 376 | switch ($type) { |
||
| 377 | case 'input': |
||
| 378 | case 'select': |
||
| 379 | case 'text': |
||
| 380 | $childRowData[$childFieldName] = $childFieldValue; |
||
| 381 | break; |
||
| 382 | case 'check': |
||
| 383 | $childRowData[$childFieldName] = (bool)$childFieldValue; |
||
| 384 | break; |
||
| 385 | default: |
||
| 386 | throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1624286814); |
||
| 387 | } |
||
| 388 | } |
||
| 389 | $newSysSiteData[$fieldName][] = $childRowData; |
||
| 390 | } |
||
| 391 | break; |
||
| 392 | |||
| 393 | case 'select': |
||
| 394 | if (MathUtility::canBeInterpretedAsInteger($fieldValue)) { |
||
| 395 | $fieldValue = (int)$fieldValue; |
||
| 396 | } elseif (is_array($fieldValue)) { |
||
| 397 | $fieldValue = implode(',', $fieldValue); |
||
| 398 | } |
||
| 399 | |||
| 400 | $newSysSiteData[$fieldName] = $fieldValue; |
||
| 401 | break; |
||
| 402 | |||
| 403 | case 'check': |
||
| 404 | $newSysSiteData[$fieldName] = (bool)$fieldValue; |
||
| 405 | break; |
||
| 406 | |||
| 407 | default: |
||
| 408 | throw new \RuntimeException('TCA type "' . $type . '" is not implemented in site handling', 1521032781); |
||
| 409 | } |
||
| 410 | } |
||
| 411 | |||
| 412 | // keep root config objects not given via GUI |
||
| 413 | // this way extension authors are able to use their own objects on root level |
||
| 414 | // that are not configurable via GUI |
||
| 415 | // however: we overwrite the full subset of any GUI object to make sure we have a clean state |
||
| 416 | $newSysSiteData = array_merge($currentSiteConfiguration, $newSysSiteData); |
||
| 417 | $newSiteConfiguration = $this->validateFullStructure($newSysSiteData); |
||
| 418 | |||
| 419 | // Persist the configuration |
||
| 420 | $siteConfigurationManager = GeneralUtility::makeInstance(SiteConfiguration::class); |
||
| 421 | if (!$isNewConfiguration && $currentIdentifier !== $siteIdentifier) { |
||
| 422 | $siteConfigurationManager->rename($currentIdentifier, $siteIdentifier); |
||
| 423 | } |
||
| 424 | $siteConfigurationManager->write($siteIdentifier, $newSiteConfiguration); |
||
| 425 | } catch (SiteValidationErrorException $e) { |
||
| 426 | // Do not store new config if a validation error is thrown, but redirect only to show a generated flash message |
||
| 427 | } |
||
| 428 | |||
| 429 | $saveRoute = $this->uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'edit', 'site' => $siteIdentifier]); |
||
| 430 | if ($isSaveClose) { |
||
| 431 | return new RedirectResponse($overviewRoute); |
||
| 432 | } |
||
| 433 | return new RedirectResponse($saveRoute); |
||
| 434 | } |
||
| 435 | |||
| 436 | /** |
||
| 437 | * Validation and processing of site identifier |
||
| 438 | * |
||
| 439 | * @param bool $isNew If true, we're dealing with a new record |
||
| 440 | * @param string $identifier Given identifier to validate and process |
||
| 441 | * @param int $rootPageId Page uid this identifier is bound to |
||
| 442 | * @return mixed Verified / modified value |
||
| 443 | */ |
||
| 444 | protected function validateAndProcessIdentifier(bool $isNew, string $identifier, int $rootPageId) |
||
| 445 | { |
||
| 446 | $languageService = $this->getLanguageService(); |
||
| 447 | // Normal "eval" processing of field first |
||
| 448 | $identifier = $this->validateAndProcessValue('site', 'identifier', $identifier); |
||
| 449 | if ($isNew) { |
||
| 450 | // Verify no other site with this identifier exists. If so, find a new unique name as |
||
| 451 | // identifier and show a flash message the identifier has been adapted |
||
| 452 | try { |
||
| 453 | $this->siteFinder->getSiteByIdentifier($identifier); |
||
| 454 | // Force this identifier to be unique |
||
| 455 | $originalIdentifier = $identifier; |
||
| 456 | $identifier = StringUtility::getUniqueId($identifier . '-'); |
||
| 457 | $message = sprintf( |
||
| 458 | $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.message'), |
||
| 459 | $originalIdentifier, |
||
| 460 | $identifier |
||
| 461 | ); |
||
| 462 | $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.title'); |
||
| 463 | $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true); |
||
| 464 | $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); |
||
| 465 | $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); |
||
| 466 | $defaultFlashMessageQueue->enqueue($flashMessage); |
||
| 467 | } catch (SiteNotFoundException $e) { |
||
| 468 | // Do nothing, this new identifier is ok |
||
| 469 | } |
||
| 470 | } else { |
||
| 471 | // If this is an existing config, the site for this identifier must have the same rootPageId, otherwise |
||
| 472 | // a user tried to rename a site identifier to a different site that already exists. If so, we do not rename |
||
| 473 | // the site and show a flash message |
||
| 474 | try { |
||
| 475 | $site = $this->siteFinder->getSiteByIdentifier($identifier); |
||
| 476 | if ($site->getRootPageId() !== $rootPageId) { |
||
| 477 | // Find original value and keep this |
||
| 478 | $origSite = $this->siteFinder->getSiteByRootPageId($rootPageId); |
||
| 479 | $originalIdentifier = $identifier; |
||
| 480 | $identifier = $origSite->getIdentifier(); |
||
| 481 | $message = sprintf( |
||
| 482 | $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.message'), |
||
| 483 | $originalIdentifier, |
||
| 484 | $identifier |
||
| 485 | ); |
||
| 486 | $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.title'); |
||
| 487 | $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true); |
||
| 488 | $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); |
||
| 489 | $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); |
||
| 490 | $defaultFlashMessageQueue->enqueue($flashMessage); |
||
| 491 | } |
||
| 492 | } catch (SiteNotFoundException $e) { |
||
| 493 | // User is renaming identifier which does not exist yet. That's ok |
||
| 494 | } |
||
| 495 | } |
||
| 496 | return $identifier; |
||
| 497 | } |
||
| 498 | |||
| 499 | /** |
||
| 500 | * Simple validation and processing method for incoming form field values. |
||
| 501 | * |
||
| 502 | * Note this does not support all TCA "eval" options but only what we really need. |
||
| 503 | * |
||
| 504 | * @param string $tableName Table name |
||
| 505 | * @param string $fieldName Field name |
||
| 506 | * @param mixed $fieldValue Incoming value from FormEngine |
||
| 507 | * @return mixed Verified / modified value |
||
| 508 | * @throws SiteValidationErrorException |
||
| 509 | * @throws \RuntimeException |
||
| 510 | */ |
||
| 511 | protected function validateAndProcessValue(string $tableName, string $fieldName, $fieldValue) |
||
| 512 | { |
||
| 513 | $languageService = $this->getLanguageService(); |
||
| 514 | $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']; |
||
| 515 | $handledEvals = []; |
||
| 516 | if (!empty($fieldConfig['eval'])) { |
||
| 517 | $evalArray = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true); |
||
| 518 | // Processing |
||
| 519 | if (in_array('alphanum_x', $evalArray, true)) { |
||
| 520 | $handledEvals[] = 'alphanum_x'; |
||
| 521 | $fieldValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $fieldValue); |
||
| 522 | } |
||
| 523 | if (in_array('lower', $evalArray, true)) { |
||
| 524 | $handledEvals[] = 'lower'; |
||
| 525 | $fieldValue = mb_strtolower($fieldValue, 'utf-8'); |
||
| 526 | } |
||
| 527 | if (in_array('trim', $evalArray, true)) { |
||
| 528 | $handledEvals[] = 'trim'; |
||
| 529 | $fieldValue = trim($fieldValue); |
||
| 530 | } |
||
| 531 | if (in_array('int', $evalArray, true)) { |
||
| 532 | $handledEvals[] = 'int'; |
||
| 533 | $fieldValue = (int)$fieldValue; |
||
| 534 | } |
||
| 535 | // Validation throws - these should be handled client side already, |
||
| 536 | // eg. 'required' being set and receiving empty, shouldn't happen server side |
||
| 537 | if (in_array('required', $evalArray, true)) { |
||
| 538 | $handledEvals[] = 'required'; |
||
| 539 | if (empty($fieldValue)) { |
||
| 540 | $message = sprintf( |
||
| 541 | $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.message'), |
||
| 542 | $fieldName |
||
| 543 | ); |
||
| 544 | $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.title'); |
||
| 545 | $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true); |
||
| 546 | $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); |
||
| 547 | $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); |
||
| 548 | $defaultFlashMessageQueue->enqueue($flashMessage); |
||
| 549 | throw new SiteValidationErrorException( |
||
| 550 | 'Field ' . $fieldName . ' is set to required, but received empty.', |
||
| 551 | 1521726421 |
||
| 552 | ); |
||
| 553 | } |
||
| 554 | } |
||
| 555 | if (!empty(array_diff($evalArray, $handledEvals))) { |
||
| 556 | throw new \RuntimeException('At least one not implemented \'eval\' in list ' . $fieldConfig['eval'], 1522491734); |
||
| 557 | } |
||
| 558 | } |
||
| 559 | if (isset($fieldConfig['range']['lower'])) { |
||
| 560 | $fieldValue = (int)$fieldValue < (int)$fieldConfig['range']['lower'] ? (int)$fieldConfig['range']['lower'] : (int)$fieldValue; |
||
| 561 | } |
||
| 562 | if (isset($fieldConfig['range']['upper'])) { |
||
| 563 | $fieldValue = (int)$fieldValue > (int)$fieldConfig['range']['upper'] ? (int)$fieldConfig['range']['upper'] : (int)$fieldValue; |
||
| 564 | } |
||
| 565 | return $fieldValue; |
||
| 566 | } |
||
| 567 | |||
| 568 | /** |
||
| 569 | * Last sanitation method after all data has been gathered. Check integrity |
||
| 570 | * of full record, manipulate if possible, or throw exception if unfixable broken. |
||
| 571 | * |
||
| 572 | * @param array $newSysSiteData Incoming data |
||
| 573 | * @return array Updated data if needed |
||
| 574 | * @throws \RuntimeException |
||
| 575 | */ |
||
| 576 | protected function validateFullStructure(array $newSysSiteData): array |
||
| 577 | { |
||
| 578 | $languageService = $this->getLanguageService(); |
||
| 579 | // Verify there are not two error handlers with the same error code |
||
| 580 | if (isset($newSysSiteData['errorHandling']) && is_array($newSysSiteData['errorHandling'])) { |
||
| 581 | $uniqueCriteria = []; |
||
| 582 | $validChildren = []; |
||
| 583 | foreach ($newSysSiteData['errorHandling'] as $child) { |
||
| 584 | if (!isset($child['errorCode'])) { |
||
| 585 | throw new \RuntimeException('No errorCode found', 1521788518); |
||
| 586 | } |
||
| 587 | if (!in_array((int)$child['errorCode'], $uniqueCriteria, true)) { |
||
| 588 | $uniqueCriteria[] = (int)$child['errorCode']; |
||
| 589 | $child['errorCode'] = (int)$child['errorCode']; |
||
| 590 | $validChildren[] = $child; |
||
| 591 | } else { |
||
| 592 | $message = sprintf( |
||
| 593 | $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.message'), |
||
| 594 | $child['errorCode'] |
||
| 595 | ); |
||
| 596 | $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.title'); |
||
| 597 | $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true); |
||
| 598 | $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); |
||
| 599 | $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); |
||
| 600 | $defaultFlashMessageQueue->enqueue($flashMessage); |
||
| 601 | } |
||
| 602 | } |
||
| 603 | $newSysSiteData['errorHandling'] = $validChildren; |
||
| 604 | } |
||
| 605 | |||
| 606 | // Verify there is only one inline child per sys_language record configured. |
||
| 607 | if (!isset($newSysSiteData['languages']) || !is_array($newSysSiteData['languages']) || count($newSysSiteData['languages']) < 1) { |
||
| 608 | throw new \RuntimeException( |
||
| 609 | 'No default language definition found. The interface does not allow this. Aborting', |
||
| 610 | 1521789306 |
||
| 611 | ); |
||
| 612 | } |
||
| 613 | $uniqueCriteria = []; |
||
| 614 | $validChildren = []; |
||
| 615 | foreach ($newSysSiteData['languages'] as $child) { |
||
| 616 | if (!isset($child['languageId'])) { |
||
| 617 | throw new \RuntimeException('languageId not found', 1521789455); |
||
| 618 | } |
||
| 619 | if (!in_array((int)$child['languageId'], $uniqueCriteria, true)) { |
||
| 620 | $uniqueCriteria[] = (int)$child['languageId']; |
||
| 621 | $child['languageId'] = (int)$child['languageId']; |
||
| 622 | $validChildren[] = $child; |
||
| 623 | } else { |
||
| 624 | $message = sprintf( |
||
| 625 | $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title'), |
||
| 626 | $child['languageId'] |
||
| 627 | ); |
||
| 628 | $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title'); |
||
| 629 | $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true); |
||
| 630 | $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); |
||
| 631 | $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(); |
||
| 632 | $defaultFlashMessageQueue->enqueue($flashMessage); |
||
| 633 | } |
||
| 634 | } |
||
| 635 | $newSysSiteData['languages'] = $validChildren; |
||
| 636 | |||
| 637 | // cleanup configuration |
||
| 638 | foreach ($newSysSiteData as $identifier => $value) { |
||
| 639 | if (is_array($value) && empty($value)) { |
||
| 640 | unset($newSysSiteData[$identifier]); |
||
| 641 | } |
||
| 642 | } |
||
| 643 | |||
| 644 | return $newSysSiteData; |
||
| 645 | } |
||
| 646 | |||
| 647 | /** |
||
| 648 | * Delete an existing configuration |
||
| 649 | * |
||
| 650 | * @param ServerRequestInterface $request |
||
| 651 | * @return ResponseInterface |
||
| 652 | */ |
||
| 653 | protected function deleteAction(ServerRequestInterface $request): ResponseInterface |
||
| 654 | { |
||
| 655 | $siteIdentifier = $request->getQueryParams()['site'] ?? ''; |
||
| 656 | if (empty($siteIdentifier)) { |
||
| 657 | throw new \RuntimeException('Not site identifier given', 1521565182); |
||
| 658 | } |
||
| 659 | // Verify site does exist, method throws if not |
||
| 660 | GeneralUtility::makeInstance(SiteConfiguration::class)->delete($siteIdentifier); |
||
| 661 | $overviewRoute = $this->uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']); |
||
| 662 | return new RedirectResponse($overviewRoute); |
||
| 663 | } |
||
| 664 | |||
| 665 | /** |
||
| 666 | * Sets up the Fluid View. |
||
| 667 | * |
||
| 668 | * @param string $templateName |
||
| 669 | */ |
||
| 670 | protected function initializeView(string $templateName): void |
||
| 671 | { |
||
| 672 | $this->view = GeneralUtility::makeInstance(StandaloneView::class); |
||
| 673 | $this->view->setTemplate($templateName); |
||
| 674 | $this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/SiteConfiguration']); |
||
| 675 | $this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']); |
||
| 676 | $this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']); |
||
| 677 | } |
||
| 678 | |||
| 679 | /** |
||
| 680 | * Create document header buttons of "edit" action |
||
| 681 | */ |
||
| 682 | protected function configureEditViewDocHeader(): void |
||
| 683 | { |
||
| 684 | $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); |
||
| 685 | $lang = $this->getLanguageService(); |
||
| 686 | $closeButton = $buttonBar->makeLinkButton() |
||
| 687 | ->setHref('#') |
||
| 688 | ->setClasses('t3js-editform-close') |
||
| 689 | ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc')) |
||
| 690 | ->setShowLabelText(true) |
||
| 691 | ->setIcon($this->iconFactory->getIcon('actions-close', Icon::SIZE_SMALL)); |
||
| 692 | $saveButton = $buttonBar->makeInputButton() |
||
| 693 | ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc')) |
||
| 694 | ->setName('_savedok') |
||
| 695 | ->setValue('1') |
||
| 696 | ->setShowLabelText(true) |
||
| 697 | ->setForm('siteConfigurationController') |
||
| 698 | ->setIcon($this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL)); |
||
| 699 | $buttonBar->addButton($closeButton); |
||
| 700 | $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2); |
||
| 701 | } |
||
| 702 | |||
| 703 | /** |
||
| 704 | * Create document header buttons of "overview" action |
||
| 705 | * |
||
| 706 | * @param string $requestUri |
||
| 707 | */ |
||
| 708 | protected function configureOverViewDocHeader(string $requestUri): void |
||
| 709 | { |
||
| 710 | $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); |
||
| 711 | $reloadButton = $buttonBar->makeLinkButton() |
||
| 712 | ->setHref($requestUri) |
||
| 713 | ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload')) |
||
| 714 | ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)); |
||
| 715 | $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT); |
||
| 716 | $shortcutButton = $buttonBar->makeShortcutButton() |
||
| 717 | ->setRouteIdentifier('site_configuration') |
||
| 718 | ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf:mlang_labels_tablabel')); |
||
| 719 | $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT); |
||
| 720 | } |
||
| 721 | |||
| 722 | /** |
||
| 723 | * Returns a list of pages that have 'is_siteroot' set |
||
| 724 | * |
||
| 725 | * @return array |
||
| 726 | */ |
||
| 727 | protected function getAllSitePages(): array |
||
| 761 | } |
||
| 762 | |||
| 763 | /** |
||
| 764 | * Get all entry duplicates which are used multiple times |
||
| 765 | * |
||
| 766 | * @param Site[] $allSites |
||
| 767 | * @param array $pages |
||
| 768 | * @return array |
||
| 769 | */ |
||
| 770 | protected function getDuplicatedEntryPoints(array $allSites, array $pages): array |
||
| 771 | { |
||
| 772 | $duplicatedEntryPoints = []; |
||
| 773 | |||
| 774 | foreach ($allSites as $identifier => $site) { |
||
| 775 | if (!isset($pages[$site->getRootPageId()])) { |
||
| 776 | continue; |
||
| 777 | } |
||
| 778 | foreach ($site->getAllLanguages() as $language) { |
||
| 779 | $base = $language->getBase(); |
||
| 780 | $entryPoint = rtrim((string)$language->getBase(), '/'); |
||
| 781 | $scheme = $base->getScheme() ? $base->getScheme() . '://' : '//'; |
||
| 782 | $entryPointWithoutScheme = str_replace($scheme, '', $entryPoint); |
||
| 783 | if (!isset($duplicatedEntryPoints[$entryPointWithoutScheme][$entryPoint])) { |
||
| 784 | $duplicatedEntryPoints[$entryPointWithoutScheme][$entryPoint] = 1; |
||
| 785 | } else { |
||
| 786 | $duplicatedEntryPoints[$entryPointWithoutScheme][$entryPoint]++; |
||
| 787 | } |
||
| 788 | } |
||
| 789 | } |
||
| 790 | return array_filter($duplicatedEntryPoints, static function (array $variants): bool { |
||
| 791 | return count($variants) > 1 || reset($variants) > 1; |
||
| 792 | }, ARRAY_FILTER_USE_BOTH); |
||
| 793 | } |
||
| 794 | |||
| 795 | /** |
||
| 796 | * Returns the last (highest) language id from all sites |
||
| 797 | * |
||
| 798 | * @return int |
||
| 799 | */ |
||
| 800 | protected function getLastLanguageId(): int |
||
| 801 | { |
||
| 802 | $lastLanguageId = 0; |
||
| 803 | foreach (GeneralUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) { |
||
| 804 | foreach ($site->getAllLanguages() as $language) { |
||
| 805 | if ($language->getLanguageId() > $lastLanguageId) { |
||
| 806 | $lastLanguageId = $language->getLanguageId(); |
||
| 807 | } |
||
| 808 | } |
||
| 809 | } |
||
| 810 | return $lastLanguageId; |
||
| 811 | } |
||
| 812 | |||
| 813 | /** |
||
| 814 | * @return LanguageService |
||
| 815 | */ |
||
| 816 | protected function getLanguageService(): LanguageService |
||
| 819 | } |
||
| 820 | |||
| 821 | /** |
||
| 822 | * @return BackendUserAuthentication |
||
| 823 | */ |
||
| 824 | protected function getBackendUser(): BackendUserAuthentication |
||
| 827 | } |
||
| 828 | } |
||
| 829 |