$fieldConfig) { if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') { continue; } // Make sure we are only processing supported renderTypes if (!$this->isTargetRenderType($fieldConfig)) { continue; } $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'] ?? [], $table, $fieldName); $fieldConfig['config']['maxitems'] = MathUtility::forceIntegerInRange($fieldConfig['config']['maxitems'] ?? 0, 0, 99999); if ($fieldConfig['config']['maxitems'] === 0) { $fieldConfig['config']['maxitems'] = 99999; } $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']); // Resolve "itemsProcFunc" if (!empty($fieldConfig['config']['itemsProcFunc']) || !empty($fieldConfig['config']['itemsProcessors'])) { $fieldConfig['config']['items'] = $this->resolveItemsProcessorFunction($result, $fieldName, $fieldConfig['config']['items']); // itemsProcFunc must not be used anymore unset( $fieldConfig['config']['itemsProcFunc'], $fieldConfig['config']['itemsProcessors'] ); } // removing items before $dynamicItems and $removedItems have been built results in having them // not populated to the dynamic database row and displayed as "invalid value" in the forms view $fieldConfig['config']['items'] = $this->removeItemsByUserStorageRestriction($result, $fieldName, $fieldConfig['config']['items']); $removedItems = $fieldConfig['config']['items']; $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']); $removedItems = array_diff_key($removedItems, $fieldConfig['config']['items']); $currentDatabaseValuesArray = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName); // Check if it's a new record to respect TCAdefaults if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') { // Getting the current database value on a mm relation doesn't make sense since the amount of selected // relations is stored in the field and not the uids of the items $currentDatabaseValuesArray = []; } $result['databaseRow'][$fieldName] = $currentDatabaseValuesArray; // add item values as keys to determine which items are stored in the database and should be preselected $itemArrayValues = array_column( array_map(fn(SelectItem|array $item): array => $item instanceof SelectItem ? $item->toArray() : $item, $fieldConfig['config']['items']), 'value' ); $itemArray = array_fill_keys( $itemArrayValues, $fieldConfig['config']['items'] ); $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $itemArray); $fieldConfig['config']['items'] = $this->addInvalidItemsFromDatabase( $result, $table, $fieldName, $fieldConfig, $currentDatabaseValuesArray, $removedItems ); // Translate labels and add icons // skip file of sys_file_metadata which is not rendered anyway but can use all memory if (!($table === 'sys_file_metadata' && $fieldName === 'file')) { $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName); $fieldConfig['config']['items'] = $this->addIconFromAltIcons($result, $fieldConfig['config']['items'], $table, $fieldName); } $unresolvedValue = $result['databaseRow'][$fieldName][0] ?? null; if ($table === 'site' && $this->envPlaceholderProcessor->canProcess($unresolvedValue ?? '')) { $resolvedValue = $this->envPlaceholderProcessor->process($unresolvedValue); $itemByResolvedPlaceholder = array_find( $fieldConfig['config']['items'], fn(array $v) => (string)$v['value'] === $resolvedValue ); if ($itemByResolvedPlaceholder === null) { throw new \RuntimeException( sprintf('Invalid placeholder value "%s" for "%s"', $resolvedValue, $unresolvedValue), 1764310149 ); } $fieldConfig['config']['items'][0] = [ ...$itemByResolvedPlaceholder, 'label' => $itemByResolvedPlaceholder['label'], 'value' => $unresolvedValue, ]; } // Keys may contain table names, so a numeric array is created $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']); $fieldConfig['config']['items'] = $this->selectItemProcessor->groupAndSortItems( $fieldConfig['config']['items'], $fieldConfig['config']['itemGroups'] ?? [], $fieldConfig['config']['sortItems'] ?? [] ); $result['processedTca']['columns'][$fieldName] = $fieldConfig; } return $result; } /** * Add values that are currently listed in the database columns but not in the selectable items list * back to the list. * * @param array $result The current result array. * @param string $table The current table name * @param string $fieldName The current field name * @param array $fieldConf The configuration of the current field. * @param array $databaseValues The item values from the database, can contain invalid items! * @param array $removedItems Items removed by access checks and restrictions, must not be added as invalid values * @return array */ public function addInvalidItemsFromDatabase(array $result, $table, $fieldName, array $fieldConf, array $databaseValues, array $removedItems) { // Early return if there are no items or invalid values should not be displayed if (empty($fieldConf['config']['items']) || ($fieldConf['config']['renderType'] ?? '') !== 'selectSingle' || ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['disableNoMatchingValueElement'] ?? false) || ($fieldConf['config']['disableNoMatchingValueElement'] ?? false) ) { return $fieldConf['config']['items']; } $languageService = $this->getLanguageService(); $noMatchingLabel = isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label']) ? $languageService->sL(trim($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label'])) : '[ ' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingLabel') . ' ]'; $unmatchedValues = array_diff( array_values($databaseValues), array_column( array_map( fn(SelectItem|array $item): array => $item instanceof SelectItem ? $item->toArray() : $item, $fieldConf['config']['items'] ), 'value' ), array_column( array_map( fn(SelectItem|array $item): array => $item instanceof SelectItem ? $item->toArray() : $item, $removedItems ), 'value' ) ); foreach ($unmatchedValues as $unmatchedValue) { $invalidItem = [ 'label' => @sprintf($noMatchingLabel, $unmatchedValue), 'value' => $unmatchedValue, 'icon' => null, 'group' => 'none', // put it in the very first position in the "none" group ]; array_unshift($fieldConf['config']['items'], $invalidItem); } return $fieldConf['config']['items']; } /** * Determines whether the current field is a valid target for this DataProvider * * @return bool */ protected function isTargetRenderType(array $fieldConfig) { return ($fieldConfig['config']['renderType'] ?? '') !== 'selectTree'; } }