72 'renderType' =>
'tcaDescription',
80 'localizationStateSelector' => [
81 'renderType' =>
'localizationStateSelector',
94 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
106 $this->inlineData = $this->data[
'inlineData'];
113 $table = $this->data[
'tableName'];
114 $row = $this->data[
'databaseRow'];
115 $field = $this->data[
'fieldName'];
116 $parameterArray = $this->data[
'parameterArray'];
120 $config = $parameterArray[
'fieldConf'][
'config'];
121 $foreign_table = $config[
'foreign_table'];
122 $isReadOnly = isset($config[
'readOnly']) && $config[
'readOnly'];
124 $languageFieldName =
$GLOBALS[
'TCA'][$table][
'ctrl'][
'languageField'];
126 $language = isset($row[$languageFieldName][0]) ? (int)$row[$languageFieldName][0] : (
int)$row[$languageFieldName];
130 $newStructureItem = [
132 'uid' => $row[
'uid'],
137 if (!empty($parameterArray[
'itemFormElName'])) {
139 if ($flexFormParts !==
null) {
140 $newStructureItem[
'flexform'] = $flexFormParts;
146 if (!empty($newStructureItem[
'flexform'])
147 && isset($this->data[
'processedTca'][
'columns'][$field][
'config'][
'dataStructureIdentifier'])
149 $config[
'dataStructureIdentifier'] = $this->data[
'processedTca'][
'columns'][$field][
'config'][
'dataStructureIdentifier'];
155 $config[
'originalReturnUrl'] = $this->data[
'returnUrl'];
162 $config[
'inline'][
'first'] =
false;
163 $firstChild = reset($this->data[
'parameterArray'][
'fieldConf'][
'children']);
164 if (isset($firstChild[
'databaseRow'][
'uid'])) {
165 $config[
'inline'][
'first'] = $firstChild[
'databaseRow'][
'uid'];
167 $config[
'inline'][
'last'] =
false;
168 $lastChild = end($this->data[
'parameterArray'][
'fieldConf'][
'children']);
169 if (isset($lastChild[
'databaseRow'][
'uid'])) {
170 $config[
'inline'][
'last'] = $lastChild[
'databaseRow'][
'uid'];
175 $this->inlineData[
'config'][$nameObject] = [
176 'table' => $foreign_table,
177 'md5' => md5($nameObject)
179 $configJson = json_encode($config);
180 $this->inlineData[
'config'][$nameObject .
'-' . $foreign_table] = [
181 'min' => $config[
'minitems'],
182 'max' => $config[
'maxitems'],
183 'sortable' => $config[
'appearance'][
'useSortable'],
185 'table' => $top[
'table'],
189 'config' => $configJson,
190 'hmac' => GeneralUtility::hmac($configJson,
'InlineContext'),
193 $this->inlineData[
'nested'][$nameObject] = $this->data[
'tabAndInlineStack'];
198 if ($config[
'foreign_unique']) {
201 $type = $config[
'selectorOrUniqueConfiguration'][
'config'][
'type'] ===
'select' ?
'select' :
'groupdb';
202 foreach ($parameterArray[
'fieldConf'][
'children'] as $child) {
204 if (!$child[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
205 $value = $child[
'databaseRow'][$config[
'foreign_unique']];
207 if ($type ===
'select') {
210 $value = $value[
'0'];
219 'uid' => $value[0][
'uid'],
220 'table' => $value[0][
'table'],
226 $uniqueIds[$child[
'databaseRow'][
'uid']] = $value;
229 $possibleRecords = $config[
'selectorOrUniquePossibleRecords'];
230 $possibleRecordsUidToTitle = [];
231 foreach ($possibleRecords as $possibleRecord) {
232 $possibleRecordsUidToTitle[$possibleRecord[1]] = $possibleRecord[0];
234 $uniqueMax = $config[
'appearance'][
'useCombination'] || empty($possibleRecords) ? -1 : count($possibleRecords);
235 $this->inlineData[
'unique'][$nameObject .
'-' . $foreign_table] = [
237 'used' => $uniqueIds,
239 'table' => $foreign_table,
240 'elTable' => $config[
'selectorOrUniqueConfiguration'][
'foreignTable'],
241 'field' => $config[
'foreign_unique'],
242 'selector' => $config[
'selectorOrUniqueConfiguration'][
'isSelector'] ? $type :
false,
243 'possible' => $possibleRecordsUidToTitle,
250 $uidOfDefaultRecord = $row[
$GLOBALS[
'TCA'][$table][
'ctrl'][
'transOrigPointerField']];
251 $isLocalizedParent = $language > 0
252 && ($uidOfDefaultRecord[0] ?? $uidOfDefaultRecord) > 0
254 $numberOfFullLocalizedChildren = 0;
255 $numberOfNotYetLocalizedChildren = 0;
256 foreach ($this->data[
'parameterArray'][
'fieldConf'][
'children'] as $child) {
257 if (!$child[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
258 $numberOfFullLocalizedChildren++;
260 if ($isLocalizedParent && $child[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
261 $numberOfNotYetLocalizedChildren++;
266 $localizationLinks =
'';
267 if ($numberOfNotYetLocalizedChildren) {
269 if (isset($config[
'appearance'][
'showAllLocalizationLink']) && $config[
'appearance'][
'showAllLocalizationLink']) {
270 $localizationLinks =
' ' . $this->
getLevelInteractionLink(
'localize', $nameObject .
'-' . $foreign_table, $config);
273 if (isset($config[
'appearance'][
'showSynchronizationLink']) && $config[
'appearance'][
'showSynchronizationLink']) {
274 $localizationLinks .=
' ' . $this->
getLevelInteractionLink(
'synchronize', $nameObject .
'-' . $foreign_table, $config);
279 if ($isReadOnly || $numberOfFullLocalizedChildren >= $config[
'maxitems'] || ($uniqueMax > 0 && $numberOfFullLocalizedChildren >= $uniqueMax)) {
280 $config[
'inline'][
'inlineNewButtonStyle'] =
'display: none;';
281 $config[
'inline'][
'inlineNewRelationButtonStyle'] =
'display: none;';
282 $config[
'inline'][
'inlineOnlineMediaAddButtonStyle'] =
'display: none;';
287 if (!empty($config[
'appearance'][
'enabledControls'][
'new'])) {
291 $html =
'<div class="form-group" id="' . $nameObject .
'">';
294 $html .= $fieldInformationResult[
'html'];
298 if ($config[
'appearance'][
'levelLinksPosition'] ===
'both' || $config[
'appearance'][
'levelLinksPosition'] ===
'top') {
299 $html .=
'<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks .
'</div>';
303 if (!$isReadOnly && $config[
'foreign_selector'] && $config[
'appearance'][
'showPossibleRecordsSelector'] !==
false) {
304 if ($config[
'selectorOrUniqueConfiguration'][
'config'][
'type'] ===
'select') {
309 $html .= $selectorBox . $localizationLinks;
312 $title = $languageService->sL(trim($parameterArray[
'fieldConf'][
'label']));
313 $html .=
'<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) .
'" id="' . $nameObject .
'_records">';
315 $sortableRecordUids = [];
316 foreach ($this->data[
'parameterArray'][
'fieldConf'][
'children'] as $options) {
317 $options[
'inlineParentUid'] = $row[
'uid'];
318 $options[
'inlineFirstPid'] = $this->data[
'inlineFirstPid'];
320 $options[
'inlineParentConfig'] = $config;
323 $options[
'inlineExpandCollapseStateArray'] = $this->data[
'inlineExpandCollapseStateArray'];
324 $options[
'renderType'] =
'inlineRecordContainer';
325 $childResult = $this->nodeFactory->create($options)->render();
326 $html .= $childResult[
'html'];
328 if (!$options[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
331 $sortableRecordUids[] = $options[
'databaseRow'][
'uid'];
338 $fieldWizardHtml = $fieldWizardResult[
'html'];
340 $html .= $fieldWizardHtml;
343 if (!$isReadOnly && ($config[
'appearance'][
'levelLinksPosition'] ===
'both' || $config[
'appearance'][
'levelLinksPosition'] ===
'bottom')) {
344 $html .= $levelLinks . $localizationLinks;
346 if (is_array($config[
'customControls'])) {
347 $html .=
'<div id="' . $nameObject .
'_customControls">';
348 foreach ($config[
'customControls'] as $customControlConfig) {
349 if (!isset($customControlConfig[
'userFunc'])) {
350 trigger_error(
'Support for customControl without a userFunc key will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
351 $customControlConfig = [
352 'userFunc' => $customControlConfig
359 'nameObject' => $nameObject,
360 'nameForm' => $nameForm,
362 'customControlConfig' => $customControlConfig,
364 $html .= GeneralUtility::callUserFunction($customControlConfig[
'userFunc'], $parameters, $this);
369 if (count($sortableRecordUids) > 1 && $config[
'appearance'][
'useSortable']) {
370 $resultArray[
'additionalJavaScriptPost'][] =
'inline.createDragAndDropSorting("' . $nameObject .
'_records' .
'");';
372 $resultArray[
'requireJsModules'] = array_merge($resultArray[
'requireJsModules'], $this->requireJsModules);
375 $html .=
'<input type="hidden" name="' . $nameForm .
'" value="' . implode(
',', $sortableRecordUids) .
'" '
376 .
' data-formengine-validation-rules="' . htmlspecialchars($this->
getValidationDataAsJsonString([
'type' =>
'inline',
'minitems' => $config[
'minitems'],
'maxitems' => $config[
'maxitems']])) .
'"'
377 .
' class="inlineRecord" />';
381 $resultArray[
'html'] = $html;
397 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
401 $title = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createnew'));
402 $icon =
'actions-add';
403 $className =
'typo3-newRecordLink';
404 $attributes[
'class'] =
'btn btn-default inlineNewButton ' . $this->inlineData[
'config'][$nameObject][
'md5'];
405 $attributes[
'onclick'] =
'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) .
')';
406 if (!empty($conf[
'inline'][
'inlineNewButtonStyle'])) {
407 $attributes[
'style'] = $conf[
'inline'][
'inlineNewButtonStyle'];
409 if (!empty($conf[
'appearance'][
'newRecordLinkAddTitle'])) {
410 $title = htmlspecialchars(sprintf(
411 $languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createnew.link'),
412 $languageService->sL(
$GLOBALS[
'TCA'][$conf[
'foreign_table']][
'ctrl'][
'title'])
414 } elseif (isset($conf[
'appearance'][
'newRecordLinkTitle']) && $conf[
'appearance'][
'newRecordLinkTitle'] !==
'') {
415 $title = htmlspecialchars($languageService->sL($conf[
'appearance'][
'newRecordLinkTitle']));
419 $title = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localizeAllRecords'));
420 $icon =
'actions-document-localize';
421 $className =
'typo3-localizationLink';
422 $attributes[
'class'] =
'btn btn-default';
423 $attributes[
'onclick'] =
'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) .
', \'localize\')';
426 $title = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:synchronizeWithOriginalLanguage'));
427 $icon =
'actions-document-synchronize';
428 $className =
'typo3-synchronizationLink';
429 $attributes[
'class'] =
'btn btn-default inlineNewButton ' . $this->inlineData[
'config'][$nameObject][
'md5'];
430 $attributes[
'onclick'] =
'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) .
', \'synchronize\')';
438 $icon = $icon ? $this->iconFactory->getIcon($icon,
Icon::SIZE_SMALL)->render() :
'';
439 $link = $this->
wrapWithAnchor($icon .
' ' . $title,
'#', $attributes);
440 return '<div' . ($className ?
' class="' . $className .
'"' :
'') .
'title="' . $title .
'">' . $link .
'</div>';
453 $attributes[
'href'] = trim($link ?:
'#');
454 return '<a ' . GeneralUtility::implodeAttributes($attributes,
true,
true) .
'>' . $text .
'</a>';
469 $groupFieldConfiguration = $inlineConfiguration[
'selectorOrUniqueConfiguration'][
'config'];
471 $foreign_table = $inlineConfiguration[
'foreign_table'];
472 $allowed = $groupFieldConfiguration[
'allowed'];
473 $currentStructureDomObjectIdPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
474 $objectPrefix = $currentStructureDomObjectIdPrefix .
'-' . $foreign_table;
475 $nameObject = $currentStructureDomObjectIdPrefix;
478 $elementBrowserEnabled =
true;
479 if (!empty($inlineConfiguration[
'appearance'][
'createNewRelationLinkTitle'])) {
480 $createNewRelationText = htmlspecialchars($languageService->sL($inlineConfiguration[
'appearance'][
'createNewRelationLinkTitle']));
482 $createNewRelationText = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createNewRelation'));
484 if (is_array($groupFieldConfiguration[
'appearance'])) {
485 if (isset($groupFieldConfiguration[
'appearance'][
'elementBrowserType'])) {
486 $mode = $groupFieldConfiguration[
'appearance'][
'elementBrowserType'];
488 if ($mode ===
'file') {
491 if (isset($inlineConfiguration[
'appearance'][
'fileUploadAllowed'])) {
492 $showUpload = (bool)$inlineConfiguration[
'appearance'][
'fileUploadAllowed'];
494 if (isset($groupFieldConfiguration[
'appearance'][
'elementBrowserAllowed'])) {
495 $allowed = $groupFieldConfiguration[
'appearance'][
'elementBrowserAllowed'];
497 if (isset($inlineConfiguration[
'appearance'][
'elementBrowserEnabled'])) {
498 $elementBrowserEnabled = (bool)$inlineConfiguration[
'appearance'][
'elementBrowserEnabled'];
502 $allowed = implode(
',', GeneralUtility::trimExplode(
',', $allowed,
true));
503 $browserParams =
'|||' . $allowed .
'|' . $objectPrefix .
'|inline.checkUniqueElement||inline.importElement';
504 $onClick =
'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($mode) .
', ' . GeneralUtility::quoteJSvalue($browserParams) .
'); return false;';
507 if (isset($inlineConfiguration[
'inline'][
'inlineNewRelationButtonStyle'])) {
508 $buttonStyle =
' style="' . $inlineConfiguration[
'inline'][
'inlineNewRelationButtonStyle'] .
'"';
511 if ($elementBrowserEnabled) {
513 <a href="#" class="btn btn-default inlineNewRelationButton ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'"
514 ' . $buttonStyle .
' onclick="' . htmlspecialchars($onClick) .
'" title="' . $createNewRelationText .
'">
515 ' . $this->iconFactory->getIcon(
'actions-insert-record',
Icon::SIZE_SMALL)->render() .
'
516 ' . $createNewRelationText .
'
520 $isDirectFileUploadEnabled = (bool)$backendUser->uc[
'edit_docModuleUpload'];
521 $allowedArray = GeneralUtility::trimExplode(
',', $allowed,
true);
523 if (!empty($allowedArray)) {
524 $onlineMediaAllowed = array_intersect($allowedArray, $onlineMediaAllowed);
526 if ($showUpload && $isDirectFileUploadEnabled) {
527 $folder = $backendUser->getDefaultUploadFolder(
528 $this->data[
'tableName'] ===
'pages' ? $this->data[
'vanillaUid'] : $this->data[
'parentPageRow'][
'uid'],
529 $this->data[
'tableName'],
530 $this->data[
'fieldName']
533 $folder instanceof Folder
534 && $folder->getStorage()->checkUserActionPermission(
'add',
'File')
536 $maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
537 $item .=
' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'"
540 data-insert-dropzone-before="1"
541 data-file-irre-object="' . htmlspecialchars($objectPrefix) .
'"
542 data-file-allowed="' . htmlspecialchars($allowed) .
'"
543 data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) .
'"
544 data-max-file-size="' . htmlspecialchars($maxFileSize) .
'"
546 $item .= $this->iconFactory->getIcon(
'actions-upload',
Icon::SIZE_SMALL)->render() .
' ';
547 $item .= htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:file_upload.select-and-submit'));
550 $this->requireJsModules[] = [
'TYPO3/CMS/Backend/DragUploader' =>
'function(dragUploader){dragUploader.initialize()}'];
551 if (!empty($onlineMediaAllowed)) {
553 if (isset($inlineConfiguration[
'inline'][
'inlineOnlineMediaAddButtonStyle'])) {
554 $buttonStyle =
' style="' . $inlineConfiguration[
'inline'][
'inlineOnlineMediaAddButtonStyle'] .
'"';
556 $this->requireJsModules[] =
'TYPO3/CMS/Backend/OnlineMedia';
557 $buttonText = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.button'));
558 $placeholder = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.placeholder'));
559 $buttonSubmit = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:online_media.new_media.submit'));
560 $allowedMediaUrl = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.allowEmbedSources'));
562 <span class="btn btn-default t3js-online-media-add-btn ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'"
564 data-file-irre-object="' . htmlspecialchars($objectPrefix) .
'"
565 data-online-media-allowed="' . htmlspecialchars(implode(
',', $onlineMediaAllowed)) .
'"
566 data-online-media-allowed-help-text="' . $allowedMediaUrl .
'"
567 data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) .
'"
568 title="' . $buttonText .
'"
569 data-btn-submit="' . $buttonSubmit .
'"
570 data-placeholder="' . $placeholder .
'"
572 ' . $this->iconFactory->getIcon(
'actions-online-media-add',
Icon::SIZE_SMALL)->render() .
'
573 ' . $buttonText .
'</span>';
578 $item =
'<div class="form-control-wrap">' . $item .
'</div>';
580 $allowedLabelKey = ($mode ===
'file') ?
'allowedFileExtensions' :
'allowedRelations';
581 $allowedLabel = htmlspecialchars($languageService->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . $allowedLabelKey));
582 foreach ($allowedArray as $allowedItem) {
583 $allowedList .=
'<span class="label label-success">' . strtoupper($allowedItem) .
'</span> ';
585 if (!empty($allowedList)) {
586 $item .=
'<div class="help-block">' . $allowedLabel .
'<br>' . $allowedList .
'</div>';
588 $item =
'<div class="form-group t3js-formengine-validation-marker">' . $item .
'</div>';
602 $possibleRecords = $config[
'selectorOrUniquePossibleRecords'];
603 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
606 foreach ($possibleRecords as $p) {
607 if (!in_array($p[1], $uniqueIds)) {
608 $opt[] =
'<option value="' . htmlspecialchars($p[1]) .
'">' . htmlspecialchars($p[0]) .
'</option>';
612 $size = (int)$config[
'size'];
614 $onChange =
'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject .
'-' . $config[
'foreign_table']) .
')';
616 <select id="' . $nameObject .
'-' . $config[
'foreign_table'] .
'_selector" class="form-control"' . ($size ?
' size="' . $size .
'"' :
'')
617 .
' onchange="' . htmlspecialchars($onChange) .
'"' . ($config[
'foreign_unique'] ?
' isunique="isunique"' :
'') .
'>
618 ' . implode(
'', $opt) .
'
626 if (!empty($config[
'appearance'][
'createNewRelationLinkTitle'])) {
627 $createNewRelationText = htmlspecialchars($this->
getLanguageService()->sL($config[
'appearance'][
'createNewRelationLinkTitle']));
629 $createNewRelationText = htmlspecialchars($this->
getLanguageService()->sL(
'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createNewRelation'));
632 <span class="input-group-btn">
633 <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) .
'" title="' . $createNewRelationText .
'">
634 ' . $this->iconFactory->getIcon(
'actions-add',
Icon::SIZE_SMALL)->render() . $createNewRelationText .
'
639 <span class="input-group-btn btn"></span>';
643 $item =
'<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'">' . $item .
'</div>';
657 $flexFormParts =
null;
659 if (preg_match(
'#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
660 $flexFormParts = GeneralUtility::trimExplode(
662 trim($matches[1],
'[]')
665 return $flexFormParts;