2 declare(strict_types = 1);
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
44 public function createAction(ServerRequestInterface $request): ResponseInterface
46 $ajaxArguments = $request->getParsedBody()[
'ajax'] ?? $request->getQueryParams()[
'ajax'];
49 $domObjectId = $ajaxArguments[0];
52 throw new \RuntimeException(
53 'inlineFirstPid should either be an integer or a "NEW..." string',
57 $childChildUid =
null;
59 $childChildUid = (int)$ajaxArguments[1];
64 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
65 $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
66 $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
67 $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
70 $parent = $inlineStackProcessor->getStructureLevel(-1);
73 $child = $inlineStackProcessor->getUnstableStructure();
76 $childVanillaUid = -1 * abs((
int)$child[
'uid']);
79 $childVanillaUid = $inlineFirstPid;
82 $childTableName = $parentConfig[
'foreign_table'];
85 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
87 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
88 $formDataCompilerInput = [
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'],
103 if ($childChildUid) {
104 $formDataCompilerInput[
'inlineChildChildUid'] = $childChildUid;
106 $childData = $formDataCompiler->compile($formDataCompilerInput);
108 if ($parentConfig[
'foreign_selector'] && $parentConfig[
'appearance'][
'useCombination']) {
113 if ($childChildUid) {
115 $childData[
'databaseRow'][$parentConfig[
'foreign_selector']] = [
118 $childData[
'combinationChild'] = $this->
compileChildChild($childData, $parentConfig, $inlineStackProcessor->getStructure());
121 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
123 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
124 $formDataCompilerInput = [
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,
133 $childData[
'combinationChild'] = $formDataCompiler->compile($formDataCompilerInput);
137 $childData[
'inlineParentUid'] = $parent[
'uid'];
138 $childData[
'renderType'] =
'inlineRecordContainer';
139 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
140 $childResult = $nodeFactory->create($childData)->render();
144 'stylesheetFiles' => [],
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) .
');';
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) .
');';
161 if ($parentConfig[
'appearance'][
'useSortable']) {
162 $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
163 $jsonArray[
'scriptCall'][] =
'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName .
'_records') .
');';
165 if (!$parentConfig[
'appearance'][
'collapseAll'] && $expandSingle) {
166 $jsonArray[
'scriptCall'][] =
'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) .
',' . GeneralUtility::quoteJSvalue($objectPrefix) .
',' . GeneralUtility::quoteJSvalue($childData[
'databaseRow'][
'uid']) .
');';
169 $jsonArray[
'scriptCall'][] =
'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId .
'_div') .
');';
180 public function detailsAction(ServerRequestInterface $request): ResponseInterface
182 $ajaxArguments = $request->getParsedBody()[
'ajax'] ?? $request->getQueryParams()[
'ajax'];
184 $domObjectId = $ajaxArguments[0] ??
null;
190 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
191 $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
192 $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
195 $parent = $inlineStackProcessor->getStructureLevel(-1);
196 $parentFieldName = $parent[
'field'];
200 $parentConfig[
'renderFieldsOnly'] =
true;
205 $parentFieldName => [
206 'config' => $parentConfig,
210 'uid' => $parent[
'uid'],
211 'tableName' => $parent[
'table'],
212 'inlineFirstPid' => $inlineFirstPid,
217 'returnUrl' => $parentConfig[
'originalReturnUrl'],
221 $child = $inlineStackProcessor->getUnstableStructure();
223 $childData = $this->
compileChild($parentData, $parentFieldName, (
int)$child[
'uid'], $inlineStackProcessor->getStructure());
225 $childData[
'inlineParentUid'] = (int)$parent[
'uid'];
226 $childData[
'renderType'] =
'inlineRecordContainer';
227 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
228 $childResult = $nodeFactory->create($childData)->render();
232 'stylesheetFiles' => [],
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'] .
'\');
';
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
') . ');
';
249 if (!$parentConfig['appearance
']['collapseAll
'] && $expandSingle) {
250 $jsonArray['scriptCall
'][] = 'inline.collapseAllRecords(
' . GeneralUtility::quoteJSvalue($objectId) . ',
' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\
'' . (
int)$child[
'uid'] .
'\');
';
253 return new JsonResponse($jsonArray);
263 public function synchronizeLocalizeAction(ServerRequestInterface $request): ResponseInterface
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
']);
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);
279 'stylesheetFiles
' => [],
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
'];
287 $processedTca = $GLOBALS['TCA
'][$parent['table
']];
288 $processedTca['columns
'][$parentFieldName]['config
'] = $parentConfig;
290 // Child, a record from this table should be rendered
291 $child = $inlineStackProcessor->getUnstableStructure();
293 $formDataCompilerInputForParent = [
294 'vanillaUid
' => (int)$parent['uid
'],
296 'tableName
' => $parent['table
'],
297 'processedTca
' => $processedTca,
298 'inlineFirstPid
' => $inlineFirstPid,
299 'columnsToProcess
' => [
302 // @todo: still needed? NO!
303 'inlineStructure
' => $inlineStackProcessor->getStructure(),
304 // Do not compile existing children, we don't need them now
305 'inlineCompileExistingChildren' =>
false,
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];
319 if (is_array($parentLanguage)) {
320 $parentLanguage = implode(
',', $parentLanguage);
326 $cmd[$parent[
'table']][$parent[
'uid']][
'inlineLocalizeSynchronize'] = [
327 'field' => $parent[
'field'],
328 'language' => $parentLanguage,
333 $cmd[$parent[
'table']][$parent[
'uid']][
'inlineLocalizeSynchronize'] = [
334 'field' => $parent[
'field'],
335 'language' => $parentLanguage,
341 $tce = GeneralUtility::makeInstance(DataHandler::class);
342 $tce->start([], $cmd);
343 $tce->process_cmdmap();
345 $newItemList = $tce->registerDBList[$parent[
'table']][$parent[
'uid']][$parentFieldName];
347 $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
348 $nameObjectForeignTable = $nameObject .
'-' . $child[
'table'];
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()
365 $jsonArray[
'hasErrors'] =
true;
371 $removedItems = array_diff($oldItems, $newItems);
372 foreach ($removedItems as $childUid) {
373 $jsonArray[
'scriptCall'][] =
'inline.deleteRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable .
'-' . $childUid) .
', {forceDirectRemoval: true});';
376 $localizedItems = array_diff($newItems, $oldItems);
377 foreach ($localizedItems as $childUid) {
378 $childData = $this->
compileChild($parentData, $parentFieldName, (
int)$childUid, $inlineStackProcessor->getStructure());
380 $childData[
'inlineParentUid'] = (int)$parent[
'uid'];
381 $childData[
'renderType'] =
'inlineRecordContainer';
382 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
383 $childResult = $nodeFactory->create($childData)->render();
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];
393 $jsonArray[
'scriptCall'][] =
'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable) .
', ' . GeneralUtility::quoteJSvalue($childUid) .
', null, ' . $selectedValue .
');';
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];
401 $jsonArray[
'scriptCall'][] =
'inline.fadeAndRemove(' . GeneralUtility::quoteJSvalue($nameObjectForeignTable .
'-' . $transOrigPointerField .
'_div') .
');';
405 if (!empty($jsonArray[
'data'])) {
406 $jsonArray[
'scriptCall'][] =
'inline.domAddNewRecord(\'bottom\', ' . GeneralUtility::quoteJSvalue($nameObject .
'_records')
407 .
', ' . GeneralUtility::quoteJSvalue($nameObjectForeignTable)
411 return new JsonResponse($jsonArray);
422 $ajaxArguments = $request->getParsedBody()[
'ajax'] ?? $request->getQueryParams()[
'ajax'];
423 $domObjectId = $ajaxArguments[0];
426 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
428 $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
429 $expand = $ajaxArguments[1];
430 $collapse = $ajaxArguments[2];
434 $currentTable = $inlineStackProcessor->getUnstableStructure();
435 $currentTable = $currentTable[
'table'];
437 $top = $inlineStackProcessor->getStructureLevel(0);
438 $topTable = $top[
'table'];
439 $topUid = $top[
'uid'];
443 $expandUids = GeneralUtility::trimExplode(
',', $expand);
444 $collapseUids = GeneralUtility::trimExplode(
',', $collapse);
446 foreach ($expandUids as $uid) {
447 $inlineView[$topTable][$topUid][$currentTable][] = $uid;
450 foreach ($collapseUids as $uid) {
451 $inlineView[$topTable][$topUid][$currentTable] = $this->
removeFromArray($uid, $inlineView[$topTable][$topUid][$currentTable]);
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();
475 protected function compileChild(array $parentData, $parentFieldName, $childUid, array $inlineStructure)
477 $parentConfig = $parentData[
'processedTca'][
'columns'][$parentFieldName][
'config'];
480 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
481 $inlineStackProcessor->initializeByGivenStructure($inlineStructure);
482 $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
485 $child = $inlineStackProcessor->getUnstableStructure();
486 $childTableName = $child[
'table'];
489 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
491 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
492 $formDataCompilerInput = [
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,
505 'inlineParentUid' => $parentData[
'databaseRow'][
'uid'] ?? $parentData[
'uid'],
506 'inlineParentTableName' => $parentData[
'tableName'],
507 'inlineParentFieldName' => $parentFieldName,
510 'inlineTopMostParentUid' => $inlineTopMostParent[
'uid'],
511 'inlineTopMostParentTableName' => $inlineTopMostParent[
'table'],
512 'inlineTopMostParentFieldName' => $inlineTopMostParent[
'field'],
517 $mainChild = $formDataCompiler->compile($formDataCompilerInput);
518 if ($parentConfig[
'foreign_selector'] && $parentConfig[
'appearance'][
'useCombination']) {
520 $mainChild[
'combinationChild'] = $this->
compileChildChild($mainChild, $parentConfig, $inlineStructure);
537 $childChildUid = $child[
'databaseRow'][$parentConfig[
'foreign_selector']][0];
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 = [
546 'tableName' => $childChildTableName,
547 'vanillaUid' => (int)$childChildUid,
548 'isInlineChild' =>
true,
549 'isInlineAjaxOpeningContext' =>
true,
551 'inlineStructure' => $inlineStructure,
552 'inlineFirstPid' => $child[
'inlineFirstPid'],
554 'inlineTopMostParentUid' => $child[
'inlineTopMostParentUid'],
555 'inlineTopMostParentTableName' => $child[
'inlineTopMostParentTableName'],
556 'inlineTopMostParentFieldName' => $child[
'inlineTopMostParentFieldName'],
558 return $formDataCompiler->compile($formDataCompilerInput);
571 $jsonResult[
'data'] .= $childResult[
'html'];
572 $jsonResult[
'stylesheetFiles'] = [];
573 foreach ($childResult[
'stylesheetFiles'] as $stylesheetFile) {
576 if (!empty($childResult[
'inlineData'])) {
577 $jsonResult[
'scriptCall'][] =
'inline.addToDataArray(' . json_encode($childResult[
'inlineData']) .
');';
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) .
'");';
584 foreach ($childResult[
'additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
585 $jsonResult[
'scriptCall'][] = $singleAdditionalJavaScriptPost;
587 if (!empty($childResult[
'additionalInlineLanguageLabelFiles'])) {
589 foreach ($childResult[
'additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
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[] =
'}';
606 $jsonResult[
'scriptCall'][] = implode(LF, $javaScriptCode);
609 $jsonResult[
'scriptCall'] = array_merge($requireJsModule, $jsonResult[
'scriptCall']);
624 $itemArray = GeneralUtility::trimExplode(
',', $itemList,
true);
626 foreach ($itemArray as &$value) {
627 $parts = explode(
'|', $value, 2);
646 if (!empty($inlineView[$table][$uid])) {
647 $result = $inlineView[$table][$uid];
665 $inlineView = unserialize($backendUser->uc[
'inlineView'], [
'allowed_classes' =>
false]);
666 if (!is_array($inlineView)) {
682 return !empty($backendUser->uc[
'inlineView']);
695 $pos = array_search($needle, $haystack, $strict);
696 if ($pos !==
false) {
697 unset($haystack[$pos]);
713 'alert("' . $message .
'");'
727 $domObjectId = str_replace(
'---',
':', $domObjectId);
729 $pattern =
'/^data' .
'-' .
'(.+?)' .
'-' .
'(.+)$/';
730 if (preg_match($pattern, $domObjectId, $match)) {
746 if ($contextString ===
'') {
747 throw new \RuntimeException(
'Empty context string given', 1489751361);
749 $context = json_decode($contextString,
true);
750 if (empty($context[
'config'])) {
751 throw new \RuntimeException(
'Empty context config section given', 1489751362);
753 if (!hash_equals(GeneralUtility::hmac((
string)$context[
'config'],
'InlineContext'), (
string)$context[
'hmac'])) {
754 throw new \RuntimeException(
'Hash does not validate', 1489751363);
756 return json_decode($context[
'config'],
true);