2 namespace TYPO3\CMS\Backend\Form\FormDataProvider
;
5 * This file is part of the TYPO3 CMS project.
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Backend\Form\FormDataProviderInterface
;
18 use TYPO3\CMS\Core\Utility\MathUtility
;
21 * Resolve select items, set processed item list in processedTca, sanitize and resolve database field
23 class TcaSelectItems
extends AbstractItemProvider
implements FormDataProviderInterface
26 * Resolve select items
28 * @param array $result
30 * @throws \UnexpectedValueException
32 public function addData(array $result)
34 $table = $result['tableName'];
36 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
37 if (empty($fieldConfig['config']['type']) ||
$fieldConfig['config']['type'] !== 'select') {
41 // Make sure we are only processing supported renderTypes
42 if (!$this->isTargetRenderType($fieldConfig)) {
46 // Make sure we only process useful dataFields
47 if ($this->isSkippableInlineField($result, $fieldName) ||
$this->isSkippableLanguageField($result, $fieldName)) {
51 $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'] ??
[], $table, $fieldName);
53 // Resolve "itemsProcFunc"
54 if (!empty($fieldConfig['config']['itemsProcFunc'])) {
55 $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']);
56 // itemsProcFunc must not be used anymore
57 unset($fieldConfig['config']['itemsProcFunc']);
60 $fieldConfig['config']['maxitems'] = MathUtility
::forceIntegerInRange($fieldConfig['config']['maxitems'] ??
0, 0, 99999);
61 if ($fieldConfig['config']['maxitems'] === 0) {
62 $fieldConfig['config']['maxitems'] = 99999;
65 $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']);
66 $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']);
67 $staticItems = $fieldConfig['config']['items'];
69 $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']);
70 // removing items before $dynamicItems and $removedItems have been built results in having them
71 // not populated to the dynamic database row and displayed as "invalid value" in the forms view
72 $fieldConfig['config']['items'] = $this->removeItemsByUserStorageRestriction($result, $fieldName, $fieldConfig['config']['items']);
74 $dynamicItems = array_diff_key($fieldConfig['config']['items'], $staticItems);
75 $removedItems = $fieldConfig['config']['items'];
77 $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
78 $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
79 $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']);
81 $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']);
82 $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']);
83 $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']);
85 $removedItems = array_diff_key($removedItems, $fieldConfig['config']['items']);
87 // needed to determine the items for invalid values
88 $currentDatabaseValuesArray = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName);
89 $result['databaseRow'][$fieldName] = $currentDatabaseValuesArray;
91 $staticValues = $this->getStaticValues($fieldConfig['config']['items'], $dynamicItems);
92 $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $staticValues);
94 $fieldConfig['config']['items'] = $this->addInvalidItemsFromDatabase(
99 $currentDatabaseValuesArray,
104 $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName);
106 // Keys may contain table names, so a numeric array is created
107 $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']);
109 $result['processedTca']['columns'][$fieldName] = $fieldConfig;
116 * Checks if the field is an inlineChild and not not exposed
118 * @param array $result The current result array
119 * @param string $fieldName Current handle field name
122 protected function isSkippableInlineField(array $result, string $fieldName): bool
125 if (empty($result['isInlineChild'])) {
128 // skip data loading for inline children if they are not visible
129 if (empty($result['isInlineChildExpanded']) && empty($result['isInlineAjaxOpeningContext'])) {
133 // skip data loading for inline children if there are the parent relation fields
134 $inlineParentConfig = $result['inlineParentConfig'];
135 if ($inlineParentConfig && isset($inlineParentConfig['foreign_field']) && $inlineParentConfig['foreign_field'] === $fieldName) {
143 * If the current form shows only the default language record, processing of language parent field can be skipped
145 * @param array $result The current result array
146 * @param string $fieldName Field name being processed
149 protected function isSkippableLanguageField(array $result, string $fieldName): bool
151 // look for a translation for this record although we're on the default language (0)
152 $transOrigPointerFieldName = $result['processedTca']['ctrl']['transOrigPointerField'] ??
'';
153 if (empty($transOrigPointerFieldName) ||
$fieldName !== $transOrigPointerFieldName) {
157 // get TCA language field name
158 $languageField = $result['processedTca']['ctrl']['languageField'];
159 $languageUids = $result['databaseRow'][$languageField];
161 // languageField can be an array or a scalar value (try to normalize it)
162 if (!is_array($languageUids)) {
163 $languageUids = [(int)$result['databaseRow'][$languageField]];
166 // only default language available?
167 return count($languageUids) === 1 && (int)$languageUids[0] === 0;
171 * Add values that are currently listed in the database columns but not in the selectable items list
174 * @param array $result The current result array.
175 * @param string $table The current table name
176 * @param string $fieldName The current field name
177 * @param array $fieldConf The configuration of the current field.
178 * @param array $databaseValues The item values from the database, can contain invalid items!
179 * @param array $removedItems Items removed by access checks and restrictions, must not be added as invalid values
182 public function addInvalidItemsFromDatabase(array $result, $table, $fieldName, array $fieldConf, array $databaseValues, array $removedItems)
184 // Early return if there are no items or invalid values should not be displayed
185 if (empty($fieldConf['config']['items'])
186 ||
$fieldConf['config']['renderType'] !== 'selectSingle'
187 ||
($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['disableNoMatchingValueElement'] ??
false)
188 ||
($fieldConf['config']['disableNoMatchingValueElement'] ??
false)
190 return $fieldConf['config']['items'];
193 $languageService = $this->getLanguageService();
194 $noMatchingLabel = isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label'])
195 ?
$languageService->sL(trim($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label']))
196 : '[ ' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue') . ' ]';
198 $unmatchedValues = array_diff(
199 array_values($databaseValues),
200 array_column($fieldConf['config']['items'], 1),
201 array_column($removedItems, 1)
204 foreach ($unmatchedValues as $unmatchedValue) {
206 @sprintf
($noMatchingLabel, $unmatchedValue),
209 array_unshift($fieldConf['config']['items'], $invalidItem);
212 return $fieldConf['config']['items'];
216 * Determines whether the current field is a valid target for this DataProvider
218 * @param array $fieldConfig
221 protected function isTargetRenderType(array $fieldConfig)
223 return $fieldConfig['config']['renderType'] !== 'selectTree';