TYPO3  7.6
InlineControlContainer.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Container;
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 = array();
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 = array(
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  // e.g. data[<table>][<uid>][<field>]
125  $nameForm = $inlineStackProcessor->getCurrentStructureFormPrefix();
126  // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
127  $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
128 
129  $config['inline']['first'] = false;
130  // @todo: This initialization shouldn't be required data provider should take care this is set?
131  if (!is_array($this->data['parameterArray']['fieldConf']['children'])) {
132  $this->data['parameterArray']['fieldConf']['children'] = array();
133  }
134  $firstChild = reset($this->data['parameterArray']['fieldConf']['children']);
135  if (isset($firstChild['databaseRow']['uid'])) {
136  $config['inline']['first'] = $firstChild['databaseRow']['uid'];
137  }
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'];
142  }
143 
144  $top = $inlineStackProcessor->getStructureLevel(0);
145 
146  $this->inlineData['config'][$nameObject] = array(
147  'table' => $foreign_table,
148  'md5' => md5($nameObject)
149  );
150  $this->inlineData['config'][$nameObject . '-' . $foreign_table] = array(
151  'min' => $config['minitems'],
152  'max' => $config['maxitems'],
153  'sortable' => $config['appearance']['useSortable'],
154  'top' => array(
155  'table' => $top['table'],
156  'uid' => $top['uid']
157  ),
158  'context' => array(
159  'config' => $config,
160  'hmac' => GeneralUtility::hmac(serialize($config)),
161  ),
162  );
163  $this->inlineData['nested'][$nameObject] = $this->data['tabAndInlineStack'];
164 
165  $uniqueMax = 0;
166  $uniqueIds = [];
167 
168  if ($config['foreign_unique']) {
169  // Add inlineData['unique'] with JS unique configuration
170  $type = $config['selectorOrUniqueConfiguration']['config']['type'] === 'select' ? 'select' : 'groupdb';
171  foreach ($parameterArray['fieldConf']['children'] as $child) {
172  // Determine used unique ids, skip not localized records
173  if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
174  $value = $child['databaseRow'][$config['foreign_unique']];
175  // We're assuming there is only one connected value here for both select and group
176  if ($type === 'select') {
177  // A resolved select field is an array - take first value
178  $value = $value['0'];
179  } else {
180  // A group field is still a list with pipe separated uid|tableName
181  $valueParts = GeneralUtility::trimExplode('|', $value);
182  $itemParts = explode('_', $valueParts[0]);
183  $value = array(
184  'uid' => array_pop($itemParts),
185  'table' => implode('_', $itemParts)
186  );
187  }
188  // @todo: This is weird, $value has different structure for group and select fields?
189  $uniqueIds[$child['databaseRow']['uid']] = $value;
190  }
191  }
192  $possibleRecords = $config['selectorOrUniquePossibleRecords'];
193  $possibleRecordsUidToTitle = [];
194  foreach ($possibleRecords as $possibleRecord) {
195  $possibleRecordsUidToTitle[$possibleRecord[1]] = $possibleRecord[0];
196  }
197  $uniqueMax = $config['appearance']['useCombination'] || empty($possibleRecords) ? -1 : count($possibleRecords);
198  $this->inlineData['unique'][$nameObject . '-' . $foreign_table] = array(
199  'max' => $uniqueMax,
200  'used' => $uniqueIds,
201  'type' => $type,
202  'table' => $foreign_table,
203  'elTable' => $config['selectorOrUniqueConfiguration']['foreignTable'],
204  'field' => $config['foreign_unique'],
205  'selector' => $config['selectorOrUniqueConfiguration']['isSelector'] ? $type : false,
206  'possible' => $possibleRecordsUidToTitle,
207  );
208  }
209 
210  $resultArray['inlineData'] = $this->inlineData;
211 
212  // @todo: It might be a good idea to have something like "isLocalizedRecord" or similar set by a data provider
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 ++;
221  }
222  if ($isLocalizedParent && $child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
223  $numberOfNotYetLocalizedChildren ++;
224  }
225  }
226 
227  // Render the localization links if needed
228  $localizationLinks = '';
229  if ($numberOfNotYetLocalizedChildren) {
230  // Add the "Localize all records" link before all child records:
231  if (isset($config['appearance']['showAllLocalizationLink']) && $config['appearance']['showAllLocalizationLink']) {
232  $localizationLinks = ' ' . $this->getLevelInteractionLink('localize', $nameObject . '-' . $foreign_table, $config);
233  }
234  // Add the "Synchronize with default language" link before all child records:
235  if (isset($config['appearance']['showSynchronizationLink']) && $config['appearance']['showSynchronizationLink']) {
236  $localizationLinks .= ' ' . $this->getLevelInteractionLink('synchronize', $nameObject . '-' . $foreign_table, $config);
237  }
238  }
239 
240  // Define how to show the "Create new record" link - if there are more than maxitems, hide it
241  if ($numberOfFullLocalizedChildren >= $config['maxitems'] || $uniqueMax > 0 && $numberOfFullLocalizedChildren >= $uniqueMax) {
242  $config['inline']['inlineNewButtonStyle'] = 'display: none;';
243  $config['inline']['inlineNewRelationButtonStyle'] = 'display: none;';
244  }
245 
246  // Render the level links (create new record):
247  $levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . '-' . $foreign_table, $config);
248 
249  // Wrap all inline fields of a record with a <div> (like a container)
250  $html = '<div class="form-group" id="' . $nameObject . '">';
251  // Add the level links before all child records:
252  if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'top') {
253  $html .= '<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks . '</div>';
254  }
255 
256  // If it's required to select from possible child records (reusable children), add a selector box
257  if ($config['foreign_selector'] && $config['appearance']['showPossibleRecordsSelector'] !== false) {
258  if ($config['selectorOrUniqueConfiguration']['config']['type'] === 'select') {
259  $selectorBox = $this->renderPossibleRecordsSelectorTypeSelect($config, $uniqueIds);
260  } else {
261  $selectorBox = $this->renderPossibleRecordsSelectorTypeGroupDB($config);
262  }
263  $html .= $selectorBox . $localizationLinks;
264  }
265 
266  $title = $languageService->sL(trim($parameterArray['fieldConf']['label']));
267  $html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
268 
269  $sortableRecordUids = [];
270  foreach ($this->data['parameterArray']['fieldConf']['children'] as $options) {
271  $options['inlineParentUid'] = $row['uid'];
272  // @todo: this can be removed if this container no longer sets additional info to $config
273  $options['inlineParentConfig'] = $config;
274  $options['inlineData'] = $this->inlineData;
275  $options['inlineStructure'] = $inlineStackProcessor->getStructure();
276  $options['inlineExpandCollapseStateArray'] = $this->data['inlineExpandCollapseStateArray'];
277  $options['renderType'] = 'inlineRecordContainer';
278  $childResult = $this->nodeFactory->create($options)->render();
279  $html .= $childResult['html'];
280  $childArray['html'] = '';
281  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childResult);
282  if (!$options['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
283  // Don't add record to list of "valid" uids if it is only the default
284  // language record of a not yet localized child
285  $sortableRecordUids[] = $options['databaseRow']['uid'];
286  }
287  }
288 
289  $html .= '</div>';
290 
291  // Add the level links after all child records:
292  if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'bottom') {
293  $html .= $levelLinks . $localizationLinks;
294  }
295  if (is_array($config['customControls'])) {
296  $html .= '<div id="' . $nameObject . '_customControls">';
297  foreach ($config['customControls'] as $customControlConfig) {
298  $parameters = array(
299  'table' => $table,
300  'field' => $field,
301  'row' => $row,
302  'nameObject' => $nameObject,
303  'nameForm' => $nameForm,
304  'config' => $config
305  );
306  $html .= GeneralUtility::callUserFunction($customControlConfig, $parameters, $this);
307  }
308  $html .= '</div>';
309  }
310  // Add Drag&Drop functions for sorting to FormEngine::$additionalJS_post
311  if (count($sortableRecordUids) > 1 && $config['appearance']['useSortable']) {
312  $resultArray['additionalJavaScriptPost'][] = 'inline.createDragAndDropSorting("' . $nameObject . '_records' . '");';
313  }
314  $resultArray['requireJsModules'] = array_merge($resultArray['requireJsModules'], $this->requireJsModules);
315 
316  // Publish the uids of the child records in the given order to the browser
317  $html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $sortableRecordUids) . '" '
318  . $this->getValidationDataAsDataAttribute(array('type' => 'inline', 'minitems' => $config['minitems'], 'maxitems' => $config['maxitems']))
319  . ' class="inlineRecord" />';
320  // Close the wrap for all inline fields (container)
321  $html .= '</div>';
322 
323  $resultArray['html'] = $html;
324  return $resultArray;
325  }
326 
336  protected function getLevelInteractionLink($type, $objectPrefix, $conf = array())
337  {
338  $languageService = $this->getLanguageService();
339  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
340  $attributes = array();
341  switch ($type) {
342  case 'newRecord':
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'];
347  $attributes['onclick'] = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ')';
348  if (!empty($conf['inline']['inlineNewButtonStyle'])) {
349  $attributes['style'] = $conf['inline']['inlineNewButtonStyle'];
350  }
351  if (!empty($conf['appearance']['newRecordLinkAddTitle'])) {
352  $title = sprintf(
353  $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createnew.link', true),
354  $languageService->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'], true)
355  );
356  } elseif (isset($conf['appearance']['newRecordLinkTitle']) && $conf['appearance']['newRecordLinkTitle'] !== '') {
357  $title = $languageService->sL($conf['appearance']['newRecordLinkTitle'], true);
358  }
359  break;
360  case 'localize':
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';
365  $attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'localize\')';
366  break;
367  case 'synchronize':
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\')';
373  break;
374  default:
375  $title = '';
376  $icon = '';
377  $className = '';
378  }
379  // Create the link:
380  $icon = $icon ? $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render() : '';
381  $link = $this->wrapWithAnchor($icon . $title, '#', $attributes);
382  return '<div' . ($className ? ' class="' . $className . '"' : '') . 'title="' . $title . '">' . $link . '</div>';
383  }
384 
393  protected function wrapWithAnchor($text, $link, $attributes = array())
394  {
395  $link = trim($link);
396  $result = '<a href="' . ($link ?: '#') . '"';
397  foreach ($attributes as $key => $value) {
398  $result .= ' ' . $key . '="' . htmlspecialchars(trim($value)) . '"';
399  }
400  $result .= '>' . $text . '</a>';
401  return $result;
402  }
403 
411  protected function renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration)
412  {
413  $backendUser = $this->getBackendUserAuthentication();
414  $languageService = $this->getLanguageService();
415 
416  $groupFieldConfiguration = $inlineConfiguration['selectorOrUniqueConfiguration']['config'];
417 
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']);
422  $mode = 'db';
423  $showUpload = false;
424  if (!empty($inlineConfiguration['appearance']['createNewRelationLinkTitle'])) {
425  $createNewRelationText = $languageService->sL($inlineConfiguration['appearance']['createNewRelationLinkTitle'], true);
426  } else {
427  $createNewRelationText = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
428  }
429  if (is_array($groupFieldConfiguration['appearance'])) {
430  if (isset($groupFieldConfiguration['appearance']['elementBrowserType'])) {
431  $mode = $groupFieldConfiguration['appearance']['elementBrowserType'];
432  }
433  if ($mode === 'file') {
434  $showUpload = true;
435  }
436  if (isset($inlineConfiguration['appearance']['fileUploadAllowed'])) {
437  $showUpload = (bool)$inlineConfiguration['appearance']['fileUploadAllowed'];
438  }
439  if (isset($groupFieldConfiguration['appearance']['elementBrowserAllowed'])) {
440  $allowed = $groupFieldConfiguration['appearance']['elementBrowserAllowed'];
441  }
442  }
443  $browserParams = '|||' . $allowed . '|' . $objectPrefix . '|inline.checkUniqueElement||inline.importElement';
444  $onClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($mode) . ', ' . GeneralUtility::quoteJSvalue($browserParams) . '); return false;';
445 
446  $buttonStyle = '';
447  if (isset($inlineConfiguration['inline']['inlineNewRelationButtonStyle'])) {
448  $buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineNewRelationButtonStyle'] . '"';
449  }
450 
451  $item = '
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 . '
456  </a>';
457  $isDirectFileUploadEnabled = (bool)$this->getBackendUserAuthentication()->uc['edit_docModuleUpload'];
458  $allowedArray = GeneralUtility::trimExplode(',', $allowed, true);
459  $onlineMediaAllowed = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions();
460  if (!empty($allowedArray)) {
461  $onlineMediaAllowed = array_intersect($allowedArray, $onlineMediaAllowed);
462  }
463  if ($showUpload && $isDirectFileUploadEnabled) {
464  $folder = $backendUser->getDefaultUploadFolder(
465  $this->data['parentPageRow']['uid'],
466  $this->data['tableName'],
467  $this->data['fieldName']
468  );
469  if (
470  $folder instanceof Folder
471  && $folder->checkActionPermission('add')
472  ) {
473  $maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
474  $item .= ' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
475  ' . $buttonStyle . '
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) . '"
482  >';
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);
485  $item .= '</a>';
486 
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);
493  $item .= '
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 . '"
501  >
502  ' . $this->iconFactory->getIcon('actions-online-media-add', Icon::SIZE_SMALL)->render() . '
503  ' . $buttonText . '</span>';
504  }
505  }
506  }
507 
508  $item = '<div class="form-control-wrap">' . $item . '</div>';
509  $allowedList = '';
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> ';
513  }
514  if (!empty($allowedList)) {
515  $item .= '<div class="help-block">' . $allowedLabel . '<br>' . $allowedList . '</div>';
516  }
517  $item = '<div class="form-group t3js-formengine-validation-marker">' . $item . '</div>';
518  return $item;
519  }
520 
529  protected function renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
530  {
531  $possibleRecords = $config['selectorOrUniquePossibleRecords'];
532  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
533  // Create option tags:
534  $opt = [];
535  foreach ($possibleRecords as $p) {
536  if (!in_array($p[1], $uniqueIds)) {
537  $opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
538  }
539  }
540  // Put together the selector box:
541  $size = (int)$config['size'];
542  $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($possibleRecords) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
543  $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $config['foreign_table']) . ')';
544  $item = '
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) . '
548  </select>';
549 
550  if ($size <= 1) {
551  // Add a "Create new relation" link for adding new relations
552  // This is necessary, if the size of the selector is "1" or if
553  // there is only one record item in the select-box, that is selected by default
554  // The selector-box creates a new relation on using an onChange event (see some line above)
555  if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
556  $createNewRelationText = $this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle'], true);
557  } else {
558  $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
559  }
560  $item .= '
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 . '
564  </a>
565  </span>';
566  } else {
567  $item .= '
568  <span class="input-group-btn btn"></span>';
569  }
570 
571  // Wrap the selector and add a spacer to the bottom
572  $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
573  return $item;
574  }
575 
584  protected function extractFlexFormParts($formElementName)
585  {
586  $flexFormParts = null;
587  $matches = array();
588  if (preg_match('#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
589  $flexFormParts = GeneralUtility::trimExplode(
590  '][',
591  trim($matches[1], '[]')
592  );
593  }
594  return $flexFormParts;
595  }
596 
600  protected function getBackendUserAuthentication()
601  {
602  return $GLOBALS['BE_USER'];
603  }
604 
608  protected function getLanguageService()
609  {
610  return $GLOBALS['LANG'];
611  }
612 }