Completed
Push — master ( 64542d...27c7de )
by
unknown
12:47
created

TcaMigration::getMessages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Migrations;
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 = [];
33
34
    /**
35
     * Run some general TCA validations, then migrate old TCA to new TCA.
36
     *
37
     * This class is typically called within bootstrap with empty caches after all TCA
38
     * files from extensions have been loaded. The migration is then applied and
39
     * the migrated result is cached.
40
     * For flex form TCA, this class is called dynamically if opening a record in the backend.
41
     *
42
     * See unit tests for details.
43
     *
44
     * @param array $tca
45
     * @return array
46
     */
47
    public function migrate(array $tca): array
48
    {
49
        $this->validateTcaType($tca);
50
51
        $tca = $this->migrateColumnsConfig($tca);
52
        $tca = $this->migrateLocalizeChildrenAtParentLocalization($tca);
53
        $tca = $this->migratePagesLanguageOverlayRemoval($tca);
54
        $tca = $this->removeSelIconFieldPath($tca);
55
        $tca = $this->removeSetToDefaultOnCopy($tca);
56
        $tca = $this->sanitizeControlSectionIntegrity($tca);
57
        $tca = $this->removeEnableMultiSelectFilterTextfieldConfiguration($tca);
58
        $tca = $this->removeExcludeFieldForTransOrigPointerField($tca);
59
        $tca = $this->removeShowRecordFieldListField($tca);
60
        $tca = $this->removeWorkspacePlaceholderShadowColumnsConfiguration($tca);
61
62
        return $tca;
63
    }
64
65
    /**
66
     * Get messages of migrated fields. Can be used for deprecation messages after migrate() was called.
67
     *
68
     * @return array Migration messages
69
     */
70
    public function getMessages(): array
71
    {
72
        return $this->messages;
73
    }
74
75
    /**
76
     * Check for required TCA configuration
77
     *
78
     * @param array $tca Incoming TCA
79
     */
80
    protected function validateTcaType(array $tca)
81
    {
82
        foreach ($tca as $table => $tableDefinition) {
83
            if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
84
                continue;
85
            }
86
            foreach ($tableDefinition['columns'] as $fieldName => $fieldConfig) {
87
                if (isset($fieldConfig['config']) && is_array($fieldConfig['config']) && empty($fieldConfig['config']['type'])) {
88
                    throw new \UnexpectedValueException(
89
                        'Missing "type" in TCA of field "[\'' . $table . '\'][\'' . $fieldName . '\'][\'config\']".',
90
                        1482394401
91
                    );
92
                }
93
            }
94
        }
95
    }
96
97
    /**
98
     * Find columns fields that don't have a 'config' section at all, add
99
     * ['config']['type'] = 'none'; for those to enforce config
100
     *
101
     * @param array $tca Incoming TCA
102
     * @return array
103
     */
104
    protected function migrateColumnsConfig(array $tca): array
105
    {
106
        foreach ($tca as $table => &$tableDefinition) {
107
            if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
108
                continue;
109
            }
110
            foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
111
                if ((!isset($fieldConfig['config']) || !is_array($fieldConfig['config'])) && !isset($fieldConfig['type'])) {
112
                    $fieldConfig['config'] = [
113
                        'type' => 'none',
114
                    ];
115
                    $this->messages[] = 'TCA table "' . $table . '" columns field "' . $fieldName . '"'
116
                        . ' had no mandatory "config" section. This has been added with default type "none":'
117
                        . ' TCA "' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'config\'][\'type\'] = \'none\'"';
118
                }
119
            }
120
        }
121
        return $tca;
122
    }
123
124
    /**
125
     * Option $TCA[$table]['columns'][$columnName]['config']['behaviour']['localizeChildrenAtParentLocalization']
126
     * is always on, so this option can be removed.
127
     *
128
     * @param array $tca
129
     * @return array the modified TCA structure
130
     */
131
    protected function migrateLocalizeChildrenAtParentLocalization(array $tca): array
132
    {
133
        foreach ($tca as $table => &$tableDefinition) {
134
            if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
135
                continue;
136
            }
137
            foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
138
                if (($fieldConfig['config']['type'] ?? null) !== 'inline') {
139
                    continue;
140
                }
141
142
                $localizeChildrenAtParentLocalization = ($fieldConfig['config']['behaviour']['localizeChildrenAtParentLocalization'] ?? null);
143
                if ($localizeChildrenAtParentLocalization === null) {
144
                    continue;
145
                }
146
147
                if ($localizeChildrenAtParentLocalization) {
148
                    $this->messages[] = 'The TCA setting \'localizeChildrenAtParentLocalization\' is deprecated '
149
                        . ' and should be removed from TCA for ' . $table . '[\'columns\']'
150
                        . '[\'' . $fieldName . '\'][\'config\'][\'behaviour\'][\'localizeChildrenAtParentLocalization\']';
151
                } else {
152
                    $this->messages[] = 'The TCA setting \'localizeChildrenAtParentLocalization\' is deprecated '
153
                        . ', as this functionality is always enabled. The option should be removed from TCA for '
154
                        . $table . '[\'columns\'][\'' . $fieldName . '\'][\'config\'][\'behaviour\']'
155
                        . '[\'localizeChildrenAtParentLocalization\']';
156
                }
157
                unset($fieldConfig['config']['behaviour']['localizeChildrenAtParentLocalization']);
158
            }
159
        }
160
        return $tca;
161
    }
162
163
    /**
164
     * Removes $TCA['pages_language_overlay'] if defined.
165
     *
166
     * @param array $tca
167
     * @return array the modified TCA structure
168
     */
169
    protected function migratePagesLanguageOverlayRemoval(array $tca)
170
    {
171
        if (isset($tca['pages_language_overlay'])) {
172
            $this->messages[] = 'The TCA table \'pages_language_overlay\' is'
173
                . ' not used anymore and has been removed automatically in'
174
                . ' order to avoid negative side-effects.';
175
            unset($tca['pages_language_overlay']);
176
        }
177
        return $tca;
178
    }
179
180
    /**
181
     * Removes configuration removeEnableMultiSelectFilterTextfield
182
     *
183
     * @param array $tca
184
     * @return array the modified TCA structure
185
     */
186
    protected function removeEnableMultiSelectFilterTextfieldConfiguration(array $tca): array
187
    {
188
        foreach ($tca as $table => &$tableDefinition) {
189
            if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) {
190
                continue;
191
            }
192
            foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) {
193
                if (!isset($fieldConfig['config']['enableMultiSelectFilterTextfield'])) {
194
                    continue;
195
                }
196
197
                $this->messages[] = 'The TCA setting \'enableMultiSelectFilterTextfield\' is deprecated '
198
                    . ' and should be removed from TCA for ' . $table . '[\'columns\']'
199
                    . '[\'' . $fieldName . '\'][\'config\'][\'enableMultiSelectFilterTextfield\']';
200
                unset($fieldConfig['config']['enableMultiSelectFilterTextfield']);
201
            }
202
        }
203
        return $tca;
204
    }
205
206
    /**
207
     * Removes $TCA[$mytable][ctrl][selicon_field_path]
208
     *
209
     * @param array $tca
210
     * @return array the modified TCA structure
211
     */
212
    protected function removeSelIconFieldPath(array $tca): array
213
    {
214
        foreach ($tca as $table => &$configuration) {
215
            if (isset($configuration['ctrl']['selicon_field_path'])) {
216
                $this->messages[] = 'The TCA table \'' . $table . '\' defines '
217
                    . '[ctrl][selicon_field_path] which should be removed from TCA, '
218
                    . 'as it is not in use anymore.';
219
                unset($configuration['ctrl']['selicon_field_path']);
220
            }
221
        }
222
        return $tca;
223
    }
224
225
    /**
226
     * Removes $TCA[$mytable][ctrl][setToDefaultOnCopy]
227
     *
228
     * @param array $tca
229
     * @return array the modified TCA structure
230
     */
231
    protected function removeSetToDefaultOnCopy(array $tca): array
232
    {
233
        foreach ($tca as $table => &$configuration) {
234
            if (isset($configuration['ctrl']['setToDefaultOnCopy'])) {
235
                $this->messages[] = 'The TCA table \'' . $table . '\' defines '
236
                    . '[ctrl][setToDefaultOnCopy] which should be removed from TCA, '
237
                    . 'as it is not in use anymore.';
238
                unset($configuration['ctrl']['setToDefaultOnCopy']);
239
            }
240
        }
241
        return $tca;
242
    }
243
244
    /**
245
     * Ensures that system internal columns that are required for data integrity
246
     * (e.g. localize or copy a record) are available in case they have been defined
247
     * in $GLOBALS['TCA'][<table-name>]['ctrl'].
248
     *
249
     * The list of references to usages below is not necessarily complete.
250
     *
251
     * @param array $tca
252
     * @return array
253
     *
254
     * @see \TYPO3\CMS\Core\DataHandling\DataHandler::fillInFieldArray()
255
     */
256
    protected function sanitizeControlSectionIntegrity(array $tca): array
257
    {
258
        $controlSectionNames = [
259
            'origUid',
260
            'languageField',
261
            'transOrigPointerField',
262
            'translationSource'
263
        ];
264
        foreach ($tca as $tableName => &$configuration) {
265
            foreach ($controlSectionNames as $controlSectionName) {
266
                $columnName = $configuration['ctrl'][$controlSectionName] ?? null;
267
                if (empty($columnName) || !empty($configuration['columns'][$columnName])) {
268
                    continue;
269
                }
270
                $configuration['columns'][$columnName] = [
271
                    'config' => [
272
                        'type' => 'passthrough',
273
                        'default' => 0,
274
                    ],
275
                ];
276
            }
277
        }
278
        return $tca;
279
    }
280
281
    /**
282
     * Removes $TCA[$mytable][columns][_transOrigPointerField_][exclude] if defined
283
     *
284
     * @param array $tca
285
     *
286
     * @return array
287
     */
288
    protected function removeExcludeFieldForTransOrigPointerField(array $tca): array
289
    {
290
        foreach ($tca as $table => &$configuration) {
291
            if (isset($configuration['ctrl']['transOrigPointerField'],
292
                $configuration['columns'][$configuration['ctrl']['transOrigPointerField']]['exclude'])
293
            ) {
294
                $this->messages[] = 'The \'' . $table . '\' TCA tables transOrigPointerField '
295
                    . '\'' . $configuration['ctrl']['transOrigPointerField'] . '\' is defined '
296
                    . ' as excluded field which is no longer needed and should therefore be removed. ';
297
                unset($configuration['columns'][$configuration['ctrl']['transOrigPointerField']]['exclude']);
298
            }
299
        }
300
301
        return $tca;
302
    }
303
304
    /**
305
     * Removes $TCA[$mytable]['interface']['showRecordFieldList'] and also $TCA[$mytable]['interface']
306
     * if `showRecordFieldList` was the only key in the array.
307
     *
308
     * @param array $tca
309
     * @return array
310
     */
311
    protected function removeShowRecordFieldListField(array $tca): array
312
    {
313
        foreach ($tca as $table => &$configuration) {
314
            if (!isset($configuration['interface']['showRecordFieldList'])) {
315
                continue;
316
            }
317
            $this->messages[] = 'The \'' . $table . '\' TCA configuration \'showRecordFieldList\''
318
                . ' inside the section \'interface\' is not evaluated anymore and should therefore be removed.';
319
            unset($configuration['interface']['showRecordFieldList']);
320
            if ($configuration['interface'] === []) {
321
                unset($configuration['interface']);
322
            }
323
        }
324
325
        return $tca;
326
    }
327
328
    /**
329
     * Removes $TCA[$mytable][ctrl][shadowColumnsForMovePlaceholders]
330
     *
331
     * @param array $tca
332
     * @return array the modified TCA structure
333
     */
334
    protected function removeWorkspacePlaceholderShadowColumnsConfiguration(array $tca): array
335
    {
336
        foreach ($tca as $table => &$configuration) {
337
            if (isset($configuration['ctrl']['shadowColumnsForMovePlaceholders'])) {
338
                $this->messages[] = 'The TCA table \'' . $table . '\' defines '
339
                    . '[ctrl][shadowColumnsForMovePlaceholders] which should be removed from TCA, '
340
                    . 'as it is not in use anymore.';
341
                unset($configuration['ctrl']['shadowColumnsForMovePlaceholders']);
342
            }
343
        }
344
        return $tca;
345
    }
346
}
347