47f786d68776cdc1092b32c611e149362b916c17
[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 = array();
211
212 /**
213 * Loaded with page record with version overlay if any.
214 *
215 * @var string[]
216 */
217 public $pageRecord = array();
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 = array();
239
240 /**
241 * Array of collapsed / uncollapsed tables in multi table view
242 *
243 * @var int[][]
244 */
245 public $tablesCollapsed = array();
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 = array();
288
289 /**
290 * Fields to display for the current table
291 *
292 * @var string[]
293 */
294 public $setFields = array();
295
296 /**
297 * Used for tracking next/prev uids
298 *
299 * @var int[][]
300 */
301 public $currentTable = array();
302
303 /**
304 * Used for tracking duplicate values of fields
305 *
306 * @var string[]
307 */
308 public $duplicateStack = array();
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 = array();
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 * @return void
349 */
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);
423 $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
424 if ($returnUrl !== '') {
425 HttpUtility::redirect($returnUrl);
426 }
427 }
428
429 // Initialize languages:
430 if ($this->localizationView) {
431 $this->initializeLanguages();
432 }
433 }
434
435 /**
436 * Traverses the table(s) to be listed and renders the output code for each:
437 * The HTML is accumulated in $this->HTMLcode
438 * Finishes off with a stopper-gif
439 *
440 * @return void
441 */
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 = array();
532 }
533 } else {
534 $fields = array();
535 }
536
537 // Finally, render the list:
538 $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
539 }
540 }
541
542 /**
543 * To be implemented in extending classes.
544 *
545 * @param string $tableName
546 * @param int $id
547 * @param string $fields List of fields to show in the listing. Pseudo fields will be added including the record header.
548 * @return string HTML code
549 */
550 public function getTable($tableName, $id, $fields = '')
551 {
552 return '';
553 }
554
555 /**
556 * Creates the search box
557 *
558 * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
559 * @return string HTML for the search box
560 */
561 public function getSearchBox($formFields = true)
562 {
563 /** @var $iconFactory IconFactory */
564 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
565 $lang = $this->getLanguageService();
566 // Setting form-elements, if applicable:
567 $formElements = array('', '');
568 if ($formFields) {
569 $formElements = array('<form action="' . htmlspecialchars($this->listURL('', '-1', 'firstElementNumber,search_field')) . '" method="post">', '</form>');
570 }
571 // Make level selector:
572 $opt = array();
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 = array();
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/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/locallang_core.xlf:labels.enterSearchString')) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/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/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/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/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/locallang_core.xlf:labels.title.search')) . '">
612 ' . $iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:lang/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 ******************************/
628 /**
629 * Setting the field names to display in extended list.
630 * Sets the internal variable $this->setFields
631 *
632 * @return void
633 */
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
650 /**
651 * Create thumbnail code for record/field
652 *
653 * @param mixed[] $row Record array
654 * @param string $table Table (record is from)
655 * @param string $field Field name for which thumbnail are to be rendered.
656 * @return string HTML for thumbnails, if any.
657 */
658 public function thumbCode($row, $table, $field)
659 {
660 return BackendUtility::thumbCode($row, $table, $field);
661 }
662
663 /**
664 * Returns the SQL-query array to select the records from a table $table with pid = $id
665 *
666 * @param string $table Table name
667 * @param int $id Page id (NOT USED! $this->pidSelect is used instead)
668 * @param string $addWhere Additional part for where clause
669 * @param string $fieldList Field list to select, * for all (for "SELECT [fieldlist] FROM ...")
670 * @return string[] Returns query array
671 *
672 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9. Please use getQueryBuilder()
673 */
674 public function makeQueryArray($table, $id, $addWhere = '', $fieldList = '*')
675 {
676 GeneralUtility::logDeprecatedFunction();
677 $hookObjectsArr = array();
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 + 1) : '';
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 = array(
701 'SELECT' => $fieldList,
702 'FROM' => $table,
703 'WHERE' => $this->getPageIdConstraint($table) . ' ' . $pC . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table) . ' ' . $addWhere . ' ' . $search,
704 'GROUPBY' => '',
705 'ORDERBY' => $this->getDatabaseConnection()->stripOrderBy($orderBy),
706 'LIMIT' => $limit
707 );
708 // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
709 if ((in_array($table, GeneralUtility::trimExplode(',', $this->hideTranslations)) || $this->hideTranslations === '*') && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) && $table !== 'pages_language_overlay') {
710 $queryParts['WHERE'] .= ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=0 ';
711 }
712 // Apply hook as requested in http://forge.typo3.org/issues/16634
713 foreach ($hookObjectsArr as $hookObj) {
714 if (method_exists($hookObj, 'makeQueryArray_post')) {
715 $_params = array(
716 'orderBy' => $orderBy,
717 'limit' => $limit,
718 'pC' => $pC,
719 'search' => $search
720 );
721 $hookObj->makeQueryArray_post($queryParts, $this, $table, $id, $addWhere, $fieldList, $_params);
722 }
723 }
724 // Return query:
725 return $queryParts;
726 }
727
728 /**
729 * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
730 * depending on the current searchlevel setting.
731 *
732 * @param string $table Table name
733 * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
734 * @param string[] $additionalConstraints Additional part for where clause
735 * @param string[] $fields Field list to select, * for all
736 * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
737 */
738 public function getQueryBuilder(
739 string $table,
740 int $pageId,
741 array $additionalConstraints = [],
742 array $fields = ['*']
743 ): QueryBuilder {
744 $queryParameters = $this->buildQueryParameters($table, $pageId, $fields, $additionalConstraints);
745
746 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
747 ->getQueryBuilderForTable($queryParameters['table']);
748 $queryBuilder->getRestrictions()
749 ->removeAll()
750 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
751 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
752 $queryBuilder
753 ->select(...$queryParameters['fields'])
754 ->from($queryParameters['table'])
755 ->where(...$queryParameters['where']);
756
757 if (!empty($queryParameters['orderBy'])) {
758 foreach ($queryParameters['orderBy'] as $fieldNameAndSorting) {
759 list($fieldName, $sorting) = $fieldNameAndSorting;
760 $queryBuilder->addOrderBy($fieldName, $sorting);
761 }
762 }
763
764 if (!empty($queryParameters['firstResult'])) {
765 $queryBuilder->setFirstResult((int)$queryParameters['firstResult']);
766 }
767
768 if (!empty($queryParameters['maxResults'])) {
769 $queryBuilder->setMaxResults((int)$queryParameters['maxResults']);
770 }
771
772 if (!empty($queryParameters['groupBy'])) {
773 $queryBuilder->groupBy($queryParameters['groupBy']);
774 }
775
776 return $queryBuilder;
777 }
778
779 /**
780 * Return the query parameters to select the records from a table $table with pid = $this->pidList
781 *
782 * @param string $table Table name
783 * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
784 * @param string[] $fieldList List of fields to select from the table
785 * @param string[] $additionalConstraints Additional part for where clause
786 * @return array
787 */
788 protected function buildQueryParameters(
789 string $table,
790 int $pageId,
791 array $fieldList = ['*'],
792 array $additionalConstraints = []
793 ): array {
794 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
795 ->getQueryBuilderForTable($table)
796 ->expr();
797
798 $parameters = [
799 'table' => $table,
800 'fields' => $fieldList,
801 'groupBy' => null,
802 'orderBy' => null,
803 'firstResult' => $this->firstElementNumber ?: null,
804 'maxResults' => $this->iLimit ? ($this->iLimit + 1) : null,
805 ];
806
807 if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
808 $parameters['orderBy'][] = $this->sortRev ? [$this->sortField, 'DESC'] : [$this->sortField, 'ASC'];
809 } else {
810 $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
811 $parameters['orderBy'] = QueryHelper::parseOrderBy((string)$orderBy);
812 }
813
814 // Build the query constraints
815 $constraints = [
816 'pidSelect' => $this->getPageIdConstraint($table),
817 'search' => $this->makeSearchString($table, $pageId)
818 ];
819
820 // Filtering on displayable pages (permissions):
821 if ($table === 'pages' && $this->perms_clause) {
822 $constraints['pagePermsClause'] = $this->perms_clause;
823 }
824
825 // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
826 if ((GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
827 && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
828 && $table !== 'pages_language_overlay'
829 ) {
830 $constraints['transOrigPointerField'] = $expressionBuilder->eq(
831 $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
832 0
833 );
834 }
835
836 $parameters['where'] = array_merge($constraints, $additionalConstraints);
837
838 $hookName = DatabaseRecordList::class;
839 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
840 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $classRef) {
841 $hookObject = GeneralUtility::getUserObj($classRef);
842 if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
843 $hookObject->buildQueryParametersPostProcess(
844 $parameters,
845 $table,
846 $pageId,
847 $additionalConstraints,
848 $fieldList,
849 $this
850 );
851 }
852 }
853 }
854
855 // array_unique / array_filter used to eliminate empty and duplicate constraints
856 // the array keys are eliminated by this as well to facilitate argument unpacking
857 // when used with the querybuilder.
858 $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
859
860 return $parameters;
861 }
862
863 /**
864 * Set the total items for the record list
865 *
866 * @param string $table Table name
867 * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
868 * @param array $constraints Additional constraints for where clause
869 */
870 public function setTotalItems(string $table, int $pageId, array $constraints)
871 {
872 $queryParameters = $this->buildQueryParameters($table, $pageId, ['*'], $constraints);
873 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
874 ->getQueryBuilderForTable($queryParameters['table']);
875 $queryBuilder->getRestrictions()
876 ->removeAll()
877 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
878 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
879 $queryBuilder
880 ->from($queryParameters['table'])
881 ->where(...$queryParameters['where']);
882
883 $this->totalItems = (int)$queryBuilder->count('*')
884 ->execute()
885 ->fetchColumn();
886 }
887
888 /**
889 * Creates part of query for searching after a word ($this->searchString)
890 * fields in input table.
891 *
892 * @param string $table Table, in which the fields are being searched.
893 * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
894 * @return string Returns part of WHERE-clause for searching, if applicable.
895 */
896 public function makeSearchString($table, $currentPid = -1)
897 {
898 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
899 $expressionBuilder = $queryBuilder->expr();
900 $constraints = [];
901 $currentPid = (int)$currentPid;
902 $tablePidField = $table === 'pages' ? 'uid' : 'pid';
903 // Make query, only if table is valid and a search string is actually defined:
904 if (empty($this->searchString)) {
905 return '1=1';
906 }
907
908 $searchableFields = $this->getSearchFields($table);
909 if (empty($searchableFields)) {
910 return '1=1';
911 }
912 if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
913 $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
914 foreach ($searchableFields as $fieldName) {
915 if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
916 continue;
917 }
918 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
919 $fieldType = $fieldConfig['type'];
920 $evalRules = $fieldConfig['eval'] ?: '';
921 if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
922 if (is_array($fieldConfig['search'])
923 && in_array('pidonly', $fieldConfig['search'], true)
924 && $currentPid > 0
925 ) {
926 $constraints[] = $expressionBuilder->andX(
927 $expressionBuilder->eq($fieldName, (int)$this->searchString),
928 $expressionBuilder->eq($tablePidField, (int)$currentPid)
929 );
930 }
931 } elseif ($fieldType === 'text'
932 || $fieldType === 'flex'
933 || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
934 ) {
935 $constraints[] = $expressionBuilder->like(
936 $fieldName,
937 $queryBuilder->quote('%' . (int)$this->searchString . '%')
938 );
939 }
940 }
941 } else {
942 $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
943 foreach ($searchableFields as $fieldName) {
944 if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
945 continue;
946 }
947 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
948 $fieldType = $fieldConfig['type'];
949 $evalRules = $fieldConfig['eval'] ?: '';
950 $searchConstraint = $expressionBuilder->andX(
951 $expressionBuilder->comparison(
952 'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
953 'LIKE',
954 'LOWER(' . $like . ')'
955 )
956 );
957 if (is_array($fieldConfig['search'])) {
958 $searchConfig = $fieldConfig['search'];
959 if (in_array('case', $searchConfig)) {
960 // Replace case insensitive default constraint
961 $searchConstraint = $expressionBuilder->andX($expressionBuilder->like($fieldName, $like));
962 }
963 if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
964 $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
965 }
966 if ($searchConfig['andWhere']) {
967 $searchConstraint->add(
968 QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
969 );
970 }
971 }
972 if ($fieldType === 'text'
973 || $fieldType === 'flex'
974 || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
975 ) {
976 if ($searchConstraint->count() !== 0) {
977 $constraints[] = $searchConstraint;
978 }
979 }
980 }
981 }
982 // If no search field conditions have been build ensure no results are returned
983 if (empty($constraints)) {
984 return '0=1';
985 }
986
987 return $expressionBuilder->orX(...$constraints);
988 }
989
990 /**
991 * Fetches a list of fields to use in the Backend search for the given table.
992 *
993 * @param string $tableName
994 * @return string[]
995 */
996 protected function getSearchFields($tableName)
997 {
998 $fieldArray = array();
999 $fieldListWasSet = false;
1000 // Get fields from ctrl section of TCA first
1001 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
1002 $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
1003 $fieldListWasSet = true;
1004 }
1005 // Call hook to add or change the list
1006 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
1007 $hookParameters = array(
1008 'tableHasSearchConfiguration' => $fieldListWasSet,
1009 'tableName' => $tableName,
1010 'searchFields' => &$fieldArray,
1011 'searchString' => $this->searchString
1012 );
1013 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
1014 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
1015 }
1016 }
1017 return $fieldArray;
1018 }
1019
1020 /**
1021 * Returns the title (based on $code) of a table ($table) with the proper link around. For headers over tables.
1022 * The link will cause the display of all extended mode or not for the table.
1023 *
1024 * @param string $table Table name
1025 * @param string $code Table label
1026 * @return string The linked table label
1027 */
1028 public function linkWrapTable($table, $code)
1029 {
1030 if ($this->table !== $table) {
1031 return '<a href="' . htmlspecialchars($this->listURL('', $table, 'firstElementNumber')) . '">' . $code . '</a>';
1032 }
1033 return '<a href="' . htmlspecialchars($this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')) . '">' . $code . '</a>';
1034 }
1035
1036 /**
1037 * 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...)
1038 *
1039 * @param string $table Table name
1040 * @param int $uid Item uid
1041 * @param string $code Item title (not htmlspecialchars()'ed yet)
1042 * @param mixed[] $row Item row
1043 * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
1044 */
1045 public function linkWrapItems($table, $uid, $code, $row)
1046 {
1047 $lang = $this->getLanguageService();
1048 $origCode = $code;
1049 // If the title is blank, make a "no title" label:
1050 if ((string)$code === '') {
1051 $code = '<i>[' . htmlspecialchars($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title')) . ']</i> - ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(
1052 BackendUtility::getRecordTitle($table, $row),
1053 $this->getBackendUserAuthentication()->uc['titleLen']
1054 ));
1055 } else {
1056 $code = htmlspecialchars(GeneralUtility::fixed_lgd_cs($code, $this->fixedL), ENT_QUOTES, 'UTF-8', false);
1057 if ($code != htmlspecialchars($origCode)) {
1058 $code = '<span title="' . htmlspecialchars($origCode, ENT_QUOTES, 'UTF-8', false) . '">' . $code . '</span>';
1059 }
1060 }
1061 switch ((string)$this->clickTitleMode) {
1062 case 'edit':
1063 // If the listed table is 'pages' we have to request the permission settings for each page:
1064 if ($table == 'pages') {
1065 $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1066 $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
1067 } else {
1068 $permsEdit = $this->calcPerms & Permission::CONTENT_EDIT;
1069 }
1070 // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1071 if ($permsEdit) {
1072 $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1073 $code = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, '', -1)) . '" title="' . htmlspecialchars($lang->getLL('edit')) . '">' . $code . '</a>';
1074 }
1075 break;
1076 case 'show':
1077 // "Show" link (only pages and tt_content elements)
1078 if ($table == 'pages' || $table == 'tt_content') {
1079 $code = '<a href="#" onclick="' . htmlspecialchars(
1080 BackendUtility::viewOnClick(($table == 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid']))
1081 ) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage')) . '">' . $code . '</a>';
1082 }
1083 break;
1084 case 'info':
1085 // "Info": (All records)
1086 $code = '<a href="#" onclick="' . htmlspecialchars(('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')) . '" title="' . htmlspecialchars($lang->getLL('showInfo')) . '">' . $code . '</a>';
1087 break;
1088 default:
1089 // Output the label now:
1090 if ($table == 'pages') {
1091 $code = '<a href="' . htmlspecialchars($this->listURL($uid, '', 'firstElementNumber')) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
1092 } else {
1093 $code = $this->linkUrlMail($code, $origCode);
1094 }
1095 }
1096 return $code;
1097 }
1098
1099 /**
1100 * Wrapping input code in link to URL or email if $testString is either.
1101 *
1102 * @param string $code code to wrap
1103 * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
1104 * @return string Link-Wrapped $code value, if $testString was URL or email.
1105 */
1106 public function linkUrlMail($code, $testString)
1107 {
1108 // Check for URL:
1109 $scheme = parse_url($testString, PHP_URL_SCHEME);
1110 if ($scheme === 'http' || $scheme === 'https' || $scheme === 'ftp') {
1111 return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1112 }
1113 // Check for email:
1114 if (GeneralUtility::validEmail($testString)) {
1115 return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
1116 }
1117 // Return if nothing else...
1118 return $code;
1119 }
1120
1121 /**
1122 * Creates the URL to this script, including all relevant GPvars
1123 * Fixed GPvars are id, table, imagemode, returnUrl, search_field, search_levels and showLimit
1124 * The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
1125 *
1126 * @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
1127 * @param string $table Table name to display. Enter "-1" for the current table.
1128 * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
1129 * @return string URL
1130 */
1131 public function listURL($altId = '', $table = '-1', $exclList = '')
1132 {
1133 $urlParameters = array();
1134 if ((string)$altId !== '') {
1135 $urlParameters['id'] = $altId;
1136 } else {
1137 $urlParameters['id'] = $this->id;
1138 }
1139 if ($table === '-1') {
1140 $urlParameters['table'] = $this->table;
1141 } else {
1142 $urlParameters['table'] = $table;
1143 }
1144 if ($this->thumbs) {
1145 $urlParameters['imagemode'] = $this->thumbs;
1146 }
1147 if ($this->returnUrl) {
1148 $urlParameters['returnUrl'] = $this->returnUrl;
1149 }
1150 if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
1151 $urlParameters['search_field'] = $this->searchString;
1152 }
1153 if ($this->searchLevels) {
1154 $urlParameters['search_levels'] = $this->searchLevels;
1155 }
1156 if ($this->showLimit) {
1157 $urlParameters['showLimit'] = $this->showLimit;
1158 }
1159 if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
1160 $urlParameters['pointer'] = $this->firstElementNumber;
1161 }
1162 if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
1163 $urlParameters['sortField'] = $this->sortField;
1164 }
1165 if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
1166 $urlParameters['sortRev'] = $this->sortRev;
1167 }
1168
1169 $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
1170
1171 if ($routePath = GeneralUtility::_GP('route')) {
1172 $router = GeneralUtility::makeInstance(Router::class);
1173 $route = $router->match($routePath);
1174 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1175 $url = (string)$uriBuilder->buildUriFromRoute($route->getOption('_identifier'), $urlParameters);
1176 } elseif ($moduleName = GeneralUtility::_GP('M')) {
1177 $url = BackendUtility::getModuleUrl($moduleName, $urlParameters);
1178 } else {
1179 $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&');
1180 }
1181 return $url;
1182 }
1183
1184 /**
1185 * Returns "requestUri" - which is basically listURL
1186 *
1187 * @return string Content of ->listURL()
1188 */
1189 public function requestUri()
1190 {
1191 return $this->listURL();
1192 }
1193
1194 /**
1195 * Makes the list of fields to select for a table
1196 *
1197 * @param string $table Table name
1198 * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
1199 * @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)
1200 * @return string[] Array, where values are fieldnames to include in query
1201 */
1202 public function makeFieldList($table, $dontCheckUser = false, $addDateFields = false)
1203 {
1204 $backendUser = $this->getBackendUserAuthentication();
1205 // Init fieldlist array:
1206 $fieldListArr = array();
1207 // Check table:
1208 if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1209 if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
1210 // Traverse configured columns and add them to field array, if available for user.
1211 foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
1212 if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check('non_exclude_fields', $table . ':' . $fN)) && $fieldValue['config']['type'] != 'passthrough') {
1213 $fieldListArr[] = $fN;
1214 }
1215 }
1216
1217 $fieldListArr[] = 'uid';
1218 $fieldListArr[] = 'pid';
1219
1220 // Add date fields
1221 if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
1222 if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
1223 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
1224 }
1225 if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
1226 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
1227 }
1228 }
1229 // Add more special fields:
1230 if ($dontCheckUser || $backendUser->isAdmin()) {
1231 if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
1232 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
1233 }
1234 if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1235 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
1236 }
1237 if (ExtensionManagementUtility::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1238 $fieldListArr[] = 't3ver_id';
1239 $fieldListArr[] = 't3ver_state';
1240 $fieldListArr[] = 't3ver_wsid';
1241 }
1242 }
1243 } else {
1244 GeneralUtility::sysLog(sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1245 }
1246 }
1247 return $fieldListArr;
1248 }
1249
1250 /**
1251 * Get all allowed mount pages to be searched in.
1252 *
1253 * @param int $id Page id
1254 * @param int $depth Depth to go down
1255 * @param string $perms_clause select clause
1256 * @return int[]
1257 */
1258 protected function getSearchableWebmounts($id, $depth, $perms_clause)
1259 {
1260 $backendUser = $this->getBackendUserAuthentication();
1261 /** @var PageTreeView $tree */
1262 $tree = GeneralUtility::makeInstance(PageTreeView::class);
1263 $tree->init('AND ' . $perms_clause);
1264 $tree->makeHTML = 0;
1265 $tree->fieldArray = array('uid', 'php_tree_stop');
1266 $idList = array();
1267
1268 $allowedMounts = !$backendUser->isAdmin() && $id === 0
1269 ? $backendUser->returnWebmounts()
1270 : array($id);
1271
1272 foreach ($allowedMounts as $allowedMount) {
1273 $idList[] = $allowedMount;
1274 if ($depth) {
1275 $tree->getTree($allowedMount, $depth, '');
1276 }
1277 $idList = array_merge($idList, $tree->ids);
1278 }
1279
1280 return $idList;
1281 }
1282
1283 /**
1284 * Redirects to FormEngine if a record is just localized.
1285 *
1286 * @param string $justLocalized String with table, orig uid and language separated by ":
1287 * @return void
1288 */
1289 public function localizationRedirect($justLocalized)
1290 {
1291 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1292 if ($GLOBALS['TCA'][$table]
1293 && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1294 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1295 ) {
1296 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1297 $queryBuilder->getRestrictions()
1298 ->removeAll()
1299 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1300 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1301
1302 $localizedRecordUid = $queryBuilder->select('uid')
1303 ->from($table)
1304 ->where(
1305 $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['languageField'], (int)$language),
1306 $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], (int)$orig_uid)
1307 )
1308 ->setMaxResults(1)
1309 ->execute()
1310 ->fetchColumn();
1311
1312 if ($localizedRecordUid !== false) {
1313 // Create parameters and finally run the classic page module for creating a new page translation
1314 $url = $this->listURL();
1315 $editUserAccountUrl = BackendUtility::getModuleUrl(
1316 'record_edit',
1317 array(
1318 'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
1319 'returnUrl' => $url
1320 )
1321 );
1322 HttpUtility::redirect($editUserAccountUrl);
1323 }
1324 }
1325 }
1326
1327 /**
1328 * Set URL parameters to override or add in the listUrl() method.
1329 *
1330 * @param string[] $urlParameters
1331 * @return void
1332 */
1333 public function setOverrideUrlParameters(array $urlParameters)
1334 {
1335 $this->overrideUrlParameters = $urlParameters;
1336 }
1337
1338 /**
1339 * Set table display order information
1340 *
1341 * Structure of $orderInformation:
1342 * 'tableName' => [
1343 * 'before' => // comma-separated string list or array of table names
1344 * 'after' => // comma-separated string list or array of table names
1345 * ]
1346 *
1347 * @param array $orderInformation
1348 * @throws \UnexpectedValueException
1349 */
1350 public function setTableDisplayOrder(array $orderInformation)
1351 {
1352 foreach ($orderInformation as $tableName => &$configuration) {
1353 if (isset($configuration['before'])) {
1354 if (is_string($configuration['before'])) {
1355 $configuration['before'] = GeneralUtility::trimExplode(',', $configuration['before'], true);
1356 } elseif (!is_array($configuration['before'])) {
1357 throw new \UnexpectedValueException('The specified "before" order configuration for table "' . $tableName . '" is invalid.', 1436195933);
1358 }
1359 }
1360 if (isset($configuration['after'])) {
1361 if (is_string($configuration['after'])) {
1362 $configuration['after'] = GeneralUtility::trimExplode(',', $configuration['after'], true);
1363 } elseif (!is_array($configuration['after'])) {
1364 throw new \UnexpectedValueException('The specified "after" order configuration for table "' . $tableName . '" is invalid.', 1436195934);
1365 }
1366 }
1367 }
1368 $this->tableDisplayOrder = $orderInformation;
1369 }
1370
1371 /**
1372 * @return array
1373 */
1374 public function getOverridePageIdList(): array
1375 {
1376 return $this->overridePageIdList;
1377 }
1378
1379 /**
1380 * @param int[]|array $overridePageIdList
1381 */
1382 public function setOverridePageIdList(array $overridePageIdList)
1383 {
1384 $this->overridePageIdList = array_map('intval', $overridePageIdList);
1385 }
1386
1387 /**
1388 * Build SQL fragment to limit a query to a list of page IDs based on
1389 * the current search level setting.
1390 *
1391 * @param string $tableName
1392 * @return string
1393 */
1394 protected function getPageIdConstraint(string $tableName): string
1395 {
1396 // Set search levels:
1397 $searchLevels = $this->searchLevels;
1398
1399 // Default is to search everywhere
1400 $constraint = '1=1';
1401
1402 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1403 ->getConnectionForTable($tableName)
1404 ->getExpressionBuilder();
1405
1406 if ($searchLevels === 0) {
1407 $constraint = $expressionBuilder->eq($tableName . '.pid', (int)$this->id);
1408 } elseif ($searchLevels > 0) {
1409 $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
1410 $constraint = $expressionBuilder->in($tableName . '.pid', array_map('intval', $allowedMounts));
1411 }
1412
1413 if (!empty($this->getOverridePageIdList())) {
1414 $constraint = $expressionBuilder->in(
1415 $tableName . '.pid',
1416 $this->getOverridePageIdList()
1417 );
1418 }
1419
1420 return (string)$constraint;
1421 }
1422
1423 /**
1424 * @return BackendUserAuthentication
1425 */
1426 protected function getBackendUserAuthentication()
1427 {
1428 return $GLOBALS['BE_USER'];
1429 }
1430 }