‪TYPO3CMS  ‪main
InlineRecordContainer.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Psr\EventDispatcher\EventDispatcherInterface;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
24 use TYPO3\CMS\Core\Imaging\IconSize;
29 
41 {
47  protected ‪$inlineData = [];
48 
49  public function ‪__construct(
50  private readonly ‪IconFactory $iconFactory,
51  private readonly ‪InlineStackProcessor $inlineStackProcessor,
52  private readonly EventDispatcherInterface $eventDispatcher,
53  ) {}
54 
60  public function ‪render(): array
61  {
63  $this->inlineData = ‪$data['inlineData'];
64 
65  $inlineStackProcessor = $this->inlineStackProcessor;
66  $inlineStackProcessor->initializeByGivenStructure(‪$data['inlineStructure']);
67 
68  ‪$record = ‪$data['databaseRow'];
69  $inlineConfig = ‪$data['inlineParentConfig'];
70  $foreignTable = $inlineConfig['foreign_table'];
71 
72  $resultArray = $this->‪initializeResultArray();
73 
74  // Send a mapping information to the browser via JSON:
75  // e.g. data[<curTable>][<curId>][<curField>] => data-<pid>-<parentTable>-<parentId>-<parentField>-<curTable>-<curId>-<curField>
76  $formPrefix = $inlineStackProcessor->getCurrentStructureFormPrefix();
77  $domObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix(‪$data['inlineFirstPid']);
78  $this->inlineData['map'][$formPrefix] = $domObjectId;
79 
80  $resultArray['inlineData'] = ‪$this->inlineData;
81 
82  // Get the current naming scheme for DOM name/id attributes:
83  $appendFormFieldNames = '[' . $foreignTable . '][' . (‪$record['uid'] ?? 0) . ']';
84  $objectId = $domObjectId . '-' . $foreignTable . '-' . (‪$record['uid'] ?? 0);
85  $classes = [];
86  $html = '';
87  $combinationHtml = '';
88  $isNewRecord = ‪$data['command'] === 'new';
89  $hiddenField = '';
90  if (isset(‪$data['processedTca']['ctrl']['enablecolumns']['disabled'])) {
91  $hiddenField = ‪$data['processedTca']['ctrl']['enablecolumns']['disabled'];
92  }
93  if (!‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
94  if ($isNewRecord || ‪$data['isInlineChildExpanded']) {
95  // Render full content ONLY IF this is an AJAX request, a new record, or the record is not collapsed
96  if (isset(‪$data['combinationChild'])) {
97  $combinationChild = $this->‪renderCombinationChild(‪$data, $appendFormFieldNames);
98  $combinationHtml = $combinationChild['html'];
99  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $combinationChild, false);
100  }
101  $childArray = $this->‪renderChild(‪$data);
102  $html = $childArray['html'];
103  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $childArray, false);
104  } else {
105  // This class is the marker for the JS-function to check if the full content has already been loaded
106  $classes[] = 't3js-not-loaded';
107  }
108  if ($isNewRecord) {
109  // Add pid of record as hidden field
110  $html .= '<input type="hidden" name="data' . htmlspecialchars($appendFormFieldNames)
111  . '[pid]" value="' . htmlspecialchars(‪$record['pid']) . '"/>';
112  // Tell DataHandler this record is expanded
113  $ucFieldName = 'uc[inlineView]'
114  . '[' . ‪$data['inlineTopMostParentTableName'] . ']'
115  . '[' . ‪$data['inlineTopMostParentUid'] . ']'
116  . $appendFormFieldNames;
117  $html .= '<input type="hidden" name="' . htmlspecialchars($ucFieldName)
118  . '" value="' . (int)‪$data['isInlineChildExpanded'] . '" />';
119  } else {
120  // Set additional field for processing for saving
121  $html .= '<input type="hidden" name="cmd' . htmlspecialchars($appendFormFieldNames)
122  . '[delete]" value="1" disabled="disabled" />';
123  if (!empty($hiddenField) && (!‪$data['isInlineChildExpanded'] || !in_array($hiddenField, ‪$data['columnsToProcess'], true))) {
124  $checked = !empty(‪$record[$hiddenField]) ? ' checked="checked"' : '';
125  $html .= '<input type="checkbox" class="d-none" data-formengine-input-name="data'
126  . htmlspecialchars($appendFormFieldNames)
127  . '[' . htmlspecialchars($hiddenField) . ']" value="1"' . $checked . ' />';
128  $html .= '<input type="input" class="d-none" name="data' . htmlspecialchars($appendFormFieldNames)
129  . '[' . htmlspecialchars($hiddenField) . ']" value="' . htmlspecialchars(‪$record[$hiddenField]) . '" />';
130  }
131  }
132  // If this record should be shown collapsed
133  $classes[] = ‪$data['isInlineChildExpanded'] ? 'panel-visible' : 'panel-collapsed';
134  }
135  $hiddenFieldHtml = implode(LF, $resultArray['additionalHiddenFields'] ?? []);
136 
137  if ($inlineConfig['renderFieldsOnly'] ?? false) {
138  // Render "body" part only
139  $html .= $hiddenFieldHtml . $combinationHtml;
140  } else {
141  // Render header row and content (if expanded)
142  if (‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
143  $classes[] = 't3-form-field-container-inline-placeHolder';
144  }
145  if (!empty($hiddenField) && isset(‪$record[$hiddenField]) && (int)‪$record[$hiddenField]) {
146  $classes[] = 't3-form-field-container-inline-hidden';
147  }
148  if ($isNewRecord) {
149  $classes[] = 'inlineIsNewRecord';
150  }
151 
152  $originalUniqueValue = '';
153  if (isset(‪$record['uid'], ‪$data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][‪$record['uid']])) {
154  $uniqueValueValues = ‪$data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][‪$record['uid']];
155  // in case of site_language we don't have the full form engine options, so fallbacks need to be taken into account
156  $originalUniqueValue = ($uniqueValueValues['table'] ?? $foreignTable) . '_';
157  // @todo In what circumstance would $uniqueValueValues be an array that lacks a 'uid' key? Unclear, but
158  // it breaks the string concatenation. This is a hacky workaround for type safety only.
159  $uVV = ($uniqueValueValues['uid'] ?? $uniqueValueValues);
160  if (is_array($uVV)) {
161  $uVV = implode(',', $uVV);
162  }
163  $originalUniqueValue .= $uVV;
164  }
165 
166  // The hashed object id needs a non-numeric prefix, the value is used as ID selector in JavaScript
167  $hashedObjectId = 'hash-' . md5($objectId);
168  $containerAttributes = [
169  'id' => $objectId . '_div',
170  'class' => 'form-irre-object panel panel-default panel-condensed ' . trim(implode(' ', $classes)),
171  'data-object-uid' => ‪$record['uid'] ?? 0,
172  'data-object-id' => $objectId,
173  'data-object-id-hash' => $hashedObjectId,
174  'data-object-parent-group' => $domObjectId . '-' . $foreignTable,
175  'data-field-name' => $appendFormFieldNames,
176  'data-topmost-parent-table' => ‪$data['inlineTopMostParentTableName'],
177  'data-topmost-parent-uid' => ‪$data['inlineTopMostParentUid'],
178  'data-table-unique-original-value' => $originalUniqueValue,
179  'data-placeholder-record' => ‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ? '1' : '0',
180  ];
181 
182  $ariaExpanded = (‪$data['isInlineChildExpanded'] ?? false) ? 'true' : 'false';
183  $ariaControls = htmlspecialchars($objectId . '_fields', ENT_QUOTES | ENT_HTML5);
184  $ariaAttributesString = 'aria-expanded="' . $ariaExpanded . '" aria-controls="' . $ariaControls . '"';
185  $html = '
186  <div ' . GeneralUtility::implodeAttributes($containerAttributes, true) . '>
187  <div class="panel-heading" data-bs-toggle="formengine-inline" id="' . htmlspecialchars($hashedObjectId, ENT_QUOTES | ENT_HTML5) . '_header" data-expandSingle="' . (($inlineConfig['appearance']['expandSingle'] ?? false) ? 1 : 0) . '">
188  <div class="form-irre-header">
189  <div class="form-irre-header-cell form-irre-header-icon">
190  <span class="caret"></span>
191  </div>
192  ' . $this->‪renderForeignRecordHeader(‪$data, $ariaAttributesString) . '
193  </div>
194  </div>
195  <div class="panel-collapse" id="' . $ariaControls . '">' . $html . $hiddenFieldHtml . $combinationHtml . '</div>
196  </div>';
197  }
198 
199  $resultArray['html'] = $html;
200  return $resultArray;
201  }
202 
208  protected function ‪renderChild(array ‪$data)
209  {
210  $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix(‪$data['inlineFirstPid']);
211  ‪$data['tabAndInlineStack'][] = [
212  'inline',
213  $domObjectId . '-' . ‪$data['tableName'] . '-' . ‪$data['databaseRow']['uid'],
214  ];
215  // @todo: ugly construct ...
216  ‪$data['inlineData'] = ‪$this->inlineData;
217  ‪$data['renderType'] = 'fullRecordContainer';
218  return $this->nodeFactory->create(‪$data)->render();
219  }
220 
232  protected function ‪renderCombinationChild(array ‪$data, $appendFormFieldNames)
233  {
234  $childData = ‪$data['combinationChild'];
235  $parentConfig = ‪$data['inlineParentConfig'];
236 
237  // If field is set to readOnly, set all fields of the relation to readOnly as well
238  if (isset($parentConfig['readOnly']) && $parentConfig['readOnly']) {
239  foreach ($childData['processedTca']['columns'] as $columnName => $columnConfiguration) {
240  $childData['processedTca']['columns'][$columnName]['config']['readOnly'] = true;
241  }
242  }
243 
244  $resultArray = $this->‪initializeResultArray();
245 
246  // Display Warning FlashMessage if it is not suppressed
247  if (!isset($parentConfig['appearance']['suppressCombinationWarning']) || empty($parentConfig['appearance']['suppressCombinationWarning'])) {
248  $combinationWarningMessage = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.inline_use_combination';
249  if (!empty($parentConfig['appearance']['overwriteCombinationWarningMessage'])) {
250  $combinationWarningMessage = $parentConfig['appearance']['overwriteCombinationWarningMessage'];
251  }
252  $message = $this->‪getLanguageService()->sL($combinationWarningMessage);
253  $markup = [];
254  // @TODO: This is not a FlashMessage! The markup must be changed and special CSS
255  // @TODO: should be created, in order to prevent confusion.
256  $markup[] = '<div class="alert alert-warning">';
257  $markup[] = ' <div class="media">';
258  $markup[] = ' <div class="media-left">';
259  $markup[] = ' <span class="icon-emphasized">';
260  $markup[] = ' ' . $this->iconFactory->getIcon('actions-exclamation', IconSize::SMALL)->render();
261  $markup[] = ' </span>';
262  $markup[] = ' </div>';
263  $markup[] = ' <div class="media-body">';
264  $markup[] = ' <div class="alert-message">' . htmlspecialchars($message) . '</div>';
265  $markup[] = ' </div>';
266  $markup[] = ' </div>';
267  $markup[] = '</div>';
268  $resultArray['html'] = implode(LF, $markup);
269  }
270 
271  $childArray = $this->‪renderChild($childData);
272  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $childArray);
273 
274  // If this is a new record, add a pid value to store this record and the pointer value for the intermediate table
275  if ($childData['command'] === 'new') {
276  $comboFormFieldName = 'data[' . $childData['tableName'] . '][' . $childData['databaseRow']['uid'] . '][pid]';
277  $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($comboFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['pid']) . '" />';
278  }
279  // If the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation
280  if ($childData['command'] === 'new' || $parentConfig['foreign_unique'] === $parentConfig['foreign_selector']) {
281  $parentFormFieldName = 'data' . $appendFormFieldNames . '[' . $parentConfig['foreign_selector'] . ']';
282  $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($parentFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['uid']) . '" />';
283  }
284 
285  return $resultArray;
286  }
287 
296  protected function ‪renderForeignRecordHeader(array ‪$data, string $ariaAttributesString): string
297  {
298  ‪$record = ‪$data['databaseRow'];
299  $recordTitle = ‪$data['recordTitle'];
300  $foreignTable = ‪$data['inlineParentConfig']['foreign_table'];
301  $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix(‪$data['inlineFirstPid']);
302 
303  if (!empty($recordTitle)) {
304  // The user function may return HTML, therefore we can't escape it
305  if (empty(‪$data['processedTca']['ctrl']['formattedLabel_userFunc'])) {
306  $recordTitle = BackendUtility::getRecordTitlePrep($recordTitle);
307  }
308  } else {
309  $recordTitle = '<em>[' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>';
310  }
311 
312  // In case the record title is not generated by a formattedLabel_userFunc, which already
313  // contains custom markup, and we are in debug mode, add the inline record table name.
314  if (empty(‪$data['processedTca']['ctrl']['formattedLabel_userFunc'])
315  && $this->‪getBackendUserAuthentication()->shallDisplayDebugInformation()
316  ) {
317  $recordTitle .= ' <code class="m-0">[' . htmlspecialchars($foreignTable) . ']</code>';
318  }
319 
320  $objectId = htmlspecialchars($domObjectId . '-' . $foreignTable . '-' . (‪$record['uid'] ?? 0));
321  return '
322  <button class="form-irre-header-cell form-irre-header-button" ' . $ariaAttributesString . '>
323  <div class="form-irre-header-icon" id="' . $objectId . '_iconcontainer">
324  ' . $this->iconFactory->getIconForRecord($foreignTable, ‪$record, IconSize::SMALL)->setTitle(BackendUtility::getRecordIconAltText(‪$record, $foreignTable, false))->render() . '
325  </div>
326  <div class="form-irre-header-body"><span id="' . $objectId . '_label">' . $recordTitle . '</span></div>
327  </button>
328  <div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">
329  ' . $this->‪renderForeignRecordHeaderControl($data) . '
330  </div>';
331  }
332 
341  protected function ‪renderForeignRecordHeaderControl(array ‪$data)
342  {
343  $rec = ‪$data['databaseRow'];
344  $rec += [
345  'uid' => 0,
346  ];
347  $inlineConfig = ‪$data['inlineParentConfig'];
348  $foreignTable = $inlineConfig['foreign_table'];
349  $languageService = $this->‪getLanguageService();
350  $backendUser = $this->‪getBackendUserAuthentication();
351  // Initialize:
352  $cells = [
353  'hide' => '',
354  'delete' => '',
355  'info' => '',
356  'new' => '',
357  'sort.up' => '',
358  'sort.down' => '',
359  'dragdrop' => '',
360  'localize' => '',
361  'locked' => '',
362  ];
363  $isNewItem = str_starts_with($rec['uid'], 'NEW');
364  $isParentReadOnly = isset($inlineConfig['readOnly']) && $inlineConfig['readOnly'];
365  $isParentExisting = ‪MathUtility::canBeInterpretedAsInteger(‪$data['inlineParentUid']);
366  $tcaTableCtrl = ‪$GLOBALS['TCA'][$foreignTable]['ctrl'];
367  $tcaTableCols = ‪$GLOBALS['TCA'][$foreignTable]['columns'];
368  $isPagesTable = $foreignTable === 'pages';
369  $enableManualSorting = ($tcaTableCtrl['sortby'] ?? false)
370  || ($inlineConfig['MM'] ?? false)
371  || (!(‪$data['isOnSymmetricSide'] ?? false) && ($inlineConfig['foreign_sortby'] ?? false))
372  || ((‪$data['isOnSymmetricSide'] ?? false) && ($inlineConfig['symmetric_sortby'] ?? false));
373  $calcPerms = new Permission($backendUser->calcPerms(BackendUtility::readPageAccess((int)(‪$data['parentPageRow']['uid'] ?? 0), $backendUser->getPagePermsClause(‪Permission::PAGE_SHOW))));
374  // If the listed table is 'pages' we have to request the permission settings for each page:
375  $localCalcPerms = new Permission(‪Permission::NOTHING);
376  if ($isPagesTable) {
377  $localCalcPerms = new Permission($backendUser->calcPerms(BackendUtility::getRecord('pages', $rec['uid'])));
378  }
379  // This expresses the edit permissions for this particular element:
380  $permsEdit = ($isPagesTable && $localCalcPerms->editPagePermissionIsGranted()) || (!$isPagesTable && $calcPerms->editContentPermissionIsGranted());
381  // The event contains all controls and their state (enabled / disabled), which might got modified by listeners
382  $event = $this->eventDispatcher->dispatch(new ModifyInlineElementEnabledControlsEvent(‪$data, $rec));
383  if (‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
384  $cells['localize'] = $this->iconFactory
385  ->getIcon('actions-edit-localize-status-low', IconSize::SMALL)
386  ->setTitle($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize.isLocalizable'))
387  ->‪render();
388  }
389  // "Info": (All records)
390  if ($event->isControlEnabled('info')) {
391  if ($isNewItem) {
392  $cells['info'] = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', IconSize::SMALL)->render() . '</span>';
393  } else {
394  $cells['info'] = '
395  <button type="button" class="btn btn-default" data-action="infowindow" data-info-table="' . htmlspecialchars($foreignTable) . '" data-info-uid="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo')) . '">
396  ' . $this->iconFactory->getIcon('actions-document-info', IconSize::SMALL)->render() . '
397  </button>';
398  }
399  }
400  // If the table is NOT a read-only table, then show these links:
401  if (!$isParentReadOnly && !($tcaTableCtrl['readOnly'] ?? false) && !(‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false)) {
402  // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row or if default values can depend on previous record):
403  if ($event->isControlEnabled('new') && ($enableManualSorting || ($tcaTableCtrl['useColumnsForDefaultValues'] ?? false))) {
404  if ((!$isPagesTable && $calcPerms->editContentPermissionIsGranted()) || ($isPagesTable && $calcPerms->createPagePermissionIsGranted())) {
405  $style = '';
406  if ($inlineConfig['inline']['inlineNewButtonStyle'] ?? false) {
407  $style = ' style="' . $inlineConfig['inline']['inlineNewButtonStyle'] . '"';
408  }
409  $cells['new'] = '
410  <button type="button" class="btn btn-default t3js-create-new-button" data-record-uid="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:new' . ($isPagesTable ? 'Page' : 'Record'))) . '" ' . $style . '>
411  ' . $this->iconFactory->getIcon('actions-' . ($isPagesTable ? 'page-new' : 'add'), IconSize::SMALL)->render() . '
412  </button>';
413  }
414  }
415  // "Up/Down" links
416  if ($event->isControlEnabled('sort') && $permsEdit && $enableManualSorting) {
417  // Up
418  $icon = 'actions-move-up';
419  $class = '';
420  if ($inlineConfig['inline']['first'] == $rec['uid']) {
421  $class = ' disabled';
422  $icon = 'empty-empty';
423  }
424  $cells['sort.up'] = '
425  <button type="button" class="btn btn-default' . $class . '" data-action="sort" data-direction="up" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveUp')) . '">
426  ' . $this->iconFactory->getIcon($icon, IconSize::SMALL)->render() . '
427  </button>';
428  // Down
429  $icon = 'actions-move-down';
430  $class = '';
431  if ($inlineConfig['inline']['last'] == $rec['uid']) {
432  $class = ' disabled';
433  $icon = 'empty-empty';
434  }
435 
436  $cells['sort.down'] = '
437  <button type="button" class="btn btn-default' . $class . '" data-action="sort" data-direction="down" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveDown')) . '">
438  ' . $this->iconFactory->getIcon($icon, IconSize::SMALL)->render() . '
439  </button>';
440  }
441  // "Delete" link:
442  if ($event->isControlEnabled('delete')
443  && (
444  ($isPagesTable && $localCalcPerms->deletePagePermissionIsGranted())
445  || (!$isPagesTable && $calcPerms->editContentPermissionIsGranted())
446  )
447  ) {
448  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete'));
449  $icon = $this->iconFactory->getIcon('actions-edit-delete', IconSize::SMALL)->render();
450 
451  $recordInfo = ‪$data['databaseRow']['uid_local'][0]['title'] ?? ‪$data['recordTitle'] ?? '';
452  if ($this->‪getBackendUserAuthentication()->shallDisplayDebugInformation()) {
453  $recordInfo .= ' [' . ‪$data['tableName'] . ':' . ‪$data['vanillaUid'] . ']';
454  }
455 
456  $cells['delete'] = '
457  <button type="button" class="btn btn-default t3js-editform-delete-inline-record" data-record-info="' . htmlspecialchars(trim($recordInfo)) . '" title="' . $title . '">
458  ' . $icon . '
459  </button>';
460  }
461 
462  // "Hide/Unhide" links:
463  $hiddenField = $tcaTableCtrl['enablecolumns']['disabled'] ?? '';
464  if ($event->isControlEnabled('hide')
465  && $permsEdit
466  && $hiddenField
467  && ($tcaTableCols[$hiddenField] ?? false)
468  && (!($tcaTableCols[$hiddenField]['exclude'] ?? false) || $backendUser->check('non_exclude_fields', $foreignTable . ':' . $hiddenField))
469  ) {
470  if ($rec[$hiddenField]) {
471  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($isPagesTable ? 'Page' : '')));
472  $cells['hide'] = '
473  <button type="button" class="btn btn-default t3js-toggle-visibility-button" data-hidden-field="' . htmlspecialchars($hiddenField) . '" title="' . $title . '">
474  ' . $this->iconFactory->getIcon('actions-edit-unhide', IconSize::SMALL)->render() . '
475  </button>';
476  } else {
477  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($isPagesTable ? 'Page' : '')));
478  $cells['hide'] = '
479  <button type="button" class="btn btn-default t3js-toggle-visibility-button" data-hidden-field="' . htmlspecialchars($hiddenField) . '" title="' . $title . '">
480  ' . $this->iconFactory->getIcon('actions-edit-hide', IconSize::SMALL)->render() . '
481  </button>';
482  }
483  }
484  // Drag&Drop Sorting: Sortable handler for script.aculo.us
485  if ($event->isControlEnabled('dragdrop') && $permsEdit && $enableManualSorting && ($inlineConfig['appearance']['useSortable'] ?? false)) {
486  $cells['dragdrop'] = '
487  <span class="btn btn-default sortableHandle" data-id="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.move')) . '">
488  ' . $this->iconFactory->getIcon('actions-move-move', IconSize::SMALL)->render() . '
489  </span>';
490  }
491  } elseif ((‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false) && $isParentExisting) {
492  if ($event->isControlEnabled('localize') && (‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ?? false)) {
493  $cells['localize'] = '
494  <button type="button" class="btn btn-default t3js-synchronizelocalize-button" data-type="' . htmlspecialchars($rec['uid']) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize')) . '">
495  ' . $this->iconFactory->getIcon('actions-document-localize', IconSize::SMALL)->render() . '
496  </button>';
497  }
498  }
499  // If the record is edit-locked by another user, we will show a little warning sign:
500  if ($lockInfo = BackendUtility::isRecordLocked($foreignTable, $rec['uid'])) {
501  $cells['locked'] = '
502  <button type="button" class="btn btn-default" title="' . htmlspecialchars($lockInfo['msg']) . '">
503  ' . $this->iconFactory->getIcon('status-user-backend', IconSize::SMALL, 'overlay-edit')->render() . '
504  </button>';
505  }
506 
507  // Get modified controls. This means their markup was modified, new controls were added or controls got removed.
508  $cells = $this->eventDispatcher->dispatch(new ModifyInlineElementControlsEvent($cells, ‪$data, $rec))->getControls();
509 
510  $out = '';
511  if (!empty($cells['hide']) || !empty($cells['delete'])) {
512  $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['hide'] . $cells['delete'] . '</div>';
513  unset($cells['hide'], $cells['delete']);
514  }
515  if (!empty($cells['info']) || !empty($cells['new']) || !empty($cells['sort.up']) || !empty($cells['sort.down']) || !empty($cells['dragdrop'])) {
516  $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['info'] . $cells['new'] . $cells['sort.up'] . $cells['sort.down'] . $cells['dragdrop'] . '</div>';
517  unset($cells['info'], $cells['new'], $cells['sort.up'], $cells['sort.down'], $cells['dragdrop']);
518  }
519  if (!empty($cells['localize'])) {
520  $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['localize'] . '</div>';
521  unset($cells['localize']);
522  }
523  if (!empty($cells)) {
524  $cellContent = trim(implode('', $cells));
525  $out .= $cellContent !== '' ? ' <div class="btn-group btn-group-sm" role="group">' . $cellContent . '</div>' : '';
526  }
527  return $out;
528  }
529 
530  protected function ‪getLanguageService(): ‪LanguageService
531  {
532  return ‪$GLOBALS['LANG'];
533  }
534 }
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderForeignRecordHeaderControl
‪string renderForeignRecordHeaderControl(array $data)
Definition: InlineRecordContainer.php:340
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:104
‪TYPO3\CMS\Core\Type\Bitmask\Permission\NOTHING
‪const NOTHING
Definition: Permission.php:30
‪TYPO3\CMS\Backend\Form\Container
Definition: AbstractContainer.php:16
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Backend\Form\Event\ModifyInlineElementControlsEvent
Definition: ModifyInlineElementControlsEvent.php:24
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\__construct
‪__construct(private readonly IconFactory $iconFactory, private readonly InlineStackProcessor $inlineStackProcessor, private readonly EventDispatcherInterface $eventDispatcher,)
Definition: InlineRecordContainer.php:48
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer\getBackendUserAuthentication
‪getBackendUserAuthentication()
Definition: AbstractContainer.php:149
‪TYPO3\CMS\Backend\Form\AbstractNode\$data
‪array $data
Definition: AbstractNode.php:35
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer
Definition: AbstractContainer.php:29
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderForeignRecordHeader
‪string renderForeignRecordHeader(array $data, string $ariaAttributesString)
Definition: InlineRecordContainer.php:295
‪TYPO3\CMS\Backend\Form\Event\ModifyInlineElementEnabledControlsEvent
Definition: ModifyInlineElementEnabledControlsEvent.php:24
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\$inlineData
‪array $inlineData
Definition: InlineRecordContainer.php:46
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\getLanguageService
‪getLanguageService()
Definition: InlineRecordContainer.php:529
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer
Definition: InlineRecordContainer.php:41
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderChild
‪array renderChild(array $data)
Definition: InlineRecordContainer.php:207
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:32
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\render
‪array render()
Definition: InlineRecordContainer.php:59
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderCombinationChild
‪array renderCombinationChild(array $data, $appendFormFieldNames)
Definition: InlineRecordContainer.php:231
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪initializeResultArray()
Definition: AbstractNode.php:77