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