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