TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
DatabaseRecordList.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 
38 {
39  // *********
40  // External:
41  // *********
42 
49  public $allowedNewTables = [];
50 
57  public $deniedNewTables = [];
58 
66  public $newWizards = false;
67 
74 
80  public $showClipboard = false;
81 
87  public $noControlPanels = false;
88 
94  public $clickMenuEnabled = true;
95 
102 
108  public $spaceIcon;
109 
115  public $disableSingleTableView = false;
116 
117  // *********
118  // Internal:
119  // *********
120 
126  public $pageRow = [];
127 
133  protected $csvLines = [];
134 
140  public $csvOutput = false;
141 
147  public $clipObj;
148 
154  public $CBnames = [];
155 
161  protected $referenceCount = [];
162 
169 
177 
181  public $pageinfo;
182 
188  public $MOD_MENU;
189 
195  protected $editable = true;
196 
200  protected $iconFactory;
201 
205  public function __construct()
206  {
207  parent::__construct();
208  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
209  }
210 
217  public function getButtons()
218  {
219  $module = $this->getModule();
220  $backendUser = $this->getBackendUserAuthentication();
221  $lang = $this->getLanguageService();
222  $buttons = [
223  'csh' => '',
224  'view' => '',
225  'edit' => '',
226  'hide_unhide' => '',
227  'move' => '',
228  'new_record' => '',
229  'paste' => '',
230  'level_up' => '',
231  'cache' => '',
232  'reload' => '',
233  'shortcut' => '',
234  'back' => '',
235  'csv' => '',
236  'export' => ''
237  ];
238  // Get users permissions for this page record:
239  $localCalcPerms = $backendUser->calcPerms($this->pageRow);
240  // CSH
241  if ((string)$this->id === '') {
242  $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module_noId');
243  } elseif (!$this->id) {
244  $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module_root');
245  } else {
246  $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module');
247  }
248  if (isset($this->id)) {
249  // View Exclude doktypes 254,255 Configuration:
250  // mod.web_list.noViewWithDokTypes = 254,255
251  if (isset($module->modTSconfig['properties']['noViewWithDokTypes'])) {
252  $noViewDokTypes = GeneralUtility::trimExplode(',', $module->modTSconfig['properties']['noViewWithDokTypes'], true);
253  } else {
254  //default exclusion: doktype 254 (folder), 255 (recycler)
255  $noViewDokTypes = [
256  PageRepository::DOKTYPE_SYSFOLDER,
257  PageRepository::DOKTYPE_RECYCLER
258  ];
259  }
260  if (!in_array($this->pageRow['doktype'], $noViewDokTypes)) {
261  $onClick = htmlspecialchars(BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id)));
262  $buttons['view'] = '<a href="#" onclick="' . $onClick . '" title="'
263  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">'
264  . $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render() . '</a>';
265  }
266  // New record on pages that are not locked by editlock
267  if (!$module->modTSconfig['properties']['noCreateRecordsLink'] && $this->editLockPermissions()) {
268  $onClick = htmlspecialchars('return jumpExt(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('db_new', ['id' => $this->id])) . ');');
269  $buttons['new_record'] = '<a href="#" onclick="' . $onClick . '" title="'
270  . htmlspecialchars($lang->getLL('newRecordGeneral')) . '">'
271  . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render() . '</a>';
272  }
273  // If edit permissions are set, see
274  // \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
275  if ($localCalcPerms & Permission::PAGE_EDIT && !empty($this->id) && $this->editLockPermissions() && $this->getBackendUserAuthentication()->checkLanguageAccess(0)) {
276  // Edit
277  $params = '&edit[pages][' . $this->pageRow['uid'] . ']=edit';
278  $onClick = htmlspecialchars(BackendUtility::editOnClick($params, '', -1));
279  $buttons['edit'] = '<a href="#" onclick="' . $onClick . '" title="' . htmlspecialchars($lang->getLL('editPage')) . '">'
280  . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
281  . '</a>';
282  }
283  // Paste
284  if (($localCalcPerms & Permission::PAGE_NEW || $localCalcPerms & Permission::CONTENT_EDIT) && $this->editLockPermissions()) {
285  $elFromTable = $this->clipObj->elFromTable('');
286  if (!empty($elFromTable)) {
287  $confirmText = $this->clipObj->confirmMsgText('pages', $this->pageRow, 'into', $elFromTable);
288  $buttons['paste'] = '<a'
289  . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('', $this->id)) . '"'
290  . ' title="' . htmlspecialchars($lang->getLL('clip_paste')) . '"'
291  . ' class="t3js-modal-trigger"'
292  . ' data-severity="warning"'
293  . ' data-title="' . htmlspecialchars($lang->getLL('clip_paste')) . '"'
294  . ' data-content="' . htmlspecialchars($confirmText) . '"'
295  . '>'
296  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
297  . '</a>';
298  }
299  }
300  // Cache
301  $buttons['cache'] = '<a href="' . htmlspecialchars(($this->listURL() . '&clear_cache=1')) . '" title="'
302  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.clear_cache')) . '">'
303  . $this->iconFactory->getIcon('actions-system-cache-clear', Icon::SIZE_SMALL)->render() . '</a>';
304  if ($this->table && (!isset($module->modTSconfig['properties']['noExportRecordsLinks'])
305  || (isset($module->modTSconfig['properties']['noExportRecordsLinks'])
306  && !$module->modTSconfig['properties']['noExportRecordsLinks']))
307  ) {
308  // CSV
309  $buttons['csv'] = '<a href="' . htmlspecialchars(($this->listURL() . '&csv=1')) . '" title="'
310  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.csv')) . '">'
311  . $this->iconFactory->getIcon('actions-document-export-csv', Icon::SIZE_SMALL)->render() . '</a>';
312  // Export
313  if (ExtensionManagementUtility::isLoaded('impexp')) {
314  $url = BackendUtility::getModuleUrl('xMOD_tximpexp', ['tx_impexp[action]' => 'export']);
315  $buttons['export'] = '<a href="' . htmlspecialchars($url . '&tx_impexp[list][]='
316  . rawurlencode($this->table . ':' . $this->id)) . '" title="'
317  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.export')) . '">'
318  . $this->iconFactory->getIcon('actions-document-export-t3d', Icon::SIZE_SMALL)->render() . '</a>';
319  }
320  }
321  // Reload
322  $buttons['reload'] = '<a href="' . htmlspecialchars($this->listURL()) . '" title="'
323  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.reload')) . '">'
324  . $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render() . '</a>';
325  // Shortcut
326  if ($backendUser->mayMakeShortcut()) {
327  $buttons['shortcut'] = $this->getDocumentTemplate()->makeShortcutIcon(
328  'id, M, imagemode, pointer, table, search_field, search_levels, showLimit, sortField, sortRev',
329  implode(',', array_keys($this->MOD_MENU)),
330  'web_list'
331  );
332  }
333  // Back
334  if ($this->returnUrl) {
335  $href = htmlspecialchars(GeneralUtility::linkThisUrl($this->returnUrl, ['id' => $this->id]));
336  $buttons['back'] = '<a href="' . $href . '" class="typo3-goBack" title="'
337  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.goBack')) . '">'
338  . $this->iconFactory->getIcon('actions-view-go-back', Icon::SIZE_SMALL)->render() . '</a>';
339  }
340  }
341  return $buttons;
342  }
343 
351  {
352  $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
353  $module = $this->getModule();
354  $backendUser = $this->getBackendUserAuthentication();
355  $lang = $this->getLanguageService();
356  // Get users permissions for this page record:
357  $localCalcPerms = $backendUser->calcPerms($this->pageRow);
358  // CSH
359  if ((string)$this->id === '') {
360  $fieldName = 'list_module_noId';
361  } elseif (!$this->id) {
362  $fieldName = 'list_module_root';
363  } else {
364  $fieldName = 'list_module';
365  }
366  $cshButton = $buttonBar->makeHelpButton()
367  ->setModuleName('xMOD_csh_corebe')
368  ->setFieldName($fieldName);
369  $buttonBar->addButton($cshButton);
370  if (isset($this->id)) {
371  // View Exclude doktypes 254,255 Configuration:
372  // mod.web_list.noViewWithDokTypes = 254,255
373  if (isset($module->modTSconfig['properties']['noViewWithDokTypes'])) {
374  $noViewDokTypes = GeneralUtility::trimExplode(',', $module->modTSconfig['properties']['noViewWithDokTypes'], true);
375  } else {
376  //default exclusion: doktype 254 (folder), 255 (recycler)
377  $noViewDokTypes = [
378  PageRepository::DOKTYPE_SYSFOLDER,
379  PageRepository::DOKTYPE_RECYCLER
380  ];
381  }
382  // New record on pages that are not locked by editlock
383  if (!$module->modTSconfig['properties']['noCreateRecordsLink'] && $this->editLockPermissions()) {
384  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('db_new', ['id' => $this->id])) . ');';
385  $newRecordButton = $buttonBar->makeLinkButton()
386  ->setHref('#')
387  ->setOnClick($onClick)
388  ->setTitle($lang->getLL('newRecordGeneral'))
389  ->setIcon($this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL));
390  $buttonBar->addButton($newRecordButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
391  }
392  if (!in_array($this->pageRow['doktype'], $noViewDokTypes)) {
393  $onClick = BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id));
394  $viewButton = $buttonBar->makeLinkButton()
395  ->setHref('#')
396  ->setOnClick($onClick)
397  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
398  ->setIcon($this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL));
399  $buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
400  }
401  // If edit permissions are set, see
402  // \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
403  if ($localCalcPerms & Permission::PAGE_EDIT && !empty($this->id) && $this->editLockPermissions()) {
404  // Edit
405  $params = '&edit[pages][' . $this->pageRow['uid'] . ']=edit';
406  $onClick = BackendUtility::editOnClick($params, '', -1);
407  $editButton = $buttonBar->makeLinkButton()
408  ->setHref('#')
409  ->setOnClick($onClick)
410  ->setTitle($lang->getLL('editPage'))
411  ->setIcon($this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL));
412  $buttonBar->addButton($editButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
413  }
414  // Paste
415  if ($this->showClipboard && ($localCalcPerms & Permission::PAGE_NEW || $localCalcPerms & Permission::CONTENT_EDIT) && $this->editLockPermissions()) {
416  $elFromTable = $this->clipObj->elFromTable('');
417  if (!empty($elFromTable)) {
418  $confirmMessage = $this->clipObj->confirmMsgText('pages', $this->pageRow, 'into', $elFromTable);
419  $pasteButton = $buttonBar->makeLinkButton()
420  ->setHref($this->clipObj->pasteUrl('', $this->id))
421  ->setTitle($lang->getLL('clip_paste'))
422  ->setClasses('t3js-modal-trigger')
423  ->setDataAttributes([
424  'severity' => 'warning',
425  'content' => $confirmMessage,
426  'title' => $lang->getLL('clip_paste')
427  ])
428  ->setIcon($this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL));
429  $buttonBar->addButton($pasteButton, ButtonBar::BUTTON_POSITION_LEFT, 40);
430  }
431  }
432  // Cache
433  $clearCacheButton = $buttonBar->makeLinkButton()
434  ->setHref($this->listURL() . '&clear_cache=1')
435  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.clear_cache'))
436  ->setIcon($this->iconFactory->getIcon('actions-system-cache-clear', Icon::SIZE_SMALL));
437  $buttonBar->addButton($clearCacheButton, ButtonBar::BUTTON_POSITION_RIGHT);
438  if ($this->table && (!isset($module->modTSconfig['properties']['noExportRecordsLinks'])
439  || (isset($module->modTSconfig['properties']['noExportRecordsLinks'])
440  && !$module->modTSconfig['properties']['noExportRecordsLinks']))
441  ) {
442  // CSV
443  $csvButton = $buttonBar->makeLinkButton()
444  ->setHref($this->listURL() . '&csv=1')
445  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.csv'))
446  ->setIcon($this->iconFactory->getIcon('actions-document-export-csv', Icon::SIZE_SMALL));
447  $buttonBar->addButton($csvButton, ButtonBar::BUTTON_POSITION_LEFT, 40);
448  // Export
449  if (ExtensionManagementUtility::isLoaded('impexp')) {
450  $url = BackendUtility::getModuleUrl('xMOD_tximpexp', ['tx_impexp[action]' => 'export']);
451  $exportButton = $buttonBar->makeLinkButton()
452  ->setHref($url . '&tx_impexp[list][]=' . rawurlencode($this->table . ':' . $this->id))
453  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.export'))
454  ->setIcon($this->iconFactory->getIcon('actions-document-export-t3d', Icon::SIZE_SMALL));
455  $buttonBar->addButton($exportButton, ButtonBar::BUTTON_POSITION_LEFT, 40);
456  }
457  }
458  // Reload
459  $reloadButton = $buttonBar->makeLinkButton()
460  ->setHref($this->listURL())
461  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.reload'))
462  ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
463  $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
464  // Shortcut
465  if ($backendUser->mayMakeShortcut()) {
466  $shortCutButton = $buttonBar->makeShortcutButton()
467  ->setModuleName('web_list')
468  ->setGetVariables([
469  'id',
470  'M',
471  'imagemode',
472  'pointer',
473  'table',
474  'search_field',
475  'search_levels',
476  'showLimit',
477  'sortField',
478  'sortRev'
479  ])
480  ->setSetVariables(array_keys($this->MOD_MENU));
481  $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT);
482  }
483  // Back
484  if ($this->returnUrl) {
485  $href = htmlspecialchars(GeneralUtility::linkThisUrl($this->returnUrl, ['id' => $this->id]));
486  $buttons['back'] = '<a href="' . $href . '" class="typo3-goBack" title="'
487  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.goBack')) . '">'
488  . $this->iconFactory->getIcon('actions-view-go-back', Icon::SIZE_SMALL) . '</a>';
489  }
490  }
491  }
492 
502  public function getTable($table, $id, $rowList = '')
503  {
504  $rowListArray = GeneralUtility::trimExplode(',', $rowList, true);
505  // if no columns have been specified, show description (if configured)
506  if (!empty($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']) && empty($rowListArray)) {
507  array_push($rowListArray, $GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']);
508  }
509  $backendUser = $this->getBackendUserAuthentication();
510  $lang = $this->getLanguageService();
511  // Init
512  $addWhere = '';
513  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
514  $titleCol = $GLOBALS['TCA'][$table]['ctrl']['label'];
515  $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
516  $l10nEnabled = $GLOBALS['TCA'][$table]['ctrl']['languageField']
517  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
518  && $table !== 'pages_language_overlay';
519  $tableCollapsed = (bool)$this->tablesCollapsed[$table];
520  // prepare space icon
521  $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
522  // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
523  $this->fieldArray = [];
524  // title Column
525  // Add title column
526  $this->fieldArray[] = $titleCol;
527  // Control-Panel
528  if (!GeneralUtility::inList($rowList, '_CONTROL_')) {
529  $this->fieldArray[] = '_CONTROL_';
530  }
531  // Clipboard
532  if ($this->showClipboard) {
533  $this->fieldArray[] = '_CLIPBOARD_';
534  }
535  // Ref
536  if (!$this->dontShowClipControlPanels) {
537  $this->fieldArray[] = '_REF_';
538  }
539  // Path
540  if ($this->searchLevels) {
541  $this->fieldArray[] = '_PATH_';
542  }
543  // Localization
544  if ($this->localizationView && $l10nEnabled) {
545  $this->fieldArray[] = '_LOCALIZATION_';
546  $this->fieldArray[] = '_LOCALIZATION_b';
547  // Only restrict to the default language if no search request is in place
548  if ($this->searchString === '') {
549  $addWhere = (string)$queryBuilder->expr()->orX(
550  $queryBuilder->expr()->lte($GLOBALS['TCA'][$table]['ctrl']['languageField'], 0),
551  $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0)
552  );
553  }
554  }
555  // Cleaning up:
556  $this->fieldArray = array_unique(array_merge($this->fieldArray, $rowListArray));
557  if ($this->noControlPanels) {
558  $tempArray = array_flip($this->fieldArray);
559  unset($tempArray['_CONTROL_']);
560  unset($tempArray['_CLIPBOARD_']);
561  $this->fieldArray = array_keys($tempArray);
562  }
563  // Creating the list of fields to include in the SQL query:
564  $selectFields = $this->fieldArray;
565  $selectFields[] = 'uid';
566  $selectFields[] = 'pid';
567  // adding column for thumbnails
568  if ($thumbsCol) {
569  $selectFields[] = $thumbsCol;
570  }
571  if ($table == 'pages') {
572  $selectFields[] = 'module';
573  $selectFields[] = 'extendToSubpages';
574  $selectFields[] = 'nav_hide';
575  $selectFields[] = 'doktype';
576  $selectFields[] = 'shortcut';
577  $selectFields[] = 'shortcut_mode';
578  $selectFields[] = 'mount_pid';
579  }
580  if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
581  $selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
582  }
583  foreach (['type', 'typeicon_column', 'editlock'] as $field) {
584  if ($GLOBALS['TCA'][$table]['ctrl'][$field]) {
585  $selectFields[] = $GLOBALS['TCA'][$table]['ctrl'][$field];
586  }
587  }
588  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
589  $selectFields[] = 't3ver_id';
590  $selectFields[] = 't3ver_state';
591  $selectFields[] = 't3ver_wsid';
592  }
593  if ($l10nEnabled) {
594  $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
595  $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
596  }
597  if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']) {
598  $selectFields = array_merge(
599  $selectFields,
600  GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], true)
601  );
602  }
603  // Unique list!
604  $selectFields = array_unique($selectFields);
605  $fieldListFields = $this->makeFieldList($table, 1);
606  if (empty($fieldListFields) && $GLOBALS['TYPO3_CONF_VARS']['BE']['debug']) {
607  $message = sprintf(htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:missingTcaColumnsMessage')), $table, $table);
608  $messageTitle = htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:missingTcaColumnsMessageTitle'));
610  $flashMessage = GeneralUtility::makeInstance(
611  FlashMessage::class,
612  $message,
613  $messageTitle,
615  true
616  );
618  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
620  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
621  $defaultFlashMessageQueue->enqueue($flashMessage);
622  }
623  // Making sure that the fields in the field-list ARE in the field-list from TCA!
624  $selectFields = array_intersect($selectFields, $fieldListFields);
625  // Implode it into a list of fields for the SQL-statement.
626  $selFieldList = implode(',', $selectFields);
627  $this->selFieldList = $selFieldList;
628  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['getTable'])) {
629  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['getTable'] as $classData) {
630  $hookObject = GeneralUtility::getUserObj($classData);
631  if (!$hookObject instanceof RecordListGetTableHookInterface) {
632  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListGetTableHookInterface::class, 1195114460);
633  }
634  $hookObject->getDBlistQuery($table, $id, $addWhere, $selFieldList, $this);
635  }
636  }
637  $additionalConstraints = empty($addWhere) ? [] : [QueryHelper::stripLogicalOperatorPrefix($addWhere)];
639 
640  // Create the SQL query for selecting the elements in the listing:
641  // do not do paging when outputting as CSV
642  if ($this->csvOutput) {
643  $this->iLimit = 0;
644  }
645  if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
646  // Get the two previous rows for sorting if displaying page > 1
647  $this->firstElementNumber = $this->firstElementNumber - 2;
648  $this->iLimit = $this->iLimit + 2;
649  // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
650  $queryBuilder = $this->getQueryBuilder($table, $id, $additionalConstraints);
651  $this->firstElementNumber = $this->firstElementNumber + 2;
652  $this->iLimit = $this->iLimit - 2;
653  } else {
654  // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
655  $queryBuilder = $this->getQueryBuilder($table, $id, $additionalConstraints);
656  }
657 
658  // Finding the total amount of records on the page
659  // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
660  $this->setTotalItems($table, $id, $additionalConstraints);
661 
662  // Init:
663  $queryResult = $queryBuilder->execute();
664  $dbCount = 0;
665  $out = '';
666  $tableHeader = '';
667  $listOnlyInSingleTableMode = $this->listOnlyInSingleTableMode && !$this->table;
668  // If the count query returned any number of records, we perform the real query,
669  // selecting records.
670  if ($this->totalItems) {
671  // Fetch records only if not in single table mode
673  $dbCount = $this->totalItems;
674  } else {
675  // Set the showLimit to the number of records when outputting as CSV
676  if ($this->csvOutput) {
677  $this->showLimit = $this->totalItems;
678  $this->iLimit = $this->totalItems;
679  }
680  $dbCount = $queryResult->rowCount();
681  }
682  }
683  // If any records was selected, render the list:
684  if ($dbCount) {
685  $tableTitle = htmlspecialchars($lang->sL($GLOBALS['TCA'][$table]['ctrl']['title']));
686  if ($tableTitle === '') {
687  $tableTitle = $table;
688  }
689  // Header line is drawn
690  $theData = [];
691  if ($this->disableSingleTableView) {
692  $theData[$titleCol] = '<span class="c-table">' . BackendUtility::wrapInHelp($table, '', $tableTitle)
693  . '</span> (<span class="t3js-table-total-items">' . $this->totalItems . '</span>)';
694  } else {
695  $icon = $this->table
696  ? '<span title="' . htmlspecialchars($lang->getLL('contractView')) . '">' . $this->iconFactory->getIcon('actions-view-table-collapse', Icon::SIZE_SMALL)->render() . '</span>'
697  : '<span title="' . htmlspecialchars($lang->getLL('expandView')) . '">' . $this->iconFactory->getIcon('actions-view-table-expand', Icon::SIZE_SMALL)->render() . '</span>';
698  $theData[$titleCol] = $this->linkWrapTable($table, $tableTitle . ' (<span class="t3js-table-total-items">' . $this->totalItems . '</span>) ' . $icon);
699  }
701  $tableHeader .= BackendUtility::wrapInHelp($table, '', $theData[$titleCol]);
702  } else {
703  // Render collapse button if in multi table mode
704  $collapseIcon = '';
705  if (!$this->table) {
706  $href = htmlspecialchars(($this->listURL() . '&collapse[' . $table . ']=' . ($tableCollapsed ? '0' : '1')));
707  $title = $tableCollapsed
708  ? htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.expandTable'))
709  : htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.collapseTable'));
710  $icon = '<span class="collapseIcon">' . $this->iconFactory->getIcon(($tableCollapsed ? 'actions-view-list-expand' : 'actions-view-list-collapse'), Icon::SIZE_SMALL)->render() . '</span>';
711  $collapseIcon = '<a href="' . $href . '" title="' . $title . '" class="pull-right t3js-toggle-recordlist" data-table="' . htmlspecialchars($table) . '" data-toggle="collapse" data-target="#recordlist-' . htmlspecialchars($table) . '">' . $icon . '</a>';
712  }
713  $tableHeader .= $theData[$titleCol] . $collapseIcon;
714  }
715  // Render table rows only if in multi table view or if in single table view
716  $rowOutput = '';
717  if (!$listOnlyInSingleTableMode || $this->table) {
718  // Fixing an order table for sortby tables
719  $this->currentTable = [];
720  $currentIdList = [];
721  $doSort = $GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$this->sortField;
722  $prevUid = 0;
723  $prevPrevUid = 0;
724  // Get first two rows and initialize prevPrevUid and prevUid if on page > 1
725  if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
726  $row = $queryResult->fetch();
727  $prevPrevUid = -((int)$row['uid']);
728  $row = $queryResult->fetch();
729  $prevUid = $row['uid'];
730  }
731  $accRows = [];
732  // Accumulate rows here
733  while ($row = $queryResult->fetch()) {
734  if (!$this->isRowListingConditionFulfilled($table, $row)) {
735  continue;
736  }
737  // In offline workspace, look for alternative record:
738  BackendUtility::workspaceOL($table, $row, $backendUser->workspace, true);
739  if (is_array($row)) {
740  $accRows[] = $row;
741  $currentIdList[] = $row['uid'];
742  if ($doSort) {
743  if ($prevUid) {
744  $this->currentTable['prev'][$row['uid']] = $prevPrevUid;
745  $this->currentTable['next'][$prevUid] = '-' . $row['uid'];
746  $this->currentTable['prevUid'][$row['uid']] = $prevUid;
747  }
748  $prevPrevUid = isset($this->currentTable['prev'][$row['uid']]) ? -$prevUid : $row['pid'];
749  $prevUid = $row['uid'];
750  }
751  }
752  }
753  $this->totalRowCount = count($accRows);
754  // CSV initiated
755  if ($this->csvOutput) {
756  $this->initCSV();
757  }
758  // Render items:
759  $this->CBnames = [];
760  $this->duplicateStack = [];
761  $this->eCounter = $this->firstElementNumber;
762  $cc = 0;
763  foreach ($accRows as $row) {
764  // Render item row if counter < limit
765  if ($cc < $this->iLimit) {
766  $cc++;
767  $this->translations = false;
768  $rowOutput .= $this->renderListRow($table, $row, $cc, $titleCol, $thumbsCol);
769  // If localization view is enabled and no search happened it means that the selected
770  // records are either default or All language and here we will not select translations
771  // which point to the main record:
772  if ($this->localizationView && $l10nEnabled && $this->searchString === '') {
773  // For each available translation, render the record:
774  if (is_array($this->translations)) {
775  foreach ($this->translations as $lRow) {
776  // $lRow isn't always what we want - if record was moved we've to work with the
777  // placeholder records otherwise the list is messed up a bit
778  if ($row['_MOVE_PLH_uid'] && $row['_MOVE_PLH_pid']) {
779  $where = 't3ver_move_id="' . (int)$lRow['uid'] . '" AND pid="' . $row['_MOVE_PLH_pid']
780  . '" AND t3ver_wsid=' . $row['t3ver_wsid'] . BackendUtility::deleteClause($table);
781  $tmpRow = BackendUtility::getRecordRaw($table, $where, $selFieldList);
782  $lRow = is_array($tmpRow) ? $tmpRow : $lRow;
783  }
784  // In offline workspace, look for alternative record:
785  BackendUtility::workspaceOL($table, $lRow, $backendUser->workspace, true);
786  if (is_array($lRow) && $backendUser->checkLanguageAccess($lRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
787  $currentIdList[] = $lRow['uid'];
788  $rowOutput .= $this->renderListRow($table, $lRow, $cc, $titleCol, $thumbsCol, 18);
789  }
790  }
791  }
792  }
793  }
794  // Counter of total rows incremented:
795  $this->eCounter++;
796  }
797  // Record navigation is added to the beginning and end of the table if in single
798  // table mode
799  if ($this->table) {
800  $rowOutput = $this->renderListNavigation('top') . $rowOutput . $this->renderListNavigation('bottom');
801  } else {
802  // Show that there are more records than shown
803  if ($this->totalItems > $this->itemsLimitPerTable) {
804  $countOnFirstPage = $this->totalItems > $this->itemsLimitSingleTable ? $this->itemsLimitSingleTable : $this->totalItems;
805  $hasMore = $this->totalItems > $this->itemsLimitSingleTable;
806  $colspan = $this->showIcon ? count($this->fieldArray) + 1 : count($this->fieldArray);
807  $rowOutput .= '<tr><td colspan="' . $colspan . '">
808  <a href="' . htmlspecialchars(($this->listURL() . '&table=' . rawurlencode($table))) . '" class="btn btn-default">'
809  . '<span class="t3-icon fa fa-chevron-down"></span> <i>[1 - ' . $countOnFirstPage . ($hasMore ? '+' : '') . ']</i></a>
810  </td></tr>';
811  }
812  }
813  // The header row for the table is now created:
814  $out .= $this->renderListHeader($table, $currentIdList);
815  }
816 
817  $collapseClass = $tableCollapsed && !$this->table ? 'collapse' : 'collapse in';
818  $dataState = $tableCollapsed && !$this->table ? 'collapsed' : 'expanded';
819 
820  // The list of records is added after the header:
821  $out .= $rowOutput;
822  // ... and it is all wrapped in a table:
823  $out = '
824 
825 
826 
827  <!--
828  DB listing of elements: "' . htmlspecialchars($table) . '"
829  -->
830  <div class="panel panel-space panel-default recordlist">
831  <div class="panel-heading">
832  ' . $tableHeader . '
833  </div>
834  <div class="' . $collapseClass . '" data-state="' . $dataState . '" id="recordlist-' . htmlspecialchars($table) . '">
835  <div class="table-fit">
836  <table data-table="' . htmlspecialchars($table) . '" class="table table-striped table-hover' . ($listOnlyInSingleTableMode ? ' typo3-dblist-overview' : '') . '">
837  ' . $out . '
838  </table>
839  </div>
840  </div>
841  </div>
842  ';
843  // Output csv if...
844  // This ends the page with exit.
845  if ($this->csvOutput) {
846  $this->outputCSV($table);
847  }
848  }
849  // Return content:
850  return $out;
851  }
852 
862  protected function isRowListingConditionFulfilled($table, $row)
863  {
864  return true;
865  }
866 
880  public function renderListRow($table, $row, $cc, $titleCol, $thumbsCol, $indent = 0)
881  {
882  if (!is_array($row)) {
883  return '';
884  }
885  $rowOutput = '';
886  $id_orig = null;
887  // If in search mode, make sure the preview will show the correct page
888  if ((string)$this->searchString !== '') {
889  $id_orig = $this->id;
890  $this->id = $row['pid'];
891  }
892  // Add special classes for first and last row
893  $rowSpecial = '';
894  if ($cc == 1 && $indent == 0) {
895  $rowSpecial .= ' firstcol';
896  }
897  if ($cc == $this->totalRowCount || $cc == $this->iLimit) {
898  $rowSpecial .= ' lastcol';
899  }
900 
901  $row_bgColor = ' class="' . $rowSpecial . '"';
902 
903  // Overriding with versions background color if any:
904  $row_bgColor = $row['_CSSCLASS'] ? ' class="' . $row['_CSSCLASS'] . '"' : $row_bgColor;
905  // Incr. counter.
906  $this->counter++;
907  // The icon with link
908  $toolTip = BackendUtility::getRecordToolTip($row, $table);
909  $additionalStyle = $indent ? ' style="margin-left: ' . $indent . 'px;"' : '';
910  $iconImg = '<span ' . $toolTip . ' ' . $additionalStyle . '>'
911  . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render()
912  . '</span>';
913  $theIcon = $this->clickMenuEnabled ? BackendUtility::wrapClickMenuOnIcon($iconImg, $table, $row['uid']) : $iconImg;
914  // Preparing and getting the data-array
915  $theData = [];
916  $localizationMarkerClass = '';
917  foreach ($this->fieldArray as $fCol) {
918  if ($fCol == $titleCol) {
919  $recTitle = BackendUtility::getRecordTitle($table, $row, false, true);
920  $warning = '';
921  // If the record is edit-locked by another user, we will show a little warning sign:
922  $lockInfo = BackendUtility::isRecordLocked($table, $row['uid']);
923  if ($lockInfo) {
924  $warning = '<a href="#" onclick="alert('
925  . GeneralUtility::quoteJSvalue($lockInfo['msg']) . '); return false;" title="'
926  . htmlspecialchars($lockInfo['msg']) . '">'
927  . $this->iconFactory->getIcon('status-warning-in-use', Icon::SIZE_SMALL)->render() . '</a>';
928  }
929  $theData[$fCol] = $theData['__label'] = $warning . $this->linkWrapItems($table, $row['uid'], $recTitle, $row);
930  // Render thumbnails, if:
931  // - a thumbnail column exists
932  // - there is content in it
933  // - the thumbnail column is visible for the current type
934  $type = 0;
935  if (isset($GLOBALS['TCA'][$table]['ctrl']['type'])) {
936  $typeColumn = $GLOBALS['TCA'][$table]['ctrl']['type'];
937  $type = $row[$typeColumn];
938  }
939  // If current type doesn't exist, set it to 0 (or to 1 for historical reasons,
940  // if 0 doesn't exist)
941  if (!isset($GLOBALS['TCA'][$table]['types'][$type])) {
942  $type = isset($GLOBALS['TCA'][$table]['types'][0]) ? 0 : 1;
943  }
944  $visibleColumns = $GLOBALS['TCA'][$table]['types'][$type]['showitem'];
945 
946  if ($this->thumbs &&
947  trim($row[$thumbsCol]) &&
948  preg_match('/(^|(.*(;|,)?))' . $thumbsCol . '(((;|,).*)|$)/', $visibleColumns) === 1
949  ) {
950  $thumbCode = '<br />' . $this->thumbCode($row, $table, $thumbsCol);
951  $theData[$fCol] .= $thumbCode;
952  $theData['__label'] .= $thumbCode;
953  }
954  if (isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
955  && $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] != 0
956  && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0
957  ) {
958  // It's a translated record with a language parent
959  $localizationMarkerClass = ' localization';
960  }
961  } elseif ($fCol == 'pid') {
962  $theData[$fCol] = $row[$fCol];
963  } elseif ($fCol == '_PATH_') {
964  $theData[$fCol] = $this->recPath($row['pid']);
965  } elseif ($fCol == '_REF_') {
966  $theData[$fCol] = $this->createReferenceHtml($table, $row['uid']);
967  } elseif ($fCol == '_CONTROL_') {
968  $theData[$fCol] = $this->makeControl($table, $row);
969  } elseif ($fCol == '_CLIPBOARD_') {
970  $theData[$fCol] = $this->makeClip($table, $row);
971  } elseif ($fCol == '_LOCALIZATION_') {
972  list($lC1, $lC2) = $this->makeLocalizationPanel($table, $row);
973  $theData[$fCol] = $lC1;
974  $theData[$fCol . 'b'] = '<div class="btn-group">' . $lC2 . '</div>';
975  } elseif ($fCol == '_LOCALIZATION_b') {
976  // deliberately empty
977  } else {
978  $pageId = $table === 'pages' ? $row['uid'] : $row['pid'];
979  $tmpProc = BackendUtility::getProcessedValueExtra($table, $fCol, $row[$fCol], 100, $row['uid'], true, $pageId);
980  $theData[$fCol] = $this->linkUrlMail(htmlspecialchars($tmpProc), $row[$fCol]);
981  if ($this->csvOutput) {
982  $row[$fCol] = BackendUtility::getProcessedValueExtra($table, $fCol, $row[$fCol], 0, $row['uid']);
983  }
984  }
985  }
986  // Reset the ID if it was overwritten
987  if ((string)$this->searchString !== '') {
988  $this->id = $id_orig;
989  }
990  // Add row to CSV list:
991  if ($this->csvOutput) {
992  $this->addToCSV($row);
993  }
994  // Add classes to table cells
995  $this->addElement_tdCssClass[$titleCol] = 'col-title col-responsive' . $localizationMarkerClass;
996  $this->addElement_tdCssClass['__label'] = $this->addElement_tdCssClass[$titleCol];
997  $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
998  if ($this->getModule()->MOD_SETTINGS['clipBoard']) {
999  $this->addElement_tdCssClass['_CLIPBOARD_'] = 'col-clipboard';
1000  }
1001  $this->addElement_tdCssClass['_PATH_'] = 'col-path';
1002  $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
1003  $this->addElement_tdCssClass['_LOCALIZATION_b'] = 'col-localizationb';
1004  // Create element in table cells:
1005  $theData['uid'] = $row['uid'];
1006  if (isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
1007  && isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
1008  && $table !== 'pages_language_overlay'
1009  ) {
1010  $theData['_l10nparent_'] = $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
1011  }
1012  $rowOutput .= $this->addElement(1, $theIcon, $theData, $row_bgColor);
1013  // Finally, return table row element:
1014  return $rowOutput;
1015  }
1016 
1025  protected function getReferenceCount($tableName, $uid)
1026  {
1027  if (!isset($this->referenceCount[$tableName][$uid])) {
1028  $numberOfReferences = GeneralUtility::makeInstance(ConnectionPool::class)
1029  ->getConnectionForTable('sys_refindex')
1030  ->count(
1031  '*',
1032  'sys_refindex',
1033  [
1034  'ref_table' => $tableName,
1035  'ref_uid' => (int)$uid,
1036  'deleted' => 0
1037  ]
1038  );
1039  $this->referenceCount[$tableName][$uid] = $numberOfReferences;
1040  }
1041  return $this->referenceCount[$tableName][$uid];
1042  }
1043 
1054  public function renderListHeader($table, $currentIdList)
1055  {
1056  $lang = $this->getLanguageService();
1057  // Init:
1058  $theData = [];
1059  $icon = '';
1060  // Traverse the fields:
1061  foreach ($this->fieldArray as $fCol) {
1062  // Calculate users permissions to edit records in the table:
1063  $permsEdit = $this->calcPerms & ($table == 'pages' ? 2 : 16) && $this->overlayEditLockPermissions($table);
1064  switch ((string)$fCol) {
1065  case '_PATH_':
1066  // Path
1067  $theData[$fCol] = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels._PATH_')) . ']</i>';
1068  break;
1069  case '_REF_':
1070  // References
1071  $theData[$fCol] = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_file_list.xlf:c__REF_')) . ']</i>';
1072  break;
1073  case '_LOCALIZATION_':
1074  // Path
1075  $theData[$fCol] = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels._LOCALIZATION_')) . ']</i>';
1076  break;
1077  case '_LOCALIZATION_b':
1078  // Path
1079  $theData[$fCol] = htmlspecialchars($lang->getLL('Localize'));
1080  break;
1081  case '_CLIPBOARD_':
1082  if (!$this->getModule()->MOD_SETTINGS['clipBoard']) {
1083  break;
1084  }
1085  // Clipboard:
1086  $cells = [];
1087  // If there are elements on the clipboard for this table, and the parent page is not locked by editlock
1088  // then display the "paste into" icon:
1089  $elFromTable = $this->clipObj->elFromTable($table);
1090  if (!empty($elFromTable) && $this->overlayEditLockPermissions($table)) {
1091  $href = htmlspecialchars($this->clipObj->pasteUrl($table, $this->id));
1092  $confirmMessage = $this->clipObj->confirmMsgText('pages', $this->pageRow, 'into', $elFromTable);
1093  $cells['pasteAfter'] = '<a class="btn btn-default t3js-modal-trigger"'
1094  . ' href="' . $href . '"'
1095  . ' title="' . htmlspecialchars($lang->getLL('clip_paste')) . '"'
1096  . ' data-title="' . htmlspecialchars($lang->getLL('clip_paste')) . '"'
1097  . ' data-content="' . htmlspecialchars($confirmMessage) . '"'
1098  . ' data-severity="warning">'
1099  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1100  . '</a>';
1101  }
1102  // If the numeric clipboard pads are enabled, display the control icons for that:
1103  if ($this->clipObj->current != 'normal') {
1104  // The "select" link:
1105  $spriteIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
1106  $cells['copyMarked'] = $this->linkClipboardHeaderIcon($spriteIcon, $table, 'setCB', '', $lang->getLL('clip_selectMarked'));
1107  // The "edit marked" link:
1108  $editIdList = implode(',', $currentIdList);
1109  $editIdList = '\'+editList(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($editIdList) . ')+\'';
1110  $params = 'edit[' . $table . '][' . $editIdList . ']=edit';
1111  $onClick = BackendUtility::editOnClick('', '', -1);
1112  $onClickArray = explode('?', $onClick, 2);
1113  $lastElement = array_pop($onClickArray);
1114  array_push($onClickArray, $params . '&' . $lastElement);
1115  $onClick = implode('?', $onClickArray);
1116  $cells['edit'] = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1117  . htmlspecialchars($lang->getLL('clip_editMarked')) . '">'
1118  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1119  // The "Delete marked" link:
1120  $cells['delete'] = $this->linkClipboardHeaderIcon(
1121  $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(),
1122  $table,
1123  'delete',
1124  sprintf($lang->getLL('clip_deleteMarkedWarning'), $lang->sL($GLOBALS['TCA'][$table]['ctrl']['title'])),
1125  $lang->getLL('clip_deleteMarked')
1126  );
1127  // The "Select all" link:
1128  $onClick = htmlspecialchars(('checkOffCB(' . GeneralUtility::quoteJSvalue(implode(',', $this->CBnames)) . ', this); return false;'));
1129  $cells['markAll'] = '<a class="btn btn-default" rel="" href="#" onclick="' . $onClick . '" title="'
1130  . htmlspecialchars($lang->getLL('clip_markRecords')) . '">'
1131  . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
1132  } else {
1133  $cells['empty'] = '';
1134  }
1142  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1143  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1144  $hookObject = GeneralUtility::getUserObj($classData);
1145  if (!$hookObject instanceof RecordListHookInterface) {
1146  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567850);
1147  }
1148  $cells = $hookObject->renderListHeaderActions($table, $currentIdList, $cells, $this);
1149  }
1150  }
1151  $theData[$fCol] = '';
1152  if (isset($cells['edit']) && isset($cells['delete'])) {
1153  $theData[$fCol] .= '<div class="btn-group" role="group">' . $cells['edit'] . $cells['delete'] . '</div>';
1154  unset($cells['edit'], $cells['delete']);
1155  }
1156  $theData[$fCol] .= '<div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1157  break;
1158  case '_CONTROL_':
1159  // Control panel:
1160  if ($this->isEditable($table)) {
1161  // If new records can be created on this page, add links:
1162  $permsAdditional = ($table === 'pages' ? 8 : 16);
1163  if ($this->calcPerms & $permsAdditional && $this->showNewRecLink($table)) {
1164  $spriteIcon = $table === 'pages'
1165  ? $this->iconFactory->getIcon('actions-page-new', Icon::SIZE_SMALL)
1166  : $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL);
1167  if ($table === 'tt_content' && $this->newWizards) {
1168  // If mod.newContentElementWizard.override is set, use that extension's create new content wizard instead:
1169  $tmpTSc = BackendUtility::getModTSconfig($this->pageinfo['uid'], 'mod');
1170  $newContentElementWizard = isset($tmpTSc['properties']['newContentElementWizard.']['override'])
1171  ? $tmpTSc['properties']['newContentElementWizard.']['override']
1172  : 'new_content_element';
1173  $newContentWizScriptPath = BackendUtility::getModuleUrl($newContentElementWizard, ['id' => $this->id]);
1174 
1175  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue($newContentWizScriptPath) . ');';
1176  $icon = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1177  . htmlspecialchars($lang->getLL('new')) . '">' . $spriteIcon->render() . '</a>';
1178  } elseif ($table == 'pages' && $this->newWizards) {
1179  $parameters = ['id' => $this->id, 'pagesOnly' => 1, 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')];
1180  $href = BackendUtility::getModuleUrl('db_new', $parameters);
1181  $icon = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="' . htmlspecialchars($lang->getLL('new')) . '">'
1182  . $spriteIcon->render() . '</a>';
1183  } else {
1184  $params = '&edit[' . $table . '][' . $this->id . ']=new';
1185  if ($table == 'pages_language_overlay') {
1186  $params .= '&overrideVals[pages_language_overlay][doktype]=' . (int)$this->pageRow['doktype'];
1187  }
1188  $icon = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1))
1189  . '" title="' . htmlspecialchars($lang->getLL('new')) . '">' . $spriteIcon->render() . '</a>';
1190  }
1191  }
1192  // If the table can be edited, add link for editing ALL SHOWN fields for all listed records:
1193  if ($permsEdit && $this->table && is_array($currentIdList)) {
1194  $editIdList = implode(',', $currentIdList);
1195  if ($this->clipNumPane()) {
1196  $editIdList = '\'+editList(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($editIdList) . ')+\'';
1197  }
1198  $params = 'edit[' . $table . '][' . $editIdList . ']=edit&columnsOnly=' . implode(',', $this->fieldArray);
1199  // we need to build this uri differently, otherwise GeneralUtility::quoteJSvalue messes up the edit list function
1200  $onClick = BackendUtility::editOnClick('', '', -1);
1201  $onClickArray = explode('?', $onClick, 2);
1202  $lastElement = array_pop($onClickArray);
1203  array_push($onClickArray, $params . '&' . $lastElement);
1204  $onClick = implode('?', $onClickArray);
1205  $icon .= '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick)
1206  . '" title="' . htmlspecialchars($lang->getLL('editShownColumns')) . '">'
1207  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1208  $icon = '<div class="btn-group" role="group">' . $icon . '</div>';
1209  }
1210  // Add an empty entry, so column count fits again after moving this into $icon
1211  $theData[$fCol] = '&nbsp;';
1212  }
1213  break;
1214  default:
1215  // Regular fields header:
1216  $theData[$fCol] = '';
1217 
1218  // Check if $fCol is really a field and get the label and remove the colons
1219  // at the end
1220  $sortLabel = BackendUtility::getItemLabel($table, $fCol);
1221  if ($sortLabel !== null) {
1222  $sortLabel = htmlspecialchars($lang->sL($sortLabel));
1223  $sortLabel = rtrim(trim($sortLabel), ':');
1224  } else {
1225  // No TCA field, only output the $fCol variable with square brackets []
1226  $sortLabel = htmlspecialchars($fCol);
1227  $sortLabel = '<i>[' . rtrim(trim($sortLabel), ':') . ']</i>';
1228  }
1229 
1230  if ($this->table && is_array($currentIdList)) {
1231  // If the numeric clipboard pads are selected, show duplicate sorting link:
1232  if ($this->clipNumPane()) {
1233  $theData[$fCol] .= '<a class="btn btn-default" href="' . htmlspecialchars($this->listURL('', '-1') . '&duplicateField=' . $fCol)
1234  . '" title="' . htmlspecialchars($lang->getLL('clip_duplicates')) . '">'
1235  . $this->iconFactory->getIcon('actions-document-duplicates-select', Icon::SIZE_SMALL)->render() . '</a>';
1236  }
1237  // If the table can be edited, add link for editing THIS field for all
1238  // listed records:
1239  if ($this->isEditable($table) && $permsEdit && $GLOBALS['TCA'][$table]['columns'][$fCol]) {
1240  $editIdList = implode(',', $currentIdList);
1241  if ($this->clipNumPane()) {
1242  $editIdList = '\'+editList(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($editIdList) . ')+\'';
1243  }
1244  $params = 'edit[' . $table . '][' . $editIdList . ']=edit&columnsOnly=' . $fCol;
1245  // we need to build this uri differently, otherwise GeneralUtility::quoteJSvalue messes up the edit list function
1246  $onClick = BackendUtility::editOnClick('', '', -1);
1247  $onClickArray = explode('?', $onClick, 2);
1248  $lastElement = array_pop($onClickArray);
1249  array_push($onClickArray, $params . '&' . $lastElement);
1250  $onClick = implode('?', $onClickArray);
1251  $iTitle = sprintf($lang->getLL('editThisColumn'), $sortLabel);
1252  $theData[$fCol] .= '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick)
1253  . '" title="' . htmlspecialchars($iTitle) . '">'
1254  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1255  }
1256  if (strlen($theData[$fCol]) > 0) {
1257  $theData[$fCol] = '<div class="btn-group" role="group">' . $theData[$fCol] . '</div> ';
1258  }
1259  }
1260  $theData[$fCol] .= $this->addSortLink($sortLabel, $fCol, $table);
1261  }
1262  }
1269  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1270  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1271  $hookObject = GeneralUtility::getUserObj($classData);
1272  if (!$hookObject instanceof RecordListHookInterface) {
1273  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567855);
1274  }
1275  $theData = $hookObject->renderListHeader($table, $currentIdList, $theData, $this);
1276  }
1277  }
1278 
1279  // Create and return header table row:
1280  return '<thead>' . $this->addElement(1, $icon, $theData, '', '', '', 'th') . '</thead>';
1281  }
1282 
1289  protected function getPointerForPage($page)
1290  {
1291  return ($page - 1) * $this->iLimit;
1292  }
1293 
1300  protected function renderListNavigation($renderPart = 'top')
1301  {
1302  $totalPages = ceil($this->totalItems / $this->iLimit);
1303  // Show page selector if not all records fit into one page
1304  if ($totalPages <= 1) {
1305  return '';
1306  }
1307  $content = '';
1308  $listURL = $this->listURL('', $this->table);
1309  // 1 = first page
1310  // 0 = first element
1311  $currentPage = floor($this->firstElementNumber / $this->iLimit) + 1;
1312  // Compile first, previous, next, last and refresh buttons
1313  if ($currentPage > 1) {
1314  $labelFirst = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:first'));
1315  $labelPrevious = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:previous'));
1316  $first = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage(1) . '" title="' . $labelFirst . '">'
1317  . $this->iconFactory->getIcon('actions-view-paging-first', Icon::SIZE_SMALL)->render() . '</a></li>';
1318  $previous = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($currentPage - 1) . '" title="' . $labelPrevious . '">'
1319  . $this->iconFactory->getIcon('actions-view-paging-previous', Icon::SIZE_SMALL)->render() . '</a></li>';
1320  } else {
1321  $first = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-first', Icon::SIZE_SMALL)->render() . '</span></li>';
1322  $previous = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-previous', Icon::SIZE_SMALL)->render() . '</span></li>';
1323  }
1324  if ($currentPage < $totalPages) {
1325  $labelNext = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:next'));
1326  $labelLast = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:last'));
1327  $next = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($currentPage + 1) . '" title="' . $labelNext . '">'
1328  . $this->iconFactory->getIcon('actions-view-paging-next', Icon::SIZE_SMALL)->render() . '</a></li>';
1329  $last = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($totalPages) . '" title="' . $labelLast . '">'
1330  . $this->iconFactory->getIcon('actions-view-paging-last', Icon::SIZE_SMALL)->render() . '</a></li>';
1331  } else {
1332  $next = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-next', Icon::SIZE_SMALL)->render() . '</span></li>';
1333  $last = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-last', Icon::SIZE_SMALL)->render() . '</span></li>';
1334  }
1335  $reload = '<li><a href="#" onclick="document.dblistForm.action=' . GeneralUtility::quoteJSvalue($listURL
1336  . '&pointer=') . '+calculatePointer(document.getElementById(' . GeneralUtility::quoteJSvalue('jumpPage-' . $renderPart)
1337  . ').value); document.dblistForm.submit(); return true;" title="'
1338  . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:reload')) . '">'
1339  . $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render() . '</a></li>';
1340  if ($renderPart === 'top') {
1341  // Add js to traverse a page select input to a pointer value
1342  $content = '
1343 <script type="text/javascript">
1344 /*<![CDATA[*/
1345  function calculatePointer(page) {
1346  if (page > ' . $totalPages . ') {
1347  page = ' . $totalPages . ';
1348  }
1349  if (page < 1) {
1350  page = 1;
1351  }
1352  return (page - 1) * ' . $this->iLimit . ';
1353  }
1354 /*]]>*/
1355 </script>
1356 ';
1357  }
1358  $pageNumberInput = '
1359  <input type="number" min="1" max="' . $totalPages . '" value="' . $currentPage . '" size="3" class="form-control input-sm paginator-input" id="jumpPage-' . $renderPart . '" name="jumpPage-'
1360  . $renderPart . '" onkeyup="if (event.keyCode == 13) { document.dblistForm.action=' . GeneralUtility::quoteJSvalue($listURL
1361  . '&pointer=') . '+calculatePointer(this.value); document.dblistForm.submit(); } return true;" />
1362  ';
1363  $pageIndicatorText = sprintf(
1364  $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:pageIndicator'),
1365  $pageNumberInput,
1366  $totalPages
1367  );
1368  $pageIndicator = '<li><span>' . $pageIndicatorText . '</span></li>';
1369  if ($this->totalItems > $this->firstElementNumber + $this->iLimit) {
1370  $lastElementNumber = $this->firstElementNumber + $this->iLimit;
1371  } else {
1372  $lastElementNumber = $this->totalItems;
1373  }
1374  $rangeIndicator = '<li><span>' . sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:rangeIndicator'), ($this->firstElementNumber + 1), $lastElementNumber) . '</span></li>';
1375 
1376  $titleColumn = $this->fieldArray[0];
1377  $data = [
1378  $titleColumn => $content . '
1379  <nav class="pagination-wrap">
1380  <ul class="pagination pagination-block">
1381  ' . $first . '
1382  ' . $previous . '
1383  ' . $rangeIndicator . '
1384  ' . $pageIndicator . '
1385  ' . $next . '
1386  ' . $last . '
1387  ' . $reload . '
1388  </ul>
1389  </nav>
1390  '
1391  ];
1392  return $this->addElement(1, '', $data);
1393  }
1394 
1395  /*********************************
1396  *
1397  * Rendering of various elements
1398  *
1399  *********************************/
1400 
1409  public function makeControl($table, $row)
1410  {
1411  $module = $this->getModule();
1412  $rowUid = $row['uid'];
1413  if (ExtensionManagementUtility::isLoaded('version') && isset($row['_ORIG_uid'])) {
1414  $rowUid = $row['_ORIG_uid'];
1415  }
1416  $cells = [
1417  'primary' => [],
1418  'secondary' => []
1419  ];
1420  // If the listed table is 'pages' we have to request the permission settings for each page:
1421  $localCalcPerms = 0;
1422  if ($table == 'pages') {
1423  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1424  }
1425  $permsEdit = $table === 'pages'
1426  && $this->getBackendUserAuthentication()->checkLanguageAccess(0)
1427  && $localCalcPerms & Permission::PAGE_EDIT
1428  || $table !== 'pages'
1429  && $this->calcPerms & Permission::CONTENT_EDIT
1430  && $this->getBackendUserAuthentication()->recordEditAccessInternals($table, $row);
1431  $permsEdit = $this->overlayEditLockPermissions($table, $row, $permsEdit);
1432  // "Show" link (only pages and tt_content elements)
1433  if ($table == 'pages' || $table == 'tt_content') {
1434  $viewAction = '<a class="btn btn-default" href="#" onclick="'
1435  . htmlspecialchars(
1437  ($table === 'tt_content' ? $this->id : $row['uid']),
1438  '',
1439  '',
1440  ($table === 'tt_content' ? '#' . $row['uid'] : '')
1441  )
1442  ) . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">'
1443  . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
1444  $this->addActionToCellGroup($cells, $viewAction, 'view');
1445  }
1446  // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1447  if ($permsEdit) {
1448  $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1449  $iconIdentifier = 'actions-open';
1450  if ($table === 'pages') {
1451  $iconIdentifier = 'actions-page-open';
1452  }
1453  $overlayIdentifier = !$this->isEditable($table) ? 'overlay-readonly' : null;
1454  $editAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1))
1455  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('edit')) . '">' . $this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL, $overlayIdentifier)->render() . '</a>';
1456  } else {
1457  $editAction = $this->spaceIcon;
1458  }
1459  $this->addActionToCellGroup($cells, $editAction, 'edit');
1460  // "Info": (All records)
1461  $onClick = 'top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . (int)$row['uid'] . '); return false;';
1462  $viewBigAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('showInfo')) . '">'
1463  . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
1464  $this->addActionToCellGroup($cells, $viewBigAction, 'viewBig');
1465  // "Move" wizard link for pages/tt_content elements:
1466  if ($permsEdit && ($table === 'tt_content' || $table === 'pages')) {
1467  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('move_element') . '&table=' . $table . '&uid=' . $row['uid']) . ');';
1468  $linkTitleLL = htmlspecialchars($this->getLanguageService()->getLL('move_' . ($table === 'tt_content' ? 'record' : 'page')));
1469  $icon = ($table == 'pages' ? $this->iconFactory->getIcon('actions-page-move', Icon::SIZE_SMALL) : $this->iconFactory->getIcon('actions-document-move', Icon::SIZE_SMALL));
1470  $moveAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $linkTitleLL . '">' . $icon->render() . '</a>';
1471  $this->addActionToCellGroup($cells, $moveAction, 'move');
1472  }
1473  // If the table is NOT a read-only table, then show these links:
1474  if ($this->isEditable($table)) {
1475  // "Revert" link (history/undo)
1476  $moduleUrl = BackendUtility::getModuleUrl('record_history', ['element' => $table . ':' . $row['uid']]);
1477  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue($moduleUrl) . ',\'#latest\');';
1478  $historyAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1479  . htmlspecialchars($this->getLanguageService()->getLL('history')) . '">'
1480  . $this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL)->render() . '</a>';
1481  $this->addActionToCellGroup($cells, $historyAction, 'history');
1482  // Versioning:
1484  $vers = BackendUtility::selectVersionsOfRecord($table, $row['uid'], 'uid', $this->getBackendUserAuthentication()->workspace, false, $row);
1485  // If table can be versionized.
1486  if (is_array($vers)) {
1487  $href = BackendUtility::getModuleUrl('web_txversionM1', [
1488  'table' => $table, 'uid' => $row['uid']
1489  ]);
1490  $versionAction = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="'
1491  . htmlspecialchars($this->getLanguageService()->getLL('displayVersions')) . '">'
1492  . $this->iconFactory->getIcon('actions-version-page-open', Icon::SIZE_SMALL)->render() . '</a>';
1493  $this->addActionToCellGroup($cells, $versionAction, 'version');
1494  }
1495  }
1496  // "Edit Perms" link:
1497  if ($table === 'pages' && $this->getBackendUserAuthentication()->check('modules', 'system_BeuserTxPermission') && ExtensionManagementUtility::isLoaded('beuser')) {
1498  $href = BackendUtility::getModuleUrl('system_BeuserTxPermission') . '&id=' . $row['uid'] . '&return_id=' . $row['uid'] . '&edit=1';
1499  $permsAction = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="'
1500  . htmlspecialchars($this->getLanguageService()->getLL('permissions')) . '">'
1501  . $this->iconFactory->getIcon('status-status-locked', Icon::SIZE_SMALL)->render() . '</a>';
1502  $this->addActionToCellGroup($cells, $permsAction, 'perms');
1503  }
1504  // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row
1505  // or if default values can depend on previous record):
1506  if (($GLOBALS['TCA'][$table]['ctrl']['sortby'] || $GLOBALS['TCA'][$table]['ctrl']['useColumnsForDefaultValues']) && $permsEdit) {
1507  if ($table !== 'pages' && $this->calcPerms & Permission::CONTENT_EDIT || $table === 'pages' && $this->calcPerms & Permission::PAGE_NEW) {
1508  if ($this->showNewRecLink($table)) {
1509  $params = '&edit[' . $table . '][' . -($row['_MOVE_PLH'] ? $row['_MOVE_PLH_uid'] : $row['uid']) . ']=new';
1510  $icon = ($table == 'pages' ? $this->iconFactory->getIcon('actions-page-new', Icon::SIZE_SMALL) : $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL));
1511  $titleLabel = 'new';
1512  if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1513  $titleLabel .= ($table === 'pages' ? 'Page' : 'Record');
1514  }
1515  $newAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1))
1516  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($titleLabel)) . '">'
1517  . $icon->render() . '</a>';
1518  $this->addActionToCellGroup($cells, $newAction, 'new');
1519  }
1520  }
1521  }
1522  // "Up/Down" links
1523  if ($permsEdit && $GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$this->sortField && !$this->searchLevels) {
1524  if (isset($this->currentTable['prev'][$row['uid']])) {
1525  // Up
1526  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['prev'][$row['uid']];
1527  $moveUpAction = '<a class="btn btn-default" href="#" onclick="'
1528  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1529  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveUp')) . '">'
1530  . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1531  } else {
1532  $moveUpAction = $this->spaceIcon;
1533  }
1534  $this->addActionToCellGroup($cells, $moveUpAction, 'moveUp');
1535 
1536  if ($this->currentTable['next'][$row['uid']]) {
1537  // Down
1538  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['next'][$row['uid']];
1539  $moveDownAction = '<a class="btn btn-default" href="#" onclick="'
1540  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1541  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('moveDown')) . '">'
1542  . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1543  } else {
1544  $moveDownAction = $this->spaceIcon;
1545  }
1546  $this->addActionToCellGroup($cells, $moveDownAction, 'moveDown');
1547  }
1548  // "Hide/Unhide" links:
1549  $hiddenField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
1550 
1551  if (
1552  !empty($GLOBALS['TCA'][$table]['columns'][$hiddenField])
1553  && (empty($GLOBALS['TCA'][$table]['columns'][$hiddenField]['exclude'])
1554  || $this->getBackendUserAuthentication()->check('non_exclude_fields', $table . ':' . $hiddenField))
1555  ) {
1556  if (!$permsEdit || $this->isRecordCurrentBackendUser($table, $row)) {
1557  $hideAction = $this->spaceIcon;
1558  } else {
1559  $hideTitle = htmlspecialchars($this->getLanguageService()->getLL('hide' . ($table == 'pages' ? 'Page' : '')));
1560  $unhideTitle = htmlspecialchars($this->getLanguageService()->getLL('unHide' . ($table == 'pages' ? 'Page' : '')));
1561  if ($row[$hiddenField]) {
1562  $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=0';
1563  $hideAction = '<a class="btn btn-default t3js-record-hide" data-state="hidden" href="#"'
1564  . ' data-params="' . htmlspecialchars($params) . '"'
1565  . ' title="' . $unhideTitle . '"'
1566  . ' data-toggle-title="' . $hideTitle . '">'
1567  . $this->iconFactory->getIcon('actions-edit-unhide', Icon::SIZE_SMALL)->render() . '</a>';
1568  } else {
1569  $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=1';
1570  $hideAction = '<a class="btn btn-default t3js-record-hide" data-state="visible" href="#"'
1571  . ' data-params="' . htmlspecialchars($params) . '"'
1572  . ' title="' . $hideTitle . '"'
1573  . ' data-toggle-title="' . $unhideTitle . '">'
1574  . $this->iconFactory->getIcon('actions-edit-hide', Icon::SIZE_SMALL)->render() . '</a>';
1575  }
1576  }
1577  $this->addActionToCellGroup($cells, $hideAction, 'hide');
1578  }
1579  // "Delete" link:
1580  $disableDeleteTS = $this->getBackendUserAuthentication()->getTSConfig('options.disableDelete');
1581  $disableDelete = (bool) trim(isset($disableDeleteTS['properties'][$table]) ? $disableDeleteTS['properties'][$table] : $disableDeleteTS['value']);
1582  if ($permsEdit && !$disableDelete && ($table === 'pages' && $localCalcPerms & Permission::PAGE_DELETE || $table !== 'pages' && $this->calcPerms & Permission::CONTENT_EDIT)) {
1583  // Check if the record version is in "deleted" state, because that will switch the action to "restore"
1584  if ($this->getBackendUserAuthentication()->workspace > 0 && isset($row['t3ver_state']) && (int)$row['t3ver_state'] === 2) {
1585  $actionName = 'restore';
1586  $refCountMsg = '';
1587  } else {
1588  $actionName = 'delete';
1589  $refCountMsg = BackendUtility::referenceCount(
1590  $table,
1591  $row['uid'],
1592  ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'),
1593  $this->getReferenceCount($table, $row['uid'])) . BackendUtility::translationCount($table, $row['uid'],
1594  ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
1595  );
1596  }
1597 
1598  if ($this->isRecordCurrentBackendUser($table, $row)) {
1599  $deleteAction = $this->spaceIcon;
1600  } else {
1601  $title = BackendUtility::getRecordTitle($table, $row);
1602  $warningText = $this->getLanguageService()->getLL($actionName . 'Warning') . ' "' . $title . '" ' . '[' . $table . ':' . $row['uid'] . ']' . $refCountMsg;
1603 
1604  $params = 'cmd[' . $table . '][' . $row['uid'] . '][delete]=1';
1605  $icon = $this->iconFactory->getIcon('actions-edit-' . $actionName, Icon::SIZE_SMALL)->render();
1606  $linkTitle = htmlspecialchars($this->getLanguageService()->getLL($actionName));
1607  $deleteAction = '<a class="btn btn-default t3js-record-delete" href="#" '
1608  . ' data-l10parent="' . htmlspecialchars($row['l10n_parent']) . '"'
1609  . ' data-params="' . htmlspecialchars($params) . '" data-title="' . htmlspecialchars($title) . '"'
1610  . ' data-message="' . htmlspecialchars($warningText) . '" title="' . $linkTitle . '"'
1611  . '>' . $icon . '</a>';
1612  }
1613  } else {
1614  $deleteAction = $this->spaceIcon;
1615  }
1616  $this->addActionToCellGroup($cells, $deleteAction, 'delete');
1617  // "Levels" links: Moving pages into new levels...
1618  if ($permsEdit && $table == 'pages' && !$this->searchLevels) {
1619  // Up (Paste as the page right after the current parent page)
1620  if ($this->calcPerms & Permission::PAGE_NEW) {
1621  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . -$this->id;
1622  $moveLeftAction = '<a class="btn btn-default" href="#" onclick="'
1623  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1624  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('prevLevel')) . '">'
1625  . $this->iconFactory->getIcon('actions-move-left', Icon::SIZE_SMALL)->render() . '</a>';
1626  $this->addActionToCellGroup($cells, $moveLeftAction, 'moveLeft');
1627  }
1628  // Down (Paste as subpage to the page right above)
1629  if ($this->currentTable['prevUid'][$row['uid']]) {
1630  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $this->currentTable['prevUid'][$row['uid']]));
1631  if ($localCalcPerms & Permission::PAGE_NEW) {
1632  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['prevUid'][$row['uid']];
1633  $moveRightAction = '<a class="btn btn-default" href="#" onclick="'
1634  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1635  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('nextLevel')) . '">'
1636  . $this->iconFactory->getIcon('actions-move-right', Icon::SIZE_SMALL)->render() . '</a>';
1637  } else {
1638  $moveRightAction = $this->spaceIcon;
1639  }
1640  } else {
1641  $moveRightAction = $this->spaceIcon;
1642  }
1643  $this->addActionToCellGroup($cells, $moveRightAction, 'moveRight');
1644  }
1645  }
1649  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
1650  $stat = '';
1651  $_params = [$table, $row['uid']];
1652  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
1653  $stat .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1654  }
1655  $this->addActionToCellGroup($cells, $stat, 'stat');
1656  }
1664  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1665  // for compatibility reason, we move all icons to the rootlevel
1666  // before calling the hooks
1667  foreach ($cells as $section => $actions) {
1668  foreach ($actions as $actionKey => $action) {
1669  $cells[$actionKey] = $action;
1670  }
1671  }
1672  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1673  $hookObject = GeneralUtility::getUserObj($classData);
1674  if (!$hookObject instanceof RecordListHookInterface) {
1675  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567840);
1676  }
1677  $cells = $hookObject->makeControl($table, $row, $cells, $this);
1678  }
1679  // now sort icons again into primary and secondary sections
1680  // after all hooks are processed
1681  $hookCells = $cells;
1682  foreach ($hookCells as $key => $value) {
1683  if ($key === 'primary' || $key === 'secondary') {
1684  continue;
1685  }
1686  $this->addActionToCellGroup($cells, $value, $key);
1687  }
1688  }
1689  $output = '<!-- CONTROL PANEL: ' . $table . ':' . $row['uid'] . ' -->';
1690  foreach ($cells as $classification => $actions) {
1691  $visibilityClass = ($classification !== 'primary' && !$module->MOD_SETTINGS['bigControlPanel'] ? 'collapsed' : 'expanded');
1692  if ($visibilityClass === 'collapsed') {
1693  $cellOutput = '';
1694  foreach ($actions as $action) {
1695  $cellOutput .= $action;
1696  }
1697  $output .= ' <div class="btn-group">' .
1698  '<span id="actions_' . $table . '_' . $row['uid'] . '" class="btn-group collapse collapse-horizontal width">' . $cellOutput . '</span>' .
1699  '<a href="#actions_' . $table . '_' . $row['uid'] . '" class="btn btn-default collapsed" data-toggle="collapse" aria-expanded="false"><span class="t3-icon fa fa-ellipsis-h"></span></a>' .
1700  '</div>';
1701  } else {
1702  $output .= ' <div class="btn-group" role="group">' . implode('', $actions) . '</div>';
1703  }
1704  }
1705  return $output;
1706  }
1707 
1716  public function makeClip($table, $row)
1717  {
1718  // Return blank, if disabled:
1719  if (!$this->getModule()->MOD_SETTINGS['clipBoard']) {
1720  return '';
1721  }
1722  $cells = [];
1723  $cells['pasteAfter'] = ($cells['pasteInto'] = $this->spaceIcon);
1724  //enables to hide the copy, cut and paste icons for localized records - doesn't make much sense to perform these options for them
1725  $isL10nOverlay = $this->localizationView && $table != 'pages_language_overlay' && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0;
1726  // Return blank, if disabled:
1727  // Whether a numeric clipboard pad is active or the normal pad we will see different content of the panel:
1728  // For the "Normal" pad:
1729  if ($this->clipObj->current === 'normal') {
1730  // Show copy/cut icons:
1731  $isSel = (string)$this->clipObj->isSelected($table, $row['uid']);
1732  if ($isL10nOverlay || !$this->overlayEditLockPermissions($table, $row)) {
1733  $cells['copy'] = $this->spaceIcon;
1734  $cells['cut'] = $this->spaceIcon;
1735  } else {
1736  $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL);
1737  $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL);
1738 
1739  if ($isSel === 'copy') {
1740  $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL);
1741  } elseif ($isSel === 'cut') {
1742  $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL);
1743  }
1744 
1745  $cells['copy'] = '<a class="btn btn-default" href="#" onclick="'
1746  . htmlspecialchars('return jumpSelf(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $row['uid'], 1, ($isSel === 'copy'), ['returnUrl' => ''])) . ');')
1747  . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy')) . '">'
1748  . $copyIcon->render() . '</a>';
1749 
1750  // Check permission to cut page or content
1751  if ($table == 'pages') {
1752  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1753  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
1754  } else {
1755  $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
1756  }
1757  $permsEdit = $this->overlayEditLockPermissions($table, $row, $permsEdit);
1758 
1759  // If the listed table is 'pages' we have to request the permission settings for each page:
1760  if ($table == 'pages') {
1761  if ($permsEdit) {
1762  $cells['cut'] = '<a class="btn btn-default" href="#" onclick="'
1763  . htmlspecialchars('return jumpSelf(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $row['uid'], 0, ($isSel === 'cut'), ['returnUrl' => ''])) . ');')
1764  . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut')) . '">'
1765  . $cutIcon->render() . '</a>';
1766  } else {
1767  $cells['cut'] = $this->spaceIcon;
1768  }
1769  } else {
1770  if ($table !== 'pages' && $this->calcPerms & Permission::CONTENT_EDIT) {
1771  $cells['cut'] = '<a class="btn btn-default" href="#" onclick="'
1772  . htmlspecialchars('return jumpSelf(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $row['uid'], 0, ($isSel === 'cut'), ['returnUrl' => ''])) . ');')
1773  . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut')) . '">'
1774  . $cutIcon->render() . '</a>';
1775  } else {
1776  $cells['cut'] = $this->spaceIcon;
1777  }
1778  }
1779  }
1780  } else {
1781  // For the numeric clipboard pads (showing checkboxes where one can select elements on/off)
1782  // Setting name of the element in ->CBnames array:
1783  $n = $table . '|' . $row['uid'];
1784  $this->CBnames[] = $n;
1785  // Check if the current element is selected and if so, prepare to set the checkbox as selected:
1786  $checked = $this->clipObj->isSelected($table, $row['uid']) ? 'checked="checked" ' : '';
1787  // If the "duplicateField" value is set then select all elements which are duplicates...
1788  if ($this->duplicateField && isset($row[$this->duplicateField])) {
1789  $checked = '';
1790  if (in_array($row[$this->duplicateField], $this->duplicateStack)) {
1791  $checked = 'checked="checked" ';
1792  }
1793  $this->duplicateStack[] = $row[$this->duplicateField];
1794  }
1795  // Adding the checkbox to the panel:
1796  $cells['select'] = $isL10nOverlay
1797  ? $this->spaceIcon
1798  : '<input type="hidden" name="CBH[' . $n . ']" value="0" /><label class="btn btn-default btn-checkbox"><input type="checkbox"'
1799  . ' name="CBC[' . $n . ']" value="1" ' . $checked . '/><span class="t3-icon fa"></span></label>';
1800  }
1801  // Now, looking for selected elements from the current table:
1802  $elFromTable = $this->clipObj->elFromTable($table);
1803  if (!empty($elFromTable) && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1804  // IF elements are found, they can be individually ordered and are not locked by editlock, then add a "paste after" icon:
1805  $cells['pasteAfter'] = $isL10nOverlay || !$this->overlayEditLockPermissions($table, $row)
1806  ? $this->spaceIcon
1807  : '<a class="btn btn-default t3js-modal-trigger"'
1808  . ' href="' . htmlspecialchars($this->clipObj->pasteUrl($table, -$row['uid'])) . '"'
1809  . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteAfter')) . '"'
1810  . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteAfter')) . '"'
1811  . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText($table, $row, 'after', $elFromTable)) . '"'
1812  . ' data-severity="warning">'
1813  . $this->iconFactory->getIcon('actions-document-paste-after', Icon::SIZE_SMALL)->render() . '</a>';
1814  }
1815  // Now, looking for elements in general:
1816  $elFromTable = $this->clipObj->elFromTable('');
1817  if ($table == 'pages' && !empty($elFromTable)) {
1818  $cells['pasteInto'] = '<a class="btn btn-default t3js-modal-trigger"'
1819  . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('', $row['uid'])) . '"'
1820  . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
1821  . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_pasteInto')) . '"'
1822  . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText($table, $row, 'into', $elFromTable)) . '"'
1823  . ' data-severity="warning">'
1824  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render() . '</a>';
1825  }
1833  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1834  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1835  $hookObject = GeneralUtility::getUserObj($classData);
1836  if (!$hookObject instanceof RecordListHookInterface) {
1837  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567845);
1838  }
1839  $cells = $hookObject->makeClip($table, $row, $cells, $this);
1840  }
1841  }
1842  // Compile items into a DIV-element:
1843  return '<!-- CLIPBOARD PANEL: ' . $table . ':' . $row['uid'] . ' -->
1844  <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1845  }
1846 
1855  protected function createReferenceHtml($tableName, $uid)
1856  {
1857  $referenceCount = GeneralUtility::makeInstance(ConnectionPool::class)
1858  ->getConnectionForTable('sys_refindex')
1859  ->count(
1860  '*',
1861  'sys_refindex',
1862  [
1863  'ref_table' => $tableName,
1864  'ref_uid' => (int)$uid,
1865  'deleted' => 0,
1866  ]
1867  );
1868 
1869  return $this->generateReferenceToolTip(
1872  );
1873  }
1874 
1882  public function makeLocalizationPanel($table, $row)
1883  {
1884  $out = [
1885  0 => '',
1886  1 => ''
1887  ];
1888  // Reset translations
1889  $this->translations = [];
1890 
1891  // Language title and icon:
1892  $out[0] = $this->languageFlag($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
1893  // Guard clause so we can quickly return if a record is localized to "all languages"
1894  // It should only be possible to localize a record off default (uid 0)
1895  // Reasoning: The Parent is for ALL languages... why overlay with a localization?
1896  if ((int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] === -1) {
1897  return $out;
1898  }
1899 
1900  $translations = $this->translateTools->translationInfo($table, $row['uid'], 0, $row, $this->selFieldList);
1901  if (is_array($translations)) {
1902  $this->translations = $translations['translations'];
1903  // Traverse page translations and add icon for each language that does NOT yet exist:
1904  $lNew = '';
1905  foreach ($this->pageOverlays as $lUid_OnPage => $lsysRec) {
1906  if ($this->isEditable($table) && !isset($translations['translations'][$lUid_OnPage]) && $this->getBackendUserAuthentication()->checkLanguageAccess($lUid_OnPage)) {
1907  $url = $this->listURL();
1909  '&cmd[' . $table . '][' . $row['uid'] . '][localize]=' . $lUid_OnPage,
1910  $url . '&justLocalized=' . rawurlencode($table . ':' . $row['uid'] . ':' . $lUid_OnPage)
1911  );
1912  $language = BackendUtility::getRecord('sys_language', $lUid_OnPage, 'title');
1913  if ($this->languageIconTitles[$lUid_OnPage]['flagIcon']) {
1914  $lC = $this->iconFactory->getIcon($this->languageIconTitles[$lUid_OnPage]['flagIcon'], Icon::SIZE_SMALL)->render();
1915  } else {
1916  $lC = $this->languageIconTitles[$lUid_OnPage]['title'];
1917  }
1918  $lC = '<a href="' . htmlspecialchars($href) . '" title="'
1919  . htmlspecialchars($language['title']) . '" class="btn btn-default">' . $lC . '</a> ';
1920  $lNew .= $lC;
1921  }
1922  }
1923  if ($lNew) {
1924  $out[1] .= $lNew;
1925  }
1926  } elseif ($row['l18n_parent']) {
1927  $out[0] = '&nbsp;&nbsp;&nbsp;&nbsp;' . $out[0];
1928  }
1929  return $out;
1930  }
1931 
1939  public function fieldSelectBox($table, $formFields = true)
1940  {
1941  $lang = $this->getLanguageService();
1942  // Init:
1943  $formElements = ['', ''];
1944  if ($formFields) {
1945  $formElements = ['<form action="' . htmlspecialchars($this->listURL()) . '" method="post" name="fieldSelectBox">', '</form>'];
1946  }
1947  // Load already selected fields, if any:
1948  $setFields = is_array($this->setFields[$table]) ? $this->setFields[$table] : [];
1949  // Request fields from table:
1950  $fields = $this->makeFieldList($table, false, true);
1951  // Add pseudo "control" fields
1952  $fields[] = '_PATH_';
1953  $fields[] = '_REF_';
1954  $fields[] = '_LOCALIZATION_';
1955  $fields[] = '_CONTROL_';
1956  $fields[] = '_CLIPBOARD_';
1957  // Create a checkbox for each field:
1958  $checkboxes = [];
1959  $checkAllChecked = true;
1960  foreach ($fields as $fieldName) {
1961  // Determine, if checkbox should be checked
1962  if (in_array($fieldName, $setFields, true) || $fieldName === $this->fieldArray[0]) {
1963  $checked = ' checked="checked"';
1964  } else {
1965  $checkAllChecked = false;
1966  $checked = '';
1967  }
1968  // Field label
1969  $fieldLabel = is_array($GLOBALS['TCA'][$table]['columns'][$fieldName])
1970  ? rtrim($lang->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label']), ':')
1971  : '';
1972  $checkboxes[] = '<tr><td class="col-checkbox"><input type="checkbox" id="check-' . $fieldName . '" name="displayFields['
1973  . $table . '][]" value="' . $fieldName . '" ' . $checked
1974  . ($fieldName === $this->fieldArray[0] ? ' disabled="disabled"' : '') . '></td><td class="col-title">'
1975  . '<label class="label-block" for="check-' . $fieldName . '">' . htmlspecialchars($fieldLabel) . ' <span class="text-muted text-monospace">[' . htmlspecialchars($fieldName) . ']</span></label></td></tr>';
1976  }
1977  // Table with the field selector::
1978  $content = $formElements[0] . '
1979  <input type="hidden" name="displayFields[' . $table . '][]" value="">
1980  <div class="table-fit table-scrollable">
1981  <table border="0" cellpadding="0" cellspacing="0" class="table table-transparent table-hover">
1982  <thead>
1983  <tr>
1984  <th class="col-checkbox checkbox" colspan="2">
1985  <label><input type="checkbox" class="checkbox checkAll" ' . ($checkAllChecked ? ' checked="checked"' : '') . '>
1986  ' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.toggleall')) . '</label>
1987  </th>
1988  </tr>
1989  </thead>
1990  <tbody>
1991  ' . implode('', $checkboxes) . '
1992  </tbody>
1993  </table>
1994  </div>
1995  <input type="submit" name="search" class="btn btn-default" value="'
1996  . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.setFields')) . '"/>
1997  ' . $formElements[1];
1998  return '<div class="fieldSelectBox">' . $content . '</div>';
1999  }
2000 
2001  /*********************************
2002  *
2003  * Helper functions
2004  *
2005  *********************************/
2018  public function linkClipboardHeaderIcon($string, $table, $cmd, $warning = '', $title = '')
2019  {
2020  $jsCode = 'document.dblistForm.cmd.value=' . GeneralUtility::quoteJSvalue($cmd)
2021  . ';document.dblistForm.cmd_table.value='
2023  . ';document.dblistForm.submit();';
2024 
2025  $attributes = [];
2026  if ($title !== '') {
2027  $attributes['title'] = $title;
2028  }
2029  if ($warning) {
2030  $attributes['class'] = 'btn btn-default t3js-modal-trigger';
2031  $attributes['data-href'] = 'javascript:' . $jsCode;
2032  $attributes['data-severity'] = 'warning';
2033  $attributes['data-title'] = $title;
2034  $attributes['data-content'] = $warning;
2035  } else {
2036  $attributes['class'] = 'btn btn-default';
2037  $attributes['onclick'] = $jsCode . 'return false;';
2038  }
2039 
2040  $attributesString = '';
2041  foreach ($attributes as $key => $value) {
2042  $attributesString .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
2043  }
2044  return '<a href="#" ' . $attributesString . '>' . $string . '</a>';
2045  }
2046 
2052  public function clipNumPane()
2053  {
2054  return in_array('_CLIPBOARD_', $this->fieldArray) && $this->clipObj->current != 'normal';
2055  }
2056 
2067  public function addSortLink($code, $field, $table)
2068  {
2069  // Certain circumstances just return string right away (no links):
2070  if ($field == '_CONTROL_' || $field == '_LOCALIZATION_' || $field == '_CLIPBOARD_' || $field == '_REF_' || $this->disableSingleTableView) {
2071  return $code;
2072  }
2073  // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!)
2074  if ($field == '_PATH_') {
2075  $field = 'pid';
2076  }
2077  // Create the sort link:
2078  $sortUrl = $this->listURL('', '-1', 'sortField,sortRev,table,firstElementNumber') . '&table=' . $table
2079  . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1);
2080  $sortArrow = $this->sortField === $field
2081  ? $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render()
2082  : '';
2083  // Return linked field:
2084  return '<a href="' . htmlspecialchars($sortUrl) . '">' . $code . $sortArrow . '</a>';
2085  }
2086 
2095  public function recPath($pid)
2096  {
2097  if (!isset($this->recPath_cache[$pid])) {
2098  $this->recPath_cache[$pid] = BackendUtility::getRecordPath($pid, $this->perms_clause, 20);
2099  }
2100  return $this->recPath_cache[$pid];
2101  }
2102 
2110  public function showNewRecLink($table)
2111  {
2112  // No deny/allow tables are set:
2113  if (empty($this->allowedNewTables) && empty($this->deniedNewTables)) {
2114  return true;
2115  }
2116  return !in_array($table, $this->deniedNewTables)
2117  && (empty($this->allowedNewTables) || in_array($table, $this->allowedNewTables));
2118  }
2119 
2127  public function makeReturnUrl()
2128  {
2129  return '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
2130  }
2131 
2132  /************************************
2133  *
2134  * CSV related functions
2135  *
2136  ************************************/
2142  protected function initCSV()
2143  {
2144  $this->addHeaderRowToCSV();
2145  }
2146 
2152  protected function addHeaderRowToCSV()
2153  {
2154  // Add header row, control fields will be reduced inside addToCSV()
2155  $this->addToCSV(array_combine($this->fieldArray, $this->fieldArray));
2156  }
2157 
2164  protected function addToCSV(array $row = [])
2165  {
2166  $rowReducedByControlFields = self::removeControlFieldsFromFieldRow($row);
2167  $rowReducedToSelectedColumns = array_intersect_key($rowReducedByControlFields, array_flip($this->fieldArray));
2168  $this->setCsvRow($rowReducedToSelectedColumns);
2169  }
2170 
2177  protected static function removeControlFieldsFromFieldRow(array $row = [])
2178  {
2179  // Possible control fields in a list row
2180  $controlFields = [
2181  '_PATH_',
2182  '_REF_',
2183  '_CONTROL_',
2184  '_CLIPBOARD_',
2185  '_LOCALIZATION_',
2186  '_LOCALIZATION_b'
2187  ];
2188  return array_diff_key($row, array_flip($controlFields));
2189  }
2190 
2197  public function setCsvRow($csvRow)
2198  {
2199  $this->csvLines[] = GeneralUtility::csvValues($csvRow);
2200  }
2201 
2209  public function outputCSV($prefix)
2210  {
2211  // Setting filename:
2212  $filename = $prefix . '_' . date('dmy-Hi') . '.csv';
2213  // Creating output header:
2214  header('Content-Type: application/octet-stream');
2215  header('Content-Disposition: attachment; filename=' . $filename);
2216  // Cache-Control header is needed here to solve an issue with browser IE and
2217  // versions lower than 9. See for more information: http://support.microsoft.com/kb/323308
2218  header("Cache-Control: ''");
2219  // Printing the content of the CSV lines:
2220  echo implode(CRLF, $this->csvLines);
2221  // Exits:
2222  die;
2223  }
2224 
2232  public function addActionToCellGroup(&$cells, $action, $actionKey)
2233  {
2234  $cellsMap = [
2235  'primary' => [
2236  'view', 'edit', 'hide', 'delete', 'stat'
2237  ],
2238  'secondary' => [
2239  'viewBig', 'history', 'perms', 'new', 'move', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'version'
2240  ]
2241  ];
2242  $classification = in_array($actionKey, $cellsMap['primary']) ? 'primary' : 'secondary';
2243  $cells[$classification][$actionKey] = $action;
2244  unset($cells[$actionKey]);
2245  }
2246 
2254  protected function isRecordCurrentBackendUser($table, $row)
2255  {
2256  return $table === 'be_users' && (int)$row['uid'] === $this->getBackendUserAuthentication()->user['uid'];
2257  }
2258 
2262  public function setIsEditable($isEditable)
2263  {
2264  $this->editable = $isEditable;
2265  }
2266 
2272  public function isEditable($table)
2273  {
2274  return $GLOBALS['TCA'][$table]['ctrl']['readOnly'] || $this->editable;
2275  }
2276 
2287  protected function overlayEditLockPermissions($table, $row = [], $editPermission = true)
2288  {
2289  if ($editPermission && !$this->getBackendUserAuthentication()->isAdmin()) {
2290  // If no $row is submitted we only check for general edit lock of current page (except for table "pages")
2291  if (empty($row)) {
2292  return $table === 'pages' ? true : !$this->pageRow['editlock'];
2293  }
2294  if (($table === 'pages' && $row['editlock']) || ($table !== 'pages' && $this->pageRow['editlock'])) {
2295  $editPermission = false;
2296  } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['editlock']) && $row[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) {
2297  $editPermission = false;
2298  }
2299  }
2300  return $editPermission;
2301  }
2302 
2309  protected function editLockPermissions()
2310  {
2311  return $this->getBackendUserAuthentication()->isAdmin() || !$this->pageRow['editlock'];
2312  }
2313 
2317  protected function getModule()
2318  {
2319  return $GLOBALS['SOBE'];
2320  }
2321 
2325  protected function getDocumentTemplate()
2326  {
2327  return $GLOBALS['TBE_TEMPLATE'];
2328  }
2329 }
static getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
static linkThisUrl($url, array $getParams=[])
static getProcessedValueExtra($table, $fN, $fV, $fixed_lgd_chars=0, $uid=0, $forceResult=true, $pid=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
generateReferenceToolTip($references, $launchViewParameter= '')
addElement($h, $icon, $data, $rowParams= '', $_= '', $_2= '', $colType= 'td')
setTotalItems(string $table, int $pageId, array $constraints)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static getLinkToDataHandlerAction($parameters, $redirectUrl= '')
linkClipboardHeaderIcon($string, $table, $cmd, $warning= '', $title= '')
static viewOnClick($pageUid, $backPath= '', $rootLine=null, $anchorSection= '', $alternativeUrl= '', $additionalGetVars= '', $switchFocus=true)
getQueryBuilder(string $table, int $pageId, array $additionalConstraints=[], array $fields=['*'])
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static selectVersionsOfRecord($table, $uid, $fields= '*', $workspace=0, $includeDeletedRecords=false, $row=null)
static BEgetRootLine($uid, $clause= '', $workspaceOL=false)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
languageFlag($sys_language_uid, $addAsAdditionalText=true)
static csvValues(array $row, $delim= ',', $quote= '"')
overlayEditLockPermissions($table, $row=[], $editPermission=true)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
makeFieldList($table, $dontCheckUser=false, $addDateFields=false)
static wrapClickMenuOnIcon($content, $table, $uid=0, $listFrame=true, $addParams= '', $enDisItems= '', $returnTagParameters=false)
static referenceCount($table, $ref, $msg= '', $count=null)
renderListRow($table, $row, $cc, $titleCol, $thumbsCol, $indent=0)
static callUserFunction($funcName, &$params, &$ref, $_= '', $errorMode=0)