1<?php 2namespace TYPO3\CMS\Backend\Form\FormDataProvider; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 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. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17use TYPO3\CMS\Backend\Form\FormDataProviderInterface; 18use TYPO3\CMS\Core\Utility\MathUtility; 19 20/** 21 * Resolve select items, set processed item list in processedTca, sanitize and resolve database field 22 */ 23class TcaSelectItems extends AbstractItemProvider implements FormDataProviderInterface 24{ 25 /** 26 * Resolve select items 27 * 28 * @param array $result 29 * @return array 30 * @throws \UnexpectedValueException 31 */ 32 public function addData(array $result) 33 { 34 $table = $result['tableName']; 35 36 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) { 37 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'select') { 38 continue; 39 } 40 41 // Make sure we are only processing supported renderTypes 42 if (!$this->isTargetRenderType($fieldConfig)) { 43 continue; 44 } 45 46 $fieldConfig['config']['items'] = $this->sanitizeItemArray($fieldConfig['config']['items'] ?? [], $table, $fieldName); 47 48 $fieldConfig['config']['maxitems'] = MathUtility::forceIntegerInRange($fieldConfig['config']['maxitems'] ?? 0, 0, 99999); 49 if ($fieldConfig['config']['maxitems'] === 0) { 50 $fieldConfig['config']['maxitems'] = 99999; 51 } 52 53 $fieldConfig['config']['items'] = $this->addItemsFromSpecial($result, $fieldName, $fieldConfig['config']['items']); 54 $fieldConfig['config']['items'] = $this->addItemsFromFolder($result, $fieldName, $fieldConfig['config']['items']); 55 56 $fieldConfig['config']['items'] = $this->addItemsFromForeignTable($result, $fieldName, $fieldConfig['config']['items']); 57 58 // Resolve "itemsProcFunc" 59 if (!empty($fieldConfig['config']['itemsProcFunc'])) { 60 $fieldConfig['config']['items'] = $this->resolveItemProcessorFunction($result, $fieldName, $fieldConfig['config']['items']); 61 // itemsProcFunc must not be used anymore 62 unset($fieldConfig['config']['itemsProcFunc']); 63 } 64 65 // removing items before $dynamicItems and $removedItems have been built results in having them 66 // not populated to the dynamic database row and displayed as "invalid value" in the forms view 67 $fieldConfig['config']['items'] = $this->removeItemsByUserStorageRestriction($result, $fieldName, $fieldConfig['config']['items']); 68 69 $removedItems = $fieldConfig['config']['items']; 70 71 $fieldConfig['config']['items'] = $this->removeItemsByKeepItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); 72 $fieldConfig['config']['items'] = $this->addItemsFromPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); 73 $fieldConfig['config']['items'] = $this->removeItemsByRemoveItemsPageTsConfig($result, $fieldName, $fieldConfig['config']['items']); 74 75 $fieldConfig['config']['items'] = $this->removeItemsByUserLanguageFieldRestriction($result, $fieldName, $fieldConfig['config']['items']); 76 $fieldConfig['config']['items'] = $this->removeItemsByUserAuthMode($result, $fieldName, $fieldConfig['config']['items']); 77 $fieldConfig['config']['items'] = $this->removeItemsByDoktypeUserRestriction($result, $fieldName, $fieldConfig['config']['items']); 78 79 $removedItems = array_diff_key($removedItems, $fieldConfig['config']['items']); 80 81 $currentDatabaseValuesArray = $this->processDatabaseFieldValue($result['databaseRow'], $fieldName); 82 // Check if it's a new record to respect TCAdefaults 83 if (!empty($fieldConfig['config']['MM']) && $result['command'] !== 'new') { 84 // Getting the current database value on a mm relation doesn't make sense since the amount of selected 85 // relations is stored in the field and not the uids of the items 86 $currentDatabaseValuesArray = []; 87 } 88 89 $result['databaseRow'][$fieldName] = $currentDatabaseValuesArray; 90 91 // add item values as keys to determine which items are stored in the database and should be preselected 92 $itemArrayValues = array_column($fieldConfig['config']['items'], 1); 93 $itemArray = array_fill_keys( 94 $itemArrayValues, 95 $fieldConfig['config']['items'] 96 ); 97 $result['databaseRow'][$fieldName] = $this->processSelectFieldValue($result, $fieldName, $itemArray); 98 99 $fieldConfig['config']['items'] = $this->addInvalidItemsFromDatabase( 100 $result, 101 $table, 102 $fieldName, 103 $fieldConfig, 104 $currentDatabaseValuesArray, 105 $removedItems 106 ); 107 108 // Translate labels 109 // skip file of sys_file_metadata which is not rendered anyway but can use all memory 110 if (!($table === 'sys_file_metadata' && $fieldName === 'file')) { 111 $fieldConfig['config']['items'] = $this->translateLabels($result, $fieldConfig['config']['items'], $table, $fieldName); 112 } 113 114 // Keys may contain table names, so a numeric array is created 115 $fieldConfig['config']['items'] = array_values($fieldConfig['config']['items']); 116 117 $result['processedTca']['columns'][$fieldName] = $fieldConfig; 118 } 119 120 return $result; 121 } 122 123 /** 124 * Add values that are currently listed in the database columns but not in the selectable items list 125 * back to the list. 126 * 127 * @param array $result The current result array. 128 * @param string $table The current table name 129 * @param string $fieldName The current field name 130 * @param array $fieldConf The configuration of the current field. 131 * @param array $databaseValues The item values from the database, can contain invalid items! 132 * @param array $removedItems Items removed by access checks and restrictions, must not be added as invalid values 133 * @return array 134 */ 135 public function addInvalidItemsFromDatabase(array $result, $table, $fieldName, array $fieldConf, array $databaseValues, array $removedItems) 136 { 137 // Early return if there are no items or invalid values should not be displayed 138 if (empty($fieldConf['config']['items']) 139 || $fieldConf['config']['renderType'] !== 'selectSingle' 140 || ($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['disableNoMatchingValueElement'] ?? false) 141 || ($fieldConf['config']['disableNoMatchingValueElement'] ?? false) 142 ) { 143 return $fieldConf['config']['items']; 144 } 145 146 $languageService = $this->getLanguageService(); 147 $noMatchingLabel = isset($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label']) 148 ? $languageService->sL(trim($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']['noMatchingValue_label'])) 149 : '[ ' . $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noMatchingValue') . ' ]'; 150 151 $unmatchedValues = array_diff( 152 array_values($databaseValues), 153 array_column($fieldConf['config']['items'], 1), 154 array_column($removedItems, 1) 155 ); 156 157 foreach ($unmatchedValues as $unmatchedValue) { 158 $invalidItem = [ 159 @sprintf($noMatchingLabel, $unmatchedValue), 160 $unmatchedValue 161 ]; 162 array_unshift($fieldConf['config']['items'], $invalidItem); 163 } 164 165 return $fieldConf['config']['items']; 166 } 167 168 /** 169 * Determines whether the current field is a valid target for this DataProvider 170 * 171 * @param array $fieldConfig 172 * @return bool 173 */ 174 protected function isTargetRenderType(array $fieldConfig) 175 { 176 return $fieldConfig['config']['renderType'] !== 'selectTree'; 177 } 178} 179