TYPO3 CMS  TYPO3_7-6
InlineControlContainer.php
Go to the documentation of this file.
1 <?php
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 
41 {
47  protected $inlineData = [];
48 
53 
57  protected $iconFactory;
58 
62  protected $requireJsModules = [];
63 
70  public function __construct(NodeFactory $nodeFactory, array $data)
71  {
72  parent::__construct($nodeFactory, $data);
73  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
74  }
75 
81  public function render()
82  {
83  $languageService = $this->getLanguageService();
84 
85  $this->inlineData = $this->data['inlineData'];
86 
88  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
89  $this->inlineStackProcessor = $inlineStackProcessor;
90  $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
91 
92  $table = $this->data['tableName'];
93  $row = $this->data['databaseRow'];
94  $field = $this->data['fieldName'];
95  $parameterArray = $this->data['parameterArray'];
96 
97  $resultArray = $this->initializeResultArray();
98 
99  $config = $parameterArray['fieldConf']['config'];
100  $foreign_table = $config['foreign_table'];
101 
102  $language = 0;
104  $language = (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
105  }
106 
107  // Add the current inline job to the structure stack
108  $newStructureItem = [
109  'table' => $table,
110  'uid' => $row['uid'],
111  'field' => $field,
112  'config' => $config,
113  'localizationMode' => BackendUtility::getInlineLocalizationMode($table, $config),
114  ];
115  // Extract FlexForm parts (if any) from element name, e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
116  if (!empty($parameterArray['itemFormElName'])) {
117  $flexFormParts = $this->extractFlexFormParts($parameterArray['itemFormElName']);
118  if ($flexFormParts !== null) {
119  $newStructureItem['flexform'] = $flexFormParts;
120  }
121  }
122  $inlineStackProcessor->pushStableStructureItem($newStructureItem);
123 
124  // Transport the flexform DS identifier fields to the FormAjaxInlineController
125  if (!empty($newStructureItem['flexform'])
126  && isset($this->data['processedTca']['columns'][$field]['config']['ds']['meta']['dataStructurePointers'])
127  ) {
128  $config['flexDataStructurePointers'] = $this->data['processedTca']['columns'][$field]['config']['ds']['meta']['dataStructurePointers'];
129  }
130 
131  // e.g. data[<table>][<uid>][<field>]
132  $nameForm = $inlineStackProcessor->getCurrentStructureFormPrefix();
133  // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
134  $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
135 
136  $config['inline']['first'] = false;
137  // @todo: This initialization shouldn't be required data provider should take care this is set?
138  if (!is_array($this->data['parameterArray']['fieldConf']['children'])) {
139  $this->data['parameterArray']['fieldConf']['children'] = [];
140  }
141  $firstChild = reset($this->data['parameterArray']['fieldConf']['children']);
142  if (isset($firstChild['databaseRow']['uid'])) {
143  $config['inline']['first'] = $firstChild['databaseRow']['uid'];
144  }
145  $config['inline']['last'] = false;
146  $lastChild = end($this->data['parameterArray']['fieldConf']['children']);
147  if (isset($lastChild['databaseRow']['uid'])) {
148  $config['inline']['last'] = $lastChild['databaseRow']['uid'];
149  }
150 
151  $top = $inlineStackProcessor->getStructureLevel(0);
152 
153  $this->inlineData['config'][$nameObject] = [
154  'table' => $foreign_table,
155  'md5' => md5($nameObject)
156  ];
157  $this->inlineData['config'][$nameObject . '-' . $foreign_table] = [
158  'min' => $config['minitems'],
159  'max' => $config['maxitems'],
160  'sortable' => $config['appearance']['useSortable'],
161  'top' => [
162  'table' => $top['table'],
163  'uid' => $top['uid']
164  ],
165  'context' => [
166  'config' => $config,
167  'hmac' => GeneralUtility::hmac(serialize($config)),
168  ],
169  ];
170  $this->inlineData['nested'][$nameObject] = $this->data['tabAndInlineStack'];
171 
172  $uniqueMax = 0;
173  $uniqueIds = [];
174 
175  if ($config['foreign_unique']) {
176  // Add inlineData['unique'] with JS unique configuration
177  $type = $config['selectorOrUniqueConfiguration']['config']['type'] === 'select' ? 'select' : 'groupdb';
178  foreach ($parameterArray['fieldConf']['children'] as $child) {
179  // Determine used unique ids, skip not localized records
180  if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
181  $value = $child['databaseRow'][$config['foreign_unique']];
182  // We're assuming there is only one connected value here for both select and group
183  if ($type === 'select') {
184  // A resolved select field is an array - take first value
185  $value = $value['0'];
186  } else {
187  // A group field is still a list with pipe separated uid|tableName
188  $valueParts = GeneralUtility::trimExplode('|', $value);
189  $itemParts = explode('_', $valueParts[0]);
190  $value = [
191  'uid' => array_pop($itemParts),
192  'table' => implode('_', $itemParts)
193  ];
194  }
195  // @todo: This is weird, $value has different structure for group and select fields?
196  $uniqueIds[$child['databaseRow']['uid']] = $value;
197  }
198  }
199  $possibleRecords = $config['selectorOrUniquePossibleRecords'];
200  $possibleRecordsUidToTitle = [];
201  foreach ($possibleRecords as $possibleRecord) {
202  $possibleRecordsUidToTitle[$possibleRecord[1]] = $possibleRecord[0];
203  }
204  $uniqueMax = $config['appearance']['useCombination'] || empty($possibleRecords) ? -1 : count($possibleRecords);
205  $this->inlineData['unique'][$nameObject . '-' . $foreign_table] = [
206  'max' => $uniqueMax,
207  'used' => $uniqueIds,
208  'type' => $type,
209  'table' => $foreign_table,
210  'elTable' => $config['selectorOrUniqueConfiguration']['foreignTable'],
211  'field' => $config['foreign_unique'],
212  'selector' => $config['selectorOrUniqueConfiguration']['isSelector'] ? $type : false,
213  'possible' => $possibleRecordsUidToTitle,
214  ];
215  }
216 
217  $resultArray['inlineData'] = $this->inlineData;
218 
219  // @todo: It might be a good idea to have something like "isLocalizedRecord" or similar set by a data provider
220  $uidOfDefaultRecord = $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
221  $isLocalizedParent = $language > 0
222  && (isset($uidOfDefaultRecord[0]) ? $uidOfDefaultRecord[0] : $uidOfDefaultRecord) > 0
224  $numberOfFullLocalizedChildren = 0;
225  $numberOfNotYetLocalizedChildren = 0;
226  foreach ($this->data['parameterArray']['fieldConf']['children'] as $child) {
227  if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
228  $numberOfFullLocalizedChildren ++;
229  }
230  if ($isLocalizedParent && $child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
231  $numberOfNotYetLocalizedChildren ++;
232  }
233  }
234 
235  // Render the localization links if needed
236  $localizationLinks = '';
237  if ($numberOfNotYetLocalizedChildren) {
238  // Add the "Localize all records" link before all child records:
239  if (isset($config['appearance']['showAllLocalizationLink']) && $config['appearance']['showAllLocalizationLink']) {
240  $localizationLinks = ' ' . $this->getLevelInteractionLink('localize', $nameObject . '-' . $foreign_table, $config);
241  }
242  // Add the "Synchronize with default language" link before all child records:
243  if (isset($config['appearance']['showSynchronizationLink']) && $config['appearance']['showSynchronizationLink']) {
244  $localizationLinks .= ' ' . $this->getLevelInteractionLink('synchronize', $nameObject . '-' . $foreign_table, $config);
245  }
246  }
247 
248  // Define how to show the "Create new record" link - if there are more than maxitems, hide it
249  if ($numberOfFullLocalizedChildren >= $config['maxitems'] || $uniqueMax > 0 && $numberOfFullLocalizedChildren >= $uniqueMax) {
250  $config['inline']['inlineNewButtonStyle'] = 'display: none;';
251  $config['inline']['inlineNewRelationButtonStyle'] = 'display: none;';
252  $config['inline']['inlineOnlineMediaAddButtonStyle'] = 'display: none;';
253  }
254 
255  // Render the level links (create new record):
256  $levelLinks = '';
257  if (!empty($config['appearance']['enabledControls']['new'])) {
258  $levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . '-' . $foreign_table, $config);
259  }
260  // Wrap all inline fields of a record with a <div> (like a container)
261  $html = '<div class="form-group" id="' . $nameObject . '">';
262  // Add the level links before all child records:
263  if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'top') {
264  $html .= '<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks . '</div>';
265  }
266 
267  // If it's required to select from possible child records (reusable children), add a selector box
268  if ($config['foreign_selector'] && $config['appearance']['showPossibleRecordsSelector'] !== false) {
269  if ($config['selectorOrUniqueConfiguration']['config']['type'] === 'select') {
270  $selectorBox = $this->renderPossibleRecordsSelectorTypeSelect($config, $uniqueIds);
271  } else {
272  $selectorBox = $this->renderPossibleRecordsSelectorTypeGroupDB($config);
273  }
274  $html .= $selectorBox . $localizationLinks;
275  }
276 
277  $title = $languageService->sL(trim($parameterArray['fieldConf']['label']));
278  $html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
279 
280  $sortableRecordUids = [];
281  foreach ($this->data['parameterArray']['fieldConf']['children'] as $options) {
282  $options['inlineParentUid'] = $row['uid'];
283  $options['inlineFirstPid'] = $this->data['inlineFirstPid'];
284  // @todo: this can be removed if this container no longer sets additional info to $config
285  $options['inlineParentConfig'] = $config;
286  $options['inlineData'] = $this->inlineData;
287  $options['inlineStructure'] = $inlineStackProcessor->getStructure();
288  $options['inlineExpandCollapseStateArray'] = $this->data['inlineExpandCollapseStateArray'];
289  $options['renderType'] = 'inlineRecordContainer';
290  $childResult = $this->nodeFactory->create($options)->render();
291  $html .= $childResult['html'];
292  $childArray['html'] = '';
293  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult);
294  if (!$options['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
295  // Don't add record to list of "valid" uids if it is only the default
296  // language record of a not yet localized child
297  $sortableRecordUids[] = $options['databaseRow']['uid'];
298  }
299  }
300 
301  $html .= '</div>';
302 
303  // Add the level links after all child records:
304  if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'bottom') {
305  $html .= $levelLinks . $localizationLinks;
306  }
307  if (is_array($config['customControls'])) {
308  $html .= '<div id="' . $nameObject . '_customControls">';
309  foreach ($config['customControls'] as $customControlConfig) {
310  $parameters = [
311  'table' => $table,
312  'field' => $field,
313  'row' => $row,
314  'nameObject' => $nameObject,
315  'nameForm' => $nameForm,
316  'config' => $config
317  ];
318  $html .= GeneralUtility::callUserFunction($customControlConfig, $parameters, $this);
319  }
320  $html .= '</div>';
321  }
322  // Add Drag&Drop functions for sorting to FormEngine::$additionalJS_post
323  if (count($sortableRecordUids) > 1 && $config['appearance']['useSortable']) {
324  $resultArray['additionalJavaScriptPost'][] = 'inline.createDragAndDropSorting("' . $nameObject . '_records' . '");';
325  }
326  $resultArray['requireJsModules'] = array_merge($resultArray['requireJsModules'], $this->requireJsModules);
327 
328  // Publish the uids of the child records in the given order to the browser
329  $html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $sortableRecordUids) . '" '
330  . $this->getValidationDataAsDataAttribute(['type' => 'inline', 'minitems' => $config['minitems'], 'maxitems' => $config['maxitems']])
331  . ' class="inlineRecord" />';
332  // Close the wrap for all inline fields (container)
333  $html .= '</div>';
334 
335  $resultArray['html'] = $html;
336  return $resultArray;
337  }
338 
348  protected function getLevelInteractionLink($type, $objectPrefix, $conf = [])
349  {
350  $languageService = $this->getLanguageService();
351  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
352  $attributes = [];
353  switch ($type) {
354  case 'newRecord':
355  $title = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createnew', true);
356  $icon = 'actions-document-new';
357  $className = 'typo3-newRecordLink';
358  $attributes['class'] = 'btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
359  $attributes['onclick'] = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ')';
360  if (!empty($conf['inline']['inlineNewButtonStyle'])) {
361  $attributes['style'] = $conf['inline']['inlineNewButtonStyle'];
362  }
363  if (!empty($conf['appearance']['newRecordLinkAddTitle'])) {
364  $title = sprintf(
365  $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createnew.link', true),
366  $languageService->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'], true)
367  );
368  } elseif (isset($conf['appearance']['newRecordLinkTitle']) && $conf['appearance']['newRecordLinkTitle'] !== '') {
369  $title = $languageService->sL($conf['appearance']['newRecordLinkTitle'], true);
370  }
371  break;
372  case 'localize':
373  $title = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localizeAllRecords', true);
374  $icon = 'actions-document-localize';
375  $className = 'typo3-localizationLink';
376  $attributes['class'] = 'btn btn-default';
377  $attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'localize\')';
378  break;
379  case 'synchronize':
380  $title = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:synchronizeWithOriginalLanguage', true);
381  $icon = 'actions-document-synchronize';
382  $className = 'typo3-synchronizationLink';
383  $attributes['class'] = 'btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
384  $attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'synchronize\')';
385  break;
386  default:
387  $title = '';
388  $icon = '';
389  $className = '';
390  }
391  // Create the link:
392  $icon = $icon ? $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() : '';
393  $link = $this->wrapWithAnchor($icon . $title, '#', $attributes);
394  return '<div' . ($className ? ' class="' . $className . '"' : '') . 'title="' . $title . '">' . $link . '</div>';
395  }
396 
405  protected function wrapWithAnchor($text, $link, $attributes = [])
406  {
407  $link = trim($link);
408  $result = '<a href="' . ($link ?: '#') . '"';
409  foreach ($attributes as $key => $value) {
410  $result .= ' ' . $key . '="' . htmlspecialchars(trim($value)) . '"';
411  }
412  $result .= '>' . $text . '</a>';
413  return $result;
414  }
415 
423  protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration)
424  {
425  $backendUser = $this->getBackendUserAuthentication();
426  $languageService = $this->getLanguageService();
427 
428  $groupFieldConfiguration = $inlineConfiguration['selectorOrUniqueConfiguration']['config'];
429 
430  $foreign_table = $inlineConfiguration['foreign_table'];
431  $allowed = $groupFieldConfiguration['allowed'];
432  $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $foreign_table;
433  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
434  $mode = 'db';
435  $showUpload = false;
436  if (!empty($inlineConfiguration['appearance']['createNewRelationLinkTitle'])) {
437  $createNewRelationText = $languageService->sL($inlineConfiguration['appearance']['createNewRelationLinkTitle'], true);
438  } else {
439  $createNewRelationText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
440  }
441  if (is_array($groupFieldConfiguration['appearance'])) {
442  if (isset($groupFieldConfiguration['appearance']['elementBrowserType'])) {
443  $mode = $groupFieldConfiguration['appearance']['elementBrowserType'];
444  }
445  if ($mode === 'file') {
446  $showUpload = true;
447  }
448  if (isset($inlineConfiguration['appearance']['fileUploadAllowed'])) {
449  $showUpload = (bool)$inlineConfiguration['appearance']['fileUploadAllowed'];
450  }
451  if (isset($groupFieldConfiguration['appearance']['elementBrowserAllowed'])) {
452  $allowed = $groupFieldConfiguration['appearance']['elementBrowserAllowed'];
453  }
454  }
455  $browserParams = '|||' . $allowed . '|' . $objectPrefix . '|inline.checkUniqueElement||inline.importElement';
456  $onClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($mode) . ', ' . GeneralUtility::quoteJSvalue($browserParams) . '); return false;';
457 
458  $buttonStyle = '';
459  if (isset($inlineConfiguration['inline']['inlineNewRelationButtonStyle'])) {
460  $buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineNewRelationButtonStyle'] . '"';
461  }
462 
463  $item = '
464  <a href="#" class="btn btn-default inlineNewRelationButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
465  ' . $buttonStyle . ' onclick="' . htmlspecialchars($onClick) . '" title="' . $createNewRelationText . '">
466  ' . $this->iconFactory->getIcon('actions-insert-record', Icon::SIZE_SMALL)->render() . '
467  ' . $createNewRelationText . '
468  </a>';
469  $isDirectFileUploadEnabled = (bool)$this->getBackendUserAuthentication()->uc['edit_docModuleUpload'];
470  $allowedArray = GeneralUtility::trimExplode(',', $allowed, true);
471  $onlineMediaAllowed = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
472  if (!empty($allowedArray)) {
473  $onlineMediaAllowed = array_intersect($allowedArray, $onlineMediaAllowed);
474  }
475  if ($showUpload && $isDirectFileUploadEnabled) {
476  $folder = $backendUser->getDefaultUploadFolder(
477  $this->data['parentPageRow']['uid'],
478  $this->data['tableName'],
479  $this->data['fieldName']
480  );
481  if (
482  $folder instanceof Folder
483  && $folder->getStorage()->checkUserActionPermission('add', 'File')
484  ) {
485  $maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
486  $item .= ' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
487  ' . $buttonStyle . '
488  data-dropzone-target="#' . htmlspecialchars($this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid'])) . '"
489  data-insert-dropzone-before="1"
490  data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
491  data-file-allowed="' . htmlspecialchars($allowed) . '"
492  data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
493  data-max-file-size="' . htmlspecialchars($maxFileSize) . '"
494  >';
495  $item .= $this->iconFactory->getIcon('actions-upload', Icon::SIZE_SMALL)->render() . ' ';
496  $item .= $languageService->sL('LLL:EXT:lang/locallang_core.xlf:file_upload.select-and-submit', true);
497  $item .= '</a>';
498 
499  $this->requireJsModules[] = ['TYPO3/CMS/Backend/DragUploader' => 'function(dragUploader){dragUploader.initialize()}'];
500  if (!empty($onlineMediaAllowed)) {
501  $buttonStyle = '';
502  if (isset($inlineConfiguration['inline']['inlineOnlineMediaAddButtonStyle'])) {
503  $buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineOnlineMediaAddButtonStyle'] . '"';
504  }
505  $this->requireJsModules[] = 'TYPO3/CMS/Backend/OnlineMedia';
506  $buttonText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.button', true);
507  $placeholder = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.placeholder', true);
508  $buttonSubmit = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:online_media.new_media.submit', true);
509  $item .= '
510  <span class="btn btn-default t3js-online-media-add-btn ' . $this->inlineData['config'][$nameObject]['md5'] . '"
511  ' . $buttonStyle . '
512  data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
513  data-online-media-allowed="' . htmlspecialchars(implode(',', $onlineMediaAllowed)) . '"
514  data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
515  title="' . $buttonText . '"
516  data-btn-submit="' . $buttonSubmit . '"
517  data-placeholder="' . $placeholder . '"
518  >
519  ' . $this->iconFactory->getIcon('actions-online-media-add', Icon::SIZE_SMALL)->render() . '
520  ' . $buttonText . '</span>';
521  }
522  }
523  }
524 
525  $item = '<div class="form-control-wrap">' . $item . '</div>';
526  $allowedList = '';
527  $allowedLabel = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions', true);
528  foreach ($allowedArray as $allowedItem) {
529  $allowedList .= '<span class="label label-success">' . strtoupper($allowedItem) . '</span> ';
530  }
531  if (!empty($allowedList)) {
532  $item .= '<div class="help-block">' . $allowedLabel . '<br>' . $allowedList . '</div>';
533  }
534  $item = '<div class="form-group t3js-formengine-validation-marker">' . $item . '</div>';
535  return $item;
536  }
537 
546  protected function renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
547  {
548  $possibleRecords = $config['selectorOrUniquePossibleRecords'];
549  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
550  // Create option tags:
551  $opt = [];
552  foreach ($possibleRecords as $p) {
553  if (!in_array($p[1], $uniqueIds)) {
554  $opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
555  }
556  }
557  // Put together the selector box:
558  $size = (int)$config['size'];
559  $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($possibleRecords) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
560  $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $config['foreign_table']) . ')';
561  $item = '
562  <select id="' . $nameObject . '-' . $config['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '')
563  . ' onchange="' . htmlspecialchars($onChange) . '"' . ($config['foreign_unique'] ? ' isunique="isunique"' : '') . '>
564  ' . implode('', $opt) . '
565  </select>';
566 
567  if ($size <= 1) {
568  // Add a "Create new relation" link for adding new relations
569  // This is necessary, if the size of the selector is "1" or if
570  // there is only one record item in the select-box, that is selected by default
571  // The selector-box creates a new relation on using an onChange event (see some line above)
572  if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
573  $createNewRelationText = $this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle'], true);
574  } else {
575  $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
576  }
577  $item .= '
578  <span class="input-group-btn">
579  <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText . '">
580  ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . $createNewRelationText . '
581  </a>
582  </span>';
583  } else {
584  $item .= '
585  <span class="input-group-btn btn"></span>';
586  }
587 
588  // Wrap the selector and add a spacer to the bottom
589  $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
590  return $item;
591  }
592 
601  protected function extractFlexFormParts($formElementName)
602  {
603  $flexFormParts = null;
604  $matches = [];
605  if (preg_match('#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
606  $flexFormParts = GeneralUtility::trimExplode(
607  '][',
608  trim($matches[1], '[]')
609  );
610  }
611  return $flexFormParts;
612  }
613 
617  protected function getBackendUserAuthentication()
618  {
619  return $GLOBALS['BE_USER'];
620  }
621 
625  protected function getLanguageService()
626  {
627  return $GLOBALS['LANG'];
628  }
629 }
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static hmac($input, $additionalSecret='')
mergeChildReturnIntoExistingResult(array $existing, array $childReturn)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static callUserFunction($funcName, &$params, &$ref, $checkPrefix='', $errorMode=0)
static getInlineLocalizationMode($table, $fieldOrConfig)
renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
getValidationDataAsDataAttribute(array $config)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']