TYPO3  7.6
InlineRecordContainer.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Container;
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 
18 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedContentEditException;
33 
45 {
51  protected $inlineData = array();
52 
57 
63  protected $hookObjects = array();
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  if (!$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
131  $collapseAll = isset($inlineConfig['appearance']['collapseAll']) && $inlineConfig['appearance']['collapseAll'];
132  $expandAll = isset($inlineConfig['appearance']['collapseAll']) && !$inlineConfig['appearance']['collapseAll'];
133  if ($isNewRecord) {
134  $isExpanded = $expandAll || !$collapseAll;
135  } else {
136  $expandCollapseStateArray = $data['inlineExpandCollapseStateArray'];
137  $isExpandedByUcState = isset($expandCollapseStateArray[$foreignTable])
138  && is_array($expandCollapseStateArray[$foreignTable])
139  && in_array($record['uid'], $expandCollapseStateArray[$foreignTable]) !== false;
140  $isExpanded = $inlineConfig['renderFieldsOnly'] || !$collapseAll && $isExpandedByUcState || $expandAll;
141  }
142 
143  if ($isNewRecord || $isExpanded) {
144  // Render full content ONLY IF this is an AJAX request, a new record, or the record is not collapsed
145  $combinationHtml = '';
146  if (isset($data['combinationChild'])) {
147  $combinationChild = $this->renderCombinationChild($data, $appendFormFieldNames);
148  $combinationHtml = $combinationChild['html'];
149  $combinationChild['html'] = '';
150  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $combinationChild);
151  }
152  $childArray = $this->renderChild($data);
153  $html = $childArray['html'];
154  $childArray['html'] = '';
155  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
156  } else {
157  // This string is the marker for the JS-function to check if the full content has already been loaded
158  $html = '<!--notloaded-->';
159  }
160  if ($isNewRecord) {
161  // Add pid of record as hidden field
162  $html .= '<input type="hidden" name="data' . $appendFormFieldNames . '[pid]" value="' . $record['pid'] . '"/>';
163  // Tell DataHandler this record is expanded
164  $ucFieldName = 'uc[inlineView]'
165  . '[' . $data['inlineTopMostParentTableName'] . ']'
166  . '[' . $data['inlineTopMostParentUid'] . ']'
167  . $appendFormFieldNames;
168  $html .= '<input type="hidden" name="' . $ucFieldName . '" value="' . $isExpanded . '" />';
169  } else {
170  // Set additional field for processing for saving
171  $html .= '<input type="hidden" name="cmd' . $appendFormFieldNames . '[delete]" value="1" disabled="disabled" />';
172  if (!$isExpanded
173  && !empty($GLOBALS['TCA'][$foreignTable]['ctrl']['enablecolumns']['disabled'])
174  ) {
175  $checked = !empty($record['hidden']) ? ' checked="checked"' : '';
176  $html .= '<input type="checkbox" name="data' . $appendFormFieldNames . '[hidden]_0" value="1"' . $checked . ' />';
177  $html .= '<input type="input" name="data' . $appendFormFieldNames . '[hidden]" value="' . $record['hidden'] . '" />';
178  }
179  }
180  // If this record should be shown collapsed
181  $class = $isExpanded ? 'panel-visible' : 'panel-collapsed';
182  }
183  if ($inlineConfig['renderFieldsOnly']) {
184  // Render "body" part only
185  $html = $html . $combinationHtml;
186  } else {
187  // Render header row and content (if expanded)
188  if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
189  $class .= ' t3-form-field-container-inline-placeHolder';
190  }
191  if (isset($record['hidden']) && (int)$record['hidden']) {
192  $class .= ' t3-form-field-container-inline-hidden';
193  }
194  $class .= ($isNewRecord ? ' inlineIsNewRecord' : '');
195  $html = '
196  <div class="panel panel-default panel-condensed ' . trim($class) . '" id="' . $objectId . '_div">
197  <div class="panel-heading" data-toggle="formengine-inline" id="' . $objectId . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
198  <div class="form-irre-header">
199  <div class="form-irre-header-cell form-irre-header-icon">
200  <span class="caret"></span>
201  </div>
202  ' . $this->renderForeignRecordHeader($data) . '
203  </div>
204  </div>
205  <div class="panel-collapse" id="' . $objectId . '_fields" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '" data-returnURL="' . htmlspecialchars(GeneralUtility::getIndpEnv('REQUEST_URI')) . '">' . $html . $combinationHtml . '</div>
206  </div>';
207  }
208 
209  $resultArray['html'] = $html;
210  return $resultArray;
211  }
212 
219  protected function renderChild(array $data)
220  {
221  $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
222  $data['tabAndInlineStack'][] = [
223  'inline',
224  $domObjectId . '-' . $data['tableName'] . '-' . $data['databaseRow']['uid'],
225  ];
226  // @todo: ugly construct ...
227  $data['inlineData'] = $this->inlineData;
228  $data['renderType'] = 'fullRecordContainer';
229  return $this->nodeFactory->create($data)->render();
230  }
231 
243  protected function renderCombinationChild(array $data, $appendFormFieldNames)
244  {
245  $childData = $data['combinationChild'];
246  $parentConfig = $data['inlineParentConfig'];
247 
248  $resultArray = $this->initializeResultArray();
249 
250  // Display Warning FlashMessage if it is not suppressed
251  if (!isset($parentConfig['appearance']['suppressCombinationWarning']) || empty($parentConfig['appearance']['suppressCombinationWarning'])) {
252  $combinationWarningMessage = 'LLL:EXT:lang/locallang_core.xlf:warning.inline_use_combination';
253  if (!empty($parentConfig['appearance']['overwriteCombinationWarningMessage'])) {
254  $combinationWarningMessage = $parentConfig['appearance']['overwriteCombinationWarningMessage'];
255  }
256  $flashMessage = GeneralUtility::makeInstance(
257  FlashMessage::class,
258  $this->getLanguageService()->sL($combinationWarningMessage),
259  '',
261  );
262  $resultArray['html'] = $flashMessage->render();
263  }
264 
265  $childArray = $this->renderChild($childData);
266  $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
267 
268  // If this is a new record, add a pid value to store this record and the pointer value for the intermediate table
269  if ($childData['command'] === 'new') {
270  $comboFormFieldName = 'data[' . $childData['tableName'] . '][' . $childData['databaseRow']['uid'] . '][pid]';
271  $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($comboFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['pid']) . '" />';
272  }
273  // If the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation
274  if ($childData['command'] === 'new' || $parentConfig['foreign_unique'] === $parentConfig['foreign_selector']) {
275  $parentFormFieldName = 'data' . $appendFormFieldNames . '[' . $parentConfig['foreign_selector'] . ']';
276  $resultArray['html'] .= '<input type="hidden" name="' . htmlspecialchars($parentFormFieldName) . '" value="' . htmlspecialchars($childData['databaseRow']['uid']) . '" />';
277  }
278 
279  return $resultArray;
280  }
281 
282 
290  protected function renderForeignRecordHeader(array $data)
291  {
292  $languageService = $this->getLanguageService();
293  $inlineConfig = $data['inlineParentConfig'];
294  $foreignTable = $inlineConfig['foreign_table'];
295  $rec = $data['databaseRow'];
296  // Init:
297  $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
298  $objectId = $domObjectId . '-' . $foreignTable . '-' . $rec['uid'];
299 
300  $recordTitle = $data['recordTitle'];
301  if (empty($recordTitle)) {
302  $recordTitle = '<em>[' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', true) . ']</em>';
303  }
304 
305  $altText = BackendUtility::getRecordIconAltText($rec, $foreignTable);
306 
307  $iconImg = '<span title="' . $altText . '" id="' . htmlspecialchars($objectId) . '_icon' . '">' . $this->iconFactory->getIconForRecord($foreignTable, $rec, Icon::SIZE_SMALL)->render() . '</span>';
308  $label = '<span id="' . $objectId . '_label">' . $recordTitle . '</span>';
309  $ctrl = $this->renderForeignRecordHeaderControl($data);
310  $thumbnail = false;
311 
312  // Renders a thumbnail for the header
313  if (!empty($inlineConfig['appearance']['headerThumbnail']['field'])) {
314  $fieldValue = $rec[$inlineConfig['appearance']['headerThumbnail']['field']];
315  $firstElement = array_shift(GeneralUtility::trimExplode('|', array_shift(GeneralUtility::trimExplode(',', $fieldValue))));
316  $fileUid = array_pop(BackendUtility::splitTable_Uid($firstElement));
317 
318  if (!empty($fileUid)) {
319  try {
320  $fileObject = ResourceFactory::getInstance()->getFileObject($fileUid);
321  } catch (\InvalidArgumentException $e) {
322  $fileObject = null;
323  }
324  if ($fileObject && $fileObject->isMissing()) {
325  $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject);
326  $thumbnail = $flashMessage->render();
327  } elseif ($fileObject) {
328  $imageSetup = $inlineConfig['appearance']['headerThumbnail'];
329  unset($imageSetup['field']);
330  if (!empty($rec['crop'])) {
331  $imageSetup['crop'] = $rec['crop'];
332  }
333  $imageSetup = array_merge(array('width' => '45', 'height' => '45c'), $imageSetup);
334  $processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
335  // Only use a thumbnail if the processing process was successful by checking if image width is set
336  if ($processedImage->getProperty('width')) {
337  $imageUrl = $processedImage->getPublicUrl(true);
338  $thumbnail = '<img src="' . $imageUrl . '" ' .
339  'width="' . $processedImage->getProperty('width') . '" ' .
340  'height="' . $processedImage->getProperty('height') . '" ' .
341  'alt="' . htmlspecialchars($altText) . '" ' .
342  'title="' . htmlspecialchars($altText) . '">';
343  }
344  }
345  }
346  }
347 
348  if (!empty($inlineConfig['appearance']['headerThumbnail']['field']) && $thumbnail) {
349  $mediaContainer = '<div class="form-irre-header-cell form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>';
350  } else {
351  $mediaContainer = '<div class="form-irre-header-cell form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>';
352  }
353  $header = $mediaContainer . '
354  <div class="form-irre-header-cell form-irre-header-body">' . $label . '</div>
355  <div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>';
356 
357  return $header;
358  }
359 
368  protected function renderForeignRecordHeaderControl(array $data)
369  {
370  $rec = $data['databaseRow'];
371  $inlineConfig = $data['inlineParentConfig'];
372  $foreignTable = $inlineConfig['foreign_table'];
373  $languageService = $this->getLanguageService();
374  $backendUser = $this->getBackendUserAuthentication();
375  // Initialize:
376  $cells = array();
377  $additionalCells = array();
378  $isNewItem = substr($rec['uid'], 0, 3) == 'NEW';
379  $isParentExisting = MathUtility::canBeInterpretedAsInteger($data['inlineParentUid']);
380  $tcaTableCtrl = &$GLOBALS['TCA'][$foreignTable]['ctrl'];
381  $tcaTableCols = &$GLOBALS['TCA'][$foreignTable]['columns'];
382  $isPagesTable = $foreignTable === 'pages';
383  $isSysFileReferenceTable = $foreignTable === 'sys_file_reference';
384  $enableManualSorting = $tcaTableCtrl['sortby'] || $inlineConfig['MM'] || !$data['isOnSymmetricSide']
385  && $inlineConfig['foreign_sortby'] || $data['isOnSymmetricSide'] && $inlineConfig['symmetric_sortby'];
386  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
387  $nameObjectFt = $nameObject . '-' . $foreignTable;
388  $nameObjectFtId = $nameObjectFt . '-' . $rec['uid'];
389  $calcPerms = $backendUser->calcPerms(BackendUtility::readPageAccess($rec['pid'], $backendUser->getPagePermsClause(1)));
390  // If the listed table is 'pages' we have to request the permission settings for each page:
391  $localCalcPerms = false;
392  if ($isPagesTable) {
393  $localCalcPerms = $backendUser->calcPerms(BackendUtility::getRecord('pages', $rec['uid']));
394  }
395  // This expresses the edit permissions for this particular element:
396  $permsEdit = $isPagesTable && $localCalcPerms & Permission::PAGE_EDIT || !$isPagesTable && $calcPerms & Permission::CONTENT_EDIT;
397  // Controls: Defines which controls should be shown
398  $enabledControls = $inlineConfig['appearance']['enabledControls'];
399  // Hook: Can disable/enable single controls for specific child records:
400  foreach ($this->hookObjects as $hookObj) {
402  $hookObj->renderForeignRecordHeaderControl_preProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $enabledControls);
403  }
404  if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
405  $cells['localize.isLocalizable'] = '<span title="' . $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localize.isLocalizable', true) . '">'
406  . $this->iconFactory->getIcon('actions-edit-localize-status-low', Icon::SIZE_SMALL)->render()
407  . '</span>';
408  }
409  // "Info": (All records)
410  if ($enabledControls['info'] && !$isNewItem) {
411  if ($rec['table_local'] === 'sys_file') {
412  $uid = (int)substr($rec['uid_local'], 9);
413  $table = '_FILE';
414  } else {
415  $uid = $rec['uid'];
416  $table = $foreignTable;
417  }
418  $cells['info'] = '
419  <a class="btn btn-default" href="#" onclick="' . htmlspecialchars(('top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($uid) . '); return false;')) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:showInfo', true) . '">
420  ' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '
421  </a>';
422  }
423  // If the table is NOT a read-only table, then show these links:
424  if (!$tcaTableCtrl['readOnly'] && !$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
425  // "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):
426  if ($enabledControls['new'] && ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues'])) {
427  if (!$isPagesTable && $calcPerms & Permission::CONTENT_EDIT || $isPagesTable && $calcPerms & Permission::PAGE_NEW) {
428  $onClick = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ',' . GeneralUtility::quoteJSvalue($rec['uid']) . ')';
429  $style = '';
430  if ($inlineConfig['inline']['inlineNewButtonStyle']) {
431  $style = ' style="' . $inlineConfig['inline']['inlineNewButtonStyle'] . '"';
432  }
433  $cells['new'] = '
434  <a class="btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'] . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $languageService->sL(('LLL:EXT:lang/locallang_mod_web_list.xlf:new' . ($isPagesTable ? 'Page' : 'Record')), true) . '" ' . $style . '>
435  ' . $this->iconFactory->getIcon('actions-' . ($isPagesTable ? 'page' : 'document') . '-new', Icon::SIZE_SMALL)->render() . '
436  </a>';
437  }
438  }
439  // "Up/Down" links
440  if ($enabledControls['sort'] && $permsEdit && $enableManualSorting) {
441  // Up
442  $onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'1\')';
443  $style = $inlineConfig['inline']['first'] == $rec['uid'] ? 'style="visibility: hidden;"' : '';
444  $cells['sort.up'] = '
445  <a class="btn btn-default sortingUp" href="#" onclick="' . htmlspecialchars($onClick) . '" ' . $style . ' title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:moveUp', true) . '">
446  ' . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '
447  </a>';
448  // Down
449  $onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'-1\')';
450  $style = $inlineConfig['inline']['last'] == $rec['uid'] ? 'style="visibility: hidden;"' : '';
451  $cells['sort.down'] = '
452  <a class="btn btn-default sortingDown" href="#" onclick="' . htmlspecialchars($onClick) . '" ' . $style . ' title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:moveDown', true) . '">
453  ' . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '
454  </a>';
455  }
456  // "Edit" link:
457  if (($rec['table_local'] === 'sys_file') && !$isNewItem) {
458  $sys_language_uid = 0;
459  if (!empty($rec['sys_language_uid'])) {
460  $sys_language_uid = $rec['sys_language_uid'][0];
461  }
462  $recordInDatabase = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
463  'uid',
464  'sys_file_metadata',
465  'file = ' . (int)substr($rec['uid_local'], 9) . ' AND sys_language_uid = ' . $sys_language_uid
466  );
467  if ($backendUser->check('tables_modify', 'sys_file_metadata')) {
468  $url = BackendUtility::getModuleUrl('record_edit', array(
469  'edit[sys_file_metadata][' . (int)$recordInDatabase['uid'] . ']' => 'edit'
470  ));
471  $editOnClick = 'if (top.content.list_frame) {' .
472  'top.content.list_frame.location.href=' .
473  GeneralUtility::quoteJSvalue($url . '&returnUrl=') .
474  '+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search)' .
475  ';' .
476  '}';
477  $title = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.editMetadata');
478  $cells['editmetadata'] = '
479  <a class="btn btn-default" href="#" class="btn" onclick="' . htmlspecialchars($editOnClick) . '" title="' . htmlspecialchars($title) . '">
480  ' . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '
481  </a>';
482  }
483  }
484  // "Delete" link:
485  if ($enabledControls['delete'] && ($isPagesTable && $localCalcPerms & Permission::PAGE_DELETE
486  || !$isPagesTable && $calcPerms & Permission::CONTENT_EDIT
487  || $isSysFileReferenceTable && $calcPerms & Permission::PAGE_EDIT)
488  ) {
489  $title = $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:delete', true);
490  $icon = $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render();
491  $cells['delete'] = '<a href="#" class="btn btn-default t3js-editform-delete-inline-record" data-objectid="' . htmlspecialchars($nameObjectFtId) . '" title="' . $title . '">' . $icon . '</a>';
492  }
493 
494  // "Hide/Unhide" links:
495  $hiddenField = $tcaTableCtrl['enablecolumns']['disabled'];
496  if ($enabledControls['hide'] && $permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $backendUser->check('non_exclude_fields', $foreignTable . ':' . $hiddenField))) {
497  $onClick = 'return inline.enableDisableRecord(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ')';
498  $className = 't3js-' . $nameObjectFtId . '_disabled';
499  if ($rec[$hiddenField]) {
500  $title = $languageService->sL(('LLL:EXT:lang/locallang_mod_web_list.xlf:unHide' . ($isPagesTable ? 'Page' : '')), true);
501  $cells['hide.unhide'] = '
502  <a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="'
503  . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">' .
504  $this->iconFactory->getIcon('actions-edit-unhide', Icon::SIZE_SMALL)->render() . '
505  </a>';
506  } else {
507  $title = $languageService->sL(('LLL:EXT:lang/locallang_mod_web_list.xlf:hide' . ($isPagesTable ? 'Page' : '')), true);
508  $cells['hide.hide'] = '
509  <a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="'
510  . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">' .
511  $this->iconFactory->getIcon('actions-edit-hide', Icon::SIZE_SMALL)->render() . '
512  </a>';
513  }
514  }
515  // Drag&Drop Sorting: Sortable handler for script.aculo.us
516  if ($enabledControls['dragdrop'] && $permsEdit && $enableManualSorting && $inlineConfig['appearance']['useSortable']) {
517  $additionalCells['dragdrop'] = '
518  <span class="btn btn-default sortableHandle" data-id="' . htmlspecialchars($rec['uid']) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move', true) . '">
519  ' . $this->iconFactory->getIcon('actions-move-move', Icon::SIZE_SMALL)->render() . '
520  </span>';
521  }
522  } elseif ($data['isInlineDefaultLanguageRecordInLocalizedParentContext'] && $isParentExisting) {
523  if ($enabledControls['localize'] && $data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
524  $onClick = 'inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ', ' . GeneralUtility::quoteJSvalue($rec['uid']) . ');';
525  $cells['localize'] = '
526  <a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localize', true) . '">
527  ' . $this->iconFactory->getIcon('actions-document-localize', Icon::SIZE_SMALL)->render() . '
528  </a>';
529  }
530  }
531  // If the record is edit-locked by another user, we will show a little warning sign:
532  if ($lockInfo = BackendUtility::isRecordLocked($foreignTable, $rec['uid'])) {
533  $cells['locked'] = '
534  <a class="btn btn-default" href="#" onclick="alert(' . GeneralUtility::quoteJSvalue($lockInfo['msg']) . ');return false;">
535  ' . '<span title="' . htmlspecialchars($lockInfo['msg']) . '">' . $this->iconFactory->getIcon('status-warning-in-use', Icon::SIZE_SMALL)->render() . '</span>' . '
536  </a>';
537  }
538  // Hook: Post-processing of single controls for specific child records:
539  foreach ($this->hookObjects as $hookObj) {
540  $hookObj->renderForeignRecordHeaderControl_postProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $cells);
541  }
542 
543  $out = '';
544  if (!empty($cells)) {
545  $out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $cells) . '</div>';
546  }
547  if (!empty($additionalCells)) {
548  $out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $additionalCells) . '</div>';
549  }
550  return $out;
551  }
552 
559  protected function normalizeUid($string)
560  {
561  $parts = explode('|', $string);
562  return $parts[0];
563  }
564 
573  protected function initHookObjects()
574  {
575  $this->hookObjects = array();
576  if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'])) {
577  $tceformsInlineHook = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'];
578  if (is_array($tceformsInlineHook)) {
579  foreach ($tceformsInlineHook as $classData) {
580  $processObject = GeneralUtility::getUserObj($classData);
581  if (!$processObject instanceof InlineElementHookInterface) {
582  throw new \UnexpectedValueException('$processObject must implement interface ' . InlineElementHookInterface::class, 1202072000);
583  }
584  $this->hookObjects[] = $processObject;
585  }
586  }
587  }
588  }
589 
593  protected function getBackendUserAuthentication()
594  {
595  return $GLOBALS['BE_USER'];
596  }
597 
601  protected function getDatabaseConnection()
602  {
603  return $GLOBALS['TYPO3_DB'];
604  }
605 
609  protected function getLanguageService()
610  {
611  return $GLOBALS['LANG'];
612  }
613 }