‪TYPO3CMS  9.5
FormInlineAjaxController.php
Go to the documentation of this file.
1 <?php
2 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 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
32 
37 {
44  public function ‪createAction(ServerRequestInterface $request): ResponseInterface
45  {
46  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
47  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
48 
49  $domObjectId = $ajaxArguments[0];
50  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
51  if (!‪MathUtility::canBeInterpretedAsInteger($inlineFirstPid) && strpos($inlineFirstPid, 'NEW') !== 0) {
52  throw new \RuntimeException(
53  'inlineFirstPid should either be an integer or a "NEW..." string',
54  1521220491
55  );
56  }
57  $childChildUid = null;
58  if (isset($ajaxArguments[1]) && ‪MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
59  $childChildUid = (int)$ajaxArguments[1];
60  }
61 
62  // Parse the DOM identifier, add the levels to the structure stack
64  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
65  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
66  $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
67  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
68 
69  // Parent, this table embeds the child table
70  $parent = $inlineStackProcessor->getStructureLevel(-1);
71 
72  // Child, a record from this table should be rendered
73  $child = $inlineStackProcessor->getUnstableStructure();
74  if (‪MathUtility::canBeInterpretedAsInteger($child['uid'])) {
75  // If uid comes in, it is the id of the record neighbor record "create after"
76  $childVanillaUid = -1 * abs((int)$child['uid']);
77  } else {
78  // Else inline first Pid is the storage pid of new inline records
79  $childVanillaUid = $inlineFirstPid;
80  }
81 
82  $childTableName = $parentConfig['foreign_table'];
83 
85  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
87  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
88  $formDataCompilerInput = [
89  'command' => 'new',
90  'tableName' => $childTableName,
91  'vanillaUid' => $childVanillaUid,
92  'isInlineChild' => true,
93  'inlineStructure' => $inlineStackProcessor->getStructure(),
94  'inlineFirstPid' => $inlineFirstPid,
95  'inlineParentUid' => $parent['uid'],
96  'inlineParentTableName' => $parent['table'],
97  'inlineParentFieldName' => $parent['field'],
98  'inlineParentConfig' => $parentConfig,
99  'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
100  'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
101  'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
102  ];
103  if ($childChildUid) {
104  $formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
105  }
106  $childData = $formDataCompiler->compile($formDataCompilerInput);
107 
108  if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
109  // We have a foreign_selector. So, we just created a new record on an intermediate table in $childData.
110  // Now, if a valid id is given as second ajax parameter, the intermediate row should be connected to an
111  // existing record of the child-child table specified by the given uid. If there is no such id, user
112  // clicked on "created new" and a new child-child should be created, too.
113  if ($childChildUid) {
114  // Fetch existing child child
115  $childData['databaseRow'][$parentConfig['foreign_selector']] = [
116  $childChildUid,
117  ];
118  $childData['combinationChild'] = $this->‪compileChildChild($childData, $parentConfig, $inlineStackProcessor->getStructure());
119  } else {
121  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
123  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
124  $formDataCompilerInput = [
125  'command' => 'new',
126  'tableName' => $childData['processedTca']['columns'][$parentConfig['foreign_selector']]['config']['foreign_table'],
127  'vanillaUid' => $inlineFirstPid,
128  'isInlineChild' => true,
129  'isInlineAjaxOpeningContext' => true,
130  'inlineStructure' => $inlineStackProcessor->getStructure(),
131  'inlineFirstPid' => $inlineFirstPid,
132  ];
133  $childData['combinationChild'] = $formDataCompiler->compile($formDataCompilerInput);
134  }
135  }
136 
137  $childData['inlineParentUid'] = $parent['uid'];
138  $childData['renderType'] = 'inlineRecordContainer';
139  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
140  $childResult = $nodeFactory->create($childData)->render();
141 
142  $jsonArray = [
143  'data' => '',
144  'stylesheetFiles' => [],
145  'scriptCall' => [],
146  ];
147 
148  // The HTML-object-id's prefix of the dynamically created record
149  $objectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
150  $objectPrefix = $objectName . '-' . $child['table'];
151  $objectId = $objectPrefix . '-' . $childData['databaseRow']['uid'];
152  $expandSingle = $parentConfig['appearance']['expandSingle'];
153  if (!$child['uid']) {
154  $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
155  $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',null,' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
156  } else {
157  $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
158  $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',' . GeneralUtility::quoteJSvalue($child['uid']) . ',' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
159  }
160  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
161  if ($parentConfig['appearance']['useSortable']) {
162  $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
163  $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
164  }
165  if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
166  $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ');';
167  }
168  // Fade out and fade in the new record in the browser view to catch the user's eye
169  $jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
170 
171  return new ‪JsonResponse($jsonArray);
172  }
173 
180  public function ‪detailsAction(ServerRequestInterface $request): ResponseInterface
181  {
182  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
183 
184  $domObjectId = $ajaxArguments[0] ?? null;
185  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
186  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
187 
188  // Parse the DOM identifier, add the levels to the structure stack
190  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
191  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
192  $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
193 
194  // Parent, this table embeds the child table
195  $parent = $inlineStackProcessor->getStructureLevel(-1);
196  $parentFieldName = $parent['field'];
197 
198  // Set flag in config so that only the fields are rendered
199  // @todo: Solve differently / rename / whatever
200  $parentConfig['renderFieldsOnly'] = true;
201 
202  $parentData = [
203  'processedTca' => [
204  'columns' => [
205  $parentFieldName => [
206  'config' => $parentConfig,
207  ],
208  ],
209  ],
210  'uid' => $parent['uid'],
211  'tableName' => $parent['table'],
212  'inlineFirstPid' => $inlineFirstPid,
213  // Hand over given original return url to compile stack. Needed if inline children compile links to
214  // another view (eg. edit metadata in a nested inline situation like news with inline content element image),
215  // so the back link is still the link from the original request. See issue #82525. This is additionally
216  // given down in TcaInline data provider to compiled children data.
217  'returnUrl' => $parentConfig['originalReturnUrl'],
218  ];
219 
220  // Child, a record from this table should be rendered
221  $child = $inlineStackProcessor->getUnstableStructure();
222 
223  $childData = $this->‪compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
224 
225  $childData['inlineParentUid'] = (int)$parent['uid'];
226  $childData['renderType'] = 'inlineRecordContainer';
227  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
228  $childResult = $nodeFactory->create($childData)->render();
229 
230  $jsonArray = [
231  'data' => '',
232  'stylesheetFiles' => [],
233  'scriptCall' => [],
234  ];
235 
236  // The HTML-object-id's prefix of the dynamically created record
237  $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $child['table'];
238  $objectId = $objectPrefix . '-' . (int)$child['uid'];
239  $expandSingle = $parentConfig['appearance']['expandSingle'];
240  $jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
241  if ($parentConfig['foreign_unique']) {
242  $jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
243  }
244  $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
245  if ($parentConfig['appearance']['useSortable']) {
246  $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
247  $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
248  }
249  if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
250  $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
251  }
252 
253  return new JsonResponse($jsonArray);
254  }
255 
263  public function synchronizeLocalizeAction(ServerRequestInterface $request): ResponseInterface
264  {
265  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
266  $domObjectId = $ajaxArguments[0] ?? null;
267  $type = $ajaxArguments[1] ?? null;
268  $parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
269 
271  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
272  // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
273  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
274  $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
275  $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
276 
277  $jsonArray = [
278  'data' => '',
279  'stylesheetFiles' => [],
280  'scriptCall' => [],
281  ];
282  if ($type === 'localize' || $type === 'synchronize' || MathUtility::canBeInterpretedAsInteger($type)) {
283  // Parent, this table embeds the child table
284  $parent = $inlineStackProcessor->getStructureLevel(-1);
285  $parentFieldName = $parent['field'];
286 
287  $processedTca = $GLOBALS['TCA'][$parent['table']];
288  $processedTca['columns'][$parentFieldName]['config'] = $parentConfig;
289 
290  // Child, a record from this table should be rendered
291  $child = $inlineStackProcessor->getUnstableStructure();
292 
293  $formDataCompilerInputForParent = [
294  'vanillaUid' => (int)$parent['uid'],
295  'command' => 'edit',
296  'tableName' => $parent['table'],
297  'processedTca' => $processedTca,
298  'inlineFirstPid' => $inlineFirstPid,
299  'columnsToProcess' => [
300  $parentFieldName
301  ],
302  // @todo: still needed? NO!
303  'inlineStructure' => $inlineStackProcessor->getStructure(),
304  // Do not compile existing children, we don't need them now
305  'inlineCompileExistingChildren' => false,
306  ];
307  // Full TcaDatabaseRecord is required here to have the list of connected uids $oldItemList
309  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
311  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
312  $parentData = $formDataCompiler->compile($formDataCompilerInputForParent);
313  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
314  $parentLanguageField = $parentData['processedTca']['ctrl']['languageField'];
315  $parentLanguage = $parentData['databaseRow'][$parentLanguageField];
316  $oldItemList = $parentData['databaseRow'][$parentFieldName];
317 
318  // DataHandler cannot handle arrays as field value
319  if (is_array($parentLanguage)) {
320  $parentLanguage = implode(',', $parentLanguage);
321  }
322 
323  $cmd = [];
324  // Localize a single child element from default language of the parent element
326  $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = [
327  'field' => $parent['field'],
328  'language' => $parentLanguage,
329  'ids' => [$type],
330  ];
331  } else {
332  // Either localize or synchronize all child elements from default language of the parent element
333  $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = [
334  'field' => $parent['field'],
335  'language' => $parentLanguage,
336  'action' => $type,
337  ];
338  }
339 
341  $tce = GeneralUtility::makeInstance(DataHandler::class);
342  $tce->start([], $cmd);
343  $tce->process_cmdmap();
344 
345  $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parentFieldName];
346 
347  $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
348  $nameObjectForeignTable = $nameObject . '-' . $child['table'];
349 
350  $oldItems = $this->‪getInlineRelatedRecordsUidArray($oldItemList);
351  $newItems = $this->‪getInlineRelatedRecordsUidArray($newItemList);
352 
353  // Render error messages from DataHandler
354  $tce->printLogErrorMessages();
355  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
356  $messages = $flashMessageService->getMessageQueueByIdentifier()->getAllMessagesAndFlush();
357  if (!empty($messages)) {
358  foreach ($messages as $message) {
359  $jsonArray['messages'][] = [
360  'title' => $message->getTitle(),
361  'message' => $message->getMessage(),
362  'severity' => $message->getSeverity()
363  ];
364  if ($message->getSeverity() === ‪AbstractMessage::ERROR) {
365  $jsonArray['hasErrors'] = true;
366  }
367  }
368  }
369 
370  // Set the items that should be removed in the forms view:
371  $removedItems = array_diff($oldItems, $newItems);
372  foreach ($removedItems as $childUid) {
373  $jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $childUid) . ', {forceDirectRemoval: true});';
374  }
375 
376  $localizedItems = array_diff($newItems, $oldItems);
377  foreach ($localizedItems as $childUid) {
378  $childData = $this->‪compileChild($parentData, $parentFieldName, (int)$childUid, $inlineStackProcessor->getStructure());
379 
380  $childData['inlineParentUid'] = (int)$parent['uid'];
381  $childData['renderType'] = 'inlineRecordContainer';
382  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
383  $childResult = $nodeFactory->create($childData)->render();
384 
385  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
386 
387  // Get the name of the field used as foreign selector (if any):
388  $foreignSelector = isset($parentConfig['foreign_selector']) && $parentConfig['foreign_selector'] ? $parentConfig['foreign_selector'] : false;
389  $selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($childData['databaseRow'][$foreignSelector]) : 'null';
390  if (is_array($selectedValue)) {
391  $selectedValue = $selectedValue[0];
392  }
393  $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($childUid) . ', null, ' . $selectedValue . ');';
394  // Remove possible virtual records in the form which showed that a child records could be localized:
395  $transOrigPointerFieldName = $childData['processedTca']['ctrl']['transOrigPointerField'];
396  if (isset($childData['databaseRow'][$transOrigPointerFieldName]) && $childData['databaseRow'][$transOrigPointerFieldName]) {
397  $transOrigPointerField = $childData['databaseRow'][$transOrigPointerFieldName];
398  if (is_array($transOrigPointerField)) {
399  $transOrigPointerField = $transOrigPointerField[0];
400  }
401  $jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $transOrigPointerField . '_div') . ');';
402  }
403  }
404  // Tell JS to add new HTML of one or multiple (localize all) records to DOM
405  if (!empty($jsonArray['data'])) {
406  $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records')
407  . ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable)
408  . ', json.data);';
409  }
410  }
411  return new JsonResponse($jsonArray);
412  }
413 
420  public function ‪expandOrCollapseAction(ServerRequestInterface $request): ResponseInterface
421  {
422  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
423  $domObjectId = $ajaxArguments[0];
424 
426  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
427  // Parse the DOM identifier (string), add the levels to the structure stack (array), don't load TCA config
428  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
429  $expand = $ajaxArguments[1];
430  $collapse = $ajaxArguments[2];
431 
432  $backendUser = $this->‪getBackendUserAuthentication();
433  // The current table - for this table we should add/import records
434  $currentTable = $inlineStackProcessor->getUnstableStructure();
435  $currentTable = $currentTable['table'];
436  // The top parent table - this table embeds the current table
437  $top = $inlineStackProcessor->getStructureLevel(0);
438  $topTable = $top['table'];
439  $topUid = $top['uid'];
440  $inlineView = $this->‪getInlineExpandCollapseStateArray();
441  // Only do some action if the top record and the current record were saved before
443  $expandUids = GeneralUtility::trimExplode(',', $expand);
444  $collapseUids = GeneralUtility::trimExplode(',', $collapse);
445  // Set records to be expanded
446  foreach ($expandUids as $uid) {
447  $inlineView[$topTable][$topUid][$currentTable][] = $uid;
448  }
449  // Set records to be collapsed
450  foreach ($collapseUids as $uid) {
451  $inlineView[$topTable][$topUid][$currentTable] = $this->‪removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
452  }
453  // Save states back to database
454  if (is_array($inlineView[$topTable][$topUid][$currentTable])) {
455  $inlineView[$topTable][$topUid][$currentTable] = array_unique($inlineView[$topTable][$topUid][$currentTable]);
456  $backendUser->uc['inlineView'] = serialize($inlineView);
457  $backendUser->writeUC();
458  }
459  }
460  return (new ‪JsonResponse())->setPayload([]);
461  }
462 
475  protected function ‪compileChild(array $parentData, $parentFieldName, $childUid, array $inlineStructure)
476  {
477  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
478 
480  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
481  $inlineStackProcessor->initializeByGivenStructure($inlineStructure);
482  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
483 
484  // @todo: do not use stack processor here ...
485  $child = $inlineStackProcessor->getUnstableStructure();
486  $childTableName = $child['table'];
487 
489  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
491  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
492  $formDataCompilerInput = [
493  'command' => 'edit',
494  'tableName' => $childTableName,
495  'vanillaUid' => (int)$childUid,
496  'returnUrl' => $parentData['returnUrl'],
497  'isInlineChild' => true,
498  'inlineStructure' => $inlineStructure,
499  'inlineFirstPid' => $parentData['inlineFirstPid'],
500  'inlineParentConfig' => $parentConfig,
501  'isInlineAjaxOpeningContext' => true,
502 
503  // values of the current parent element
504  // it is always a string either an id or new...
505  'inlineParentUid' => $parentData['databaseRow']['uid'] ?? $parentData['uid'],
506  'inlineParentTableName' => $parentData['tableName'],
507  'inlineParentFieldName' => $parentFieldName,
508 
509  // values of the top most parent element set on first level and not overridden on following levels
510  'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
511  'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
512  'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
513  ];
514  // For foreign_selector with useCombination $mainChild is the mm record
515  // and $combinationChild is the child-child. For "normal" relations, $mainChild
516  // is just the normal child record and $combinationChild is empty.
517  $mainChild = $formDataCompiler->compile($formDataCompilerInput);
518  if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
519  // This kicks in if opening an existing mainChild that has a child-child set
520  $mainChild['combinationChild'] = $this->‪compileChildChild($mainChild, $parentConfig, $inlineStructure);
521  }
522  return $mainChild;
523  }
524 
534  protected function ‪compileChildChild(array $child, array $parentConfig, array $inlineStructure)
535  {
536  // foreign_selector on intermediate is probably type=select, so data provider of this table resolved that to the uid already
537  $childChildUid = $child['databaseRow'][$parentConfig['foreign_selector']][0];
538  // child-child table name is set in child tca "the selector field" foreign_table
539  $childChildTableName = $child['processedTca']['columns'][$parentConfig['foreign_selector']]['config']['foreign_table'];
541  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
543  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
544  $formDataCompilerInput = [
545  'command' => 'edit',
546  'tableName' => $childChildTableName,
547  'vanillaUid' => (int)$childChildUid,
548  'isInlineChild' => true,
549  'isInlineAjaxOpeningContext' => true,
550  // @todo: this is the wrong inline structure, isn't it? Shouldn't contain it the part from child child, too?
551  'inlineStructure' => $inlineStructure,
552  'inlineFirstPid' => $child['inlineFirstPid'],
553  // values of the top most parent element set on first level and not overridden on following levels
554  'inlineTopMostParentUid' => $child['inlineTopMostParentUid'],
555  'inlineTopMostParentTableName' => $child['inlineTopMostParentTableName'],
556  'inlineTopMostParentFieldName' => $child['inlineTopMostParentFieldName'],
557  ];
558  return $formDataCompiler->compile($formDataCompilerInput);
559  }
560 
569  protected function ‪mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
570  {
571  $jsonResult['data'] .= $childResult['html'];
572  $jsonResult['stylesheetFiles'] = [];
573  foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
574  $jsonResult['stylesheetFiles'][] = $this->‪getRelativePathToStylesheetFile($stylesheetFile);
575  }
576  if (!empty($childResult['inlineData'])) {
577  $jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
578  }
579  if (!empty($childResult['additionalJavaScriptSubmit'])) {
580  $additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
581  $additionalJavaScriptSubmit = str_replace([CR, LF], '', $additionalJavaScriptSubmit);
582  $jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
583  }
584  foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
585  $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
586  }
587  if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
588  $labels = [];
589  foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
591  $labels,
592  $this->‪getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
593  );
594  }
595  $javaScriptCode = [];
596  $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
597  $javaScriptCode[] = ' TYPO3.lang = {}';
598  $javaScriptCode[] = '}';
599  $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
600  $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
601  $javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
602  $javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
603  $javaScriptCode[] = ' }';
604  $javaScriptCode[] = '}';
605 
606  $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
607  }
608  $requireJsModule = $this->‪createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
609  $jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']);
610 
611  return $jsonResult;
612  }
613 
622  protected function ‪getInlineRelatedRecordsUidArray($itemList)
623  {
624  $itemArray = GeneralUtility::trimExplode(',', $itemList, true);
625  // Perform modification of the selected items array:
626  foreach ($itemArray as &$value) {
627  $parts = explode('|', $value, 2);
628  $value = $parts[0];
629  }
630  unset($value);
631  return $itemArray;
632  }
633 
641  protected function ‪getInlineExpandCollapseStateArrayForTableUid($table, $uid)
642  {
643  $inlineView = $this->‪getInlineExpandCollapseStateArray();
644  $result = [];
646  if (!empty($inlineView[$table][$uid])) {
647  $result = $inlineView[$table][$uid];
648  }
649  }
650  return $result;
651  }
652 
659  {
660  $backendUser = $this->‪getBackendUserAuthentication();
661  if (!$this->‪backendUserHasUcInlineView($backendUser)) {
662  return [];
663  }
664 
665  $inlineView = unserialize($backendUser->uc['inlineView'], ['allowed_classes' => false]);
666  if (!is_array($inlineView)) {
667  $inlineView = [];
668  }
669 
670  return $inlineView;
671  }
672 
681  {
682  return !empty($backendUser->uc['inlineView']);
683  }
684 
693  protected function ‪removeFromArray($needle, $haystack, $strict = false)
694  {
695  $pos = array_search($needle, $haystack, $strict);
696  if ($pos !== false) {
697  unset($haystack[$pos]);
698  }
699  return $haystack;
700  }
701 
708  protected function ‪getErrorMessageForAJAX($message)
709  {
710  return [
711  'data' => $message,
712  'scriptCall' => [
713  'alert("' . $message . '");'
714  ],
715  ];
716  }
717 
724  protected function ‪getInlineFirstPidFromDomObjectId($domObjectId)
725  {
726  // Substitute FlexForm addition and make parsing a bit easier
727  $domObjectId = str_replace('---', ':', $domObjectId);
728  // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
729  $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
730  if (preg_match($pattern, $domObjectId, $match)) {
731  return $match[1];
732  }
733  return null;
734  }
735 
744  protected function ‪extractSignedParentConfigFromRequest(string $contextString): array
745  {
746  if ($contextString === '') {
747  throw new \RuntimeException('Empty context string given', 1489751361);
748  }
749  $context = json_decode($contextString, true);
750  if (empty($context['config'])) {
751  throw new \RuntimeException('Empty context config section given', 1489751362);
752  }
753  if (!hash_equals(GeneralUtility::hmac((string)$context['config'], 'InlineContext'), (string)$context['hmac'])) {
754  throw new \RuntimeException('Hash does not validate', 1489751363);
755  }
756  return json_decode($context['config'], true);
757  }
758 
762  protected function ‪getBackendUserAuthentication()
763  {
764  return ‪$GLOBALS['BE_USER'];
765  }
766 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:81
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\expandOrCollapseAction
‪ResponseInterface expandOrCollapseAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:420
‪TYPO3\CMS\Core\Messaging\AbstractMessage
Definition: AbstractMessage.php:24
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController
Definition: FormInlineAjaxController.php:37
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\compileChildChild
‪array compileChildChild(array $child, array $parentConfig, array $inlineStructure)
Definition: FormInlineAjaxController.php:534
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\backendUserHasUcInlineView
‪bool backendUserHasUcInlineView(BackendUserAuthentication $backendUser)
Definition: FormInlineAjaxController.php:680
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getErrorMessageForAJAX
‪array getErrorMessageForAJAX($message)
Definition: FormInlineAjaxController.php:708
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\createAction
‪ResponseInterface createAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:44
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:614
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\extractSignedParentConfigFromRequest
‪array extractSignedParentConfigFromRequest(string $contextString)
Definition: FormInlineAjaxController.php:744
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineExpandCollapseStateArrayForTableUid
‪array getInlineExpandCollapseStateArrayForTableUid($table, $uid)
Definition: FormInlineAjaxController.php:641
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\detailsAction
‪ResponseInterface detailsAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:180
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\createExecutableStringRepresentationOfRegisteredRequireJsModules
‪array createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result)
Definition: AbstractFormEngineAjaxController.php:39
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\getRelativePathToStylesheetFile
‪string getRelativePathToStylesheetFile(string $stylesheetFile)
Definition: AbstractFormEngineAjaxController.php:80
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getBackendUserAuthentication
‪BackendUserAuthentication getBackendUserAuthentication()
Definition: FormInlineAjaxController.php:762
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineFirstPidFromDomObjectId
‪int null getInlineFirstPidFromDomObjectId($domObjectId)
Definition: FormInlineAjaxController.php:724
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\compileChild
‪array compileChild(array $parentData, $parentFieldName, $childUid, array $inlineStructure)
Definition: FormInlineAjaxController.php:475
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\getLabelsFromLocalizationFile
‪array getLabelsFromLocalizationFile($file)
Definition: AbstractFormEngineAjaxController.php:99
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineExpandCollapseStateArray
‪array getInlineExpandCollapseStateArray()
Definition: FormInlineAjaxController.php:658
‪TYPO3\CMS\Backend\Form\NodeFactory
Definition: NodeFactory.php:36
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\removeFromArray
‪array removeFromArray($needle, $haystack, $strict=false)
Definition: FormInlineAjaxController.php:693
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:23
‪TYPO3\CMS\Core\Http\JsonResponse
Definition: JsonResponse.php:25
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineRelatedRecordsUidArray
‪array getInlineRelatedRecordsUidArray($itemList)
Definition: FormInlineAjaxController.php:622
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\mergeChildResultIntoJsonResult
‪array mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
Definition: FormInlineAjaxController.php:569
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController
Definition: AbstractFormEngineAjaxController.php:31
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:29
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Backend\Form\FormDataCompiler
Definition: FormDataCompiler.php:24
‪TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord
Definition: TcaDatabaseRecord.php:24
‪TYPO3\CMS\Backend\Controller
Definition: AbstractFormEngineAjaxController.php:3
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:25
‪TYPO3\CMS\Core\Messaging\AbstractMessage\ERROR
‪const ERROR
Definition: AbstractMessage.php:29