‪TYPO3CMS  ‪main
DatabaseRecordList.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Http\Message\ServerRequestInterface;
34 use TYPO3\CMS\Backend\Utility\BackendUtility;
41 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
48 use TYPO3\CMS\Core\Imaging\IconSize;
62 
68 {
69  // *********
70  // External:
71  // *********
72 
79  public ‪$allowedNewTables = [];
80 
87  public ‪$deniedNewTables = [];
88 
94  public ‪$showClipboardActions = false;
95 
101  public ‪$noControlPanels = false;
102 
108  public ‪$clickMenuEnabled = true;
109 
113  protected string ‪$spaceIcon;
114 
120  public ‪$disableSingleTableView = false;
121 
122  // *********
123  // Internal:
124  // *********
125 
131  public ‪$pageRow = [];
132 
138  public string ‪$hideTranslations = '';
139 
145  protected array ‪$recPath_cache = [];
146 
150  public string ‪$sortField = '';
151 
155  protected ?‪ModuleData ‪$moduleData = null;
156 
162  public ‪$id;
163 
169  protected array ‪$duplicateStack = [];
170 
177 
181  public ‪$modTSconfig;
182 
186  protected array ‪$addElement_tdCssClass = [];
187 
193  public ‪$currentTable = [];
194 
198  public int ‪$showLimit = 0;
199 
205  public ‪$fieldArray = [];
206 
212  public ‪$hideTables = '';
213 
219  public ‪$perms_clause = '';
220 
226  public ‪$returnUrl = '';
227 
233  public ‪$table = '';
234 
240  public ‪$calcPerms;
241 
247  public ‪$clickTitleMode = '';
248 
252  protected int ‪$searchLevels = 0;
253 
259  public array ‪$tableTSconfigOverTCA = [];
260 
266  public array ‪$setFields = [];
267 
273  protected ‪$page = 0;
274 
280  public ‪$searchString = '';
281 
287  public bool ‪$sortRev = false;
288 
292  protected string ‪$duplicateField = '';
293 
299  public ‪$tableList = '';
300 
306  public ‪$clipObj;
307 
313  public ‪$CBnames = [];
314 
320  public bool ‪$displayColumnSelector = true;
321 
327  public bool ‪$displayRecordDownload = true;
328 
334  protected array ‪$referenceCount = [];
335 
339  protected bool ‪$editable = true;
340 
351  protected array ‪$tableDisplayOrder = [];
352 
356  protected array ‪$overridePageIdList = [];
357 
362  protected array ‪$overrideUrlParameters = [];
363 
367  protected array ‪$currentLink = [];
368 
372  protected bool ‪$showOnlyTranslatedRecords = false;
373 
384  protected array ‪$possibleTranslations = [];
385 
391  protected array ‪$languagesAllowedForUser = [];
392 
396  protected array ‪$pagePermsCache = [];
397  protected array ‪$showLocalizeColumn = [];
398 
399  protected ServerRequestInterface ‪$request;
400 
401  public function ‪__construct(
402  protected readonly ‪IconFactory $iconFactory,
403  protected readonly ‪UriBuilder $uriBuilder,
404  protected readonly ‪TranslationConfigurationProvider $translateTools,
405  protected readonly EventDispatcherInterface $eventDispatcher,
406  protected readonly ‪BackendViewFactory $backendViewFactory,
407  protected readonly ‪ModuleProvider $moduleProvider,
408  ) {
409  $this->calcPerms = new ‪Permission();
410  $this->spaceIcon = '<span class="btn btn-default disabled" aria-hidden="true">' . $this->iconFactory->getIcon('empty-empty', IconSize::SMALL)->render() . '</span>';
411  }
412 
413  public function ‪setRequest(ServerRequestInterface ‪$request)
414  {
415  $this->request = ‪$request;
416  }
417 
422  public function ‪getColumnsToRender(string ‪$table, bool $includeMetaColumns): array
423  {
424  $titleCol = ‪$GLOBALS['TCA'][‪$table]['ctrl']['label'] ?? '';
425 
426  // Setting fields selected in columnSelectorBox (saved in uc)
427  $rowListArray = [];
428  if (is_array($this->setFields[‪$table] ?? null)) {
429  $rowListArray = BackendUtility::getAllowedFieldsForTable(‪$table);
430  if ($includeMetaColumns) {
431  $rowListArray[] = '_PATH_';
432  $rowListArray[] = '_REF_';
433  }
434  $rowListArray = array_intersect($rowListArray, $this->setFields[‪$table]);
435  }
436  // if no columns have been specified, show description (if configured)
437  if (!empty(‪$GLOBALS['TCA'][‪$table]['ctrl']['descriptionColumn']) && empty($rowListArray)) {
438  $rowListArray[] = ‪$GLOBALS['TCA'][‪$table]['ctrl']['descriptionColumn'];
439  }
440 
441  // Initialize columns to select
442  $columnsToSelect = [$titleCol];
443  if ($includeMetaColumns) {
444  // If meta columns are enabled, add the record icon
445  array_unshift($columnsToSelect, 'icon');
446 
447  if ($this->noControlPanels === false) {
448  // Add _SELECTOR_ as first item in case control panels are not disabled
449  array_unshift($columnsToSelect, '_SELECTOR_');
450 
451  // Control-Panel
452  $columnsToSelect[] = '_CONTROL_';
453  }
454  // Path
455  if (!in_array('_PATH_', $rowListArray, true) && $this->searchLevels) {
456  $columnsToSelect[] = '_PATH_';
457  }
458  // Localization
459  if (BackendUtility::isTableLocalizable(‪$table)) {
460  $columnsToSelect[] = '_LOCALIZATION_';
461  // Do not show the "Localize to:" field when only translated records should be shown
462  if (!$this->‪showOnlyTranslatedRecords) {
463  $columnsToSelect[] = '_LOCALIZATION_b';
464  }
465  }
466  }
467  return array_unique(array_merge($columnsToSelect, $rowListArray));
468  }
469 
476  public function ‪getFieldsToSelect(string ‪$table, array $columnsToRender): array
477  {
478  $selectFields = $columnsToRender;
479  $selectFields[] = 'uid';
480  $selectFields[] = 'pid';
481  if (‪$table === 'pages') {
482  $selectFields[] = 'module';
483  $selectFields[] = 'extendToSubpages';
484  $selectFields[] = 'nav_hide';
485  $selectFields[] = 'doktype';
486  $selectFields[] = 'shortcut';
487  $selectFields[] = 'shortcut_mode';
488  $selectFields[] = 'mount_pid';
489  }
490  if (is_array(‪$GLOBALS['TCA'][‪$table]['ctrl']['enablecolumns'] ?? null)) {
491  $selectFields = array_merge($selectFields, array_values(‪$GLOBALS['TCA'][‪$table]['ctrl']['enablecolumns']));
492  }
493  foreach (['type', 'typeicon_column', 'editlock'] as $field) {
494  if (‪$GLOBALS['TCA'][‪$table]['ctrl'][$field] ?? false) {
495  $selectFields[] = ‪$GLOBALS['TCA'][‪$table]['ctrl'][$field];
496  }
497  }
498  if (BackendUtility::isTableWorkspaceEnabled(‪$table)) {
499  $selectFields[] = 't3ver_state';
500  $selectFields[] = 't3ver_wsid';
501  $selectFields[] = 't3ver_oid';
502  }
503  if (BackendUtility::isTableLocalizable(‪$table)) {
504  $selectFields[] = ‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'];
505  $selectFields[] = ‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'];
506  }
507  if (‪$GLOBALS['TCA'][‪$table]['ctrl']['label_alt'] ?? false) {
508  $selectFields = array_merge(
509  $selectFields,
510  GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][‪$table]['ctrl']['label_alt'], true)
511  );
512  }
513  // Unique list!
514  $selectFields = array_unique($selectFields);
515  $fieldListFields = BackendUtility::getAllowedFieldsForTable(‪$table, false);
516  // Making sure that the fields in the field-list ARE in the field-list from TCA!
517  return array_intersect($selectFields, $fieldListFields);
518  }
519 
527  public function ‪getTable(‪$table)
528  {
529  // Finding the total amount of records on the page
530  $queryBuilderTotalItems = $this->‪getQueryBuilder($table, ['*'], false, 0, 1);
531  $totalItems = (int)$queryBuilderTotalItems->count('*')
532  ->executeQuery()
533  ->fetchOne();
534  if ($totalItems === 0) {
535  return '';
536  }
537  // set the limits
538  // Use default value and overwrite with page ts config and tca config depending on the current view
539  // Force limit in range 5, 10000
540  // default 100
541  $itemsLimitSingleTable = ‪MathUtility::forceIntegerInRange((int)(
542  ‪$GLOBALS['TCA'][‪$table]['interface']['maxSingleDBListItems'] ??
543  $this->modTSconfig['itemsLimitSingleTable'] ??
544  100
545  ), 5, 10000);
546 
547  // default 20
548  $itemsLimitPerTable = ‪MathUtility::forceIntegerInRange((int)(
549  ‪$GLOBALS['TCA'][‪$table]['interface']['maxDBListItems'] ??
550  $this->modTSconfig['itemsLimitPerTable'] ??
551  20
552  ), 5, 10000);
553 
554  // Set limit depending on the view (single table vs. default)
555  $itemsPerPage = $this->table ? $itemsLimitSingleTable : $itemsLimitPerTable;
556 
557  // Set limit defined by calling code
558  if ($this->showLimit) {
559  $itemsPerPage = ‪$this->showLimit;
560  }
561 
562  // Set limit from search
563  if ($this->searchString) {
564  $itemsPerPage = $totalItems;
565  }
566 
567  // Init
568  $titleCol = ‪$GLOBALS['TCA'][‪$table]['ctrl']['label'];
569  $l10nEnabled = BackendUtility::isTableLocalizable(‪$table);
570 
571  $this->‪fieldArray = $this->‪getColumnsToRender($table, true);
572  // Creating the list of fields to include in the SQL query
573  $selectFields = $this->‪getFieldsToSelect($table, $this->‪fieldArray);
574 
575  $firstElement = ($this->page - 1) * $itemsPerPage;
576  if ($firstElement > 2 && $itemsPerPage > 0) {
577  // Get the two previous rows for sorting if displaying page > 1
578  $firstElement -= 2;
579  $itemsPerPage += 2;
580  $queryBuilder = $this->‪getQueryBuilder($table, $selectFields, true, $firstElement, $itemsPerPage);
581  $firstElement += 2;
582  $itemsPerPage -= 2;
583  } else {
584  $queryBuilder = $this->‪getQueryBuilder($table, $selectFields, true, $firstElement, $itemsPerPage);
585  }
586 
587  $queryResult = $queryBuilder->executeQuery();
588  $columnsOutput = '';
589  $onlyShowRecordsInSingleTableMode = $this->listOnlyInSingleTableMode && !‪$this->table;
590  // Fetch records only if not in single table mode
591  if ($onlyShowRecordsInSingleTableMode) {
592  $dbCount = $totalItems;
593  } elseif ($firstElement + $itemsPerPage <= $totalItems) {
594  $dbCount = $itemsPerPage + 2;
595  } else {
596  $dbCount = $totalItems - $firstElement + 2;
597  }
598  // If any records was selected, render the list:
599  if ($dbCount === 0) {
600  return '';
601  }
602 
603  // Get configuration of collapsed tables from user uc
604  $lang = $this->‪getLanguageService();
605 
606  $tableIdentifier = ‪$table;
607  // Use a custom table title for translated pages
608  if (‪$table === 'pages' && $this->‪showOnlyTranslatedRecords) {
609  // pages records in list module are split into two own sections, one for pages with
610  // sys_language_uid = 0 "Page" and an own section for sys_language_uid > 0 "Page Translation".
611  // This if sets the different title for the page translation case and a unique table identifier
612  // which is used in DOM as id.
613  $tableTitle = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:pageTranslation'));
614  $tableIdentifier = 'pages_translated';
615  } else {
616  $tableTitle = htmlspecialchars($lang->sL(‪$GLOBALS['TCA'][‪$table]['ctrl']['title']));
617  if ($tableTitle === '') {
618  $tableTitle = ‪$table;
619  }
620  }
621 
622  $backendUser = $this->‪getBackendUserAuthentication();
623  $tableCollapsed = (bool)($this->moduleData?->get('collapsedTables')[$tableIdentifier] ?? false);
624 
625  // Header line is drawn
626  $theData = [];
627  if ($this->disableSingleTableView) {
628  $theData[$titleCol] = $tableTitle . ' (<span class="t3js-table-total-items">' . $totalItems . '</span>)';
629  } else {
630  $icon = $this->table // @todo separate table header from contract/expand link
631  ? $this->iconFactory
632  ->getIcon('actions-view-table-collapse', IconSize::SMALL)
633  ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:contractView'))
634  ->render()
635  : $this->iconFactory
636  ->getIcon('actions-view-table-expand', IconSize::SMALL)
637  ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:expandView'))
638  ->render();
639  $theData[$titleCol] = $this->‪linkWrapTable($table, $tableTitle . ' (<span class="t3js-table-total-items">' . $totalItems . '</span>) ' . $icon);
640  }
641  $tableActions = '';
642  $tableHeader = $theData[$titleCol];
643  if (!$onlyShowRecordsInSingleTableMode) {
644  // Add the "new record" button
645  $tableActions .= $this->‪createActionButtonNewRecord($table) ?? '';
646  // Show the select box
647  $tableActions .= $this->‪createActionButtonColumnSelector($table) ?? '';
648  // Create the Download button
649  $tableActions .= $this->‪createActionButtonDownload($table, $totalItems) ?? '';
650  // Render collapse button if in multi table mode
651  $tableActions .= $this->‪createActionButtonCollapse($table) ?? '';
652  }
653  $currentIdList = [];
654  // Render table rows only if in multi table view or if in single table view
655  $rowOutput = '';
656  if (!$onlyShowRecordsInSingleTableMode || $this->table) {
657  // Fixing an order table for sortby tables
658  $this->currentTable = [];
659  $allowManualSorting = (‪$GLOBALS['TCA'][‪$table]['ctrl']['sortby'] ?? false) && !$this->sortField;
660  $prevUid = 0;
661  $prevPrevUid = 0;
662  // Get first two rows and initialize prevPrevUid and prevUid if on page > 1
663  if ($firstElement > 2 && $itemsPerPage > 0) {
664  $row = $queryResult->fetchAssociative();
665  $prevPrevUid = -((int)$row['uid']);
666  $row = $queryResult->fetchAssociative();
667  $prevUid = $row['uid'];
668  }
669  $accRows = [];
670  // Accumulate rows here
671  while ($row = $queryResult->fetchAssociative()) {
672  if (!$this->‪isRowListingConditionFulfilled($table, $row)) {
673  continue;
674  }
675  // In offline workspace, look for alternative record
676  BackendUtility::workspaceOL(‪$table, $row, $backendUser->workspace, true);
677  if (is_array($row)) {
678  $accRows[] = $row;
679  $currentIdList[] = $row['uid'];
680  if ($allowManualSorting) {
681  if ($prevUid) {
682  $this->currentTable['prev'][$row['uid']] = $prevPrevUid;
683  $this->currentTable['next'][$prevUid] = '-' . $row['uid'];
684  $this->currentTable['prevUid'][$row['uid']] = $prevUid;
685  }
686  $prevPrevUid = isset($this->currentTable['prev'][$row['uid']]) ? -$prevUid : $row['pid'];
687  $prevUid = $row['uid'];
688  }
689  }
690  }
691  // Render items:
692  $this->CBnames = [];
693  $this->duplicateStack = [];
694  $cc = 0;
695 
696  // If no search happened it means that the selected
697  // records are either default or All language and here we will not select translations
698  // which point to the main record:
699  $listTranslatedRecords = $l10nEnabled && $this->searchString === '' && !($this->hideTranslations === '*' || ‪GeneralUtility::inList($this->hideTranslations, ‪$table));
700  foreach ($accRows as $row) {
701  // Render item row if counter < limit
702  if ($cc < $itemsPerPage) {
703  $cc++;
704  // Reset translations
705  $translations = [];
706  // Initialize with FALSE which causes the localization panel to not be displayed as
707  // the record is already localized, in free mode or has sys_language_uid -1 set.
708  // Only set to TRUE if TranslationConfigurationProvider::translationInfo() returns
709  // an array indicating the record can be translated.
710  $translationEnabled = false;
711  // Guard clause so we can quickly return if a record is localized to "all languages"
712  // It should only be possible to localize a record off default (uid 0)
713  if ($l10nEnabled && ($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'] ?? null] ?? false) !== -1) {
714  $translationsRaw = $this->translateTools->translationInfo(‪$table, $row['uid'], 0, $row, $selectFields);
715  if (is_array($translationsRaw)) {
716  $translationEnabled = true;
717  $translations = $translationsRaw['translations'] ?? [];
718  }
719  }
720  $rowOutput .= $this->‪renderListRow($table, $row, 0, $translations, $translationEnabled);
721  if ($listTranslatedRecords) {
722  foreach ($translations ?? [] as $lRow) {
723  if (!$this->‪isRowListingConditionFulfilled($table, $lRow)) {
724  continue;
725  }
726  // In offline workspace, look for alternative record:
727  BackendUtility::workspaceOL(‪$table, $lRow, $backendUser->workspace, true);
728  if (is_array($lRow) && $backendUser->checkLanguageAccess($lRow[‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField']])) {
729  $currentIdList[] = $lRow['uid'];
730  $rowOutput .= $this->‪renderListRow($table, $lRow, 1, [], false);
731  }
732  }
733  }
734  }
735  }
736  // Record navigation is added to the beginning and end of the table if in single table mode
737  if ($this->table) {
738  $pagination = $this->‪renderListNavigation($this->table, $totalItems, $itemsPerPage);
739  $rowOutput = $pagination . $rowOutput . $pagination;
740  } elseif ($totalItems > $itemsLimitPerTable) {
741  // Show that there are more records than shown
742  $rowOutput .= '
743  <tr data-multi-record-selection-element="true">
744  <td colspan="' . (count($this->‪fieldArray)) . '">
745  <a href="' . htmlspecialchars($this->‪listURL() . '&table=' . rawurlencode($tableIdentifier)) . '" class="btn btn-sm btn-default">
746  ' . $this->iconFactory->getIcon('actions-caret-down', IconSize::SMALL)->render() . '
747  ' . $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.expandTable') . '
748  </a>
749  </td>
750  </tr>';
751  }
752  // The header row for the table is now created
753  $columnsOutput = $this->‪renderListHeader($table, $currentIdList);
754  }
755 
756  // Initialize multi record selection actions
757  $multiRecordSelectionActions = '';
758  if ($this->noControlPanels === false) {
759  $multiRecordSelectionActions = '
760  <div class="recordlist-heading-row t3js-multi-record-selection-actions hidden">
761  <div class="recordlist-heading-title">
762  <strong>' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.selection')) . '</strong>
763  </div>
764  <div class="recordlist-heading-actions">
765  ' . $this->‪renderMultiRecordSelectionActions($table, $currentIdList) . '
766  </div>
767  </div>
768  ';
769  }
770 
771  $recordListMessages = '';
772  $recordlistMessageEntries = [];
773  if ($backendUser->workspace > 0 && ‪ExtensionManagementUtility::isLoaded('workspaces') && !BackendUtility::isTableWorkspaceEnabled(‪$table)) {
774  // In case the table is not editable in workspace inform the user about the missing actions
775  if ($backendUser->workspaceAllowsLiveEditingInTable(‪$table)) {
776  $recordlistMessageEntries[] = [
777  'message' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editingLiveRecordsWarning'),
778  'severity' => ContextualFeedbackSeverity::WARNING,
779  ];
780  } else {
781  $recordlistMessageEntries[] = [
782  'message' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.notEditableInWorkspace'),
783  'severity' => ContextualFeedbackSeverity::INFO,
784  ];
785  }
786  }
787 
788  foreach ($recordlistMessageEntries as $messageEntry) {
789  $recordListMessages .= '<div class="alert alert-' . $messageEntry['severity']->getCssClass() . '">';
790  $recordListMessages .= $this->iconFactory->getIcon($messageEntry['severity']->getIconIdentifier(), IconSize::SMALL)->render();
791  $recordListMessages .= ' ';
792  $recordListMessages .= htmlspecialchars($messageEntry['message'], ENT_QUOTES | ENT_HTML5);
793  $recordListMessages .= '</div>';
794  }
795 
796  $collapseClass = $tableCollapsed && !$this->table ? 'collapse' : 'collapse show';
797  $dataState = $tableCollapsed && !$this->table ? 'collapsed' : 'expanded';
798  return '
799  <div class="recordlist" id="t3-table-' . htmlspecialchars($tableIdentifier) . '" data-multi-record-selection-identifier="t3-table-' . htmlspecialchars($tableIdentifier) . '">
800  <form action="' . htmlspecialchars($this->‪listURL()) . '#t3-table-' . htmlspecialchars($tableIdentifier) . '" method="post" name="list-table-form-' . htmlspecialchars($tableIdentifier) . '">
801  <input type="hidden" name="cmd_table" value="' . htmlspecialchars($tableIdentifier) . '" />
802  <input type="hidden" name="cmd" />
803  <div class="recordlist-heading ' . ($multiRecordSelectionActions !== '' ? 'multi-record-selection-panel' : '') . '">
804  <div class="recordlist-heading-row">
805  <div class="recordlist-heading-title">' . $tableHeader . '</div>
806  <div class="recordlist-heading-actions">' . $tableActions . '</div>
807  </div>
808  ' . $multiRecordSelectionActions . '
809  </div>
810  ' . $recordListMessages . '
811  <div class="' . $collapseClass . '" data-state="' . $dataState . '" id="recordlist-' . htmlspecialchars($tableIdentifier) . '">
812  <div class="table-fit">
813  <table data-table="' . htmlspecialchars($tableIdentifier) . '" class="table table-striped table-hover">
814  <thead>
815  ' . $columnsOutput . '
816  </thead>
817  <tbody data-multi-record-selection-row-selection="true">
818  ' . $rowOutput . '
819  </tbody>
820  </table>
821  </div>
822  </div>
823  </form>
824  </div>
825  ';
826  }
827 
831  protected function ‪createActionButtonNewRecord(string ‪$table): ?‪ButtonInterface
832  {
833  if (!$this->‪isEditable($table)) {
834  return null;
835  }
836  if (!$this->‪showNewRecLink($table)) {
837  return null;
838  }
839  $permsAdditional = (‪$table === 'pages' ? ‪Permission::PAGE_NEW : ‪Permission::CONTENT_EDIT);
840  if (!$this->calcPerms->isGranted($permsAdditional)) {
841  return null;
842  }
843 
844  $tag = 'a';
845  $iconIdentifier = 'actions-plus';
846  $label = sprintf(
847  $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:newRecordOfType'),
848  $this->‪getLanguageService()->sL(‪$GLOBALS['TCA'][‪$table]['ctrl']['title'])
849  );
850  $attributes = [
851  'data-recordlist-action' => 'new',
852  ];
853 
854  if (‪$table === 'tt_content') {
855  $tag = 'typo3-backend-new-content-element-wizard-button';
856  $attributes['url'] = (string)$this->uriBuilder->buildUriFromRoute(
857  'new_content_element_wizard',
858  [
859  'id' => $this->id,
860  'returnUrl' => $this->listURL(),
861  ]
862  );
863  } elseif (‪$table === 'pages') {
864  $iconIdentifier = 'actions-page-new';
865  $attributes['data-new'] = 'page';
866  $attributes['href'] = (string)$this->uriBuilder->buildUriFromRoute(
867  'db_new_pages',
868  ['id' => $this->id, 'returnUrl' => $this->listURL()]
869  );
870  } else {
871  $attributes['href'] = $this->uriBuilder->buildUriFromRoute(
872  'record_edit',
873  [
874  'edit' => [
875  ‪$table => [
876  $this->id => 'new',
877  ],
878  ],
879  'returnUrl' => $this->‪listURL(),
880  ]
881  );
882  }
883 
884  $button = GeneralUtility::makeInstance(GenericButton::class);
885  $button->setTag($tag);
886  $button->setLabel($label);
887  $button->setShowLabelText(true);
888  $button->setIcon($this->iconFactory->getIcon($iconIdentifier, IconSize::SMALL));
889  $button->setAttributes($attributes);
890 
891  return $button;
892  }
893 
894  protected function ‪createActionButtonDownload(string ‪$table, int $totalItems): ?ButtonInterface
895  {
896  // Do not render the download button for page translations or in case it is disabled
897  if (!$this->displayRecordDownload
898  || ($this->modTSconfig['noExportRecordsLinks'] ?? false)
900  ) {
901  return null;
902  }
903 
904  $downloadButtonLabel = $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_download.xlf:download');
905  $downloadButtonTitle = sprintf($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_download.xlf:' . ($totalItems === 1 ? 'downloadRecord' : 'downloadRecords')), $totalItems);
906  $downloadCancelTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel');
907  $downloadSettingsUrl = (string)$this->uriBuilder->buildUriFromRoute(
908  'ajax_record_download_settings',
909  ['id' => $this->id, 'table' => ‪$table, 'searchString' => $this->searchString, 'searchLevels' => $this->searchLevels]
910  );
911  $downloadSettingsTitle = sprintf(
912  $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_download.xlf:' . ($totalItems === 1 ? 'downloadRecordSettings' : 'downloadRecordsSettings')),
913  $this->‪getLanguageService()->sL(‪$GLOBALS['TCA'][‪$table]['ctrl']['title'] ?? '') ?: ‪$table,
914  $totalItems
915  );
916 
917  $button = GeneralUtility::makeInstance(GenericButton::class);
918  $button->setTag('typo3-recordlist-record-download-button');
919  $button->setLabel($downloadButtonLabel);
920  $button->setShowLabelText(true);
921  $button->setIcon($this->iconFactory->getIcon('actions-download', IconSize::SMALL));
922  $button->setAttributes([
923  'url' => $downloadSettingsUrl,
924  'subject' => $downloadSettingsTitle,
925  'ok' => $downloadButtonTitle,
926  'close' => $downloadCancelTitle,
927  'data-recordlist-action' => 'download',
928  ]);
929 
930  return $button;
931  }
932 
937  {
938  if ($this->displayColumnSelector === false) {
939  // Early return in case column selector is disabled
940  return null;
941  }
942 
943  $shouldRenderSelector = true;
944  // See if it is disabled in general
945  if (isset($this->modTSconfig['displayColumnSelector'])) {
946  $shouldRenderSelector = (bool)$this->modTSconfig['displayColumnSelector'];
947  }
948  // Table override was explicitly set to false
949  if (isset($this->modTSconfig['table.'][‪$table . '.']['displayColumnSelector'])) {
950  $shouldRenderSelector = (bool)$this->modTSconfig['table.'][‪$table . '.']['displayColumnSelector'];
951  }
952  // Do not render button if column selector is disabled
953  if ($shouldRenderSelector === false) {
954  return null;
955  }
956 
957  $lang = $this->‪getLanguageService();
958  $tableIdentifier = ‪$table . ((‪$table === 'pages' && ‪$this->showOnlyTranslatedRecords) ? '_translated' : '');
959  $columnSelectorUrl = (string)$this->uriBuilder->buildUriFromRoute(
960  'ajax_show_columns_selector',
961  ['id' => $this->id, 'table' => ‪$table]
962  );
963  $columnSelectorTitle = sprintf(
964  $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:showColumnsSelection'),
965  $lang->sL(‪$GLOBALS['TCA'][‪$table]['ctrl']['title'] ?? '') ?: ‪$table,
966  );
967 
968  $button = GeneralUtility::makeInstance(GenericButton::class);
969  $button->setTag('typo3-backend-column-selector-button');
970  $button->setLabel($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:showColumns'));
971  $button->setShowLabelText(true);
972  $button->setIcon($this->iconFactory->getIcon('actions-options', IconSize::SMALL));
973  $button->setAttributes([
974  'data-url' => $columnSelectorUrl,
975  'data-target' => $this->‪listURL() . '#t3-table-' . $tableIdentifier,
976  'data-title' => $columnSelectorTitle,
977  'data-button-ok' => $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView'),
978  'data-button-close' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.cancel'),
979  'data-error-message' => $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_column_selector.xlf:updateColumnView.error'),
980  'data-recordlist-action' => 'columns',
981  ]);
982 
983  return $button;
984  }
985 
986  protected function ‪createActionButtonCollapse(string ‪$table): ?‪ButtonInterface
987  {
988  if ($this->table !== '') {
989  return null;
990  }
991 
992  $tableIdentifier = ‪$table . ((‪$table === 'pages' && ‪$this->showOnlyTranslatedRecords) ? '_translated' : '');
993  $tableCollapsed = (bool)($this->moduleData?->get('collapsedTables')[$tableIdentifier] ?? false);
994 
995  $button = GeneralUtility::makeInstance(GenericButton::class);
996  $button->setLabel(sprintf(
997  $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:collapseExpandTable'),
998  $this->‪getLanguageService()->sL(‪$GLOBALS['TCA'][‪$table]['ctrl']['title'])
999  ));
1000  $button->setClasses('t3js-toggle-recordlist');
1001  $button->setIcon($this->iconFactory->getIcon(($tableCollapsed ? 'actions-view-list-expand' : 'actions-view-list-collapse'), IconSize::SMALL));
1002  $button->setAttributes([
1003  'aria-expanded' => ($tableCollapsed ? 'false' : 'true'),
1004  'data-table' => $tableIdentifier,
1005  'data-recordlist-action' => 'toggle',
1006  'data-bs-toggle' => 'collapse',
1007  'data-bs-target' => '#recordlist-' . $tableIdentifier,
1008  ]);
1009 
1010  return $button;
1011  }
1012 
1016  protected function ‪getPreviewUriBuilder(string ‪$table, array $row): ‪PreviewUriBuilder
1017  {
1018  if (‪$table === 'tt_content') {
1019  // Link to a content element, possibly translated and with anchor
1020  $previewUriBuilder = ‪PreviewUriBuilder::create($this->id)
1021  ->withSection('#c' . $row['uid'])
1022  ->withLanguage((int)($row[‪$GLOBALS['TCA']['tt_content']['ctrl']['languageField'] ?? null] ?? 0));
1023  } elseif (‪$table === 'pages' && ($row[‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] ?? null] ?? 0) > 0) {
1024  // Link to a page translation needs uid of default language page as id
1025  $previewUriBuilder = ‪PreviewUriBuilder::create((int)$row[‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']])
1026  ->withSection('#c' . $row['uid'])
1027  ->withLanguage((int)($row[‪$GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null] ?? 0));
1028  } else {
1029  // Link to a page in the default language
1030  $previewUriBuilder = ‪PreviewUriBuilder::create((int)($row['uid'] ?? 0));
1031  }
1032  return $previewUriBuilder;
1033  }
1034 
1044  protected function ‪isRowListingConditionFulfilled(‪$table, $row)
1045  {
1046  return true;
1047  }
1048 
1061  public function ‪renderListRow(‪$table, array $row, int $indent, array $translations, bool $translationEnabled)
1062  {
1063  $titleCol = ‪$GLOBALS['TCA'][‪$table]['ctrl']['label'] ?? '';
1064  $languageService = $this->‪getLanguageService();
1065  $rowOutput = '';
1066  $id_orig = ‪$this->id;
1067  // If in search mode, make sure the preview will show the correct page
1068  if ((string)$this->searchString !== '') {
1069  $this->id = $row['pid'];
1070  }
1071 
1072  $tagAttributes = [
1073  'class' => [],
1074  'data-table' => ‪$table,
1075  'title' => 'id=' . $row['uid'],
1076  ];
1077 
1078  // Add active class to record of current link
1079  if (
1080  isset($this->currentLink['tableNames'])
1081  && (int)$this->currentLink['uid'] === (int)$row['uid']
1082  && ‪GeneralUtility::inList($this->currentLink['tableNames'], ‪$table)
1083  ) {
1084  $tagAttributes['class'][] = 'active';
1085  }
1086  // Overriding with versions background color if any:
1087  if (!empty($row['_CSSCLASS'])) {
1088  $tagAttributes['class'] = [$row['_CSSCLASS']];
1089  }
1090 
1091  $tagAttributes['class'][] = 't3js-entity';
1092 
1093  // Preparing and getting the data-array
1094  $theData = [];
1095  $deletePlaceholderClass = '';
1096  foreach ($this->‪fieldArray as $fCol) {
1097  if ($fCol === $titleCol) {
1098  $recTitle = BackendUtility::getRecordTitle(‪$table, $row, false, true);
1099  $warning = '';
1100  // If the record is edit-locked by another user, we will show a little warning sign:
1101  $lockInfo = BackendUtility::isRecordLocked(‪$table, $row['uid']);
1102  if ($lockInfo) {
1103  $warning = '<span tabindex="0"'
1104  . ' title="' . htmlspecialchars($lockInfo['msg']) . '"'
1105  . ' aria-label="' . htmlspecialchars($lockInfo['msg']) . '">'
1106  . $this->iconFactory->getIcon('status-user-backend', IconSize::SMALL, 'overlay-edit')->render()
1107  . '</span>';
1108  }
1109  if ($this->‪isRecordDeletePlaceholder($row)) {
1110  // Delete placeholder records do not link to formEngine edit and are rendered strike-through
1111  $deletePlaceholderClass = ' deletePlaceholder';
1112  $theData[$fCol] = $theData['__label'] =
1113  $warning
1114  . '<span title="' . htmlspecialchars($languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:row.deletePlaceholder.title')) . '">'
1115  . htmlspecialchars($recTitle)
1116  . '</span>';
1117  } else {
1118  $theData[$fCol] = $theData['__label'] = $warning . $this->‪linkWrapItems($table, $row['uid'], $recTitle, $row);
1119  }
1120  } elseif ($fCol === 'pid') {
1121  $theData[$fCol] = $row[$fCol];
1122  } elseif ($fCol === '_SELECTOR_') {
1123  if (‪$table !== 'pages' || !$this->‪showOnlyTranslatedRecords) {
1124  // Add checkbox for all tables except the special page translations table
1125  $theData[$fCol] = $this->‪makeCheckbox($table, $row);
1126  } else {
1127  // Remove "_SELECTOR_", which is always the first item, from the field list
1128  array_splice($this->‪fieldArray, 0, 1);
1129  }
1130  } elseif ($fCol === 'icon') {
1131  $icon = $this->iconFactory
1132  ->getIconForRecord(‪$table, $row, IconSize::SMALL)
1133  ->setTitle(BackendUtility::getRecordIconAltText($row, ‪$table))
1134  ->render();
1135  $theData[$fCol] = ''
1136  . ($indent ? '<span class="indent indent-inline-block" style="--indent-level: ' . $indent . '"></span> ' : '')
1137  . (($this->clickMenuEnabled && !$this->‪isRecordDeletePlaceholder($row)) ? BackendUtility::wrapClickMenuOnIcon($icon, ‪$table, $row['uid']) : $icon);
1138  } elseif ($fCol === '_PATH_') {
1139  $theData[$fCol] = $this->‪recPath($row['pid']);
1140  } elseif ($fCol === '_REF_') {
1141  $theData[$fCol] = $this->‪generateReferenceToolTip($table, $row['uid']);
1142  } elseif ($fCol === '_CONTROL_') {
1143  $theData[$fCol] = $this->‪makeControl($table, $row);
1144  } elseif ($fCol === '_LOCALIZATION_') {
1145  // Language flag an title
1146  $theData[$fCol] = $this->‪languageFlag($table, $row);
1147  // Localize record
1148  $localizationPanel = $translationEnabled ? $this->‪makeLocalizationPanel($table, $row, $translations) : '';
1149  if ($localizationPanel !== '') {
1150  $theData['_LOCALIZATION_b'] = '<div class="btn-group">' . $localizationPanel . '</div>';
1151  $this->showLocalizeColumn[‪$table] = true;
1152  }
1153  } elseif ($fCol !== '_LOCALIZATION_b') {
1154  // default for all other columns, except "_LOCALIZATION_b"
1155  $pageId = ‪$table === 'pages' ? $row['uid'] : $row['pid'];
1156  $tmpProc = BackendUtility::getProcessedValueExtra(‪$table, $fCol, $row[$fCol], 100, $row['uid'], true, $pageId);
1157  $theData[$fCol] = $this->‪linkUrlMail(htmlspecialchars((string)$tmpProc), (string)($row[$fCol] ?? ''));
1158  }
1159  }
1160  // Reset the ID if it was overwritten
1161  if ((string)$this->searchString !== '') {
1162  $this->id = $id_orig;
1163  }
1164  // Add classes to table cells
1165  $this->addElement_tdCssClass['_SELECTOR_'] = 'col-checkbox';
1166  $this->addElement_tdCssClass[$titleCol] = 'col-title col-responsive' . $deletePlaceholderClass;
1167  $this->addElement_tdCssClass['__label'] = $this->addElement_tdCssClass[$titleCol];
1168  $this->addElement_tdCssClass['icon'] = 'col-icon';
1169  $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
1170  $this->addElement_tdCssClass['_PATH_'] = 'col-path';
1171  $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
1172  $this->addElement_tdCssClass['_LOCALIZATION_b'] = 'col-localizationb';
1173  // Create element in table cells:
1174  $theData['uid'] = $row['uid'];
1175  if (isset(‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'])
1176  && isset(‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'])
1177  ) {
1178  $theData['_l10nparent_'] = $row[‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField']];
1179  }
1180 
1181  $tagAttributes = array_map(
1182  static function (array|string $attributeValue): string {
1183  if (is_array($attributeValue)) {
1184  return implode(' ', $attributeValue);
1185  }
1186  return $attributeValue;
1187  },
1188  $tagAttributes
1189  );
1190 
1191  $rowOutput .= $this->‪addElement($theData, GeneralUtility::implodeAttributes($tagAttributes, true));
1192  // Finally, return table row element:
1193  return $rowOutput;
1194  }
1195 
1204  protected function ‪getReferenceCount($tableName, ‪$uid)
1205  {
1206  if (!isset($this->referenceCount[$tableName][‪$uid])) {
1207  $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
1208  $numberOfReferences = $referenceIndex->getNumberOfReferencedRecords($tableName, ‪$uid);
1209  $this->referenceCount[$tableName][‪$uid] = $numberOfReferences;
1210  }
1211  return $this->referenceCount[$tableName][‪$uid];
1212  }
1213 
1224  public function ‪renderListHeader(‪$table, $currentIdList)
1225  {
1226  $tsConfig = BackendUtility::getPagesTSconfig($this->id)['TCEFORM.'][$table . '.'] ?? null;
1227  $tsConfigOfTable = is_array($tsConfig) ? $tsConfig : null;
1228 
1229  $lang = $this->‪getLanguageService();
1230  // Init:
1231  $theData = [];
1232  // Traverse the fields:
1233  foreach ($this->‪fieldArray as $fCol) {
1234  // Calculate users permissions to edit records in the table:
1235  if (‪$table === 'pages') {
1236  $permsEdit = $this->calcPerms->editPagePermissionIsGranted();
1237  } else {
1238  $permsEdit = $this->calcPerms->editContentPermissionIsGranted();
1239  }
1240 
1241  $permsEdit = $permsEdit && $this->‪overlayEditLockPermissions($table);
1242  switch ((string)$fCol) {
1243  case '_SELECTOR_':
1244  if (‪$table !== 'pages' || !$this->‪showOnlyTranslatedRecords) {
1245  // Add checkbox actions for all tables except the special page translations table
1246  $theData[$fCol] = $this->‪renderCheckboxActions();
1247  } else {
1248  // Remove "_SELECTOR_", which is always the first item, from the field list
1249  array_splice($this->‪fieldArray, 0, 1);
1250  }
1251  break;
1252  case 'icon':
1253  // In case no checkboxes are rendered (page translations or disabled) add the icon
1254  // column, otherwise the selector column is using "colspan=2"
1255  if (!in_array('_SELECTOR_', $this->‪fieldArray, true)
1256  || ($table === 'pages' && $this->‪showOnlyTranslatedRecords)
1257  ) {
1258  $theData[$fCol] = '';
1259  }
1260  break;
1261  case '_CONTROL_':
1262  $theData[$fCol] = '<i class="hidden">' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._CONTROL_')) . '</i>';
1263  // In single table view, add button to edit displayed fields of marked / listed records
1264  if ($this->table && $permsEdit && is_array($currentIdList) && $this->‪isEditable($table)) {
1265  $label = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:editShownColumns'));
1266  $theData[$fCol] = '<button type="button"'
1267  . ' class="btn btn-default t3js-record-edit-multiple"'
1268  . ' title="' . $label . '"'
1269  . ' aria-label="' . $label . '"'
1270  . ' data-return-url="' . htmlspecialchars($this->‪listURL()) . '"'
1271  . ' data-columns-only="' . htmlspecialchars(implode(',', $this->‪fieldArray)) . '">'
1272  . $this->iconFactory->getIcon('actions-document-open', IconSize::SMALL)->render()
1273  . '</button>';
1274  }
1275  break;
1276  case '_PATH_':
1277  // Path
1278  $theData[$fCol] = '<i>' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._PATH_')) . '</i>';
1279  break;
1280  case '_REF_':
1281  // References
1282  $theData[$fCol] = '<i>' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._REF_')) . '</i>';
1283  break;
1284  case '_LOCALIZATION_':
1285  // Show language of record
1286  $theData[$fCol] = '<i>' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._LOCALIZATION_')) . '</i>';
1287  break;
1288  case '_LOCALIZATION_b':
1289  // Show translation options
1290  if ($this->showLocalizeColumn[‪$table] ?? false) {
1291  $theData[$fCol] = '<i>' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:Localize')) . '</i>';
1292  }
1293  break;
1294  default:
1295  // Regular fields header
1296  $theData[$fCol] = '';
1297 
1298  // Check if $fCol is really a field and get the label and remove the colons at the end
1299  $sortLabel = BackendUtility::getItemLabel(‪$table, $fCol);
1300  if ($sortLabel !== null) {
1301  // Field label
1302  $fieldTSConfig = [];
1303  if (isset($tsConfigOfTable[$fCol . '.'])
1304  && is_array($tsConfigOfTable[$fCol . '.'])
1305  ) {
1306  $fieldTSConfig = $tsConfigOfTable[$fCol . '.'];
1307  }
1308  $sortLabel = $lang->translateLabel(
1309  $fieldTSConfig['label.'] ?? [],
1310  $fieldTSConfig['label'] ?? $sortLabel
1311  );
1312  $sortLabel = htmlspecialchars(rtrim(trim($sortLabel), ':'));
1313  } elseif ($specialLabel = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.' . $fCol)) {
1314  // Special label exists for this field (Probably a management field, e.g. sorting)
1315  $sortLabel = htmlspecialchars($specialLabel);
1316  } else {
1317  // No TCA field, only output the $fCol variable with square brackets []
1318  $sortLabel = htmlspecialchars($fCol);
1319  $sortLabel = '<i>[' . rtrim(trim($sortLabel), ':') . ']</i>';
1320  }
1321 
1322  if ($this->table && is_array($currentIdList)) {
1323  // If the numeric clipboard pads are selected, show duplicate sorting link:
1324  if ($this->noControlPanels === false
1325  && $this->‪isClipboardFunctionalityEnabled($table)
1326  && $this->clipObj->current !== 'normal'
1327  ) {
1328  $theData[$fCol] .= '<a class="btn btn-default" href="' . htmlspecialchars($this->‪listURL() . '&duplicateField=' . $fCol)
1329  . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_duplicates')) . '">'
1330  . $this->iconFactory->getIcon('actions-document-duplicates-select', IconSize::SMALL)->render() . '</a>';
1331  }
1332  // If the table can be edited, add link for editing THIS field for all
1333  // listed records:
1334  if ($this->‪isEditable($table) && $permsEdit && (‪$GLOBALS['TCA'][‪$table]['columns'][$fCol] ?? false)) {
1335  $iTitle = sprintf($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:editThisColumn'), $sortLabel);
1336  $theData[$fCol] .= '<button type="button"'
1337  . ' class="btn btn-default t3js-record-edit-multiple"'
1338  . ' title="' . htmlspecialchars($iTitle) . '"'
1339  . ' aria-label="' . htmlspecialchars($iTitle) . '"'
1340  . ' data-return-url="' . htmlspecialchars($this->‪listURL()) . '"'
1341  . ' data-columns-only="' . htmlspecialchars($fCol) . '">'
1342  . $this->iconFactory->getIcon('actions-document-open', IconSize::SMALL)->render()
1343  . '</button>';
1344  }
1345  if (strlen($theData[$fCol]) > 0) {
1346  $theData[$fCol] = '<div class="btn-group">' . $theData[$fCol] . '</div> ';
1347  }
1348  }
1349  $theData[$fCol] .= $this->‪addSortLink($sortLabel, $fCol, ‪$table);
1350  }
1351  }
1352 
1353  $event = $this->eventDispatcher->dispatch(
1354  new ModifyRecordListHeaderColumnsEvent($theData, ‪$table, $currentIdList, $this)
1355  );
1356 
1357  // Create and return header table row:
1358  return $this->‪addElement($event->getColumns(), GeneralUtility::implodeAttributes($event->getHeaderAttributes(), true), 'th');
1359  }
1360 
1366  protected function ‪renderListNavigation(string ‪$table, int $totalItems, int $itemsPerPage): string
1367  {
1368  $currentPage = ‪$this->page;
1369  $paginationColumns = count($this->‪fieldArray);
1370  $totalPages = (int)ceil($totalItems / $itemsPerPage);
1371  // Show page selector if not all records fit into one page
1372  if ($totalPages <= 1) {
1373  return '';
1374  }
1375  if ($totalItems > $currentPage * $itemsPerPage) {
1376  $lastElementNumber = $currentPage * $itemsPerPage;
1377  } else {
1378  $lastElementNumber = $totalItems;
1379  }
1380  $view = $this->backendViewFactory->create($this->request);
1381  return $view->assignMultiple([
1382  'currentUrl' => $this->‪listURL('', $table, 'pointer'),
1383  'currentPage' => $currentPage,
1384  'totalPages' => $totalPages,
1385  'firstElement' => ((($currentPage - 1) * $itemsPerPage) + 1),
1386  'lastElement' => $lastElementNumber,
1387  'colspan' => $paginationColumns,
1388  ])
1389  ->render('ListNavigation');
1390  }
1391 
1392  /*********************************
1393  *
1394  * Rendering of various elements
1395  *
1396  *********************************/
1397 
1406  public function ‪makeControl(‪$table, $row)
1407  {
1408  $backendUser = $this->‪getBackendUserAuthentication();
1409  $userTsConfig = $backendUser->getTSConfig();
1410  $rowUid = $row['uid'];
1411  if (isset($row['_ORIG_uid'])) {
1412  $rowUid = $row['_ORIG_uid'];
1413  }
1414  $isDeletePlaceHolder = $this->‪isRecordDeletePlaceholder($row);
1415  $cells = [
1416  'primary' => [],
1417  'secondary' => [],
1418  ];
1419 
1420  // Hide the move elements for localized records - doesn't make much sense to perform these options for them
1421  $isL10nOverlay = (int)($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'] ?? null] ?? 0) !== 0;
1422  $localCalcPerms = $this->‪getPagePermissionsForRecord($table, $row);
1423  if (‪$table === 'pages') {
1424  $permsEdit = ($backendUser->checkLanguageAccess($row[‪$GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null] ?? 0))
1425  && $localCalcPerms->editPagePermissionIsGranted();
1426  } else {
1427  $permsEdit = $localCalcPerms->editContentPermissionIsGranted() && $backendUser->recordEditAccessInternals(‪$table, $row);
1428  }
1429  $permsEdit = $this->‪overlayEditLockPermissions($table, $row, $permsEdit);
1430 
1431  // "Show" link (only pages and tt_content elements)
1432  $tsConfig = BackendUtility::getPagesTSconfig($this->id)['mod.']['web_list.'] ?? [];
1433  if ((
1434  $table === 'pages'
1435  && isset($row['doktype'])
1436  && !in_array((int)$row['doktype'], $this->‪getNoViewWithDokTypes($tsConfig), true)
1437  )
1438  || (
1439  ‪$table === 'tt_content'
1440  && isset($this->pageRow['doktype'])
1441  && !in_array((int)$this->pageRow['doktype'], $this->‪getNoViewWithDokTypes($tsConfig), true)
1442  )
1443  ) {
1444  if (!$isDeletePlaceHolder
1445  && ($attributes = $this->‪getPreviewUriBuilder($table, $row)->serializeDispatcherAttributes()) !== null
1446  ) {
1447  $viewAction = '<button'
1448  . ' type="button"'
1449  . ' class="btn btn-default" ' . $attributes
1450  . ' title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">';
1451  if (‪$table === 'pages') {
1452  $viewAction .= $this->iconFactory->getIcon('actions-view-page', IconSize::SMALL)->render();
1453  } else {
1454  $viewAction .= $this->iconFactory->getIcon('actions-view', IconSize::SMALL)->render();
1455  }
1456  $viewAction .= '</button>';
1457  $this->‪addActionToCellGroup($cells, $viewAction, 'view');
1458  } else {
1459  $this->‪addActionToCellGroup($cells, $this->spaceIcon, 'view');
1460  }
1461  } else {
1462  $this->‪addActionToCellGroup($cells, $this->spaceIcon, 'view');
1463  }
1464 
1465  // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1466  if ($permsEdit && !$isDeletePlaceHolder && $this->‪isEditable($table)) {
1467  $params = [
1468  'edit' => [
1469  ‪$table => [
1470  $row['uid'] => 'edit',
1471  ],
1472  ],
1473  ];
1474  $iconIdentifier = 'actions-open';
1475  if (‪$table === 'pages') {
1476  // Disallow manual adjustment of the language field for pages
1477  $params['overrideVals']['pages']['sys_language_uid'] = $row[‪$GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? null] ?? 0;
1478  $iconIdentifier = 'actions-page-open';
1479  }
1480  $params['returnUrl'] = $this->‪listURL();
1481  $editLink = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
1482  $editAction = '<a class="btn btn-default" href="' . htmlspecialchars($editLink) . '"'
1483  . ' title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:edit')) . '">' . $this->iconFactory->getIcon($iconIdentifier, IconSize::SMALL)->render() . '</a>';
1484  } else {
1485  $editAction = ‪$this->spaceIcon;
1486  }
1487  $this->‪addActionToCellGroup($cells, $editAction, 'edit');
1488 
1489  // "Info"
1490  if (!$isDeletePlaceHolder) {
1491  $label = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo'));
1492  $viewBigAction = '<button type="button" aria-haspopup="dialog"'
1493  . ' class="btn btn-default" '
1494  . $this->‪createShowItemTagAttributes($table . ',' . ($row['uid'] ?? 0))
1495  . ' title="' . $label . '"'
1496  . ' aria-label="' . $label . '">'
1497  . $this->iconFactory->getIcon('actions-document-info', IconSize::SMALL)->render()
1498  . '</button>';
1499  $this->‪addActionToCellGroup($cells, $viewBigAction, 'viewBig');
1500  } else {
1501  $this->‪addActionToCellGroup($cells, $this->spaceIcon, 'viewBig');
1502  }
1503 
1504  // "Move" wizard link for pages/tt_content elements:
1505  if ($permsEdit && (‪$table === 'tt_content' || ‪$table === 'pages') && $this->‪isEditable($table)) {
1506  if ($isL10nOverlay || $isDeletePlaceHolder) {
1507  $moveAction = ‪$this->spaceIcon;
1508  } elseif (‪$table === 'pages') {
1509  $linkTitleLL = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:move_page'));
1510  $icon = $this->iconFactory->getIcon('actions-page-move', IconSize::SMALL);
1511  ‪$url = (string)$this->uriBuilder->buildUriFromRoute('move_page', [
1512  'uid' => $row['uid'],
1513  'returnUrl' => $this->listURL(),
1514  ]);
1515  $moveAction = '<a class="btn btn-default" href="' . htmlspecialchars(‪$url) . '" aria-label="' . $linkTitleLL . '">' . $icon->render() . '</a>';
1516  } else {
1517  $linkTitleLL = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:move_record'));
1518  $icon = $this->iconFactory->getIcon('actions-document-move', IconSize::SMALL);
1519  ‪$url = (string)$this->uriBuilder->buildUriFromRoute('move_element', [
1520  'uid' => $row['uid'],
1521  'returnUrl' => $this->listURL(),
1522  ]);
1523  $moveAction = '<a class="btn btn-default" href="' . htmlspecialchars(‪$url) . '" aria-label="' . $linkTitleLL . '">' . $icon->render() . '</a>';
1524  }
1525  $this->‪addActionToCellGroup($cells, $moveAction, 'move');
1526  }
1527 
1528  // If the table is NOT a read-only table, then show these links:
1529  if ($this->‪isEditable($table)) {
1530  // "Revert" link (history/undo)
1531  if (\trim($userTsConfig['options.']['showHistory.'][‪$table] ?? $userTsConfig['options.']['showHistory'] ?? '1')) {
1532  if (!$isDeletePlaceHolder) {
1533  $moduleUrl = $this->uriBuilder->buildUriFromRoute('record_history', [
1534  'element' => ‪$table . ':' . $row['uid'],
1535  'returnUrl' => $this->‪listURL(),
1536  ]) . '#latest';
1537  $historyAction = '<a class="btn btn-default" href="' . htmlspecialchars($moduleUrl) . '" title="'
1538  . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:history')) . '">'
1539  . $this->iconFactory->getIcon('actions-document-history-open', IconSize::SMALL)->render() . '</a>';
1540  $this->‪addActionToCellGroup($cells, $historyAction, 'history');
1541  } else {
1542  $this->‪addActionToCellGroup($cells, $this->spaceIcon, 'history');
1543  }
1544  }
1545 
1546  // "Edit Perms" link:
1547  if (‪$table === 'pages' && $this->moduleProvider->accessGranted('permissions_pages', $backendUser)) {
1548  if ($isL10nOverlay || $isDeletePlaceHolder) {
1549  $permsAction = ‪$this->spaceIcon;
1550  } else {
1551  $params = [
1552  'id' => $row['uid'],
1553  'action' => 'edit',
1554  'returnUrl' => $this->‪listURL(),
1555  ];
1556  $href = (string)$this->uriBuilder->buildUriFromRoute('permissions_pages', $params);
1557  $permsAction = '<a class="btn btn-default" href="' . htmlspecialchars($href) . '" title="'
1558  . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:permissions')) . '">'
1559  . $this->iconFactory->getIcon('actions-lock', IconSize::SMALL)->render() . '</a>';
1560  }
1561  $this->‪addActionToCellGroup($cells, $permsAction, 'perms');
1562  }
1563 
1564  // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row
1565  // or if default values can depend on previous record):
1566  if (((‪$GLOBALS['TCA'][‪$table]['ctrl']['sortby'] ?? false) || (‪$GLOBALS['TCA'][‪$table]['ctrl']['useColumnsForDefaultValues'] ?? false)) && $permsEdit) {
1567  $neededPermission = ‪$table === 'pages' ? ‪Permission::PAGE_NEW : ‪Permission::CONTENT_EDIT;
1568  if ($this->calcPerms->isGranted($neededPermission)) {
1569  if ($isL10nOverlay || $isDeletePlaceHolder) {
1570  $this->‪addActionToCellGroup($cells, $this->spaceIcon, 'new');
1571  } elseif ($this->‪showNewRecLink($table)) {
1572  $params = [
1573  'edit' => [
1574  ‪$table => [
1575  (0 - (($row['_MOVE_PLH'] ?? 0) ? $row['_MOVE_PLH_uid'] : $row['uid'])) => 'new',
1576  ],
1577  ],
1578  'returnUrl' => $this->‪listURL(),
1579  ];
1580  $icon = (‪$table === 'pages' ? $this->iconFactory->getIcon('actions-page-new', IconSize::SMALL) : $this->iconFactory->getIcon('actions-plus', IconSize::SMALL));
1581  $titleLabel = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:new');
1582  if (‪$GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? false) {
1583  $titleLabel = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:newRecord');
1584  if ($table === 'pages') {
1585  $titleLabel = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:newPage');
1586  }
1587  }
1588  $newLink = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
1589  $newAction = '<a class="btn btn-default" href="' . htmlspecialchars($newLink) . '" title="' . htmlspecialchars($titleLabel) . '">'
1590  . $icon->render() . '</a>';
1591  $this->‪addActionToCellGroup($cells, $newAction, 'new');
1592  }
1593  }
1594  }
1595 
1596  // "Hide/Unhide" links:
1597  $hiddenField = ‪$GLOBALS['TCA'][‪$table]['ctrl']['enablecolumns']['disabled'] ?? null;
1598  if ($hiddenField !== null
1599  && !empty(‪$GLOBALS['TCA'][‪$table]['columns'][$hiddenField])
1600  && (empty(‪$GLOBALS['TCA'][‪$table]['columns'][$hiddenField]['exclude']) || $backendUser->check('non_exclude_fields', ‪$table . ':' . $hiddenField))
1601  ) {
1602  if (!$permsEdit || $isDeletePlaceHolder || $this->‪isRecordCurrentBackendUser($table, $row)) {
1603  $hideAction = ‪$this->spaceIcon;
1604  } else {
1605  $visibleTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:hide' . ($table === 'pages' ? 'Page' : ''));
1606  $visibleIcon = 'actions-edit-hide';
1607  $visibleValue = '0';
1608  $hiddenTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:unHide' . ($table === 'pages' ? 'Page' : ''));
1609  $hiddenIcon = 'actions-edit-unhide';
1610  $hiddenValue = '1';
1611  if ($row[$hiddenField] ?? false) {
1612  $titleLabel = $hiddenTitle;
1613  $iconIdentifier = $hiddenIcon;
1614  $status = 'hidden';
1615  } else {
1616  $titleLabel = $visibleTitle;
1617  $iconIdentifier = $visibleIcon;
1618  $status = 'visible';
1619  }
1620  $attributesString = GeneralUtility::implodeAttributes(
1621  [
1622  'class' => 'btn btn-default',
1623  'type' => 'button',
1624  'title' => $titleLabel,
1625  'data-datahandler-action' => 'visibility',
1626  'data-datahandler-table' => ‪$table,
1627  'data-datahandler-uid' => $rowUid,
1628  'data-datahandler-field' => $hiddenField,
1629  'data-datahandler-status' => $status,
1630  'data-datahandler-visible-label' => $visibleTitle,
1631  'data-datahandler-visible-value' => $visibleValue,
1632  'data-datahandler-visible-icon' => $visibleIcon,
1633  'data-datahandler-hidden-label' => $hiddenTitle,
1634  'data-datahandler-hidden-value' => $hiddenValue,
1635  'data-datahandler-hidden-icon' => $hiddenIcon,
1636  ],
1637  true
1638  );
1639  $hideAction = '<button ' . $attributesString . '>'
1640  . $this->iconFactory->getIcon($iconIdentifier, IconSize::SMALL)
1641  . '</button>';
1642  }
1643  $this->‪addActionToCellGroup($cells, $hideAction, 'hide');
1644  }
1645 
1646  // "Up/Down" links
1647  if ($permsEdit && (‪$GLOBALS['TCA'][‪$table]['ctrl']['sortby'] ?? false) && !$this->sortField && !$this->searchLevels) {
1648  if (!$isL10nOverlay && !$isDeletePlaceHolder && isset($this->currentTable['prev'][$row['uid']])) {
1649  // Up
1650  $params = [];
1651  $params['redirect'] = $this->‪listURL();
1652  $params['cmd'][‪$table][$row['uid']]['move'] = $this->currentTable['prev'][$row['uid']];
1653  ‪$url = (string)$this->uriBuilder->buildUriFromRoute('tce_db', $params);
1654  $moveUpAction = '<a class="btn btn-default" href="' . htmlspecialchars(‪$url) . '" title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveUp')) . '">'
1655  . $this->iconFactory->getIcon('actions-move-up', IconSize::SMALL)->render() . '</a>';
1656  } else {
1657  $moveUpAction = ‪$this->spaceIcon;
1658  }
1659  $this->‪addActionToCellGroup($cells, $moveUpAction, 'moveUp');
1660 
1661  if (!$isL10nOverlay && !$isDeletePlaceHolder && !empty($this->currentTable['next'][$row['uid']])) {
1662  // Down
1663  $params = [];
1664  $params['redirect'] = $this->‪listURL();
1665  $params['cmd'][‪$table][$row['uid']]['move'] = $this->currentTable['next'][$row['uid']];
1666  ‪$url = (string)$this->uriBuilder->buildUriFromRoute('tce_db', $params);
1667  $moveDownAction = '<a class="btn btn-default" href="' . htmlspecialchars(‪$url) . '" title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:moveDown')) . '">'
1668  . $this->iconFactory->getIcon('actions-move-down', IconSize::SMALL)->render() . '</a>';
1669  } else {
1670  $moveDownAction = ‪$this->spaceIcon;
1671  }
1672  $this->‪addActionToCellGroup($cells, $moveDownAction, 'moveDown');
1673  }
1674 
1675  // "Delete" link:
1676  $disableDelete = (bool)\trim((string)($userTsConfig['options.']['disableDelete.'][‪$table] ?? $userTsConfig['options.']['disableDelete'] ?? ''));
1677  if ($permsEdit
1678  && !$disableDelete
1679  && ((‪$table === 'pages' && $localCalcPerms->deletePagePermissionIsGranted()) || (‪$table !== 'pages' && $this->calcPerms->editContentPermissionIsGranted()))
1680  && !$this->‪isRecordCurrentBackendUser($table, $row)
1681  && !$isDeletePlaceHolder
1682  ) {
1683  $actionName = 'delete';
1684  $recordInfo = BackendUtility::getRecordTitle(‪$table, $row);
1685  if ($this->‪getBackendUserAuthentication()->shallDisplayDebugInformation()) {
1686  $recordInfo .= ' [' . ‪$table . ':' . $row['uid'] . ']';
1687  }
1688  $refCountMsg = BackendUtility::referenceCount(
1689  ‪$table,
1690  $row['uid'],
1691  LF . $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'),
1692  (string)$this->‪getReferenceCount($table, $row['uid'])
1693  ) . BackendUtility::translationCount(
1694  ‪$table,
1695  $row['uid'],
1696  LF . $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord')
1697  );
1698  $warningText = sprintf($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:' . $actionName . 'Warning'), trim($recordInfo)) . $refCountMsg;
1699  $params = 'cmd[' . ‪$table . '][' . $row['uid'] . '][delete]=1';
1700  $icon = $this->iconFactory->getIcon('actions-edit-' . $actionName, IconSize::SMALL)->render();
1701  $linkTitle = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:' . $actionName));
1702  $titleText = $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:label.confirm.delete_record.title');
1703  $l10nParentField = ‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'] ?? '';
1704  $deleteAction = '<button type="button" class="btn btn-default t3js-record-delete"'
1705  . ' title="' . $linkTitle . '"'
1706  . ' aria-label="' . $linkTitle . '"'
1707  . ' aria-haspopup="dialog"'
1708  . ' data-button-ok-text="' . htmlspecialchars($linkTitle) . '"'
1709  . ' data-l10parent="' . ($l10nParentField ? htmlspecialchars((string)$row[$l10nParentField]) : '') . '"'
1710  . ' data-params="' . htmlspecialchars($params) . '"'
1711  . ' data-message="' . htmlspecialchars($warningText) . '"'
1712  . ' data-title="' . htmlspecialchars($titleText) . '">'
1713  . $icon
1714  . '</button>';
1715  } else {
1716  $deleteAction = ‪$this->spaceIcon;
1717  }
1718  $this->‪addActionToCellGroup($cells, $deleteAction, 'delete');
1719 
1720  // "Levels" links: Moving pages into new levels...
1721  if ($permsEdit && ‪$table === 'pages' && !$this->searchLevels) {
1722  // Up (Paste as the page right after the current parent page)
1723  if ($this->calcPerms->createPagePermissionIsGranted()) {
1724  if (!$isDeletePlaceHolder && !$isL10nOverlay) {
1725  $params = [];
1726  $params['redirect'] = $this->‪listURL();
1727  $params['cmd'][‪$table][$row['uid']]['move'] = -‪$this->id;
1728  ‪$url = (string)$this->uriBuilder->buildUriFromRoute('tce_db', $params);
1729  $label = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:prevLevel'));
1730  $moveLeftAction = '<a class="btn btn-default"'
1731  . ' href="' . htmlspecialchars(‪$url) . '"'
1732  . ' title="' . $label . '"'
1733  . ' aria-label="' . $label . '">'
1734  . $this->iconFactory->getIcon('actions-move-left', IconSize::SMALL)->render()
1735  . '</a>';
1736  $this->‪addActionToCellGroup($cells, $moveLeftAction, 'moveLeft');
1737  } else {
1738  $this->‪addActionToCellGroup($cells, $this->spaceIcon, 'moveLeft');
1739  }
1740  }
1741  // Down (Paste as subpage to the page right above)
1742  if (!$isL10nOverlay && !$isDeletePlaceHolder && !empty($this->currentTable['prevUid'][$row['uid']])) {
1743  $localCalcPerms = $this->‪getPagePermissionsForRecord(
1744  'pages',
1745  BackendUtility::getRecord('pages', $this->currentTable['prevUid'][$row['uid']]) ?? []
1746  );
1747  if ($localCalcPerms->createPagePermissionIsGranted()) {
1748  $params = [];
1749  $params['redirect'] = $this->‪listURL();
1750  $params['cmd'][‪$table][$row['uid']]['move'] = $this->currentTable['prevUid'][$row['uid']];
1751  ‪$url = (string)$this->uriBuilder->buildUriFromRoute('tce_db', $params);
1752  $label = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:nextLevel'));
1753  $moveRightAction = '<a class="btn btn-default"'
1754  . ' href="' . htmlspecialchars(‪$url) . '"'
1755  . ' title="' . $label . '"'
1756  . ' aria-label="' . $label . '">'
1757  . $this->iconFactory->getIcon('actions-move-right', IconSize::SMALL)->render() . '</a>';
1758  } else {
1759  $moveRightAction = ‪$this->spaceIcon;
1760  }
1761  } else {
1762  $moveRightAction = ‪$this->spaceIcon;
1763  }
1764  $this->‪addActionToCellGroup($cells, $moveRightAction, 'moveRight');
1765  }
1766  }
1767 
1768  // Add clipboard related actions
1769  $this->‪makeClip($table, $row, $cells);
1770 
1771  $event = $this->eventDispatcher->dispatch(
1772  new ModifyRecordListRecordActionsEvent($cells, ‪$table, $row, $this)
1773  );
1774 
1775  ‪$output = '';
1776  foreach ($event->getActions() as $classification => $actions) {
1777  if ($classification !== 'primary') {
1778  $cellOutput = '';
1779  foreach ($actions as $action) {
1780  if ($action === $this->spaceIcon) {
1781  continue;
1782  }
1783  // This is a backwards-compat layer for the existing hook items, which will be removed in TYPO3 v12.
1784  $action = str_replace('btn btn-default', 'dropdown-item dropdown-item-spaced', $action);
1785  $title = [];
1786  preg_match('/title="([^"]*)"/', $action, $title);
1787  if (empty($title)) {
1788  preg_match('/aria-label="([^"]*)"/', $action, $title);
1789  }
1790  if (!empty($title[1] ?? '')) {
1791  $action = str_replace(
1792  [
1793  '</a>',
1794  '</button>',
1795  ],
1796  [
1797  ' ' . $title[1] . '</a>',
1798  ' ' . $title[1] . '</button>',
1799  ],
1800  $action
1801  );
1802  // In case we added the title as tag content, we can remove the attribute,
1803  // since this is duplicated and would trigger a tooltip with the same content.
1804  if (!empty($title[0] ?? '')) {
1805  $action = str_replace($title[0], '', $action);
1806  }
1807  }
1808  $cellOutput .= '<li>' . $action . '</li>';
1809  }
1810 
1811  if ($cellOutput !== '') {
1812  $icon = $this->iconFactory->getIcon('actions-menu-alternative', IconSize::SMALL);
1813  $title = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.more');
1814  ‪$output .= ' <div class="btn-group dropdown" title="' . htmlspecialchars($title) . '">' .
1815  '<a href="#actions_' . ‪$table . '_' . $row['uid'] . '" class="btn btn-default dropdown-toggle dropdown-toggle-no-chevron" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">' . $icon->render() . '</a>' .
1816  '<ul id="actions_' . ‪$table . '_' . $row['uid'] . '" class="dropdown-menu">' . $cellOutput . '</ul>' .
1817  '</div>';
1818  } else {
1819  ‪$output .= ' <div class="btn-group">' . $this->spaceIcon . '</div>';
1820  }
1821  } else {
1822  ‪$output .= ' <div class="btn-group">' . implode('', $actions) . '</div>';
1823  }
1824  }
1825 
1826  return ‪$output;
1827  }
1828 
1836  public function ‪makeClip(string ‪$table, array $row, array &$cells): void
1837  {
1838  // Return, if disabled:
1839  if (!$this->‪isClipboardFunctionalityEnabled($table, $row)) {
1840  return;
1841  }
1842  $clipboardCells = [];
1843  $isEditable = $this->‪isEditable($table);
1844 
1845  if ($this->clipObj->current !== 'normal') {
1846  $clipboardCells['copy'] = $clipboardCells['cut'] = ‪$this->spaceIcon;
1847  } else {
1848  $this->‪addDividerToCellGroup($cells);
1849  $isSel = $this->clipObj->isSelected(‪$table, $row['uid']);
1850 
1851  $copyTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . ($isSel === 'copy' ? 'copyrelease' : 'copy'));
1852  $copyUrl = $this->clipObj->selUrlDB(‪$table, (int)$row['uid'], true, $isSel === 'copy');
1853  $clipboardCells['copy'] = '
1854  <a class="btn btn-default" href="' . htmlspecialchars($copyUrl) . '" title="' . htmlspecialchars($copyTitle) . '" aria-label="' . htmlspecialchars($copyTitle) . '">
1855  ' . $this->iconFactory->getIcon($isSel === 'copy' ? 'actions-edit-copy-release' : 'actions-edit-copy', IconSize::SMALL)->render() . '
1856  </a>';
1857 
1858  // Calculate permission to cut page or content
1859  if (‪$table === 'pages') {
1860  $localCalcPerms = $this->‪getPagePermissionsForRecord('pages', $row);
1861  $permsEdit = $localCalcPerms->editPagePermissionIsGranted();
1862  } else {
1863  $permsEdit = $this->calcPerms->editContentPermissionIsGranted() && $this->‪getBackendUserAuthentication()->recordEditAccessInternals($table, $row);
1864  }
1865  if (!$isEditable || !$this->‪overlayEditLockPermissions($table, $row, $permsEdit)) {
1866  $clipboardCells['cut'] = ‪$this->spaceIcon;
1867  } else {
1868  $cutTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . ($isSel === 'cut' ? 'cutrelease' : 'cut'));
1869  $cutUrl = $this->clipObj->selUrlDB(‪$table, (int)$row['uid'], false, $isSel === 'cut');
1870  $clipboardCells['cut'] = '
1871  <a class="btn btn-default" href="' . htmlspecialchars($cutUrl) . '" title="' . htmlspecialchars($cutTitle) . '" aria-label="' . htmlspecialchars($cutTitle) . '">
1872  ' . $this->iconFactory->getIcon($isSel === 'cut' ? 'actions-edit-cut-release' : 'actions-edit-cut', IconSize::SMALL)->render() . '
1873  </a>';
1874  }
1875  }
1876 
1877  // Now, looking for selected elements from the current table:
1878  $elFromTable = $this->clipObj->elFromTable(‪$table);
1879  if (!$isEditable
1880  || empty(‪$GLOBALS['TCA'][‪$table]['ctrl']['sortby'])
1881  || $this->clipObj->elFromTable(‪$table) === []
1882  || !$this->overlayEditLockPermissions(‪$table, $row)
1883  ) {
1884  $clipboardCells['pasteAfter'] = ‪$this->spaceIcon;
1885  } else {
1886  $this->‪addDividerToCellGroup($cells);
1887  $pasteAfterUrl = $this->clipObj->pasteUrl(‪$table, -$row['uid']);
1888  $pasteAfterTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_pasteAfter');
1889  $pasteAfterContent = $this->clipObj->confirmMsgText(‪$table, $row, 'after', $elFromTable);
1890  $clipboardCells['pasteAfter'] = '
1891  <button type="button" class="btn btn-default t3js-modal-trigger" data-severity="warning" aria-haspopup="dialog" title="' . htmlspecialchars($pasteAfterTitle) . '" aria-label="' . htmlspecialchars($pasteAfterTitle) . '" data-uri="' . htmlspecialchars($pasteAfterUrl) . '" data-bs-content="' . htmlspecialchars($pasteAfterContent) . '">
1892  ' . $this->iconFactory->getIcon('actions-document-paste-after', IconSize::SMALL)->render() . '
1893  </button>';
1894  }
1895 
1896  // Now, looking for elements in general:
1897  if (‪$table !== 'pages' || !$isEditable || $this->clipObj->elFromTable() === []) {
1898  $clipboardCells['pasteInto'] = ‪$this->spaceIcon;
1899  } else {
1901  $pasteIntoUrl = $this->clipObj->pasteUrl('', $row['uid']);
1902  $pasteIntoTitle = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_pasteInto');
1903  $pasteIntoContent = $this->clipObj->confirmMsgText(‪$table, $row, 'into', $elFromTable);
1904  $clipboardCells['pasteInto'] = '
1905  <button type="button" class="btn btn-default t3js-modal-trigger" aria-haspopup="dialog" data-severity="warning" title="' . htmlspecialchars($pasteIntoTitle) . '" aria-label="' . htmlspecialchars($pasteIntoTitle) . '" data-uri="' . htmlspecialchars($pasteIntoUrl) . '" data-bs-content="' . htmlspecialchars($pasteIntoContent) . '">
1906  ' . $this->iconFactory->getIcon('actions-document-paste-into', IconSize::SMALL)->render() . '
1907  </button>';
1908  }
1909 
1910  // Add the clipboard actions to the cell group
1911  foreach ($clipboardCells as $key => $value) {
1912  $this->‪addActionToCellGroup($cells, $value, $key);
1913  }
1914  }
1915 
1923  public function ‪makeCheckbox(string ‪$table, array $row): string
1924  {
1925  // Early return if current record is a "delete placeholder" or a translation
1926  if ($this->‪isRecordDeletePlaceholder($row)
1927  || (int)($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'] ?? null] ?? 0) !== 0
1928  ) {
1929  return '';
1930  }
1931 
1932  // In case clipObj is not set, just add a checkbox without any clipboard functionality
1933  if ($this->clipObj === null) {
1934  return '
1935  <span class="form-check form-check-type-toggle">
1936  <input class="form-check-input t3js-multi-record-selection-check" type="checkbox" />
1937  </span>';
1938  }
1939 
1940  // For the numeric clipboard pads (showing checkboxes where one can select elements on/off)
1941  // Setting name of the element in ->CBnames array:
1942  ‪$identifier = ‪$table . '|' . $row['uid'];
1943  $this->CBnames[] = ‪$identifier;
1944  $isSelected = false;
1945  // If the "duplicateField" value is set then select all elements which are duplicates...
1946  if ($this->duplicateField && isset($row[$this->duplicateField])) {
1947  $isSelected = in_array((string)$row[$this->duplicateField], $this->duplicateStack, true);
1948  $this->duplicateStack[] = (string)$row[$this->duplicateField];
1949  }
1950  // Adding the checkbox to the panel:
1951  return '
1952  <span class="form-check form-check-type-toggle">
1953  <input class="form-check-input t3js-multi-record-selection-check" type="checkbox" name="CBC[' . ‪$identifier . ']" value="1" ' . ($isSelected ? 'checked="checked" ' : '') . '/>
1954  </span>';
1955  }
1956 
1963  public function ‪makeLocalizationPanel(‪$table, $row, array $translations): string
1964  {
1965  $out = '';
1966  // All records excluding pages
1968  if (‪$table === 'pages') {
1969  // Calculate possible translations for pages
1970  ‪$possibleTranslations = array_map(static fn(SiteLanguage $siteLanguage): int => $siteLanguage->getLanguageId(), $this->languagesAllowedForUser);
1971  ‪$possibleTranslations = array_filter(‪$possibleTranslations, static fn(int $languageUid): bool => $languageUid > 0);
1972  }
1973 
1974  // Traverse page translations and add icon for each language that does NOT yet exist and is included in site configuration:
1975  $pageId = (int)(‪$table === 'pages' ? $row['uid'] : $row['pid']);
1976  $languageInformation = $this->translateTools->getSystemLanguages($pageId);
1977 
1978  foreach (‪$possibleTranslations as $lUid_OnPage) {
1979  if ($this->‪isEditable($table)
1980  && !$this->‪isRecordDeletePlaceholder($row)
1981  && !isset($translations[$lUid_OnPage])
1982  && $this->‪getBackendUserAuthentication()->checkLanguageAccess($lUid_OnPage)
1983  ) {
1984  $redirectUrl = (string)$this->uriBuilder->buildUriFromRoute(
1985  'record_edit',
1986  [
1987  'justLocalized' => ‪$table . ':' . $row['uid'] . ':' . $lUid_OnPage,
1988  'returnUrl' => $this->listURL(),
1989  ]
1990  );
1991  $params = [];
1992  $params['redirect'] = $redirectUrl;
1993  $params['cmd'][‪$table][$row['uid']]['localize'] = $lUid_OnPage;
1994  $href = (string)$this->uriBuilder->buildUriFromRoute('tce_db', $params);
1995  $title = htmlspecialchars($languageInformation[$lUid_OnPage]['title'] ?? '');
1996 
1997  $lC = ($languageInformation[$lUid_OnPage]['flagIcon'] ?? false)
1998  ? $this->iconFactory->getIcon($languageInformation[$lUid_OnPage]['flagIcon'], IconSize::SMALL)->render()
1999  : $title;
2000 
2001  $out .= '<a href="' . htmlspecialchars($href) . '"'
2002  . '" class="btn btn-default t3js-action-localize"'
2003  . ' title="' . $title . '">'
2004  . $lC . '</a> ';
2005  }
2006  }
2007  return $out;
2008  }
2009 
2010  /*********************************
2011  *
2012  * Helper functions
2013  *
2014  *********************************/
2015 
2026  public function ‪addSortLink($label, $field, ‪$table): string
2027  {
2028  // Certain circumstances just return string right away (no links):
2029  if ($this->disableSingleTableView
2030  || in_array($field, ['_SELECTOR', '_CONTROL_', '_LOCALIZATION_', '_REF_'], true)
2031  ) {
2032  return $label;
2033  }
2034 
2035  // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!)
2036  if ($field === '_PATH_') {
2037  $field = 'pid';
2038  }
2039 
2040  // Create the sort link:
2041  ‪$url = $this->‪listURL('', $table, 'sortField,sortRev,table,pointer')
2042  . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1);
2043  $icon = $this->sortField === $field
2044  ? $this->iconFactory->getIcon('actions-sort-amount-' . ($this->sortRev ? 'down' : 'up'), IconSize::SMALL)->render()
2045  : $this->iconFactory->getIcon('actions-sort-amount', IconSize::SMALL)->render();
2046 
2047  // Return linked field:
2048  $attributes = [
2049  'class' => 'table-sorting-button ' . ($this->sortField === $field ? 'table-sorting-button-active' : ''),
2050  'href' => ‪$url,
2051  ];
2052 
2053  return '<a ' . GeneralUtility::implodeAttributes($attributes, true) . '>
2054  <span class="table-sorting-label">' . $label . '</span>
2055  <span class="table-sorting-icon">' . $icon . '</span>
2056  </a>';
2057  }
2058 
2067  public function ‪recPath($pid)
2068  {
2069  if (!isset($this->recPath_cache[$pid])) {
2070  $this->recPath_cache[$pid] = BackendUtility::getRecordPath($pid, $this->perms_clause, 20);
2071  }
2072  return $this->recPath_cache[$pid];
2073  }
2074 
2079  protected function ‪getPagePermissionsForRecord(string ‪$table, array $row): ‪Permission
2080  {
2081  // If the listed table is 'pages' we have to request the permission settings for each page.
2082  // If the listed table is not 'pages' we have to request the permission settings from the parent page
2083  $pageId = (int)(‪$table === 'pages' ? ($row['l10n_parent'] ?: $row['uid']) : $row['pid']);
2084  if (!isset($this->pagePermsCache[$pageId])) {
2085  $this->pagePermsCache[$pageId] = new ‪Permission($this->‪getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $pageId)));
2086  }
2087  return $this->pagePermsCache[$pageId];
2088  }
2089 
2096  public function ‪showNewRecLink(‪$table)
2097  {
2098  // No deny/allow tables are set:
2099  if (empty($this->allowedNewTables) && empty($this->deniedNewTables)) {
2100  return true;
2101  }
2102  return !in_array(‪$table, $this->deniedNewTables)
2103  && (empty($this->allowedNewTables) || in_array(‪$table, $this->allowedNewTables));
2104  }
2105 
2113  public function ‪addActionToCellGroup(&$cells, $action, $actionKey)
2114  {
2115  $cellsMap = [
2116  'primary' => [
2117  'edit', 'hide', 'delete', 'moveUp', 'moveDown',
2118  ],
2119  'secondary' => [
2120  'view', 'viewBig', 'history', 'stat', 'perms', 'new', 'move', 'moveLeft', 'moveRight', 'version', 'divider', 'copy', 'cut', 'pasteAfter', 'pasteInto',
2121  ],
2122  ];
2123  $classification = in_array($actionKey, $cellsMap['primary']) ? 'primary' : 'secondary';
2124  $cells[$classification][$actionKey] = $action;
2125  unset($cells[$actionKey]);
2126  }
2127 
2135  protected function ‪isRecordCurrentBackendUser(‪$table, $row)
2136  {
2137  return ‪$table === 'be_users' && (int)($row['uid'] ?? 0) === (int)$this->‪getBackendUserAuthentication()->user['uid'];
2138  }
2139 
2143  protected function ‪isRecordDeletePlaceholder(array $row): bool
2144  {
2145  return $this->‪getBackendUserAuthentication()->workspace > 0
2146  && VersionState::tryFrom($row['t3ver_state'] ?? 0) === VersionState::DELETE_PLACEHOLDER;
2147  }
2148 
2149  public function ‪setIsEditable(bool $isEditable): void
2150  {
2151  $this->editable = $isEditable;
2152  }
2157  public function ‪isEditable(string ‪$table): bool
2158  {
2159  $backendUser = $this->‪getBackendUserAuthentication();
2160  return !(‪$GLOBALS['TCA'][‪$table]['ctrl']['readOnly'] ?? false)
2161  && $this->editable
2162  && ($backendUser->isAdmin() || $backendUser->check('tables_modify', ‪$table))
2163  && (BackendUtility::isTableWorkspaceEnabled(‪$table) || $backendUser->workspaceAllowsLiveEditingInTable(‪$table));
2164  }
2165 
2176  protected function ‪overlayEditLockPermissions(‪$table, $row = [], $editPermission = true)
2177  {
2178  if ($editPermission && !$this->‪getBackendUserAuthentication()->isAdmin()) {
2179  // If no $row is submitted we only check for general edit lock of current page (except for table "pages")
2180  $pageHasEditLock = !empty($this->pageRow['editlock']);
2181  if (empty($row)) {
2182  return (‪$table === 'pages') || !$pageHasEditLock;
2183  }
2184  if ((‪$table === 'pages' && ($row['editlock'] ?? false)) || (‪$table !== 'pages' && $pageHasEditLock)) {
2185  $editPermission = false;
2186  } elseif (isset(‪$GLOBALS['TCA'][‪$table]['ctrl']['editlock']) && ($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['editlock']] ?? false)) {
2187  $editPermission = false;
2188  }
2189  }
2190  return $editPermission;
2191  }
2192 
2193  public function ‪setModuleData(ModuleData ‪$moduleData): void
2194  {
2195  $this->moduleData = ‪$moduleData;
2196  }
2197 
2208  public function ‪start(‪$id, ‪$table, $pointer, $search = '', $levels = 0, ‪$showLimit = 0)
2209  {
2210  $backendUser = $this->‪getBackendUserAuthentication();
2211  // Setting internal variables:
2212  // sets the parent id
2213  $this->id = (int)‪$id;
2214  if (‪$GLOBALS['TCA'][‪$table] ?? false) {
2215  // Setting single table mode, if table exists:
2216  $this->table = ‪$table;
2217  }
2218  $this->page = ‪MathUtility::forceIntegerInRange((int)$pointer, 1, 1000);
2219  $this->showLimit = ‪MathUtility::forceIntegerInRange((int)‪$showLimit, 0, 10000);
2220  $this->searchString = trim($search);
2221  $this->searchLevels = (int)$levels;
2222  $this->sortField = (string)($this->request->getParsedBody()['sortField'] ?? $this->request->getQueryParams()['sortField'] ?? '');
2223  $this->sortRev = (bool)($this->request->getParsedBody()['sortRev'] ?? $this->request->getQueryParams()['sortRev'] ?? false);
2224  $this->duplicateField = (string)($this->request->getParsedBody()['duplicateField'] ?? $this->request->getQueryParams()['duplicateField'] ?? '');
2225 
2226  // If there is a current link to a record, set the current link uid and get the table name from the link handler configuration
2227  $currentLinkValue = trim($this->overrideUrlParameters['P']['currentValue'] ?? '');
2228  if ($currentLinkValue) {
2229  $linkService = GeneralUtility::makeInstance(LinkService::class);
2230  try {
2231  $currentLinkParts = $linkService->resolve($currentLinkValue);
2232  if ($currentLinkParts['type'] === 'record' && isset($currentLinkParts['identifier'])) {
2233  $this->currentLink['tableNames'] = ‪$this->tableList;
2234  $this->currentLink['uid'] = (int)$currentLinkParts['uid'];
2235  }
2236  } catch (UnknownLinkHandlerException $e) {
2237  }
2238  }
2239 
2240  // $table might be NULL at this point in the code. As the expressionBuilder
2241  // is used to limit returned records based on the page permissions and the
2242  // uid field of the pages it can hardcoded to work on the pages table.
2243  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2244  ->getQueryBuilderForTable('pages')
2245  ->expr();
2246  $permsClause = $expressionBuilder->and($backendUser->getPagePermsClause(‪Permission::PAGE_SHOW));
2247  // This will hide records from display - it has nothing to do with user rights!!
2248  $pidList = GeneralUtility::intExplode(',', (string)($backendUser->getTSConfig()['options.']['hideRecords.']['pages'] ?? ''), true);
2249  if (!empty($pidList)) {
2250  $permsClause = $permsClause->with($expressionBuilder->notIn('pages.uid', $pidList));
2251  }
2252  $this->perms_clause = (string)$permsClause;
2253 
2254  $this->possibleTranslations = $this->‪getPossibleTranslations($this->id);
2255  $this->setFields = $this->‪getBackendUserAuthentication()->getModuleData('list/displayFields') ?? [];
2256  }
2257 
2263  public function ‪generateList(): string
2264  {
2265  $tableNames = $this->‪getTablesToRender();
2266  ‪$output = '';
2267  foreach ($tableNames as $tableName) {
2268  ‪$output .= $this->‪getTable($tableName);
2269  }
2270  return ‪$output;
2271  }
2272 
2279  protected function ‪getTablesToRender(): array
2280  {
2281  $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
2282  $backendUser = $this->‪getBackendUserAuthentication();
2283 
2284  // pre-process tables and add sorting instructions
2285  $tableNames = array_flip(array_keys(‪$GLOBALS['TCA']));
2286  foreach ($tableNames as $tableName => $_) {
2287  $hideTable = false;
2288 
2289  // Checking if the table should be rendered:
2290  // Checks that we see only permitted/requested tables:
2291  if (($this->table && $tableName !== $this->table)
2292  || ($this->tableList && !‪GeneralUtility::inList($this->tableList, (string)$tableName))
2293  || !$backendUser->check('tables_select', $tableName)
2294  ) {
2295  $hideTable = true;
2296  }
2297 
2298  if (!$hideTable) {
2299  // Don't show table if hidden by TCA ctrl section
2300  // Don't show table if hidden by page TSconfig mod.web_list.hideTables
2301  $hideTable = !empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
2302  || in_array($tableName, $hideTablesArray, true)
2303  || in_array('*', $hideTablesArray, true);
2304  // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
2305  $hideTable = (bool)($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'] ?? $hideTable);
2306  }
2307  if ($hideTable) {
2308  unset($tableNames[$tableName]);
2309  } else {
2310  if (isset($this->tableDisplayOrder[$tableName])) {
2311  // Copy display order information
2312  $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
2313  } else {
2314  $tableNames[$tableName] = [];
2315  }
2316  }
2317  }
2318  try {
2319  $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
2320  ->orderByDependencies($tableNames);
2321  } catch (\UnexpectedValueException $e) {
2322  // If you have circular dependencies we just keep the original order and give a notice
2323  // Example mod.web_list.tableDisplayOrder.pages.after = tt_content
2324  $lang = $this->‪getLanguageService();
2325  $header = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.tableDisplayOrder.title');
2326  $msg = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.tableDisplayOrder.message');
2327  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $msg, $header, ContextualFeedbackSeverity::WARNING, true);
2328  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
2329  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
2330  $defaultFlashMessageQueue->enqueue($flashMessage);
2331  $orderedTableNames = $tableNames;
2332  }
2333  return array_keys($orderedTableNames);
2334  }
2335 
2343  public function ‪getQueryBuilder(
2344  string ‪$table,
2345  array ‪$fields = ['*'],
2346  bool $addSorting = true,
2347  int $firstResult = 0,
2348  int $maxResult = 0
2349  ): QueryBuilder {
2350  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2351  ->getQueryBuilderForTable(‪$table);
2352  $queryBuilder->getRestrictions()
2353  ->removeAll()
2354  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2355  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUserAuthentication()->workspace));
2356  $queryBuilder
2357  ->select(...‪$fields)
2358  ->from(‪$table);
2359 
2360  // Additional constraints
2361  if ((‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'] ?? false)
2362  && (‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'] ?? false)) {
2363  // Only restrict to the default language if no search request is in place
2364  // And if only translations should be shown
2365  if ($this->searchString === '' && !$this->‪showOnlyTranslatedRecords) {
2366  $queryBuilder->andWhere(
2367  $queryBuilder->expr()->or(
2368  $queryBuilder->expr()->lte(‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'], 0),
2369  $queryBuilder->expr()->eq(‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'], 0)
2370  )
2371  );
2372  }
2373  }
2374  if (‪$table === 'pages' && $this->‪showOnlyTranslatedRecords) {
2375  $queryBuilder->andWhere(
2376  $queryBuilder->expr()->in(
2377  ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'],
2378  array_keys($this->languagesAllowedForUser)
2379  )
2380  );
2381  }
2382  // Former prepareQueryBuilder
2383  if ($maxResult > 0) {
2384  $queryBuilder->setMaxResults($maxResult);
2385  }
2386  if ($firstResult > 0) {
2387  $queryBuilder->setFirstResult($firstResult);
2388  }
2389  if ($addSorting) {
2390  if ($this->sortField && in_array($this->sortField, BackendUtility::getAllowedFieldsForTable(‪$table, false))) {
2391  $queryBuilder->orderBy($this->sortField, $this->sortRev ? 'DESC' : 'ASC');
2392  } else {
2393  $orderBy = (‪$GLOBALS['TCA'][‪$table]['ctrl']['sortby'] ?? '') ?: ‪$GLOBALS['TCA'][‪$table]['ctrl']['default_sortby'] ?? '';
2394  $orderBys = ‪QueryHelper::parseOrderBy($orderBy);
2395  foreach ($orderBys as $orderBy) {
2396  $queryBuilder->addOrderBy($orderBy[0], $orderBy[1]);
2397  }
2398  }
2399  }
2400 
2401  // Build the query constraints
2402  $queryBuilder = $this->‪addPageIdConstraint($table, $queryBuilder, $this->searchLevels);
2403  $searchWhere = $this->‪makeSearchString($table, $this->id, $queryBuilder);
2404  if (!empty($searchWhere)) {
2405  $queryBuilder->andWhere($searchWhere);
2406  }
2407 
2408  // Filtering on displayable pages (permissions):
2409  if (‪$table === 'pages' && $this->perms_clause) {
2410  $queryBuilder->andWhere($this->perms_clause);
2411  }
2412 
2413  // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
2414  if (!empty(‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'])
2415  && (‪GeneralUtility::inList($this->hideTranslations, ‪$table) || $this->hideTranslations === '*')
2416  ) {
2417  $queryBuilder->andWhere(
2418  $queryBuilder->expr()->eq(
2419  ‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'],
2420  0
2421  )
2422  );
2423  } elseif (!empty(‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField']) && $this->‪showOnlyTranslatedRecords) {
2424  // When only translated records should be shown, it is necessary to use l10n_parent=pageId, instead of
2425  // a check to the PID
2426  $queryBuilder->andWhere(
2427  $queryBuilder->expr()->eq(
2428  ‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'],
2429  $queryBuilder->createNamedParameter(
2430  $this->id,
2432  )
2433  )
2434  );
2435  }
2436 
2438  $queryBuilder,
2439  ‪$table,
2440  $this->id,
2441  ‪$fields,
2442  $firstResult,
2443  $maxResult,
2444  $this
2445  );
2446  $this->eventDispatcher->dispatch($event);
2447  return $event->getQueryBuilder();
2448  }
2449 
2458  protected function ‪makeSearchString(string ‪$table, int $currentPid, QueryBuilder $queryBuilder)
2459  {
2460  $expressionBuilder = $queryBuilder->expr();
2461  $constraints = [];
2462  $tablePidField = ‪$table === 'pages' ? 'uid' : 'pid';
2463  // Make query only if table is valid and a search string is actually defined
2464  if (empty($this->searchString)) {
2465  return '';
2466  }
2467 
2468  $searchableFields = [];
2469  // Get fields from ctrl section of TCA first
2470  if (isset(‪$GLOBALS['TCA'][‪$table]['ctrl']['searchFields'])) {
2471  $searchableFields = GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][‪$table]['ctrl']['searchFields'], true);
2472  }
2473 
2474  if (‪MathUtility::canBeInterpretedAsInteger($this->searchString)) {
2475  $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
2476  foreach ($searchableFields as $fieldName) {
2477  if (!isset(‪$GLOBALS['TCA'][‪$table]['columns'][$fieldName])) {
2478  continue;
2479  }
2480  $fieldConfig = ‪$GLOBALS['TCA'][‪$table]['columns'][$fieldName]['config'];
2481  $fieldType = $fieldConfig['type'];
2482  if (($fieldType === 'number' && ($fieldConfig['format'] ?? 'integer') === 'integer')
2483  || ($fieldType === 'datetime' && !in_array($fieldConfig['dbType'] ?? '', ‪QueryHelper::getDateTimeTypes(), true))
2484  ) {
2485  if (!isset($fieldConfig['search']['pidonly'])
2486  || ($fieldConfig['search']['pidonly'] && $currentPid > 0)
2487  ) {
2488  $constraints[] = $expressionBuilder->and(
2489  $expressionBuilder->eq($fieldName, (int)$this->searchString),
2490  $expressionBuilder->eq($tablePidField, (int)$currentPid)
2491  );
2492  }
2493  } elseif ($this->‪isTextFieldType($fieldType)) {
2494  $constraints[] = $expressionBuilder->like(
2495  $fieldName,
2496  $queryBuilder->quote('%' . (int)$this->searchString . '%')
2497  );
2498  }
2499  }
2500  } elseif (!empty($searchableFields)) {
2501  $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
2502  foreach ($searchableFields as $fieldName) {
2503  if (!isset(‪$GLOBALS['TCA'][‪$table]['columns'][$fieldName])) {
2504  continue;
2505  }
2506  $fieldConfig = ‪$GLOBALS['TCA'][‪$table]['columns'][$fieldName]['config'];
2507  $fieldType = $fieldConfig['type'];
2508  $searchConstraint = $expressionBuilder->and(
2509  $expressionBuilder->comparison(
2510  'LOWER(' . $queryBuilder->castFieldToTextType($fieldName) . ')',
2511  'LIKE',
2512  'LOWER(' . $like . ')'
2513  )
2514  );
2515  if (is_array($fieldConfig['search'] ?? null)) {
2516  $searchConfig = $fieldConfig['search'];
2517  if ($searchConfig['case'] ?? false) {
2518  // Replace case insensitive default constraint
2519  $searchConstraint = $expressionBuilder->and($expressionBuilder->like($fieldName, $like));
2520  }
2521  if (($searchConfig['pidonly'] ?? false) && $currentPid > 0) {
2522  $searchConstraint = $searchConstraint->with($expressionBuilder->eq($tablePidField, (int)$currentPid));
2523  }
2524  if ($searchConfig['andWhere'] ?? false) {
2525  $searchConstraint = $searchConstraint->with(
2526  ‪QueryHelper::quoteDatabaseIdentifiers($queryBuilder->getConnection(), ‪QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere']))
2527  );
2528  }
2529  }
2530  if ($this->‪isTextFieldType($fieldType) && $searchConstraint->count() !== 0) {
2531  $constraints[] = $searchConstraint;
2532  }
2533  }
2534  }
2535  // If no search field conditions have been built ensure no results are returned
2536  if (empty($constraints)) {
2537  return '0=1';
2538  }
2539 
2540  return (string)$expressionBuilder->or(...$constraints);
2541  }
2542 
2551  public function ‪linkWrapTable(string ‪$table, string $label): string
2552  {
2553  if ($this->table !== ‪$table) {
2554  ‪$url = $this->‪listURL('', $table, 'pointer');
2555  } else {
2556  ‪$url = $this->‪listURL('', '', 'sortField,sortRev,table,pointer');
2557  }
2558  return '<a href="' . htmlspecialchars(‪$url) . '">' . $label . '</a>';
2559  }
2560 
2570  public function ‪linkWrapItems(‪$table, ‪$uid, $code, $row)
2571  {
2572  $lang = $this->‪getLanguageService();
2573  $origCode = $code;
2574  // If the title is blank, make a "no title" label:
2575  if ((string)$code === '') {
2576  $code = '<i>[' . htmlspecialchars(
2577  $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.no_title')
2578  ) . ']</i> - '
2579  . htmlspecialchars(BackendUtility::getRecordTitle(‪$table, $row));
2580  } else {
2581  $code = htmlspecialchars($code);
2582  }
2583  switch ((string)$this->clickTitleMode) {
2584  case 'edit':
2585  // If the listed table is 'pages' we have to request the permission settings for each page:
2586  if (‪$table === 'pages') {
2587  $localCalcPerms = $this->‪getPagePermissionsForRecord('pages', $row);
2588  $permsEdit = $localCalcPerms->editPagePermissionIsGranted();
2589  } else {
2590  $backendUser = $this->‪getBackendUserAuthentication();
2591  $permsEdit = $this->calcPerms->editContentPermissionIsGranted() && $backendUser->recordEditAccessInternals(‪$table, $row);
2592  }
2593  // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
2594  if ($permsEdit && $this->‪isEditable($table)) {
2595  $params = [
2596  'edit' => [
2597  ‪$table => [
2598  $row['uid'] => 'edit',
2599  ],
2600  ],
2601  'returnUrl' => $this->‪listURL(),
2602  ];
2603  $editLink = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
2604  $label = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:edit'));
2605  $code = '<a href="' . htmlspecialchars($editLink) . '"'
2606  . ' title="' . $label . '"'
2607  . ' aria-label="' . $label . '">'
2608  . $code . '</a>';
2609  }
2610  break;
2611  case 'show':
2612  // "Show" link (only pages and tt_content elements)
2613  if ((‪$table === 'pages' || ‪$table === 'tt_content')
2614  && ($attributes = $this->‪getPreviewUriBuilder($table, $row)->serializeDispatcherAttributes()) !== null
2615  ) {
2616  $title = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'));
2617  $code = '<button ' . $attributes
2618  . ' title="' . $title . '"'
2619  . ' aria-label="' . $title . '">'
2620  . $code . '</button>';
2621  }
2622  break;
2623  case 'info':
2624  // "Info": (All records)
2625  $label = htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo'));
2626  $code = '<a href="#" role="button"' // @todo add handler that triggers click on space key
2627  . $this->‪createShowItemTagAttributes($table . ',' . (int)$row['uid'])
2628  . ' title="' . $label . '"'
2629  . ' aria-label="' . $label . '"'
2630  . ' aria-haspopup="dialog">'
2631  . $code
2632  . '</a>';
2633  break;
2634  default:
2635  // Output the label now:
2636  if (‪$table === 'pages') {
2637  $code = '<a href="' . htmlspecialchars(
2638  $this->‪listURL((string)$uid, '', 'pointer')
2639  ) . '">' . $code . '</a>';
2640  } else {
2641  $code = $this->‪linkUrlMail($code, $origCode);
2642  }
2643  }
2644  return $code;
2645  }
2646 
2654  protected function ‪linkUrlMail(string $code, string $testString): string
2655  {
2656  // Check for URL:
2657  $scheme = parse_url($testString, PHP_URL_SCHEME);
2658  if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
2659  return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
2660  }
2661  // Check for email:
2662  if (GeneralUtility::validEmail($testString)) {
2663  return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
2664  }
2665  // Return if nothing else...
2666  return $code;
2667  }
2668 
2679  public function ‪listURL($altId = '', ‪$table = '-1', $exclList = '')
2680  {
2681  $urlParameters = [];
2682  if ((string)$altId !== '') {
2683  $urlParameters['id'] = $altId;
2684  } else {
2685  $urlParameters['id'] = ‪$this->id;
2686  }
2687  if (‪$table === '-1') {
2688  $urlParameters['table'] = ‪$this->table;
2689  } else {
2690  $urlParameters['table'] = ‪$table;
2691  }
2692  if ($this->returnUrl) {
2693  $urlParameters['returnUrl'] = ‪$this->returnUrl;
2694  }
2695  if ((!$exclList || !‪GeneralUtility::inList($exclList, 'searchTerm')) && $this->searchString) {
2696  $urlParameters['searchTerm'] = ‪$this->searchString;
2697  }
2698  if ($this->searchLevels) {
2699  $urlParameters['search_levels'] = ‪$this->searchLevels;
2700  }
2701  if ((!$exclList || !‪GeneralUtility::inList($exclList, 'pointer')) && $this->page) {
2702  $urlParameters['pointer'] = ‪$this->page;
2703  }
2704  if ((!$exclList || !‪GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
2705  $urlParameters['sortField'] = ‪$this->sortField;
2706  }
2707  if ((!$exclList || !‪GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
2708  $urlParameters['sortRev'] = ‪$this->sortRev;
2709  }
2710 
2711  return (string)$this->uriBuilder->buildUriFromRequest(
2712  $this->request,
2713  array_replace($urlParameters, $this->overrideUrlParameters)
2714  );
2715  }
2716 
2722  public function ‪setOverrideUrlParameters(array $urlParameters, ServerRequestInterface ‪$request)
2723  {
2724  $currentUrlParameter = ‪$request->getParsedBody()['curUrl'] ?? ‪$request->getQueryParams()['curUrl'] ?? '';
2725  if (isset($currentUrlParameter['url'])) {
2726  $urlParameters['P']['currentValue'] = $currentUrlParameter['url'];
2727  }
2728  $this->overrideUrlParameters = $urlParameters;
2729  }
2730 
2743  public function ‪setTableDisplayOrder(array $orderInformation)
2744  {
2745  foreach ($orderInformation as $tableName => &$configuration) {
2746  if (isset($configuration['before'])) {
2747  if (is_string($configuration['before'])) {
2748  $configuration['before'] = GeneralUtility::trimExplode(',', $configuration['before'], true);
2749  } elseif (!is_array($configuration['before'])) {
2750  throw new \UnexpectedValueException(
2751  'The specified "before" order configuration for table "' . $tableName . '" is invalid.',
2752  1504793406
2753  );
2754  }
2755  }
2756  if (isset($configuration['after'])) {
2757  if (is_string($configuration['after'])) {
2758  $configuration['after'] = GeneralUtility::trimExplode(',', $configuration['after'], true);
2759  } elseif (!is_array($configuration['after'])) {
2760  throw new \UnexpectedValueException(
2761  'The specified "after" order configuration for table "' . $tableName . '" is invalid.',
2762  1504793407
2763  );
2764  }
2765  }
2766  }
2767  $this->tableDisplayOrder = $orderInformation;
2768  }
2769 
2770  public function ‪getOverridePageIdList(): array
2771  {
2773  }
2774 
2778  public function ‪setOverridePageIdList(array ‪$overridePageIdList)
2779  {
2780  $this->overridePageIdList = array_map(intval(...), ‪$overridePageIdList);
2781  }
2782 
2790  protected function ‪getSearchableWebmounts(int ‪$id, int $depth): array
2791  {
2792  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
2793  $hash = 'webmounts_list' . md5(‪$id . '-' . $depth . '-' . $this->perms_clause);
2794  $idList = $runtimeCache->get($hash);
2795  if ($idList === false) {
2796  $backendUser = $this->‪getBackendUserAuthentication();
2797 
2798  if (!$backendUser->isAdmin() && ‪$id === 0) {
2799  $mountPoints = array_map(intval(...), $backendUser->returnWebmounts());
2800  $mountPoints = array_unique($mountPoints);
2801  } else {
2802  $mountPoints = [‪$id];
2803  }
2804  // Add the initial mount points to the pids
2805  $idList = $mountPoints;
2806  $repository = GeneralUtility::makeInstance(PageTreeRepository::class);
2807  $repository->setAdditionalWhereClause($this->perms_clause);
2808  $pages = $repository->getFlattenedPages($mountPoints, $depth);
2809  foreach ($pages as ‪$page) {
2810  $idList[] = (int)‪$page['uid'];
2811  }
2812  $idList = array_unique($idList);
2813  $runtimeCache->set($hash, $idList);
2814  }
2815 
2816  return $idList;
2817  }
2818 
2825  protected function ‪addPageIdConstraint(string $tableName, QueryBuilder $queryBuilder, int ‪$searchLevels): QueryBuilder
2826  {
2827  // Set search levels to 999 instead of -1 as the following methods
2828  // do not support -1 as valid value for infinite search.
2829  if (‪$searchLevels === -1) {
2830  ‪$searchLevels = 999;
2831  }
2832 
2833  // When querying translated pages, the PID of the translated pages should be the same as the
2834  // the PID of the current page
2835  if ($tableName === 'pages' && $this->‪showOnlyTranslatedRecords) {
2836  $pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
2837  $queryBuilder->andWhere(
2838  $queryBuilder->expr()->eq(
2839  $tableName . '.pid',
2840  $queryBuilder->createNamedParameter($pageRecord['pid'] ?? 0, ‪Connection::PARAM_INT)
2841  )
2842  );
2843  } elseif (‪$searchLevels === 0) {
2844  $queryBuilder->andWhere(
2845  $queryBuilder->expr()->eq(
2846  $tableName . '.pid',
2847  $queryBuilder->createNamedParameter($this->id, ‪Connection::PARAM_INT)
2848  )
2849  );
2850  } elseif (‪$searchLevels > 0) {
2851  $allowedMounts = $this->‪getSearchableWebmounts($this->id, $searchLevels);
2852  $queryBuilder->andWhere(
2853  $queryBuilder->expr()->in(
2854  $tableName . '.pid',
2855  $queryBuilder->createNamedParameter($allowedMounts, Connection::PARAM_INT_ARRAY)
2856  )
2857  );
2858  }
2859 
2860  if (!empty($this->‪getOverridePageIdList())) {
2861  $queryBuilder->andWhere(
2862  $queryBuilder->expr()->in(
2863  $tableName . '.pid',
2864  $queryBuilder->createNamedParameter($this->getOverridePageIdList(), Connection::PARAM_INT_ARRAY)
2865  )
2866  );
2867  }
2868 
2869  return $queryBuilder;
2870  }
2871 
2873  {
2874  return ‪$GLOBALS['BE_USER'];
2875  }
2876 
2887  protected function ‪addElement($data, $rowParams = '', $colType = 'td')
2888  {
2889  $colType = ($colType === 'th') ? 'th' : 'td';
2890  $dataUid = ($colType === 'td') ? ($data['uid'] ?? 0) : 0;
2891  $l10nParent = $data['_l10nparent_'] ?? 0;
2892  $out = '<tr ' . $rowParams . ' data-uid="' . $dataUid . '" data-l10nparent="' . $l10nParent . '" data-multi-record-selection-element="true">';
2893 
2894  // Init rendering.
2895  $colsp = '';
2896  $lastKey = '';
2897  $c = 0;
2898  // __label is used as the label key to circumvent problems with uid used as label (see #67756)
2899  // as it was introduced later on, check if it really exists before using it
2901  if ($colType === 'td' && isset($data['__label'])) {
2902  // The title label column does always follow the icon column. Since
2903  // in some cases the first column - "_SELECTOR_" - might not be rendered,
2904  // we always have to calculate the key by searching for the icon column.
2905  $titleLabelKey = (int)(array_search('icon', ‪$fields, true)) + 1;
2906  ‪$fields[$titleLabelKey] = '__label';
2907  }
2908  // Traverse field array which contains the data to present:
2909  foreach (‪$fields as $vKey) {
2910  if (isset($data[$vKey])) {
2911  if ($lastKey) {
2912  $cssClass = $this->addElement_tdCssClass[$lastKey] ?? '';
2913  $out .= '
2914  <' . $colType . ' class="' . $cssClass . ' nowrap' . '"' . $colsp . '>' . $data[$lastKey] . '</' . $colType . '>';
2915  }
2916  $lastKey = $vKey;
2917  $c = 1;
2918  } else {
2919  if (!$lastKey) {
2920  $lastKey = $vKey;
2921  }
2922  $c++;
2923  }
2924  if ($c > 1) {
2925  $colsp = ' colspan="' . $c . '"';
2926  } else {
2927  $colsp = '';
2928  }
2929  }
2930  if ($lastKey) {
2931  $cssClass = $this->addElement_tdCssClass[$lastKey] ?? '';
2932  $out .= '
2933  <' . $colType . ' class="' . $cssClass . ' nowrap' . '"' . $colsp . '>' . $data[$lastKey] . '</' . $colType . '>';
2934  }
2935  // End row
2936  $out .= '
2937  </tr>';
2938  return $out;
2939  }
2940 
2950  protected function ‪getPossibleTranslations(int $pageUid): array
2951  {
2952  // Store languages that are included in the site configuration for the current page.
2953  $availableSystemLanguageUids = array_keys($this->translateTools->getSystemLanguages($pageUid));
2954  if ($availableSystemLanguageUids === []) {
2955  return [];
2956  }
2957  // Look up page overlays:
2958  $localizationParentField = ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] ?? '';
2959  $languageField = ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'] ?? '';
2960  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
2961  ->getQueryBuilderForTable('pages');
2962  $queryBuilder->getRestrictions()
2963  ->removeAll()
2964  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2965  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUserAuthentication()->workspace));
2966  $result = $queryBuilder
2967  ->select('*')
2968  ->from('pages')
2969  ->where(
2970  $queryBuilder->expr()->and(
2971  $queryBuilder->expr()->eq($localizationParentField, $queryBuilder->createNamedParameter($pageUid, ‪Connection::PARAM_INT)),
2972  $queryBuilder->expr()->in($languageField, $queryBuilder->createNamedParameter($availableSystemLanguageUids, Connection::PARAM_INT_ARRAY)),
2973  $queryBuilder->expr()->gt(
2974  $languageField,
2975  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
2976  )
2977  )
2978  )
2979  ->executeQuery();
2980  $allowedTranslationsOnPage = [];
2981  while ($row = $result->fetchAssociative()) {
2982  $allowedTranslationsOnPage[] = (int)$row[$languageField];
2983  }
2984  return $allowedTranslationsOnPage;
2985  }
2986 
2992  protected function ‪languageFlag(string ‪$table, array $row): string
2993  {
2994  $pageId = (int)(‪$table === 'pages' ? ($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField']] ?: $row['uid']) : $row['pid']);
2995  $languageUid = (int)($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'] ?? null] ?? 0);
2996  $languageInformation = $this->translateTools->getSystemLanguages($pageId);
2997  $title = htmlspecialchars($languageInformation[$languageUid]['title'] ?? '');
2998  $indent = $this->‪isLocalized($table, $row) ? '<span class="indent indent-inline-block" style="--indent-level: 1"></span> ' : '';
2999  if ($languageInformation[$languageUid]['flagIcon'] ?? false) {
3000  return $indent . $this->iconFactory
3001  ->getIcon($languageInformation[$languageUid]['flagIcon'], IconSize::SMALL)
3002  ->setTitle($title)
3003  ->render() . ' ' . $title;
3004  }
3005  return $title;
3006  }
3007 
3012  protected function ‪generateReferenceToolTip(string ‪$table, int ‪$uid): string
3013  {
3014  $numberOfReferences = $this->‪getReferenceCount($table, ‪$uid);
3015  if (!$numberOfReferences) {
3016  $htmlCode = '<button type="button" class="btn btn-default" disabled><span style="display:inline-block;min-width:16px">-</span></button>';
3017  } else {
3018  $showReferences = $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:show_references');
3019  $htmlCode = '<button type="button"'
3020  . ' class="btn btn-default"'
3021  . ' aria-haspopup="dialog"'
3022  . ' ' . $this->‪createShowItemTagAttributes($table . ',' . ‪$uid)
3023  . ' title="' . htmlspecialchars($showReferences) . ' (' . $numberOfReferences . ')' . '">'
3024  . '<span style="display:inline-block;min-width:16px">'
3025  . $numberOfReferences
3026  . '<span class="visually-hidden">' . $showReferences . '</span>'
3027  . '</span>'
3028  . '</button>';
3029  }
3030  return $htmlCode;
3031  }
3032 
3038  protected function ‪renderCheckboxActions(): string
3039  {
3040  $lang = $this->‪getLanguageService();
3041 
3042  $dropdownItems['checkAll'] = '
3043  <li>
3044  <button type="button" class="dropdown-item disabled" data-multi-record-selection-check-action="check-all" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) . '">
3045  ' . $this->iconFactory->getIcon('actions-selection-elements-all', IconSize::SMALL)->render() . '
3046  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) . '
3047  </button>
3048  </li>';
3049 
3050  $dropdownItems['checkNone'] = '
3051  <li>
3052  <button type="button" class="dropdown-item disabled" data-multi-record-selection-check-action="check-none" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) . '">
3053  ' . $this->iconFactory->getIcon('actions-selection-elements-none', IconSize::SMALL)->render() . '
3054  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) . '
3055  </button>
3056  </li>';
3058  $dropdownItems['toggleSelection'] = '
3059  <li>
3060  <button type="button" class="dropdown-item disabled" data-multi-record-selection-check-action="toggle" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) . '">
3061  ' . $this->iconFactory->getIcon('actions-selection-elements-invert', IconSize::SMALL)->render() . '
3062  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) . '
3063  </button>
3064  </li>';
3065 
3066  return '
3067  <div class="btn-group dropdown">
3068  <button type="button" class="dropdown-toggle dropdown-toggle-link t3js-multi-record-selection-check-actions-toggle" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">
3069  ' . $this->iconFactory->getIcon('actions-selection', IconSize::SMALL) . '
3070  </button>
3071  <ul class="dropdown-menu t3js-multi-record-selection-check-actions">
3072  ' . implode(PHP_EOL, $dropdownItems) . '
3073  </ul>
3074  </div>';
3075  }
3076 
3080  protected function ‪renderMultiRecordSelectionActions(string ‪$table, array $currentIdList): string
3081  {
3082  $actions = [];
3083  $lang = $this->‪getLanguageService();
3084  $userTsConfig = $this->‪getBackendUserAuthentication()->getTSConfig();
3085  $addClipboardActions = $this->showClipboardActions && $this->‪isClipboardFunctionalityEnabled($table);
3086  $editPermission = (
3087  (‪$table === 'pages') ? $this->calcPerms->editPagePermissionIsGranted() : $this->calcPerms->editContentPermissionIsGranted()
3088  ) && $this->‪overlayEditLockPermissions($table);
3089 
3090  // Add actions in case table can be modified by the current user
3091  if ($editPermission && $this->‪isEditable($table)) {
3092  $editActionConfiguration = GeneralUtility::jsonEncodeForHtmlAttribute([
3093  'idField' => 'uid',
3094  'tableName' => ‪$table,
3095  'returnUrl' => $this->‪listURL(),
3096  ], true);
3097  $actions['edit'] = '
3098  <button
3099  type="button"
3100  title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.edit')) . '"
3101  class="btn btn-sm btn-default"
3102  data-multi-record-selection-action="edit"
3103  data-multi-record-selection-action-config="' . $editActionConfiguration . '"
3104  >
3105  ' . $this->iconFactory->getIcon('actions-document-open', IconSize::SMALL)->render() . '
3106  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.edit')) . '
3107  </button>';
3108 
3109  if (!(bool)trim((string)($userTsConfig['options.']['disableDelete.'][‪$table] ?? $userTsConfig['options.']['disableDelete'] ?? ''))) {
3110  $deleteActionConfiguration = GeneralUtility::jsonEncodeForHtmlAttribute([
3111  'idField' => 'uid',
3112  'ok' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete'),
3113  'title' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_deleteMarked'),
3114  'content' => sprintf($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_deleteMarkedWarning'), $lang->sL(‪$GLOBALS['TCA'][‪$table]['ctrl']['title'])),
3115  ], true);
3116  $actions['delete'] = '
3117  <button
3118  type="button"
3119  title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete')) . '"
3120  class="btn btn-sm btn-default"
3121  data-multi-record-selection-action="delete"
3122  data-multi-record-selection-action-config="' . $deleteActionConfiguration . '"
3123  aria-haspopup="dialog"
3124  >
3125  ' . $this->iconFactory->getIcon('actions-edit-delete', IconSize::SMALL)->render() . '
3126  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete')) . '
3127  </button>';
3128  }
3129  }
3130 
3131  // Add clipboard actions in case they are enabled and clipboard is not deactivated
3132  if ($addClipboardActions && (string)($this->modTSconfig['enableClipBoard'] ?? '') !== 'deactivated') {
3133  $copyMarked = '
3134  <button type="button"
3135  class="btn btn-sm btn-default ' . ($this->clipObj->current === 'normal' ? 'disabled' : '') . '"
3136  title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.transferToClipboard')) . '"
3137  data-multi-record-selection-action="copyMarked"
3138  >
3139  ' . $this->iconFactory->getIcon('actions-edit-copy', IconSize::SMALL)->render() . '
3140  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.transferToClipboard')) . '
3141  </button>';
3142  $removeMarked = '
3143  <button type="button"
3144  class="btn btn-sm btn-default ' . ($this->clipObj->current === 'normal' ? 'disabled' : '') . '"
3145  title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.removeFromClipboard')) . '"
3146  data-multi-record-selection-action="removeMarked"
3147  >
3148  ' . $this->iconFactory->getIcon('actions-minus', IconSize::SMALL)->render() . '
3149  ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.removeFromClipboard')) . '
3150  </button>';
3151  // Add "copy marked" after "edit", or in case "edit" is not set, as first item
3152  if (!isset($actions['edit'])) {
3153  $actions = array_merge(['copyMarked' => $copyMarked], $actions);
3154  } else {
3155  $end = array_splice($actions, (int)(array_search('edit', array_keys($actions), true)) + 1);
3156  $actions = array_merge($actions, ['copyMarked' => $copyMarked, 'removeMarked' => $removeMarked], $end);
3157  }
3158  }
3159 
3160  $event = $this->eventDispatcher->dispatch(
3161  new ‪ModifyRecordListTableActionsEvent($actions, ‪$table, $currentIdList, $this)
3162  );
3164  $actions = $event->getActions();
3165 
3166  if ($actions === []) {
3167  // In case the user does not have permissions to execute on of the above
3168  // actions or a hook removed all remaining actions, inform the user about this.
3169  return '
3170  <span class="badge badge-info">
3171  ' . htmlspecialchars($lang->sL($event->getNoActionLabel() ?: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noActionAvailable')) . '
3172  </span>
3173  ';
3174  }
3175 
3176  return implode(LF, $actions);
3177  }
3178 
3184  {
3186  }
3187 
3191  protected function ‪createShowItemTagAttributes(string $arguments): string
3192  {
3193  return GeneralUtility::implodeAttributes([
3194  'data-dispatch-action' => 'TYPO3.InfoWindow.showItem',
3195  'data-dispatch-args-list' => $arguments,
3196  ], true);
3197  }
3198 
3199  protected function ‪getLanguageService(): LanguageService
3200  {
3201  return ‪$GLOBALS['LANG'];
3202  }
3203 
3205  {
3206  $this->languagesAllowedForUser = ‪$languagesAllowedForUser;
3207  return $this;
3208  }
3209 
3213  protected function ‪isLocalized(string ‪$table, array $row): bool
3214  {
3215  $languageField = ‪$GLOBALS['TCA'][‪$table]['ctrl']['languageField'] ?? '';
3216  $transOrigPointerField = ‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'] ?? '';
3217 
3218  return ($row[$languageField] ?? false) && ($row[$transOrigPointerField] ?? false);
3219  }
3220 
3225  protected function ‪getNoViewWithDokTypes(array $tsConfig): array
3226  {
3227  if (isset($tsConfig['noViewWithDokTypes'])) {
3228  $noViewDokTypes = GeneralUtility::intExplode(',', (string)$tsConfig['noViewWithDokTypes'], true);
3229  } else {
3230  $noViewDokTypes = [
3233  ];
3234  }
3235 
3236  return $noViewDokTypes;
3237  }
3244  protected function ‪isClipboardFunctionalityEnabled(string ‪$table, array $row = []): bool
3245  {
3246  return $this->clipObj !== null
3247  && (‪$table !== 'pages' || !‪$this->showOnlyTranslatedRecords)
3248  && (
3249  $row === []
3250  || (
3251  !$this->‪isRecordDeletePlaceholder($row)
3252  && (int)($row[‪$GLOBALS['TCA'][‪$table]['ctrl']['transOrigPointerField'] ?? null] ?? 0) === 0
3253  )
3254  )
3255  && (BackendUtility::isTableWorkspaceEnabled(‪$table) || $this->‪getBackendUserAuthentication()->workspaceAllowsLiveEditingInTable($table));
3256  }
3257 
3261  protected function ‪addDividerToCellGroup(array &$cells): void
3262  {
3263  if (!($cells['secondary']['divider'] ?? false)) {
3264  $this->‪addActionToCellGroup($cells, '<hr class="dropdown-divider">', 'divider');
3265  }
3266  }
3267 
3268  protected function ‪isTextFieldType(string $fieldType): bool
3269  {
3270  $textFieldTypes = [
3271  'input',
3272  'text',
3273  'json',
3274  'flex',
3275  'email',
3276  'link',
3277  'slug',
3278  'color',
3279  'uuid',
3280  ];
3281 
3282  return in_array($fieldType, $textFieldTypes, true);
3283  }
3284 }
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$showLocalizeColumn
‪array $showLocalizeColumn
Definition: DatabaseRecordList.php:374
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$currentTable
‪int[][] $currentTable
Definition: DatabaseRecordList.php:182
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$showOnlyTranslatedRecords
‪bool $showOnlyTranslatedRecords
Definition: DatabaseRecordList.php:349
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:44
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\makeSearchString
‪string makeSearchString(string $table, int $currentPid, QueryBuilder $queryBuilder)
Definition: DatabaseRecordList.php:2435
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$overridePageIdList
‪array $overridePageIdList
Definition: DatabaseRecordList.php:333
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$disableSingleTableView
‪bool $disableSingleTableView
Definition: DatabaseRecordList.php:114
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isLocalized
‪isLocalized(string $table, array $row)
Definition: DatabaseRecordList.php:3190
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$duplicateField
‪string $duplicateField
Definition: DatabaseRecordList.php:272
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\overlayEditLockPermissions
‪bool overlayEditLockPermissions($table, $row=[], $editPermission=true)
Definition: DatabaseRecordList.php:2153
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\addElement
‪string addElement($data, $rowParams='', $colType='td')
Definition: DatabaseRecordList.php:2864
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getNoViewWithDokTypes
‪getNoViewWithDokTypes(array $tsConfig)
Definition: DatabaseRecordList.php:3202
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getFieldsToSelect
‪string[] getFieldsToSelect(string $table, array $columnsToRender)
Definition: DatabaseRecordList.php:453
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:50
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isEditable
‪isEditable(string $table)
Definition: DatabaseRecordList.php:2134
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\addSortLink
‪string addSortLink($label, $field, $table)
Definition: DatabaseRecordList.php:2003
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getTablesToRender
‪array getTablesToRender()
Definition: DatabaseRecordList.php:2256
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$calcPerms
‪Permission $calcPerms
Definition: DatabaseRecordList.php:223
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$displayRecordDownload
‪bool $displayRecordDownload
Definition: DatabaseRecordList.php:304
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getSearchableWebmounts
‪int[] getSearchableWebmounts(int $id, int $depth)
Definition: DatabaseRecordList.php:2767
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getQueryBuilder
‪getQueryBuilder(string $table, array $fields=[' *'], bool $addSorting=true, int $firstResult=0, int $maxResult=0)
Definition: DatabaseRecordList.php:2320
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setModuleData
‪setModuleData(ModuleData $moduleData)
Definition: DatabaseRecordList.php:2170
‪TYPO3\CMS\Core\Database\Query\QueryHelper\quoteDatabaseIdentifiers
‪static quoteDatabaseIdentifiers(Connection $connection, string $sql)
Definition: QueryHelper.php:224
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$tableDisplayOrder
‪array $tableDisplayOrder
Definition: DatabaseRecordList.php:328
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getTable
‪string getTable($table)
Definition: DatabaseRecordList.php:504
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_NEW
‪const PAGE_NEW
Definition: Permission.php:50
‪TYPO3\CMS\Backend\Clipboard\Clipboard
Definition: Clipboard.php:48
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\start
‪start($id, $table, $pointer, $search='', $levels=0, $showLimit=0)
Definition: DatabaseRecordList.php:2185
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$request
‪ServerRequestInterface $request
Definition: DatabaseRecordList.php:376
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\linkWrapItems
‪string linkWrapItems($table, $uid, $code, $row)
Definition: DatabaseRecordList.php:2547
‪TYPO3\CMS\Backend\Template\Components\Buttons\GenericButton
Definition: GenericButton.php:34
‪TYPO3\CMS\Backend\View\BackendViewFactory
Definition: BackendViewFactory.php:35
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$referenceCount
‪array $referenceCount
Definition: DatabaseRecordList.php:311
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\listURL
‪string listURL($altId='', $table='-1', $exclList='')
Definition: DatabaseRecordList.php:2656
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\createActionButtonNewRecord
‪createActionButtonNewRecord(string $table)
Definition: DatabaseRecordList.php:808
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$allowedNewTables
‪string[] $allowedNewTables
Definition: DatabaseRecordList.php:78
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$page
‪int $page
Definition: DatabaseRecordList.php:254
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$languagesAllowedForUser
‪array $languagesAllowedForUser
Definition: DatabaseRecordList.php:368
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:40
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$table
‪string $table
Definition: DatabaseRecordList.php:217
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\showNewRecLink
‪bool showNewRecLink($table)
Definition: DatabaseRecordList.php:2073
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\renderListRow
‪string renderListRow($table, array $row, int $indent, array $translations, bool $translationEnabled)
Definition: DatabaseRecordList.php:1038
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$CBnames
‪string[] $CBnames
Definition: DatabaseRecordList.php:290
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setIsEditable
‪setIsEditable(bool $isEditable)
Definition: DatabaseRecordList.php:2126
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\linkWrapTable
‪string linkWrapTable(string $table, string $label)
Definition: DatabaseRecordList.php:2528
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\showOnlyTranslatedRecords
‪showOnlyTranslatedRecords(bool $showOnlyTranslatedRecords)
Definition: DatabaseRecordList.php:3160
‪TYPO3\CMS\Backend\Module\ModuleData
Definition: ModuleData.php:30
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$sortField
‪string $sortField
Definition: DatabaseRecordList.php:143
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\addDividerToCellGroup
‪addDividerToCellGroup(array &$cells)
Definition: DatabaseRecordList.php:3238
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isRowListingConditionFulfilled
‪bool isRowListingConditionFulfilled($table, $row)
Definition: DatabaseRecordList.php:1021
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$pagePermsCache
‪array $pagePermsCache
Definition: DatabaseRecordList.php:373
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setOverrideUrlParameters
‪setOverrideUrlParameters(array $urlParameters, ServerRequestInterface $request)
Definition: DatabaseRecordList.php:2699
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\createActionButtonCollapse
‪createActionButtonCollapse(string $table)
Definition: DatabaseRecordList.php:963
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isTextFieldType
‪isTextFieldType(string $fieldType)
Definition: DatabaseRecordList.php:3245
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$addElement_tdCssClass
‪array $addElement_tdCssClass
Definition: DatabaseRecordList.php:176
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\generateList
‪string generateList()
Definition: DatabaseRecordList.php:2240
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList
Definition: DatabaseRecordList.php:68
‪TYPO3\CMS\Backend\Module\ModuleProvider
Definition: ModuleProvider.php:29
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getPossibleTranslations
‪int[] getPossibleTranslations(int $pageUid)
Definition: DatabaseRecordList.php:2927
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Filelist\Type\fieldArray
‪@ fieldArray
Definition: Mode.php:28
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$sortRev
‪bool $sortRev
Definition: DatabaseRecordList.php:267
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$listOnlyInSingleTableMode
‪bool $listOnlyInSingleTableMode
Definition: DatabaseRecordList.php:167
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$clickTitleMode
‪string $clickTitleMode
Definition: DatabaseRecordList.php:229
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\makeControl
‪string makeControl($table, $row)
Definition: DatabaseRecordList.php:1383
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$tableTSconfigOverTCA
‪array $tableTSconfigOverTCA
Definition: DatabaseRecordList.php:241
‪TYPO3\CMS\Backend\RecordList
Definition: DatabaseRecordList.php:18
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\linkUrlMail
‪string linkUrlMail(string $code, string $testString)
Definition: DatabaseRecordList.php:2631
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\isLoaded
‪static isLoaded(string $key)
Definition: ExtensionManagementUtility.php:54
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\renderMultiRecordSelectionActions
‪renderMultiRecordSelectionActions(string $table, array $currentIdList)
Definition: DatabaseRecordList.php:3057
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\makeCheckbox
‪string makeCheckbox(string $table, array $row)
Definition: DatabaseRecordList.php:1900
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$searchLevels
‪int $searchLevels
Definition: DatabaseRecordList.php:234
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:31
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$duplicateStack
‪array $duplicateStack
Definition: DatabaseRecordList.php:161
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:27
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\createActionButtonColumnSelector
‪createActionButtonColumnSelector(string $table)
Definition: DatabaseRecordList.php:913
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isRecordCurrentBackendUser
‪bool isRecordCurrentBackendUser($table, $row)
Definition: DatabaseRecordList.php:2112
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getPreviewUriBuilder
‪getPreviewUriBuilder(string $table, array $row)
Definition: DatabaseRecordList.php:993
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\makeClip
‪makeClip(string $table, array $row, array &$cells)
Definition: DatabaseRecordList.php:1813
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$hideTables
‪string $hideTables
Definition: DatabaseRecordList.php:199
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$currentLink
‪array $currentLink
Definition: DatabaseRecordList.php:344
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\createShowItemTagAttributes
‪createShowItemTagAttributes(string $arguments)
Definition: DatabaseRecordList.php:3168
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isClipboardFunctionalityEnabled
‪isClipboardFunctionalityEnabled(string $table, array $row=[])
Definition: DatabaseRecordList.php:3221
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$showClipboardActions
‪bool $showClipboardActions
Definition: DatabaseRecordList.php:91
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getReferenceCount
‪int getReferenceCount($tableName, $uid)
Definition: DatabaseRecordList.php:1181
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\renderListHeader
‪string renderListHeader($table, $currentIdList)
Definition: DatabaseRecordList.php:1201
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\createActionButtonDownload
‪createActionButtonDownload(string $table, int $totalItems)
Definition: DatabaseRecordList.php:871
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeTypes
‪static array getDateTimeTypes()
Definition: QueryHelper.php:211
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$deniedNewTables
‪string[] $deniedNewTables
Definition: DatabaseRecordList.php:85
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder\create
‪static static create(int $pageId)
Definition: PreviewUriBuilder.php:65
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$id
‪int $id
Definition: DatabaseRecordList.php:154
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$recPath_cache
‪array $recPath_cache
Definition: DatabaseRecordList.php:138
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SPACER
‪const DOKTYPE_SPACER
Definition: PageRepository.php:113
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage\getLanguageId
‪getLanguageId()
Definition: SiteLanguage.php:169
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\makeLocalizationPanel
‪makeLocalizationPanel($table, $row, array $translations)
Definition: DatabaseRecordList.php:1940
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Service\DependencyOrderingService
Definition: DependencyOrderingService.php:32
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$clickMenuEnabled
‪bool $clickMenuEnabled
Definition: DatabaseRecordList.php:103
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$displayColumnSelector
‪bool $displayColumnSelector
Definition: DatabaseRecordList.php:297
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:60
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListRecordActionsEvent
Definition: ModifyRecordListRecordActionsEvent.php:27
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$clipObj
‪Clipboard $clipObj
Definition: DatabaseRecordList.php:284
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SYSFOLDER
‪const DOKTYPE_SYSFOLDER
Definition: PageRepository.php:114
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getColumnsToRender
‪getColumnsToRender(string $table, bool $includeMetaColumns)
Definition: DatabaseRecordList.php:399
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$hideTranslations
‪string $hideTranslations
Definition: DatabaseRecordList.php:131
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\renderCheckboxActions
‪string renderCheckboxActions()
Definition: DatabaseRecordList.php:3015
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$editable
‪bool $editable
Definition: DatabaseRecordList.php:316
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:39
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\languageFlag
‪string languageFlag(string $table, array $row)
Definition: DatabaseRecordList.php:2969
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$returnUrl
‪string $returnUrl
Definition: DatabaseRecordList.php:211
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\__construct
‪__construct(protected readonly IconFactory $iconFactory, protected readonly UriBuilder $uriBuilder, protected readonly TranslationConfigurationProvider $translateTools, protected readonly EventDispatcherInterface $eventDispatcher, protected readonly BackendViewFactory $backendViewFactory, protected readonly ModuleProvider $moduleProvider,)
Definition: DatabaseRecordList.php:378
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$overrideUrlParameters
‪array $overrideUrlParameters
Definition: DatabaseRecordList.php:339
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$spaceIcon
‪string $spaceIcon
Definition: DatabaseRecordList.php:108
‪TYPO3\CMS\Webhooks\Message\$url
‪identifier readonly UriInterface $url
Definition: LoginErrorOccurredMessage.php:36
‪TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListTableActionsEvent
Definition: ModifyRecordListTableActionsEvent.php:27
‪TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider
Definition: TranslationConfigurationProvider.php:39
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$pageRow
‪string[] $pageRow
Definition: DatabaseRecordList.php:124
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$showLimit
‪int $showLimit
Definition: DatabaseRecordList.php:187
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:55
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:171
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$setFields
‪array $setFields
Definition: DatabaseRecordList.php:248
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder
Definition: PreviewUriBuilder.php:44
‪TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository
Definition: PageTreeRepository.php:40
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getPagePermissionsForRecord
‪getPagePermissionsForRecord(string $table, array $row)
Definition: DatabaseRecordList.php:2056
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$fieldArray
‪array $fieldArray
Definition: DatabaseRecordList.php:193
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$tableList
‪string $tableList
Definition: DatabaseRecordList.php:278
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\generateReferenceToolTip
‪generateReferenceToolTip(string $table, int $uid)
Definition: DatabaseRecordList.php:2989
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setTableDisplayOrder
‪setTableDisplayOrder(array $orderInformation)
Definition: DatabaseRecordList.php:2720
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setLanguagesAllowedForUser
‪setLanguagesAllowedForUser(array $languagesAllowedForUser)
Definition: DatabaseRecordList.php:3181
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setRequest
‪setRequest(ServerRequestInterface $request)
Definition: DatabaseRecordList.php:390
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:421
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\isRecordDeletePlaceholder
‪isRecordDeletePlaceholder(array $row)
Definition: DatabaseRecordList.php:2120
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\recPath
‪mixed[] recPath($pid)
Definition: DatabaseRecordList.php:2044
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:61
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:48
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\setOverridePageIdList
‪setOverridePageIdList(array $overridePageIdList)
Definition: DatabaseRecordList.php:2755
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$noControlPanels
‪bool $noControlPanels
Definition: DatabaseRecordList.php:97
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange(mixed $theInt, int $min, int $max=2000000000, int $defaultValue=0)
Definition: MathUtility.php:34
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getBackendUserAuthentication
‪getBackendUserAuthentication()
Definition: DatabaseRecordList.php:2849
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$possibleTranslations
‪array $possibleTranslations
Definition: DatabaseRecordList.php:361
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getOverridePageIdList
‪getOverridePageIdList()
Definition: DatabaseRecordList.php:2747
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$perms_clause
‪string $perms_clause
Definition: DatabaseRecordList.php:205
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\renderListNavigation
‪string renderListNavigation(string $table, int $totalItems, int $itemsPerPage)
Definition: DatabaseRecordList.php:1343
‪TYPO3\CMS\Backend\RecordList\Event\ModifyRecordListHeaderColumnsEvent
Definition: ModifyRecordListHeaderColumnsEvent.php:26
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\addActionToCellGroup
‪addActionToCellGroup(&$cells, $action, $actionKey)
Definition: DatabaseRecordList.php:2090
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\addPageIdConstraint
‪QueryBuilder addPageIdConstraint(string $tableName, QueryBuilder $queryBuilder, int $searchLevels)
Definition: DatabaseRecordList.php:2802
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$modTSconfig
‪array[] $modTSconfig
Definition: DatabaseRecordList.php:171
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$searchString
‪string $searchString
Definition: DatabaseRecordList.php:260
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Backend\View\Event\ModifyDatabaseQueryForRecordListingEvent
Definition: ModifyDatabaseQueryForRecordListingEvent.php:28
‪TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface
Definition: ButtonInterface.php:22
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\$moduleData
‪ModuleData $moduleData
Definition: DatabaseRecordList.php:148
‪TYPO3\CMS\Backend\RecordList\DatabaseRecordList\getLanguageService
‪getLanguageService()
Definition: DatabaseRecordList.php:3176
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39