[CLEANUP] EXT:recordlist
[Packages/TYPO3.CMS.git] / typo3 / sysext / recordlist / Classes / RecordList / AbstractDatabaseRecordList.php
1 <?php
2 namespace TYPO3\CMS\Recordlist\RecordList;
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
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;
36
37 /**
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
41 */
42 class AbstractDatabaseRecordList extends AbstractRecordList
43 {
44 /**
45 * Specify a list of tables which are the only ones allowed to be displayed.
46 *
47 * @var string
48 */
49 public $tableList = '';
50
51 /**
52 * Return URL
53 *
54 * @var string
55 */
56 public $returnUrl = '';
57
58 /**
59 * Thumbnails on records containing files (pictures)
60 *
61 * @var bool
62 */
63 public $thumbs = 0;
64
65 /**
66 * default Max items shown per table in "multi-table mode", may be overridden by tables.php
67 *
68 * @var int
69 */
70 public $itemsLimitPerTable = 20;
71
72 /**
73 * default Max items shown per table in "single-table mode", may be overridden by tables.php
74 *
75 * @var int
76 */
77 public $itemsLimitSingleTable = 100;
78
79 /**
80 * Current script name
81 *
82 * @var string
83 */
84 public $script = 'index.php';
85
86 /**
87 * Indicates if all available fields for a user should be selected or not.
88 *
89 * @var int
90 */
91 public $allFields = 0;
92
93 /**
94 * Whether to show localization view or not
95 *
96 * @var bool
97 */
98 public $localizationView = false;
99
100 /**
101 * If set, csvList is outputted.
102 *
103 * @var bool
104 */
105 public $csvOutput = false;
106
107 /**
108 * Field, to sort list by
109 *
110 * @var string
111 */
112 public $sortField;
113
114 /**
115 * Field, indicating to sort in reverse order.
116 *
117 * @var bool
118 */
119 public $sortRev;
120
121 /**
122 * Containing which fields to display in extended mode
123 *
124 * @var string[]
125 */
126 public $displayFields;
127
128 /**
129 * String, can contain the field name from a table which must have duplicate values marked.
130 *
131 * @var string
132 */
133 public $duplicateField;
134
135 /**
136 * Page id
137 *
138 * @var int
139 */
140 public $id;
141
142 /**
143 * Tablename if single-table mode
144 *
145 * @var string
146 */
147 public $table = '';
148
149 /**
150 * If TRUE, records are listed only if a specific table is selected.
151 *
152 * @var bool
153 */
154 public $listOnlyInSingleTableMode = false;
155
156 /**
157 * Pointer for browsing list
158 *
159 * @var int
160 */
161 public $firstElementNumber = 0;
162
163 /**
164 * Search string
165 *
166 * @var string
167 */
168 public $searchString = '';
169
170 /**
171 * Levels to search down.
172 *
173 * @var int
174 */
175 public $searchLevels = '';
176
177 /**
178 * Number of records to show
179 *
180 * @var int
181 */
182 public $showLimit = 0;
183
184 /**
185 * Page select permissions
186 *
187 * @var string
188 */
189 public $perms_clause = '';
190
191 /**
192 * Some permissions...
193 *
194 * @var int
195 */
196 public $calcPerms = 0;
197
198 /**
199 * Mode for what happens when a user clicks the title of a record.
200 *
201 * @var string
202 */
203 public $clickTitleMode = '';
204
205 /**
206 * Shared module configuration, used by localization features
207 *
208 * @var array
209 */
210 public $modSharedTSconfig = [];
211
212 /**
213 * Loaded with page record with version overlay if any.
214 *
215 * @var string[]
216 */
217 public $pageRecord = [];
218
219 /**
220 * Tables which should not get listed
221 *
222 * @var string
223 */
224 public $hideTables = '';
225
226 /**
227 * Tables which should not list their translations
228 *
229 * @var string
230 */
231 public $hideTranslations = '';
232
233 /**
234 * TSconfig which overwrites TCA-Settings
235 *
236 * @var mixed[][]
237 */
238 public $tableTSconfigOverTCA = [];
239
240 /**
241 * Array of collapsed / uncollapsed tables in multi table view
242 *
243 * @var int[][]
244 */
245 public $tablesCollapsed = [];
246
247 /**
248 * JavaScript code accumulation
249 *
250 * @var string
251 */
252 public $JScode = '';
253
254 /**
255 * HTML output
256 *
257 * @var string
258 */
259 public $HTMLcode = '';
260
261 /**
262 * "LIMIT " in SQL...
263 *
264 * @var int
265 */
266 public $iLimit = 0;
267
268 /**
269 * Counting the elements no matter what...
270 *
271 * @var int
272 */
273 public $eCounter = 0;
274
275 /**
276 * Set to the total number of items for a table when selecting.
277 *
278 * @var string
279 */
280 public $totalItems = '';
281
282 /**
283 * Cache for record path
284 *
285 * @var mixed[]
286 */
287 public $recPath_cache = [];
288
289 /**
290 * Fields to display for the current table
291 *
292 * @var string[]
293 */
294 public $setFields = [];
295
296 /**
297 * Used for tracking next/prev uids
298 *
299 * @var int[][]
300 */
301 public $currentTable = [];
302
303 /**
304 * Used for tracking duplicate values of fields
305 *
306 * @var string[]
307 */
308 public $duplicateStack = [];
309
310 /**
311 * @var array[] Module configuration
312 */
313 public $modTSconfig;
314
315 /**
316 * Override/add urlparameters in listUrl() method
317 * @var string[]
318 */
319 protected $overrideUrlParameters = [];
320
321 /**
322 * Override the page ids taken into account by getPageIdConstraint()
323 *
324 * @var array
325 */
326 protected $overridePageIdList = [];
327
328 /**
329 * Array with before/after setting for tables
330 * Structure:
331 * 'tableName' => [
332 * 'before' => ['A', ...]
333 * 'after' => []
334 * ]
335 * @var array[]
336 */
337 protected $tableDisplayOrder = [];
338
339 /**
340 * Initializes the list generation
341 *
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.
348 */
349 public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
350 {
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;
358 }
359 $this->firstElementNumber = $pointer;
360 $this->searchString = trim($search);
361 $this->searchLevels = (int)$levels;
362 $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
363 // Setting GPvars:
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'));
371 }
372 // Init dynamic vars:
373 $this->counter = 0;
374 $this->JScode = '';
375 $this->HTMLcode = '';
376 // Limits
377 if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
378 $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
379 (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
380 1,
381 10000
382 );
383 }
384 if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
385 $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
386 (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
387 1,
388 10000
389 );
390 }
391
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')
397 ->expr();
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));
404 }
405 }
406 $this->perms_clause = (string)$permsClause;
407
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']
411 : [];
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;
417 }
418 }
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);
425 }
426 }
427
428 // Initialize languages:
429 if ($this->localizationView) {
430 $this->initializeLanguages();
431 }
432 }
433
434 /**
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
438 */
439 public function generateList()
440 {
441 // Set page record in header
442 $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
443 $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
444
445 $backendUser = $this->getBackendUserAuthentication();
446
447 // pre-process tables and add sorting instructions
448 $tableNames = array_flip(array_keys($GLOBALS['TCA']));
449 foreach ($tableNames as $tableName => &$config) {
450 $hideTable = false;
451
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)
457 ) {
458 $hideTable = true;
459 }
460
461 if (!$hideTable) {
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'];
471 }
472 }
473 if ($hideTable) {
474 unset($tableNames[$tableName]);
475 } else {
476 if (isset($this->tableDisplayOrder[$tableName])) {
477 // Copy display order information
478 $tableNames[$tableName] = $this->tableDisplayOrder[$tableName];
479 } else {
480 $tableNames[$tableName] = [];
481 }
482 }
483 }
484 unset($config);
485
486 $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
487 ->orderByDependencies($tableNames);
488
489 foreach ($orderedTableNames as $tableName => $_) {
490 // check if we are in single- or multi-table mode
491 if ($this->table) {
492 $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'])
493 ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']
494 : $this->itemsLimitSingleTable;
495 } else {
496 // if there are no records in table continue current foreach
497 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
498 ->getQueryBuilderForTable($tableName);
499 $queryBuilder->getRestrictions()
500 ->removeAll()
501 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
502 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
503 $firstRow = $queryBuilder->select('uid')
504 ->from($tableName)
505 ->where($this->getPageIdConstraint($tableName))
506 ->execute()
507 ->fetch();
508 if (!is_array($firstRow)) {
509 continue;
510 }
511 $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'])
512 ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']
513 : $this->itemsLimitPerTable;
514 }
515 if ($this->showLimit) {
516 $this->iLimit = $this->showLimit;
517 }
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]);
527 } else {
528 $fields = [];
529 }
530 } else {
531 $fields = [];
532 }
533
534 // Finally, render the list:
535 $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
536 }
537 }
538
539 /**
540 * To be implemented in extending classes.
541 *
542 * @param string $tableName
543 * @param int $id
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
546 */
547 public function getTable($tableName, $id, $fields = '')
548 {
549 return '';
550 }
551
552 /**
553 * Creates the search box
554 *
555 * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
556 * @return string HTML for the search box
557 */
558 public function getSearchBox($formFields = true)
559 {
560 /** @var $iconFactory IconFactory */
561 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
562 $lang = $this->getLanguageService();
563 // Setting form-elements, if applicable:
564 $formElements = ['', ''];
565 if ($formFields) {
566 $formElements = ['<form action="' . htmlspecialchars($this->listURL('', '-1', 'firstElementNumber,search_field')) . '" method="post">', '</form>'];
567 }
568 // Make level selector:
569 $opt = [];
570
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 = [];
575
576 // get translated labels for search levels from pagets
577 foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
578 $label = $lang->sL('LLL:' . $labelConfigured);
579 if ($label === '') {
580 $label = $labelConfigured;
581 }
582 $searchLevelItems[$keySearchLevel] = $label;
583 }
584
585 foreach ($searchLevelItems as $kv => $label) {
586 $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars($label) . '</option>';
587 }
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">
595 <div class="row">
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) . '" />
599 </div>
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>
602 ' . $lMenu . '
603 </div>
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 : '')) . '" />
607 </div>
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')) . '
612 </button>
613 </div>
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 ******************************/
628 /**
629 * Setting the field names to display in extended list.
630 * Sets the internal variable $this->setFields
631 */
632 public function setDispFields()
633 {
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);
643 }
644 // Setting result:
645 $this->setFields = $dispFields;
646 }
647
648 /**
649 * Create thumbnail code for record/field
650 *
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.
655 */
656 public function thumbCode($row, $table, $field)
657 {
658 return BackendUtility::thumbCode($row, $table, $field);
659 }
660
661 /**
662 * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
663 * depending on the current searchlevel setting.
664 *
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
670 */
671 public function getQueryBuilder(
672 string $table,
673 int $pageId,
674 array $additionalConstraints = [],
675 array $fields = ['*']
676 ): QueryBuilder {
677 $queryParameters = $this->buildQueryParameters($table, $pageId, $fields, $additionalConstraints);
678
679 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
680 ->getQueryBuilderForTable($queryParameters['table']);
681 $queryBuilder->getRestrictions()
682 ->removeAll()
683 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
684 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
685 $queryBuilder
686 ->select(...$queryParameters['fields'])
687 ->from($queryParameters['table'])
688 ->where(...$queryParameters['where']);
689
690 if (!empty($queryParameters['orderBy'])) {
691 foreach ($queryParameters['orderBy'] as $fieldNameAndSorting) {
692 list($fieldName, $sorting) = $fieldNameAndSorting;
693 $queryBuilder->addOrderBy($fieldName, $sorting);
694 }
695 }
696
697 if (!empty($queryParameters['firstResult'])) {
698 $queryBuilder->setFirstResult((int)$queryParameters['firstResult']);
699 }
700
701 if (!empty($queryParameters['maxResults'])) {
702 $queryBuilder->setMaxResults((int)$queryParameters['maxResults']);
703 }
704
705 if (!empty($queryParameters['groupBy'])) {
706 $queryBuilder->groupBy($queryParameters['groupBy']);
707 }
708
709 return $queryBuilder;
710 }
711
712 /**
713 * Return the query parameters to select the records from a table $table with pid = $this->pidList
714 *
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
719 * @return array
720 */
721 protected function buildQueryParameters(
722 string $table,
723 int $pageId,
724 array $fieldList = ['*'],
725 array $additionalConstraints = []
726 ): array {
727 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
728 ->getQueryBuilderForTable($table)
729 ->expr();
730
731 $parameters = [
732 'table' => $table,
733 'fields' => $fieldList,
734 'groupBy' => null,
735 'orderBy' => null,
736 'firstResult' => $this->firstElementNumber ?: null,
737 'maxResults' => $this->iLimit ? $this->iLimit : null,
738 ];
739
740 if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
741 $parameters['orderBy'][] = $this->sortRev ? [$this->sortField, 'DESC'] : [$this->sortField, 'ASC'];
742 } else {
743 $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
744 $parameters['orderBy'] = QueryHelper::parseOrderBy((string)$orderBy);
745 }
746
747 // Build the query constraints
748 $constraints = [
749 'pidSelect' => $this->getPageIdConstraint($table),
750 'search' => $this->makeSearchString($table, $pageId)
751 ];
752
753 // Filtering on displayable pages (permissions):
754 if ($table === 'pages' && $this->perms_clause) {
755 $constraints['pagePermsClause'] = $this->perms_clause;
756 }
757
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'
762 ) {
763 $constraints['transOrigPointerField'] = $expressionBuilder->eq(
764 $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
765 0
766 );
767 }
768
769 $parameters['where'] = array_merge($constraints, $additionalConstraints);
770
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(
777 $parameters,
778 $table,
779 $pageId,
780 $additionalConstraints,
781 $fieldList,
782 $this
783 );
784 }
785 }
786 }
787
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'])));
792
793 return $parameters;
794 }
795
796 /**
797 * Set the total items for the record list
798 *
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
802 */
803 public function setTotalItems(string $table, int $pageId, array $constraints)
804 {
805 $queryParameters = $this->buildQueryParameters($table, $pageId, ['*'], $constraints);
806 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
807 ->getQueryBuilderForTable($queryParameters['table']);
808 $queryBuilder->getRestrictions()
809 ->removeAll()
810 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
811 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
812 $queryBuilder
813 ->from($queryParameters['table'])
814 ->where(...$queryParameters['where']);
815
816 $this->totalItems = (int)$queryBuilder->count('*')
817 ->execute()
818 ->fetchColumn();
819 }
820
821 /**
822 * Creates part of query for searching after a word ($this->searchString)
823 * fields in input table.
824 *
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.
828 */
829 public function makeSearchString($table, $currentPid = -1)
830 {
831 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
832 $expressionBuilder = $queryBuilder->expr();
833 $constraints = [];
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)) {
838 return '';
839 }
840
841 $searchableFields = $this->getSearchFields($table);
842 if (empty($searchableFields)) {
843 return '';
844 }
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])) {
849 continue;
850 }
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)
857 && $currentPid > 0
858 ) {
859 $constraints[] = $expressionBuilder->andX(
860 $expressionBuilder->eq($fieldName, (int)$this->searchString),
861 $expressionBuilder->eq($tablePidField, (int)$currentPid)
862 );
863 }
864 } elseif ($fieldType === 'text'
865 || $fieldType === 'flex'
866 || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
867 ) {
868 $constraints[] = $expressionBuilder->like(
869 $fieldName,
870 $queryBuilder->quote('%' . (int)$this->searchString . '%')
871 );
872 }
873 }
874 } else {
875 $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
876 foreach ($searchableFields as $fieldName) {
877 if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
878 continue;
879 }
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) . ')',
886 'LIKE',
887 'LOWER(' . $like . ')'
888 )
889 );
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));
895 }
896 if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
897 $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
898 }
899 if ($searchConfig['andWhere']) {
900 $searchConstraint->add(
901 QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
902 );
903 }
904 }
905 if ($fieldType === 'text'
906 || $fieldType === 'flex'
907 || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
908 ) {
909 if ($searchConstraint->count() !== 0) {
910 $constraints[] = $searchConstraint;
911 }
912 }
913 }
914 }
915 // If no search field conditions have been build ensure no results are returned
916 if (empty($constraints)) {
917 return '0=1';
918 }
919
920 return $expressionBuilder->orX(...$constraints);
921 }
922
923 /**
924 * Fetches a list of fields to use in the Backend search for the given table.
925 *
926 * @param string $tableName
927 * @return string[]
928 */
929 protected function getSearchFields($tableName)
930 {
931 $fieldArray = [];
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;
937 }
938 // Call hook to add or change the list
939 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
940 $hookParameters = [
941 'tableHasSearchConfiguration' => $fieldListWasSet,
942 'tableName' => $tableName,
943 'searchFields' => &$fieldArray,
944 'searchString' => $this->searchString
945 ];
946 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
947 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
948 }
949 }
950 return $fieldArray;
951 }
952
953 /**
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.
956 *
957 * @param string $table Table name
958 * @param string $code Table label
959 * @return string The linked table label
960 */
961 public function linkWrapTable($table, $code)
962 {
963 if ($this->table !== $table) {
964 return '<a href="' . htmlspecialchars($this->listURL('', $table, 'firstElementNumber')) . '">' . $code . '</a>';
965 }
966 return '<a href="' . htmlspecialchars($this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')) . '">' . $code . '</a>';
967 }
968
969 /**
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...)
971 *
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)
977 */
978 public function linkWrapItems($table, $uid, $code, $row)
979 {
980 $lang = $this->getLanguageService();
981 $origCode = $code;
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));
986 } else {
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>';
990 }
991 }
992 switch ((string)$this->clickTitleMode) {
993 case 'edit':
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;
998 } else {
999 $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
1000 }
1001 // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1002 if ($permsEdit) {
1003 $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1004 $code = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1)) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
1005 }
1006 break;
1007 case 'show':
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>';
1013 }
1014 break;
1015 case 'info':
1016 // "Info": (All records)
1017 $code = '<a href="#" onclick="' . htmlspecialchars(('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
1018 break;
1019 default:
1020 // Output the label now:
1021 if ($table === 'pages') {
1022 $code = '<a href="' . htmlspecialchars($this->listURL($uid, '', 'firstElementNumber')) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
1023 } else {
1024 $code = $this->linkUrlMail($code, $origCode);
1025 }
1026 }
1027 return $code;
1028 }
1029
1030 /**
1031 * Wrapping input code in link to URL or email if $testString is either.
1032 *
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.
1036 */
1037 public function linkUrlMail($code, $testString)
1038 {
1039 // Check for URL:
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>';
1043 }
1044 // Check for email:
1045 if (GeneralUtility::validEmail($testString)) {
1046 return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1047 }
1048 // Return if nothing else...
1049 return $code;
1050 }
1051
1052 /**
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.
1056 *
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
1061 */
1062 public function listURL($altId = '', $table = '-1', $exclList = '')
1063 {
1064 $urlParameters = [];
1065 if ((string)$altId !== '') {
1066 $urlParameters['id'] = $altId;
1067 } else {
1068 $urlParameters['id'] = $this->id;
1069 }
1070 if ($table === '-1') {
1071 $urlParameters['table'] = $this->table;
1072 } else {
1073 $urlParameters['table'] = $table;
1074 }
1075 if ($this->thumbs) {
1076 $urlParameters['imagemode'] = $this->thumbs;
1077 }
1078 if ($this->returnUrl) {
1079 $urlParameters['returnUrl'] = $this->returnUrl;
1080 }
1081 if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
1082 $urlParameters['search_field'] = $this->searchString;
1083 }
1084 if ($this->searchLevels) {
1085 $urlParameters['search_levels'] = $this->searchLevels;
1086 }
1087 if ($this->showLimit) {
1088 $urlParameters['showLimit'] = $this->showLimit;
1089 }
1090 if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
1091 $urlParameters['pointer'] = $this->firstElementNumber;
1092 }
1093 if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
1094 $urlParameters['sortField'] = $this->sortField;
1095 }
1096 if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
1097 $urlParameters['sortRev'] = $this->sortRev;
1098 }
1099
1100 $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
1101
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);
1109 } else {
1110 $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&');
1111 }
1112 return $url;
1113 }
1114
1115 /**
1116 * Returns "requestUri" - which is basically listURL
1117 *
1118 * @return string Content of ->listURL()
1119 */
1120 public function requestUri()
1121 {
1122 return $this->listURL();
1123 }
1124
1125 /**
1126 * Makes the list of fields to select for a table
1127 *
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
1132 */
1133 public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
1134 {
1135 $backendUser = $this->getBackendUserAuthentication();
1136 // Init fieldlist array:
1137 $fieldListArr = [];
1138 // Check table:
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;
1145 }
1146 }
1147
1148 $fieldListArr[] = 'uid';
1149 $fieldListArr[] = 'pid';
1150
1151 // Add date fields
1152 if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
1153 if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1154 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
1155 }
1156 if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1157 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
1158 }
1159 }
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'];
1164 }
1165 if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1166 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
1167 }
1168 if (ExtensionManagementUtility::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1169 $fieldListArr[] = 't3ver_id';
1170 $fieldListArr[] = 't3ver_state';
1171 $fieldListArr[] = 't3ver_wsid';
1172 }
1173 }
1174 } else {
1175 GeneralUtility::sysLog(sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1176 }
1177 }
1178 return $fieldListArr;
1179 }
1180
1181 /**
1182 * Get all allowed mount pages to be searched in.
1183 *
1184 * @param int $id Page id
1185 * @param int $depth Depth to go down
1186 * @param string $perms_clause select clause
1187 * @return int[]
1188 */
1189 protected function getSearchableWebmounts($id, $depth, $perms_clause)
1190 {
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'];
1197 $idList = [];
1198
1199 $allowedMounts = !$backendUser->isAdmin() && $id === 0
1200 ? $backendUser->returnWebmounts()
1201 : [$id];
1202
1203 foreach ($allowedMounts as $allowedMount) {
1204 $idList[] = $allowedMount;
1205 if ($depth) {
1206 $tree->getTree($allowedMount, $depth, '');
1207 }
1208 $idList = array_merge($idList, $tree->ids);
1209 }
1210
1211 return $idList;
1212 }
1213
1214 /**
1215 * Redirects to FormEngine if a record is just localized.
1216 *
1217 * @param string $justLocalized String with table, orig uid and language separated by ":
1218 */
1219 public function localizationRedirect($justLocalized)
1220 {
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']
1225 ) {
1226 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1227 $queryBuilder->getRestrictions()
1228 ->removeAll()
1229 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1230 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1231
1232 $localizedRecordUid = $queryBuilder->select('uid')
1233 ->from($table)
1234 ->where(
1235 $queryBuilder->expr()->eq(
1236 $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1237 $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1238 ),
1239 $queryBuilder->expr()->eq(
1240 $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1241 $queryBuilder->createNamedParameter($orig_uid, \PDO::PARAM_INT)
1242 )
1243 )
1244 ->setMaxResults(1)
1245 ->execute()
1246 ->fetchColumn();
1247
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(
1252 'record_edit',
1253 [
1254 'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
1255 'returnUrl' => $url
1256 ]
1257 );
1258 HttpUtility::redirect($editUserAccountUrl);
1259 }
1260 }
1261 }
1262
1263 /**
1264 * Set URL parameters to override or add in the listUrl() method.
1265 *
1266 * @param string[] $urlParameters
1267 */
1268 public function setOverrideUrlParameters(array $urlParameters)
1269 {
1270 $this->overrideUrlParameters = $urlParameters;
1271 }
1272
1273 /**
1274 * Set table display order information
1275 *
1276 * Structure of $orderInformation:
1277 * 'tableName' => [
1278 * 'before' => // comma-separated string list or array of table names
1279 * 'after' => // comma-separated string list or array of table names
1280 * ]
1281 *
1282 * @param array $orderInformation
1283 * @throws \UnexpectedValueException
1284 */
1285 public function setTableDisplayOrder(array $orderInformation)
1286 {
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);
1293 }
1294 }
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);
1300 }
1301 }
1302 }
1303 $this->tableDisplayOrder = $orderInformation;
1304 }
1305
1306 /**
1307 * @return array
1308 */
1309 public function getOverridePageIdList(): array
1310 {
1311 return $this->overridePageIdList;
1312 }
1313
1314 /**
1315 * @param int[]|array $overridePageIdList
1316 */
1317 public function setOverridePageIdList(array $overridePageIdList)
1318 {
1319 $this->overridePageIdList = array_map('intval', $overridePageIdList);
1320 }
1321
1322 /**
1323 * Build SQL fragment to limit a query to a list of page IDs based on
1324 * the current search level setting.
1325 *
1326 * @param string $tableName
1327 * @return string
1328 */
1329 protected function getPageIdConstraint(string $tableName): string
1330 {
1331 // Set search levels:
1332 $searchLevels = $this->searchLevels;
1333
1334 // Default is to search everywhere
1335 $constraint = '1=1';
1336
1337 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1338 ->getConnectionForTable($tableName)
1339 ->getExpressionBuilder();
1340
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));
1346 }
1347
1348 if (!empty($this->getOverridePageIdList())) {
1349 $constraint = $expressionBuilder->in(
1350 $tableName . '.pid',
1351 $this->getOverridePageIdList()
1352 );
1353 }
1354
1355 return (string)$constraint;
1356 }
1357
1358 /**
1359 * @return BackendUserAuthentication
1360 */
1361 protected function getBackendUserAuthentication()
1362 {
1363 return $GLOBALS['BE_USER'];
1364 }
1365 }