‪TYPO3CMS  9.5
InlineRecordContainer.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 
32 
44 {
50  protected ‪$inlineData = [];
51 
55  protected ‪$inlineStackProcessor;
56 
62  protected ‪$hookObjects = [];
63 
67  protected ‪$iconFactory;
68 
76  {
77  parent::__construct(‪$nodeFactory, ‪$data);
78  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
79  $this->inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
80  $this->‪initHookObjects();
81  }
82 
89  public function ‪render()
90  {
92  $this->inlineData = ‪$data['inlineData'];
93 
96 
97  $record = ‪$data['databaseRow'];
98  $inlineConfig = ‪$data['inlineParentConfig'];
99  $foreignTable = $inlineConfig['foreign_table'];
100 
101  $resultArray = $this->‪initializeResultArray();
102 
103  // Send a mapping information to the browser via JSON:
104  // e.g. data[<curTable>][<curId>][<curField>] => data-<pid>-<parentTable>-<parentId>-<parentField>-<curTable>-<curId>-<curField>
107  $this->inlineData['map'][$formPrefix] = $domObjectId;
108 
109  $resultArray['inlineData'] = ‪$this->inlineData;
110 
111  // If there is a selector field, normalize it:
112  if (!empty($inlineConfig['foreign_selector'])) {
113  $foreign_selector = $inlineConfig['foreign_selector'];
114  $valueToNormalize = $record[$foreign_selector];
115  if (is_array($record[$foreign_selector])) {
116  // @todo: this can be kicked again if always prepared rows are handled here
117  $valueToNormalize = implode(',', $record[$foreign_selector]);
118  }
119  $record[$foreign_selector] = $this->‪normalizeUid($valueToNormalize);
120  }
121 
122  // Get the current naming scheme for DOM name/id attributes:
123  $appendFormFieldNames = '[' . $foreignTable . '][' . $record['uid'] . ']';
124  $objectId = $domObjectId . '-' . $foreignTable . '-' . $record['uid'];
125  $class = '';
126  $html = '';
127  $combinationHtml = '';
128  $isNewRecord = ‪$data['command'] === 'new';
129  $hiddenField = '';
130  if (isset(‪$data['processedTca']['ctrl']['enablecolumns']['disabled'])) {
131  $hiddenField = ‪$data['processedTca']['ctrl']['enablecolumns']['disabled'];
132  }
133  if (!‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
134  if ($isNewRecord || ‪$data['isInlineChildExpanded']) {
135  // Render full content ONLY IF this is an AJAX request, a new record, or the record is not collapsed
136  $combinationHtml = '';
137  if (isset(‪$data['combinationChild'])) {
138  $combinationChild = $this->‪renderCombinationChild(‪$data, $appendFormFieldNames);
139  $combinationHtml = $combinationChild['html'];
140  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $combinationChild, false);
141  }
142  $childArray = $this->‪renderChild(‪$data);
143  $html = $childArray['html'];
144  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $childArray, false);
145  } else {
146  // This string is the marker for the JS-function to check if the full content has already been loaded
147  $html = '<!--notloaded-->';
148  }
149  if ($isNewRecord) {
150  // Add pid of record as hidden field
151  $html .= '<input type="hidden" name="data' . htmlspecialchars($appendFormFieldNames)
152  . '[pid]" value="' . htmlspecialchars($record['pid']) . '"/>';
153  // Tell DataHandler this record is expanded
154  $ucFieldName = 'uc[inlineView]'
155  . '[' . ‪$data['inlineTopMostParentTableName'] . ']'
156  . '[' . ‪$data['inlineTopMostParentUid'] . ']'
157  . $appendFormFieldNames;
158  $html .= '<input type="hidden" name="' . htmlspecialchars($ucFieldName)
159  . '" value="' . (int)‪$data['isInlineChildExpanded'] . '" />';
160  } else {
161  // Set additional field for processing for saving
162  $html .= '<input type="hidden" name="cmd' . htmlspecialchars($appendFormFieldNames)
163  . '[delete]" value="1" disabled="disabled" />';
164  if (!‪$data['isInlineChildExpanded'] && !empty($hiddenField)) {
165  $checked = !empty($record[$hiddenField]) ? ' checked="checked"' : '';
166  $html .= '<input type="checkbox" data-formengine-input-name="data'
167  . htmlspecialchars($appendFormFieldNames)
168  . '[' . htmlspecialchars($hiddenField) . ']" value="1"' . $checked . ' />';
169  $html .= '<input type="input" name="data' . htmlspecialchars($appendFormFieldNames)
170  . '[' . htmlspecialchars($hiddenField) . ']" value="' . htmlspecialchars($record[$hiddenField]) . '" />';
171  }
172  }
173  // If this record should be shown collapsed
174  $class = ‪$data['isInlineChildExpanded'] ? 'panel-visible' : 'panel-collapsed';
175  }
176  $hiddenFieldHtml = implode(LF, $resultArray['additionalHiddenFields'] ?? []);
177 
178  if ($inlineConfig['renderFieldsOnly']) {
179  // Render "body" part only
180  $html = $html . $combinationHtml;
181  } else {
182  // Render header row and content (if expanded)
183  if (‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
184  $class .= ' t3-form-field-container-inline-placeHolder';
185  }
186  if (!empty($hiddenField) && isset($record[$hiddenField]) && (int)$record[$hiddenField]) {
187  $class .= ' t3-form-field-container-inline-hidden';
188  }
189  $class .= ($isNewRecord ? ' inlineIsNewRecord' : '');
190  $tableUniqueOriginalValue = '';
191  if (isset(‪$data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']])) {
192  $uniqueValueValues = ‪$data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']];
193  // in case of site_language we don't have the full form engine options, so fallbacks need to be taken into account
194  $tableUniqueOriginalValue = ($uniqueValueValues['table'] ?? $foreignTable) . '_' . ($uniqueValueValues['uid'] ?? $uniqueValueValues);
195  }
196  $html = '
197  <div class="panel panel-default panel-condensed ' . trim($class) . '" id="' . htmlspecialchars($objectId) . '_div" data-table-unique-original-value="' . htmlspecialchars($tableUniqueOriginalValue) . '">
198  <div class="panel-heading" data-toggle="formengine-inline" id="' . htmlspecialchars($objectId) . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
199  <div class="form-irre-header">
200  <div class="form-irre-header-cell form-irre-header-icon">
201  <span class="caret"></span>
202  </div>
203  ' . $this->‪renderForeignRecordHeader(‪$data) . '
204  </div>
205  </div>
206  <div class="panel-collapse" id="' . htmlspecialchars($objectId) . '_fields">' . $html . $hiddenFieldHtml . $combinationHtml . '</div>
207  </div>';
208  }
209 
210  $resultArray['html'] = $html;
211  return $resultArray;
212  }
213 
220  protected function ‪renderChild(array ‪$data)
221  {
222  $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix(‪$data['inlineFirstPid']);
223  ‪$data['tabAndInlineStack'][] = [
224  'inline',
225  $domObjectId . '-' . ‪$data['tableName'] . '-' . ‪$data['databaseRow']['uid'],
226  ];
227  // @todo: ugly construct ...
228  ‪$data['inlineData'] = ‪$this->inlineData;
229  ‪$data['renderType'] = 'fullRecordContainer';
230  return $this->nodeFactory->create(‪$data)->render();
231  }
232 
244  protected function ‪renderCombinationChild(array ‪$data, $appendFormFieldNames)
245  {
246  $childData = ‪$data['combinationChild'];
247  $parentConfig = ‪$data['inlineParentConfig'];
248 
249  // If field is set to readOnly, set all fields of the relation to readOnly as well
250  if (isset($parentConfig['readOnly']) && $parentConfig['readOnly']) {
251  foreach ($childData['processedTca']['columns'] as $columnName => $columnConfiguration) {
252  $childData['processedTca']['columns'][$columnName]['config']['readOnly'] = true;
253  }
254  }
255 
256  $resultArray = $this->‪initializeResultArray();
257 
258  // Display Warning FlashMessage if it is not suppressed
259  if (!isset($parentConfig['appearance']['suppressCombinationWarning']) || empty($parentConfig['appearance']['suppressCombinationWarning'])) {
260  $combinationWarningMessage = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.inline_use_combination';
261  if (!empty($parentConfig['appearance']['overwriteCombinationWarningMessage'])) {
262  $combinationWarningMessage = $parentConfig['appearance']['overwriteCombinationWarningMessage'];
263  }
264  $message = $this->‪getLanguageService()->‪sL($combinationWarningMessage);
265  $markup = [];
266  // @TODO: This is not a FlashMessage! The markup must be changed and special CSS
267  // @TODO: should be created, in order to prevent confusion.
268  $markup[] = '<div class="alert alert-warning">';
269  $markup[] = ' <div class="media">';
270  $markup[] = ' <div class="media-left">';
271  $markup[] = ' <span class="fa-stack fa-lg">';
272  $markup[] = ' <i class="fa fa-circle fa-stack-2x"></i>';
273  $markup[] = ' <i class="fa fa-exclamation fa-stack-1x"></i>';
274  $markup[] = ' </span>';
275  $markup[] = ' </div>';
276  $markup[] = ' <div class="media-body">';
277  $markup[] = ' <div class="alert-message">' . htmlspecialchars($message) . '</div>';
278  $markup[] = ' </div>';
279  $markup[] = ' </div>';
280  $markup[] = '</div>';
281  $resultArray['html'] = implode(LF, $markup);
282  }
283 
284  $childArray = $this->‪renderChild($childData);
285  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $childArray);
286 
287  // If this is a new record, add a pid value to store this record and the pointer value for the intermediate table
288  if ($childData['command'] === 'new') {
289  $comboFormFieldName = 'data[' . $childData['tableName'] . '][' . $childData['databaseRow']['uid'] . '][pid]';
290  $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($comboFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['pid']) . '" />';
291  }
292  // If the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation
293  if ($childData['command'] === 'new' || $parentConfig['foreign_unique'] === $parentConfig['foreign_selector']) {
294  $parentFormFieldName = 'data' . $appendFormFieldNames . '[' . $parentConfig['foreign_selector'] . ']';
295  $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($parentFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['uid']) . '" />';
296  }
297 
298  return $resultArray;
299  }
300 
308  protected function ‪renderForeignRecordHeader(array ‪$data)
309  {
310  $languageService = $this->‪getLanguageService();
311  $inlineConfig = ‪$data['inlineParentConfig'];
312  $foreignTable = $inlineConfig['foreign_table'];
313  $rec = ‪$data['databaseRow'];
314  // Init:
315  $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix(‪$data['inlineFirstPid']);
316  $objectId = $domObjectId . '-' . $foreignTable . '-' . $rec['uid'];
317 
318  $recordTitle = ‪$data['recordTitle'];
319  if (!empty($recordTitle)) {
320  // The user function may return HTML, therefore we can't escape it
321  if (empty(‪$data['processedTca']['ctrl']['formattedLabel_userFunc'])) {
322  $recordTitle = ‪BackendUtility::getRecordTitlePrep($recordTitle);
323  }
324  } else {
325  $recordTitle = '<em>[' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</em>';
326  }
327 
328  $altText = ‪BackendUtility::getRecordIconAltText($rec, $foreignTable);
329 
330  $iconImg = '<span title="' . $altText . '" id="' . htmlspecialchars($objectId) . '_icon' . '">' . $this->iconFactory->getIconForRecord($foreignTable, $rec, ‪Icon::SIZE_SMALL)->render() . '</span>';
331  $label = '<span id="' . $objectId . '_label">' . $recordTitle . '</span>';
332  $ctrl = $this->‪renderForeignRecordHeaderControl($data);
333  $thumbnail = false;
334 
335  // Renders a thumbnail for the header
336  if (‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails'] && !empty($inlineConfig['appearance']['headerThumbnail']['field'])) {
337  $fieldValue = $rec[$inlineConfig['appearance']['headerThumbnail']['field']];
338  $fileUid = $fieldValue[0]['uid'];
339 
340  if (!empty($fileUid)) {
341  try {
342  $fileObject = ‪ResourceFactory::getInstance()->‪getFileObject($fileUid);
343  } catch (\InvalidArgumentException $e) {
344  $fileObject = null;
345  }
346  if ($fileObject && $fileObject->isMissing()) {
347  $thumbnail .= '<span class="label label-danger">'
348  . htmlspecialchars(static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_missing'))
349  . '</span>&nbsp;' . htmlspecialchars($fileObject->getName()) . '<br />';
350  } elseif ($fileObject) {
351  $imageSetup = $inlineConfig['appearance']['headerThumbnail'];
352  unset($imageSetup['field']);
353  if (!empty($rec['crop'])) {
354  $imageSetup['crop'] = $rec['crop'];
355  }
356  $imageSetup = array_merge(['width' => '45', 'height' => '45c'], $imageSetup);
357 
358  if (‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']
359  && GeneralUtility::inList(‪$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
360  $processedImage = $fileObject->process(‪ProcessedFile::CONTEXT_IMAGEPREVIEW, $imageSetup);
361  // Only use a thumbnail if the processing process was successful by checking if image width is set
362  if ($processedImage->getProperty('width')) {
363  $imageUrl = $processedImage->getPublicUrl(true);
364  $thumbnail = '<img src="' . $imageUrl . '" ' .
365  'width="' . $processedImage->getProperty('width') . '" ' .
366  'height="' . $processedImage->getProperty('height') . '" ' .
367  'alt="' . htmlspecialchars($altText) . '" ' .
368  'title="' . htmlspecialchars($altText) . '">';
369  }
370  } else {
371  $thumbnail = '';
372  }
373  }
374  }
375  }
376 
377  if (!empty($inlineConfig['appearance']['headerThumbnail']['field']) && $thumbnail) {
378  $mediaContainer = '<div class="form-irre-header-cell form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>';
379  } else {
380  $mediaContainer = '<div class="form-irre-header-cell form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>';
381  }
382  $header = $mediaContainer . '
383  <div class="form-irre-header-cell form-irre-header-body">' . $label . '</div>
384  <div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>';
385 
386  return $header;
387  }
388 
397  protected function ‪renderForeignRecordHeaderControl(array ‪$data)
398  {
399  $rec = ‪$data['databaseRow'];
400  $inlineConfig = ‪$data['inlineParentConfig'];
401  $foreignTable = $inlineConfig['foreign_table'];
402  $languageService = $this->‪getLanguageService();
403  $backendUser = $this->‪getBackendUserAuthentication();
404  // Initialize:
405  $cells = [
406  'edit' => '',
407  'hide' => '',
408  'delete' => '',
409  'info' => '',
410  'new' => '',
411  'sort.up' => '',
412  'sort.down' => '',
413  'dragdrop' => '',
414  'localize' => '',
415  'locked' => '',
416  ];
417  $isNewItem = strpos($rec['uid'], 'NEW') === 0;
418  $isParentReadOnly = isset($inlineConfig['readOnly']) && $inlineConfig['readOnly'];
419  $isParentExisting = ‪MathUtility::canBeInterpretedAsInteger(‪$data['inlineParentUid']);
420  $tcaTableCtrl = ‪$GLOBALS['TCA'][$foreignTable]['ctrl'];
421  $tcaTableCols = ‪$GLOBALS['TCA'][$foreignTable]['columns'];
422  $isPagesTable = $foreignTable === 'pages';
423  $isSysFileReferenceTable = $foreignTable === 'sys_file_reference';
424  $enableManualSorting = $tcaTableCtrl['sortby'] || $inlineConfig['MM'] || !‪$data['isOnSymmetricSide']
425  && $inlineConfig['foreign_sortby'] || ‪$data['isOnSymmetricSide'] && $inlineConfig['symmetric_sortby'];
426  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix(‪$data['inlineFirstPid']);
427  $nameObjectFt = $nameObject . '-' . $foreignTable;
428  $nameObjectFtId = $nameObjectFt . '-' . $rec['uid'];
429  $calcPerms = $backendUser->calcPerms(‪BackendUtility::readPageAccess($rec['pid'], $backendUser->getPagePermsClause(‪Permission::PAGE_SHOW)));
430  // If the listed table is 'pages' we have to request the permission settings for each page:
431  $localCalcPerms = false;
432  if ($isPagesTable) {
433  $localCalcPerms = $backendUser->calcPerms(‪BackendUtility::getRecord('pages', $rec['uid']));
434  }
435  // This expresses the edit permissions for this particular element:
436  $permsEdit = $isPagesTable && $localCalcPerms & ‪Permission::PAGE_EDIT || !$isPagesTable && $calcPerms & ‪Permission::CONTENT_EDIT;
437  // Controls: Defines which controls should be shown
438  $enabledControls = $inlineConfig['appearance']['enabledControls'];
439  // Hook: Can disable/enable single controls for specific child records:
440  foreach ($this->hookObjects as $hookObj) {
442  $hookObj->renderForeignRecordHeaderControl_preProcess(‪$data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, ‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $enabledControls);
443  }
444  if (‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
445  $cells['localize'] = '<span title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize.isLocalizable')) . '">
446  ' . $this->iconFactory->getIcon('actions-edit-localize-status-low', ‪Icon::SIZE_SMALL)->render() . '
447  </span>';
448  }
449  // "Info": (All records)
450  // @todo: hardcoded sys_file!
451  if ($rec['table_local'] === 'sys_file') {
452  $uid = $rec['uid_local'][0]['uid'];
453  $table = '_FILE';
454  } else {
455  $uid = $rec['uid'];
456  $table = $foreignTable;
457  }
458  if ($enabledControls['info']) {
459  if ($isNewItem) {
460  $cells['info'] = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', ‪Icon::SIZE_SMALL)->render() . '</span>';
461  } else {
462  $cells['info'] = '
463  <a class="btn btn-default" href="#" onclick="' . htmlspecialchars('top.TYPO3.InfoWindow.showItem(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($uid) . '); return false;') . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo')) . '">
464  ' . $this->iconFactory->getIcon('actions-document-info', ‪Icon::SIZE_SMALL)->render() . '
465  </a>';
466  }
467  }
468  // If the table is NOT a read-only table, then show these links:
469  if (!$isParentReadOnly && !$tcaTableCtrl['readOnly'] && !‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
470  // "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):
471  if ($enabledControls['new'] && ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues'])) {
472  if (!$isPagesTable && $calcPerms & ‪Permission::CONTENT_EDIT || $isPagesTable && $calcPerms & ‪Permission::PAGE_NEW) {
473  $onClick = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ',' . GeneralUtility::quoteJSvalue($rec['uid']) . ')';
474  $style = '';
475  if ($inlineConfig['inline']['inlineNewButtonStyle']) {
476  $style = ' style="' . $inlineConfig['inline']['inlineNewButtonStyle'] . '"';
477  }
478  $cells['new'] = '
479  <a class="btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'] . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:new' . ($isPagesTable ? 'Page' : 'Record'))) . '" ' . $style . '>
480  ' . $this->iconFactory->getIcon('actions-' . ($isPagesTable ? 'page-new' : 'add'), ‪Icon::SIZE_SMALL)->render() . '
481  </a>';
482  }
483  }
484  // "Up/Down" links
485  if ($enabledControls['sort'] && $permsEdit && $enableManualSorting) {
486  // Up
487  $onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'1\')';
488  $icon = 'actions-move-up';
489  $class = '';
490  if ($inlineConfig['inline']['first'] == $rec['uid']) {
491  $class = ' disabled';
492  $icon = 'empty-empty';
493  }
494  $cells['sort.up'] = '
495  <a class="btn btn-default sortingUp' . $class . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveUp')) . '">
496  ' . $this->iconFactory->getIcon($icon, ‪Icon::SIZE_SMALL)->render() . '
497  </a>';
498  // Down
499  $onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'-1\')';
500  $icon = 'actions-move-down';
501  $class = '';
502  if ($inlineConfig['inline']['last'] == $rec['uid']) {
503  $class = ' disabled';
504  $icon = 'empty-empty';
505  }
506 
507  $cells['sort.down'] = '
508  <a class="btn btn-default sortingDown' . $class . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveDown')) . '">
509  ' . $this->iconFactory->getIcon($icon, ‪Icon::SIZE_SMALL)->render() . '
510  </a>';
511  }
512  // "Edit" link:
513  if (($rec['table_local'] === 'sys_file') && !$isNewItem && $backendUser->check('tables_modify', 'sys_file_metadata')) {
514  $sys_language_uid = 0;
515  if (!empty($rec['sys_language_uid'])) {
516  $sys_language_uid = $rec['sys_language_uid'][0];
517  }
518  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
519  ->getQueryBuilderForTable('sys_file_metadata');
520  $recordInDatabase = $queryBuilder
521  ->select('uid')
522  ->from('sys_file_metadata')
523  ->where(
524  $queryBuilder->expr()->eq(
525  'file',
526  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
527  ),
528  $queryBuilder->expr()->eq(
529  'sys_language_uid',
530  $queryBuilder->createNamedParameter($sys_language_uid, \PDO::PARAM_INT)
531  )
532  )
533  ->setMaxResults(1)
534  ->execute()
535  ->fetch();
536  if (!empty($recordInDatabase)) {
537  $uriBuilder = GeneralUtility::makeInstance(\‪TYPO3\CMS\Backend\Routing\UriBuilder::class);
538  $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
539  'edit[sys_file_metadata][' . (int)$recordInDatabase['uid'] . ']' => 'edit',
540  'returnUrl' => $this->data['returnUrl']
541  ]);
542  $title = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.editMetadata');
543  $cells['edit'] = '
544  <a class="btn btn-default" href="' . htmlspecialchars($url) . '" title="' . htmlspecialchars($title) . '">
545  ' . $this->iconFactory->getIcon('actions-open', ‪Icon::SIZE_SMALL)->render() . '
546  </a>';
547  }
548  }
549  // "Delete" link:
550  if ($enabledControls['delete'] && ($isPagesTable && $localCalcPerms & ‪Permission::PAGE_DELETE
551  || !$isPagesTable && $calcPerms & ‪Permission::CONTENT_EDIT
552  || $isSysFileReferenceTable && $calcPerms & ‪Permission::PAGE_EDIT)
553  ) {
554  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete'));
555  $icon = $this->iconFactory->getIcon('actions-edit-delete', ‪Icon::SIZE_SMALL)->render();
556  $cells['delete'] = '<a href="#" class="btn btn-default t3js-editform-delete-inline-record" data-objectid="' . htmlspecialchars($nameObjectFtId) . '" title="' . $title . '">' . $icon . '</a>';
557  }
558 
559  // "Hide/Unhide" links:
560  $hiddenField = $tcaTableCtrl['enablecolumns']['disabled'];
561  if ($enabledControls['hide'] && $permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $backendUser->check('non_exclude_fields', $foreignTable . ':' . $hiddenField))) {
562  $onClick = 'return inline.enableDisableRecord(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ',' .
563  GeneralUtility::quoteJSvalue($hiddenField) . ')';
564  $className = 't3js-' . $nameObjectFtId . '_disabled';
565  if ($rec[$hiddenField]) {
566  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($isPagesTable ? 'Page' : '')));
567  $cells['hide'] = '
568  <a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="
569  ' . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">
570  ' . $this->iconFactory->getIcon('actions-edit-unhide', ‪Icon::SIZE_SMALL)->render() . '
571  </a>';
572  } else {
573  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($isPagesTable ? 'Page' : '')));
574  $cells['hide'] = '
575  <a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="
576  ' . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">
577  ' . $this->iconFactory->getIcon('actions-edit-hide', ‪Icon::SIZE_SMALL)->render() . '
578  </a>';
579  }
580  }
581  // Drag&Drop Sorting: Sortable handler for script.aculo.us
582  if ($enabledControls['dragdrop'] && $permsEdit && $enableManualSorting && $inlineConfig['appearance']['useSortable']) {
583  $cells['dragdrop'] = '
584  <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')) . '">
585  ' . $this->iconFactory->getIcon('actions-move-move', ‪Icon::SIZE_SMALL)->render() . '
586  </span>';
587  }
588  } elseif (‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'] && $isParentExisting) {
589  if ($enabledControls['localize'] && ‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
590  $onClick = 'inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ', ' . GeneralUtility::quoteJSvalue($rec['uid']) . ');';
591  $cells['localize'] = '
592  <a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localize')) . '">
593  ' . $this->iconFactory->getIcon('actions-document-localize', ‪Icon::SIZE_SMALL)->render() . '
594  </a>';
595  }
596  }
597  // If the record is edit-locked by another user, we will show a little warning sign:
598  if ($lockInfo = ‪BackendUtility::isRecordLocked($foreignTable, $rec['uid'])) {
599  $cells['locked'] = '
600  <a class="btn btn-default" href="#" data-toggle="tooltip" data-title="' . htmlspecialchars($lockInfo['msg']) . '">
601  ' . '<span title="' . htmlspecialchars($lockInfo['msg']) . '">' . $this->iconFactory->getIcon('warning-in-use', ‪Icon::SIZE_SMALL)->render() . '</span>' . '
602  </a>';
603  }
604  // Hook: Post-processing of single controls for specific child records:
605  foreach ($this->hookObjects as $hookObj) {
606  $hookObj->renderForeignRecordHeaderControl_postProcess(‪$data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, ‪$data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $cells);
607  }
608 
609  $out = '';
610  if (!empty($cells['edit']) || !empty($cells['hide']) || !empty($cells['delete'])) {
611  $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['edit'] . $cells['hide'] . $cells['delete'] . '</div>';
612  unset($cells['edit'], $cells['hide'], $cells['delete']);
613  }
614  if (!empty($cells['info']) || !empty($cells['new']) || !empty($cells['sort.up']) || !empty($cells['sort.down']) || !empty($cells['dragdrop'])) {
615  $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['info'] . $cells['new'] . $cells['sort.up'] . $cells['sort.down'] . $cells['dragdrop'] . '</div>';
616  unset($cells['info'], $cells['new'], $cells['sort.up'], $cells['sort.down'], $cells['dragdrop']);
617  }
618  if (!empty($cells['localize'])) {
619  $out .= '<div class="btn-group btn-group-sm" role="group">' . $cells['localize'] . '</div>';
620  unset($cells['localize']);
621  }
622  if (!empty($cells)) {
623  $out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $cells) . '</div>';
624  }
625  return $out;
626  }
627 
634  protected function ‪normalizeUid($string)
635  {
636  $parts = explode('|', $string);
637  return $parts[0];
638  }
639 
647  protected function ‪initHookObjects()
648  {
649  $this->hookObjects = [];
650  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'] ?? [] as $className) {
651  $processObject = GeneralUtility::makeInstance($className);
652  if (!$processObject instanceof InlineElementHookInterface) {
653  throw new \UnexpectedValueException($className . ' must implement interface ' . InlineElementHookInterface::class, 1202072000);
654  }
655  $this->hookObjects[] = $processObject;
656  }
657  }
658 
662  protected function ‪getBackendUserAuthentication()
663  {
664  return ‪$GLOBALS['BE_USER'];
665  }
666 
670  protected function ‪getLanguageService()
671  {
672  return ‪$GLOBALS['LANG'];
673  }
674 }
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:29
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderForeignRecordHeaderControl
‪string renderForeignRecordHeaderControl(array $data)
Definition: InlineRecordContainer.php:393
‪TYPO3\CMS\Backend\Form\Exception\AccessDeniedContentEditException
Definition: AccessDeniedContentEditException.php:21
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderForeignRecordHeader
‪string renderForeignRecordHeader(array $data)
Definition: InlineRecordContainer.php:304
‪TYPO3\CMS\Core\Resource\ProcessedFile\CONTEXT_IMAGEPREVIEW
‪const CONTEXT_IMAGEPREVIEW
Definition: ProcessedFile.php:50
‪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\Container\InlineRecordContainer\$inlineStackProcessor
‪InlineStackProcessor $inlineStackProcessor
Definition: InlineRecordContainer.php:53
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪array initializeResultArray()
Definition: AbstractNode.php:88
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_NEW
‪const PAGE_NEW
Definition: Permission.php:47
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:25
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\getLanguageService
‪LanguageService getLanguageService()
Definition: InlineRecordContainer.php:666
‪TYPO3
‪TYPO3\CMS\Backend\Form\AbstractNode\$nodeFactory
‪NodeFactory $nodeFactory
Definition: AbstractNode.php:34
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\__construct
‪__construct(NodeFactory $nodeFactory, array $data)
Definition: InlineRecordContainer.php:71
‪TYPO3\CMS\Backend\Form\Container
Definition: AbstractContainer.php:2
‪TYPO3\CMS\Core\Resource\ResourceFactory\getInstance
‪static ResourceFactory getInstance()
Definition: ResourceFactory.php:39
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordIconAltText
‪static string getRecordIconAltText($row, $table='pages')
Definition: BackendUtility.php:1621
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:31
‪TYPO3\CMS\Backend\Form\InlineStackProcessor\getCurrentStructureDomObjectIdPrefix
‪string getCurrentStructureDomObjectIdPrefix($inlineFirstPid)
Definition: InlineStackProcessor.php:163
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\$iconFactory
‪IconFactory $iconFactory
Definition: InlineRecordContainer.php:63
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:158
‪TYPO3\CMS\Backend\Form\InlineStackProcessor\initializeByGivenStructure
‪initializeByGivenStructure(array $structure=[])
Definition: InlineStackProcessor.php:41
‪TYPO3\CMS\Backend\Form\Element\InlineElementHookInterface
Definition: InlineElementHookInterface.php:21
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:23
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitlePrep
‪static string getRecordTitlePrep($title, $titleLength=0)
Definition: BackendUtility.php:1883
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\normalizeUid
‪string normalizeUid($string)
Definition: InlineRecordContainer.php:630
‪TYPO3\CMS\Backend\Form\AbstractNode\$data
‪array $data
Definition: AbstractNode.php:40
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:33
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer
Definition: AbstractContainer.php:27
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:32
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\$inlineData
‪array $inlineData
Definition: InlineRecordContainer.php:49
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Backend\Form\NodeFactory
Definition: NodeFactory.php:36
‪TYPO3\CMS\Core\Resource\ProcessedFile
Definition: ProcessedFile.php:42
‪TYPO3\CMS\Backend\Form\InlineStackProcessor\getCurrentStructureFormPrefix
‪string getCurrentStructureFormPrefix()
Definition: InlineStackProcessor.php:146
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:52
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_EDIT
‪const PAGE_EDIT
Definition: Permission.php:37
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_DELETE
‪const PAGE_DELETE
Definition: Permission.php:42
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer
Definition: InlineRecordContainer.php:44
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\initHookObjects
‪initHookObjects()
Definition: InlineRecordContainer.php:643
‪TYPO3\CMS\Core\Resource\ResourceFactory\getFileObject
‪File getFileObject($uid, array $fileData=[])
Definition: ResourceFactory.php:399
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderChild
‪array renderChild(array $data)
Definition: InlineRecordContainer.php:216
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:29
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:29
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\render
‪array render()
Definition: InlineRecordContainer.php:85
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\renderCombinationChild
‪array renderCombinationChild(array $data, $appendFormFieldNames)
Definition: InlineRecordContainer.php:240
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\$hookObjects
‪array $hookObjects
Definition: InlineRecordContainer.php:59
‪TYPO3\CMS\Backend\Form\Container\InlineRecordContainer\getBackendUserAuthentication
‪BackendUserAuthentication getBackendUserAuthentication()
Definition: InlineRecordContainer.php:658
‪TYPO3\CMS\Backend\Utility\BackendUtility\readPageAccess
‪static array bool readPageAccess($id, $perms_clause)
Definition: BackendUtility.php:635
‪TYPO3\CMS\Backend\Utility\BackendUtility\isRecordLocked
‪static array bool isRecordLocked($table, $uid)
Definition: BackendUtility.php:3397