TYPO3  7.6
FormInlineAjaxController.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Controller;
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 
28 
33 {
41  public function createAction(ServerRequestInterface $request, ResponseInterface $response)
42  {
43  $ajaxArguments = isset($request->getParsedBody()['ajax']) ? $request->getParsedBody()['ajax'] : $request->getQueryParams()['ajax'];
44 
45  $domObjectId = $ajaxArguments[0];
46  $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
47  $childChildUid = null;
48  if (isset($ajaxArguments[1]) && MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
49  $childChildUid = (int)$ajaxArguments[1];
50  }
51 
52  // Parse the DOM identifier, add the levels to the structure stack
54  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
55  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
56  $inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
57 
58  // Parent, this table embeds the child table
59  $parent = $inlineStackProcessor->getStructureLevel(-1);
60  $parentFieldName = $parent['field'];
61 
62  if (MathUtility::canBeInterpretedAsInteger($parent['uid'])) {
63  $command = 'edit';
64  $vanillaUid = (int)$parent['uid'];
65  $databaseRow = [
66  // TcaInlineExpandCollapseState needs the record uid
67  'uid' => (int)$parent['uid'],
68  ];
69  } else {
70  $command = 'new';
71  $databaseRow = [];
72  $vanillaUid = (int)$inlineFirstPid;
73  }
74  $formDataCompilerInputForParent = [
75  'vanillaUid' => $vanillaUid,
76  'command' => $command,
77  'tableName' => $parent['table'],
78  'databaseRow' => $databaseRow,
79  'inlineFirstPid' => $inlineFirstPid,
80  'columnsToProcess' => [
81  $parentFieldName
82  ],
83  // Do not resolve existing children, we don't need them now
84  'inlineResolveExistingChildren' => false,
85  ];
87  $formDataGroup = GeneralUtility::makeInstance(InlineParentRecord::class);
89  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
90  $parentData = $formDataCompiler->compile($formDataCompilerInputForParent);
91  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
92 
93  // Child, a record from this table should be rendered
94  $child = $inlineStackProcessor->getUnstableStructure();
95  if (MathUtility::canBeInterpretedAsInteger($child['uid'])) {
96  // If uid comes in, it is the id of the record neighbor record "create after"
97  $childVanillaUid = -1 * abs((int)$child['uid']);
98  } else {
99  // Else inline first Pid is the storage pid of new inline records
100  $childVanillaUid = (int)$inlineFirstPid;
101  }
102 
103  if ($parentConfig['type'] === 'flex') {
104  $parentConfig = $this->getParentConfigFromFlexForm($parentConfig, $domObjectId);
105  }
106  $childTableName = $parentConfig['foreign_table'];
107 
109  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
111  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
112  $formDataCompilerInput = [
113  'command' => 'new',
114  'tableName' => $childTableName,
115  'vanillaUid' => $childVanillaUid,
116  'isInlineChild' => true,
117  'inlineStructure' => $inlineStackProcessor->getStructure(),
118  'inlineFirstPid' => $inlineFirstPid,
119  'inlineParentUid' => $parent['uid'],
120  'inlineParentTableName' => $parent['table'],
121  'inlineParentFieldName' => $parent['field'],
122  'inlineParentConfig' => $parentConfig,
123  ];
124  if ($childChildUid) {
125  $formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
126  }
127  $childData = $formDataCompiler->compile($formDataCompilerInput);
128 
129  // Set language of new child record to the language of the parent record:
130  // @todo: To my understanding, the below case can't happen: With localizationMode select, lang overlays
131  // @todo: of children are only created with the "synchronize" button that will trigger a different ajax action.
132  // @todo: The edge case of new page overlay together with localized media field, this code won't kick in either.
144  if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
145  // We have a foreign_selector. So, we just created a new record on an intermediate table in $mainChild.
146  // Now, if a valid id is given as second ajax parameter, the intermediate row should be connected to an
147  // existing record of the child-child table specified by the given uid. If there is no such id, user
148  // clicked on "created new" and a new child-child should be created, too.
149  if ($childChildUid) {
150  // Fetch existing child child
151  $childData['databaseRow'][$parentConfig['foreign_selector']] = [
152  $childChildUid,
153  ];
154  $childData['combinationChild'] = $this->compileCombinationChild($childData, $parentConfig);
155  } else {
157  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
159  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
160  $formDataCompilerInput = [
161  'command' => 'new',
162  'tableName' => $childData['processedTca']['columns'][$parentConfig['foreign_selector']]['config']['foreign_table'],
163  'vanillaUid' => (int)$inlineFirstPid,
164  'inlineFirstPid' => (int)$inlineFirstPid,
165  ];
166  $childData['combinationChild'] = $formDataCompiler->compile($formDataCompilerInput);
167  }
168  }
169 
170  $childData['inlineParentUid'] = (int)$parent['uid'];
171  $childData['renderType'] = 'inlineRecordContainer';
172  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
173  $childResult = $nodeFactory->create($childData)->render();
174 
175  $jsonArray = [
176  'data' => '',
177  'stylesheetFiles' => [],
178  'scriptCall' => [],
179  ];
180 
181  // The HTML-object-id's prefix of the dynamically created record
182  $objectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
183  $objectPrefix = $objectName . '-' . $child['table'];
184  $objectId = $objectPrefix . '-' . $childData['databaseRow']['uid'];
185  $expandSingle = $parentConfig['appearance']['expandSingle'];
186  if (!$child['uid']) {
187  $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
188  $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',null,' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
189  } else {
190  $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
191  $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',' . GeneralUtility::quoteJSvalue($child['uid']) . ',' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
192  }
193  $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
194  if ($parentConfig['appearance']['useSortable']) {
195  $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
196  $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
197  }
198  if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
199  $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ');';
200  }
201  // Fade out and fade in the new record in the browser view to catch the user's eye
202  $jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
203 
204  $response->getBody()->write(json_encode($jsonArray));
205 
206  return $response;
207  }
208 
216  public function detailsAction(ServerRequestInterface $request, ResponseInterface $response)
217  {
218  $ajaxArguments = isset($request->getParsedBody()['ajax']) ? $request->getParsedBody()['ajax'] : $request->getQueryParams()['ajax'];
219 
220  $domObjectId = $ajaxArguments[0];
221  $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
222 
223  // Parse the DOM identifier, add the levels to the structure stack
225  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
226  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
227  $inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
228 
229  // Parent, this table embeds the child table
230  $parent = $inlineStackProcessor->getStructureLevel(-1);
231  $parentFieldName = $parent['field'];
232 
233  $formDataCompilerInputForParent = [
234  'vanillaUid' => (int)$parent['uid'],
235  'command' => 'edit',
236  'tableName' => $parent['table'],
237  'databaseRow' => [
238  // TcaInlineExpandCollapseState needs this
239  'uid' => (int)$parent['uid'],
240  ],
241  'inlineFirstPid' => $inlineFirstPid,
242  'columnsToProcess' => [
243  $parentFieldName
244  ],
245  // @todo: still needed?
246  'inlineStructure' => $inlineStackProcessor->getStructure(),
247  // Do not resolve existing children, we don't need them now
248  'inlineResolveExistingChildren' => false,
249  ];
251  $formDataGroup = GeneralUtility::makeInstance(InlineParentRecord::class);
253  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
254  $parentData = $formDataCompiler->compile($formDataCompilerInputForParent);
255  // Set flag in config so that only the fields are rendered
256  // @todo: Solve differently / rename / whatever
257  $parentData['processedTca']['columns'][$parentFieldName]['config']['renderFieldsOnly'] = true;
258  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
259 
260  // Child, a record from this table should be rendered
261  $child = $inlineStackProcessor->getUnstableStructure();
262 
263  $childData = $this->compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
264 
265  $childData['inlineParentUid'] = (int)$parent['uid'];
266  $childData['renderType'] = 'inlineRecordContainer';
267  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
268  $childResult = $nodeFactory->create($childData)->render();
269 
270  $jsonArray = [
271  'data' => '',
272  'stylesheetFiles' => [],
273  'scriptCall' => [],
274  ];
275 
276  // The HTML-object-id's prefix of the dynamically created record
277  $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $child['table'];
278  $objectId = $objectPrefix . '-' . (int)$child['uid'];
279  $expandSingle = $parentConfig['appearance']['expandSingle'];
280  $jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
281  if ($parentConfig['foreign_unique']) {
282  $jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
283  }
284  $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
285  if ($parentConfig['appearance']['useSortable']) {
286  $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
287  $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
288  }
289  if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
290  $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
291  }
292 
293  $response->getBody()->write(json_encode($jsonArray));
294 
295  return $response;
296  }
297 
306  public function synchronizeLocalizeAction(ServerRequestInterface $request, ResponseInterface $response)
307  {
308  $ajaxArguments = isset($request->getParsedBody()['ajax']) ? $request->getParsedBody()['ajax'] : $request->getQueryParams()['ajax'];
309  $domObjectId = $ajaxArguments[0];
310  $type = $ajaxArguments[1];
311 
313  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
314  // Parse the DOM identifier (string), add the levels to the structure stack (array), load the TCA config:
315  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
316  $inlineStackProcessor->injectAjaxConfiguration($ajaxArguments['context']);
317  $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
318 
319  $jsonArray = false;
320  if ($type === 'localize' || $type === 'synchronize' || MathUtility::canBeInterpretedAsInteger($type)) {
321  // Parent, this table embeds the child table
322  $parent = $inlineStackProcessor->getStructureLevel(-1);
323  $parentFieldName = $parent['field'];
324 
325  // Child, a record from this table should be rendered
326  $child = $inlineStackProcessor->getUnstableStructure();
327 
328  $formDataCompilerInputForParent = [
329  'vanillaUid' => (int)$parent['uid'],
330  'command' => 'edit',
331  'tableName' => $parent['table'],
332  'databaseRow' => [
333  // TcaInlineExpandCollapseState needs this
334  'uid' => (int)$parent['uid'],
335  ],
336  'inlineFirstPid' => $inlineFirstPid,
337  'columnsToProcess' => [
338  $parentFieldName
339  ],
340  // @todo: still needed? NO!
341  'inlineStructure' => $inlineStackProcessor->getStructure(),
342  // Do not compile existing children, we don't need them now
343  'inlineCompileExistingChildren' => false,
344  ];
345  // Full TcaDatabaseRecord is required here to have the list of connected uids $oldItemList
347  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
349  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
350  $parentData = $formDataCompiler->compile($formDataCompilerInputForParent);
351  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
352  $oldItemList = $parentData['databaseRow'][$parentFieldName];
353 
354  $cmd = array();
355  $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = $parent['field'] . ',' . $type;
357  $tce = GeneralUtility::makeInstance(DataHandler::class);
358  $tce->stripslashes_values = false;
359  $tce->start(array(), $cmd);
360  $tce->process_cmdmap();
361 
362  $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parentFieldName];
363 
364  $jsonArray = array(
365  'data' => '',
366  'stylesheetFiles' => [],
367  'scriptCall' => [],
368  );
369  $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
370  $nameObjectForeignTable = $nameObject . '-' . $child['table'];
371 
372  $oldItems = $this->getInlineRelatedRecordsUidArray($oldItemList);
373  $newItems = $this->getInlineRelatedRecordsUidArray($newItemList);
374 
375  // Set the items that should be removed in the forms view:
376  $removedItems = array_diff($oldItems, $newItems);
377  foreach ($removedItems as $childUid) {
378  $jsonArray['scriptCall'][] = 'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $childUid) . ', {forceDirectRemoval: true});';
379  }
380 
381  $localizedItems = array_diff($newItems, $oldItems);
382  foreach ($localizedItems as $childUid) {
383  $childData = $this->compileChild($parentData, $parentFieldName, (int)$childUid, $inlineStackProcessor->getStructure());
384 
385  $childData['inlineParentUid'] = (int)$parent['uid'];
386  $childData['renderType'] = 'inlineRecordContainer';
387  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
388  $childResult = $nodeFactory->create($childData)->render();
389 
390  $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
391 
392  // Get the name of the field used as foreign selector (if any):
393  $foreignSelector = isset($parentConfig['foreign_selector']) && $parentConfig['foreign_selector'] ? $parentConfig['foreign_selector'] : false;
394  $selectedValue = $foreignSelector ? GeneralUtility::quoteJSvalue($childData['databaseRow'][$foreignSelector]) : 'null';
395  if (is_array($selectedValue)) {
396  $selectedValue = $selectedValue[0];
397  }
398  $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) . ', ' . GeneralUtility::quoteJSvalue($childUid) . ', null, ' . $selectedValue . ');';
399  // Remove possible virtual records in the form which showed that a child records could be localized:
400  $transOrigPointerFieldName = $childData['processedTca']['ctrl']['transOrigPointerField'];
401  if (isset($childData['databaseRow'][$transOrigPointerFieldName]) && $childData['databaseRow'][$transOrigPointerFieldName]) {
402  $transOrigPointerField = $childData['databaseRow'][$transOrigPointerFieldName];
403  if (is_array($transOrigPointerField)) {
404  $transOrigPointerField = $transOrigPointerField[0];
405  }
406  $jsonArray['scriptCall'][] = 'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable . '-' . $transOrigPointerField . '_div') . ');';
407  }
408 
409  }
410  // Tell JS to add new HTML of one or multiple (localize all) records to DOM
411  if (!empty($jsonArray['data'])) {
412  array_push(
413  $jsonArray['scriptCall'],
414  'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject . '_records')
415  . ', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable)
416  . ', json.data);'
417  );
418  }
419  }
420 
421  $response->getBody()->write(json_encode($jsonArray));
422 
423  return $response;
424  }
425 
433  public function expandOrCollapseAction(ServerRequestInterface $request, ResponseInterface $response)
434  {
435  $ajaxArguments = isset($request->getParsedBody()['ajax']) ? $request->getParsedBody()['ajax'] : $request->getQueryParams()['ajax'];
436  $domObjectId = $ajaxArguments[0];
437 
439  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
440  // Parse the DOM identifier (string), add the levels to the structure stack (array), don't load TCA config
441  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
442  $expand = $ajaxArguments[1];
443  $collapse = $ajaxArguments[2];
444 
445  $backendUser = $this->getBackendUserAuthentication();
446  // The current table - for this table we should add/import records
447  $currentTable = $inlineStackProcessor->getUnstableStructure();
448  $currentTable = $currentTable['table'];
449  // The top parent table - this table embeds the current table
450  $top = $inlineStackProcessor->getStructureLevel(0);
451  $topTable = $top['table'];
452  $topUid = $top['uid'];
453  $inlineView = $this->getInlineExpandCollapseStateArray();
454  // Only do some action if the top record and the current record were saved before
456  $expandUids = GeneralUtility::trimExplode(',', $expand);
457  $collapseUids = GeneralUtility::trimExplode(',', $collapse);
458  // Set records to be expanded
459  foreach ($expandUids as $uid) {
460  $inlineView[$topTable][$topUid][$currentTable][] = $uid;
461  }
462  // Set records to be collapsed
463  foreach ($collapseUids as $uid) {
464  $inlineView[$topTable][$topUid][$currentTable] = $this->removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
465  }
466  // Save states back to database
467  if (is_array($inlineView[$topTable][$topUid][$currentTable])) {
468  $inlineView[$topTable][$topUid][$currentTable] = array_unique($inlineView[$topTable][$topUid][$currentTable]);
469  $backendUser->uc['inlineView'] = serialize($inlineView);
470  $backendUser->writeUC();
471  }
472  }
473 
474  $response->getBody()->write(json_encode(array()));
475  return $response;
476  }
477 
491  protected function compileChild(array $parentData, $parentFieldName, $childUid, array $inlineStructure)
492  {
493  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
494 
496  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
497  $inlineStackProcessor->initializeByGivenStructure($inlineStructure);
498  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
499 
500  // @todo: do not use stack processor here ...
501  $child = $inlineStackProcessor->getUnstableStructure();
502  $childTableName = $child['table'];
503 
505  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
507  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
508  $formDataCompilerInput = [
509  'command' => 'edit',
510  'tableName' => $childTableName,
511  'vanillaUid' => (int)$childUid,
512  'isInlineChild' => true,
513  'inlineStructure' => $inlineStructure,
514  'inlineFirstPid' => $parentData['inlineFirstPid'],
515  'inlineParentConfig' => $parentConfig,
516  'isInlineAjaxOpeningContext' => true,
517 
518  // values of the current parent element
519  // it is always a string either an id or new...
520  'inlineParentUid' => $parentData['databaseRow']['uid'],
521  'inlineParentTableName' => $parentData['tableName'],
522  'inlineParentFieldName' => $parentFieldName,
523 
524  // values of the top most parent element set on first level and not overridden on following levels
525  'inlineTopMostParentUid' => $parentData['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'],
526  'inlineTopMostParentTableName' => $parentData['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'],
527  'inlineTopMostParentFieldName' => $parentData['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'],
528  ];
529  // For foreign_selector with useCombination $mainChild is the mm record
530  // and $combinationChild is the child-child. For "normal" relations, $mainChild
531  // is just the normal child record and $combinationChild is empty.
532  $mainChild = $formDataCompiler->compile($formDataCompilerInput);
533  if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
534  $mainChild['combinationChild'] = $this->compileCombinationChild($mainChild, $parentConfig, $inlineStructure);
535  }
536  return $mainChild;
537  }
538 
548  protected function compileCombinationChild(array $intermediate, array $parentConfig, array $inlineStructure)
549  {
550  // foreign_selector on intermediate is probably type=select, so data provider of this table resolved that to the uid already
551  $intermediateUid = $intermediate['databaseRow'][$parentConfig['foreign_selector']][0];
552  $combinationChild = $this->compileChild($intermediate, $parentConfig['foreign_selector'], $intermediateUid, $inlineStructure);
553  return $combinationChild;
554  }
555 
564  protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
565  {
566  $jsonResult['data'] .= $childResult['html'];
567  $jsonResult['stylesheetFiles'] = $childResult['stylesheetFiles'];
568  if (!empty($childResult['inlineData'])) {
569  $jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
570  }
571  if (!empty($childResult['additionalJavaScriptSubmit'])) {
572  $additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
573  $additionalJavaScriptSubmit = str_replace(array(CR, LF), '', $additionalJavaScriptSubmit);
574  $jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
575  }
576  foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
577  $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
578  }
579  $jsonResult['scriptCall'][] = $childResult['extJSCODE'];
580  if (!empty($childResult['requireJsModules'])) {
581  foreach ($childResult['requireJsModules'] as $module) {
582  $moduleName = null;
583  $callback = null;
584  if (is_string($module)) {
585  // if $module is a string, no callback
586  $moduleName = $module;
587  $callback = null;
588  } elseif (is_array($module)) {
589  // if $module is an array, callback is possible
590  foreach ($module as $key => $value) {
591  $moduleName = $key;
592  $callback = $value;
593  break;
594  }
595  }
596  if ($moduleName !== null) {
597  $inlineCodeKey = $moduleName;
598  $javaScriptCode = 'require(["' . $moduleName . '"]';
599  if ($callback !== null) {
600  $inlineCodeKey .= sha1($callback);
601  $javaScriptCode .= ', ' . $callback;
602  }
603  $javaScriptCode .= ');';
604  $jsonResult['scriptCall'][] = '/*RequireJS-Module-' . $inlineCodeKey . '*/' . LF . $javaScriptCode;
605  }
606  }
607  }
608  return $jsonResult;
609  }
610 
619  protected function getInlineRelatedRecordsUidArray($itemList)
620  {
621  $itemArray = GeneralUtility::trimExplode(',', $itemList, true);
622  // Perform modification of the selected items array:
623  foreach ($itemArray as &$value) {
624  $parts = explode('|', $value, 2);
625  $value = $parts[0];
626  }
627  unset($value);
628  return $itemArray;
629  }
630 
639  protected function checkInlineFileTypeAccessForField(array $selectorConfiguration, array $fileRecord)
640  {
641  if (!empty($selectorConfiguration['PA']['fieldConf']['config']['appearance']['elementBrowserAllowed'])) {
642  $allowedFileExtensions = GeneralUtility::trimExplode(
643  ',',
644  $selectorConfiguration['PA']['fieldConf']['config']['appearance']['elementBrowserAllowed'],
645  true
646  );
647  if (!in_array(strtolower($fileRecord['extension']), $allowedFileExtensions, true)) {
648  return false;
649  }
650  }
651  return true;
652  }
653 
661  protected function getInlineExpandCollapseStateArrayForTableUid($table, $uid)
662  {
663  $inlineView = $this->getInlineExpandCollapseStateArray();
664  $result = array();
666  if (!empty($inlineView[$table][$uid])) {
667  $result = $inlineView[$table][$uid];
668  }
669  }
670  return $result;
671  }
672 
679  {
680  $backendUser = $this->getBackendUserAuthentication();
681  $inlineView = unserialize($backendUser->uc['inlineView']);
682  if (!is_array($inlineView)) {
683  $inlineView = array();
684  }
685  return $inlineView;
686  }
687 
696  protected function removeFromArray($needle, $haystack, $strict = null)
697  {
698  $pos = array_search($needle, $haystack, $strict);
699  if ($pos !== false) {
700  unset($haystack[$pos]);
701  }
702  return $haystack;
703  }
704 
711  protected function getErrorMessageForAJAX($message)
712  {
713  return [
714  'data' => $message,
715  'scriptCall' => [
716  'alert("' . $message . '");'
717  ],
718  ];
719  }
720 
727  protected function getInlineFirstPidFromDomObjectId($domObjectId)
728  {
729  // Substitute FlexForm addition and make parsing a bit easier
730  $domObjectId = str_replace('---', ':', $domObjectId);
731  // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
732  $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
733  if (preg_match($pattern, $domObjectId, $match)) {
734  return $match[1];
735  }
736  return null;
737  }
738 
742  protected function getBackendUserAuthentication()
743  {
744  return $GLOBALS['BE_USER'];
745  }
746 
757  protected function getParentConfigFromFlexForm(array $parentConfig, $domObjectId)
758  {
759  // Substitute FlexForm addition and make parsing a bit easier
760  $domObjectId = str_replace('---', ':', $domObjectId);
761  // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
762  $pattern = '/^data' . '-' . '(?<firstPidValue>.+?)' . '-' . '(?<anything>.+)$/';
763 
764  $flexFormPath = [];
765 
766  if (preg_match($pattern, $domObjectId, $match)) {
767  $parts = explode('-', $match['anything']);
768 
769  if (!isset($parts[2]) || strpos($parts[2], ':') === false) {
770  throw new \UnexpectedValueException(
771  'DOM Object ID' . $domObjectId. 'does not contain required information '
772  . 'to extract inline field configuration.',
773  1446996136
774  );
775  }
776 
777  $fieldParts = GeneralUtility::trimExplode(':', $parts[2]);
778 
779  // FlexForm parts start with data:
780  if (empty($fieldParts) || !isset($fieldParts[1]) || $fieldParts[1] !== 'data') {
781  throw new \UnexpectedValueException(
782  'Malformed flexform identifier: ' . $parts[2],
783  1446996254
784  );
785  }
786 
787  $flexFormPath = array_slice($fieldParts, 2);
788  }
789 
790  $childConfig = $parentConfig['ds']['sheets'];
791 
792  foreach ($flexFormPath as $flexFormNode) {
793  // We are dealing with configuration information from a flexform,
794  // not value storage, identifiers that the reference language or
795  // value nodes must be skipped.
796  if (!isset($childConfig[$flexFormNode]) && preg_match('/^[lv][[:alpha:]]+$/', $flexFormNode)) {
797  continue;
798  }
799  $childConfig = $childConfig[$flexFormNode];
800 
801  // Skip to the field configuration of a sheet
802  if (isset($childConfig['ROOT']) && $childConfig['ROOT']['type'] == 'array') {
803  $childConfig = $childConfig['ROOT']['el'];
804  }
805  }
806 
807  if (!isset($childConfig['config'])
808  || !is_array($childConfig['config'])
809  || $childConfig['config']['type'] !== 'inline'
810  ) {
811  throw new \UnexpectedValueException(
812  'Configuration retrieved from FlexForm is incomplete or not of type "inline".',
813  1446996319
814  );
815  }
816  return $childConfig['config'];
817  }
818 }