TYPO3 CMS  TYPO3_7-6
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 
32 
37 {
38  // *********
39  // External:
40  // *********
41 
48  public $allowedNewTables = [];
49 
56  public $deniedNewTables = [];
57 
65  public $newWizards = false;
66 
73 
79  public $showClipboard = false;
80 
86  public $noControlPanels = false;
87 
93  public $clickMenuEnabled = true;
94 
101 
107  public $spaceIcon;
108 
114  public $disableSingleTableView = false;
115 
116  // *********
117  // Internal:
118  // *********
119 
125  public $pageRow = [];
126 
132  protected $csvLines = [];
133 
139  public $csvOutput = false;
140 
146  public $clipObj;
147 
153  public $CBnames = [];
154 
160  protected $referenceCount = [];
161 
168 
176 
180  public $pageinfo;
181 
187  public $MOD_MENU;
188 
194  protected $editable = true;
195 
199  protected $iconFactory;
200 
204  public function __construct()
205  {
206  parent::__construct();
207  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
208  }
209 
216  public function getButtons()
217  {
218  $module = $this->getModule();
219  $backendUser = $this->getBackendUserAuthentication();
220  $lang = $this->getLanguageService();
221  $buttons = [
222  'csh' => '',
223  'view' => '',
224  'edit' => '',
225  'hide_unhide' => '',
226  'move' => '',
227  'new_record' => '',
228  'paste' => '',
229  'level_up' => '',
230  'cache' => '',
231  'reload' => '',
232  'shortcut' => '',
233  'back' => '',
234  'csv' => '',
235  'export' => ''
236  ];
237  // Get users permissions for this page record:
238  $localCalcPerms = $backendUser->calcPerms($this->pageRow);
239  // CSH
240  if ((string)$this->id === '') {
241  $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module_noId');
242  } elseif (!$this->id) {
243  $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module_root');
244  } else {
245  $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module');
246  }
247  if (isset($this->id)) {
248  // View Exclude doktypes 254,255 Configuration:
249  // mod.web_list.noViewWithDokTypes = 254,255
250  if (isset($module->modTSconfig['properties']['noViewWithDokTypes'])) {
251  $noViewDokTypes = GeneralUtility::trimExplode(',', $module->modTSconfig['properties']['noViewWithDokTypes'], true);
252  } else {
253  //default exclusion: doktype 254 (folder), 255 (recycler)
254  $noViewDokTypes = [
255  PageRepository::DOKTYPE_SYSFOLDER,
256  PageRepository::DOKTYPE_RECYCLER
257  ];
258  }
259  if (!in_array($this->pageRow['doktype'], $noViewDokTypes)) {
260  $onClick = htmlspecialchars(BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id)));
261  $buttons['view'] = '<a href="#" onclick="' . $onClick . '" title="'
262  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', true) . '">'
263  . $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render() . '</a>';
264  }
265  // New record on pages that are not locked by editlock
266  if (!$module->modTSconfig['properties']['noCreateRecordsLink'] && $this->editLockPermissions()) {
267  $onClick = htmlspecialchars('return jumpExt(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('db_new', ['id' => $this->id])) . ');');
268  $buttons['new_record'] = '<a href="#" onclick="' . $onClick . '" title="'
269  . $lang->getLL('newRecordGeneral', true) . '">'
270  . $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL)->render() . '</a>';
271  }
272  // If edit permissions are set, see
273  // \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
274  if ($localCalcPerms & Permission::PAGE_EDIT && !empty($this->id) && $this->editLockPermissions() && $this->getBackendUserAuthentication()->checkLanguageAccess(0)) {
275  // Edit
276  $params = '&edit[pages][' . $this->pageRow['uid'] . ']=edit';
277  $onClick = htmlspecialchars(BackendUtility::editOnClick($params, '', -1));
278  $buttons['edit'] = '<a href="#" onclick="' . $onClick . '" title="' . $lang->getLL('editPage', true) . '">'
279  . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render()
280  . '</a>';
281  }
282  // Paste
283  if (($localCalcPerms & Permission::PAGE_NEW || $localCalcPerms & Permission::CONTENT_EDIT) && $this->editLockPermissions()) {
284  $elFromTable = $this->clipObj->elFromTable('');
285  if (!empty($elFromTable)) {
286  $confirmText = $this->clipObj->confirmMsgText('pages', $this->pageRow, 'into', $elFromTable);
287  $buttons['paste'] = '<a'
288  . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('', $this->id)) . '"'
289  . ' title="' . $lang->getLL('clip_paste', true) . '"'
290  . ' class="t3js-modal-trigger"'
291  . ' data-severity="warning"'
292  . ' data-title="' . $lang->getLL('clip_paste', true) . '"'
293  . ' data-content="' . htmlspecialchars($confirmText) . '"'
294  . '>'
295  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
296  . '</a>';
297  }
298  }
299  // Cache
300  $buttons['cache'] = '<a href="' . htmlspecialchars(($this->listURL() . '&clear_cache=1')) . '" title="'
301  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.clear_cache', true) . '">'
302  . $this->iconFactory->getIcon('actions-system-cache-clear', Icon::SIZE_SMALL)->render() . '</a>';
303  if ($this->table && (!isset($module->modTSconfig['properties']['noExportRecordsLinks'])
304  || (isset($module->modTSconfig['properties']['noExportRecordsLinks'])
305  && !$module->modTSconfig['properties']['noExportRecordsLinks']))
306  ) {
307  // CSV
308  $buttons['csv'] = '<a href="' . htmlspecialchars(($this->listURL() . '&csv=1')) . '" title="'
309  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.csv', true) . '">'
310  . $this->iconFactory->getIcon('actions-document-export-csv', Icon::SIZE_SMALL)->render() . '</a>';
311  // Export
312  if (ExtensionManagementUtility::isLoaded('impexp')) {
313  $url = BackendUtility::getModuleUrl('xMOD_tximpexp', ['tx_impexp[action]' => 'export']);
314  $buttons['export'] = '<a href="' . htmlspecialchars($url . '&tx_impexp[list][]='
315  . rawurlencode($this->table . ':' . $this->id)) . '" title="'
316  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.export', true) . '">'
317  . $this->iconFactory->getIcon('actions-document-export-t3d', Icon::SIZE_SMALL)->render() . '</a>';
318  }
319  }
320  // Reload
321  $buttons['reload'] = '<a href="' . htmlspecialchars($this->listURL()) . '" title="'
322  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.reload', true) . '">'
323  . $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render() . '</a>';
324  // Shortcut
325  if ($backendUser->mayMakeShortcut()) {
326  $buttons['shortcut'] = $this->getDocumentTemplate()->makeShortcutIcon(
327  'id, M, imagemode, pointer, table, search_field, search_levels, showLimit, sortField, sortRev',
328  implode(',', array_keys($this->MOD_MENU)),
329  'web_list'
330  );
331  }
332  // Back
333  if ($this->returnUrl) {
334  $href = htmlspecialchars(GeneralUtility::linkThisUrl($this->returnUrl, ['id' => $this->id]));
335  $buttons['back'] = '<a href="' . $href . '" class="typo3-goBack" title="'
336  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.goBack', true) . '">'
337  . $this->iconFactory->getIcon('actions-view-go-back', Icon::SIZE_SMALL)->render() . '</a>';
338  }
339  }
340  return $buttons;
341  }
342 
350  {
351  $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
352  $module = $this->getModule();
353  $backendUser = $this->getBackendUserAuthentication();
354  $lang = $this->getLanguageService();
355  // Get users permissions for this page record:
356  $localCalcPerms = $backendUser->calcPerms($this->pageRow);
357  // CSH
358  if ((string)$this->id === '') {
359  $fieldName = 'list_module_noId';
360  } elseif (!$this->id) {
361  $fieldName = 'list_module_root';
362  } else {
363  $fieldName = 'list_module';
364  }
365  $cshButton = $buttonBar->makeHelpButton()
366  ->setModuleName('xMOD_csh_corebe')
367  ->setFieldName($fieldName);
368  $buttonBar->addButton($cshButton);
369  if (isset($this->id)) {
370  // View Exclude doktypes 254,255 Configuration:
371  // mod.web_list.noViewWithDokTypes = 254,255
372  if (isset($module->modTSconfig['properties']['noViewWithDokTypes'])) {
373  $noViewDokTypes = GeneralUtility::trimExplode(',', $module->modTSconfig['properties']['noViewWithDokTypes'], true);
374  } else {
375  //default exclusion: doktype 254 (folder), 255 (recycler)
376  $noViewDokTypes = [
377  PageRepository::DOKTYPE_SYSFOLDER,
378  PageRepository::DOKTYPE_RECYCLER
379  ];
380  }
381  // New record on pages that are not locked by editlock
382  if (!$module->modTSconfig['properties']['noCreateRecordsLink'] && $this->editLockPermissions()) {
383  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('db_new', ['id' => $this->id])) . ');';
384  $newRecordButton = $buttonBar->makeLinkButton()
385  ->setHref('#')
386  ->setOnClick($onClick)
387  ->setTitle($lang->getLL('newRecordGeneral'))
388  ->setIcon($this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL));
389  $buttonBar->addButton($newRecordButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
390  }
391  if (!in_array($this->pageRow['doktype'], $noViewDokTypes)) {
392  $onClick = BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id));
393  $viewButton = $buttonBar->makeLinkButton()
394  ->setHref('#')
395  ->setOnClick($onClick)
396  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage'))
397  ->setIcon($this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL));
398  $buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
399  }
400  // If edit permissions are set, see
401  // \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
402  if ($localCalcPerms & Permission::PAGE_EDIT && !empty($this->id) && $this->editLockPermissions()) {
403  // Edit
404  $params = '&edit[pages][' . $this->pageRow['uid'] . ']=edit';
405  $onClick = BackendUtility::editOnClick($params, '', -1);
406  $editButton = $buttonBar->makeLinkButton()
407  ->setHref('#')
408  ->setOnClick($onClick)
409  ->setTitle($lang->getLL('editPage'))
410  ->setIcon($this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL));
411  $buttonBar->addButton($editButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
412  }
413  // Paste
414  if ($this->showClipboard && ($localCalcPerms & Permission::PAGE_NEW || $localCalcPerms & Permission::CONTENT_EDIT) && $this->editLockPermissions()) {
415  $elFromTable = $this->clipObj->elFromTable('');
416  if (!empty($elFromTable)) {
417  $confirmMessage = $this->clipObj->confirmMsgText('pages', $this->pageRow, 'into', $elFromTable);
418  $pasteButton = $buttonBar->makeLinkButton()
419  ->setHref($this->clipObj->pasteUrl('', $this->id))
420  ->setTitle($lang->getLL('clip_paste'))
421  ->setClasses('t3js-modal-trigger')
422  ->setDataAttributes([
423  'severity' => 'warning',
424  'content' => $confirmMessage,
425  'title' => $lang->getLL('clip_paste')
426  ])
427  ->setIcon($this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL));
428  $buttonBar->addButton($pasteButton, ButtonBar::BUTTON_POSITION_LEFT, 40);
429  }
430  }
431  // Cache
432  $clearCacheButton = $buttonBar->makeLinkButton()
433  ->setHref($this->listURL() . '&clear_cache=1')
434  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.clear_cache'))
435  ->setIcon($this->iconFactory->getIcon('actions-system-cache-clear', Icon::SIZE_SMALL));
436  $buttonBar->addButton($clearCacheButton, ButtonBar::BUTTON_POSITION_RIGHT);
437  if ($this->table && (!isset($module->modTSconfig['properties']['noExportRecordsLinks'])
438  || (isset($module->modTSconfig['properties']['noExportRecordsLinks'])
439  && !$module->modTSconfig['properties']['noExportRecordsLinks']))
440  ) {
441  // CSV
442  $csvButton = $buttonBar->makeLinkButton()
443  ->setHref($this->listURL() . '&csv=1')
444  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.csv'))
445  ->setIcon($this->iconFactory->getIcon('actions-document-export-csv', Icon::SIZE_SMALL));
446  $buttonBar->addButton($csvButton, ButtonBar::BUTTON_POSITION_LEFT, 40);
447  // Export
448  if (ExtensionManagementUtility::isLoaded('impexp')) {
449  $url = BackendUtility::getModuleUrl('xMOD_tximpexp', ['tx_impexp[action]' => 'export']);
450  $exportButton = $buttonBar->makeLinkButton()
451  ->setHref($url . '&tx_impexp[list][]=' . rawurlencode($this->table . ':' . $this->id))
452  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.export'))
453  ->setIcon($this->iconFactory->getIcon('actions-document-export-t3d', Icon::SIZE_SMALL));
454  $buttonBar->addButton($exportButton, ButtonBar::BUTTON_POSITION_LEFT, 40);
455  }
456  }
457  // Reload
458  $reloadButton = $buttonBar->makeLinkButton()
459  ->setHref($this->listURL())
460  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.reload'))
461  ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
462  $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
463  // Shortcut
464  if ($backendUser->mayMakeShortcut()) {
465  $shortCutButton = $buttonBar->makeShortcutButton()
466  ->setModuleName('web_list')
467  ->setGetVariables([
468  'id',
469  'M',
470  'imagemode',
471  'pointer',
472  'table',
473  'search_field',
474  'search_levels',
475  'showLimit',
476  'sortField',
477  'sortRev'
478  ])
479  ->setSetVariables(array_keys($this->MOD_MENU));
480  $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT);
481  }
482  // Back
483  if ($this->returnUrl) {
484  $href = htmlspecialchars(GeneralUtility::linkThisUrl($this->returnUrl, ['id' => $this->id]));
485  $buttons['back'] = '<a href="' . $href . '" class="typo3-goBack" title="'
486  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.goBack', true) . '">'
487  . $this->iconFactory->getIcon('actions-view-go-back', Icon::SIZE_SMALL) . '</a>';
488  }
489  }
490  }
491 
501  public function getTable($table, $id, $rowList = '')
502  {
503  $rowListArray = GeneralUtility::trimExplode(',', $rowList, true);
504  // if no columns have been specified, show description (if configured)
505  if (!empty($GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']) && empty($rowListArray)) {
506  array_push($rowListArray, $GLOBALS['TCA'][$table]['ctrl']['descriptionColumn']);
507  }
508  $backendUser = $this->getBackendUserAuthentication();
509  $lang = $this->getLanguageService();
510  $db = $this->getDatabaseConnection();
511  // Init
512  $addWhere = '';
513  $titleCol = $GLOBALS['TCA'][$table]['ctrl']['label'];
514  $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
515  $l10nEnabled = $GLOBALS['TCA'][$table]['ctrl']['languageField']
516  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
517  && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'];
518  $tableCollapsed = (bool)$this->tablesCollapsed[$table];
519  // prepare space icon
520  $this->spaceIcon = '<span class="btn btn-default disabled">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
521  // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
522  $this->fieldArray = [];
523  // title Column
524  // Add title column
525  $this->fieldArray[] = $titleCol;
526  // Control-Panel
527  if (!GeneralUtility::inList($rowList, '_CONTROL_')) {
528  $this->fieldArray[] = '_CONTROL_';
529  }
530  // Clipboard
531  if ($this->showClipboard) {
532  $this->fieldArray[] = '_CLIPBOARD_';
533  }
534  // Ref
535  if (!$this->dontShowClipControlPanels) {
536  $this->fieldArray[] = '_REF_';
537  }
538  // Path
539  if ($this->searchLevels) {
540  $this->fieldArray[] = '_PATH_';
541  }
542  // Localization
543  if ($this->localizationView && $l10nEnabled) {
544  $this->fieldArray[] = '_LOCALIZATION_';
545  $this->fieldArray[] = '_LOCALIZATION_b';
546  // Only restrict to the default language if no search request is in place
547  if ($this->searchString === '') {
548  $addWhere .= ' AND (
549  ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '<=0
550  OR
551  ' . $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($lang->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:missingTcaColumnsMessage', true), $table, $table);
608  $messageTitle = $lang->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:missingTcaColumnsMessageTitle', true);
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  // Create the SQL query for selecting the elements in the listing:
638  // do not do paging when outputting as CSV
639  if ($this->csvOutput) {
640  $this->iLimit = 0;
641  }
642  if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
643  // Get the two previous rows for sorting if displaying page > 1
644  $this->firstElementNumber = $this->firstElementNumber - 2;
645  $this->iLimit = $this->iLimit + 2;
646  // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
647  $queryParts = $this->makeQueryArray($table, $id, $addWhere);
648 
649  $this->firstElementNumber = $this->firstElementNumber + 2;
650  $this->iLimit = $this->iLimit - 2;
651  } else {
652  // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
653  $queryParts = $this->makeQueryArray($table, $id, $addWhere);
654  }
655 
656  // Finding the total amount of records on the page
657  // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
658  $this->setTotalItems($queryParts);
659 
660  // Init:
661  $dbCount = 0;
662  $out = '';
663  $tableHeader = '';
664  $result = null;
665  $listOnlyInSingleTableMode = $this->listOnlyInSingleTableMode && !$this->table;
666  // If the count query returned any number of records, we perform the real query,
667  // selecting records.
668  if ($this->totalItems) {
669  // Fetch records only if not in single table mode
671  $dbCount = $this->totalItems;
672  } else {
673  // Set the showLimit to the number of records when outputting as CSV
674  if ($this->csvOutput) {
675  $this->showLimit = $this->totalItems;
676  $this->iLimit = $this->totalItems;
677  }
678  $result = $db->exec_SELECT_queryArray($queryParts);
679  $dbCount = $db->sql_num_rows($result);
680  }
681  }
682  // If any records was selected, render the list:
683  if ($dbCount) {
684  $tableTitle = $lang->sL($GLOBALS['TCA'][$table]['ctrl']['title'], true);
685  if ($tableTitle === '') {
686  $tableTitle = $table;
687  }
688  // Header line is drawn
689  $theData = [];
690  if ($this->disableSingleTableView) {
691  $theData[$titleCol] = '<span class="c-table">' . BackendUtility::wrapInHelp($table, '', $tableTitle)
692  . '</span> (<span class="t3js-table-total-items">' . $this->totalItems . '</span>)';
693  } else {
694  $icon = $this->table
695  ? '<span title="' . $lang->getLL('contractView', true) . '">' . $this->iconFactory->getIcon('actions-view-table-collapse', Icon::SIZE_SMALL)->render() . '</span>'
696  : '<span title="' . $lang->getLL('expandView', true) . '">' . $this->iconFactory->getIcon('actions-view-table-expand', Icon::SIZE_SMALL)->render() . '</span>';
697  $theData[$titleCol] = $this->linkWrapTable($table, $tableTitle . ' (<span class="t3js-table-total-items">' . $this->totalItems . '</span>) ' . $icon);
698  }
700  $tableHeader .= BackendUtility::wrapInHelp($table, '', $theData[$titleCol]);
701  } else {
702  // Render collapse button if in multi table mode
703  $collapseIcon = '';
704  if (!$this->table) {
705  $href = htmlspecialchars(($this->listURL() . '&collapse[' . $table . ']=' . ($tableCollapsed ? '0' : '1')));
706  $title = $tableCollapsed
707  ? $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.expandTable', true)
708  : $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.collapseTable', true);
709  $icon = '<span class="collapseIcon">' . $this->iconFactory->getIcon(($tableCollapsed ? 'actions-view-list-expand' : 'actions-view-list-collapse'), Icon::SIZE_SMALL)->render() . '</span>';
710  $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>';
711  }
712  $tableHeader .= $theData[$titleCol] . $collapseIcon;
713  }
714  // Render table rows only if in multi table view or if in single table view
715  $rowOutput = '';
716  if (!$listOnlyInSingleTableMode || $this->table) {
717  // Fixing an order table for sortby tables
718  $this->currentTable = [];
719  $currentIdList = [];
720  $doSort = $GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$this->sortField;
721  $prevUid = 0;
722  $prevPrevUid = 0;
723  // Get first two rows and initialize prevPrevUid and prevUid if on page > 1
724  if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
725  $row = $db->sql_fetch_assoc($result);
726  $prevPrevUid = -((int)$row['uid']);
727  $row = $db->sql_fetch_assoc($result);
728  $prevUid = $row['uid'];
729  }
730  $accRows = [];
731  // Accumulate rows here
732  while ($row = $db->sql_fetch_assoc($result)) {
733  if (!$this->isRowListingConditionFulfilled($table, $row)) {
734  continue;
735  }
736  // In offline workspace, look for alternative record:
737  BackendUtility::workspaceOL($table, $row, $backendUser->workspace, true);
738  if (is_array($row)) {
739  $accRows[] = $row;
740  $currentIdList[] = $row['uid'];
741  if ($doSort) {
742  if ($prevUid) {
743  $this->currentTable['prev'][$row['uid']] = $prevPrevUid;
744  $this->currentTable['next'][$prevUid] = '-' . $row['uid'];
745  $this->currentTable['prevUid'][$row['uid']] = $prevUid;
746  }
747  $prevPrevUid = isset($this->currentTable['prev'][$row['uid']]) ? -$prevUid : $row['pid'];
748  $prevUid = $row['uid'];
749  }
750  }
751  }
752  $db->sql_free_result($result);
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' . $localizationMarkerClass;
996  $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
997  if ($this->getModule()->MOD_SETTINGS['clipBoard']) {
998  $this->addElement_tdCssClass['_CLIPBOARD_'] = 'col-clipboard';
999  }
1000  $this->addElement_tdCssClass['_PATH_'] = 'col-path';
1001  $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
1002  $this->addElement_tdCssClass['_LOCALIZATION_b'] = 'col-localizationb';
1003  // Create element in table cells:
1004  $theData['uid'] = $row['uid'];
1005  if (isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
1006  && isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
1007  && !isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'])
1008  ) {
1009  $theData['_l10nparent_'] = $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
1010  }
1011  $rowOutput .= $this->addElement(1, $theIcon, $theData, $row_bgColor);
1012  // Finally, return table row element:
1013  return $rowOutput;
1014  }
1015 
1024  protected function getReferenceCount($tableName, $uid)
1025  {
1026  $db = $this->getDatabaseConnection();
1027  if (!isset($this->referenceCount[$tableName][$uid])) {
1028  $where = 'ref_table = ' . $db->fullQuoteStr($tableName, 'sys_refindex')
1029  . ' AND ref_uid = ' . $uid . ' AND deleted = 0';
1030  $numberOfReferences = $db->exec_SELECTcountRows('*', 'sys_refindex', $where);
1031  $this->referenceCount[$tableName][$uid] = $numberOfReferences;
1032  }
1033  return $this->referenceCount[$tableName][$uid];
1034  }
1035 
1046  public function renderListHeader($table, $currentIdList)
1047  {
1048  $lang = $this->getLanguageService();
1049  // Init:
1050  $theData = [];
1051  $icon = '';
1052  // Traverse the fields:
1053  foreach ($this->fieldArray as $fCol) {
1054  // Calculate users permissions to edit records in the table:
1055  $permsEdit = $this->calcPerms & ($table == 'pages' ? 2 : 16) && $this->overlayEditLockPermissions($table);
1056  switch ((string)$fCol) {
1057  case '_PATH_':
1058  // Path
1059  $theData[$fCol] = '<i>[' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels._PATH_', true) . ']</i>';
1060  break;
1061  case '_REF_':
1062  // References
1063  $theData[$fCol] = '<i>[' . $lang->sL('LLL:EXT:lang/locallang_mod_file_list.xlf:c__REF_', true) . ']</i>';
1064  break;
1065  case '_LOCALIZATION_':
1066  // Path
1067  $theData[$fCol] = '<i>[' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels._LOCALIZATION_', true) . ']</i>';
1068  break;
1069  case '_LOCALIZATION_b':
1070  // Path
1071  $theData[$fCol] = $lang->getLL('Localize', true);
1072  break;
1073  case '_CLIPBOARD_':
1074  if (!$this->getModule()->MOD_SETTINGS['clipBoard']) {
1075  break;
1076  }
1077  // Clipboard:
1078  $cells = [];
1079  // If there are elements on the clipboard for this table, and the parent page is not locked by editlock
1080  // then display the "paste into" icon:
1081  $elFromTable = $this->clipObj->elFromTable($table);
1082  if (!empty($elFromTable) && $this->overlayEditLockPermissions($table)) {
1083  $href = htmlspecialchars($this->clipObj->pasteUrl($table, $this->id));
1084  $confirmMessage = $this->clipObj->confirmMsgText('pages', $this->pageRow, 'into', $elFromTable);
1085  $cells['pasteAfter'] = '<a class="btn btn-default t3js-modal-trigger"'
1086  . ' href="' . $href . '"'
1087  . ' title="' . $lang->getLL('clip_paste', true) . '"'
1088  . ' data-title="' . $lang->getLL('clip_paste', true) . '"'
1089  . ' data-content="' . htmlspecialchars($confirmMessage) . '"'
1090  . ' data-severity="warning">'
1091  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render()
1092  . '</a>';
1093  }
1094  // If the numeric clipboard pads are enabled, display the control icons for that:
1095  if ($this->clipObj->current != 'normal') {
1096  // The "select" link:
1097  $spriteIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render();
1098  $cells['copyMarked'] = $this->linkClipboardHeaderIcon($spriteIcon, $table, 'setCB', '', $lang->getLL('clip_selectMarked'));
1099  // The "edit marked" link:
1100  $editIdList = implode(',', $currentIdList);
1101  $editIdList = '\'+editList(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($editIdList) . ')+\'';
1102  $params = 'edit[' . $table . '][' . $editIdList . ']=edit';
1103  $onClick = BackendUtility::editOnClick('', '', -1);
1104  $onClickArray = explode('?', $onClick, 2);
1105  $lastElement = array_pop($onClickArray);
1106  array_push($onClickArray, $params . '&' . $lastElement);
1107  $onClick = implode('?', $onClickArray);
1108  $cells['edit'] = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1109  . $lang->getLL('clip_editMarked', true) . '">'
1110  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1111  // The "Delete marked" link:
1112  $cells['delete'] = $this->linkClipboardHeaderIcon(
1113  $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(),
1114  $table,
1115  'delete',
1116  sprintf($lang->getLL('clip_deleteMarkedWarning'), $lang->sL($GLOBALS['TCA'][$table]['ctrl']['title'])),
1117  $lang->getLL('clip_deleteMarked')
1118  );
1119  // The "Select all" link:
1120  $onClick = htmlspecialchars(('checkOffCB(' . GeneralUtility::quoteJSvalue(implode(',', $this->CBnames)) . ', this); return false;'));
1121  $cells['markAll'] = '<a class="btn btn-default" rel="" href="#" onclick="' . $onClick . '" title="'
1122  . $lang->getLL('clip_markRecords', true) . '">'
1123  . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
1124  } else {
1125  $cells['empty'] = '';
1126  }
1134  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1135  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1136  $hookObject = GeneralUtility::getUserObj($classData);
1137  if (!$hookObject instanceof RecordListHookInterface) {
1138  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567850);
1139  }
1140  $cells = $hookObject->renderListHeaderActions($table, $currentIdList, $cells, $this);
1141  }
1142  }
1143  $theData[$fCol] = '<div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1144  break;
1145  case '_CONTROL_':
1146  // Control panel:
1147  if ($this->isEditable($table)) {
1148  // If new records can be created on this page, add links:
1149  $permsAdditional = ($table === 'pages' ? 8 : 16);
1150  if ($this->calcPerms & $permsAdditional && $this->showNewRecLink($table)) {
1151  $spriteIcon = $table === 'pages'
1152  ? $this->iconFactory->getIcon('actions-page-new', Icon::SIZE_SMALL)
1153  : $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL);
1154  if ($table === 'tt_content' && $this->newWizards) {
1155  // If mod.newContentElementWizard.override is set, use that extension's create new content wizard instead:
1156  $tmpTSc = BackendUtility::getModTSconfig($this->pageinfo['uid'], 'mod');
1157  $newContentElementWizard = isset($tmpTSc['properties']['newContentElementWizard.']['override'])
1158  ? $tmpTSc['properties']['newContentElementWizard.']['override']
1159  : 'new_content_element';
1160  $newContentWizScriptPath = BackendUtility::getModuleUrl($newContentElementWizard, ['id' => $this->id]);
1161 
1162  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue($newContentWizScriptPath) . ');';
1163  $icon = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1164  . $lang->getLL('new', true) . '">' . $spriteIcon->render() . '</a>';
1165  } elseif ($table == 'pages' && $this->newWizards) {
1166  $parameters = ['id' => $this->id, 'pagesOnly' => 1, 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')];
1167  $href = BackendUtility::getModuleUrl('db_new', $parameters);
1168  $icon = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="' . $lang->getLL('new', true) . '">'
1169  . $spriteIcon->render() . '</a>';
1170  } else {
1171  $params = '&edit[' . $table . '][' . $this->id . ']=new';
1172  if ($table == 'pages_language_overlay') {
1173  $params .= '&overrideVals[pages_language_overlay][doktype]=' . (int)$this->pageRow['doktype'];
1174  }
1175  $icon = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1))
1176  . '" title="' . $lang->getLL('new', true) . '">' . $spriteIcon->render() . '</a>';
1177  }
1178  }
1179  // If the table can be edited, add link for editing ALL SHOWN fields for all listed records:
1180  if ($permsEdit && $this->table && is_array($currentIdList)) {
1181  $editIdList = implode(',', $currentIdList);
1182  if ($this->clipNumPane()) {
1183  $editIdList = '\'+editList(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($editIdList) . ')+\'';
1184  }
1185  $params = 'edit[' . $table . '][' . $editIdList . ']=edit&columnsOnly=' . implode(',', $this->fieldArray);
1186  // we need to build this uri differently, otherwise GeneralUtility::quoteJSvalue messes up the edit list function
1187  $onClick = BackendUtility::editOnClick('', '', -1);
1188  $onClickArray = explode('?', $onClick, 2);
1189  $lastElement = array_pop($onClickArray);
1190  array_push($onClickArray, $params . '&' . $lastElement);
1191  $onClick = implode('?', $onClickArray);
1192  $icon .= '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick)
1193  . '" title="' . $lang->getLL('editShownColumns', true) . '">'
1194  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1195  $icon = '<div class="btn-group" role="group">' . $icon . '</div>';
1196  }
1197  // Add an empty entry, so column count fits again after moving this into $icon
1198  $theData[$fCol] = '&nbsp;';
1199  }
1200  break;
1201  default:
1202  // Regular fields header:
1203  $theData[$fCol] = '';
1204 
1205  // Check if $fCol is really a field and get the label and remove the colons
1206  // at the end
1207  $sortLabel = BackendUtility::getItemLabel($table, $fCol);
1208  if ($sortLabel !== null) {
1209  $sortLabel = $lang->sL($sortLabel, true);
1210  $sortLabel = rtrim(trim($sortLabel), ':');
1211  } else {
1212  // No TCA field, only output the $fCol variable with square brackets []
1213  $sortLabel = htmlspecialchars($fCol);
1214  $sortLabel = '<i>[' . rtrim(trim($sortLabel), ':') . ']</i>';
1215  }
1216 
1217  if ($this->table && is_array($currentIdList)) {
1218  // If the numeric clipboard pads are selected, show duplicate sorting link:
1219  if ($this->clipNumPane()) {
1220  $theData[$fCol] .= '<a class="btn btn-default" href="' . htmlspecialchars($this->listURL('', '-1') . '&duplicateField=' . $fCol)
1221  . '" title="' . $lang->getLL('clip_duplicates', true) . '">'
1222  . $this->iconFactory->getIcon('actions-document-duplicates-select', Icon::SIZE_SMALL)->render() . '</a>';
1223  }
1224  // If the table can be edited, add link for editing THIS field for all
1225  // listed records:
1226  if ($this->isEditable($table) && $permsEdit && $GLOBALS['TCA'][$table]['columns'][$fCol]) {
1227  $editIdList = implode(',', $currentIdList);
1228  if ($this->clipNumPane()) {
1229  $editIdList = '\'+editList(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($editIdList) . ')+\'';
1230  }
1231  $params = 'edit[' . $table . '][' . $editIdList . ']=edit&columnsOnly=' . $fCol;
1232  // we need to build this uri differently, otherwise GeneralUtility::quoteJSvalue messes up the edit list function
1233  $onClick = BackendUtility::editOnClick('', '', -1);
1234  $onClickArray = explode('?', $onClick, 2);
1235  $lastElement = array_pop($onClickArray);
1236  array_push($onClickArray, $params . '&' . $lastElement);
1237  $onClick = implode('?', $onClickArray);
1238  $iTitle = sprintf($lang->getLL('editThisColumn'), $sortLabel);
1239  $theData[$fCol] .= '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick)
1240  . '" title="' . htmlspecialchars($iTitle) . '">'
1241  . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
1242  }
1243  if (strlen($theData[$fCol]) > 0) {
1244  $theData[$fCol] = '<div class="btn-group" role="group">' . $theData[$fCol] . '</div> ';
1245  }
1246  }
1247  $theData[$fCol] .= $this->addSortLink($sortLabel, $fCol, $table);
1248  }
1249  }
1256  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1257  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1258  $hookObject = GeneralUtility::getUserObj($classData);
1259  if (!$hookObject instanceof RecordListHookInterface) {
1260  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567855);
1261  }
1262  $theData = $hookObject->renderListHeader($table, $currentIdList, $theData, $this);
1263  }
1264  }
1265 
1266  // Create and return header table row:
1267  return '<thead>' . $this->addElement(1, $icon, $theData, '', '', '', 'th') . '</thead>';
1268  }
1269 
1276  protected function getPointerForPage($page)
1277  {
1278  return ($page - 1) * $this->iLimit;
1279  }
1280 
1287  protected function renderListNavigation($renderPart = 'top')
1288  {
1289  $totalPages = ceil($this->totalItems / $this->iLimit);
1290  // Show page selector if not all records fit into one page
1291  if ($totalPages <= 1) {
1292  return '';
1293  }
1294  $content = '';
1295  $listURL = $this->listURL('', $this->table);
1296  // 1 = first page
1297  // 0 = first element
1298  $currentPage = floor($this->firstElementNumber / $this->iLimit) + 1;
1299  // Compile first, previous, next, last and refresh buttons
1300  if ($currentPage > 1) {
1301  $labelFirst = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_common.xlf:first', true);
1302  $labelPrevious = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_common.xlf:previous', true);
1303  $first = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage(1) . '" title="' . $labelFirst . '">'
1304  . $this->iconFactory->getIcon('actions-view-paging-first', Icon::SIZE_SMALL)->render() . '</a></li>';
1305  $previous = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($currentPage - 1) . '" title="' . $labelPrevious . '">'
1306  . $this->iconFactory->getIcon('actions-view-paging-previous', Icon::SIZE_SMALL)->render() . '</a></li>';
1307  } else {
1308  $first = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-first', Icon::SIZE_SMALL)->render() . '</span></li>';
1309  $previous = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-previous', Icon::SIZE_SMALL)->render() . '</span></li>';
1310  }
1311  if ($currentPage < $totalPages) {
1312  $labelNext = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_common.xlf:next', true);
1313  $labelLast = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_common.xlf:last', true);
1314  $next = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($currentPage + 1) . '" title="' . $labelNext . '">'
1315  . $this->iconFactory->getIcon('actions-view-paging-next', Icon::SIZE_SMALL)->render() . '</a></li>';
1316  $last = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($totalPages) . '" title="' . $labelLast . '">'
1317  . $this->iconFactory->getIcon('actions-view-paging-last', Icon::SIZE_SMALL)->render() . '</a></li>';
1318  } else {
1319  $next = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-next', Icon::SIZE_SMALL)->render() . '</span></li>';
1320  $last = '<li class="disabled"><span>' . $this->iconFactory->getIcon('actions-view-paging-last', Icon::SIZE_SMALL)->render() . '</span></li>';
1321  }
1322  $reload = '<li><a href="#" onclick="document.dblistForm.action=' . GeneralUtility::quoteJSvalue($listURL
1323  . '&pointer=') . '+calculatePointer(document.getElementById(' . GeneralUtility::quoteJSvalue('jumpPage-' . $renderPart)
1324  . ').value); document.dblistForm.submit(); return true;" title="'
1325  . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_common.xlf:reload', true) . '">'
1326  . $this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)->render() . '</a></li>';
1327  if ($renderPart === 'top') {
1328  // Add js to traverse a page select input to a pointer value
1329  $content = '
1330 <script type="text/javascript">
1331 /*<![CDATA[*/
1332  function calculatePointer(page) {
1333  if (page > ' . $totalPages . ') {
1334  page = ' . $totalPages . ';
1335  }
1336  if (page < 1) {
1337  page = 1;
1338  }
1339  return (page - 1) * ' . $this->iLimit . ';
1340  }
1341 /*]]>*/
1342 </script>
1343 ';
1344  }
1345  $pageNumberInput = '
1346  <input type="number" min="1" max="' . $totalPages . '" value="' . $currentPage . '" size="3" class="form-control input-sm paginator-input" id="jumpPage-' . $renderPart . '" name="jumpPage-'
1347  . $renderPart . '" onkeyup="if (event.keyCode == 13) { document.dblistForm.action=' . GeneralUtility::quoteJSvalue($listURL
1348  . '&pointer=') . '+calculatePointer(this.value); document.dblistForm.submit(); } return true;" />
1349  ';
1350  $pageIndicatorText = sprintf(
1351  $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:pageIndicator'),
1352  $pageNumberInput,
1353  $totalPages
1354  );
1355  $pageIndicator = '<li><span>' . $pageIndicatorText . '</span></li>';
1356  if ($this->totalItems > $this->firstElementNumber + $this->iLimit) {
1357  $lastElementNumber = $this->firstElementNumber + $this->iLimit;
1358  } else {
1359  $lastElementNumber = $this->totalItems;
1360  }
1361  $rangeIndicator = '<li><span>' . sprintf($this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:rangeIndicator'), ($this->firstElementNumber + 1), $lastElementNumber) . '</span></li>';
1362 
1363  $titleColumn = $this->fieldArray[0];
1364  $data = [
1365  $titleColumn => $content . '
1366  <nav class="pagination-wrap">
1367  <ul class="pagination pagination-block">
1368  ' . $first . '
1369  ' . $previous . '
1370  ' . $rangeIndicator . '
1371  ' . $pageIndicator . '
1372  ' . $next . '
1373  ' . $last . '
1374  ' . $reload . '
1375  </ul>
1376  </nav>
1377  '
1378  ];
1379  return $this->addElement(1, '', $data);
1380  }
1381 
1382  /*********************************
1383  *
1384  * Rendering of various elements
1385  *
1386  *********************************/
1387 
1396  public function makeControl($table, $row)
1397  {
1398  $module = $this->getModule();
1399  $rowUid = $row['uid'];
1400  if (ExtensionManagementUtility::isLoaded('version') && isset($row['_ORIG_uid'])) {
1401  $rowUid = $row['_ORIG_uid'];
1402  }
1403  $cells = [
1404  'primary' => [],
1405  'secondary' => []
1406  ];
1407  // If the listed table is 'pages' we have to request the permission settings for each page:
1408  $localCalcPerms = 0;
1409  if ($table == 'pages') {
1410  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1411  }
1412  $permsEdit = $table === 'pages'
1413  && $this->getBackendUserAuthentication()->checkLanguageAccess(0)
1414  && $localCalcPerms & Permission::PAGE_EDIT
1415  || $table !== 'pages'
1416  && $this->calcPerms & Permission::CONTENT_EDIT
1417  && $this->getBackendUserAuthentication()->recordEditAccessInternals($table, $row);
1418  $permsEdit = $this->overlayEditLockPermissions($table, $row, $permsEdit);
1419  // "Show" link (only pages and tt_content elements)
1420  if ($table == 'pages' || $table == 'tt_content') {
1421  $viewAction = '<a class="btn btn-default" href="#" onclick="'
1422  . htmlspecialchars(
1424  ($table === 'tt_content' ? $this->id : $row['uid']),
1425  '',
1426  '',
1427  ($table === 'tt_content' ? '#c' . $row['uid'] : '')
1428  )
1429  ) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', true) . '">'
1430  . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
1431  $this->addActionToCellGroup($cells, $viewAction, 'view');
1432  }
1433  // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1434  if ($permsEdit) {
1435  $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1436  $iconIdentifier = 'actions-open';
1437  $overlayIdentifier = !$this->isEditable($table) ? 'overlay-readonly' : null;
1438  $editAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1))
1439  . '" title="' . $this->getLanguageService()->getLL('edit', true) . '">' . $this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL, $overlayIdentifier)->render() . '</a>';
1440  } else {
1441  $editAction = $this->spaceIcon;
1442  }
1443  $this->addActionToCellGroup($cells, $editAction, 'edit');
1444  // "Info": (All records)
1445  $onClick = 'top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . (int)$row['uid'] . '); return false;';
1446  $viewBigAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->getLL('showInfo', true) . '">'
1447  . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>';
1448  $this->addActionToCellGroup($cells, $viewBigAction, 'viewBig');
1449  // "Move" wizard link for pages/tt_content elements:
1450  if ($permsEdit && ($table === 'tt_content' || $table === 'pages')) {
1451  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('move_element') . '&table=' . $table . '&uid=' . $row['uid']) . ');';
1452  $linkTitleLL = $this->getLanguageService()->getLL('move_' . ($table === 'tt_content' ? 'record' : 'page'), true);
1453  $icon = ($table == 'pages' ? $this->iconFactory->getIcon('actions-page-move', Icon::SIZE_SMALL) : $this->iconFactory->getIcon('actions-document-move', Icon::SIZE_SMALL));
1454  $moveAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $linkTitleLL . '">' . $icon->render() . '</a>';
1455  $this->addActionToCellGroup($cells, $moveAction, 'move');
1456  }
1457  // If the table is NOT a read-only table, then show these links:
1458  if ($this->isEditable($table)) {
1459  // "Revert" link (history/undo)
1460  $moduleUrl = BackendUtility::getModuleUrl('record_history', ['element' => $table . ':' . $row['uid']]);
1461  $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue($moduleUrl) . ',\'#latest\');';
1462  $historyAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1463  . $this->getLanguageService()->getLL('history', true) . '">'
1464  . $this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL)->render() . '</a>';
1465  $this->addActionToCellGroup($cells, $historyAction, 'history');
1466  // Versioning:
1468  $vers = BackendUtility::selectVersionsOfRecord($table, $row['uid'], 'uid', $this->getBackendUserAuthentication()->workspace, false, $row);
1469  // If table can be versionized.
1470  if (is_array($vers)) {
1471  $href = BackendUtility::getModuleUrl('web_txversionM1', [
1472  'table' => $table, 'uid' => $row['uid']
1473  ]);
1474  $versionAction = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="'
1475  . $this->getLanguageService()->getLL('displayVersions', true) . '">'
1476  . $this->iconFactory->getIcon('actions-version-page-open', Icon::SIZE_SMALL)->render() . '</a>';
1477  $this->addActionToCellGroup($cells, $versionAction, 'version');
1478  }
1479  }
1480  // "Edit Perms" link:
1481  if ($table === 'pages' && $this->getBackendUserAuthentication()->check('modules', 'system_BeuserTxPermission') && ExtensionManagementUtility::isLoaded('beuser')) {
1482  $href = BackendUtility::getModuleUrl('system_BeuserTxPermission') . '&id=' . $row['uid'] . '&tx_beuser_system_beusertxpermission[action]=edit' . $this->makeReturnUrl();
1483  $permsAction = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="'
1484  . $this->getLanguageService()->getLL('permissions', true) . '">'
1485  . $this->iconFactory->getIcon('status-status-locked', Icon::SIZE_SMALL)->render() . '</a>';
1486  $this->addActionToCellGroup($cells, $permsAction, 'perms');
1487  }
1488  // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row
1489  // or if default values can depend on previous record):
1490  if (($GLOBALS['TCA'][$table]['ctrl']['sortby'] || $GLOBALS['TCA'][$table]['ctrl']['useColumnsForDefaultValues']) && $permsEdit) {
1491  if ($table !== 'pages' && $this->calcPerms & Permission::CONTENT_EDIT || $table === 'pages' && $this->calcPerms & Permission::PAGE_NEW) {
1492  if ($this->showNewRecLink($table)) {
1493  $params = '&edit[' . $table . '][' . -($row['_MOVE_PLH'] ? $row['_MOVE_PLH_uid'] : $row['uid']) . ']=new';
1494  $icon = ($table == 'pages' ? $this->iconFactory->getIcon('actions-page-new', Icon::SIZE_SMALL) : $this->iconFactory->getIcon('actions-add', Icon::SIZE_SMALL));
1495  $titleLabel = 'new';
1496  if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1497  $titleLabel .= ($table === 'pages' ? 'Page' : 'Record');
1498  }
1499  $newAction = '<a class="btn btn-default" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1))
1500  . '" title="' . htmlspecialchars($this->getLanguageService()->getLL($titleLabel)) . '">'
1501  . $icon->render() . '</a>';
1502  $this->addActionToCellGroup($cells, $newAction, 'new');
1503  }
1504  }
1505  }
1506  // "Up/Down" links
1507  if ($permsEdit && $GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$this->sortField && !$this->searchLevels) {
1508  if (isset($this->currentTable['prev'][$row['uid']])) {
1509  // Up
1510  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['prev'][$row['uid']];
1511  $moveUpAction = '<a class="btn btn-default" href="#" onclick="'
1512  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1513  . '" title="' . $this->getLanguageService()->getLL('moveUp', true) . '">'
1514  . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '</a>';
1515  } else {
1516  $moveUpAction = $this->spaceIcon;
1517  }
1518  $this->addActionToCellGroup($cells, $moveUpAction, 'moveUp');
1519 
1520  if ($this->currentTable['next'][$row['uid']]) {
1521  // Down
1522  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['next'][$row['uid']];
1523  $moveDownAction = '<a class="btn btn-default" href="#" onclick="'
1524  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1525  . '" title="' . $this->getLanguageService()->getLL('moveDown', true) . '">'
1526  . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '</a>';
1527  } else {
1528  $moveDownAction = $this->spaceIcon;
1529  }
1530  $this->addActionToCellGroup($cells, $moveDownAction, 'moveDown');
1531  }
1532  // "Hide/Unhide" links:
1533  $hiddenField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
1534 
1535  if (
1536  !empty($GLOBALS['TCA'][$table]['columns'][$hiddenField])
1537  && (empty($GLOBALS['TCA'][$table]['columns'][$hiddenField]['exclude'])
1538  || $this->getBackendUserAuthentication()->check('non_exclude_fields', $table . ':' . $hiddenField))
1539  ) {
1540  if (!$permsEdit || $this->isRecordCurrentBackendUser($table, $row)) {
1541  $hideAction = $this->spaceIcon;
1542  } else {
1543  $hideTitle = $this->getLanguageService()->getLL('hide' . ($table == 'pages' ? 'Page' : ''), true);
1544  $unhideTitle = $this->getLanguageService()->getLL('unHide' . ($table == 'pages' ? 'Page' : ''), true);
1545  if ($row[$hiddenField]) {
1546  $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=0';
1547  $hideAction = '<a class="btn btn-default t3js-record-hide" data-state="hidden" href="#"'
1548  . ' data-params="' . htmlspecialchars($params) . '"'
1549  . ' title="' . $unhideTitle . '"'
1550  . ' data-toggle-title="' . $hideTitle . '">'
1551  . $this->iconFactory->getIcon('actions-edit-unhide', Icon::SIZE_SMALL)->render() . '</a>';
1552  } else {
1553  $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=1';
1554  $hideAction = '<a class="btn btn-default t3js-record-hide" data-state="visible" href="#"'
1555  . ' data-params="' . htmlspecialchars($params) . '"'
1556  . ' title="' . $hideTitle . '"'
1557  . ' data-toggle-title="' . $unhideTitle . '">'
1558  . $this->iconFactory->getIcon('actions-edit-hide', Icon::SIZE_SMALL)->render() . '</a>';
1559  }
1560  }
1561  $this->addActionToCellGroup($cells, $hideAction, 'hide');
1562  }
1563  // "Delete" link:
1564  $disableDeleteTS = $this->getBackendUserAuthentication()->getTSConfig('options.disableDelete');
1565  $disableDelete = (bool) trim(isset($disableDeleteTS['properties'][$table]) ? $disableDeleteTS['properties'][$table] : $disableDeleteTS['value']);
1566  if ($permsEdit && !$disableDelete && ($table === 'pages' && $localCalcPerms & Permission::PAGE_DELETE || $table !== 'pages' && $this->calcPerms & Permission::CONTENT_EDIT)) {
1567  // Check if the record version is in "deleted" state, because that will switch the action to "restore"
1568  if ($this->getBackendUserAuthentication()->workspace > 0 && isset($row['t3ver_state']) && (int)$row['t3ver_state'] === 2) {
1569  $actionName = 'restore';
1570  $refCountMsg = '';
1571  } else {
1572  $actionName = 'delete';
1573  $refCountMsg = BackendUtility::referenceCount(
1574  $table,
1575  $row['uid'],
1576  ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToRecord'),
1577  $this->getReferenceCount($table, $row['uid'])) . BackendUtility::translationCount($table, $row['uid'],
1578  ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.translationsOfRecord')
1579  );
1580  }
1581 
1582  if ($this->isRecordCurrentBackendUser($table, $row)) {
1583  $deleteAction = $this->spaceIcon;
1584  } else {
1585  $titleOrig = BackendUtility::getRecordTitle($table, $row, false, true);
1586  $title = GeneralUtility::slashJS(GeneralUtility::fixed_lgd_cs($titleOrig, $this->fixedL), true);
1587  $warningText = $this->getLanguageService()->getLL($actionName . 'Warning') . ' "' . $title . '" ' . '[' . $table . ':' . $row['uid'] . ']' . $refCountMsg;
1588 
1589  $params = 'cmd[' . $table . '][' . $row['uid'] . '][delete]=1';
1590  $icon = $this->iconFactory->getIcon('actions-edit-' . $actionName, Icon::SIZE_SMALL)->render();
1591  $linkTitle = $this->getLanguageService()->getLL($actionName, true);
1592  $deleteAction = '<a class="btn btn-default t3js-record-delete" href="#" '
1593  . ' data-l10parent="' . htmlspecialchars($row['l10n_parent']) . '"'
1594  . ' data-params="' . htmlspecialchars($params) . '" data-title="' . htmlspecialchars($titleOrig) . '"'
1595  . ' data-message="' . htmlspecialchars($warningText) . '" title="' . $linkTitle . '"'
1596  . '>' . $icon . '</a>';
1597  }
1598  } else {
1599  $deleteAction = $this->spaceIcon;
1600  }
1601  $this->addActionToCellGroup($cells, $deleteAction, 'delete');
1602  // "Levels" links: Moving pages into new levels...
1603  if ($permsEdit && $table == 'pages' && !$this->searchLevels) {
1604  // Up (Paste as the page right after the current parent page)
1605  if ($this->calcPerms & Permission::PAGE_NEW) {
1606  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . -$this->id;
1607  $moveLeftAction = '<a class="btn btn-default" href="#" onclick="'
1608  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1609  . '" title="' . $this->getLanguageService()->getLL('prevLevel', true) . '">'
1610  . $this->iconFactory->getIcon('actions-move-left', Icon::SIZE_SMALL)->render() . '</a>';
1611  $this->addActionToCellGroup($cells, $moveLeftAction, 'moveLeft');
1612  }
1613  // Down (Paste as subpage to the page right above)
1614  if ($this->currentTable['prevUid'][$row['uid']]) {
1615  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $this->currentTable['prevUid'][$row['uid']]));
1616  if ($localCalcPerms & Permission::PAGE_NEW) {
1617  $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['prevUid'][$row['uid']];
1618  $moveRightAction = '<a class="btn btn-default" href="#" onclick="'
1619  . htmlspecialchars('return jumpToUrl(' . BackendUtility::getLinkToDataHandlerAction($params, -1) . ');')
1620  . '" title="' . $this->getLanguageService()->getLL('nextLevel', true) . '">'
1621  . $this->iconFactory->getIcon('actions-move-right', Icon::SIZE_SMALL)->render() . '</a>';
1622  } else {
1623  $moveRightAction = $this->spaceIcon;
1624  }
1625  } else {
1626  $moveRightAction = $this->spaceIcon;
1627  }
1628  $this->addActionToCellGroup($cells, $moveRightAction, 'moveRight');
1629  }
1630  }
1634  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
1635  $stat = '';
1636  $_params = [$table, $row['uid']];
1637  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
1638  $stat .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1639  }
1640  $this->addActionToCellGroup($cells, $stat, 'stat');
1641  }
1649  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1650  // for compatibility reason, we move all icons to the rootlevel
1651  // before calling the hooks
1652  foreach ($cells as $section => $actions) {
1653  foreach ($actions as $actionKey => $action) {
1654  $cells[$actionKey] = $action;
1655  }
1656  }
1657  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1658  $hookObject = GeneralUtility::getUserObj($classData);
1659  if (!$hookObject instanceof RecordListHookInterface) {
1660  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567840);
1661  }
1662  $cells = $hookObject->makeControl($table, $row, $cells, $this);
1663  }
1664  // now sort icons again into primary and secondary sections
1665  // after all hooks are processed
1666  $hookCells = $cells;
1667  foreach ($hookCells as $key => $value) {
1668  if ($key === 'primary' || $key === 'secondary') {
1669  continue;
1670  }
1671  $this->addActionToCellGroup($cells, $value, $key);
1672  }
1673  }
1674  $output = '<!-- CONTROL PANEL: ' . $table . ':' . $row['uid'] . ' -->';
1675  foreach ($cells as $classification => $actions) {
1676  $visibilityClass = ($classification !== 'primary' && !$module->MOD_SETTINGS['bigControlPanel'] ? 'collapsed' : 'expanded');
1677  if ($visibilityClass === 'collapsed') {
1678  $cellOutput = '';
1679  foreach ($actions as $action) {
1680  $cellOutput .= $action;
1681  }
1682  $output .= ' <div class="btn-group">' .
1683  '<span id="actions_' . $table . '_' . $row['uid'] . '" class="btn-group collapse collapse-horizontal width">' . $cellOutput . '</span>' .
1684  '<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>' .
1685  '</div>';
1686  } else {
1687  $output .= ' <div class="btn-group" role="group">' . implode('', $actions) . '</div>';
1688  }
1689  }
1690  return $output;
1691  }
1692 
1701  public function makeClip($table, $row)
1702  {
1703  // Return blank, if disabled:
1704  if (!$this->getModule()->MOD_SETTINGS['clipBoard']) {
1705  return '';
1706  }
1707  $cells = [];
1708  $cells['pasteAfter'] = ($cells['pasteInto'] = $this->spaceIcon);
1709  //enables to hide the copy, cut and paste icons for localized records - doesn't make much sense to perform these options for them
1710  $isL10nOverlay = $this->localizationView && $table != 'pages_language_overlay' && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0;
1711  // Return blank, if disabled:
1712  // Whether a numeric clipboard pad is active or the normal pad we will see different content of the panel:
1713  // For the "Normal" pad:
1714  if ($this->clipObj->current === 'normal') {
1715  // Show copy/cut icons:
1716  $isSel = (string)$this->clipObj->isSelected($table, $row['uid']);
1717  if ($isL10nOverlay || !$this->overlayEditLockPermissions($table, $row)) {
1718  $cells['copy'] = $this->spaceIcon;
1719  $cells['cut'] = $this->spaceIcon;
1720  } else {
1721  $copyIcon = $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL);
1722  $cutIcon = $this->iconFactory->getIcon('actions-edit-cut', Icon::SIZE_SMALL);
1723 
1724  if ($isSel === 'copy') {
1725  $copyIcon = $this->iconFactory->getIcon('actions-edit-copy-release', Icon::SIZE_SMALL);
1726  } elseif ($isSel === 'cut') {
1727  $cutIcon = $this->iconFactory->getIcon('actions-edit-cut-release', Icon::SIZE_SMALL);
1728  }
1729 
1730  $cells['copy'] = '<a class="btn btn-default" href="#" onclick="'
1731  . htmlspecialchars('return jumpSelf(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $row['uid'], 1, ($isSel === 'copy'), ['returnUrl' => ''])) . ');')
1732  . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.copy', true) . '">'
1733  . $copyIcon->render() . '</a>';
1734 
1735  // Check permission to cut page or content
1736  if ($table == 'pages') {
1737  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1738  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
1739  } else {
1740  $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
1741  }
1742  $permsEdit = $this->overlayEditLockPermissions($table, $row, $permsEdit);
1743 
1744  // If the listed table is 'pages' we have to request the permission settings for each page:
1745  if ($table == 'pages') {
1746  if ($permsEdit) {
1747  $cells['cut'] = '<a class="btn btn-default" href="#" onclick="'
1748  . htmlspecialchars('return jumpSelf(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $row['uid'], 0, ($isSel === 'cut'), ['returnUrl' => ''])) . ');')
1749  . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.cut', true) . '">'
1750  . $cutIcon->render() . '</a>';
1751  } else {
1752  $cells['cut'] = $this->spaceIcon;
1753  }
1754  } else {
1755  if ($table !== 'pages' && $this->calcPerms & Permission::CONTENT_EDIT) {
1756  $cells['cut'] = '<a class="btn btn-default" href="#" onclick="'
1757  . htmlspecialchars('return jumpSelf(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $row['uid'], 0, ($isSel === 'cut'), ['returnUrl' => ''])) . ');')
1758  . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.cut', true) . '">'
1759  . $cutIcon->render() . '</a>';
1760  } else {
1761  $cells['cut'] = $this->spaceIcon;
1762  }
1763  }
1764  }
1765  } else {
1766  // For the numeric clipboard pads (showing checkboxes where one can select elements on/off)
1767  // Setting name of the element in ->CBnames array:
1768  $n = $table . '|' . $row['uid'];
1769  $this->CBnames[] = $n;
1770  // Check if the current element is selected and if so, prepare to set the checkbox as selected:
1771  $checked = $this->clipObj->isSelected($table, $row['uid']) ? 'checked="checked" ' : '';
1772  // If the "duplicateField" value is set then select all elements which are duplicates...
1773  if ($this->duplicateField && isset($row[$this->duplicateField])) {
1774  $checked = '';
1775  if (in_array($row[$this->duplicateField], $this->duplicateStack)) {
1776  $checked = 'checked="checked" ';
1777  }
1778  $this->duplicateStack[] = $row[$this->duplicateField];
1779  }
1780  // Adding the checkbox to the panel:
1781  $cells['select'] = $isL10nOverlay
1782  ? $this->spaceIcon
1783  : '<input type="hidden" name="CBH[' . $n . ']" value="0" /><label class="btn btn-default btn-checkbox"><input type="checkbox"'
1784  . ' name="CBC[' . $n . ']" value="1" ' . $checked . '/><span class="t3-icon fa"></span></label>';
1785  }
1786  // Now, looking for selected elements from the current table:
1787  $elFromTable = $this->clipObj->elFromTable($table);
1788  if (!empty($elFromTable) && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1789  // IF elements are found, they can be individually ordered and are not locked by editlock, then add a "paste after" icon:
1790  $cells['pasteAfter'] = $isL10nOverlay || !$this->overlayEditLockPermissions($table, $row)
1791  ? $this->spaceIcon
1792  : '<a class="btn btn-default t3js-modal-trigger"'
1793  . ' href="' . htmlspecialchars($this->clipObj->pasteUrl($table, -$row['uid'])) . '"'
1794  . ' title="' . $this->getLanguageService()->getLL('clip_pasteAfter', true) . '"'
1795  . ' data-title="' . $this->getLanguageService()->getLL('clip_pasteAfter', true) . '"'
1796  . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText($table, $row, 'after', $elFromTable)) . '"'
1797  . ' data-severity="warning">'
1798  . $this->iconFactory->getIcon('actions-document-paste-after', Icon::SIZE_SMALL)->render() . '</a>';
1799  }
1800  // Now, looking for elements in general:
1801  $elFromTable = $this->clipObj->elFromTable('');
1802  if ($table == 'pages' && !empty($elFromTable)) {
1803  $cells['pasteInto'] = '<a class="btn btn-default t3js-modal-trigger"'
1804  . ' href="' . htmlspecialchars($this->clipObj->pasteUrl('', $row['uid'])) . '"'
1805  . ' title="' . $this->getLanguageService()->getLL('clip_pasteInto', true) . '"'
1806  . ' data-title="' . $this->getLanguageService()->getLL('clip_pasteInto', true) . '"'
1807  . ' data-content="' . htmlspecialchars($this->clipObj->confirmMsgText($table, $row, 'into', $elFromTable)) . '"'
1808  . ' data-severity="warning">'
1809  . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render() . '</a>';
1810  }
1818  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1819  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1820  $hookObject = GeneralUtility::getUserObj($classData);
1821  if (!$hookObject instanceof RecordListHookInterface) {
1822  throw new \UnexpectedValueException($classData . ' must implement interface ' . RecordListHookInterface::class, 1195567845);
1823  }
1824  $cells = $hookObject->makeClip($table, $row, $cells, $this);
1825  }
1826  }
1827  // Compile items into a DIV-element:
1828  return '<!-- CLIPBOARD PANEL: ' . $table . ':' . $row['uid'] . ' -->
1829  <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1830  }
1831 
1840  protected function createReferenceHtml($tableName, $uid)
1841  {
1842  $db = $this->getDatabaseConnection();
1843  $referenceCount = $db->exec_SELECTcountRows(
1844  '*',
1845  'sys_refindex',
1846  'ref_table = ' . $db->fullQuoteStr($tableName, 'sys_refindex') .
1847  ' AND ref_uid = ' . $uid . ' AND deleted = 0'
1848  );
1850  }
1851 
1859  public function makeLocalizationPanel($table, $row)
1860  {
1861  $out = [
1862  0 => '',
1863  1 => ''
1864  ];
1865  // Reset translations
1866  $this->translations = [];
1867 
1868  // Language title and icon:
1869  $out[0] = $this->languageFlag($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
1870  // Guard clause so we can quickly return if a record is localized to "all languages"
1871  // It should only be possible to localize a record off default (uid 0)
1872  // Reasoning: The Parent is for ALL languages... why overlay with a localization?
1873  if ((int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] === -1) {
1874  return $out;
1875  }
1876 
1877  $translations = $this->translateTools->translationInfo($table, $row['uid'], 0, $row, $this->selFieldList);
1878  if (is_array($translations)) {
1879  $this->translations = $translations['translations'];
1880  // Traverse page translations and add icon for each language that does NOT yet exist:
1881  $lNew = '';
1882  foreach ($this->pageOverlays as $lUid_OnPage => $lsysRec) {
1883  if ($this->isEditable($table) && !isset($translations['translations'][$lUid_OnPage]) && $this->getBackendUserAuthentication()->checkLanguageAccess($lUid_OnPage)) {
1884  $url = $this->listURL();
1886  '&cmd[' . $table . '][' . $row['uid'] . '][localize]=' . $lUid_OnPage,
1887  $url . '&justLocalized=' . rawurlencode($table . ':' . $row['uid'] . ':' . $lUid_OnPage)
1888  );
1889  $language = BackendUtility::getRecord('sys_language', $lUid_OnPage, 'title');
1890  if ($this->languageIconTitles[$lUid_OnPage]['flagIcon']) {
1891  $lC = $this->iconFactory->getIcon($this->languageIconTitles[$lUid_OnPage]['flagIcon'], Icon::SIZE_SMALL)->render();
1892  } else {
1893  $lC = $this->languageIconTitles[$lUid_OnPage]['title'];
1894  }
1895  $lC = '<a href="' . htmlspecialchars($href) . '" title="'
1896  . htmlspecialchars($language['title']) . '" class="btn btn-default">' . $lC . '</a> ';
1897  $lNew .= $lC;
1898  }
1899  }
1900  if ($lNew) {
1901  $out[1] .= $lNew;
1902  }
1903  } elseif ($row['l18n_parent']) {
1904  $out[0] = '&nbsp;&nbsp;&nbsp;&nbsp;' . $out[0];
1905  }
1906  return $out;
1907  }
1908 
1916  public function fieldSelectBox($table, $formFields = true)
1917  {
1918  $lang = $this->getLanguageService();
1919  // Init:
1920  $formElements = ['', ''];
1921  if ($formFields) {
1922  $formElements = ['<form action="' . htmlspecialchars($this->listURL()) . '" method="post" name="fieldSelectBox">', '</form>'];
1923  }
1924  // Load already selected fields, if any:
1925  $setFields = is_array($this->setFields[$table]) ? $this->setFields[$table] : [];
1926  // Request fields from table:
1927  $fields = $this->makeFieldList($table, false, true);
1928  // Add pseudo "control" fields
1929  $fields[] = '_PATH_';
1930  $fields[] = '_REF_';
1931  $fields[] = '_LOCALIZATION_';
1932  $fields[] = '_CONTROL_';
1933  $fields[] = '_CLIPBOARD_';
1934  // Create a checkbox for each field:
1935  $checkboxes = [];
1936  $checkAllChecked = true;
1937  foreach ($fields as $fieldName) {
1938  // Determine, if checkbox should be checked
1939  if (in_array($fieldName, $setFields, true) || $fieldName === $this->fieldArray[0]) {
1940  $checked = ' checked="checked"';
1941  } else {
1942  $checkAllChecked = false;
1943  $checked = '';
1944  }
1945  // Field label
1946  $fieldLabel = is_array($GLOBALS['TCA'][$table]['columns'][$fieldName])
1947  ? rtrim($lang->sL($GLOBALS['TCA'][$table]['columns'][$fieldName]['label']), ':')
1948  : '';
1949  $checkboxes[] = '<tr><td class="col-checkbox"><input type="checkbox" id="check-' . $fieldName . '" name="displayFields['
1950  . $table . '][]" value="' . $fieldName . '" ' . $checked
1951  . ($fieldName === $this->fieldArray[0] ? ' disabled="disabled"' : '') . '></td><td class="col-title">'
1952  . '<label class="label-block" for="check-' . $fieldName . '">' . htmlspecialchars($fieldLabel) . ' <span class="text-muted text-monospace">[' . htmlspecialchars($fieldName) . ']</span></label></td></tr>';
1953  }
1954  // Table with the field selector::
1955  $content = $formElements[0] . '
1956  <input type="hidden" name="displayFields[' . $table . '][]" value="">
1957  <div class="table-fit table-scrollable">
1958  <table border="0" cellpadding="0" cellspacing="0" class="table table-transparent table-hover">
1959  <thead>
1960  <tr>
1961  <th class="col-checkbox" colspan="2">
1962  <input type="checkbox" class="checkbox checkAll" ' . ($checkAllChecked ? ' checked="checked"' : '') . '>
1963  </th>
1964  </tr>
1965  </thead>
1966  <tbody>
1967  ' . implode('', $checkboxes) . '
1968  </tbody>
1969  </table>
1970  </div>
1971  <input type="submit" name="search" class="btn btn-default" value="'
1972  . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.setFields', true) . '"/>
1973  ' . $formElements[1];
1974  return '<div class="fieldSelectBox">' . $content . '</div>';
1975  }
1976 
1977  /*********************************
1978  *
1979  * Helper functions
1980  *
1981  *********************************/
1994  public function linkClipboardHeaderIcon($string, $table, $cmd, $warning = '', $title = '')
1995  {
1996  $jsCode = 'document.dblistForm.cmd.value=' . GeneralUtility::quoteJSvalue($cmd)
1997  . ';document.dblistForm.cmd_table.value='
1999  . ';document.dblistForm.submit();';
2000 
2001  $attributes = [];
2002  if ($title !== '') {
2003  $attributes['title'] = $title;
2004  }
2005  if ($warning) {
2006  $attributes['class'] = 'btn btn-default t3js-modal-trigger';
2007  $attributes['data-href'] = 'javascript:' . $jsCode;
2008  $attributes['data-severity'] = 'warning';
2009  $attributes['data-title'] = $title;
2010  $attributes['data-content'] = $warning;
2011  } else {
2012  $attributes['class'] = 'btn btn-default';
2013  $attributes['onclick'] = $jsCode . 'return false;';
2014  }
2015 
2016  $attributesString = '';
2017  foreach ($attributes as $key => $value) {
2018  $attributesString .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
2019  }
2020  return '<a href="#" ' . $attributesString . '>' . $string . '</a>';
2021  }
2022 
2028  public function clipNumPane()
2029  {
2030  return in_array('_CLIPBOARD_', $this->fieldArray) && $this->clipObj->current != 'normal';
2031  }
2032 
2043  public function addSortLink($code, $field, $table)
2044  {
2045  // Certain circumstances just return string right away (no links):
2046  if ($field == '_CONTROL_' || $field == '_LOCALIZATION_' || $field == '_CLIPBOARD_' || $field == '_REF_' || $this->disableSingleTableView) {
2047  return $code;
2048  }
2049  // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!)
2050  if ($field == '_PATH_') {
2051  $field = 'pid';
2052  }
2053  // Create the sort link:
2054  $sortUrl = $this->listURL('', '-1', 'sortField,sortRev,table,firstElementNumber') . '&table=' . $table
2055  . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1);
2056  $sortArrow = $this->sortField === $field
2057  ? $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render()
2058  : '';
2059  // Return linked field:
2060  return '<a href="' . htmlspecialchars($sortUrl) . '">' . $code . $sortArrow . '</a>';
2061  }
2062 
2071  public function recPath($pid)
2072  {
2073  if (!isset($this->recPath_cache[$pid])) {
2074  $this->recPath_cache[$pid] = BackendUtility::getRecordPath($pid, $this->perms_clause, 20);
2075  }
2076  return $this->recPath_cache[$pid];
2077  }
2078 
2086  public function showNewRecLink($table)
2087  {
2088  // No deny/allow tables are set:
2089  if (empty($this->allowedNewTables) && empty($this->deniedNewTables)) {
2090  return true;
2091  }
2092  return !in_array($table, $this->deniedNewTables)
2093  && (empty($this->allowedNewTables) || in_array($table, $this->allowedNewTables));
2094  }
2095 
2103  public function makeReturnUrl()
2104  {
2105  return '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
2106  }
2107 
2108  /************************************
2109  *
2110  * CSV related functions
2111  *
2112  ************************************/
2118  protected function initCSV()
2119  {
2120  $this->addHeaderRowToCSV();
2121  }
2122 
2128  protected function addHeaderRowToCSV()
2129  {
2130  // Add header row, control fields will be reduced inside addToCSV()
2131  $this->addToCSV(array_combine($this->fieldArray, $this->fieldArray));
2132  }
2133 
2140  protected function addToCSV(array $row = [])
2141  {
2142  $rowReducedByControlFields = self::removeControlFieldsFromFieldRow($row);
2143  // Get an field array without control fields but in the expected order
2144  $fieldArray = array_intersect_key(array_flip($this->fieldArray), $rowReducedByControlFields);
2145  // Overwrite fieldArray to keep the order with an array of needed fields
2146  $rowReducedToSelectedColumns = array_replace($fieldArray, array_intersect_key($rowReducedByControlFields, $fieldArray));
2147  $this->setCsvRow($rowReducedToSelectedColumns);
2148  }
2149 
2156  protected static function removeControlFieldsFromFieldRow(array $row = [])
2157  {
2158  // Possible control fields in a list row
2159  $controlFields = [
2160  '_PATH_',
2161  '_REF_',
2162  '_CONTROL_',
2163  '_CLIPBOARD_',
2164  '_LOCALIZATION_',
2165  '_LOCALIZATION_b'
2166  ];
2167  return array_diff_key($row, array_flip($controlFields));
2168  }
2169 
2176  public function setCsvRow($csvRow)
2177  {
2178  $this->csvLines[] = GeneralUtility::csvValues($csvRow);
2179  }
2180 
2188  public function outputCSV($prefix)
2189  {
2190  // Setting filename:
2191  $filename = $prefix . '_' . date('dmy-Hi') . '.csv';
2192  // Creating output header:
2193  header('Content-Type: application/octet-stream');
2194  header('Content-Disposition: attachment; filename=' . $filename);
2195  // Cache-Control header is needed here to solve an issue with browser IE and
2196  // versions lower than 9. See for more information: http://support.microsoft.com/kb/323308
2197  header("Cache-Control: ''");
2198  // Printing the content of the CSV lines:
2199  echo implode(CRLF, $this->csvLines);
2200  // Exits:
2201  die;
2202  }
2203 
2211  public function addActionToCellGroup(&$cells, $action, $actionKey)
2212  {
2213  $cellsMap = [
2214  'primary' => [
2215  'view', 'edit', 'hide', 'delete', 'stat'
2216  ],
2217  'secondary' => [
2218  'viewBig', 'history', 'perms', 'new', 'move', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'version'
2219  ]
2220  ];
2221  $classification = in_array($actionKey, $cellsMap['primary']) ? 'primary' : 'secondary';
2222  $cells[$classification][$actionKey] = $action;
2223  unset($cells[$actionKey]);
2224  }
2225 
2233  protected function isRecordCurrentBackendUser($table, $row)
2234  {
2235  return $table === 'be_users' && (int)$row['uid'] === $this->getBackendUserAuthentication()->user['uid'];
2236  }
2237 
2241  public function setIsEditable($isEditable)
2242  {
2243  $this->editable = $isEditable;
2244  }
2245 
2251  public function isEditable($table)
2252  {
2253  return $GLOBALS['TCA'][$table]['ctrl']['readOnly'] || $this->editable;
2254  }
2255 
2266  protected function overlayEditLockPermissions($table, $row = [], $editPermission = true)
2267  {
2268  if ($editPermission && !$this->getBackendUserAuthentication()->isAdmin()) {
2269  // If no $row is submitted we only check for general edit lock of current page (except for table "pages")
2270  if (empty($row)) {
2271  return $table === 'pages' ? true : !$this->pageRow['editlock'];
2272  }
2273  if (($table === 'pages' && $row['editlock']) || ($table !== 'pages' && $this->pageRow['editlock'])) {
2274  $editPermission = false;
2275  } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['editlock']) && $row[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) {
2276  $editPermission = false;
2277  }
2278  }
2279  return $editPermission;
2280  }
2281 
2288  protected function editLockPermissions()
2289  {
2290  return $this->getBackendUserAuthentication()->isAdmin() || !$this->pageRow['editlock'];
2291  }
2292 
2296  protected function getDatabaseConnection()
2297  {
2298  return $GLOBALS['TYPO3_DB'];
2299  }
2300 
2304  protected function getModule()
2305  {
2306  return $GLOBALS['SOBE'];
2307  }
2308 
2312  protected function getDocumentTemplate()
2313  {
2314  return $GLOBALS['TBE_TEMPLATE'];
2315  }
2316 }
static translationCount($table, $ref, $msg='')
overlayEditLockPermissions($table, $row=[], $editPermission=true)
static editOnClick($params, $_='', $requestUri='')
static getItemLabel($table, $col, $printAllWrap='')
static getProcessedValueExtra($table, $fN, $fV, $fixed_lgd_chars=0, $uid=0, $forceResult=true, $pid=0)
static csvValues(array $row, $delim=',', $quote='"')
static getRecordToolTip(array $row, $table='pages')
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static callUserFunction($funcName, &$params, &$ref, $checkPrefix='', $errorMode=0)
static linkThisUrl($url, array $getParams=[])
addElement($h, $icon, $data, $rowParams='', $_='', $_2='', $colType='td')
static referenceCount($table, $ref, $msg='', $count=null)
static cshItem($table, $field, $_='', $wrap='')
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static getRecordRaw($table, $where='', $fields=' *')
languageFlag($sys_language_uid, $addAsAdditionalText=true)
static viewOnClick($pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
makeFieldList($table, $dontCheckUser=false, $addDateFields=false)
static fixed_lgd_cs($string, $chars, $appendString='...')
$uid
Definition: server.php:38
static getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
renderListRow($table, $row, $cc, $titleCol, $thumbsCol, $indent=0)
static wrapClickMenuOnIcon( $content, $table, $uid=0, $listFrame=true, $addParams='', $enDisItems='', $returnTagParameters=false)
generateReferenceToolTip($references, $launchViewParameter='')
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static selectVersionsOfRecord($table, $uid, $fields=' *', $workspace=0, $includeDeletedRecords=false, $row=null)
static slashJS($string, $extended=false, $char='\'')
static getLinkToDataHandlerAction($parameters, $redirectUrl='')
static deleteClause($table, $tableAlias='')
linkClipboardHeaderIcon($string, $table, $cmd, $warning='', $title='')