2 namespace TYPO3\CMS\Backend\Form\Container;
72 parent::__construct($nodeFactory, $data);
85 $this->inlineData = $this->data[
'inlineData'];
92 $table = $this->data[
'tableName'];
93 $row = $this->data[
'databaseRow'];
94 $field = $this->data[
'fieldName'];
95 $parameterArray = $this->data[
'parameterArray'];
99 $config = $parameterArray[
'fieldConf'][
'config'];
100 $foreign_table = $config[
'foreign_table'];
104 $language = (int)$row[
$GLOBALS[
'TCA'][$table][
'ctrl'][
'languageField']];
108 $newStructureItem = array(
110 'uid' => $row[
'uid'],
116 if (!empty($parameterArray[
'itemFormElName'])) {
118 if ($flexFormParts !== null) {
119 $newStructureItem[
'flexform'] = $flexFormParts;
127 $nameObject =
$inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
129 $config[
'inline'][
'first'] =
false;
131 if (!is_array($this->data[
'parameterArray'][
'fieldConf'][
'children'])) {
132 $this->data[
'parameterArray'][
'fieldConf'][
'children'] = array();
134 $firstChild = reset($this->data[
'parameterArray'][
'fieldConf'][
'children']);
135 if (isset($firstChild[
'databaseRow'][
'uid'])) {
136 $config[
'inline'][
'first'] = $firstChild[
'databaseRow'][
'uid'];
138 $config[
'inline'][
'last'] =
false;
139 $lastChild = end($this->data[
'parameterArray'][
'fieldConf'][
'children']);
140 if (isset($lastChild[
'databaseRow'][
'uid'])) {
141 $config[
'inline'][
'last'] = $lastChild[
'databaseRow'][
'uid'];
146 $this->inlineData[
'config'][$nameObject] = array(
147 'table' => $foreign_table,
148 'md5' => md5($nameObject)
150 $this->inlineData[
'config'][$nameObject .
'-' . $foreign_table] = array(
151 'min' => $config[
'minitems'],
152 'max' => $config[
'maxitems'],
153 'sortable' => $config[
'appearance'][
'useSortable'],
155 'table' => $top[
'table'],
163 $this->inlineData[
'nested'][$nameObject] = $this->data[
'tabAndInlineStack'];
168 if ($config[
'foreign_unique']) {
170 $type = $config[
'selectorOrUniqueConfiguration'][
'config'][
'type'] ===
'select' ?
'select' :
'groupdb';
171 foreach ($parameterArray[
'fieldConf'][
'children'] as $child) {
173 if (!$child[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
174 $value = $child[
'databaseRow'][$config[
'foreign_unique']];
176 if ($type ===
'select') {
178 $value = $value[
'0'];
182 $itemParts = explode(
'_', $valueParts[0]);
184 'uid' => array_pop($itemParts),
185 'table' => implode(
'_', $itemParts)
189 $uniqueIds[$child[
'databaseRow'][
'uid']] = $value;
192 $possibleRecords = $config[
'selectorOrUniquePossibleRecords'];
193 $possibleRecordsUidToTitle = [];
194 foreach ($possibleRecords as $possibleRecord) {
195 $possibleRecordsUidToTitle[$possibleRecord[1]] = $possibleRecord[0];
197 $uniqueMax = $config[
'appearance'][
'useCombination'] || empty($possibleRecords) ? -1 : count($possibleRecords);
198 $this->inlineData[
'unique'][$nameObject .
'-' . $foreign_table] = array(
200 'used' => $uniqueIds,
202 'table' => $foreign_table,
203 'elTable' => $config[
'selectorOrUniqueConfiguration'][
'foreignTable'],
204 'field' => $config[
'foreign_unique'],
205 'selector' => $config[
'selectorOrUniqueConfiguration'][
'isSelector'] ? $type :
false,
206 'possible' => $possibleRecordsUidToTitle,
213 $isLocalizedParent = $language > 0
214 && $row[
$GLOBALS[
'TCA'][$table][
'ctrl'][
'transOrigPointerField']][0] > 0
216 $numberOfFullLocalizedChildren = 0;
217 $numberOfNotYetLocalizedChildren = 0;
218 foreach ($this->data[
'parameterArray'][
'fieldConf'][
'children'] as $child) {
219 if (!$child[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
220 $numberOfFullLocalizedChildren ++;
222 if ($isLocalizedParent && $child[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
223 $numberOfNotYetLocalizedChildren ++;
228 $localizationLinks =
'';
229 if ($numberOfNotYetLocalizedChildren) {
231 if (isset($config[
'appearance'][
'showAllLocalizationLink']) && $config[
'appearance'][
'showAllLocalizationLink']) {
232 $localizationLinks =
' ' . $this->
getLevelInteractionLink(
'localize', $nameObject .
'-' . $foreign_table, $config);
235 if (isset($config[
'appearance'][
'showSynchronizationLink']) && $config[
'appearance'][
'showSynchronizationLink']) {
236 $localizationLinks .=
' ' . $this->
getLevelInteractionLink(
'synchronize', $nameObject .
'-' . $foreign_table, $config);
241 if ($numberOfFullLocalizedChildren >= $config[
'maxitems'] || $uniqueMax > 0 && $numberOfFullLocalizedChildren >= $uniqueMax) {
242 $config[
'inline'][
'inlineNewButtonStyle'] =
'display: none;';
243 $config[
'inline'][
'inlineNewRelationButtonStyle'] =
'display: none;';
250 $html =
'<div class="form-group" id="' . $nameObject .
'">';
252 if ($config[
'appearance'][
'levelLinksPosition'] ===
'both' || $config[
'appearance'][
'levelLinksPosition'] ===
'top') {
253 $html .=
'<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks .
'</div>';
257 if ($config[
'foreign_selector'] && $config[
'appearance'][
'showPossibleRecordsSelector'] !==
false) {
258 if ($config[
'selectorOrUniqueConfiguration'][
'config'][
'type'] ===
'select') {
263 $html .= $selectorBox . $localizationLinks;
266 $title = $languageService->sL(trim($parameterArray[
'fieldConf'][
'label']));
267 $html .=
'<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) .
'" id="' . $nameObject .
'_records">';
269 $sortableRecordUids = [];
270 foreach ($this->data[
'parameterArray'][
'fieldConf'][
'children'] as $options) {
271 $options[
'inlineParentUid'] = $row[
'uid'];
273 $options[
'inlineParentConfig'] = $config;
276 $options[
'inlineExpandCollapseStateArray'] = $this->data[
'inlineExpandCollapseStateArray'];
277 $options[
'renderType'] =
'inlineRecordContainer';
278 $childResult = $this->nodeFactory->create($options)->render();
279 $html .= $childResult[
'html'];
280 $childArray[
'html'] =
'';
282 if (!$options[
'isInlineDefaultLanguageRecordInLocalizedParentContext']) {
285 $sortableRecordUids[] = $options[
'databaseRow'][
'uid'];
292 if ($config[
'appearance'][
'levelLinksPosition'] ===
'both' || $config[
'appearance'][
'levelLinksPosition'] ===
'bottom') {
293 $html .= $levelLinks . $localizationLinks;
295 if (is_array($config[
'customControls'])) {
296 $html .=
'<div id="' . $nameObject .
'_customControls">';
297 foreach ($config[
'customControls'] as $customControlConfig) {
302 'nameObject' => $nameObject,
303 'nameForm' => $nameForm,
311 if (count($sortableRecordUids) > 1 && $config[
'appearance'][
'useSortable']) {
312 $resultArray[
'additionalJavaScriptPost'][] =
'inline.createDragAndDropSorting("' . $nameObject .
'_records' .
'");';
314 $resultArray[
'requireJsModules'] = array_merge($resultArray[
'requireJsModules'], $this->requireJsModules);
317 $html .=
'<input type="hidden" name="' . $nameForm .
'" value="' . implode(
',', $sortableRecordUids) .
'" '
319 .
' class="inlineRecord" />';
323 $resultArray[
'html'] = $html;
339 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
340 $attributes = array();
343 $title = $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:cm.createnew',
true);
344 $icon =
'actions-document-new';
345 $className =
'typo3-newRecordLink';
346 $attributes[
'class'] =
'btn btn-default inlineNewButton ' . $this->inlineData[
'config'][$nameObject][
'md5'];
348 if (!empty($conf[
'inline'][
'inlineNewButtonStyle'])) {
349 $attributes[
'style'] = $conf[
'inline'][
'inlineNewButtonStyle'];
351 if (!empty($conf[
'appearance'][
'newRecordLinkAddTitle'])) {
353 $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:cm.createnew.link',
true),
354 $languageService->sL(
$GLOBALS[
'TCA'][$conf[
'foreign_table']][
'ctrl'][
'title'],
true)
356 }
elseif (isset($conf[
'appearance'][
'newRecordLinkTitle']) && $conf[
'appearance'][
'newRecordLinkTitle'] !==
'') {
357 $title = $languageService->sL($conf[
'appearance'][
'newRecordLinkTitle'],
true);
361 $title = $languageService->sL(
'LLL:EXT:lang/locallang_misc.xlf:localizeAllRecords',
true);
362 $icon =
'actions-document-localize';
363 $className =
'typo3-localizationLink';
364 $attributes[
'class'] =
'btn btn-default';
368 $title = $languageService->sL(
'LLL:EXT:lang/locallang_misc.xlf:synchronizeWithOriginalLanguage',
true);
369 $icon =
'actions-document-synchronize';
370 $className =
'typo3-synchronizationLink';
371 $attributes[
'class'] =
'btn btn-default inlineNewButton ' . $this->inlineData[
'config'][$nameObject][
'md5'];
372 $attributes[
'onclick'] =
'return inline.synchronizeLocalizeRecords(' .
GeneralUtility::quoteJSvalue($objectPrefix) .
', \'synchronize\')';
380 $icon = $icon ? $this->iconFactory->getIcon($icon,
Icon::SIZE_SMALL)->render() :
'';
382 return '<div' . ($className ?
' class="' . $className .
'"' :
'') .
'title="' . $title .
'">' . $link .
'</div>';
396 $result =
'<a href="' . ($link ?:
'#') .
'"';
397 foreach ($attributes as $key => $value) {
398 $result .=
' ' . $key .
'="' . htmlspecialchars(trim($value)) .
'"';
400 $result .=
'>' . $text .
'</a>';
416 $groupFieldConfiguration = $inlineConfiguration[
'selectorOrUniqueConfiguration'][
'config'];
418 $foreign_table = $inlineConfiguration[
'foreign_table'];
419 $allowed = $groupFieldConfiguration[
'allowed'];
420 $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']) .
'-' . $foreign_table;
421 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
424 if (!empty($inlineConfiguration[
'appearance'][
'createNewRelationLinkTitle'])) {
425 $createNewRelationText = $languageService->sL($inlineConfiguration[
'appearance'][
'createNewRelationLinkTitle'],
true);
427 $createNewRelationText = $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation',
true);
429 if (is_array($groupFieldConfiguration[
'appearance'])) {
430 if (isset($groupFieldConfiguration[
'appearance'][
'elementBrowserType'])) {
431 $mode = $groupFieldConfiguration[
'appearance'][
'elementBrowserType'];
433 if ($mode ===
'file') {
436 if (isset($inlineConfiguration[
'appearance'][
'fileUploadAllowed'])) {
437 $showUpload = (bool)$inlineConfiguration[
'appearance'][
'fileUploadAllowed'];
439 if (isset($groupFieldConfiguration[
'appearance'][
'elementBrowserAllowed'])) {
440 $allowed = $groupFieldConfiguration[
'appearance'][
'elementBrowserAllowed'];
443 $browserParams =
'|||' . $allowed .
'|' . $objectPrefix .
'|inline.checkUniqueElement||inline.importElement';
447 if (isset($inlineConfiguration[
'inline'][
'inlineNewRelationButtonStyle'])) {
448 $buttonStyle =
' style="' . $inlineConfiguration[
'inline'][
'inlineNewRelationButtonStyle'] .
'"';
452 <a href="#" class="btn btn-default inlineNewRelationButton ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'"
453 ' . $buttonStyle .
' onclick="' . htmlspecialchars($onClick) .
'" title="' . $createNewRelationText .
'">
454 ' . $this->iconFactory->getIcon(
'actions-insert-record',
Icon::SIZE_SMALL)->render() .
'
455 ' . $createNewRelationText .
'
460 if (!empty($allowedArray)) {
461 $onlineMediaAllowed = array_intersect($allowedArray, $onlineMediaAllowed);
463 if ($showUpload && $isDirectFileUploadEnabled) {
464 $folder = $backendUser->getDefaultUploadFolder(
465 $this->data[
'parentPageRow'][
'uid'],
466 $this->data[
'tableName'],
467 $this->data[
'fieldName']
474 $item .=
' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'"
476 data-dropzone-target="#' . htmlspecialchars($this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid'])) .
'"
477 data-insert-dropzone-before="1"
478 data-file-irre-object="' . htmlspecialchars($objectPrefix) .
'"
479 data-file-allowed="' . htmlspecialchars($allowed) .
'"
480 data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) .
'"
481 data-max-file-size="' . htmlspecialchars($maxFileSize) .
'"
483 $item .= $this->iconFactory->getIcon(
'actions-upload',
Icon::SIZE_SMALL)->render() .
' ';
484 $item .= $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:file_upload.select-and-submit',
true);
487 $this->requireJsModules[] = [
'TYPO3/CMS/Backend/DragUploader' =>
'function(dragUploader){dragUploader.initialize()}'];
488 if (!empty($onlineMediaAllowed)) {
489 $this->requireJsModules[] =
'TYPO3/CMS/Backend/OnlineMedia';
490 $buttonText = $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:online_media.new_media.button',
true);
491 $placeholder = $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:online_media.new_media.placeholder',
true);
492 $buttonSubmit = $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:online_media.new_media.submit',
true);
494 <span class="btn btn-default t3js-online-media-add-btn ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'"
495 data-file-irre-object="' . htmlspecialchars($objectPrefix) .
'"
496 data-online-media-allowed="' . htmlspecialchars(implode(
',', $onlineMediaAllowed)) .
'"
497 data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) .
'"
498 title="' . $buttonText .
'"
499 data-btn-submit="' . $buttonSubmit .
'"
500 data-placeholder="' . $placeholder .
'"
502 ' . $this->iconFactory->getIcon(
'actions-online-media-add',
Icon::SIZE_SMALL)->render() .
'
503 ' . $buttonText .
'</span>';
508 $item =
'<div class="form-control-wrap">' . $item .
'</div>';
510 $allowedLabel = $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions',
true);
511 foreach ($allowedArray as $allowedItem) {
512 $allowedList .=
'<span class="label label-success">' . strtoupper($allowedItem) .
'</span> ';
514 if (!empty($allowedList)) {
515 $item .=
'<div class="help-block">' . $allowedLabel .
'<br>' . $allowedList .
'</div>';
517 $item =
'<div class="form-group t3js-formengine-validation-marker">' . $item .
'</div>';
531 $possibleRecords = $config[
'selectorOrUniquePossibleRecords'];
532 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data[
'inlineFirstPid']);
535 foreach ($possibleRecords as $p) {
536 if (!in_array($p[1], $uniqueIds)) {
537 $opt[] =
'<option value="' . htmlspecialchars($p[1]) .
'">' . htmlspecialchars($p[0]) .
'</option>';
541 $size = (int)$config[
'size'];
545 <select id="' . $nameObject .
'-' . $config[
'foreign_table'] .
'_selector" class="form-control"' . ($size ?
' size="' . $size .
'"' :
'')
546 .
' onchange="' . htmlspecialchars($onChange) .
'"' . ($config[
'foreign_unique'] ?
' isunique="isunique"' :
'') .
'>
547 ' . implode(
'', $opt) .
'
555 if (!empty($config[
'appearance'][
'createNewRelationLinkTitle'])) {
556 $createNewRelationText = $this->
getLanguageService()->sL($config[
'appearance'][
'createNewRelationLinkTitle'],
true);
558 $createNewRelationText = $this->
getLanguageService()->sL(
'LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation',
true);
561 <span class="input-group-btn">
562 <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) .
'" title="' . $createNewRelationText .
'">
563 ' . $this->iconFactory->getIcon(
'actions-document-new',
Icon::SIZE_SMALL)->render() . $createNewRelationText .
'
568 <span class="input-group-btn btn"></span>';
572 $item =
'<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData[
'config'][$nameObject][
'md5'] .
'">' . $item .
'</div>';
586 $flexFormParts = null;
588 if (preg_match(
'#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
591 trim($matches[1],
'[]')
594 return $flexFormParts;