| Total Complexity | 43 |
| Total Lines | 297 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like TcaSelectItems 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 TcaSelectItems, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 25 | class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInterface |
||
| 26 | { |
||
| 27 | /** |
||
| 28 | * Resolve select items |
||
| 29 | * |
||
| 30 | * @param array $result |
||
| 31 | * @return array |
||
| 32 | * @throws \UnexpectedValueException |
||
| 33 | */ |
||
| 34 | public function addData(array $result) |
||
| 35 | { |
||
| 36 | $table = $result['tableName']; |
||
| 37 | |||
| 38 | foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) { |
||
| 39 | if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') { |
||
| 40 | continue; |
||
| 41 | } |
||
| 42 | |||
| 43 | // Make sure we are only processing supported renderTypes |
||
| 44 | if (!$this->isTargetRenderType($fieldConfig)) { |
||
| 45 | continue; |
||
| 46 | } |
||
| 47 | |||
| 48 | $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'] ?? [], $table, $fieldName); |
||
| 49 | |||
| 50 | $fieldConfig['config']['maxitems'] = MathUtility::forceIntegerInRange($fieldConfig['config']['maxitems'] ?? 0, 0, 99999); |
||
| 51 | if ($fieldConfig['config']['maxitems'] === 0) { |
||
| 52 | $fieldConfig['config']['maxitems'] = 99999; |
||
| 53 | } |
||
| 54 | |||
| 55 | $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']); |
||
| 56 | $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']); |
||
| 57 | |||
| 58 | $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']); |
||
| 59 | |||
| 60 | // Resolve "itemsProcFunc" |
||
| 61 | if (!empty($fieldConfig['config']['itemsProcFunc'])) { |
||
| 62 | $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']); |
||
| 63 | // itemsProcFunc must not be used anymore |
||
| 64 | unset($fieldConfig['config']['itemsProcFunc']); |
||
| 65 | } |
||
| 66 | |||
| 67 | // removing items before $dynamicItems and $removedItems have been built results in having them |
||
| 68 | // not populated to the dynamic database row and displayed as "invalid value" in the forms view |
||
| 69 | $fieldConfig['config']['items'] = $this->removeItemsByUserStorageRestriction($result, $fieldName, $fieldConfig['config']['items']); |
||
| 70 | |||
| 71 | $removedItems = $fieldConfig['config']['items']; |
||
| 72 | |||
| 73 | $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); |
||
| 74 | $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); |
||
| 75 | $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); |
||
| 76 | |||
| 77 | $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']); |
||
| 78 | $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']); |
||
| 79 | $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']); |
||
| 80 | |||
| 81 | $removedItems = array_diff_key($removedItems, $fieldConfig['config']['items']); |
||
| 82 | |||
| 83 | $currentDatabaseValuesArray = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName); |
||
| 84 | // Check if it's a new record to respect TCAdefaults |
||
| 85 | if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') { |
||
| 86 | // Getting the current database value on a mm relation doesn't make sense since the amount of selected |
||
| 87 | // relations is stored in the field and not the uids of the items |
||
| 88 | $currentDatabaseValuesArray = []; |
||
| 89 | } |
||
| 90 | |||
| 91 | $result['databaseRow'][$fieldName] = $currentDatabaseValuesArray; |
||
| 92 | |||
| 93 | // add item values as keys to determine which items are stored in the database and should be preselected |
||
| 94 | $itemArrayValues = array_column($fieldConfig['config']['items'], 1); |
||
| 95 | $itemArray = array_fill_keys( |
||
| 96 | $itemArrayValues, |
||
| 97 | $fieldConfig['config']['items'] |
||
| 98 | ); |
||
| 99 | $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $itemArray); |
||
| 100 | |||
| 101 | $fieldConfig['config']['items'] = $this->addInvalidItemsFromDatabase( |
||
| 102 | $result, |
||
| 103 | $table, |
||
| 104 | $fieldName, |
||
| 105 | $fieldConfig, |
||
| 106 | $currentDatabaseValuesArray, |
||
| 107 | $removedItems |
||
| 108 | ); |
||
| 109 | |||
| 110 | // Translate labels and add icons |
||
| 111 | // skip file of sys_file_metadata which is not rendered anyway but can use all memory |
||
| 112 | if (!($table === 'sys_file_metadata' && $fieldName === 'file')) { |
||
| 113 | $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName); |
||
| 114 | $fieldConfig['config']['items'] = $this->addIconFromAltIcons($result, $fieldConfig['config']['items'], $table, $fieldName); |
||
| 115 | } |
||
| 116 | |||
| 117 | // Keys may contain table names, so a numeric array is created |
||
| 118 | $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']); |
||
| 119 | |||
| 120 | $fieldConfig['config']['items'] = $this->groupAndSortItems( |
||
| 121 | $fieldConfig['config']['items'], |
||
| 122 | $fieldConfig['config']['itemGroups'] ?? [], |
||
| 123 | $fieldConfig['config']['sortItems'] ?? [] |
||
| 124 | ); |
||
| 125 | |||
| 126 | $result['processedTca']['columns'][$fieldName] = $fieldConfig; |
||
| 127 | } |
||
| 128 | |||
| 129 | return $result; |
||
| 130 | } |
||
| 131 | |||
| 132 | /** |
||
| 133 | * Add values that are currently listed in the database columns but not in the selectable items list |
||
| 134 | * back to the list. |
||
| 135 | * |
||
| 136 | * @param array $result The current result array. |
||
| 137 | * @param string $table The current table name |
||
| 138 | * @param string $fieldName The current field name |
||
| 139 | * @param array $fieldConf The configuration of the current field. |
||
| 140 | * @param array $databaseValues The item values from the database, can contain invalid items! |
||
| 141 | * @param array $removedItems Items removed by access checks and restrictions, must not be added as invalid values |
||
| 142 | * @return array |
||
| 143 | */ |
||
| 144 | public function addInvalidItemsFromDatabase(array $result, $table, $fieldName, array $fieldConf, array $databaseValues, array $removedItems) |
||
| 145 | { |
||
| 146 | // Early return if there are no items or invalid values should not be displayed |
||
| 147 | if (empty($fieldConf['config']['items']) |
||
| 148 | || $fieldConf['config']['renderType'] !== 'selectSingle' |
||
| 149 | || ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['disableNoMatchingValueElement'] ?? false) |
||
| 150 | || ($fieldConf['config']['disableNoMatchingValueElement'] ?? false) |
||
| 151 | ) { |
||
| 152 | return $fieldConf['config']['items']; |
||
| 153 | } |
||
| 154 | |||
| 155 | $languageService = $this->getLanguageService(); |
||
| 156 | $noMatchingLabel = isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label']) |
||
| 157 | ? $languageService->sL(trim($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label'])) |
||
| 158 | : '[ ' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue') . ' ]'; |
||
| 159 | |||
| 160 | $unmatchedValues = array_diff( |
||
| 161 | array_values($databaseValues), |
||
| 162 | array_column($fieldConf['config']['items'], 1), |
||
| 163 | array_column($removedItems, 1) |
||
| 164 | ); |
||
| 165 | |||
| 166 | foreach ($unmatchedValues as $unmatchedValue) { |
||
| 167 | $invalidItem = [ |
||
| 168 | @sprintf($noMatchingLabel, $unmatchedValue), |
||
| 169 | $unmatchedValue, |
||
| 170 | null, |
||
| 171 | 'none' // put it in the very first position in the "none" group |
||
| 172 | ]; |
||
| 173 | array_unshift($fieldConf['config']['items'], $invalidItem); |
||
| 174 | } |
||
| 175 | |||
| 176 | return $fieldConf['config']['items']; |
||
| 177 | } |
||
| 178 | |||
| 179 | /** |
||
| 180 | * Determines whether the current field is a valid target for this DataProvider |
||
| 181 | * |
||
| 182 | * @param array $fieldConfig |
||
| 183 | * @return bool |
||
| 184 | */ |
||
| 185 | protected function isTargetRenderType(array $fieldConfig) |
||
| 186 | { |
||
| 187 | return $fieldConfig['config']['renderType'] !== 'selectTree'; |
||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * Is used when --div-- elements in the item list are used, or if groups are defined via "groupItems" config array. |
||
| 192 | * |
||
| 193 | * This method takes the --div-- elements out of the list, and adds them to the group lists. |
||
| 194 | * |
||
| 195 | * A main "none" group is added, which is always on top, when items are not set to be in a group. |
||
| 196 | * All items without a groupId - which is defined by the fourth key of an item in the item array - are added |
||
| 197 | * to the "none" group, or to the last group used previously, to ensure ordering as much as possible as before. |
||
| 198 | * |
||
| 199 | * Then the found groups are iterated over the order in the [itemGroups] list, |
||
| 200 | * and items within a group can be sorted via "sortOrders" configuration. |
||
| 201 | * |
||
| 202 | * All grouped items are then "flattened" out and --div-- items are added for each group to keep backwards-compatibility. |
||
| 203 | * |
||
| 204 | * @param array $allItems all resolved items including the ones from foreign_table values. The group ID information can be found in fourth key [3] of an item. |
||
| 205 | * @param array $definedGroups [config][itemGroups] |
||
| 206 | * @param array $sortOrders [config][sortOrders] |
||
| 207 | * @return array |
||
| 208 | */ |
||
| 209 | protected function groupAndSortItems(array $allItems, array $definedGroups, array $sortOrders): array |
||
| 278 | } |
||
| 279 | |||
| 280 | /** |
||
| 281 | * Sort given items by label or value or a custom user function built like |
||
| 282 | * "MyVendor\MyExtension\TcaSorter->sortItems" or a callable. |
||
| 283 | * |
||
| 284 | * @param array $items |
||
| 285 | * @param array $sortOrders should be something like like [label => desc] |
||
| 286 | * @return array the sorted items |
||
| 287 | */ |
||
| 288 | protected function sortItems(array $items, array $sortOrders): array |
||
| 324 |