| Total Complexity | 100 |
| Total Lines | 448 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like TcaItemsProcessorFunctions 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 TcaItemsProcessorFunctions, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 33 | class TcaItemsProcessorFunctions |
||
| 34 | { |
||
| 35 | protected IconFactory $iconFactory; |
||
| 36 | protected IconRegistry $iconRegistry; |
||
| 37 | |||
| 38 | public function __construct() |
||
| 39 | { |
||
| 40 | $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); |
||
| 41 | $this->iconRegistry = GeneralUtility::makeInstance(IconRegistry::class); |
||
| 42 | } |
||
| 43 | |||
| 44 | public function populateAvailableTables(array &$fieldDefinition): void |
||
| 45 | { |
||
| 46 | foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) { |
||
| 47 | if ($tableConfiguration['ctrl']['adminOnly'] ?? false) { |
||
| 48 | // Hide "admin only" tables |
||
| 49 | continue; |
||
| 50 | } |
||
| 51 | $label = ($tableConfiguration['ctrl']['title'] ?? '') ?: ''; |
||
| 52 | $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($tableName, []); |
||
| 53 | $this->getLanguageService()->loadSingleTableDescription($tableName); |
||
| 54 | $helpText = (string)($GLOBALS['TCA_DESCR'][$tableName]['columns']['']['description'] ?? ''); |
||
| 55 | $fieldDefinition['items'][] = [$label, $tableName, $icon, null, $helpText]; |
||
| 56 | } |
||
| 57 | } |
||
| 58 | |||
| 59 | public function populateAvailablePageTypes(array &$fieldDefinition): void |
||
| 60 | { |
||
| 61 | $pageTypes = $GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'] ?? []; |
||
| 62 | if (is_array($pageTypes) && $pageTypes !== []) { |
||
| 63 | foreach ($pageTypes as $pageType) { |
||
| 64 | if (!is_array($pageType) || !isset($pageType[1]) || $pageType[1] === '--div--') { |
||
| 65 | // Skip non arrays and divider items |
||
| 66 | continue; |
||
| 67 | } |
||
| 68 | [$label, $value] = $pageType; |
||
| 69 | $icon = $this->iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $pageType[1]]); |
||
| 70 | $fieldDefinition['items'][] = [$label, $value, $icon]; |
||
| 71 | } |
||
| 72 | } |
||
| 73 | } |
||
| 74 | |||
| 75 | public function populateAvailableGroupModules(array &$fieldDefinition): void |
||
| 76 | { |
||
| 77 | $fieldDefinition['items'] = $this->getAvailableModules('group', $fieldDefinition['items']); |
||
| 78 | } |
||
| 79 | |||
| 80 | public function populateAvailableUserModules(array &$fieldDefinition): void |
||
| 81 | { |
||
| 82 | $fieldDefinition['items'] = $this->getAvailableModules('user', $fieldDefinition['items']); |
||
| 83 | } |
||
| 84 | |||
| 85 | public function populateExcludeFields(array &$fieldDefinition): void |
||
| 86 | { |
||
| 87 | $languageService = $this->getLanguageService(); |
||
| 88 | foreach ($this->getGroupedExcludeFields() as $excludeFieldGroup) { |
||
| 89 | $table = $excludeFieldGroup['table'] ?? ''; |
||
| 90 | $origin = $excludeFieldGroup['origin'] ?? ''; |
||
| 91 | // If the field comes from a FlexForm, the syntax is more complex |
||
| 92 | if ($origin === 'flexForm') { |
||
| 93 | // The field comes from a plugins FlexForm |
||
| 94 | // Add header if not yet set for plugin section |
||
| 95 | $sectionHeader = $excludeFieldGroup['sectionHeader'] ?? ''; |
||
| 96 | if (!isset($fieldDefinition['items'][$sectionHeader])) { |
||
| 97 | // there is no icon handling for plugins - we take the icon from the table |
||
| 98 | $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, []); |
||
| 99 | $fieldDefinition['items'][$sectionHeader] = [$sectionHeader, '--div--', $icon]; |
||
| 100 | } |
||
| 101 | } elseif (!isset($fieldDefinition['items'][$table])) { |
||
| 102 | // Add header if not yet set for table |
||
| 103 | $sectionHeader = $GLOBALS['TCA'][$table]['ctrl']['title'] ?? ''; |
||
| 104 | $icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, []); |
||
| 105 | $fieldDefinition['items'][$table] = [$sectionHeader, '--div--', $icon]; |
||
| 106 | } |
||
| 107 | $fullField = $excludeFieldGroup['fullField'] ?? ''; |
||
| 108 | $fieldName = $excludeFieldGroup['fieldName'] ?? ''; |
||
| 109 | $label = $origin === 'flexForm' |
||
| 110 | ? ($excludeFieldGroup['fieldLabel'] ?? '') |
||
| 111 | : $languageService->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label'] ?? ''); |
||
| 112 | // Add help text |
||
| 113 | $languageService->loadSingleTableDescription($table); |
||
| 114 | $helpText = (string)($GLOBALS['TCA_DESCR'][$table]['columns'][$fullField]['description'] ?? ''); |
||
| 115 | // Item configuration: |
||
| 116 | $fieldDefinition['items'][] = [ |
||
| 117 | rtrim($label, ':') . ' (' . $fieldName . ')', |
||
| 118 | $table . ':' . $fullField, |
||
| 119 | 'empty-empty', |
||
| 120 | null, |
||
| 121 | $helpText |
||
| 122 | ]; |
||
| 123 | } |
||
| 124 | } |
||
| 125 | |||
| 126 | public function populateExplicitAuthValues(array &$fieldDefinition): void |
||
| 127 | { |
||
| 128 | $icons = [ |
||
| 129 | 'ALLOW' => 'status-status-permission-granted', |
||
| 130 | 'DENY' => 'status-status-permission-denied' |
||
| 131 | ]; |
||
| 132 | // Traverse grouped field values: |
||
| 133 | foreach ($this->getGroupedExplicitAuthFieldValues() as $groupKey => $tableFields) { |
||
| 134 | if (empty($tableFields['items']) || !is_array($tableFields['items'])) { |
||
| 135 | continue; |
||
| 136 | } |
||
| 137 | // Add header: |
||
| 138 | $fieldDefinition['items'][] = [ |
||
| 139 | $tableFields['tableFieldLabel'] ?? '', |
||
| 140 | '--div--', |
||
| 141 | ]; |
||
| 142 | // Traverse options for this field: |
||
| 143 | foreach ($tableFields['items'] as $itemValue => $itemContent) { |
||
| 144 | [$allowDenyMode, $itemLabel, $allowDenyModeLabel] = $itemContent; |
||
| 145 | // Add item to be selected: |
||
| 146 | $fieldDefinition['items'][] = [ |
||
| 147 | '[' . $allowDenyModeLabel . '] ' . $itemLabel, |
||
| 148 | $groupKey . ':' . preg_replace('/[:|,]/', '', $itemValue) . ':' . $allowDenyMode, |
||
| 149 | $icons[$allowDenyMode] |
||
| 150 | ]; |
||
| 151 | } |
||
| 152 | } |
||
| 153 | } |
||
| 154 | |||
| 155 | public function populateCustomPermissionOptions(array &$fieldDefinition): void |
||
| 156 | { |
||
| 157 | $customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] ?? []; |
||
| 158 | if (!is_array($customOptions) || $customOptions === []) { |
||
| 159 | return; |
||
| 160 | } |
||
| 161 | $languageService = $this->getLanguageService(); |
||
| 162 | foreach ($customOptions as $customOptionsKey => $customOptionsValue) { |
||
| 163 | if (empty($customOptionsValue['items']) || !is_array($customOptionsValue['items'])) { |
||
| 164 | continue; |
||
| 165 | } |
||
| 166 | // Add header: |
||
| 167 | $fieldDefinition['items'][] = [ |
||
| 168 | $languageService->sL($customOptionsValue['header'] ?? ''), |
||
| 169 | '--div--' |
||
| 170 | ]; |
||
| 171 | // Traverse items: |
||
| 172 | foreach ($customOptionsValue['items'] as $itemKey => $itemConfig) { |
||
| 173 | $icon = 'empty-empty'; |
||
| 174 | $helpText = ''; |
||
| 175 | if (!empty($itemConfig[1]) && $this->iconRegistry->isRegistered($itemConfig[1])) { |
||
| 176 | // Use icon identifier when registered |
||
| 177 | $icon = $itemConfig[1]; |
||
| 178 | } |
||
| 179 | if (!empty($itemConfig[2])) { |
||
| 180 | $helpText = $languageService->sL($itemConfig[2]); |
||
| 181 | } |
||
| 182 | $fieldDefinition['items'][] = [ |
||
| 183 | $languageService->sL($itemConfig[0] ?? ''), |
||
| 184 | $customOptionsKey . ':' . preg_replace('/[:|,]/', '', $itemKey), |
||
| 185 | $icon, |
||
| 186 | null, |
||
| 187 | $helpText |
||
| 188 | ]; |
||
| 189 | } |
||
| 190 | } |
||
| 191 | } |
||
| 192 | |||
| 193 | /** |
||
| 194 | * Get all available modules for the given context: "user" or "group" |
||
| 195 | * |
||
| 196 | * @param string $context |
||
| 197 | * @param array $items |
||
| 198 | * @return array |
||
| 199 | */ |
||
| 200 | protected function getAvailableModules(string $context, array $items): array |
||
| 240 | } |
||
| 241 | |||
| 242 | /** |
||
| 243 | * Returns an array with the exclude fields as defined in TCA and FlexForms |
||
| 244 | * Used for listing the exclude fields in be_groups forms. |
||
| 245 | * |
||
| 246 | * @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA |
||
| 247 | * and FlexForms (fieldName, table:extKey;sheetName;fieldName) |
||
| 248 | */ |
||
| 249 | protected function getGroupedExcludeFields(): array |
||
| 250 | { |
||
| 251 | $languageService = $this->getLanguageService(); |
||
| 252 | $excludeFieldGroups = []; |
||
| 253 | |||
| 254 | // Fetch translations for table names |
||
| 255 | $tableToTranslation = []; |
||
| 256 | // All TCA keys |
||
| 257 | foreach ($GLOBALS['TCA'] as $table => $conf) { |
||
| 258 | $tableToTranslation[$table] = $languageService->sL($conf['ctrl']['title'] ?? ''); |
||
| 259 | } |
||
| 260 | // Sort by translations |
||
| 261 | asort($tableToTranslation); |
||
| 262 | foreach ($tableToTranslation as $table => $translatedTable) { |
||
| 263 | $excludeFieldGroup = []; |
||
| 264 | |||
| 265 | // All field names configured and not restricted to admins |
||
| 266 | if (!empty($GLOBALS['TCA'][$table]['columns']) |
||
| 267 | && is_array($GLOBALS['TCA'][$table]['columns']) |
||
| 268 | && empty($GLOBALS['TCA'][$table]['ctrl']['adminOnly']) |
||
| 269 | && (empty($GLOBALS['TCA'][$table]['ctrl']['rootLevel']) || !empty($GLOBALS['TCA'][$table]['ctrl']['security']['ignoreRootLevelRestriction'])) |
||
| 270 | ) { |
||
| 271 | foreach ($GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldDefinition) { |
||
| 272 | // Only show fields that can be excluded for editors, or are hidden for non-admins |
||
| 273 | if (($fieldDefinition['exclude'] ?? false) && ($fieldDefinition['displayCond'] ?? '') !== 'HIDE_FOR_NON_ADMINS') { |
||
| 274 | // Get human readable names of fields |
||
| 275 | $translatedField = $languageService->sL($fieldDefinition['label'] ?? ''); |
||
| 276 | // Add entry, key 'labels' needed for sorting |
||
| 277 | $excludeFieldGroup[] = [ |
||
| 278 | 'labels' => $translatedTable . ':' . $translatedField, |
||
| 279 | 'sectionHeader' => $translatedTable, |
||
| 280 | 'table' => $table, |
||
| 281 | 'tableField' => $fieldName, |
||
| 282 | 'fieldName' => $fieldName, |
||
| 283 | 'fullField' => $fieldName, |
||
| 284 | 'fieldLabel' => $translatedField, |
||
| 285 | 'origin' => 'tca', |
||
| 286 | ]; |
||
| 287 | } |
||
| 288 | } |
||
| 289 | } |
||
| 290 | // All FlexForm fields |
||
| 291 | $flexFormArray = $this->getRegisteredFlexForms((string)$table); |
||
| 292 | foreach ($flexFormArray as $tableField => $flexForms) { |
||
| 293 | // Prefix for field label, e.g. "Plugin Options:" |
||
| 294 | $labelPrefix = ''; |
||
| 295 | if (!empty($GLOBALS['TCA'][$table]['columns'][$tableField]['label'])) { |
||
| 296 | $labelPrefix = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$tableField]['label']); |
||
| 297 | } |
||
| 298 | // Get all sheets |
||
| 299 | foreach ($flexForms as $extIdent => $extConf) { |
||
| 300 | if (empty($extConf['sheets']) || !is_array($extConf['sheets'])) { |
||
| 301 | continue; |
||
| 302 | } |
||
| 303 | // Get all fields in sheet |
||
| 304 | foreach ($extConf['sheets'] as $sheetName => $sheet) { |
||
| 305 | if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) { |
||
| 306 | continue; |
||
| 307 | } |
||
| 308 | foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) { |
||
| 309 | // Use only fields that have exclude flag set |
||
| 310 | if (empty($field['TCEforms']['exclude'])) { |
||
| 311 | continue; |
||
| 312 | } |
||
| 313 | $fieldLabel = !empty($field['TCEforms']['label']) ? $languageService->sL($field['TCEforms']['label']) : $pluginFieldName; |
||
| 314 | $excludeFieldGroup[] = [ |
||
| 315 | 'labels' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ': ') . ':' . $fieldLabel, |
||
| 316 | 'sectionHeader' => trim($translatedTable . ' ' . $labelPrefix . ' ' . $extIdent, ':'), |
||
| 317 | 'table' => $table, |
||
| 318 | 'tableField' => $tableField, |
||
| 319 | 'extIdent' => $extIdent, |
||
| 320 | 'fieldName' => $pluginFieldName, |
||
| 321 | 'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName, |
||
| 322 | 'fieldLabel' => $fieldLabel, |
||
| 323 | 'origin' => 'flexForm', |
||
| 324 | ]; |
||
| 325 | } |
||
| 326 | } |
||
| 327 | } |
||
| 328 | } |
||
| 329 | // Sort fields by the translated value |
||
| 330 | if (!empty($excludeFieldGroup)) { |
||
| 331 | usort($excludeFieldGroup, static function (array $array1, array $array2) { |
||
| 332 | $array1 = reset($array1); |
||
| 333 | $array2 = reset($array2); |
||
| 334 | if (is_string($array1) && is_string($array2)) { |
||
| 335 | return strcasecmp($array1, $array2); |
||
| 336 | } |
||
| 337 | return 0; |
||
| 338 | }); |
||
| 339 | $excludeFieldGroups = array_merge($excludeFieldGroups, $excludeFieldGroup); |
||
| 340 | } |
||
| 341 | } |
||
| 342 | |||
| 343 | return $excludeFieldGroups; |
||
| 344 | } |
||
| 345 | |||
| 346 | /** |
||
| 347 | * Returns FlexForm data structures it finds. Used in select "special" for be_groups |
||
| 348 | * to set "exclude" flags for single flex form fields. |
||
| 349 | * |
||
| 350 | * This only finds flex forms registered in 'ds' config sections. |
||
| 351 | * This does not resolve other sophisticated flex form data structure references. |
||
| 352 | * |
||
| 353 | * @todo: This approach is limited and doesn't find everything. It works for casual tt_content plugins, though: |
||
| 354 | * @todo: The data structure identifier determination depends on data row, but we don't have all rows at hand here. |
||
| 355 | * @todo: The code thus "guesses" some standard data structure identifier scenarios and tries to resolve those. |
||
| 356 | * @todo: This guessing can not be solved in a good way. A general registry of "all" possible data structures is |
||
| 357 | * @todo: probably not wanted, since that wouldn't work for truly dynamic DS calculations. Probably the only |
||
| 358 | * @todo: thing we could do here is a hook to allow extensions declaring specific data structures to |
||
| 359 | * @todo: allow backend admins to set exclude flags for certain fields in those cases. |
||
| 360 | * |
||
| 361 | * @param string $table Table to handle |
||
| 362 | * @return array Data structures |
||
| 363 | */ |
||
| 364 | protected function getRegisteredFlexForms(string $table): array |
||
| 365 | { |
||
| 366 | if (empty($GLOBALS['TCA'][$table]['columns']) || !is_array($GLOBALS['TCA'][$table]['columns'])) { |
||
| 367 | return []; |
||
| 368 | } |
||
| 369 | $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); |
||
| 370 | $flexForms = []; |
||
| 371 | foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldDefinition) { |
||
| 372 | if (($fieldDefinition['config']['type'] ?? '') !== 'flex' |
||
| 373 | || empty($fieldDefinition['config']['ds']) |
||
| 374 | || !is_array($fieldDefinition['config']['ds']) |
||
| 375 | ) { |
||
| 376 | continue; |
||
| 377 | } |
||
| 378 | $flexForms[$field] = []; |
||
| 379 | foreach (array_keys($fieldDefinition['config']['ds']) as $flexFormKey) { |
||
| 380 | $flexFormKey = (string)$flexFormKey; |
||
| 381 | // Get extension identifier (uses second value if it's not empty, "list" or "*", else first one) |
||
| 382 | $identFields = GeneralUtility::trimExplode(',', $flexFormKey); |
||
| 383 | $extIdent = $identFields[0] ?? ''; |
||
| 384 | if (!empty($identFields[1]) && $identFields[1] !== 'list' && $identFields[1] !== '*') { |
||
| 385 | $extIdent = $identFields[1]; |
||
| 386 | } |
||
| 387 | $flexFormDataStructureIdentifier = json_encode([ |
||
| 388 | 'type' => 'tca', |
||
| 389 | 'tableName' => $table, |
||
| 390 | 'fieldName' => $field, |
||
| 391 | 'dataStructureKey' => $flexFormKey, |
||
| 392 | ]); |
||
| 393 | try { |
||
| 394 | $dataStructure = $flexFormTools->parseDataStructureByIdentifier($flexFormDataStructureIdentifier); |
||
| 395 | $flexForms[$field][$extIdent] = $dataStructure; |
||
| 396 | } catch (InvalidIdentifierException $e) { |
||
| 397 | // Deliberately empty: The DS identifier is guesswork and the flex ds parser throws |
||
| 398 | // this exception if it can not resolve to a valid data structure. This is "ok" here |
||
| 399 | // and the exception is just eaten. |
||
| 400 | } |
||
| 401 | } |
||
| 402 | } |
||
| 403 | return $flexForms; |
||
| 404 | } |
||
| 405 | |||
| 406 | /** |
||
| 407 | * Returns an array with explicit Allow/Deny fields. |
||
| 408 | * Used for listing these field/value pairs in be_groups forms |
||
| 409 | * |
||
| 410 | * @return array Array with information from all of $GLOBALS['TCA'] |
||
| 411 | */ |
||
| 412 | protected function getGroupedExplicitAuthFieldValues(): array |
||
| 413 | { |
||
| 414 | $languageService = $this->getLanguageService(); |
||
| 415 | $allowDenyLabels = [ |
||
| 416 | 'ALLOW' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.allow'), |
||
| 417 | 'DENY' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.deny') |
||
| 418 | ]; |
||
| 419 | $allowDenyOptions = []; |
||
| 420 | foreach ($GLOBALS['TCA'] as $table => $tableConfiguration) { |
||
| 421 | if (empty($tableConfiguration['columns']) || !is_array($tableConfiguration['columns'])) { |
||
| 422 | continue; |
||
| 423 | } |
||
| 424 | // All field names configured: |
||
| 425 | foreach ($tableConfiguration['columns'] as $field => $fieldDefinition) { |
||
| 426 | $fieldConfig = $fieldDefinition['config'] ?? []; |
||
| 427 | if (($fieldConfig['type'] ?? '') !== 'select' || !(bool)($fieldConfig['authMode'] ?? false)) { |
||
| 428 | continue; |
||
| 429 | } |
||
| 430 | // Check for items |
||
| 431 | if (empty($fieldConfig['items']) || !is_array($fieldConfig['items'])) { |
||
| 432 | continue; |
||
| 433 | } |
||
| 434 | // Get Human Readable names of fields and table: |
||
| 435 | $allowDenyOptions[$table . ':' . $field]['tableFieldLabel'] = |
||
| 436 | $languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'] ?? '') . ': ' |
||
| 437 | . $languageService->sL($GLOBALS['TCA'][$table]['columns'][$field]['label'] ?? ''); |
||
| 438 | |||
| 439 | foreach ($fieldConfig['items'] as $item) { |
||
| 440 | $itemIdentifier = (string)($item[1] ?? ''); |
||
| 441 | // Values '' and '--div--' are not controlled by this setting. |
||
| 442 | if ($itemIdentifier === '' || $itemIdentifier === '--div--') { |
||
| 443 | continue; |
||
| 444 | } |
||
| 445 | // Find allowDenyMode |
||
| 446 | $allowDenyMode = ''; |
||
| 447 | switch ((string)$fieldConfig['authMode']) { |
||
| 448 | case 'explicitAllow': |
||
| 449 | $allowDenyMode = 'ALLOW'; |
||
| 450 | break; |
||
| 451 | case 'explicitDeny': |
||
| 452 | $allowDenyMode = 'DENY'; |
||
| 453 | break; |
||
| 454 | case 'individual': |
||
| 455 | if ($item[4] ?? false) { |
||
| 456 | if ($item[4] === 'EXPL_ALLOW') { |
||
| 457 | $allowDenyMode = 'ALLOW'; |
||
| 458 | } elseif ($item[4] === 'EXPL_DENY') { |
||
| 459 | $allowDenyMode = 'DENY'; |
||
| 460 | } |
||
| 461 | } |
||
| 462 | break; |
||
| 463 | } |
||
| 464 | // Set allowDenyMode |
||
| 465 | if ($allowDenyMode) { |
||
| 466 | $allowDenyOptions[$table . ':' . $field]['items'][$itemIdentifier] = [ |
||
| 467 | $allowDenyMode, |
||
| 468 | $languageService->sL($item[0] ?? ''), |
||
| 469 | $allowDenyLabels[$allowDenyMode] |
||
| 470 | ]; |
||
| 471 | } |
||
| 472 | } |
||
| 473 | } |
||
| 474 | } |
||
| 475 | return $allowDenyOptions; |
||
| 476 | } |
||
| 477 | |||
| 478 | protected function getLanguageService(): LanguageService |
||
| 481 | } |
||
| 482 | } |
||
| 483 |