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