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