e0f2843b3714403890d3a597f14ed2c317817167
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaFlexPrepare.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\Configuration\FlexForm\FlexFormTools;
19 use TYPO3\CMS\Core\Migrations\TcaMigration;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Resolve flex data structure and data values, prepare and normalize.
24 *
25 * This is the first data provider in the chain of flex form related providers.
26 */
27 class TcaFlexPrepare implements FormDataProviderInterface
28 {
29 /**
30 * Resolve flex data structures and prepare flex data values.
31 *
32 * Normalize some details to have aligned array nesting for the rest of the
33 * processing method and the render engine.
34 *
35 * @param array $result
36 * @return array
37 */
38 public function addData(array $result)
39 {
40 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
41 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'flex') {
42 continue;
43 }
44 $result = $this->initializeDataStructure($result, $fieldName);
45 $result = $this->initializeDataValues($result, $fieldName);
46 $result = $this->removeTceFormsArrayKeyFromDataStructureElements($result, $fieldName);
47 $result = $this->migrateFlexformTcaDataStructureElements($result, $fieldName);
48 }
49
50 return $result;
51 }
52
53 /**
54 * Fetch / initialize data structure.
55 *
56 * The sub array with different possible data structures in ['config']['ds'] is
57 * resolved here, ds array contains only the one resolved data structure after this method.
58 *
59 * @param array $result Result array
60 * @param string $fieldName Currently handled field name
61 * @return array Modified result
62 * @throws \UnexpectedValueException
63 */
64 protected function initializeDataStructure(array $result, $fieldName)
65 {
66 if (!isset($result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'])) {
67 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
68 $dataStructureIdentifier = $flexFormTools->getDataStructureIdentifier(
69 $result['processedTca']['columns'][$fieldName],
70 $result['tableName'],
71 $fieldName,
72 $result['databaseRow']
73 );
74 // Add the identifier to TCA to use it later during rendering
75 $result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'] = $dataStructureIdentifier;
76 $dataStructureArray = $flexFormTools->parseDataStructureByIdentifier($dataStructureIdentifier);
77 } else {
78 // Assume the data structure has been given from outside if the data structure identifier is already set.
79 $dataStructureArray = $result['processedTca']['columns'][$fieldName]['config']['ds'];
80 }
81 if (!isset($dataStructureArray['meta']) || !is_array($dataStructureArray['meta'])) {
82 $dataStructureArray['meta'] = [];
83 }
84 // This kicks one array depth: config['ds']['listOfDataStructures'] becomes config['ds']
85 // This also ensures the final ds can be found in 'ds', even if the DS was fetch from
86 // a record, see FlexFormTools->getDataStructureIdentifier() for details.
87 $result['processedTca']['columns'][$fieldName]['config']['ds'] = $dataStructureArray;
88 return $result;
89 }
90
91 /**
92 * Parse / initialize value from xml string to array
93 *
94 * @param array $result Result array
95 * @param string $fieldName Currently handled field name
96 * @return array Modified result
97 */
98 protected function initializeDataValues(array $result, $fieldName)
99 {
100 if (!array_key_exists($fieldName, $result['databaseRow'])) {
101 $result['databaseRow'][$fieldName] = '';
102 }
103 $valueArray = [];
104 if (isset($result['databaseRow'][$fieldName])) {
105 $valueArray = $result['databaseRow'][$fieldName];
106 }
107 if (!is_array($result['databaseRow'][$fieldName])) {
108 $valueArray = GeneralUtility::xml2array($result['databaseRow'][$fieldName]);
109 }
110 if (!is_array($valueArray)) {
111 $valueArray = [];
112 }
113 if (!isset($valueArray['data'])) {
114 $valueArray['data'] = [];
115 }
116 if (!isset($valueArray['meta'])) {
117 $valueArray['meta'] = [];
118 }
119 $result['databaseRow'][$fieldName] = $valueArray;
120 return $result;
121 }
122
123 /**
124 * Remove "TCEforms" key from all elements in data structure to simplify further parsing.
125 *
126 * Example config:
127 * ['config']['ds']['sheets']['sDEF']['ROOT']['el']['anElement']['TCEforms']['label'] becomes
128 * ['config']['ds']['sheets']['sDEF']['ROOT']['el']['anElement']['label']
129 *
130 * @param array $result Result array
131 * @param string $fieldName Currently handled field name
132 * @return array Modified result
133 */
134 protected function removeTceFormsArrayKeyFromDataStructureElements(array $result, $fieldName)
135 {
136 $modifiedDataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
137 $modifiedDataStructure = $this->removeElementTceFormsRecursive($modifiedDataStructure);
138 $result['processedTca']['columns'][$fieldName]['config']['ds'] = $modifiedDataStructure;
139 return $result;
140 }
141
142 /**
143 * Moves ['el']['something']['TCEforms'] to ['el']['something'] and ['ROOT']['TCEforms'] to ['ROOT'] recursive
144 *
145 * @param array $structure Given hierarchy
146 * @return array Modified hierarchy
147 */
148 protected function removeElementTceFormsRecursive(array $structure)
149 {
150 $newStructure = [];
151 foreach ($structure as $key => $value) {
152 if ($key === 'ROOT' && is_array($value) && isset($value['TCEforms'])) {
153 $value = array_merge($value, $value['TCEforms']);
154 unset($value['TCEforms']);
155 }
156 if ($key === 'el' && is_array($value)) {
157 $newSubStructure = [];
158 foreach ($value as $subKey => $subValue) {
159 if (is_array($subValue) && count($subValue) === 1 && isset($subValue['TCEforms'])) {
160 $newSubStructure[$subKey] = $subValue['TCEforms'];
161 } else {
162 $newSubStructure[$subKey] = $subValue;
163 }
164 }
165 $value = $newSubStructure;
166 }
167 if (is_array($value)) {
168 $value = $this->removeElementTceFormsRecursive($value);
169 }
170 $newStructure[$key] = $value;
171 }
172 return $newStructure;
173 }
174
175 /**
176 * On-the-fly migration for flex form "TCA"
177 *
178 * @param array $result Result array
179 * @param string $fieldName Currently handled field name
180 * @return array Modified result
181 */
182 protected function migrateFlexformTcaDataStructureElements(array $result, $fieldName)
183 {
184 $modifiedDataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
185 $modifiedDataStructure = $this->migrateFlexformTcaRecursive($modifiedDataStructure, $result['tableName'], $fieldName);
186 $result['processedTca']['columns'][$fieldName]['config']['ds'] = $modifiedDataStructure;
187 return $result;
188 }
189
190 /**
191 * Recursively migrate flex form TCA
192 *
193 * @param array $structure Given hierarchy
194 * @param string $table
195 * @param string $fieldName
196 * @return array Modified hierarchy
197 */
198 protected function migrateFlexformTcaRecursive($structure, $table, $fieldName)
199 {
200 $newStructure = [];
201 foreach ($structure as $key => $value) {
202 if ($key === 'el' && is_array($value)) {
203 $newSubStructure = [];
204 $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
205 foreach ($value as $subKey => $subValue) {
206 // On-the-fly migration for flex form "TCA"
207 // @deprecated since TYPO3 CMS 7. Not removed in TYPO3 CMS 8 though. This call will stay for now to allow further TCA migrations in 8.
208 $dummyTca = [
209 'dummyTable' => [
210 'columns' => [
211 'dummyField' => $subValue,
212 ],
213 ],
214 ];
215 $migratedTca = $tcaMigration->migrate($dummyTca);
216 $messages = $tcaMigration->getMessages();
217 if (!empty($messages)) {
218 $context = 'FormEngine did an on-the-fly migration of a flex form data structure. This is deprecated and will be removed.'
219 . ' Merge the following changes into the flex form definition of table "' . $table . '"" in field "' . $fieldName . '"":';
220 array_unshift($messages, $context);
221 GeneralUtility::deprecationLog(implode(LF, $messages));
222 }
223 $newSubStructure[$subKey] = $migratedTca['dummyTable']['columns']['dummyField'];
224 }
225 $value = $newSubStructure;
226 }
227 if (is_array($value)) {
228 $value = $this->migrateFlexformTcaRecursive($value, $table, $fieldName);
229 }
230 $newStructure[$key] = $value;
231 }
232 return $newStructure;
233 }
234 }