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