TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
36 
43 {
49  public $tableList = '';
50 
56  public $returnUrl = '';
57 
63  public $thumbs = 0;
64 
70  public $itemsLimitPerTable = 20;
71 
77  public $itemsLimitSingleTable = 100;
78 
84  public $script = 'index.php';
85 
91  public $allFields = 0;
92 
98  public $localizationView = false;
99 
105  public $csvOutput = false;
106 
112  public $sortField;
113 
119  public $sortRev;
120 
127 
134 
140  public $id;
141 
147  public $table = '';
148 
155 
162 
168  public $searchString = '';
169 
175  public $searchLevels = '';
176 
182  public $showLimit = 0;
183 
189  public $perms_clause = '';
190 
196  public $calcPerms = 0;
197 
203  public $clickTitleMode = '';
204 
210  public $modSharedTSconfig = [];
211 
217  public $pageRecord = [];
218 
224  public $hideTables = '';
225 
231  public $hideTranslations = '';
232 
239 
245  public $tablesCollapsed = [];
246 
252  public $JScode = '';
253 
259  public $HTMLcode = '';
260 
266  public $iLimit = 0;
267 
273  public $eCounter = 0;
274 
280  public $totalItems = '';
281 
287  public $recPath_cache = [];
288 
294  public $setFields = [];
295 
301  public $currentTable = [];
302 
308  public $duplicateStack = [];
309 
313  public $modTSconfig;
314 
319  protected $overrideUrlParameters = [];
320 
326  protected $overridePageIdList = [];
327 
337  protected $tableDisplayOrder = [];
338 
350  public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
351  {
352  $backendUser = $this->getBackendUserAuthentication();
353  // Setting internal variables:
354  // sets the parent id
355  $this->id = (int)$id;
356  if ($GLOBALS['TCA'][$table]) {
357  // Setting single table mode, if table exists:
358  $this->table = $table;
359  }
360  $this->firstElementNumber = $pointer;
361  $this->searchString = trim($search);
362  $this->searchLevels = (int)$levels;
363  $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
364  // Setting GPvars:
365  $this->csvOutput = (bool)GeneralUtility::_GP('csv');
366  $this->sortField = GeneralUtility::_GP('sortField');
367  $this->sortRev = GeneralUtility::_GP('sortRev');
368  $this->displayFields = GeneralUtility::_GP('displayFields');
369  $this->duplicateField = GeneralUtility::_GP('duplicateField');
370  if (GeneralUtility::_GP('justLocalized')) {
371  $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
372  }
373  // Init dynamic vars:
374  $this->counter = 0;
375  $this->JScode = '';
376  $this->HTMLcode = '';
377  // Limits
378  if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
379  $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
380  (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
381  1,
382  10000
383  );
384  }
385  if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
386  $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
387  (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
388  1,
389  10000
390  );
391  }
392 
393  // $table might be NULL at this point in the code. As the expressionBuilder
394  // is used to limit returned records based on the page permissions and the
395  // uid field of the pages it can hardcoded to work on the pages table.
396  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
397  ->getQueryBuilderForTable('pages')
398  ->expr();
399  $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
400  // This will hide records from display - it has nothing to do with user rights!!
401  if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
402  $pidList = GeneralUtility::intExplode(',', $pidList, true);
403  if (!empty($pidList)) {
404  $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
405  }
406  }
407  $this->perms_clause = (string)$permsClause;
408 
409  // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
410  $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list'])
411  ? $backendUser->uc['moduleData']['list']
412  : [];
413  $collapseOverride = GeneralUtility::_GP('collapse');
414  if (is_array($collapseOverride)) {
415  foreach ($collapseOverride as $collapseTable => $collapseValue) {
416  if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 || $collapseValue == 1)) {
417  $this->tablesCollapsed[$collapseTable] = $collapseValue;
418  }
419  }
420  // Save modified user uc
421  $backendUser->uc['moduleData']['list'] = $this->tablesCollapsed;
422  $backendUser->writeUC($backendUser->uc);
424  if ($returnUrl !== '') {
425  HttpUtility::redirect($returnUrl);
426  }
427  }
428 
429  // Initialize languages:
430  if ($this->localizationView) {
431  $this->initializeLanguages();
432  }
433  }
434 
442  public function generateList()
443  {
444  // Set page record in header
445  $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
446  $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
447 
448  $backendUser = $this->getBackendUserAuthentication();
449 
450  // pre-process tables and add sorting instructions
451  $tableNames = array_flip(array_keys($GLOBALS['TCA']));
452  foreach ($tableNames as $tableName => &$config) {
453  $hideTable = false;
454 
455  // Checking if the table should be rendered:
456  // Checks that we see only permitted/requested tables:
457  if ($this->table && $tableName !== $this->table
458  || $this->tableList && !GeneralUtility::inList($this->tableList, $tableName)
459  || !$backendUser->check('tables_select', $tableName)
460  ) {
461  $hideTable = true;
462  }
463 
464  if (!$hideTable) {
465  // Don't show table if hidden by TCA ctrl section
466  // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
467  $hideTable = $hideTable
468  || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
469  || in_array($tableName, $hideTablesArray, true)
470  || in_array('*', $hideTablesArray, true);
471  // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
472  if (isset($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'])) {
473  $hideTable = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
474  }
475  }
476  if ($hideTable) {
477  unset($tableNames[$tableName]);
478  } else {
479  if (isset($this->tableDisplayOrder[$tableName])) {
480  // Copy display order information
481  $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
482  } else {
483  $tableNames[$tableName] = [];
484  }
485  }
486  }
487  unset($config);
488 
489  $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
490  ->orderByDependencies($tableNames);
491 
492  foreach ($orderedTableNames as $tableName => $_) {
493  // check if we are in single- or multi-table mode
494  if ($this->table) {
495  $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'])
496  ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']
497  : $this->itemsLimitSingleTable;
498  } else {
499  // if there are no records in table continue current foreach
500  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
501  ->getQueryBuilderForTable($tableName);
502  $queryBuilder->getRestrictions()
503  ->removeAll()
504  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
505  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
506  $firstRow = $queryBuilder->select('uid')
507  ->from($tableName)
508  ->where($this->getPageIdConstraint($tableName))
509  ->execute()
510  ->fetch();
511  if (!is_array($firstRow)) {
512  continue;
513  }
514  $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'])
515  ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']
516  : $this->itemsLimitPerTable;
517  }
518  if ($this->showLimit) {
519  $this->iLimit = $this->showLimit;
520  }
521  // Setting fields to select:
522  if ($this->allFields) {
523  $fields = $this->makeFieldList($tableName);
524  $fields[] = 'tstamp';
525  $fields[] = 'crdate';
526  $fields[] = '_PATH_';
527  $fields[] = '_CONTROL_';
528  if (is_array($this->setFields[$tableName])) {
529  $fields = array_intersect($fields, $this->setFields[$tableName]);
530  } else {
531  $fields = [];
532  }
533  } else {
534  $fields = [];
535  }
536 
537  // Finally, render the list:
538  $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
539  }
540  }
541 
550  public function getTable($tableName, $id, $fields = '')
551  {
552  return '';
553  }
554 
561  public function getSearchBox($formFields = true)
562  {
564  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
565  $lang = $this->getLanguageService();
566  // Setting form-elements, if applicable:
567  $formElements = ['', ''];
568  if ($formFields) {
569  $formElements = ['<form action="' . htmlspecialchars($this->listURL('', '-1', 'firstElementNumber,search_field')) . '" method="post">', '</form>'];
570  }
571  // Make level selector:
572  $opt = [];
573 
574  // "New" generation of search levels ... based on TS config
575  $config = BackendUtility::getPagesTSconfig($this->id);
576  $searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
577  $searchLevelItems = [];
578 
579  // get translated labels for search levels from pagets
580  foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
581  $label = $lang->sL('LLL:' . $labelConfigured, false);
582  if ($label === '') {
583  $label = $labelConfigured;
584  }
585  $searchLevelItems[$keySearchLevel] = $label;
586  }
587 
588  foreach ($searchLevelItems as $kv => $label) {
589  $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars($label) . '</option>';
590  }
591  $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>';
592  // Table with the search box:
593  $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') . ';">
594  ' . $formElements[0] . '
595  <div id="typo3-dblist-search">
596  <div class="panel panel-default">
597  <div class="panel-body">
598  <div class="form-inline form-inline-spaced">
599  <div class="form-group">
600  <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) . '" />
601  </div>
602  <div class="form-group">
603  <label for="search_levels">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')) . ': </label>
604  ' . $lMenu . '
605  </div>
606  <div class="form-group">
607  <label for="showLimit">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.limit')) . ': </label>
608  <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 : '')) . '" />
609  </div>
610  <div class="form-group">
611  <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')) . '">
612  ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.search')) . '
613  </button>
614  </div>
615  </div>
616  </div>
617  </div>
618  </div>
619  ' . $formElements[1] . '</div>';
620  return $content;
621  }
622 
623  /******************************
624  *
625  * Various helper functions
626  *
627  ******************************/
634  public function setDispFields()
635  {
636  $backendUser = $this->getBackendUserAuthentication();
637  // Getting from session:
638  $dispFields = $backendUser->getModuleData('list/displayFields');
639  // If fields has been inputted, then set those as the value and push it to session variable:
640  if (is_array($this->displayFields)) {
641  reset($this->displayFields);
642  $tKey = key($this->displayFields);
643  $dispFields[$tKey] = $this->displayFields[$tKey];
644  $backendUser->pushModuleData('list/displayFields', $dispFields);
645  }
646  // Setting result:
647  $this->setFields = $dispFields;
648  }
649 
658  public function thumbCode($row, $table, $field)
659  {
660  return BackendUtility::thumbCode($row, $table, $field);
661  }
662 
674  public function makeQueryArray($table, $id, $addWhere = '', $fieldList = '*')
675  {
677  $hookObjectsArr = [];
678  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'])) {
679  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'] as $classRef) {
680  $hookObjectsArr[] = GeneralUtility::getUserObj($classRef);
681  }
682  }
683  // Set ORDER BY:
684  $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ? 'ORDER BY ' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
685  if ($this->sortField) {
686  if (in_array($this->sortField, $this->makeFieldList($table, 1))) {
687  $orderBy = 'ORDER BY ' . $this->sortField;
688  if ($this->sortRev) {
689  $orderBy .= ' DESC';
690  }
691  }
692  }
693  // Set LIMIT:
694  $limit = $this->iLimit ? ($this->firstElementNumber ? $this->firstElementNumber . ',' : '') . $this->iLimit : '';
695  // Filtering on displayable pages (permissions):
696  $pC = $table == 'pages' && $this->perms_clause ? ' AND ' . $this->perms_clause : '';
697  // Adding search constraints:
698  $search = $this->makeSearchString($table, $id);
699  // Compiling query array:
700  $queryParts = [
701  'SELECT' => $fieldList,
702  'FROM' => $table,
703  'WHERE' => $this->getPageIdConstraint($table) . ' ' . $pC . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table) . ' ' . $addWhere . ' ' . $search,
704  'GROUPBY' => '',
705  'LIMIT' => $limit
706  ];
707  $tempOrderBy = [];
708  foreach (QueryHelper::parseOrderBy($orderBy) as $orderPair) {
709  list($fieldName, $order) = $orderPair;
710  if ($order !== null) {
711  $tempOrderBy[] = implode(' ', $orderPair);
712  } else {
713  $tempOrderBy[] = $fieldName;
714  }
715  }
716  $queryParts['ORDERBY'] = implode(',', $tempOrderBy);
717  // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
718  if ((in_array($table, GeneralUtility::trimExplode(',', $this->hideTranslations)) || $this->hideTranslations === '*') && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) && $table !== 'pages_language_overlay') {
719  $queryParts['WHERE'] .= ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=0 ';
720  }
721  // Apply hook as requested in http://forge.typo3.org/issues/16634
722  foreach ($hookObjectsArr as $hookObj) {
723  if (method_exists($hookObj, 'makeQueryArray_post')) {
724  $_params = [
725  'orderBy' => $orderBy,
726  'limit' => $limit,
727  'pC' => $pC,
728  'search' => $search
729  ];
730  $hookObj->makeQueryArray_post($queryParts, $this, $table, $id, $addWhere, $fieldList, $_params);
731  }
732  }
733  // Return query:
734  return $queryParts;
735  }
736 
747  public function getQueryBuilder(
748  string $table,
749  int $pageId,
750  array $additionalConstraints = [],
751  array $fields = ['*']
752  ): QueryBuilder {
753  $queryParameters = $this->buildQueryParameters($table, $pageId, $fields, $additionalConstraints);
754 
755  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
756  ->getQueryBuilderForTable($queryParameters['table']);
757  $queryBuilder->getRestrictions()
758  ->removeAll()
759  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
760  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
761  $queryBuilder
762  ->select(...$queryParameters['fields'])
763  ->from($queryParameters['table'])
764  ->where(...$queryParameters['where']);
765 
766  if (!empty($queryParameters['orderBy'])) {
767  foreach ($queryParameters['orderBy'] as $fieldNameAndSorting) {
768  list($fieldName, $sorting) = $fieldNameAndSorting;
769  $queryBuilder->addOrderBy($fieldName, $sorting);
770  }
771  }
772 
773  if (!empty($queryParameters['firstResult'])) {
774  $queryBuilder->setFirstResult((int)$queryParameters['firstResult']);
775  }
776 
777  if (!empty($queryParameters['maxResults'])) {
778  $queryBuilder->setMaxResults((int)$queryParameters['maxResults']);
779  }
780 
781  if (!empty($queryParameters['groupBy'])) {
782  $queryBuilder->groupBy($queryParameters['groupBy']);
783  }
784 
785  return $queryBuilder;
786  }
787 
797  protected function buildQueryParameters(
798  string $table,
799  int $pageId,
800  array $fieldList = ['*'],
801  array $additionalConstraints = []
802  ): array {
803  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
804  ->getQueryBuilderForTable($table)
805  ->expr();
806 
807  $parameters = [
808  'table' => $table,
809  'fields' => $fieldList,
810  'groupBy' => null,
811  'orderBy' => null,
812  'firstResult' => $this->firstElementNumber ?: null,
813  'maxResults' => $this->iLimit ? $this->iLimit : null,
814  ];
815 
816  if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
817  $parameters['orderBy'][] = $this->sortRev ? [$this->sortField, 'DESC'] : [$this->sortField, 'ASC'];
818  } else {
819  $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
820  $parameters['orderBy'] = QueryHelper::parseOrderBy((string)$orderBy);
821  }
822 
823  // Build the query constraints
824  $constraints = [
825  'pidSelect' => $this->getPageIdConstraint($table),
826  'search' => $this->makeSearchString($table, $pageId)
827  ];
828 
829  // Filtering on displayable pages (permissions):
830  if ($table === 'pages' && $this->perms_clause) {
831  $constraints['pagePermsClause'] = $this->perms_clause;
832  }
833 
834  // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
835  if ((GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
836  && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
837  && $table !== 'pages_language_overlay'
838  ) {
839  $constraints['transOrigPointerField'] = $expressionBuilder->eq(
840  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
841  0
842  );
843  }
844 
845  $parameters['where'] = array_merge($constraints, $additionalConstraints);
846 
847  $hookName = DatabaseRecordList::class;
848  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
849  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $classRef) {
850  $hookObject = GeneralUtility::getUserObj($classRef);
851  if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
852  $hookObject->buildQueryParametersPostProcess(
853  $parameters,
854  $table,
855  $pageId,
856  $additionalConstraints,
857  $fieldList,
858  $this
859  );
860  }
861  }
862  }
863 
864  // array_unique / array_filter used to eliminate empty and duplicate constraints
865  // the array keys are eliminated by this as well to facilitate argument unpacking
866  // when used with the querybuilder.
867  $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
868 
869  return $parameters;
870  }
871 
879  public function setTotalItems(string $table, int $pageId, array $constraints)
880  {
881  $queryParameters = $this->buildQueryParameters($table, $pageId, ['*'], $constraints);
882  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
883  ->getQueryBuilderForTable($queryParameters['table']);
884  $queryBuilder->getRestrictions()
885  ->removeAll()
886  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
887  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
888  $queryBuilder
889  ->from($queryParameters['table'])
890  ->where(...$queryParameters['where']);
891 
892  $this->totalItems = (int)$queryBuilder->count('*')
893  ->execute()
894  ->fetchColumn();
895  }
896 
905  public function makeSearchString($table, $currentPid = -1)
906  {
907  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
908  $expressionBuilder = $queryBuilder->expr();
909  $constraints = [];
910  $currentPid = (int)$currentPid;
911  $tablePidField = $table === 'pages' ? 'uid' : 'pid';
912  // Make query, only if table is valid and a search string is actually defined:
913  if (empty($this->searchString)) {
914  return '';
915  }
916 
917  $searchableFields = $this->getSearchFields($table);
918  if (empty($searchableFields)) {
919  return '';
920  }
921  if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
922  $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
923  foreach ($searchableFields as $fieldName) {
924  if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
925  continue;
926  }
927  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
928  $fieldType = $fieldConfig['type'];
929  $evalRules = $fieldConfig['eval'] ?: '';
930  if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
931  if (is_array($fieldConfig['search'])
932  && in_array('pidonly', $fieldConfig['search'], true)
933  && $currentPid > 0
934  ) {
935  $constraints[] = $expressionBuilder->andX(
936  $expressionBuilder->eq($fieldName, (int)$this->searchString),
937  $expressionBuilder->eq($tablePidField, (int)$currentPid)
938  );
939  }
940  } elseif ($fieldType === 'text'
941  || $fieldType === 'flex'
942  || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
943  ) {
944  $constraints[] = $expressionBuilder->like(
945  $fieldName,
946  $queryBuilder->quote('%' . (int)$this->searchString . '%')
947  );
948  }
949  }
950  } else {
951  $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
952  foreach ($searchableFields as $fieldName) {
953  if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
954  continue;
955  }
956  $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
957  $fieldType = $fieldConfig['type'];
958  $evalRules = $fieldConfig['eval'] ?: '';
959  $searchConstraint = $expressionBuilder->andX(
960  $expressionBuilder->comparison(
961  'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
962  'LIKE',
963  'LOWER(' . $like . ')'
964  )
965  );
966  if (is_array($fieldConfig['search'])) {
967  $searchConfig = $fieldConfig['search'];
968  if (in_array('case', $searchConfig)) {
969  // Replace case insensitive default constraint
970  $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
971  }
972  if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
973  $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
974  }
975  if ($searchConfig['andWhere']) {
976  $searchConstraint->add(
977  QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
978  );
979  }
980  }
981  if ($fieldType === 'text'
982  || $fieldType === 'flex'
983  || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
984  ) {
985  if ($searchConstraint->count() !== 0) {
986  $constraints[] = $searchConstraint;
987  }
988  }
989  }
990  }
991  // If no search field conditions have been build ensure no results are returned
992  if (empty($constraints)) {
993  return '0=1';
994  }
995 
996  return $expressionBuilder->orX(...$constraints);
997  }
998 
1005  protected function getSearchFields($tableName)
1006  {
1007  $fieldArray = [];
1008  $fieldListWasSet = false;
1009  // Get fields from ctrl section of TCA first
1010  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
1011  $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
1012  $fieldListWasSet = true;
1013  }
1014  // Call hook to add or change the list
1015  if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
1016  $hookParameters = [
1017  'tableHasSearchConfiguration' => $fieldListWasSet,
1018  'tableName' => $tableName,
1019  'searchFields' => &$fieldArray,
1020  'searchString' => $this->searchString
1021  ];
1022  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
1023  GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
1024  }
1025  }
1026  return $fieldArray;
1027  }
1028 
1037  public function linkWrapTable($table, $code)
1038  {
1039  if ($this->table !== $table) {
1040  return '<a href="' . htmlspecialchars($this->listURL('', $table, 'firstElementNumber')) . '">' . $code . '</a>';
1041  }
1042  return '<a href="' . htmlspecialchars($this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')) . '">' . $code . '</a>';
1043  }
1044 
1054  public function linkWrapItems($table, $uid, $code, $row)
1055  {
1056  $lang = $this->getLanguageService();
1057  $origCode = $code;
1058  // If the title is blank, make a "no title" label:
1059  if ((string)$code === '') {
1060  $code = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</i> - '
1061  . htmlspecialchars(BackendUtility::getRecordTitle($table, $row));
1062  } else {
1063  $code = htmlspecialchars($code, ENT_QUOTES, 'UTF-8', false);
1064  if ($code != htmlspecialchars($origCode)) {
1065  $code = '<span title="' . htmlspecialchars($origCode, ENT_QUOTES, 'UTF-8', false) . '">' . $code . '</span>';
1066  }
1067  }
1068  switch ((string)$this->clickTitleMode) {
1069  case 'edit':
1070  // If the listed table is 'pages' we have to request the permission settings for each page:
1071  if ($table == 'pages') {
1072  $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1073  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
1074  } else {
1075  $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
1076  }
1077  // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1078  if ($permsEdit) {
1079  $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1080  $code = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1)) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
1081  }
1082  break;
1083  case 'show':
1084  // "Show" link (only pages and tt_content elements)
1085  if ($table == 'pages' || $table == 'tt_content') {
1086  $code = '<a href="#" onclick="' . htmlspecialchars(
1087  BackendUtility::viewOnClick(($table == 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid']))
1088  ) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $code . '</a>';
1089  }
1090  break;
1091  case 'info':
1092  // "Info": (All records)
1093  $code = '<a href="#" onclick="' . htmlspecialchars(('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
1094  break;
1095  default:
1096  // Output the label now:
1097  if ($table == 'pages') {
1098  $code = '<a href="' . htmlspecialchars($this->listURL($uid, '', 'firstElementNumber')) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
1099  } else {
1100  $code = $this->linkUrlMail($code, $origCode);
1101  }
1102  }
1103  return $code;
1104  }
1105 
1113  public function linkUrlMail($code, $testString)
1114  {
1115  // Check for URL:
1116  $scheme = parse_url($testString, PHP_URL_SCHEME);
1117  if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
1118  return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1119  }
1120  // Check for email:
1121  if (GeneralUtility::validEmail($testString)) {
1122  return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1123  }
1124  // Return if nothing else...
1125  return $code;
1126  }
1127 
1138  public function listURL($altId = '', $table = '-1', $exclList = '')
1139  {
1140  $urlParameters = [];
1141  if ((string)$altId !== '') {
1142  $urlParameters['id'] = $altId;
1143  } else {
1144  $urlParameters['id'] = $this->id;
1145  }
1146  if ($table === '-1') {
1147  $urlParameters['table'] = $this->table;
1148  } else {
1149  $urlParameters['table'] = $table;
1150  }
1151  if ($this->thumbs) {
1152  $urlParameters['imagemode'] = $this->thumbs;
1153  }
1154  if ($this->returnUrl) {
1155  $urlParameters['returnUrl'] = $this->returnUrl;
1156  }
1157  if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
1158  $urlParameters['search_field'] = $this->searchString;
1159  }
1160  if ($this->searchLevels) {
1161  $urlParameters['search_levels'] = $this->searchLevels;
1162  }
1163  if ($this->showLimit) {
1164  $urlParameters['showLimit'] = $this->showLimit;
1165  }
1166  if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
1167  $urlParameters['pointer'] = $this->firstElementNumber;
1168  }
1169  if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
1170  $urlParameters['sortField'] = $this->sortField;
1171  }
1172  if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
1173  $urlParameters['sortRev'] = $this->sortRev;
1174  }
1175 
1176  $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
1177 
1178  if ($routePath = GeneralUtility::_GP('route')) {
1179  $router = GeneralUtility::makeInstance(Router::class);
1180  $route = $router->match($routePath);
1181  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1182  $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
1183  } elseif ($moduleName = GeneralUtility::_GP('M')) {
1184  $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
1185  } else {
1186  $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&');
1187  }
1188  return $url;
1189  }
1190 
1196  public function requestUri()
1197  {
1198  return $this->listURL();
1199  }
1200 
1209  public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
1210  {
1211  $backendUser = $this->getBackendUserAuthentication();
1212  // Init fieldlist array:
1213  $fieldListArr = [];
1214  // Check table:
1215  if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1216  if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1217  // Traverse configured columns and add them to field array, if available for user.
1218  foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
1219  if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check('non_exclude_fields', $table . ':' . $fN)) && $fieldValue['config']['type'] != 'passthrough') {
1220  $fieldListArr[] = $fN;
1221  }
1222  }
1223 
1224  $fieldListArr[] = 'uid';
1225  $fieldListArr[] = 'pid';
1226 
1227  // Add date fields
1228  if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
1229  if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1230  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
1231  }
1232  if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1233  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
1234  }
1235  }
1236  // Add more special fields:
1237  if ($dontCheckUser || $backendUser->isAdmin()) {
1238  if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1239  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
1240  }
1241  if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1242  $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
1243  }
1244  if (ExtensionManagementUtility::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1245  $fieldListArr[] = 't3ver_id';
1246  $fieldListArr[] = 't3ver_state';
1247  $fieldListArr[] = 't3ver_wsid';
1248  }
1249  }
1250  } else {
1251  GeneralUtility::sysLog(sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1252  }
1253  }
1254  return $fieldListArr;
1255  }
1256 
1265  protected function getSearchableWebmounts($id, $depth, $perms_clause)
1266  {
1267  $backendUser = $this->getBackendUserAuthentication();
1269  $tree = GeneralUtility::makeInstance(PageTreeView::class);
1270  $tree->init('AND ' . $perms_clause);
1271  $tree->makeHTML = 0;
1272  $tree->fieldArray = ['uid', 'php_tree_stop'];
1273  $idList = [];
1274 
1275  $allowedMounts = !$backendUser->isAdmin() && $id === 0
1276  ? $backendUser->returnWebmounts()
1277  : [$id];
1278 
1279  foreach ($allowedMounts as $allowedMount) {
1280  $idList[] = $allowedMount;
1281  if ($depth) {
1282  $tree->getTree($allowedMount, $depth, '');
1283  }
1284  $idList = array_merge($idList, $tree->ids);
1285  }
1286 
1287  return $idList;
1288  }
1289 
1296  public function localizationRedirect($justLocalized)
1297  {
1298  list($table, $orig_uid, $language) = explode(':', $justLocalized);
1299  if ($GLOBALS['TCA'][$table]
1300  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1301  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1302  ) {
1303  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1304  $queryBuilder->getRestrictions()
1305  ->removeAll()
1306  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1307  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1308 
1309  $localizedRecordUid = $queryBuilder->select('uid')
1310  ->from($table)
1311  ->where(
1312  $queryBuilder->expr()->eq(
1313  $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1314  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1315  ),
1316  $queryBuilder->expr()->eq(
1317  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1318  $queryBuilder->createNamedParameter($orig_uid, \PDO::PARAM_INT)
1319  )
1320  )
1321  ->setMaxResults(1)
1322  ->execute()
1323  ->fetchColumn();
1324 
1325  if ($localizedRecordUid !== false) {
1326  // Create parameters and finally run the classic page module for creating a new page translation
1327  $url = $this->listURL();
1328  $editUserAccountUrl = BackendUtility::getModuleUrl(
1329  'record_edit',
1330  [
1331  'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
1332  'returnUrl' => $url
1333  ]
1334  );
1335  HttpUtility::redirect($editUserAccountUrl);
1336  }
1337  }
1338  }
1339 
1346  public function setOverrideUrlParameters(array $urlParameters)
1347  {
1348  $this->overrideUrlParameters = $urlParameters;
1349  }
1350 
1363  public function setTableDisplayOrder(array $orderInformation)
1364  {
1365  foreach ($orderInformation as $tableName => &$configuration) {
1366  if (isset($configuration['before'])) {
1367  if (is_string($configuration['before'])) {
1368  $configuration['before'] = GeneralUtility::trimExplode(',', $configuration['before'], true);
1369  } elseif (!is_array($configuration['before'])) {
1370  throw new \UnexpectedValueException('The specified "before" order configuration for table "' . $tableName . '" is invalid.', 1436195933);
1371  }
1372  }
1373  if (isset($configuration['after'])) {
1374  if (is_string($configuration['after'])) {
1375  $configuration['after'] = GeneralUtility::trimExplode(',', $configuration['after'], true);
1376  } elseif (!is_array($configuration['after'])) {
1377  throw new \UnexpectedValueException('The specified "after" order configuration for table "' . $tableName . '" is invalid.', 1436195934);
1378  }
1379  }
1380  }
1381  $this->tableDisplayOrder = $orderInformation;
1382  }
1383 
1387  public function getOverridePageIdList(): array
1388  {
1389  return $this->overridePageIdList;
1390  }
1391 
1395  public function setOverridePageIdList(array $overridePageIdList)
1396  {
1397  $this->overridePageIdList = array_map('intval', $overridePageIdList);
1398  }
1399 
1407  protected function getPageIdConstraint(string $tableName): string
1408  {
1409  // Set search levels:
1410  $searchLevels = $this->searchLevels;
1411 
1412  // Default is to search everywhere
1413  $constraint = '1=1';
1414 
1415  $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1416  ->getConnectionForTable($tableName)
1417  ->getExpressionBuilder();
1418 
1419  if ($searchLevels === 0) {
1420  $constraint = $expressionBuilder->eq($tableName . '.pid', (int)$this->id);
1421  } elseif ($searchLevels > 0) {
1422  $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
1423  $constraint = $expressionBuilder->in($tableName . '.pid', array_map('intval', $allowedMounts));
1424  }
1425 
1426  if (!empty($this->getOverridePageIdList())) {
1427  $constraint = $expressionBuilder->in(
1428  $tableName . '.pid',
1429  $this->getOverridePageIdList()
1430  );
1431  }
1432 
1433  return (string)$constraint;
1434  }
1435 
1439  protected function getBackendUserAuthentication()
1440  {
1441  return $GLOBALS['BE_USER'];
1442  }
1443 }
static getRecordWSOL($table, $uid, $fields= '*', $where= '', $useDeleteClause=true, $unsetMovePointers=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
setTotalItems(string $table, int $pageId, array $constraints)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static viewOnClick($pageUid, $backPath= '', $rootLine=null, $anchorSection= '', $alternativeUrl= '', $additionalGetVars= '', $switchFocus=true)
getQueryBuilder(string $table, int $pageId, array $additionalConstraints=[], array $fields=['*'])
buildQueryParameters(string $table, int $pageId, array $fieldList=['*'], array $additionalConstraints=[])
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
makeFieldList($table, $dontCheckUser=false, $addDateFields=false)
start($id, $table, $pointer, $search= '', $levels=0, $showLimit=0)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static callUserFunction($funcName, &$params, &$ref, $_= '', $errorMode=0)