‪TYPO3CMS  10.4
TcaSelectItems.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
21 
26 {
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 
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 
185  protected function ‪isTargetRenderType(array $fieldConfig)
186  {
187  return $fieldConfig['config']['renderType'] !== 'selectTree';
188  }
189 
209  protected function ‪groupAndSortItems(array $allItems, array $definedGroups, array $sortOrders): array
210  {
211  $groupedItems = [];
212  // Append defined groups at first, as their order is prioritized
213  $itemGroups = ['none' => ''];
214  foreach ($definedGroups as $groupId => $groupLabel) {
215  $itemGroups[$groupId] = $this->‪getLanguageService()->‪sL($groupLabel);
216  }
217  $currentGroup = 'none';
218  // Extract --div-- into itemGroups
219  foreach ($allItems as $key => $item) {
220  if ($item[1] === '--div--') {
221  // A divider is added as a group (existing groups will get their label overridden)
222  if (isset($item[3])) {
223  $currentGroup = $item[3];
224  $itemGroups[$currentGroup] = $item[0];
225  } else {
226  $currentGroup = 'none';
227  }
228  continue;
229  }
230  // Put the given item in the currentGroup if no group has been given already
231  if (!isset($item[3])) {
232  $item[3] = $currentGroup;
233  }
234  $groupIdOfItem = !empty($item[3]) ? $item[3] : 'none';
235  // It is still possible to have items that have an "unassigned" group, so they are moved to the "none" group
236  if (!isset($itemGroups[$groupIdOfItem])) {
237  $itemGroups[$groupIdOfItem] = '';
238  }
239 
240  // Put the item in its corresponding group (and create it if it does not exist yet)
241  if (!is_array($groupedItems[$groupIdOfItem] ?? null)) {
242  $groupedItems[$groupIdOfItem] = [];
243  }
244  $groupedItems[$groupIdOfItem][] = $item;
245  }
246  // Only "none" = no grouping used explicitly via "itemGroups" or via "--div--"
247  if (count($itemGroups) === 1) {
248  if (!empty($sortOrders)) {
249  $allItems = $this->‪sortItems($allItems, $sortOrders);
250  }
251  return $allItems;
252  }
253 
254  // $groupedItems contains all items per group
255  // $itemGroups contains all groups in order of each group
256 
257  // Let's add the --div-- items again ("unpacking")
258  // And use the group ordering given by the itemGroups
259  $finalItems = [];
260  foreach ($itemGroups as $groupId => $groupLabel) {
261  $itemsInGroup = $groupedItems[$groupId] ?? [];
262  if (empty($itemsInGroup)) {
263  continue;
264  }
265  // If sorting is defined, sort within each group now
266  if (!empty($sortOrders)) {
267  $itemsInGroup = $this->‪sortItems($itemsInGroup, $sortOrders);
268  }
269  // Add the --div-- if it is not the "none" default item
270  if ($groupId !== 'none') {
271  // Fall back to the groupId, if there is no label for it
272  $groupLabel = $groupLabel ?: $groupId;
273  $finalItems[] = [$groupLabel, '--div--', null, $groupId, null];
274  }
275  $finalItems = array_merge($finalItems, $itemsInGroup);
276  }
277  return $finalItems;
278  }
279 
288  protected function ‪sortItems(array $items, array $sortOrders): array
289  {
290  foreach ($sortOrders as $order => $direction) {
291  switch ($order) {
292  case 'label':
293  $direction = strtolower($direction);
294  @usort(
295  $items,
296  function ($item1, $item2) use ($direction) {
297  if ($direction === 'desc') {
298  return strcasecmp($item1[0], $item2[0]) <= 0;
299  }
300  return strcasecmp($item1[0], $item2[0]);
301  }
302  );
303  break;
304  case 'value':
305  $direction = strtolower($direction);
306  @usort(
307  $items,
308  function ($item1, $item2) use ($direction) {
309  if ($direction === 'desc') {
310  return strcasecmp($item1[1], $item2[1]) <= 0;
311  }
312  return strcasecmp($item1[1], $item2[1]);
313  }
314  );
315  break;
316  default:
317  $reference = null;
318  GeneralUtility::callUserFunction($direction, $items, $reference);
319  }
320  }
321  return $items;
322  }
323 }
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\sanitizeItemArray
‪array sanitizeItemArray($itemArray, $tableName, $fieldName)
Definition: AbstractItemProvider.php:1424
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromPageTsConfig
‪array addItemsFromPageTsConfig(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:140
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems\addData
‪array addData(array $result)
Definition: TcaSelectItems.php:34
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByUserLanguageFieldRestriction
‪array removeItemsByUserLanguageFieldRestriction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:652
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\getLanguageService
‪LanguageService getLanguageService()
Definition: AbstractItemProvider.php:1468
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addIconFromAltIcons
‪array addIconFromAltIcons(array $result, array $items, string $table, string $fieldName)
Definition: AbstractItemProvider.php:1400
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\translateLabels
‪array translateLabels(array $result, array $itemArray, $table, $fieldName)
Definition: AbstractItemProvider.php:1355
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromSpecial
‪array addItemsFromSpecial(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:177
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:194
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromForeignTable
‪array addItemsFromForeignTable(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:479
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems\isTargetRenderType
‪bool isTargetRenderType(array $fieldConfig)
Definition: TcaSelectItems.php:185
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByUserStorageRestriction
‪array removeItemsByUserStorageRestriction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:742
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems\groupAndSortItems
‪array groupAndSortItems(array $allItems, array $definedGroups, array $sortOrders)
Definition: TcaSelectItems.php:209
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\resolveItemProcessorFunction
‪array resolveItemProcessorFunction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:59
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByDoktypeUserRestriction
‪array removeItemsByDoktypeUserRestriction(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:712
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems\sortItems
‪array sortItems(array $items, array $sortOrders)
Definition: TcaSelectItems.php:288
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems
Definition: TcaSelectItems.php:26
‪TYPO3\CMS\Backend\Form\FormDataProvider
Definition: AbstractDatabaseRecordProvider.php:16
‪TYPO3\CMS\Backend\Form\FormDataProviderInterface
Definition: FormDataProviderInterface.php:23
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByKeepItemsPageTsConfig
‪array removeItemsByKeepItemsPageTsConfig(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:585
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\processDatabaseFieldValue
‪array processDatabaseFieldValue(array $row, $fieldName)
Definition: AbstractItemProvider.php:1267
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\processSelectFieldValue
‪array processSelectFieldValue(array $result, $fieldName, array $staticValues)
Definition: AbstractItemProvider.php:1289
‪TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems\addInvalidItemsFromDatabase
‪array addInvalidItemsFromDatabase(array $result, $table, $fieldName, array $fieldConf, array $databaseValues, array $removedItems)
Definition: TcaSelectItems.php:144
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByUserAuthMode
‪array removeItemsByUserAuthMode(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:681
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\addItemsFromFolder
‪array addItemsFromFolder(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:422
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider
Definition: AbstractItemProvider.php:50
‪TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider\removeItemsByRemoveItemsPageTsConfig
‪array removeItemsByRemoveItemsPageTsConfig(array $result, $fieldName, array $items)
Definition: AbstractItemProvider.php:618