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