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