2 namespace TYPO3\CMS\Recordlist\RecordList
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Backend\RecordList\AbstractRecordList
;
18 use TYPO3\CMS\Backend\Routing\Router
;
19 use TYPO3\CMS\Backend\Routing\UriBuilder
;
20 use TYPO3\CMS\Backend\Tree\View\PageTreeView
;
21 use TYPO3\CMS\Backend\Utility\BackendUtility
;
22 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication
;
23 use TYPO3\CMS\Core\Database\ConnectionPool
;
24 use TYPO3\CMS\Core\Database\Query\QueryBuilder
;
25 use TYPO3\CMS\Core\Database\Query\QueryHelper
;
26 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
;
27 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
;
28 use TYPO3\CMS\Core\Imaging\Icon
;
29 use TYPO3\CMS\Core\Imaging\IconFactory
;
30 use TYPO3\CMS\Core\Service\DependencyOrderingService
;
31 use TYPO3\CMS\Core\Type\Bitmask\Permission
;
32 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility
;
33 use TYPO3\CMS\Core\Utility\GeneralUtility
;
34 use TYPO3\CMS\Core\Utility\HttpUtility
;
35 use TYPO3\CMS\Core\Utility\MathUtility
;
38 * Child class for rendering of Web > List (not the final class)
39 * Shared between Web>List and Web>Page
40 * @see \TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList
42 class AbstractDatabaseRecordList
extends AbstractRecordList
45 * Specify a list of tables which are the only ones allowed to be displayed.
49 public $tableList = '';
56 public $returnUrl = '';
59 * Thumbnails on records containing files (pictures)
66 * default Max items shown per table in "multi-table mode", may be overridden by tables.php
70 public $itemsLimitPerTable = 20;
73 * default Max items shown per table in "single-table mode", may be overridden by tables.php
77 public $itemsLimitSingleTable = 100;
84 public $script = 'index.php';
87 * Indicates if all available fields for a user should be selected or not.
91 public $allFields = 0;
94 * Whether to show localization view or not
98 public $localizationView = false;
101 * If set, csvList is outputted.
105 public $csvOutput = false;
108 * Field, to sort list by
115 * Field, indicating to sort in reverse order.
122 * Containing which fields to display in extended mode
126 public $displayFields;
129 * String, can contain the field name from a table which must have duplicate values marked.
133 public $duplicateField;
143 * Tablename if single-table mode
150 * If TRUE, records are listed only if a specific table is selected.
154 public $listOnlyInSingleTableMode = false;
157 * Pointer for browsing list
161 public $firstElementNumber = 0;
168 public $searchString = '';
171 * Levels to search down.
175 public $searchLevels = '';
178 * Number of records to show
182 public $showLimit = 0;
185 * Page select permissions
189 public $perms_clause = '';
192 * Some permissions...
196 public $calcPerms = 0;
199 * Mode for what happens when a user clicks the title of a record.
203 public $clickTitleMode = '';
206 * Shared module configuration, used by localization features
210 public $modSharedTSconfig = [];
213 * Loaded with page record with version overlay if any.
217 public $pageRecord = [];
220 * Tables which should not get listed
224 public $hideTables = '';
227 * Tables which should not list their translations
231 public $hideTranslations = '';
234 * TSconfig which overwrites TCA-Settings
238 public $tableTSconfigOverTCA = [];
241 * Array of collapsed / uncollapsed tables in multi table view
245 public $tablesCollapsed = [];
248 * JavaScript code accumulation
259 public $HTMLcode = '';
269 * Counting the elements no matter what...
273 public $eCounter = 0;
276 * Set to the total number of items for a table when selecting.
280 public $totalItems = '';
283 * Cache for record path
287 public $recPath_cache = [];
290 * Fields to display for the current table
294 public $setFields = [];
297 * Used for tracking next/prev uids
301 public $currentTable = [];
304 * Used for tracking duplicate values of fields
308 public $duplicateStack = [];
311 * @var array[] Module configuration
316 * Override/add urlparameters in listUrl() method
319 protected $overrideUrlParameters = [];
322 * Override the page ids taken into account by getPageIdConstraint()
326 protected $overridePageIdList = [];
329 * Array with before/after setting for tables
332 * 'before' => ['A', ...]
337 protected $tableDisplayOrder = [];
340 * Initializes the list generation
342 * @param int $id Page id for which the list is rendered. Must be >= 0
343 * @param string $table Tablename - if extended mode where only one table is listed at a time.
344 * @param int $pointer Browsing pointer.
345 * @param string $search Search word, if any
346 * @param int $levels Number of levels to search down the page tree
347 * @param int $showLimit Limit of records to be listed.
349 public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
351 $backendUser = $this->getBackendUserAuthentication();
352 // Setting internal variables:
353 // sets the parent id
354 $this->id
= (int)$id;
355 if ($GLOBALS['TCA'][$table]) {
356 // Setting single table mode, if table exists:
357 $this->table
= $table;
359 $this->firstElementNumber
= $pointer;
360 $this->searchString
= trim($search);
361 $this->searchLevels
= (int)$levels;
362 $this->showLimit
= MathUtility
::forceIntegerInRange($showLimit, 0, 10000);
364 $this->csvOutput
= (bool)GeneralUtility
::_GP('csv');
365 $this->sortField
= GeneralUtility
::_GP('sortField');
366 $this->sortRev
= GeneralUtility
::_GP('sortRev');
367 $this->displayFields
= GeneralUtility
::_GP('displayFields');
368 $this->duplicateField
= GeneralUtility
::_GP('duplicateField');
369 if (GeneralUtility
::_GP('justLocalized')) {
370 $this->localizationRedirect(GeneralUtility
::_GP('justLocalized'));
372 // Init dynamic vars:
375 $this->HTMLcode
= '';
377 if (isset($this->modTSconfig
['properties']['itemsLimitPerTable'])) {
378 $this->itemsLimitPerTable
= MathUtility
::forceIntegerInRange(
379 (int)$this->modTSconfig
['properties']['itemsLimitPerTable'],
384 if (isset($this->modTSconfig
['properties']['itemsLimitSingleTable'])) {
385 $this->itemsLimitSingleTable
= MathUtility
::forceIntegerInRange(
386 (int)$this->modTSconfig
['properties']['itemsLimitSingleTable'],
392 // $table might be NULL at this point in the code. As the expressionBuilder
393 // is used to limit returned records based on the page permissions and the
394 // uid field of the pages it can hardcoded to work on the pages table.
395 $expressionBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)
396 ->getQueryBuilderForTable('pages')
398 $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
399 // This will hide records from display - it has nothing to do with user rights!!
400 if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
401 $pidList = GeneralUtility
::intExplode(',', $pidList, true);
402 if (!empty($pidList)) {
403 $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
406 $this->perms_clause
= (string)$permsClause;
408 // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
409 $this->tablesCollapsed
= is_array($backendUser->uc
['moduleData']['list'])
410 ?
$backendUser->uc
['moduleData']['list']
412 $collapseOverride = GeneralUtility
::_GP('collapse');
413 if (is_array($collapseOverride)) {
414 foreach ($collapseOverride as $collapseTable => $collapseValue) {
415 if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 ||
$collapseValue == 1)) {
416 $this->tablesCollapsed
[$collapseTable] = $collapseValue;
419 // Save modified user uc
420 $backendUser->uc
['moduleData']['list'] = $this->tablesCollapsed
;
421 $backendUser->writeUC($backendUser->uc
);
422 $returnUrl = GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'));
423 if ($returnUrl !== '') {
424 HttpUtility
::redirect($returnUrl);
428 // Initialize languages:
429 if ($this->localizationView
) {
430 $this->initializeLanguages();
435 * Traverses the table(s) to be listed and renders the output code for each:
436 * The HTML is accumulated in $this->HTMLcode
437 * Finishes off with a stopper-gif
439 public function generateList()
441 // Set page record in header
442 $this->pageRecord
= BackendUtility
::getRecordWSOL('pages', $this->id
);
443 $hideTablesArray = GeneralUtility
::trimExplode(',', $this->hideTables
);
445 $backendUser = $this->getBackendUserAuthentication();
447 // pre-process tables and add sorting instructions
448 $tableNames = array_flip(array_keys($GLOBALS['TCA']));
449 foreach ($tableNames as $tableName => &$config) {
452 // Checking if the table should be rendered:
453 // Checks that we see only permitted/requested tables:
454 if ($this->table
&& $tableName !== $this->table
455 ||
$this->tableList
&& !GeneralUtility
::inList($this->tableList
, $tableName)
456 ||
!$backendUser->check('tables_select', $tableName)
462 // Don't show table if hidden by TCA ctrl section
463 // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
464 $hideTable = $hideTable
465 ||
!empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
466 ||
in_array($tableName, $hideTablesArray, true)
467 ||
in_array('*', $hideTablesArray, true);
468 // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
469 if (isset($this->tableTSconfigOverTCA
[$tableName . '.']['hideTable'])) {
470 $hideTable = (bool)$this->tableTSconfigOverTCA
[$tableName . '.']['hideTable'];
474 unset($tableNames[$tableName]);
476 if (isset($this->tableDisplayOrder
[$tableName])) {
477 // Copy display order information
478 $tableNames[$tableName] = $this->tableDisplayOrder
[$tableName];
480 $tableNames[$tableName] = [];
486 $orderedTableNames = GeneralUtility
::makeInstance(DependencyOrderingService
::class)
487 ->orderByDependencies($tableNames);
489 foreach ($orderedTableNames as $tableName => $_) {
490 // check if we are in single- or multi-table mode
492 $this->iLimit
= isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'])
493 ?
(int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']
494 : $this->itemsLimitSingleTable
;
496 // if there are no records in table continue current foreach
497 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)
498 ->getQueryBuilderForTable($tableName);
499 $queryBuilder->getRestrictions()
501 ->add(GeneralUtility
::makeInstance(DeletedRestriction
::class))
502 ->add(GeneralUtility
::makeInstance(BackendWorkspaceRestriction
::class));
503 $firstRow = $queryBuilder->select('uid')
505 ->where($this->getPageIdConstraint($tableName))
508 if (!is_array($firstRow)) {
511 $this->iLimit
= isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'])
512 ?
(int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']
513 : $this->itemsLimitPerTable
;
515 if ($this->showLimit
) {
516 $this->iLimit
= $this->showLimit
;
518 // Setting fields to select:
519 if ($this->allFields
) {
520 $fields = $this->makeFieldList($tableName);
521 $fields[] = 'tstamp';
522 $fields[] = 'crdate';
523 $fields[] = '_PATH_';
524 $fields[] = '_CONTROL_';
525 if (is_array($this->setFields
[$tableName])) {
526 $fields = array_intersect($fields, $this->setFields
[$tableName]);
534 // Finally, render the list:
535 $this->HTMLcode
.= $this->getTable($tableName, $this->id
, implode(',', $fields));
540 * To be implemented in extending classes.
542 * @param string $tableName
544 * @param string $fields List of fields to show in the listing. Pseudo fields will be added including the record header.
545 * @return string HTML code
547 public function getTable($tableName, $id, $fields = '')
553 * Creates the search box
555 * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
556 * @return string HTML for the search box
558 public function getSearchBox($formFields = true)
560 /** @var $iconFactory IconFactory */
561 $iconFactory = GeneralUtility
::makeInstance(IconFactory
::class);
562 $lang = $this->getLanguageService();
563 // Setting form-elements, if applicable:
564 $formElements = ['', ''];
566 $formElements = ['<form action="' . htmlspecialchars($this->listURL('', '-1', 'firstElementNumber,search_field')) . '" method="post">', '</form>'];
568 // Make level selector:
571 // "New" generation of search levels ... based on TS config
572 $config = BackendUtility
::getPagesTSconfig($this->id
);
573 $searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
574 $searchLevelItems = [];
576 // get translated labels for search levels from pagets
577 foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
578 $label = $lang->sL('LLL:' . $labelConfigured, false);
580 $label = $labelConfigured;
582 $searchLevelItems[$keySearchLevel] = $label;
585 foreach ($searchLevelItems as $kv => $label) {
586 $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ?
' selected="selected"' : '') . '>' . htmlspecialchars($label) . '</option>';
588 $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>';
589 // Table with the search box:
590 $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') . ';">
591 ' . $formElements[0] . '
592 <div id="typo3-dblist-search">
593 <div class="panel panel-default">
594 <div class="panel-body">
596 <div class="form-group col-xs-12">
597 <label for="search_field">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.searchString')) . ': </label>
598 <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
) . '" />
600 <div class="form-group col-xs-12 col-sm-6">
601 <label for="search_levels">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')) . ': </label>
604 <div class="form-group col-xs-12 col-sm-6">
605 <label for="showLimit">' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.label.limit')) . ': </label>
606 <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
: '')) . '" />
608 <div class="form-group col-xs-12">
609 <div class="form-control-wrap">
610 <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')) . '">
611 ' . $iconFactory->getIcon('actions-search', Icon
::SIZE_SMALL
)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.search')) . '
619 ' . $formElements[1] . '</div>';
623 /******************************
625 * Various helper functions
627 ******************************/
629 * Setting the field names to display in extended list.
630 * Sets the internal variable $this->setFields
632 public function setDispFields()
634 $backendUser = $this->getBackendUserAuthentication();
635 // Getting from session:
636 $dispFields = $backendUser->getModuleData('list/displayFields');
637 // If fields has been inputted, then set those as the value and push it to session variable:
638 if (is_array($this->displayFields
)) {
639 reset($this->displayFields
);
640 $tKey = key($this->displayFields
);
641 $dispFields[$tKey] = $this->displayFields
[$tKey];
642 $backendUser->pushModuleData('list/displayFields', $dispFields);
645 $this->setFields
= $dispFields;
649 * Create thumbnail code for record/field
651 * @param mixed[] $row Record array
652 * @param string $table Table (record is from)
653 * @param string $field Field name for which thumbnail are to be rendered.
654 * @return string HTML for thumbnails, if any.
656 public function thumbCode($row, $table, $field)
658 return BackendUtility
::thumbCode($row, $table, $field);
662 * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
663 * depending on the current searchlevel setting.
665 * @param string $table Table name
666 * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
667 * @param string[] $additionalConstraints Additional part for where clause
668 * @param string[] $fields Field list to select, * for all
669 * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
671 public function getQueryBuilder(
674 array $additionalConstraints = [],
675 array $fields = ['*']
677 $queryParameters = $this->buildQueryParameters($table, $pageId, $fields, $additionalConstraints);
679 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)
680 ->getQueryBuilderForTable($queryParameters['table']);
681 $queryBuilder->getRestrictions()
683 ->add(GeneralUtility
::makeInstance(DeletedRestriction
::class))
684 ->add(GeneralUtility
::makeInstance(BackendWorkspaceRestriction
::class));
686 ->select(...$queryParameters['fields'])
687 ->from($queryParameters['table'])
688 ->where(...$queryParameters['where']);
690 if (!empty($queryParameters['orderBy'])) {
691 foreach ($queryParameters['orderBy'] as $fieldNameAndSorting) {
692 list($fieldName, $sorting) = $fieldNameAndSorting;
693 $queryBuilder->addOrderBy($fieldName, $sorting);
697 if (!empty($queryParameters['firstResult'])) {
698 $queryBuilder->setFirstResult((int)$queryParameters['firstResult']);
701 if (!empty($queryParameters['maxResults'])) {
702 $queryBuilder->setMaxResults((int)$queryParameters['maxResults']);
705 if (!empty($queryParameters['groupBy'])) {
706 $queryBuilder->groupBy($queryParameters['groupBy']);
709 return $queryBuilder;
713 * Return the query parameters to select the records from a table $table with pid = $this->pidList
715 * @param string $table Table name
716 * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
717 * @param string[] $fieldList List of fields to select from the table
718 * @param string[] $additionalConstraints Additional part for where clause
721 protected function buildQueryParameters(
724 array $fieldList = ['*'],
725 array $additionalConstraints = []
727 $expressionBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)
728 ->getQueryBuilderForTable($table)
733 'fields' => $fieldList,
736 'firstResult' => $this->firstElementNumber ?
: null,
737 'maxResults' => $this->iLimit ?
$this->iLimit
: null,
740 if ($this->sortField
&& in_array($this->sortField
, $this->makeFieldList($table, 1))) {
741 $parameters['orderBy'][] = $this->sortRev ?
[$this->sortField
, 'DESC'] : [$this->sortField
, 'ASC'];
743 $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?
: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
744 $parameters['orderBy'] = QueryHelper
::parseOrderBy((string)$orderBy);
747 // Build the query constraints
749 'pidSelect' => $this->getPageIdConstraint($table),
750 'search' => $this->makeSearchString($table, $pageId)
753 // Filtering on displayable pages (permissions):
754 if ($table === 'pages' && $this->perms_clause
) {
755 $constraints['pagePermsClause'] = $this->perms_clause
;
758 // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
759 if ((GeneralUtility
::inList($this->hideTranslations
, $table) ||
$this->hideTranslations
=== '*')
760 && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
761 && $table !== 'pages_language_overlay'
763 $constraints['transOrigPointerField'] = $expressionBuilder->eq(
764 $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
769 $parameters['where'] = array_merge($constraints, $additionalConstraints);
771 $hookName = DatabaseRecordList
::class;
772 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
773 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $className) {
774 $hookObject = GeneralUtility
::makeInstance($className);
775 if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
776 $hookObject->buildQueryParametersPostProcess(
780 $additionalConstraints,
788 // array_unique / array_filter used to eliminate empty and duplicate constraints
789 // the array keys are eliminated by this as well to facilitate argument unpacking
790 // when used with the querybuilder.
791 $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
797 * Set the total items for the record list
799 * @param string $table Table name
800 * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
801 * @param array $constraints Additional constraints for where clause
803 public function setTotalItems(string $table, int $pageId, array $constraints)
805 $queryParameters = $this->buildQueryParameters($table, $pageId, ['*'], $constraints);
806 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)
807 ->getQueryBuilderForTable($queryParameters['table']);
808 $queryBuilder->getRestrictions()
810 ->add(GeneralUtility
::makeInstance(DeletedRestriction
::class))
811 ->add(GeneralUtility
::makeInstance(BackendWorkspaceRestriction
::class));
813 ->from($queryParameters['table'])
814 ->where(...$queryParameters['where']);
816 $this->totalItems
= (int)$queryBuilder->count('*')
822 * Creates part of query for searching after a word ($this->searchString)
823 * fields in input table.
825 * @param string $table Table, in which the fields are being searched.
826 * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
827 * @return string Returns part of WHERE-clause for searching, if applicable.
829 public function makeSearchString($table, $currentPid = -1)
831 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable($table);
832 $expressionBuilder = $queryBuilder->expr();
834 $currentPid = (int)$currentPid;
835 $tablePidField = $table === 'pages' ?
'uid' : 'pid';
836 // Make query, only if table is valid and a search string is actually defined:
837 if (empty($this->searchString
)) {
841 $searchableFields = $this->getSearchFields($table);
842 if (empty($searchableFields)) {
845 if (MathUtility
::canBeInterpretedAsInteger($this->searchString
)) {
846 $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString
);
847 foreach ($searchableFields as $fieldName) {
848 if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
851 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
852 $fieldType = $fieldConfig['type'];
853 $evalRules = $fieldConfig['eval'] ?
: '';
854 if ($fieldType === 'input' && $evalRules && GeneralUtility
::inList($evalRules, 'int')) {
855 if (is_array($fieldConfig['search'])
856 && in_array('pidonly', $fieldConfig['search'], true)
859 $constraints[] = $expressionBuilder->andX(
860 $expressionBuilder->eq($fieldName, (int)$this->searchString
),
861 $expressionBuilder->eq($tablePidField, (int)$currentPid)
864 } elseif ($fieldType === 'text'
865 ||
$fieldType === 'flex'
866 ||
($fieldType === 'input' && (!$evalRules ||
!preg_match('/date|time|int/', $evalRules)))
868 $constraints[] = $expressionBuilder->like(
870 $queryBuilder->quote('%' . (int)$this->searchString
. '%')
875 $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString
) . '%');
876 foreach ($searchableFields as $fieldName) {
877 if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
880 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
881 $fieldType = $fieldConfig['type'];
882 $evalRules = $fieldConfig['eval'] ?
: '';
883 $searchConstraint = $expressionBuilder->andX(
884 $expressionBuilder->comparison(
885 'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
887 'LOWER(' . $like . ')'
890 if (is_array($fieldConfig['search'])) {
891 $searchConfig = $fieldConfig['search'];
892 if (in_array('case', $searchConfig)) {
893 // Replace case insensitive default constraint
894 $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
896 if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
897 $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
899 if ($searchConfig['andWhere']) {
900 $searchConstraint->add(
901 QueryHelper
::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
905 if ($fieldType === 'text'
906 ||
$fieldType === 'flex'
907 ||
$fieldType === 'input' && (!$evalRules ||
!preg_match('/date|time|int/', $evalRules))
909 if ($searchConstraint->count() !== 0) {
910 $constraints[] = $searchConstraint;
915 // If no search field conditions have been build ensure no results are returned
916 if (empty($constraints)) {
920 return $expressionBuilder->orX(...$constraints);
924 * Fetches a list of fields to use in the Backend search for the given table.
926 * @param string $tableName
929 protected function getSearchFields($tableName)
932 $fieldListWasSet = false;
933 // Get fields from ctrl section of TCA first
934 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
935 $fieldArray = GeneralUtility
::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
936 $fieldListWasSet = true;
938 // Call hook to add or change the list
939 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
941 'tableHasSearchConfiguration' => $fieldListWasSet,
942 'tableName' => $tableName,
943 'searchFields' => &$fieldArray,
944 'searchString' => $this->searchString
946 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
947 GeneralUtility
::callUserFunction($hookFunction, $hookParameters, $this);
954 * Returns the title (based on $code) of a table ($table) with the proper link around. For headers over tables.
955 * The link will cause the display of all extended mode or not for the table.
957 * @param string $table Table name
958 * @param string $code Table label
959 * @return string The linked table label
961 public function linkWrapTable($table, $code)
963 if ($this->table
!== $table) {
964 return '<a href="' . htmlspecialchars($this->listURL('', $table, 'firstElementNumber')) . '">' . $code . '</a>';
966 return '<a href="' . htmlspecialchars($this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')) . '">' . $code . '</a>';
970 * Returns the title (based on $code) of a record (from table $table) with the proper link around (that is for 'pages'-records a link to the level of that record...)
972 * @param string $table Table name
973 * @param int $uid Item uid
974 * @param string $code Item title (not htmlspecialchars()'ed yet)
975 * @param mixed[] $row Item row
976 * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
978 public function linkWrapItems($table, $uid, $code, $row)
980 $lang = $this->getLanguageService();
982 // If the title is blank, make a "no title" label:
983 if ((string)$code === '') {
984 $code = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.no_title')) . ']</i> - '
985 . htmlspecialchars(BackendUtility
::getRecordTitle($table, $row));
987 $code = htmlspecialchars($code, ENT_QUOTES
, 'UTF-8', false);
988 if ($code != htmlspecialchars($origCode)) {
989 $code = '<span title="' . htmlspecialchars($origCode, ENT_QUOTES
, 'UTF-8', false) . '">' . $code . '</span>';
992 switch ((string)$this->clickTitleMode
) {
994 // If the listed table is 'pages' we have to request the permission settings for each page:
995 if ($table === 'pages') {
996 $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility
::getRecord('pages', $row['uid']));
997 $permsEdit = $localCalcPerms & Permission
::PAGE_EDIT
;
999 $permsEdit = $this->calcPerms
& Permission
::CONTENT_EDIT
;
1001 // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1003 $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1004 $code = '<a href="#" onclick="' . htmlspecialchars(BackendUtility
::editOnClick($params, '', -1)) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
1008 // "Show" link (only pages and tt_content elements)
1009 if ($table === 'pages' ||
$table === 'tt_content') {
1010 $code = '<a href="#" onclick="' . htmlspecialchars(
1011 BackendUtility
::viewOnClick(($table === 'tt_content' ?
$this->id
. '#' . $row['uid'] : $row['uid']))
1012 ) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage')) . '">' . $code . '</a>';
1016 // "Info": (All records)
1017 $code = '<a href="#" onclick="' . htmlspecialchars(('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
1020 // Output the label now:
1021 if ($table === 'pages') {
1022 $code = '<a href="' . htmlspecialchars($this->listURL($uid, '', 'firstElementNumber')) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
1024 $code = $this->linkUrlMail($code, $origCode);
1031 * Wrapping input code in link to URL or email if $testString is either.
1033 * @param string $code code to wrap
1034 * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
1035 * @return string Link-Wrapped $code value, if $testString was URL or email.
1037 public function linkUrlMail($code, $testString)
1040 $scheme = parse_url($testString, PHP_URL_SCHEME
);
1041 if ($scheme === 'http' ||
$scheme === 'https' ||
$scheme === 'ftp') {
1042 return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1045 if (GeneralUtility
::validEmail($testString)) {
1046 return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1048 // Return if nothing else...
1053 * Creates the URL to this script, including all relevant GPvars
1054 * Fixed GPvars are id, table, imagemode, returnUrl, search_field, search_levels and showLimit
1055 * The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
1057 * @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
1058 * @param string $table Table name to display. Enter "-1" for the current table.
1059 * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
1060 * @return string URL
1062 public function listURL($altId = '', $table = '-1', $exclList = '')
1064 $urlParameters = [];
1065 if ((string)$altId !== '') {
1066 $urlParameters['id'] = $altId;
1068 $urlParameters['id'] = $this->id
;
1070 if ($table === '-1') {
1071 $urlParameters['table'] = $this->table
;
1073 $urlParameters['table'] = $table;
1075 if ($this->thumbs
) {
1076 $urlParameters['imagemode'] = $this->thumbs
;
1078 if ($this->returnUrl
) {
1079 $urlParameters['returnUrl'] = $this->returnUrl
;
1081 if ((!$exclList ||
!GeneralUtility
::inList($exclList, 'search_field')) && $this->searchString
) {
1082 $urlParameters['search_field'] = $this->searchString
;
1084 if ($this->searchLevels
) {
1085 $urlParameters['search_levels'] = $this->searchLevels
;
1087 if ($this->showLimit
) {
1088 $urlParameters['showLimit'] = $this->showLimit
;
1090 if ((!$exclList ||
!GeneralUtility
::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber
) {
1091 $urlParameters['pointer'] = $this->firstElementNumber
;
1093 if ((!$exclList ||
!GeneralUtility
::inList($exclList, 'sortField')) && $this->sortField
) {
1094 $urlParameters['sortField'] = $this->sortField
;
1096 if ((!$exclList ||
!GeneralUtility
::inList($exclList, 'sortRev')) && $this->sortRev
) {
1097 $urlParameters['sortRev'] = $this->sortRev
;
1100 $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters
);
1102 if ($routePath = GeneralUtility
::_GP('route')) {
1103 $router = GeneralUtility
::makeInstance(Router
::class);
1104 $route = $router->match($routePath);
1105 $uriBuilder = GeneralUtility
::makeInstance(UriBuilder
::class);
1106 $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
1107 } elseif ($moduleName = GeneralUtility
::_GP('M')) {
1108 $url = BackendUtility
::getModuleUrl($moduleName, $urlParameters);
1110 $url = GeneralUtility
::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility
::implodeArrayForUrl('', $urlParameters), '&');
1116 * Returns "requestUri" - which is basically listURL
1118 * @return string Content of ->listURL()
1120 public function requestUri()
1122 return $this->listURL();
1126 * Makes the list of fields to select for a table
1128 * @param string $table Table name
1129 * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
1130 * @param bool $addDateFields If set, also adds crdate and tstamp fields (note: they will also be added if user is admin or dontCheckUser is set)
1131 * @return string[] Array, where values are fieldnames to include in query
1133 public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
1135 $backendUser = $this->getBackendUserAuthentication();
1136 // Init fieldlist array:
1139 if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1140 if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1141 // Traverse configured columns and add them to field array, if available for user.
1142 foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
1143 if ($dontCheckUser ||
(!$fieldValue['exclude'] ||
$backendUser->check('non_exclude_fields', $table . ':' . $fN)) && $fieldValue['config']['type'] !== 'passthrough') {
1144 $fieldListArr[] = $fN;
1148 $fieldListArr[] = 'uid';
1149 $fieldListArr[] = 'pid';
1152 if ($dontCheckUser ||
$backendUser->isAdmin() ||
$addDateFields) {
1153 if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1154 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
1156 if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1157 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
1160 // Add more special fields:
1161 if ($dontCheckUser ||
$backendUser->isAdmin()) {
1162 if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1163 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
1165 if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1166 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
1168 if (ExtensionManagementUtility
::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1169 $fieldListArr[] = 't3ver_id';
1170 $fieldListArr[] = 't3ver_state';
1171 $fieldListArr[] = 't3ver_wsid';
1175 GeneralUtility
::sysLog(sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table), 'core', GeneralUtility
::SYSLOG_SEVERITY_ERROR
);
1178 return $fieldListArr;
1182 * Get all allowed mount pages to be searched in.
1184 * @param int $id Page id
1185 * @param int $depth Depth to go down
1186 * @param string $perms_clause select clause
1189 protected function getSearchableWebmounts($id, $depth, $perms_clause)
1191 $backendUser = $this->getBackendUserAuthentication();
1192 /** @var PageTreeView $tree */
1193 $tree = GeneralUtility
::makeInstance(PageTreeView
::class);
1194 $tree->init('AND ' . $perms_clause);
1195 $tree->makeHTML
= 0;
1196 $tree->fieldArray
= ['uid', 'php_tree_stop'];
1199 $allowedMounts = !$backendUser->isAdmin() && $id === 0
1200 ?
$backendUser->returnWebmounts()
1203 foreach ($allowedMounts as $allowedMount) {
1204 $idList[] = $allowedMount;
1206 $tree->getTree($allowedMount, $depth, '');
1208 $idList = array_merge($idList, $tree->ids
);
1215 * Redirects to FormEngine if a record is just localized.
1217 * @param string $justLocalized String with table, orig uid and language separated by ":
1219 public function localizationRedirect($justLocalized)
1221 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1222 if ($GLOBALS['TCA'][$table]
1223 && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1224 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1226 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable($table);
1227 $queryBuilder->getRestrictions()
1229 ->add(GeneralUtility
::makeInstance(DeletedRestriction
::class))
1230 ->add(GeneralUtility
::makeInstance(BackendWorkspaceRestriction
::class));
1232 $localizedRecordUid = $queryBuilder->select('uid')
1235 $queryBuilder->expr()->eq(
1236 $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1237 $queryBuilder->createNamedParameter($language, \PDO
::PARAM_INT
)
1239 $queryBuilder->expr()->eq(
1240 $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1241 $queryBuilder->createNamedParameter($orig_uid, \PDO
::PARAM_INT
)
1248 if ($localizedRecordUid !== false) {
1249 // Create parameters and finally run the classic page module for creating a new page translation
1250 $url = $this->listURL();
1251 $editUserAccountUrl = BackendUtility
::getModuleUrl(
1254 'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
1258 HttpUtility
::redirect($editUserAccountUrl);
1264 * Set URL parameters to override or add in the listUrl() method.
1266 * @param string[] $urlParameters
1268 public function setOverrideUrlParameters(array $urlParameters)
1270 $this->overrideUrlParameters
= $urlParameters;
1274 * Set table display order information
1276 * Structure of $orderInformation:
1278 * 'before' => // comma-separated string list or array of table names
1279 * 'after' => // comma-separated string list or array of table names
1282 * @param array $orderInformation
1283 * @throws \UnexpectedValueException
1285 public function setTableDisplayOrder(array $orderInformation)
1287 foreach ($orderInformation as $tableName => &$configuration) {
1288 if (isset($configuration['before'])) {
1289 if (is_string($configuration['before'])) {
1290 $configuration['before'] = GeneralUtility
::trimExplode(',', $configuration['before'], true);
1291 } elseif (!is_array($configuration['before'])) {
1292 throw new \
UnexpectedValueException('The specified "before" order configuration for table "' . $tableName . '" is invalid.', 1436195933);
1295 if (isset($configuration['after'])) {
1296 if (is_string($configuration['after'])) {
1297 $configuration['after'] = GeneralUtility
::trimExplode(',', $configuration['after'], true);
1298 } elseif (!is_array($configuration['after'])) {
1299 throw new \
UnexpectedValueException('The specified "after" order configuration for table "' . $tableName . '" is invalid.', 1436195934);
1303 $this->tableDisplayOrder
= $orderInformation;
1309 public function getOverridePageIdList(): array
1311 return $this->overridePageIdList
;
1315 * @param int[]|array $overridePageIdList
1317 public function setOverridePageIdList(array $overridePageIdList)
1319 $this->overridePageIdList
= array_map('intval', $overridePageIdList);
1323 * Build SQL fragment to limit a query to a list of page IDs based on
1324 * the current search level setting.
1326 * @param string $tableName
1329 protected function getPageIdConstraint(string $tableName): string
1331 // Set search levels:
1332 $searchLevels = $this->searchLevels
;
1334 // Default is to search everywhere
1335 $constraint = '1=1';
1337 $expressionBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)
1338 ->getConnectionForTable($tableName)
1339 ->getExpressionBuilder();
1341 if ($searchLevels === 0) {
1342 $constraint = $expressionBuilder->eq($tableName . '.pid', (int)$this->id
);
1343 } elseif ($searchLevels > 0) {
1344 $allowedMounts = $this->getSearchableWebmounts($this->id
, $searchLevels, $this->perms_clause
);
1345 $constraint = $expressionBuilder->in($tableName . '.pid', array_map('intval', $allowedMounts));
1348 if (!empty($this->getOverridePageIdList())) {
1349 $constraint = $expressionBuilder->in(
1350 $tableName . '.pid',
1351 $this->getOverridePageIdList()
1355 return (string)$constraint;
1359 * @return BackendUserAuthentication
1361 protected function getBackendUserAuthentication()
1363 return $GLOBALS['BE_USER'];