TYPO3 CMS  TYPO3_8-7
AbstractDatabaseRecordList.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
39 
46 {
52  public $tableList = '';
53 
59  public $returnUrl = '';
60 
66  public $thumbs = 0;
67 
73  public $itemsLimitPerTable = 20;
74 
80  public $itemsLimitSingleTable = 100;
81 
87  public $script = 'index.php';
88 
94  public $allFields = 0;
95 
101  public $localizationView = false;
102 
108  public $csvOutput = false;
109 
115  public $sortField;
116 
122  public $sortRev;
123 
130 
137 
143  public $id;
144 
150  public $table = '';
151 
158 
165 
171  public $searchString = '';
172 
178  public $searchLevels = '';
179 
185  public $showLimit = 0;
186 
192  public $perms_clause = '';
193 
199  public $calcPerms = 0;
200 
206  public $clickTitleMode = '';
207 
213  public $modSharedTSconfig = [];
214 
220  public $pageRecord = [];
221 
227  public $hideTables = '';
228 
234  public $hideTranslations = '';
235 
242 
248  public $tablesCollapsed = [];
249 
255  public $JScode = '';
256 
262  public $HTMLcode = '';
263 
269  public $iLimit = 0;
270 
276  public $eCounter = 0;
277 
283  public $totalItems = '';
284 
290  public $recPath_cache = [];
291 
297  public $setFields = [];
298 
304  public $currentTable = [];
305 
311  public $duplicateStack = [];
312 
316  public $modTSconfig;
317 
322  protected $overrideUrlParameters = [];
323 
329  protected $currentLink = [];
330 
336  protected $overridePageIdList = [];
337 
347  protected $tableDisplayOrder = [];
348 
359  public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
360  {
361  $backendUser = $this->getBackendUserAuthentication();
362  // Setting internal variables:
363  // sets the parent id
364  $this->id = (int)$id;
365  if ($GLOBALS['TCA'][$table]) {
366  // Setting single table mode, if table exists:
367  $this->table = $table;
368  }
369  $this->firstElementNumber = $pointer;
370  $this->searchString = trim($search);
371  $this->searchLevels = (int)$levels;
372  $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
373  // Setting GPvars:
374  $this->csvOutput = (bool)GeneralUtility::_GP('csv');
375  $this->sortField = GeneralUtility::_GP('sortField');
376  $this->sortRev = GeneralUtility::_GP('sortRev');
377  $this->displayFields = GeneralUtility::_GP('displayFields');
378  $this->duplicateField = GeneralUtility::_GP('duplicateField');
379  if (GeneralUtility::_GP('justLocalized')) {
380  $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
381  }
382  // Init dynamic vars:
383  $this->counter = 0;
384  $this->JScode = '';
385  $this->HTMLcode = '';
386  // Limits
387  if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
388  $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
389  (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
390  1,
391  10000
392  );
393  }
394  if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
395  $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
396  (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
397  1,
398  10000
399  );
400  }
401 
402  // If there is a current link to a record, set the current link uid and get the table name from the link handler configuration
403  $currentLinkValue = isset($this->overrideUrlParameters['P']['currentValue']) ? trim($this->overrideUrlParameters['P']['currentValue']) : '';
404  if ($currentLinkValue) {
405  $linkService = GeneralUtility::makeInstance(LinkService::class);
406  try {
407  $currentLinkParts = $linkService->resolve($currentLinkValue);
408  if ($currentLinkParts['type'] === 'record' && isset($currentLinkParts['identifier'])) {
409  $this->currentLink['tableNames'] = $this->tableList;
410  $this->currentLink['uid'] = (int)$currentLinkParts['uid'];
411  }
412  } catch (UnknownLinkHandlerException $e) {
413  }
414  }
415 
416  // $table might be NULL at this point in the code. As the expressionBuilder
417  // is used to limit returned records based on the page permissions and the
418  // uid field of the pages it can hardcoded to work on the pages table.
419  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
420  ->getQueryBuilderForTable('pages')
421  ->expr();
422  $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
423  // This will hide records from display - it has nothing to do with user rights!!
424  if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
425  $pidList = GeneralUtility::intExplode(',', $pidList, true);
426  if (!empty($pidList)) {
427  $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
428  }
429  }
430  $this->perms_clause = (string)$permsClause;
431 
432  // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
433  $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list'])
434  ? $backendUser->uc['moduleData']['list']
435  : [];
436  $collapseOverride = GeneralUtility::_GP('collapse');
437  if (is_array($collapseOverride)) {
438  foreach ($collapseOverride as $collapseTable => $collapseValue) {
439  if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 || $collapseValue == 1)) {
440  $this->tablesCollapsed[$collapseTable] = $collapseValue;
441  }
442  }
443  // Save modified user uc
444  $backendUser->uc['moduleData']['list'] = $this->tablesCollapsed;
445  $backendUser->writeUC($backendUser->uc);
447  if ($returnUrl !== '') {
448  HttpUtility::redirect($returnUrl);
449  }
450  }
451 
452  // Initialize languages:
453  if ($this->localizationView) {
454  $this->initializeLanguages();
455  }
456  }
457 
463  public function generateList()
464  {
465  // Set page record in header
466  $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
467  $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
468 
469  $backendUser = $this->getBackendUserAuthentication();
470 
471  // pre-process tables and add sorting instructions
472  $tableNames = array_flip(array_keys($GLOBALS['TCA']));
473  foreach ($tableNames as $tableName => &$config) {
474  $hideTable = false;
475 
476  // Checking if the table should be rendered:
477  // Checks that we see only permitted/requested tables:
478  if ($this->table && $tableName !== $this->table
479  || $this->tableList && !GeneralUtility::inList($this->tableList, $tableName)
480  || !$backendUser->check('tables_select', $tableName)
481  ) {
482  $hideTable = true;
483  }
484 
485  if (!$hideTable) {
486  // Don't show table if hidden by TCA ctrl section
487  // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
488  $hideTable = $hideTable
489  || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
490  || in_array($tableName, $hideTablesArray, true)
491  || in_array('*', $hideTablesArray, true);
492  // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
493  if (isset($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'])) {
494  $hideTable = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
495  }
496  }
497  if ($hideTable) {
498  unset($tableNames[$tableName]);
499  } else {
500  if (isset($this->tableDisplayOrder[$tableName])) {
501  // Copy display order information
502  $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
503  } else {
504  $tableNames[$tableName] = [];
505  }
506  }
507  }
508  unset($config);
509 
510  $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
511  ->orderByDependencies($tableNames);
512 
513  foreach ($orderedTableNames as $tableName => $_) {
514  // check if we are in single- or multi-table mode
515  if ($this->table) {
516  $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'])
517  ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']
518  : $this->itemsLimitSingleTable;
519  } else {
520  // if there are no records in table continue current foreach
521  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
522  ->getQueryBuilderForTable($tableName);
523  $queryBuilder->getRestrictions()
524  ->removeAll()
525  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
526  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
527  $firstRow = $queryBuilder->select('uid')
528  ->from($tableName)
529  ->where($this->getPageIdConstraint($tableName))
530  ->execute()
531  ->fetch();
532  if (!is_array($firstRow)) {
533  continue;
534  }
535  $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'])
536  ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']
537  : $this->itemsLimitPerTable;
538  }
539  if ($this->showLimit) {
540  $this->iLimit = $this->showLimit;
541  }
542  // Setting fields to select:
543  if ($this->allFields) {
544  $fields = $this->makeFieldList($tableName);
545  $fields[] = 'tstamp';
546  $fields[] = 'crdate';
547  $fields[] = '_PATH_';
548  $fields[] = '_CONTROL_';
549  if (is_array($this->setFields[$tableName])) {
550  $fields = array_intersect($fields, $this->setFields[$tableName]);
551  } else {
552  $fields = [];
553  }
554  } else {
555  $fields = [];
556  }
557 
558  // Finally, render the list:
559  $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
560  }
561  }
562 
571  public function getTable($tableName, $id, $fields = '')
572  {
573  return '';
574  }
575 
584  protected function getOnClickForRow(string $table, array $row): string
585  {
586  if ($table === 'tt_content') {
587  // Link to a content element, possibly translated and with anchor
588  $additionalParams = '';
589  $language = (int)$row[$GLOBALS['TCA']['tt_content']['ctrl']['languageField']];
590  if ($language > 0) {
591  $additionalParams = '&L=' . $language;
592  }
593  $onClick = BackendUtility::viewOnClick(
594  $this->id,
595  '',
596  null,
597  '',
598  '',
599  $additionalParams
600  );
601  } else {
602  // Link to a page in the default language
603  $onClick = BackendUtility::viewOnClick($row['uid']);
604  }
605  return $onClick;
606  }
607 
614  public function getSearchBox($formFields = true)
615  {
617  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
618  $lang = $this->getLanguageService();
619  // Setting form-elements, if applicable:
620  $formElements = ['', ''];
621  if ($formFields) {
622  $formElements = ['<form action="' . htmlspecialchars($this->listURL('', '-1', 'firstElementNumber,search_field')) . '" method="post">', '</form>'];
623  }
624  // Make level selector:
625  $opt = [];
626 
627  // "New" generation of search levels ... based on TS config
628  $config = BackendUtility::getPagesTSconfig($this->id);
629  $searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
630  $searchLevelItems = [];
631 
632  // get translated labels for search levels from pagets
633  foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
634  $label = $lang->sL('LLL:' . $labelConfigured, false);
635  if ($label === '') {
636  $label = $labelConfigured;
637  }
638  $searchLevelItems[$keySearchLevel] = $label;
639  }
640 
641  foreach ($searchLevelItems as $kv => $label) {
642  $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars($label) . '</option>';
643  }
644  $lMenu = '<select class="form-control" name="search_levels" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels')) . '" id="search_levels">' . implode('', $opt) . '</select>';
645  // Table with the search box:
646  $content = '<div class="db_list-searchbox-form db_list-searchbox-toolbar module-docheader-bar module-docheader-bar-search t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: ' . ($this->searchString == '' ? 'none' : 'block') . ';">
647  ' . $formElements[0] . '
648  <div id="typo3-dblist-search">
649  <div class="panel panel-default">
650  <div class="panel-body">
651  <div class="row">
652  <div class="form-group col-xs-12">
653  <label for="search_field">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.searchString')) . ': </label>
654  <input class="form-control" type="search" placeholder="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')) . '" name="search_field" id="search_field" value="' . htmlspecialchars($this->searchString) . '" />
655  </div>
656  <div class="form-group col-xs-12 col-sm-6">
657  <label for="search_levels">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')) . ': </label>
658  ' . $lMenu . '
659  </div>
660  <div class="form-group col-xs-12 col-sm-6">
661  <label for="showLimit">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.limit')) . ': </label>
662  <input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.limit')) . '" name="showLimit" id="showLimit" value="' . htmlspecialchars(($this->showLimit ? $this->showLimit : '')) . '" />
663  </div>
664  <div class="form-group col-xs-12">
665  <div class="form-control-wrap">
666  <button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.title.search')) . '">
667  ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.search')) . '
668  </button>
669  </div>
670  </div>
671  </div>
672  </div>
673  </div>
674  </div>
675  ' . $formElements[1] . '</div>';
676  return $content;
677  }
678 
679  /******************************
680  *
681  * Various helper functions
682  *
683  ******************************/
688  public function setDispFields()
689  {
690  $backendUser = $this->getBackendUserAuthentication();
691  // Getting from session:
692  $dispFields = $backendUser->getModuleData('list/displayFields');
693  // If fields has been inputted, then set those as the value and push it to session variable:
694  if (is_array($this->displayFields)) {
695  reset($this->displayFields);
696  $tKey = key($this->displayFields);
697  $dispFields[$tKey] = $this->displayFields[$tKey];
698  $backendUser->pushModuleData('list/displayFields', $dispFields);
699  }
700  // Setting result:
701  $this->setFields = $dispFields;
702  }
703 
712  public function thumbCode($row, $table, $field)
713  {
714  return BackendUtility::thumbCode($row, $table, $field);
715  }
716 
728  public function makeQueryArray($table, $id, $addWhere = '', $fieldList = '*')
729  {
731  $hookObjectsArr = [];
732  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'])) {
733  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'] as $classRef) {
734  $hookObjectsArr[] = GeneralUtility::getUserObj($classRef);
735  }
736  }
737  // Set ORDER BY:
738  $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ? 'ORDER BY ' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
739  if ($this->sortField) {
740  if (in_array($this->sortField, $this->makeFieldList($table, 1))) {
741  $orderBy = 'ORDER BY ' . $this->sortField;
742  if ($this->sortRev) {
743  $orderBy .= ' DESC';
744  }
745  }
746  }
747  // Set LIMIT:
748  $limit = $this->iLimit ? ($this->firstElementNumber ? $this->firstElementNumber . ',' : '') . $this->iLimit : '';
749  // Filtering on displayable pages (permissions):
750  $pC = $table === 'pages' && $this->perms_clause ? ' AND ' . $this->perms_clause : '';
751  // Adding search constraints:
752  $search = $this->makeSearchString($table, $id);
753  if ($search !== '') {
754  $search = ' AND ' . $search;
755  }
756  // Compiling query array:
757  $queryParts = [
758  'SELECT' => $fieldList,
759  'FROM' => $table,
760  'WHERE' => $this->getPageIdConstraint($table) . ' ' . $pC . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table) . ' ' . $addWhere . ' ' . $search,
761  'GROUPBY' => '',
762  'LIMIT' => $limit
763  ];
764  $tempOrderBy = [];
765  foreach (QueryHelper::parseOrderBy($orderBy) as $orderPair) {
766  list($fieldName, $order) = $orderPair;
767  if ($order !== null) {
768  $tempOrderBy[] = implode(' ', $orderPair);
769  } else {
770  $tempOrderBy[] = $fieldName;
771  }
772  }
773  $queryParts['ORDERBY'] = implode(',', $tempOrderBy);
774  // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
775  if ((in_array($table, GeneralUtility::trimExplode(',', $this->hideTranslations)) || $this->hideTranslations === '*') && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) && $table !== 'pages_language_overlay') {
776  $queryParts['WHERE'] .= ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=0 ';
777  }
778  // Apply hook as requested in http://forge.typo3.org/issues/16634
779  foreach ($hookObjectsArr as $hookObj) {
780  if (method_exists($hookObj, 'makeQueryArray_post')) {
781  $_params = [
782  'orderBy' => $orderBy,
783  'limit' => $limit,
784  'pC' => $pC,
785  'search' => $search
786  ];
787  $hookObj->makeQueryArray_post($queryParts, $this, $table, $id, $addWhere, $fieldList, $_params);
788  }
789  }
790  // Return query:
791  return $queryParts;
792  }
793 
804  public function getQueryBuilder(
805  string $table,
806  int $pageId,
807  array $additionalConstraints = [],
808  array $fields = ['*']
809  ): QueryBuilder {
810  $queryParameters = $this->buildQueryParameters($table, $pageId, $fields, $additionalConstraints);
811 
812  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
813  ->getQueryBuilderForTable($queryParameters['table']);
814  $queryBuilder->getRestrictions()
815  ->removeAll()
816  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
817  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
818  $queryBuilder
819  ->select(...$queryParameters['fields'])
820  ->from($queryParameters['table'])
821  ->where(...$queryParameters['where']);
822 
823  if (!empty($queryParameters['orderBy'])) {
824  foreach ($queryParameters['orderBy'] as $fieldNameAndSorting) {
825  list($fieldName, $sorting) = $fieldNameAndSorting;
826  $queryBuilder->addOrderBy($fieldName, $sorting);
827  }
828  }
829 
830  if (!empty($queryParameters['firstResult'])) {
831  $queryBuilder->setFirstResult((int)$queryParameters['firstResult']);
832  }
833 
834  if (!empty($queryParameters['maxResults'])) {
835  $queryBuilder->setMaxResults((int)$queryParameters['maxResults']);
836  }
837 
838  if (!empty($queryParameters['groupBy'])) {
839  $queryBuilder->groupBy($queryParameters['groupBy']);
840  }
841 
842  return $queryBuilder;
843  }
844 
855  protected function buildQueryParameters(
856  string $table,
857  int $pageId,
858  array $fieldList = ['*'],
859  array $additionalConstraints = [],
860  bool $addSorting = true
861  ): array {
862  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
863  ->getQueryBuilderForTable($table)
864  ->expr();
865 
866  $parameters = [
867  'table' => $table,
868  'fields' => $fieldList,
869  'groupBy' => null,
870  'orderBy' => null,
871  'firstResult' => $this->firstElementNumber ?: null,
872  'maxResults' => $this->iLimit ? $this->iLimit : null,
873  ];
874 
875  if ($addSorting === true) {
876  if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
877  $parameters['orderBy'][] = $this->sortRev ? [$this->sortField, 'DESC'] : [$this->sortField, 'ASC'];
878  } else {
879  $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
880  $parameters['orderBy'] = QueryHelper::parseOrderBy((string)$orderBy);
881  }
882  }
883 
884  // Build the query constraints
885  $constraints = [
886  'pidSelect' => $this->getPageIdConstraint($table),
887  'search' => $this->makeSearchString($table, $pageId)
888  ];
889 
890  // Filtering on displayable pages (permissions):
891  if ($table === 'pages' && $this->perms_clause) {
892  $constraints['pagePermsClause'] = $this->perms_clause;
893  }
894 
895  // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
896  if ((GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
897  && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
898  && $table !== 'pages_language_overlay'
899  ) {
900  $constraints['transOrigPointerField'] = $expressionBuilder->eq(
901  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
902  0
903  );
904  }
905 
906  $parameters['where'] = array_merge($constraints, $additionalConstraints);
907 
908  $hookName = DatabaseRecordList::class;
909  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
910  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $classRef) {
911  $hookObject = GeneralUtility::getUserObj($classRef);
912  if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
913  $hookObject->buildQueryParametersPostProcess(
914  $parameters,
915  $table,
916  $pageId,
917  $additionalConstraints,
918  $fieldList,
919  $this
920  );
921  }
922  }
923  }
924 
925  // array_unique / array_filter used to eliminate empty and duplicate constraints
926  // the array keys are eliminated by this as well to facilitate argument unpacking
927  // when used with the querybuilder.
928  $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
929 
930  return $parameters;
931  }
932 
940  public function setTotalItems(string $table, int $pageId, array $constraints)
941  {
942  $queryParameters = $this->buildQueryParameters($table, $pageId, ['*'], $constraints, false);
943  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
944  ->getQueryBuilderForTable($queryParameters['table']);
945  $queryBuilder->getRestrictions()
946  ->removeAll()
947  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
948  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
949  $queryBuilder
950  ->from($queryParameters['table'])
951  ->where(...$queryParameters['where']);
952 
953  $this->totalItems = (int)$queryBuilder->count('*')
954  ->execute()
955  ->fetchColumn();
956  }
957 
966  public function makeSearchString($table, $currentPid = -1)
967  {
968  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
969  $expressionBuilder = $queryBuilder->expr();
970  $constraints = [];
971  $currentPid = (int)$currentPid;
972  $tablePidField = $table === 'pages' ? 'uid' : 'pid';
973  // Make query only if table is valid and a search string is actually defined
974  if (empty($this->searchString)) {
975  return '';
976  }
977 
978  $searchableFields = $this->getSearchFields($table);
979  if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
980  $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
981  foreach ($searchableFields as $fieldName) {
982  if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
983  continue;
984  }
985  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
986  $fieldType = $fieldConfig['type'];
987  $evalRules = $fieldConfig['eval'] ?: '';
988  if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
989  if (is_array($fieldConfig['search'])
990  && in_array('pidonly', $fieldConfig['search'], true)
991  && $currentPid > 0
992  ) {
993  $constraints[] = $expressionBuilder->andX(
994  $expressionBuilder->eq($fieldName, (int)$this->searchString),
995  $expressionBuilder->eq($tablePidField, (int)$currentPid)
996  );
997  }
998  } elseif ($fieldType === 'text'
999  || $fieldType === 'flex'
1000  || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
1001  ) {
1002  $constraints[] = $expressionBuilder->like(
1003  $fieldName,
1004  $queryBuilder->quote('%' . (int)$this->searchString . '%')
1005  );
1006  }
1007  }
1008  } elseif (!empty($searchableFields)) {
1009  $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
1010  foreach ($searchableFields as $fieldName) {
1011  if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
1012  continue;
1013  }
1014  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
1015  $fieldType = $fieldConfig['type'];
1016  $evalRules = $fieldConfig['eval'] ?: '';
1017  $searchConstraint = $expressionBuilder->andX(
1018  $expressionBuilder->comparison(
1019  'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
1020  'LIKE',
1021  'LOWER(' . $like . ')'
1022  )
1023  );
1024  if (is_array($fieldConfig['search'])) {
1025  $searchConfig = $fieldConfig['search'];
1026  if (in_array('case', $searchConfig)) {
1027  // Replace case insensitive default constraint
1028  $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
1029  }
1030  if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
1031  $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
1032  }
1033  if ($searchConfig['andWhere']) {
1034  $searchConstraint->add(
1035  QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
1036  );
1037  }
1038  }
1039  if ($fieldType === 'text'
1040  || $fieldType === 'flex'
1041  || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
1042  ) {
1043  if ($searchConstraint->count() !== 0) {
1044  $constraints[] = $searchConstraint;
1045  }
1046  }
1047  }
1048  }
1049  // If no search field conditions have been built ensure no results are returned
1050  if (empty($constraints)) {
1051  return '0=1';
1052  }
1053 
1054  return $expressionBuilder->orX(...$constraints);
1055  }
1056 
1063  protected function getSearchFields($tableName)
1064  {
1065  $fieldArray = [];
1066  $fieldListWasSet = false;
1067  // Get fields from ctrl section of TCA first
1068  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
1069  $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
1070  $fieldListWasSet = true;
1071  }
1072  // Call hook to add or change the list
1073  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
1074  $hookParameters = [
1075  'tableHasSearchConfiguration' => $fieldListWasSet,
1076  'tableName' => $tableName,
1077  'searchFields' => &$fieldArray,
1078  'searchString' => $this->searchString
1079  ];
1080  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
1081  GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
1082  }
1083  }
1084  return $fieldArray;
1085  }
1086 
1095  public function linkWrapTable($table, $code)
1096  {
1097  if ($this->table !== $table) {
1098  return '<a href="' . htmlspecialchars($this->listURL('', $table, 'firstElementNumber')) . '">' . $code . '</a>';
1099  }
1100  return '<a href="' . htmlspecialchars($this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')) . '">' . $code . '</a>';
1101  }
1102 
1112  public function linkWrapItems($table, $uid, $code, $row)
1113  {
1114  $lang = $this->getLanguageService();
1115  $origCode = $code;
1116  // If the title is blank, make a "no title" label:
1117  if ((string)$code === '') {
1118  $code = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</i> - '
1119  . htmlspecialchars(BackendUtility::getRecordTitle($table, $row));
1120  } else {
1121  $code = htmlspecialchars($code, ENT_QUOTES, 'UTF-8', false);
1122  if ($code != htmlspecialchars($origCode)) {
1123  $code = '<span title="' . htmlspecialchars($origCode, ENT_QUOTES, 'UTF-8', false) . '">' . $code . '</span>';
1124  }
1125  }
1126  switch ((string)$this->clickTitleMode) {
1127  case 'edit':
1128  // If the listed table is 'pages' we have to request the permission settings for each page:
1129  if ($table === 'pages') {
1130  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1131  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
1132  } else {
1133  $backendUser = $this->getBackendUserAuthentication();
1134  $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT && $backendUser->recordEditAccessInternals($table, $row);
1135  }
1136  // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1137  if ($permsEdit) {
1138  $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1139  $code = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1)) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
1140  }
1141  break;
1142  case 'show':
1143  // "Show" link (only pages and tt_content elements)
1144  if ($table === 'pages' || $table === 'tt_content') {
1145  $onClick = $this->getOnClickForRow($table, $row);
1146  $code = '<a href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $code . '</a>';
1147  }
1148  break;
1149  case 'info':
1150  // "Info": (All records)
1151  $code = '<a href="#" onclick="' . htmlspecialchars('top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . (int)$row['uid'] . ');')
1152  . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
1153  break;
1154  default:
1155  // Output the label now:
1156  if ($table === 'pages') {
1157  $code = '<a href="' . htmlspecialchars($this->listURL($uid, '', 'firstElementNumber')) . '" onclick="setHighlight(' . (int)$uid . ')">' . $code . '</a>';
1158  } else {
1159  $code = $this->linkUrlMail($code, $origCode);
1160  }
1161  }
1162  return $code;
1163  }
1164 
1172  public function linkUrlMail($code, $testString)
1173  {
1174  // Check for URL:
1175  $scheme = parse_url($testString, PHP_URL_SCHEME);
1176  if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
1177  return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1178  }
1179  // Check for email:
1180  if (GeneralUtility::validEmail($testString)) {
1181  return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1182  }
1183  // Return if nothing else...
1184  return $code;
1185  }
1186 
1197  public function listURL($altId = '', $table = '-1', $exclList = '')
1198  {
1199  $urlParameters = [];
1200  if ((string)$altId !== '') {
1201  $urlParameters['id'] = $altId;
1202  } else {
1203  $urlParameters['id'] = $this->id;
1204  }
1205  if ($table === '-1') {
1206  $urlParameters['table'] = $this->table;
1207  } else {
1208  $urlParameters['table'] = $table;
1209  }
1210  if ($this->thumbs) {
1211  $urlParameters['imagemode'] = $this->thumbs;
1212  }
1213  if ($this->returnUrl) {
1214  $urlParameters['returnUrl'] = $this->returnUrl;
1215  }
1216  if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
1217  $urlParameters['search_field'] = $this->searchString;
1218  }
1219  if ($this->searchLevels) {
1220  $urlParameters['search_levels'] = $this->searchLevels;
1221  }
1222  if ($this->showLimit) {
1223  $urlParameters['showLimit'] = $this->showLimit;
1224  }
1225  if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
1226  $urlParameters['pointer'] = $this->firstElementNumber;
1227  }
1228  if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
1229  $urlParameters['sortField'] = $this->sortField;
1230  }
1231  if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
1232  $urlParameters['sortRev'] = $this->sortRev;
1233  }
1234 
1235  $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
1236 
1237  if ($routePath = GeneralUtility::_GP('route')) {
1238  $router = GeneralUtility::makeInstance(Router::class);
1239  $route = $router->match($routePath);
1240  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1241  $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
1242  } elseif ($moduleName = GeneralUtility::_GP('M')) {
1243  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
1244  } else {
1245  $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&');
1246  }
1247  return $url;
1248  }
1249 
1255  public function requestUri()
1256  {
1257  return $this->listURL();
1258  }
1259 
1268  public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
1269  {
1270  $backendUser = $this->getBackendUserAuthentication();
1271  // Init fieldlist array:
1272  $fieldListArr = [];
1273  // Check table:
1274  if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1275  if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1276  // Traverse configured columns and add them to field array, if available for user.
1277  foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
1278  if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check('non_exclude_fields', $table . ':' . $fN)) && $fieldValue['config']['type'] !== 'passthrough') {
1279  $fieldListArr[] = $fN;
1280  }
1281  }
1282 
1283  $fieldListArr[] = 'uid';
1284  $fieldListArr[] = 'pid';
1285 
1286  // Add date fields
1287  if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
1288  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1289  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
1290  }
1291  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1292  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
1293  }
1294  }
1295  // Add more special fields:
1296  if ($dontCheckUser || $backendUser->isAdmin()) {
1297  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1298  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
1299  }
1300  if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1301  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
1302  }
1303  if (ExtensionManagementUtility::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1304  $fieldListArr[] = 't3ver_id';
1305  $fieldListArr[] = 't3ver_state';
1306  $fieldListArr[] = 't3ver_wsid';
1307  }
1308  }
1309  } else {
1310  GeneralUtility::sysLog(sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1311  }
1312  }
1313  return $fieldListArr;
1314  }
1315 
1324  protected function getSearchableWebmounts($id, $depth, $perms_clause)
1325  {
1326  $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
1327  $cacheIdentifier = md5('pidList_' . $id . '_' . $depth . '_' . $perms_clause);
1328  $idList = $runtimeCache->get($cacheIdentifier);
1329  if ($idList) {
1330  return $idList;
1331  }
1332 
1333  $backendUser = $this->getBackendUserAuthentication();
1335  $tree = GeneralUtility::makeInstance(PageTreeView::class);
1336  $tree->init('AND ' . $perms_clause);
1337  $tree->makeHTML = 0;
1338  $tree->fieldArray = ['uid', 'php_tree_stop'];
1339  $idList = [];
1340 
1341  $allowedMounts = !$backendUser->isAdmin() && $id === 0
1342  ? $backendUser->returnWebmounts()
1343  : [$id];
1344 
1345  foreach ($allowedMounts as $allowedMount) {
1346  $idList[] = $allowedMount;
1347  if ($depth) {
1348  $tree->getTree($allowedMount, $depth, '');
1349  }
1350  $idList = array_merge($idList, $tree->ids);
1351  }
1352  $runtimeCache->set($cacheIdentifier, $idList);
1353  return $idList;
1354  }
1355 
1361  public function localizationRedirect($justLocalized)
1362  {
1363  list($table, $orig_uid, $language) = explode(':', $justLocalized);
1364  if ($GLOBALS['TCA'][$table]
1365  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1366  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1367  ) {
1368  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1369  $queryBuilder->getRestrictions()
1370  ->removeAll()
1371  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1372  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1373 
1374  $localizedRecordUid = $queryBuilder->select('uid')
1375  ->from($table)
1376  ->where(
1377  $queryBuilder->expr()->eq(
1378  $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1379  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1380  ),
1381  $queryBuilder->expr()->eq(
1382  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1383  $queryBuilder->createNamedParameter($orig_uid, \PDO::PARAM_INT)
1384  )
1385  )
1386  ->setMaxResults(1)
1387  ->execute()
1388  ->fetchColumn();
1389 
1390  if ($localizedRecordUid !== false) {
1391  // Create parameters and finally run the classic page module for creating a new page translation
1392  $url = $this->listURL();
1393  $editUserAccountUrl = BackendUtility::getModuleUrl(
1394  'record_edit',
1395  [
1396  'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
1397  'returnUrl' => $url
1398  ]
1399  );
1400  HttpUtility::redirect($editUserAccountUrl);
1401  }
1402  }
1403  }
1404 
1410  public function setOverrideUrlParameters(array $urlParameters)
1411  {
1412  $currentUrlParameter = GeneralUtility::_GP('curUrl');
1413  if (isset($currentUrlParameter['url'])) {
1414  $urlParameters['P']['currentValue'] = $currentUrlParameter['url'];
1415  }
1416  $this->overrideUrlParameters = $urlParameters;
1417  }
1418 
1431  public function setTableDisplayOrder(array $orderInformation)
1432  {
1433  foreach ($orderInformation as $tableName => &$configuration) {
1434  if (isset($configuration['before'])) {
1435  if (is_string($configuration['before'])) {
1436  $configuration['before'] = GeneralUtility::trimExplode(',', $configuration['before'], true);
1437  } elseif (!is_array($configuration['before'])) {
1438  throw new \UnexpectedValueException('The specified "before" order configuration for table "' . $tableName . '" is invalid.', 1436195933);
1439  }
1440  }
1441  if (isset($configuration['after'])) {
1442  if (is_string($configuration['after'])) {
1443  $configuration['after'] = GeneralUtility::trimExplode(',', $configuration['after'], true);
1444  } elseif (!is_array($configuration['after'])) {
1445  throw new \UnexpectedValueException('The specified "after" order configuration for table "' . $tableName . '" is invalid.', 1436195934);
1446  }
1447  }
1448  }
1449  $this->tableDisplayOrder = $orderInformation;
1450  }
1451 
1455  public function getOverridePageIdList(): array
1456  {
1458  }
1459 
1464  {
1465  $this->overridePageIdList = array_map('intval', $overridePageIdList);
1466  }
1467 
1475  protected function getPageIdConstraint(string $tableName): string
1476  {
1477  // Set search levels:
1479 
1480  // Set search levels to 999 instead of -1 as the following methods
1481  // do not support -1 as valid value for infinite search.
1482  if ($searchLevels === -1) {
1483  $searchLevels = 999;
1484  }
1485 
1486  // Default is to search everywhere
1487  $constraint = '1=1';
1488 
1489  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1490  ->getConnectionForTable($tableName)
1491  ->getExpressionBuilder();
1492 
1493  if ($searchLevels === 0) {
1494  $constraint = $expressionBuilder->eq($tableName . '.pid', (int)$this->id);
1495  } elseif ($searchLevels > 0) {
1496  $allowedPidList = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
1497  $constraint = $expressionBuilder->in($tableName . '.pid', array_map('intval', $allowedPidList));
1498  }
1499 
1500  if (!empty($this->getOverridePageIdList())) {
1501  $constraint = $expressionBuilder->in(
1502  $tableName . '.pid',
1503  $this->getOverridePageIdList()
1504  );
1505  }
1506 
1507  return (string)$constraint;
1508  }
1509 
1513  protected function getBackendUserAuthentication()
1514  {
1515  return $GLOBALS['BE_USER'];
1516  }
1517 }
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
static getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static editOnClick($params, $_='', $requestUri='')
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static viewOnClick( $pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
$fields
Definition: pages.php:4
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
makeFieldList($table, $dontCheckUser=false, $addDateFields=false)
buildQueryParameters(string $table, int $pageId, array $fieldList=[' *'], array $additionalConstraints=[], bool $addSorting=true)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
setTotalItems(string $table, int $pageId, array $constraints)
start($id, $table, $pointer, $search='', $levels=0, $showLimit=0)
static deleteClause($table, $tableAlias='')
getQueryBuilder(string $table, int $pageId, array $additionalConstraints=[], array $fields=[' *'])