[BUGFIX] MM Relation with add wizard resets the MM Relation
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / Wizard / AddController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller\Wizard;
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 Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Form\FormDataCompiler;
20 use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
23 use TYPO3\CMS\Core\DataHandling\DataHandler;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\HttpUtility;
26 use TYPO3\CMS\Core\Utility\MathUtility;
27
28 /**
29 * Script Class for adding new items to a group/select field. Performs proper redirection as needed.
30 * Script is typically called after new child record was added and then adds the new child to select value of parent.
31 */
32 class AddController extends AbstractWizardController
33 {
34 /**
35 * Content accumulation for the module.
36 *
37 * @var string
38 */
39 public $content;
40
41 /**
42 * If set, the TCEmain class is loaded and used to add the returning ID to the parent record.
43 *
44 * @var int
45 */
46 public $processDataFlag = 0;
47
48 /**
49 * Create new record -pid (pos/neg). If blank, return immediately
50 *
51 * @var int
52 */
53 public $pid;
54
55 /**
56 * The parent table we are working on.
57 *
58 * @var string
59 */
60 public $table;
61
62 /**
63 * Loaded with the created id of a record FormEngine returns ...
64 *
65 * @var int
66 */
67 public $id;
68
69 /**
70 * Wizard parameters, coming from TCEforms linking to the wizard.
71 *
72 * @var array
73 */
74 public $P;
75
76 /**
77 * Information coming back from the FormEngine script, telling what the table/id was of the newly created record.
78 *
79 * @var array
80 */
81 public $returnEditConf;
82
83 /**
84 * Constructor
85 */
86 public function __construct()
87 {
88 parent::__construct();
89 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_wizards.xlf');
90 $GLOBALS['SOBE'] = $this;
91
92 $this->init();
93 }
94
95 /**
96 * Initialization of the class.
97 *
98 * @return void
99 */
100 protected function init()
101 {
102 // Init GPvars:
103 $this->P = GeneralUtility::_GP('P');
104 $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
105 // Get this record
106 $record = BackendUtility::getRecord($this->P['table'], $this->P['uid']);
107 // Set table:
108 $this->table = $this->P['params']['table'];
109 // Get TSconfig for it.
110 $TSconfig = BackendUtility::getTCEFORM_TSconfig(
111 $this->P['table'],
112 is_array($record) ? $record : ['pid' => $this->P['pid']]
113 );
114 // Set [params][pid]
115 if (substr($this->P['params']['pid'], 0, 3) === '###' && substr($this->P['params']['pid'], -3) === '###') {
116 $keyword = substr($this->P['params']['pid'], 3, -3);
117 if (strpos($keyword, 'PAGE_TSCONFIG_') === 0) {
118 $this->pid = (int)$TSconfig[$this->P['field']][$keyword];
119 } else {
120 $this->pid = (int)$TSconfig['_' . $keyword];
121 }
122 } else {
123 $this->pid = (int)$this->P['params']['pid'];
124 }
125 // Return if new record as parent (not possibly/allowed)
126 if ($this->pid === '') {
127 HttpUtility::redirect(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
128 }
129 // Else proceed:
130 // If a new id has returned from a newly created record...
131 if ($this->returnEditConf) {
132 $editConfiguration = json_decode($this->returnEditConf, true);
133 if (is_array($editConfiguration[$this->table]) && MathUtility::canBeInterpretedAsInteger($this->P['uid'])) {
134 // Getting id and cmd from returning editConf array.
135 reset($editConfiguration[$this->table]);
136 $this->id = (int)key($editConfiguration[$this->table]);
137 $cmd = current($editConfiguration[$this->table]);
138 // ... and if everything seems OK we will register some classes for inclusion and instruct the object
139 // to perform processing later.
140 if ($this->P['params']['setValue']
141 && $cmd === 'edit'
142 && $this->id
143 && $this->P['table']
144 && $this->P['field'] && $this->P['uid']
145 ) {
146 $liveRecord = BackendUtility::getLiveVersionOfRecord($this->table, $this->id, 'uid');
147 if ($liveRecord) {
148 $this->id = $liveRecord['uid'];
149 }
150 $this->processDataFlag = 1;
151 }
152 }
153 }
154 }
155
156 /**
157 * Injects the request object for the current request or subrequest
158 * As this controller goes only through the main() method, it is rather simple for now
159 *
160 * @param ServerRequestInterface $request
161 * @param ResponseInterface $response
162 * @return ResponseInterface
163 */
164 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
165 {
166 $this->main();
167 return $response;
168 }
169
170 /**
171 * Main function
172 * Will issue a location-header, redirecting either BACK or to a new FormEngine instance...
173 *
174 * @return void
175 */
176 public function main()
177 {
178 if ($this->returnEditConf) {
179 if ($this->processDataFlag) {
180 // Because OnTheFly can't handle MM relations with intermediate tables we use TcaDatabaseRecord here
181 // Otherwise already stored relations are overwritten with the new entry
182 /** @var TcaDatabaseRecord $formDataGroup */
183 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
184 /** @var FormDataCompiler $formDataCompiler */
185 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
186 $input = [
187 'tableName' => $this->P['table'],
188 'vanillaUid' => (int)$this->P['uid'],
189 'command' => 'edit',
190 ];
191 $result = $formDataCompiler->compile($input);
192 $currentParentRow = $result['databaseRow'];
193
194 // If that record was found (should absolutely be...), then init DataHandler and set, prepend or append
195 // the record
196 if (is_array($currentParentRow)) {
197 /** @var DataHandler $dataHandler */
198 $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
199 $dataHandler->stripslashes_values = false;
200 $data = [];
201 $recordId = $this->table . '_' . $this->id;
202 // Setting the new field data:
203 // If the field is a flexForm field, work with the XML structure instead:
204 if ($this->P['flexFormPath']) {
205 // Current value of flexForm path:
206 $currentFlexFormData = GeneralUtility::xml2array($currentParentRow[$this->P['field']]);
207 /** @var FlexFormTools $flexFormTools */
208 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
209 $currentFlexFormValue = $flexFormTools->getArrayValueByPath(
210 $this->P['flexFormPath'],
211 $currentFlexFormData
212 );
213 $insertValue = '';
214 switch ((string)$this->P['params']['setValue']) {
215 case 'set':
216 $insertValue = $recordId;
217 break;
218 case 'prepend':
219 $insertValue = $currentFlexFormValue . ',' . $recordId;
220 break;
221 case 'append':
222 $insertValue = $recordId . ',' . $currentFlexFormValue;
223 break;
224 }
225 $insertValue = implode(',', GeneralUtility::trimExplode(',', $insertValue, true));
226 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = [];
227 $flexFormTools->setArrayValueByPath(
228 $this->P['flexFormPath'],
229 $data[$this->P['table']][$this->P['uid']][$this->P['field']],
230 $insertValue
231 );
232 } else {
233 // Check the row for its datatype. If it is an array it stores the relation
234 // to other rows. Implode it into a comma separated list to be able to restore the stored
235 // values after the wizard falls back to the parent record
236 $currentValue = $currentParentRow[$this->P['field']];
237 if (is_array($currentValue)) {
238 $currentValue = implode(',', array_column($currentValue, 'uid'));
239 }
240 switch ((string)$this->P['params']['setValue']) {
241 case 'set':
242 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $recordId;
243 break;
244 case 'prepend':
245 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $currentValue . ',' . $recordId;
246 break;
247 case 'append':
248 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = $recordId . ',' . $currentValue;
249 break;
250 }
251 $data[$this->P['table']][$this->P['uid']][$this->P['field']] = implode(
252 ',',
253 GeneralUtility::trimExplode(
254 ',',
255 $data[$this->P['table']][$this->P['uid']][$this->P['field']],
256 true
257 )
258 );
259 }
260 // Submit the data:
261 $dataHandler->start($data, []);
262 $dataHandler->process_datamap();
263 }
264 }
265 // Return to the parent FormEngine record editing session:
266 HttpUtility::redirect(GeneralUtility::sanitizeLocalUrl($this->P['returnUrl']));
267 } else {
268 // Redirecting to FormEngine with instructions to create a new record
269 // AND when closing to return back with information about that records ID etc.
270 $redirectUrl = BackendUtility::getModuleUrl('record_edit', [
271 'returnEditConf' => 1,
272 'edit[' . $this->P['params']['table'] . '][' . $this->pid . ']' => 'new',
273 'returnUrl' => GeneralUtility::removeXSS(GeneralUtility::getIndpEnv('REQUEST_URI'))
274 ]);
275 HttpUtility::redirect($redirectUrl);
276 }
277 }
278 }