‪TYPO3CMS  ‪main
FormInlineAjaxController.php
Go to the documentation of this file.
1 <?php
2 
3 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 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
32 use TYPO3\CMS\Core\Page\JavaScriptItems;
37 
41 #[AsController]
43 {
44  public function ‪__construct(
45  private readonly ‪FormDataCompiler $formDataCompiler,
46  private readonly ‪HashService $hashService,
47  ) {}
48 
52  public function ‪createAction(ServerRequestInterface $request): ResponseInterface
53  {
54  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
55  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
56 
57  $domObjectId = $ajaxArguments[0] ?? '';
58  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
59  if (!‪MathUtility::canBeInterpretedAsInteger($inlineFirstPid)
60  && !str_starts_with((string)$inlineFirstPid, 'NEW')
61  ) {
62  throw new \RuntimeException(
63  'inlineFirstPid should either be an integer or a "NEW..." string',
64  1521220491
65  );
66  }
67  $childChildUid = null;
68  if (isset($ajaxArguments[1]) && ‪MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
69  $childChildUid = (int)$ajaxArguments[1];
70  }
71 
72  // Parse the DOM identifier, add the levels to the structure stack
73  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
74  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
75  $inlineStackProcessor->setAjaxConfiguration($parentConfig);
76  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
77 
78  // Parent, this table embeds the child table
79  $parent = $inlineStackProcessor->getStructureLevel(-1);
80 
81  // Child, a record from this table should be rendered
82  $child = $inlineStackProcessor->getUnstableStructure();
83  if (isset($child['uid']) && ‪MathUtility::canBeInterpretedAsInteger($child['uid'])) {
84  // If uid comes in, it is the id of the record neighbor record "create after"
85  $childVanillaUid = -1 * abs((int)$child['uid']);
86  } else {
87  // Else inline first Pid is the storage pid of new inline records
88  $childVanillaUid = $inlineFirstPid;
89  }
90 
91  $childTableName = $parentConfig['foreign_table'];
92 
93  $formDataCompilerInput = [
94  'request' => $request,
95  'command' => 'new',
96  'tableName' => $childTableName,
97  'vanillaUid' => $childVanillaUid,
98  'isInlineChild' => true,
99  'inlineStructure' => $inlineStackProcessor->getStructure(),
100  'inlineFirstPid' => $inlineFirstPid,
101  'inlineParentUid' => $parent['uid'],
102  'inlineParentTableName' => $parent['table'],
103  'inlineParentFieldName' => $parent['field'],
104  'inlineParentConfig' => $parentConfig,
105  'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
106  'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
107  'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
108  ];
109  if ($childChildUid) {
110  $formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
111  }
112  $childData = $this->formDataCompiler->compile($formDataCompilerInput, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
113 
114  if (($parentConfig['foreign_selector'] ?? false) && ($parentConfig['appearance']['useCombination'] ?? false)) {
115  // We have a foreign_selector. So, we just created a new record on an intermediate table in $childData.
116  // Now, if a valid id is given as second ajax parameter, the intermediate row should be connected to an
117  // existing record of the child-child table specified by the given uid. If there is no such id, user
118  // clicked on "created new" and a new child-child should be created, too.
119  if ($childChildUid) {
120  // Fetch existing child child
121  $childData['databaseRow'][$parentConfig['foreign_selector']] = [
122  $childChildUid,
123  ];
124  $childData['combinationChild'] = $this->‪compileChildChild($request, $childData, $parentConfig, $inlineStackProcessor->getStructure());
125  } else {
126  $formDataCompilerInput = [
127  'request' => $request,
128  'command' => 'new',
129  'tableName' => $this->‪getChildChildTableName($parentConfig['foreign_selector'], $childData),
130  'vanillaUid' => $inlineFirstPid,
131  'isInlineChild' => true,
132  'isInlineAjaxOpeningContext' => true,
133  'inlineStructure' => $inlineStackProcessor->getStructure(),
134  'inlineFirstPid' => $inlineFirstPid,
135  ];
136  $childData['combinationChild'] = $this->formDataCompiler->compile($formDataCompilerInput, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
137  }
138  }
139 
140  $childData['inlineParentUid'] = $parent['uid'];
141  $childData['renderType'] = 'inlineRecordContainer';
142  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
143  $childResult = $nodeFactory->create($childData)->render();
144 
145  $jsonArray = [
146  'data' => '',
147  'stylesheetFiles' => [],
148  'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
149  'compilerInput' => [
150  'uid' => $childData['databaseRow']['uid'],
151  'childChildUid' => $childChildUid,
152  'parentConfig' => $parentConfig,
153  ],
154  ];
155 
156  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
157 
158  return new ‪JsonResponse($jsonArray);
159  }
160 
164  public function ‪detailsAction(ServerRequestInterface $request): ResponseInterface
165  {
166  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
167 
168  $domObjectId = $ajaxArguments[0] ?? '';
169  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
170  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
171 
172  // Parse the DOM identifier, add the levels to the structure stack
173  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
174  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
175  $inlineStackProcessor->setAjaxConfiguration($parentConfig);
176 
177  // Parent, this table embeds the child table
178  $parent = $inlineStackProcessor->getStructureLevel(-1);
179  $parentFieldName = $parent['field'];
180 
181  // Set flag in config so that only the fields are rendered
182  // @todo: Solve differently / rename / whatever
183  $parentConfig['renderFieldsOnly'] = true;
184 
185  $parentData = [
186  'processedTca' => [
187  'columns' => [
188  $parentFieldName => [
189  'config' => $parentConfig,
190  ],
191  ],
192  ],
193  'uid' => $parent['uid'],
194  'tableName' => $parent['table'],
195  'inlineFirstPid' => $inlineFirstPid,
196  // Hand over given original return url to compile stack. Needed if inline children compile links to
197  // another view (eg. edit metadata in a nested inline situation like news with inline content element image),
198  // so the back link is still the link from the original request. See issue #82525. This is additionally
199  // given down in TcaInline data provider to compiled children data.
200  'returnUrl' => $parentConfig['originalReturnUrl'],
201  ];
202 
203  // Child, a record from this table should be rendered
204  $child = $inlineStackProcessor->getUnstableStructure();
205 
206  $childData = $this->‪compileChild($request, $parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
207 
208  $childData['inlineParentUid'] = (int)$parent['uid'];
209  $childData['renderType'] = 'inlineRecordContainer';
210  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
211  $childResult = $nodeFactory->create($childData)->render();
212 
213  $jsonArray = [
214  'data' => '',
215  'stylesheetFiles' => [],
216  'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
217  ];
218 
219  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
220 
221  return new ‪JsonResponse($jsonArray);
222  }
223 
231  public function ‪synchronizeLocalizeAction(ServerRequestInterface $request): ResponseInterface
232  {
233  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
234  $domObjectId = $ajaxArguments[0] ?? '';
235  $type = $ajaxArguments[1] ?? null;
236  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
237 
238  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
239  // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
240  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
241  $inlineStackProcessor->setAjaxConfiguration($parentConfig);
242  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
243 
244  $jsonArray = [
245  'data' => '',
246  'stylesheetFiles' => [],
247  'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
248  'compilerInput' => [
249  'localize' => [],
250  ],
251  ];
252  if ($type === 'localize' || $type === 'synchronize' || ‪MathUtility::canBeInterpretedAsInteger($type)) {
253  // Parent, this table embeds the child table
254  $parent = $inlineStackProcessor->getStructureLevel(-1);
255  $parentFieldName = $parent['field'];
256 
257  $processedTca = ‪$GLOBALS['TCA'][$parent['table']];
258  $processedTca['columns'][$parentFieldName]['config'] = $parentConfig;
259 
260  $formDataCompilerInputForParent = [
261  'request' => $request,
262  'vanillaUid' => (int)$parent['uid'],
263  'command' => 'edit',
264  'tableName' => $parent['table'],
265  'processedTca' => $processedTca,
266  'inlineFirstPid' => $inlineFirstPid,
267  'columnsToProcess' => [
268  $parentFieldName,
269  ],
270  // @todo: still needed? NO!
271  'inlineStructure' => $inlineStackProcessor->getStructure(),
272  // Do not compile existing children, we don't need them now
273  'inlineCompileExistingChildren' => false,
274  ];
275  // Full TcaDatabaseRecord is required here to have the list of connected uids $oldItemList
276  $parentData = $this->formDataCompiler->compile($formDataCompilerInputForParent, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
277  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
278  $parentLanguageField = $parentData['processedTca']['ctrl']['languageField'];
279  $parentLanguage = $parentData['databaseRow'][$parentLanguageField];
280  $oldItemList = $parentData['databaseRow'][$parentFieldName];
281 
282  // DataHandler cannot handle arrays as field value
283  if (is_array($parentLanguage)) {
284  $parentLanguage = implode(',', $parentLanguage);
285  }
286 
287  $cmd = [];
288  // Localize a single child element from default language of the parent element
290  $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = [
291  'field' => $parent['field'],
292  'language' => $parentLanguage,
293  'ids' => [$type],
294  ];
295  } else {
296  // Either localize or synchronize all child elements from default language of the parent element
297  $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = [
298  'field' => $parent['field'],
299  'language' => $parentLanguage,
300  'action' => $type,
301  ];
302  }
303 
304  $tce = GeneralUtility::makeInstance(DataHandler::class);
305  $tce->start([], $cmd);
306  $tce->process_cmdmap();
307 
308  $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parentFieldName];
309 
310  $oldItems = $this->‪getInlineRelatedRecordsUidArray($oldItemList);
311  $newItems = $this->‪getInlineRelatedRecordsUidArray($newItemList);
312 
313  // Render error messages from DataHandler
314  $tce->printLogErrorMessages();
315  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
316  $messages = $flashMessageService->getMessageQueueByIdentifier()->getAllMessagesAndFlush();
317  if (!empty($messages)) {
318  foreach ($messages as $message) {
319  $jsonArray['messages'][] = [
320  'title' => $message->getTitle(),
321  'message' => $message->getMessage(),
322  'severity' => $message->getSeverity(),
323  ];
324  if ($message->getSeverity() === ContextualFeedbackSeverity::ERROR) {
325  $jsonArray['hasErrors'] = true;
326  }
327  }
328  }
329 
330  // Set the items that should be removed in the forms view:
331  $removedItems = array_diff($oldItems, $newItems);
332  $jsonArray['compilerInput']['delete'] = $removedItems;
333 
334  $localizedItems = array_diff($newItems, $oldItems);
335  foreach ($localizedItems as $i => $childUid) {
336  $childData = $this->‪compileChild($request, $parentData, $parentFieldName, (int)$childUid, $inlineStackProcessor->getStructure());
337 
338  $childData['inlineParentUid'] = (int)$parent['uid'];
339  $childData['renderType'] = 'inlineRecordContainer';
340  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
341  $childResult = $nodeFactory->create($childData)->render();
342 
343  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
344 
345  // Get the name of the field used as foreign selector (if any):
346  $foreignSelector = isset($parentConfig['foreign_selector']) && $parentConfig['foreign_selector'] ? $parentConfig['foreign_selector'] : false;
347  $selectedValue = $foreignSelector ? $childData['databaseRow'][$foreignSelector] : null;
348  if (is_array($selectedValue)) {
349  $selectedValue = $selectedValue[0];
350  }
351 
352  $jsonArray['compilerInput']['localize'][$i] = [
353  'uid' => $childUid,
354  'selectedValue' => $selectedValue,
355  ];
356 
357  // Remove possible virtual records in the form which showed that a child records could be localized:
358  $transOrigPointerFieldName = $childData['processedTca']['ctrl']['transOrigPointerField'];
359  if (isset($childData['databaseRow'][$transOrigPointerFieldName]) && $childData['databaseRow'][$transOrigPointerFieldName]) {
360  $transOrigPointerFieldValue = $childData['databaseRow'][$transOrigPointerFieldName];
361  if (is_array($transOrigPointerFieldValue)) {
362  $transOrigPointerFieldValue = $transOrigPointerFieldValue[0];
363  if (is_array($transOrigPointerFieldValue) && ($transOrigPointerFieldValue['uid'] ?? false)) {
364  // With nested inline containers (eg. fal sys_file_reference), row[l10n_parent][0] is sometimes
365  // a table / row combination again. See tx_styleguide_file file_5. If this happens we
366  // pick the uid field from the array ... Basically, we need the uid of the 'default language' record,
367  // since this is used in JS to locate and remove the 'shadowed' container.
368  // @todo: Find out if this is really necessary that sometimes ['databaseRow']['l10n_parent'][0]
369  // is resolved to a direct uid, and sometimes it's an array with items. Could this be harmonized?
370  $transOrigPointerFieldValue = $transOrigPointerFieldValue['uid'];
371  }
372  }
373  $jsonArray['compilerInput']['localize'][$i]['remove'] = $transOrigPointerFieldValue;
374  }
375  }
376  }
377  return new ‪JsonResponse($jsonArray);
378  }
379 
386  public function ‪expandOrCollapseAction(ServerRequestInterface $request): ResponseInterface
387  {
388  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
389  [$domObjectId, $expand, $collapse] = $ajaxArguments;
390 
391  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
392  // Parse the DOM identifier (string), add the levels to the structure stack (array), don't load TCA config
393  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
394 
395  $backendUser = $this->‪getBackendUserAuthentication();
396  // The current table - for this table we should add/import records
397  $currentTable = $inlineStackProcessor->getUnstableStructure();
398  $currentTable = $currentTable['table'];
399  // The top parent table - this table embeds the current table
400  $top = $inlineStackProcessor->getStructureLevel(0);
401  $topTable = $top['table'];
402  $topUid = $top['uid'];
403  $inlineView = $this->‪getInlineExpandCollapseStateArray();
404  // Only do some action if the top record and the current record were saved before
406  $expandUids = ‪GeneralUtility::trimExplode(',', $expand);
407  $collapseUids = ‪GeneralUtility::trimExplode(',', $collapse);
408  // Set records to be expanded
409  foreach ($expandUids as ‪$uid) {
410  $inlineView[$topTable][$topUid][$currentTable][] = ‪$uid;
411  }
412  // Set records to be collapsed
413  foreach ($collapseUids as ‪$uid) {
414  $inlineView[$topTable][$topUid][$currentTable] = $this->‪removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
415  }
416  // Save states back to database
417  if (is_array($inlineView[$topTable][$topUid][$currentTable])) {
418  $inlineView[$topTable][$topUid][$currentTable] = array_unique($inlineView[$topTable][$topUid][$currentTable]);
419  $backendUser->uc['inlineView'] = json_encode($inlineView);
420  $backendUser->writeUC();
421  }
422  }
423  return new ‪JsonResponse([]);
424  }
425 
438  protected function ‪compileChild(ServerRequestInterface $request, array $parentData, $parentFieldName, $childUid, array $inlineStructure)
439  {
440  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
441 
442  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
443  $inlineStackProcessor->initializeByGivenStructure($inlineStructure);
444  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
445 
446  // @todo: do not use stack processor here ...
447  $child = $inlineStackProcessor->getUnstableStructure();
448  $childTableName = $child['table'];
449 
450  $formDataCompilerInput = [
451  'request' => $request,
452  'command' => 'edit',
453  'tableName' => $childTableName,
454  'vanillaUid' => (int)$childUid,
455  'returnUrl' => $parentData['returnUrl'],
456  'isInlineChild' => true,
457  'inlineStructure' => $inlineStructure,
458  'inlineFirstPid' => $parentData['inlineFirstPid'],
459  'inlineParentConfig' => $parentConfig,
460  'isInlineAjaxOpeningContext' => true,
461 
462  // values of the current parent element
463  // it is always a string either an id or new...
464  'inlineParentUid' => $parentData['databaseRow']['uid'] ?? $parentData['uid'],
465  'inlineParentTableName' => $parentData['tableName'],
466  'inlineParentFieldName' => $parentFieldName,
467 
468  // values of the top most parent element set on first level and not overridden on following levels
469  'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
470  'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
471  'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
472  ];
473  // For foreign_selector with useCombination $mainChild is the mm record
474  // and $combinationChild is the child-child. For "normal" relations, $mainChild
475  // is just the normal child record and $combinationChild is empty.
476  $mainChild = $this->formDataCompiler->compile($formDataCompilerInput, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
477  if (($parentConfig['foreign_selector'] ?? false) && ($parentConfig['appearance']['useCombination'] ?? false)) {
478  // This kicks in if opening an existing mainChild that has a child-child set
479  $mainChild['combinationChild'] = $this->‪compileChildChild($request, $mainChild, $parentConfig, $inlineStructure);
480  }
481  return $mainChild;
482  }
483 
493  protected function ‪compileChildChild(ServerRequestInterface $request, array $child, array $parentConfig, array $inlineStructure)
494  {
495  // foreign_selector on intermediate is probably type=select, so data provider of this table resolved that to the uid already
496  $childChildUid = $child['databaseRow'][$parentConfig['foreign_selector']][0];
497  $formDataCompilerInput = [
498  'request' => $request,
499  'command' => 'edit',
500  'tableName' => $this->‪getChildChildTableName($parentConfig['foreign_selector'] ?? '', $child),
501  'vanillaUid' => (int)$childChildUid,
502  'isInlineChild' => true,
503  'isInlineAjaxOpeningContext' => true,
504  // @todo: this is the wrong inline structure, isn't it? Shouldn't contain it the part from child child, too?
505  'inlineStructure' => $inlineStructure,
506  'inlineFirstPid' => $child['inlineFirstPid'],
507  // values of the top most parent element set on first level and not overridden on following levels
508  'inlineTopMostParentUid' => $child['inlineTopMostParentUid'],
509  'inlineTopMostParentTableName' => $child['inlineTopMostParentTableName'],
510  'inlineTopMostParentFieldName' => $child['inlineTopMostParentFieldName'],
511  ];
512  return $this->formDataCompiler->compile($formDataCompilerInput, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
513  }
514 
523  protected function ‪mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
524  {
526  $scriptItems = $jsonResult['scriptItems'];
527 
528  $jsonResult['data'] .= $childResult['html'];
529  $jsonResult['stylesheetFiles'] = [];
530  foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
531  $jsonResult['stylesheetFiles'][] = $this->‪getRelativePathToStylesheetFile($stylesheetFile);
532  }
533  if (!empty($childResult['inlineData'])) {
534  $jsonResult['inlineData'] = $childResult['inlineData'];
535  }
536  if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
537  $labels = [];
538  foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
539  ArrayUtility::mergeRecursiveWithOverrule(
540  $labels,
541  $this->‪getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
542  );
543  }
544  $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]);
545  }
546  $this->‪addJavaScriptModulesToJavaScriptItems($childResult['javaScriptModules'] ?? [], $scriptItems);
547 
548  return $jsonResult;
549  }
550 
559  protected function ‪getInlineRelatedRecordsUidArray($itemList)
560  {
561  $itemArray = ‪GeneralUtility::trimExplode(',', $itemList, true);
562  // Perform modification of the selected items array:
563  foreach ($itemArray as &$value) {
564  $parts = explode('|', $value, 2);
565  $value = $parts[0];
566  }
567  unset($value);
568  return $itemArray;
569  }
570 
577  {
578  $backendUser = $this->‪getBackendUserAuthentication();
579  if (!$this->‪backendUserHasUcInlineView($backendUser)) {
580  return [];
581  }
582 
583  $inlineView = json_decode($backendUser->uc['inlineView'], true);
584  if (!is_array($inlineView)) {
585  $inlineView = [];
586  }
587 
588  return $inlineView;
589  }
590 
598  {
599  return !empty($backendUser->uc['inlineView']);
600  }
601 
610  protected function ‪removeFromArray($needle, $haystack, $strict = false)
611  {
612  $pos = array_search($needle, $haystack, $strict);
613  if ($pos !== false) {
614  unset($haystack[$pos]);
615  }
616  return $haystack;
617  }
618 
625  protected function ‪getInlineFirstPidFromDomObjectId(string $domObjectId)
626  {
627  // Substitute FlexForm addition and make parsing a bit easier
628  $domObjectId = str_replace('---', ':', $domObjectId);
629  // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
630  $pattern = '/^data-(.+?)-(.+)$/';
631  if (preg_match($pattern, $domObjectId, $match)) {
632  return $match[1];
633  }
634  return null;
635  }
636 
643  protected function ‪extractSignedParentConfigFromRequest(string $contextString): array
644  {
645  if ($contextString === '') {
646  throw new \RuntimeException('Empty context string given', 1489751361);
647  }
648  $context = json_decode($contextString, true);
649  if (empty($context['config'])) {
650  throw new \RuntimeException('Empty context config section given', 1489751362);
651  }
652  if (!hash_equals($this->hashService->hmac((string)$context['config'], 'InlineContext'), (string)$context['hmac'])) {
653  throw new \RuntimeException('Hash does not validate', 1489751363);
654  }
655  return json_decode($context['config'], true);
656  }
657 
662  protected function ‪getChildChildTableName(string $foreignSelector, array $childConfiguration): string
663  {
664  $config = $childConfiguration['processedTca']['columns'][$foreignSelector]['config'] ?? [];
665  $type = $config['type'] ?? '';
666 
667  return match ($type) {
668  'select' => $config['foreign_table'] ?? '',
669  'group' => ‪GeneralUtility::trimExplode(',', $config['allowed'] ?? '', true)[0] ?? '',
670  default => '',
671  };
672  }
673 
675  {
676  return ‪$GLOBALS['BE_USER'];
677  }
678 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:94
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\expandOrCollapseAction
‪ResponseInterface expandOrCollapseAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:386
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController
Definition: FormInlineAjaxController.php:43
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\backendUserHasUcInlineView
‪bool backendUserHasUcInlineView(BackendUserAuthentication $backendUser)
Definition: FormInlineAjaxController.php:597
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\detailsAction
‪detailsAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:164
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\compileChild
‪array compileChild(ServerRequestInterface $request, array $parentData, $parentFieldName, $childUid, array $inlineStructure)
Definition: FormInlineAjaxController.php:438
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\extractSignedParentConfigFromRequest
‪extractSignedParentConfigFromRequest(string $contextString)
Definition: FormInlineAjaxController.php:643
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\getLabelsFromLocalizationFile
‪array getLabelsFromLocalizationFile(string $file)
Definition: AbstractFormEngineAjaxController.php:80
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\synchronizeLocalizeAction
‪ResponseInterface synchronizeLocalizeAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:231
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\getRelativePathToStylesheetFile
‪string getRelativePathToStylesheetFile(string $stylesheetFile)
Definition: AbstractFormEngineAjaxController.php:60
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\createAction
‪createAction(ServerRequestInterface $request)
Definition: FormInlineAjaxController.php:52
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineExpandCollapseStateArray
‪array getInlineExpandCollapseStateArray()
Definition: FormInlineAjaxController.php:576
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\addJavaScriptModulesToJavaScriptItems
‪addJavaScriptModulesToJavaScriptItems(array $modules, JavaScriptItems $items)
Definition: AbstractFormEngineAjaxController.php:37
‪TYPO3\CMS\Backend\Form\NodeFactory
Definition: NodeFactory.php:40
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\removeFromArray
‪array removeFromArray($needle, $haystack, $strict=false)
Definition: FormInlineAjaxController.php:610
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\__construct
‪__construct(private readonly FormDataCompiler $formDataCompiler, private readonly HashService $hashService,)
Definition: FormInlineAjaxController.php:44
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Core\Http\JsonResponse
Definition: JsonResponse.php:28
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineRelatedRecordsUidArray
‪array getInlineRelatedRecordsUidArray($itemList)
Definition: FormInlineAjaxController.php:559
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\mergeChildResultIntoJsonResult
‪array mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
Definition: FormInlineAjaxController.php:523
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController
Definition: AbstractFormEngineAjaxController.php:36
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getBackendUserAuthentication
‪getBackendUserAuthentication()
Definition: FormInlineAjaxController.php:674
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:32
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getInlineFirstPidFromDomObjectId
‪int string null getInlineFirstPidFromDomObjectId(string $domObjectId)
Definition: FormInlineAjaxController.php:625
‪TYPO3\CMS\Backend\Form\FormDataCompiler
Definition: FormDataCompiler.php:26
‪TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord
Definition: TcaDatabaseRecord.php:25
‪TYPO3\CMS\Core\Crypto\HashService
Definition: HashService.php:27
‪TYPO3\CMS\Backend\Controller
Definition: AboutController.php:18
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\getChildChildTableName
‪getChildChildTableName(string $foreignSelector, array $childConfiguration)
Definition: FormInlineAjaxController.php:662
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Backend\Controller\FormInlineAjaxController\compileChildChild
‪array compileChildChild(ServerRequestInterface $request, array $child, array $parentConfig, array $inlineStructure)
Definition: FormInlineAjaxController.php:493