[TASK] Simplify icon handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Migrations / TcaMigration.php
1 <?php
2 namespace TYPO3\CMS\Core\Migrations;
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\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\StringUtility;
19
20 /**
21 * Migrate TCA from old to new syntax. Used in bootstrap and Flex Form Data Structures.
22 *
23 * @internal Class and API may change any time.
24 */
25 class TcaMigration {
26
27 /**
28 * Accumulate migration messages
29 *
30 * @var array
31 */
32 protected $messages = array();
33
34 /**
35 * Migrate old TCA to new TCA.
36 *
37 * See unit tests for details.
38 *
39 * @param array $tca
40 * @return array
41 */
42 public function migrate(array $tca) {
43 $tca = $this->migrateT3editorWizardToRenderTypeT3editorIfNotEnabledByTypeConfig($tca);
44 $tca = $this->migrateSpecialConfigurationAndRemoveShowItemStylePointerConfig($tca);
45 $tca = $this->migrateT3editorWizardWithEnabledByTypeConfigToColumnsOverrides($tca);
46 $tca = $this->migrateShowItemAdditionalPaletteToOwnPalette($tca);
47 $tca = $this->migrateIconsForFormFieldWizardsToNewLocation($tca);
48 $tca = $this->migrateExtAndSysextPathToEXTPath($tca);
49 // @todo: if showitem/defaultExtras wizards[xy] is migrated to columnsOverrides here, enableByTypeConfig could be dropped
50 return $tca;
51 }
52
53 /**
54 * Get messages of migrated fields. Can be used for deprecation messages after migrate() was called.
55 *
56 * @return array Migration messages
57 */
58 public function getMessages() {
59 return $this->messages;
60 }
61
62 /**
63 * Migrate type=text field with t3editor wizard to renderType=t3editor without this wizard
64 *
65 * @param array $tca Incoming TCA
66 * @return array Migrated TCA
67 */
68 protected function migrateT3editorWizardToRenderTypeT3editorIfNotEnabledByTypeConfig(array $tca) {
69 $newTca = $tca;
70 foreach ($tca as $table => $tableDefinition) {
71 if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
72 continue;
73 }
74 foreach ($tableDefinition['columns'] as $fieldName => $fieldConfig) {
75 if (
76 !empty($fieldConfig['config']['type']) // type is set
77 && trim($fieldConfig['config']['type']) === 'text' // to "text"
78 && isset($fieldConfig['config']['wizards'])
79 && is_array($fieldConfig['config']['wizards']) // and there are wizards
80 ) {
81 foreach ($fieldConfig['config']['wizards'] as $wizardName => $wizardConfig) {
82 if (
83 !empty($wizardConfig['userFunc']) // a userFunc is defined
84 && trim($wizardConfig['userFunc']) === 'TYPO3\\CMS\\T3editor\\FormWizard->main' // and set to FormWizard
85 && (
86 !isset($wizardConfig['enableByTypeConfig']) // and enableByTypeConfig is not set
87 || (isset($wizardConfig['enableByTypeConfig']) && !$wizardConfig['enableByTypeConfig']) // or set, but not enabled
88 )
89 ) {
90 // Set renderType from text to t3editor
91 $newTca[$table]['columns'][$fieldName]['config']['renderType'] = 't3editor';
92 // Unset this wizard definition
93 unset($newTca[$table]['columns'][$fieldName]['config']['wizards'][$wizardName]);
94 // Move format parameter
95 if (!empty($wizardConfig['params']['format'])) {
96 $newTca[$table]['columns'][$fieldName]['config']['format'] = $wizardConfig['params']['format'];
97 }
98 $this->messages[] = 'Migrated t3editor wizard in TCA of table "' . $table . '" field "' . $fieldName . '" to a renderType definition.';
99 }
100 }
101 // If no wizard is left after migration, unset the whole sub array
102 if (empty($newTca[$table]['columns'][$fieldName]['config']['wizards'])) {
103 unset($newTca[$table]['columns'][$fieldName]['config']['wizards']);
104 }
105 }
106 }
107 }
108 return $newTca;
109 }
110
111 /**
112 * Remove "style pointer", the 5th parameter from "types" "showitem" configuration.
113 * Move "specConf", 4th parameter from "tyes" "showitem" to "types" "columnsOverrides.
114 *
115 * @param array $tca Incoming TCA
116 * @return array Modified TCA
117 */
118 protected function migrateSpecialConfigurationAndRemoveShowItemStylePointerConfig(array $tca) {
119 $newTca = $tca;
120 foreach ($tca as $table => $tableDefinition) {
121 if (!isset($tableDefinition['types']) || !is_array($tableDefinition['types'])) {
122 continue;
123 }
124 foreach ($tableDefinition['types'] as $typeName => $typeArray) {
125 if (!is_string($typeArray['showitem']) || strpos($typeArray['showitem'], ';') === FALSE) {
126 // Continue directly if no semicolon is found
127 continue;
128 }
129 $itemList = GeneralUtility::trimExplode(',', $typeArray['showitem'], TRUE);
130 $newFieldStrings = array();
131 foreach ($itemList as $fieldString) {
132 $fieldString = rtrim($fieldString, ';');
133 // Unpack the field definition, migrate and remove as much as possible
134 // Keep empty parameters in trimExplode here (third parameter FALSE), so position is not changed
135 $fieldArray = GeneralUtility::trimExplode(';', $fieldString);
136 $fieldArray = array(
137 'fieldName' => isset($fieldArray[0]) ? $fieldArray[0] : '',
138 'fieldLabel' => isset($fieldArray[1]) ? $fieldArray[1] : NULL,
139 'paletteName' => isset($fieldArray[2]) ? $fieldArray[2] : NULL,
140 'fieldExtra' => isset($fieldArray[3]) ? $fieldArray[3] : NULL,
141 );
142 $fieldName = $fieldArray['fieldName'];
143 if (!empty($fieldArray['fieldExtra'])) {
144 // Move fieldExtra "specConf" to columnsOverrides "defaultExtras"
145 if (!isset($newTca[$table]['types'][$typeName]['columnsOverrides'])) {
146 $newTca[$table]['types'][$typeName]['columnsOverrides'] = array();
147 }
148 if (!isset($newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldArray['fieldName']])) {
149 $newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldArray['fieldName']] = array();
150 }
151 // Merge with given defaultExtras from columns.
152 // They will be the first part of the string, so if "specConf" from types changes the same settings,
153 // those will override settings from defaultExtras of columns
154 $newDefaultExtras = array();
155 if (!empty($tca[$table]['columns'][$fieldArray['fieldName']]['defaultExtras'])) {
156 $newDefaultExtras[] = $tca[$table]['columns'][$fieldArray['fieldName']]['defaultExtras'];
157 }
158 $newDefaultExtras[] = $fieldArray['fieldExtra'];
159 $newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldArray['fieldName']]['defaultExtras'] = implode(':', $newDefaultExtras);
160 }
161 unset($fieldArray['fieldExtra']);
162 if (count($fieldArray) === 3 && empty($fieldArray['paletteName'])) {
163 unset($fieldArray['paletteName']);
164 }
165 if (count($fieldArray) === 2 && empty($fieldArray['fieldLabel'])) {
166 unset($fieldArray['fieldLabel']);
167 }
168 if (count($fieldArray) === 1 && empty($fieldArray['fieldName'])) {
169 // The field may vanish if nothing is left
170 unset($fieldArray['fieldName']);
171 }
172 $newFieldString = implode(';', $fieldArray);
173 if ($newFieldString !== $fieldString) {
174 $this->messages[] = 'Changed showitem string of TCA table "' . $table . '" type "' . $typeName . '" due to changed field "' . $fieldName . '".';
175 }
176 if (!empty($newFieldString)) {
177 $newFieldStrings[] = $newFieldString;
178 }
179 }
180 $newTca[$table]['types'][$typeName]['showitem'] = implode(',', $newFieldStrings);
181 }
182 }
183 return $newTca;
184 }
185
186 /**
187 * Migrate type=text field with t3editor wizard that is "enableByTypeConfig" to columnsOverrides
188 * with renderType=t3editor
189 *
190 * @param array $tca Incoming TCA
191 * @return array Migrated TCA
192 */
193 protected function migrateT3editorWizardWithEnabledByTypeConfigToColumnsOverrides(array $tca) {
194 $newTca = $tca;
195 foreach ($tca as $table => $tableDefinition) {
196 if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
197 continue;
198 }
199 foreach ($tableDefinition['columns'] as $fieldName => $fieldConfig) {
200 if (
201 !empty($fieldConfig['config']['type']) // type is set
202 && trim($fieldConfig['config']['type']) === 'text' // to "text"
203 && isset($fieldConfig['config']['wizards'])
204 && is_array($fieldConfig['config']['wizards']) // and there are wizards
205 ) {
206 foreach ($fieldConfig['config']['wizards'] as $wizardName => $wizardConfig) {
207 if (
208 !empty($wizardConfig['userFunc']) // a userFunc is defined
209 && trim($wizardConfig['userFunc']) === 'TYPO3\CMS\T3editor\FormWizard->main' // and set to FormWizard
210 && !empty($wizardConfig['enableByTypeConfig']) // and enableByTypeConfig is enabled
211 ) {
212 // Remove this wizard
213 unset($newTca[$table]['columns'][$fieldName]['config']['wizards'][$wizardName]);
214 // Find configured types that use this wizard
215 if (!isset($tableDefinition['types']) || !is_array($tableDefinition['types'])) {
216 // No type definition at all ... continue directly
217 continue;
218 }
219 foreach ($tableDefinition['types'] as $typeName => $typeArray) {
220 if (
221 empty($typeArray['columnsOverrides'][$fieldName]['defaultExtras'])
222 || strpos($typeArray['columnsOverrides'][$fieldName]['defaultExtras'], $wizardName) === FALSE
223 ) {
224 // Continue directly if this wizard is not enabled for given type
225 continue;
226 }
227 $defaultExtras = $typeArray['columnsOverrides'][$fieldName]['defaultExtras'];
228 $defaultExtrasArray = GeneralUtility::trimExplode(':', $defaultExtras, TRUE);
229 $newDefaultExtrasArray = array();
230 foreach ($defaultExtrasArray as $fieldExtraField) {
231 // There might be multiple enabled wizards separated by | ... split them
232 if (substr($fieldExtraField, 0, 8) === 'wizards[') {
233 $enabledWizards = substr($fieldExtraField, 8, strlen($fieldExtraField) - 8); // Cut off "wizards[
234 $enabledWizards = substr($enabledWizards, 0, strlen($enabledWizards) - 1);
235 $enabledWizardsArray = GeneralUtility::trimExplode('|', $enabledWizards, TRUE);
236 $newEnabledWizardsArray = array();
237 foreach ($enabledWizardsArray as $enabledWizardName) {
238 if ($enabledWizardName === $wizardName) {
239 // Found a columnsOverrides configuration that has this wizard enabled
240 // Force renderType = t3editor
241 $newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldName]['config']['renderType'] = 't3editor';
242 // Transfer format option if given
243 if (!empty($wizardConfig['params']['format'])) {
244 $newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldName]['config']['format'] = $wizardConfig['params']['format'];
245 }
246 $this->messages[] = 'Migrated t3editor wizard in TCA of table "' . $table . '" field "' . $fieldName
247 . '" to a renderType definition with columnsOverrides in type "' . $typeName . '".';
248 } else {
249 // Some other enabled wizard
250 $newEnabledWizardsArray[] = $enabledWizardName;
251 }
252 }
253 if (!empty($newEnabledWizardsArray)) {
254 $newDefaultExtrasArray[] = 'wizards[' . implode('|', $newEnabledWizardsArray) . ']';
255 }
256 } else {
257 $newDefaultExtrasArray[] = $fieldExtraField;
258 }
259 }
260 if (!empty($newDefaultExtrasArray)) {
261 $newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldName]['defaultExtras'] = implode(':', $newDefaultExtrasArray);
262 } else {
263 unset($newTca[$table]['types'][$typeName]['columnsOverrides'][$fieldName]['defaultExtras']);
264 }
265 }
266 }
267 }
268 // If no wizard is left after migration, unset the whole sub array
269 if (empty($newTca[$table]['columns'][$fieldName]['config']['wizards'])) {
270 unset($newTca[$table]['columns'][$fieldName]['config']['wizards']);
271 }
272 }
273 }
274 }
275 return $newTca;
276 }
277
278 /**
279 * Migrate types showitem 'aField;aLabel;aPalette' to 'afield;aLabel, --palette--;;aPalette'
280 *
281 * Old showitem can have a syntax like:
282 * fieldName;aLabel;aPalette
283 * This way, the palette with name "aPalette" is rendered after fieldName.
284 * The migration parses this to a syntax like:
285 * fieldName;aLabel, --palette--;;paletteName
286 *
287 * @param array $tca Incoming TCA
288 * @return array Migrated TCA
289 */
290 protected function migrateShowItemAdditionalPaletteToOwnPalette(array $tca) {
291 $newTca = $tca;
292 foreach ($tca as $table => $tableDefinition) {
293 if (!isset($tableDefinition['types']) || !is_array($tableDefinition['types'])) {
294 continue;
295 }
296 foreach ($tableDefinition['types'] as $typeName => $typeArray) {
297 if (
298 !isset($typeArray['showitem'])
299 || !is_string($typeArray['showitem'])
300 || strpos($typeArray['showitem'], ';') === FALSE // no field parameters
301 ) {
302 continue;
303 }
304 $itemList = GeneralUtility::trimExplode(',', $typeArray['showitem'], TRUE);
305 $newFieldStrings = array();
306 foreach ($itemList as $fieldString) {
307 $fieldArray = GeneralUtility::trimExplode(';', $fieldString);
308 $fieldArray = array(
309 'fieldName' => isset($fieldArray[0]) ? $fieldArray[0] : '',
310 'fieldLabel' => isset($fieldArray[1]) ? $fieldArray[1] : NULL,
311 'paletteName' => isset($fieldArray[2]) ? $fieldArray[2] : NULL,
312 );
313 if ($fieldArray['fieldName'] !== '--palette--' && $fieldArray['paletteName'] !== NULL) {
314 if ($fieldArray['fieldLabel']) {
315 $fieldString = $fieldArray['fieldName'] . ';' . $fieldArray['fieldLabel'];
316 } else {
317 $fieldString = $fieldArray['fieldName'];
318 }
319 $paletteString = '--palette--;;' . $fieldArray['paletteName'];
320 $this->messages[] = 'Migrated TCA table "' . $table . '" showitem field of type "' . $typeName . '": Moved additional palette'
321 . ' with name "' . $fieldArray['paletteName'] . '" as 3rd argument of field "' . $fieldArray['fieldName']
322 . '" to an own palette. The result of this part is: "' . $fieldString . ', ' . $paletteString . '"';
323 $newFieldStrings[] = $fieldString;
324 $newFieldStrings[] = $paletteString;
325 } else {
326 $newFieldStrings[] = $fieldString;
327 }
328 }
329 $newTca[$table]['types'][$typeName]['showitem'] = implode(',', $newFieldStrings);
330 }
331 }
332 return $newTca;
333 }
334
335 /**
336 * Migrate core icons for form field wizard to new location
337 *
338 * add.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_add.gif
339 * link_popup.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif
340 * wizard_rte2.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_rte.gif
341 * wizard_table.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_table.gif
342 * edit2.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_edit.gif
343 * list.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_list.gif
344 * wizard_forms.gif => EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_forms.gif
345 *
346 * @param array $tca Incoming TCA
347 * @return array Migrated TCA
348 */
349 protected function migrateIconsForFormFieldWizardsToNewLocation($tca) {
350 $newTca = $tca;
351
352 $oldFileNames = array(
353 'add.gif',
354 'link_popup.gif',
355 'wizard_rte2.gif',
356 'wizard_table.gif',
357 'edit2.gif',
358 'list.gif',
359 'wizard_forms.gif',
360 );
361
362 $newFileLocations = array(
363 'add.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_add.gif',
364 'link_popup.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif',
365 'wizard_rte2.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_rte.gif',
366 'wizard_table.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_table.gif',
367 'edit2.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_edit.gif',
368 'list.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_list.gif',
369 'wizard_forms.gif' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_forms.gif',
370 );
371
372 foreach ($tca as $table => $tableDefinition) {
373 if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
374 continue;
375 }
376 foreach ($tableDefinition['columns'] as $fieldName => $fieldConfig) {
377 if (
378 isset($fieldConfig['config']['wizards'])
379 && is_array($fieldConfig['config']['wizards']) // and there are wizards
380 ) {
381 foreach ($fieldConfig['config']['wizards'] as $wizardName => $wizardConfig) {
382 if (!is_array($wizardConfig)) {
383 continue;
384 }
385
386 foreach ($wizardConfig as $option => $value) {
387 if ($option === 'icon' && in_array($value, $oldFileNames, TRUE)) {
388 $newTca[$table]['columns'][$fieldName]['config']['wizards'][$wizardName]['icon'] = $newFileLocations[$value];
389 $this->messages[] = 'Migrated icon path of wizard "' . $wizardName . '" in field "' . $fieldName . '" from TCA table "' . $table . '". New path is: ' . $newFileLocations[$value];
390 }
391 }
392 }
393 }
394 }
395 }
396
397 return $newTca;
398 }
399
400 /**
401 * Migrate file reference which starts with ext/ or sysext/ to EXT:
402 *
403 * @param array $tca Incoming TCA
404 * @return array Migrated TCA
405 */
406 protected function migrateExtAndSysextPathToEXTPath($tca) {
407 foreach ($tca as $table => &$tableDefinition) {
408 if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
409 continue;
410 }
411 foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
412 if (
413 !empty($fieldConfig['config']['type']) // type is set
414 && trim($fieldConfig['config']['type']) === 'select' // to "select"
415 && isset($fieldConfig['config']['items'])
416 && is_array($fieldConfig['config']['items']) // and there are items
417 ) {
418 foreach ($fieldConfig['config']['items'] as &$itemConfig) {
419 // more then two values? then the third entry is the image path
420 if (count($itemConfig) > 2) {
421 // If the path starts with ext/ or sysext/ migrate it
422 if (
423 StringUtility::beginsWith($itemConfig[2], 'ext/')
424 || StringUtility::beginsWith($itemConfig[2], 'sysext/')
425 ) {
426 $tcaPath = implode('.', [$table, 'columns', $fieldName, 'config', 'items']);
427 $pathParts = GeneralUtility::trimExplode('/', $itemConfig[2]);
428 // remove first element (ext or sysext)
429 array_shift($pathParts);
430 $path = implode('/', $pathParts);
431 $this->messages[] = '[' . $tcaPath . '] ext/ or sysext/ within the path (' . $path . ') in items array is deprecated, use EXT: reference';
432 $itemConfig[2] = 'EXT:' . $path;
433 } elseif (StringUtility::beginsWith($itemConfig[2], 'i/')) {
434 $this->messages[] = '[' . $tcaPath . '] i/ within the path (' . $path . ') in items array is deprecated, use EXT: reference';
435 $itemConfig[2] = 'EXT:t3skin/icons/gfx/' . $itemConfig[2];
436 }
437 }
438 }
439 }
440 }
441 }
442 return $tca;
443 }
444 }