Total Complexity | 83 |
Total Lines | 457 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like TcaMigration often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TcaMigration, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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 | $tca = $this->migrateLanguageFieldToTcaTypeLanguage($tca); |
||
62 | $tca = $this->migrateSpecialLanguagesToTcaTypeLanguage($tca); |
||
63 | $tca = $this->removeShowRemovedLocalizationRecords($tca); |
||
64 | $tca = $this->migrateFileFolderConfiguration($tca); |
||
65 | |||
66 | return $tca; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Get messages of migrated fields. Can be used for deprecation messages after migrate() was called. |
||
71 | * |
||
72 | * @return array Migration messages |
||
73 | */ |
||
74 | public function getMessages(): array |
||
75 | { |
||
76 | return $this->messages; |
||
77 | } |
||
78 | |||
79 | /** |
||
80 | * Check for required TCA configuration |
||
81 | * |
||
82 | * @param array $tca Incoming TCA |
||
83 | */ |
||
84 | protected function validateTcaType(array $tca) |
||
85 | { |
||
86 | foreach ($tca as $table => $tableDefinition) { |
||
87 | if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { |
||
88 | continue; |
||
89 | } |
||
90 | foreach ($tableDefinition['columns'] as $fieldName => $fieldConfig) { |
||
91 | if (isset($fieldConfig['config']) && is_array($fieldConfig['config']) && empty($fieldConfig['config']['type'])) { |
||
92 | throw new \UnexpectedValueException( |
||
93 | 'Missing "type" in TCA of field "[\'' . $table . '\'][\'' . $fieldName . '\'][\'config\']".', |
||
94 | 1482394401 |
||
95 | ); |
||
96 | } |
||
97 | } |
||
98 | } |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Find columns fields that don't have a 'config' section at all, add |
||
103 | * ['config']['type'] = 'none'; for those to enforce config |
||
104 | * |
||
105 | * @param array $tca Incoming TCA |
||
106 | * @return array |
||
107 | */ |
||
108 | protected function migrateColumnsConfig(array $tca): array |
||
109 | { |
||
110 | foreach ($tca as $table => &$tableDefinition) { |
||
111 | if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { |
||
112 | continue; |
||
113 | } |
||
114 | foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) { |
||
115 | if ((!isset($fieldConfig['config']) || !is_array($fieldConfig['config'])) && !isset($fieldConfig['type'])) { |
||
116 | $fieldConfig['config'] = [ |
||
117 | 'type' => 'none', |
||
118 | ]; |
||
119 | $this->messages[] = 'TCA table "' . $table . '" columns field "' . $fieldName . '"' |
||
120 | . ' had no mandatory "config" section. This has been added with default type "none":' |
||
121 | . ' TCA "' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'config\'][\'type\'] = \'none\'"'; |
||
122 | } |
||
123 | } |
||
124 | } |
||
125 | return $tca; |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Option $TCA[$table]['columns'][$columnName]['config']['behaviour']['localizeChildrenAtParentLocalization'] |
||
130 | * is always on, so this option can be removed. |
||
131 | * |
||
132 | * @param array $tca |
||
133 | * @return array the modified TCA structure |
||
134 | */ |
||
135 | protected function migrateLocalizeChildrenAtParentLocalization(array $tca): array |
||
136 | { |
||
137 | foreach ($tca as $table => &$tableDefinition) { |
||
138 | if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { |
||
139 | continue; |
||
140 | } |
||
141 | foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) { |
||
142 | if (($fieldConfig['config']['type'] ?? null) !== 'inline') { |
||
143 | continue; |
||
144 | } |
||
145 | |||
146 | $localizeChildrenAtParentLocalization = ($fieldConfig['config']['behaviour']['localizeChildrenAtParentLocalization'] ?? null); |
||
147 | if ($localizeChildrenAtParentLocalization === null) { |
||
148 | continue; |
||
149 | } |
||
150 | |||
151 | if ($localizeChildrenAtParentLocalization) { |
||
152 | $this->messages[] = 'The TCA setting \'localizeChildrenAtParentLocalization\' is deprecated ' |
||
153 | . ' and should be removed from TCA for ' . $table . '[\'columns\']' |
||
154 | . '[\'' . $fieldName . '\'][\'config\'][\'behaviour\'][\'localizeChildrenAtParentLocalization\']'; |
||
155 | } else { |
||
156 | $this->messages[] = 'The TCA setting \'localizeChildrenAtParentLocalization\' is deprecated ' |
||
157 | . ', as this functionality is always enabled. The option should be removed from TCA for ' |
||
158 | . $table . '[\'columns\'][\'' . $fieldName . '\'][\'config\'][\'behaviour\']' |
||
159 | . '[\'localizeChildrenAtParentLocalization\']'; |
||
160 | } |
||
161 | unset($fieldConfig['config']['behaviour']['localizeChildrenAtParentLocalization']); |
||
162 | } |
||
163 | } |
||
164 | return $tca; |
||
165 | } |
||
166 | |||
167 | /** |
||
168 | * Removes $TCA['pages_language_overlay'] if defined. |
||
169 | * |
||
170 | * @param array $tca |
||
171 | * @return array the modified TCA structure |
||
172 | */ |
||
173 | protected function migratePagesLanguageOverlayRemoval(array $tca) |
||
174 | { |
||
175 | if (isset($tca['pages_language_overlay'])) { |
||
176 | $this->messages[] = 'The TCA table \'pages_language_overlay\' is' |
||
177 | . ' not used anymore and has been removed automatically in' |
||
178 | . ' order to avoid negative side-effects.'; |
||
179 | unset($tca['pages_language_overlay']); |
||
180 | } |
||
181 | return $tca; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Removes configuration removeEnableMultiSelectFilterTextfield |
||
186 | * |
||
187 | * @param array $tca |
||
188 | * @return array the modified TCA structure |
||
189 | */ |
||
190 | protected function removeEnableMultiSelectFilterTextfieldConfiguration(array $tca): array |
||
191 | { |
||
192 | foreach ($tca as $table => &$tableDefinition) { |
||
193 | if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { |
||
194 | continue; |
||
195 | } |
||
196 | foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) { |
||
197 | if (!isset($fieldConfig['config']['enableMultiSelectFilterTextfield'])) { |
||
198 | continue; |
||
199 | } |
||
200 | |||
201 | $this->messages[] = 'The TCA setting \'enableMultiSelectFilterTextfield\' is deprecated ' |
||
202 | . ' and should be removed from TCA for ' . $table . '[\'columns\']' |
||
203 | . '[\'' . $fieldName . '\'][\'config\'][\'enableMultiSelectFilterTextfield\']'; |
||
204 | unset($fieldConfig['config']['enableMultiSelectFilterTextfield']); |
||
205 | } |
||
206 | } |
||
207 | return $tca; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Removes $TCA[$mytable][ctrl][selicon_field_path] |
||
212 | * |
||
213 | * @param array $tca |
||
214 | * @return array the modified TCA structure |
||
215 | */ |
||
216 | protected function removeSelIconFieldPath(array $tca): array |
||
217 | { |
||
218 | foreach ($tca as $table => &$configuration) { |
||
219 | if (isset($configuration['ctrl']['selicon_field_path'])) { |
||
220 | $this->messages[] = 'The TCA table \'' . $table . '\' defines ' |
||
221 | . '[ctrl][selicon_field_path] which should be removed from TCA, ' |
||
222 | . 'as it is not in use anymore.'; |
||
223 | unset($configuration['ctrl']['selicon_field_path']); |
||
224 | } |
||
225 | } |
||
226 | return $tca; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Removes $TCA[$mytable][ctrl][setToDefaultOnCopy] |
||
231 | * |
||
232 | * @param array $tca |
||
233 | * @return array the modified TCA structure |
||
234 | */ |
||
235 | protected function removeSetToDefaultOnCopy(array $tca): array |
||
236 | { |
||
237 | foreach ($tca as $table => &$configuration) { |
||
238 | if (isset($configuration['ctrl']['setToDefaultOnCopy'])) { |
||
239 | $this->messages[] = 'The TCA table \'' . $table . '\' defines ' |
||
240 | . '[ctrl][setToDefaultOnCopy] which should be removed from TCA, ' |
||
241 | . 'as it is not in use anymore.'; |
||
242 | unset($configuration['ctrl']['setToDefaultOnCopy']); |
||
243 | } |
||
244 | } |
||
245 | return $tca; |
||
246 | } |
||
247 | |||
248 | /** |
||
249 | * Ensures that system internal columns that are required for data integrity |
||
250 | * (e.g. localize or copy a record) are available in case they have been defined |
||
251 | * in $GLOBALS['TCA'][<table-name>]['ctrl']. |
||
252 | * |
||
253 | * The list of references to usages below is not necessarily complete. |
||
254 | * |
||
255 | * @param array $tca |
||
256 | * @return array |
||
257 | * |
||
258 | * @see \TYPO3\CMS\Core\DataHandling\DataHandler::fillInFieldArray() |
||
259 | */ |
||
260 | protected function sanitizeControlSectionIntegrity(array $tca): array |
||
261 | { |
||
262 | $defaultControlSectionColumnConfig = [ |
||
263 | 'type' => 'passthrough', |
||
264 | 'default' => 0, |
||
265 | ]; |
||
266 | $controlSectionNames = [ |
||
267 | 'origUid' => $defaultControlSectionColumnConfig, |
||
268 | 'languageField' => [ |
||
269 | 'type' => 'language' |
||
270 | ], |
||
271 | 'transOrigPointerField' => $defaultControlSectionColumnConfig, |
||
272 | 'translationSource' => $defaultControlSectionColumnConfig, |
||
273 | ]; |
||
274 | |||
275 | foreach ($tca as $tableName => &$configuration) { |
||
276 | foreach ($controlSectionNames as $controlSectionName => $controlSectionColumnConfig) { |
||
277 | $columnName = $configuration['ctrl'][$controlSectionName] ?? null; |
||
278 | if (empty($columnName) || !empty($configuration['columns'][$columnName])) { |
||
279 | continue; |
||
280 | } |
||
281 | $configuration['columns'][$columnName] = [ |
||
282 | 'config' => $controlSectionColumnConfig |
||
283 | ]; |
||
284 | } |
||
285 | } |
||
286 | return $tca; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Removes $TCA[$mytable][columns][_transOrigPointerField_][exclude] if defined |
||
291 | * |
||
292 | * @param array $tca |
||
293 | * |
||
294 | * @return array |
||
295 | */ |
||
296 | protected function removeExcludeFieldForTransOrigPointerField(array $tca): array |
||
297 | { |
||
298 | foreach ($tca as $table => &$configuration) { |
||
299 | if (isset($configuration['ctrl']['transOrigPointerField'], |
||
300 | $configuration['columns'][$configuration['ctrl']['transOrigPointerField']]['exclude']) |
||
301 | ) { |
||
302 | $this->messages[] = 'The \'' . $table . '\' TCA tables transOrigPointerField ' |
||
303 | . '\'' . $configuration['ctrl']['transOrigPointerField'] . '\' is defined ' |
||
304 | . ' as excluded field which is no longer needed and should therefore be removed. '; |
||
305 | unset($configuration['columns'][$configuration['ctrl']['transOrigPointerField']]['exclude']); |
||
306 | } |
||
307 | } |
||
308 | |||
309 | return $tca; |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * Removes $TCA[$mytable]['interface']['showRecordFieldList'] and also $TCA[$mytable]['interface'] |
||
314 | * if `showRecordFieldList` was the only key in the array. |
||
315 | * |
||
316 | * @param array $tca |
||
317 | * @return array |
||
318 | */ |
||
319 | protected function removeShowRecordFieldListField(array $tca): array |
||
320 | { |
||
321 | foreach ($tca as $table => &$configuration) { |
||
322 | if (!isset($configuration['interface']['showRecordFieldList'])) { |
||
323 | continue; |
||
324 | } |
||
325 | $this->messages[] = 'The \'' . $table . '\' TCA configuration \'showRecordFieldList\'' |
||
326 | . ' inside the section \'interface\' is not evaluated anymore and should therefore be removed.'; |
||
327 | unset($configuration['interface']['showRecordFieldList']); |
||
328 | if ($configuration['interface'] === []) { |
||
329 | unset($configuration['interface']); |
||
330 | } |
||
331 | } |
||
332 | |||
333 | return $tca; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Removes $TCA[$mytable][ctrl][shadowColumnsForMovePlaceholders] |
||
338 | * and $TCA[$mytable][ctrl][shadowColumnsForNewPlaceholders] |
||
339 | * |
||
340 | * @param array $tca |
||
341 | * @return array the modified TCA structure |
||
342 | */ |
||
343 | protected function removeWorkspacePlaceholderShadowColumnsConfiguration(array $tca): array |
||
344 | { |
||
345 | foreach ($tca as $table => &$configuration) { |
||
346 | if (isset($configuration['ctrl']['shadowColumnsForNewPlaceholders'])) { |
||
347 | $this->messages[] = 'The TCA table \'' . $table . '\' defines ' |
||
348 | . '[ctrl][shadowColumnsForNewPlaceholders] which should be removed from TCA, ' |
||
349 | . 'as it is not in use anymore.'; |
||
350 | unset($configuration['ctrl']['shadowColumnsForNewPlaceholders']); |
||
351 | } |
||
352 | if (isset($configuration['ctrl']['shadowColumnsForMovePlaceholders'])) { |
||
353 | $this->messages[] = 'The TCA table \'' . $table . '\' defines ' |
||
354 | . '[ctrl][shadowColumnsForMovePlaceholders] which should be removed from TCA, ' |
||
355 | . 'as it is not in use anymore.'; |
||
356 | unset($configuration['ctrl']['shadowColumnsForMovePlaceholders']); |
||
357 | } |
||
358 | } |
||
359 | return $tca; |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Replaces $TCA[$mytable][columns][$TCA[$mytable][ctrl][languageField]][config] with |
||
364 | * $TCA[$mytable][columns][$TCA[$mytable][ctrl][languageField]][config][type] = 'language' |
||
365 | * |
||
366 | * @param array $tca |
||
367 | * @return array |
||
368 | */ |
||
369 | protected function migrateLanguageFieldToTcaTypeLanguage(array $tca): array |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * Replaces $TCA[$mytable][columns][field][config][special] = 'languages' with |
||
390 | * $TCA[$mytable][columns][field][config][type] = 'language' |
||
391 | * |
||
392 | * @param array $tca |
||
393 | * @return array |
||
394 | */ |
||
395 | protected function migrateSpecialLanguagesToTcaTypeLanguage(array $tca): array |
||
396 | { |
||
397 | foreach ($tca as $table => &$tableDefinition) { |
||
398 | if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { |
||
399 | continue; |
||
400 | } |
||
401 | foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) { |
||
402 | if ((string)($fieldConfig['config']['type'] ?? '') !== 'select' |
||
403 | || (string)($fieldConfig['config']['special'] ?? '') !== 'languages' |
||
404 | ) { |
||
405 | continue; |
||
406 | } |
||
407 | $this->messages[] = 'The TCA field \'' . $fieldName . '\' of table \'' . $table . '\' is ' |
||
408 | . 'defined as type \'select\' with the \'special=languages\' option. This is not ' |
||
409 | . 'evaluated anymore and should be replaced by the TCA type \'language\'.'; |
||
410 | $fieldConfig['config'] = [ |
||
411 | 'type' => 'language' |
||
412 | ]; |
||
413 | } |
||
414 | } |
||
415 | |||
416 | return $tca; |
||
417 | } |
||
418 | |||
419 | protected function removeShowRemovedLocalizationRecords(array $tca): array |
||
420 | { |
||
421 | foreach ($tca as $table => &$tableDefinition) { |
||
422 | if (!isset($tableDefinition['columns']) || !is_array($tableDefinition['columns'])) { |
||
423 | continue; |
||
424 | } |
||
425 | foreach ($tableDefinition['columns'] as $fieldName => &$fieldConfig) { |
||
426 | if ((string)($fieldConfig['config']['type'] ?? '') !== 'inline' |
||
427 | || !isset($fieldConfig['config']['appearance']['showRemovedLocalizationRecords']) |
||
428 | ) { |
||
429 | continue; |
||
430 | } |
||
431 | $this->messages[] = 'The TCA field \'' . $fieldName . '\' of table \'' . $table . '\' is ' |
||
432 | . 'defined as type \'inline\' with the \'appearance.showRemovedLocalizationRecords\' option set. ' |
||
433 | . 'As this option is not evaluated anymore and no replacement exists, it should be removed from TCA.'; |
||
434 | unset($fieldConfig['config']['appearance']['showRemovedLocalizationRecords']); |
||
435 | } |
||
436 | } |
||
437 | |||
438 | return $tca; |
||
439 | } |
||
440 | |||
441 | /** |
||
442 | * Moves the "fileFolder" configuration of TCA columns type=select |
||
443 | * into sub array "fileFolderConfig", while renaming those options. |
||
444 | * |
||
445 | * @param array $tca |
||
446 | * @return array |
||
447 | */ |
||
448 | protected function migrateFileFolderConfiguration(array $tca): array |
||
482 | } |
||
483 | } |
||
484 |