[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaInlineConfiguration.php
1 <?php
2 namespace 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
17 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19 use TYPO3\CMS\Core\Utility\MathUtility;
20
21 /**
22 * Set or initialize configuration for inline fields in TCA
23 */
24 class TcaInlineConfiguration implements FormDataProviderInterface
25 {
26 /**
27 * Find all inline fields and force proper configuration
28 *
29 * @param array $result
30 * @return array
31 * @throws \UnexpectedValueException If inline configuration is broken
32 */
33 public function addData(array $result)
34 {
35 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
36 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'inline') {
37 continue;
38 }
39
40 // Throw if an inline field without foreign_table is set
41 if (!isset($fieldConfig['config']['foreign_table'])) {
42 throw new \UnexpectedValueException(
43 'Inline field ' . $fieldName . ' of table ' . $result['tableName'] . ' must have a foreign_table config',
44 1443793404
45 );
46 }
47
48 $result = $this->initializeMinMaxItems($result, $fieldName);
49 $result = $this->initializeLocalizationMode($result, $fieldName);
50 $result = $this->initializeAppearance($result, $fieldName);
51 $result = $this->addInlineSelectorAndUniqueConfiguration($result, $fieldName);
52 }
53 return $result;
54 }
55
56 /**
57 * Set and validate minitems and maxitems in config
58 *
59 * @param array $result Result array
60 * @param string $fieldName Current handle field name
61 * @return array Modified item array
62 * @return array
63 */
64 protected function initializeMinMaxItems(array $result, $fieldName)
65 {
66 $config = $result['processedTca']['columns'][$fieldName]['config'];
67
68 $minItems = 0;
69 if (isset($config['minitems'])) {
70 $minItems = MathUtility::forceIntegerInRange($config['minitems'], 0);
71 }
72 $result['processedTca']['columns'][$fieldName]['config']['minitems'] = $minItems;
73
74 $maxItems = 99999;
75 if (isset($config['maxitems'])) {
76 $maxItems = MathUtility::forceIntegerInRange($config['maxitems'], 1);
77 }
78 $result['processedTca']['columns'][$fieldName]['config']['maxitems'] = $maxItems;
79
80 return $result;
81 }
82
83 /**
84 * Set appearance configuration
85 *
86 * @param array $result Result array
87 * @param string $fieldName Current handle field name
88 * @return array Modified item array
89 * @return array
90 */
91 protected function initializeAppearance(array $result, $fieldName)
92 {
93 $config = $result['processedTca']['columns'][$fieldName]['config'];
94 if (!isset($config['appearance']) || !is_array($config['appearance'])) {
95 // Init appearance if not set
96 $config['appearance'] = [];
97 }
98 // Set the position/appearance of the "Create new record" link
99 if (isset($config['foreign_selector']) && $config['foreign_selector']
100 && (!isset($config['appearance']['useCombination']) || !$config['appearance']['useCombination'])
101 ) {
102 $config['appearance']['levelLinksPosition'] = 'none';
103 } elseif (!isset($config['appearance']['levelLinksPosition'])
104 || !in_array($config['appearance']['levelLinksPosition'], ['top', 'bottom', 'both', 'none'], true)
105 ) {
106 $config['appearance']['levelLinksPosition'] = 'top';
107 }
108 $config['appearance']['showPossibleLocalizationRecords']
109 = isset($config['appearance']['showPossibleLocalizationRecords']) && $config['appearance']['showPossibleLocalizationRecords'];
110 $config['appearance']['showRemovedLocalizationRecords']
111 = isset($config['appearance']['showRemovedLocalizationRecords']) && $config['appearance']['showRemovedLocalizationRecords'];
112 // Defines which controls should be shown in header of each record
113 $enabledControls = [
114 'info' => true,
115 'new' => true,
116 'dragdrop' => true,
117 'sort' => true,
118 'hide' => true,
119 'delete' => true,
120 'localize' => true
121 ];
122 if (isset($config['appearance']['enabledControls']) && is_array($config['appearance']['enabledControls'])) {
123 $config['appearance']['enabledControls'] = array_merge($enabledControls, $config['appearance']['enabledControls']);
124 } else {
125 $config['appearance']['enabledControls'] = $enabledControls;
126 }
127 $result['processedTca']['columns'][$fieldName]['config'] = $config;
128
129 return $result;
130 }
131
132 /**
133 * Set localization mode. This will end up with localizationMode to be set to either 'select', 'keep'
134 * or 'none' if the handled record is a localized record.
135 *
136 * @see TcaInline for a detailed explanation on the meaning of these modes.
137 *
138 * @param array $result Result array
139 * @param string $fieldName Current handle field name
140 * @return array Modified item array
141 * @throws \UnexpectedValueException If localizationMode configuration is broken
142 */
143 protected function initializeLocalizationMode(array $result, $fieldName)
144 {
145 if ($result['defaultLanguageRow'] === null) {
146 // Currently handled parent is a localized row if a former provider added the "default" row
147 // If handled record is not localized, set localizationMode to 'none' and return
148 $result['processedTca']['columns'][$fieldName]['config']['behaviour']['localizationMode'] = 'none';
149 return $result;
150 }
151
152 $childTableName = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
153 $parentConfig = $result['processedTca']['columns'][$fieldName]['config'];
154
155 $isChildTableLocalizable = false;
156 if (isset($GLOBALS['TCA'][$childTableName]['ctrl']) && is_array($GLOBALS['TCA'][$childTableName]['ctrl'])
157 && isset($GLOBALS['TCA'][$childTableName]['ctrl']['languageField'])
158 && $GLOBALS['TCA'][$childTableName]['ctrl']['languageField']
159 && isset($GLOBALS['TCA'][$childTableName]['ctrl']['transOrigPointerField'])
160 && $GLOBALS['TCA'][$childTableName]['ctrl']['transOrigPointerField']
161 ) {
162 $isChildTableLocalizable = true;
163 }
164
165 $mode = null;
166
167 if (isset($parentConfig['behaviour']['localizationMode'])) {
168 // Use explicit set mode, but validate before use
169 // Use mode if set, but throw if not set to either 'select' or 'keep'
170 if ($parentConfig['behaviour']['localizationMode'] !== 'keep' && $parentConfig['behaviour']['localizationMode'] !== 'select') {
171 throw new \UnexpectedValueException(
172 'localizationMode of table ' . $result['tableName'] . ' field ' . $fieldName . ' is not valid, set to either \'keep\' or \'select\'',
173 1443829370
174 );
175 }
176 // Throw if is set to select, but child can not be localized
177 if ($parentConfig['behaviour']['localizationMode'] === 'select' && !$isChildTableLocalizable) {
178 throw new \UnexpectedValueException(
179 'Wrong configuration: localizationMode of table ' . $result['tableName'] . ' field ' . $fieldName . ' is set to \'select\', but table is not localizable.',
180 1443944274
181 );
182 }
183 $mode = $parentConfig['behaviour']['localizationMode'];
184 } else {
185 // Not set explicitly -> use "none"
186 $mode = 'none';
187 if ($isChildTableLocalizable) {
188 // Except if child is localizable, then use "select"
189 $mode = 'select';
190 }
191 }
192
193 $result['processedTca']['columns'][$fieldName]['config']['behaviour']['localizationMode'] = $mode;
194 return $result;
195 }
196
197 /**
198 * If foreign_selector or foreign_unique is set, this points to a field configuration of the child
199 * table. The InlineControlContainer may render a drop down field or an element browser later from this.
200 *
201 * Fetch configuration from child table configuration, sanitize and merge with
202 * foreign_selector_fieldTcaOverride that allows overriding this field definition again.
203 *
204 * Final configuration is written to selectorOrUniqueConfiguration of inline config section.
205 *
206 * @param array $result Result array
207 * @param string $fieldName Current handle field name
208 * @return array Modified item array
209 * @throws \UnexpectedValueException If configuration is broken
210 */
211 protected function addInlineSelectorAndUniqueConfiguration(array $result, $fieldName)
212 {
213 $config = $result['processedTca']['columns'][$fieldName]['config'];
214
215 // Early return if neither foreign_unique nor foreign_selector are set
216 if (!isset($config['foreign_unique']) && !isset($config['foreign_selector'])) {
217 return $result;
218 }
219
220 // If both are set, they must point to the same field
221 if (isset($config['foreign_unique']) && isset($config['foreign_selector'])
222 && $config['foreign_unique'] !== $config['foreign_selector']
223 ) {
224 throw new \UnexpectedValueException(
225 'Table ' . $result['tableName'] . ' field ' . $fieldName . ': If both foreign_unique and'
226 . ' foreign_selector are set, they must point to the same field',
227 1444995464
228 );
229 }
230
231 if (isset($config['foreign_unique'])) {
232 $fieldNameInChildConfiguration = $config['foreign_unique'];
233 } else {
234 $fieldNameInChildConfiguration = $config['foreign_selector'];
235 }
236
237 // Throw if field name in globals does not exist or is not of type select or group
238 if (!isset($GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config']['type'])
239 || ($GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config']['type'] !== 'select'
240 && $GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config']['type'] !== 'group')
241 ) {
242 throw new \UnexpectedValueException(
243 'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
244 . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . ', but this field'
245 . ' is either not defined or is not of type select or group',
246 1444996537
247 );
248 }
249
250 $selectorOrUniqueConfiguration = [
251 'config' => $GLOBALS['TCA'][$config['foreign_table']]['columns'][$fieldNameInChildConfiguration]['config'],
252 ];
253
254 // Throw if field is type group, but not internal_type db
255 if ($selectorOrUniqueConfiguration['config']['type'] === 'group'
256 && (!isset($selectorOrUniqueConfiguration['config']['internal_type']) || $selectorOrUniqueConfiguration['config']['internal_type'] !== 'db')) {
257 throw new \UnexpectedValueException(
258 'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
259 . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . '. This field'
260 . ' is of type group and must be of internal_type db, which is not the case',
261 1444999130
262 );
263 }
264
265 // Merge foreign_selector_fieldTcaOverride if given
266 if (isset($config['foreign_selector'])
267 && isset($config['foreign_selector_fieldTcaOverride']['config'])
268 && is_array($config['foreign_selector_fieldTcaOverride']['config'])
269 ) {
270 ArrayUtility::mergeRecursiveWithOverrule($selectorOrUniqueConfiguration['config'], $config['foreign_selector_fieldTcaOverride']['config']);
271 }
272
273 // Add field name to config for easy access later
274 $selectorOrUniqueConfiguration['fieldName'] = $fieldNameInChildConfiguration;
275
276 // Add remote table name for easy access later
277 if ($selectorOrUniqueConfiguration['config']['type'] === 'select') {
278 if (!isset($selectorOrUniqueConfiguration['config']['foreign_table'])) {
279 throw new \UnexpectedValueException(
280 'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
281 . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . '. This field'
282 . ' is of type select and must define foreign_table',
283 1445078627
284 );
285 }
286 $foreignTable = $selectorOrUniqueConfiguration['config']['foreign_table'];
287 } else {
288 if (!isset($selectorOrUniqueConfiguration['config']['allowed'])) {
289 throw new \UnexpectedValueException(
290 'Table ' . $result['tableName'] . ' field ' . $fieldName . ' points in foreign_selector or foreign_unique'
291 . ' to field ' . $fieldNameInChildConfiguration . ' of table ' . $config['foreign_table'] . '. This field'
292 . ' is of type select and must define allowed',
293 1445078628
294 );
295 }
296 $foreignTable = $selectorOrUniqueConfiguration['config']['allowed'];
297 }
298 $selectorOrUniqueConfiguration['foreignTable'] = $foreignTable;
299
300 // If this is a foreign_selector field, mark it as such for data fetching later
301 $selectorOrUniqueConfiguration['isSelector'] = false;
302 if (isset($config['foreign_selector'])) {
303 $selectorOrUniqueConfiguration['isSelector'] = true;
304 }
305
306 // If this is a foreign_unique field, mark it a such for unique data fetching later
307 $selectorOrUniqueConfiguration['isUnique'] = false;
308 if (isset($config['foreign_unique'])) {
309 $selectorOrUniqueConfiguration['isUnique'] = true;
310 }
311
312 // Add field configuration to inline configuration
313 $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'] = $selectorOrUniqueConfiguration;
314
315 return $result;
316 }
317 }