[CLEANUP] Make use of possible imports and remove unused ones
[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\Tree\View\PageTreeView;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Core\Database\DatabaseConnection;
22 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\HttpUtility;
25 use TYPO3\CMS\Core\Utility\MathUtility;
26
27 /**
28 * Child class for rendering of Web > List (not the final class.
29 * Shared between Web>List (db_list.php) and Web>Page (sysext/cms/layout/db_layout.php)
30 *
31 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
32 * @see \TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList
33 */
34 class AbstractDatabaseRecordList extends AbstractRecordList {
35
36 /**
37 * Specify a list of tables which are the only ones allowed to be displayed.
38 *
39 * @var string
40 */
41 public $tableList = '';
42
43 /**
44 * Return URL
45 *
46 * @var string
47 */
48 public $returnUrl = '';
49
50 /**
51 * Thumbnails on records containing files (pictures)
52 *
53 * @var bool
54 */
55 public $thumbs = 0;
56
57 /**
58 * default Max items shown per table in "multi-table mode", may be overridden by tables.php
59 *
60 * @var int
61 */
62 public $itemsLimitPerTable = 20;
63
64 /**
65 * default Max items shown per table in "single-table mode", may be overridden by tables.php
66 *
67 * @var int
68 */
69 public $itemsLimitSingleTable = 100;
70
71 /**
72 * Current script name
73 *
74 * @var string
75 */
76 public $script = 'index.php';
77
78 /**
79 * Indicates if all available fields for a user should be selected or not.
80 *
81 * @var int
82 */
83 public $allFields = 0;
84
85 /**
86 * Whether to show localization view or not
87 *
88 * @var bool
89 */
90 public $localizationView = FALSE;
91
92 /**
93 * If set, csvList is outputted.
94 *
95 * @var bool
96 */
97 public $csvOutput = FALSE;
98
99 /**
100 * Field, to sort list by
101 *
102 * @var string
103 */
104 public $sortField;
105
106 /**
107 * Field, indicating to sort in reverse order.
108 *
109 * @var bool
110 */
111 public $sortRev;
112
113 /**
114 * Containing which fields to display in extended mode
115 *
116 * @var string[]
117 */
118 public $displayFields;
119
120 /**
121 * String, can contain the field name from a table which must have duplicate values marked.
122 *
123 * @var string
124 */
125 public $duplicateField;
126
127 /**
128 * Page id
129 *
130 * @var int
131 */
132 public $id;
133
134 /**
135 * Tablename if single-table mode
136 *
137 * @var string
138 */
139 public $table = '';
140
141 /**
142 * If TRUE, records are listed only if a specific table is selected.
143 *
144 * @var bool
145 */
146 public $listOnlyInSingleTableMode = FALSE;
147
148 /**
149 * Pointer for browsing list
150 *
151 * @var int
152 */
153 public $firstElementNumber = 0;
154
155 /**
156 * Search string
157 *
158 * @var string
159 */
160 public $searchString = '';
161
162 /**
163 * Levels to search down.
164 *
165 * @var int
166 */
167 public $searchLevels = '';
168
169 /**
170 * Number of records to show
171 *
172 * @var int
173 */
174 public $showLimit = 0;
175
176 /**
177 * Query part for either a list of ids "pid IN (1,2,3)" or a single id "pid = 123" from
178 * which to select/search etc. (when search-levels are set high). See start()
179 *
180 * @var string
181 */
182 public $pidSelect = '';
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 * Initializes the list generation
323 *
324 * @param int $id Page id for which the list is rendered. Must be >= 0
325 * @param string $table Tablename - if extended mode where only one table is listed at a time.
326 * @param int $pointer Browsing pointer.
327 * @param string $search Search word, if any
328 * @param int $levels Number of levels to search down the page tree
329 * @param int $showLimit Limit of records to be listed.
330 * @return void
331 */
332 public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0) {
333 $backendUser = $this->getBackendUserAuthentication();
334 $db = $this->getDatabaseConnection();
335 // Setting internal variables:
336 // sets the parent id
337 $this->id = (int)$id;
338 if ($GLOBALS['TCA'][$table]) {
339 // Setting single table mode, if table exists:
340 $this->table = $table;
341 }
342 $this->firstElementNumber = $pointer;
343 $this->searchString = trim($search);
344 $this->searchLevels = (int)$levels;
345 $this->showLimit = MathUtility::forceIntegerInRange($showLimit, 0, 10000);
346 // Setting GPvars:
347 $this->csvOutput = GeneralUtility::_GP('csv') ? TRUE : FALSE;
348 $this->sortField = GeneralUtility::_GP('sortField');
349 $this->sortRev = GeneralUtility::_GP('sortRev');
350 $this->displayFields = GeneralUtility::_GP('displayFields');
351 $this->duplicateField = GeneralUtility::_GP('duplicateField');
352 if (GeneralUtility::_GP('justLocalized')) {
353 $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
354 }
355 // Init dynamic vars:
356 $this->counter = 0;
357 $this->JScode = '';
358 $this->HTMLcode = '';
359 // Limits
360 if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
361 $this->itemsLimitPerTable = MathUtility::forceIntegerInRange((int)$this->modTSconfig['properties']['itemsLimitPerTable'], 1, 10000);
362 }
363 if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
364 $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange((int)$this->modTSconfig['properties']['itemsLimitSingleTable'], 1, 10000);
365 }
366 // Set search levels:
367 $searchLevels = $this->searchLevels;
368 $this->perms_clause = $backendUser->getPagePermsClause(1);
369 // This will hide records from display - it has nothing to do with user rights!!
370 if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
371 if ($pidList = $db->cleanIntList($pidList)) {
372 $this->perms_clause .= ' AND pages.uid NOT IN (' . $pidList . ')';
373 }
374 }
375 // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
376 $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list']) ? $backendUser->uc['moduleData']['list'] : array();
377 $collapseOverride = GeneralUtility::_GP('collapse');
378 if (is_array($collapseOverride)) {
379 foreach ($collapseOverride as $collapseTable => $collapseValue) {
380 if (is_array($GLOBALS['TCA'][$collapseTable]) && ($collapseValue == 0 || $collapseValue == 1)) {
381 $this->tablesCollapsed[$collapseTable] = $collapseValue;
382 }
383 }
384 // Save modified user uc
385 $backendUser->uc['moduleData']['list'] = $this->tablesCollapsed;
386 $backendUser->writeUC($backendUser->uc);
387 $returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
388 if ($returnUrl !== '') {
389 HttpUtility::redirect($returnUrl);
390 }
391 }
392 if ($searchLevels > 0) {
393 $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
394 $pidList = implode(',', $db->cleanIntArray($allowedMounts));
395 $this->pidSelect = 'pid IN (' . $pidList . ')';
396 } elseif ($searchLevels < 0) {
397 // Search everywhere
398 $this->pidSelect = '1=1';
399 } else {
400 $this->pidSelect = 'pid=' . (int)$id;
401 }
402 // Initialize languages:
403 if ($this->localizationView) {
404 $this->initializeLanguages();
405 }
406 }
407
408 /**
409 * Traverses the table(s) to be listed and renders the output code for each:
410 * The HTML is accumulated in $this->HTMLcode
411 * Finishes off with a stopper-gif
412 *
413 * @return void
414 */
415 public function generateList() {
416 // Set page record in header
417 $this->pageRecord = BackendUtility::getRecordWSOL('pages', $this->id);
418 $hideTablesArray = GeneralUtility::trimExplode(',', $this->hideTables);
419 // Traverse the TCA table array:
420 foreach ($GLOBALS['TCA'] as $tableName => $value) {
421 // Checking if the table should be rendered:
422 // Checks that we see only permitted/requested tables:
423 if ((!$this->table || $tableName == $this->table) && (!$this->tableList || GeneralUtility::inList($this->tableList, $tableName)) && $this->getBackendUserAuthentication()->check('tables_select', $tableName)) {
424 // Don't show table if hidden by TCA ctrl section
425 $hideTable = $GLOBALS['TCA'][$tableName]['ctrl']['hideTable'] ? TRUE : FALSE;
426 // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
427 if (in_array($tableName, $hideTablesArray)) {
428 $hideTable = TRUE;
429 }
430 // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
431 if (isset($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'])) {
432 $hideTable = $this->tableTSconfigOverTCA[$tableName . '.']['hideTable'] ? TRUE : FALSE;
433 }
434 if ($hideTable) {
435 continue;
436 }
437 // check if we are in single- or multi-table mode
438 if ($this->table) {
439 $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'] : $this->itemsLimitSingleTable;
440 } else {
441 // if there are no records in table continue current foreach
442 $firstRow = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
443 'uid',
444 $tableName,
445 $this->pidSelect . BackendUtility::deleteClause($tableName) . BackendUtility::versioningPlaceholderClause($tableName)
446 );
447 if ($firstRow === FALSE) {
448 continue;
449 }
450 $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'] : $this->itemsLimitPerTable;
451 }
452 if ($this->showLimit) {
453 $this->iLimit = $this->showLimit;
454 }
455 // Setting fields to select:
456 if ($this->allFields) {
457 $fields = $this->makeFieldList($tableName);
458 $fields[] = 'tstamp';
459 $fields[] = 'crdate';
460 $fields[] = '_PATH_';
461 $fields[] = '_CONTROL_';
462 if (is_array($this->setFields[$tableName])) {
463 $fields = array_intersect($fields, $this->setFields[$tableName]);
464 } else {
465 $fields = array();
466 }
467 } else {
468 $fields = array();
469 }
470 // Find ID to use (might be different for "versioning_followPages" tables)
471 if ($this->searchLevels === 0) {
472 $this->pidSelect = 'pid=' . (int)$this->id;
473 }
474 // Finally, render the list:
475 $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
476 }
477 }
478 }
479
480 /**
481 * To be implemented in extending classes.
482 *
483 * @param string $tableName
484 * @param int $id
485 * @param string $fields List of fields to show in the listing. Pseudo fields will be added including the record header.
486 * @return string HTML code
487 */
488 public function getTable($tableName, $id, $fields) {
489 return '';
490 }
491
492 /**
493 * Creates the search box
494 *
495 * @param bool $formFields If TRUE, the search box is wrapped in its own form-tags
496 * @return string HTML for the search box
497 */
498 public function getSearchBox($formFields = TRUE) {
499 $lang = $this->getLanguageService();
500 // Setting form-elements, if applicable:
501 $formElements = array('', '');
502 if ($formFields) {
503 $formElements = array('<form action="' . htmlspecialchars($this->listURL('', -1, 'firstElementNumber,search_field')) . '" method="post">', '</form>');
504 }
505 // Make level selector:
506 $opt = array();
507 $parts = explode('|', $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.enterSearchLevels'));
508 foreach ($parts as $kv => $label) {
509 $opt[] = '<option value="' . $kv . '"' . ($kv === $this->searchLevels ? ' selected="selected"' : '') . '>' . htmlspecialchars($label) . '</option>';
510 }
511 $lMenu = '<select class="form-control" name="search_levels" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.title.search_levels', TRUE) . '" id="search_levels">' . implode('', $opt) . '</select>';
512 // Table with the search box:
513 $content = '<div class="db_list-searchbox-form db_list-searchbox-toolbar" id="db_list-searchbox-toolbar" style="display: ' . ($this->searchString == '' ? 'none' : 'block') . ';">
514 ' . $formElements[0] . '
515 <div id="typo3-dblist-search" class="container">
516 <div class="row">
517 <div class="col-xs-3 col-md-3 col-lg-3">
518 <div class="input-group">
519 <label class="pull-left" for="search_field">' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.label.searchString', TRUE) . ': </label>
520 <input class="form-control" type="search" placeholder="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.enterSearchString', TRUE) . '" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.title.searchString', TRUE) . '" name="search_field" id="search_field" value="' . htmlspecialchars($this->searchString) . '" />
521 </div>
522 </div>
523 <div class="col-xs-3 col-md-3 col-lg-3">
524 <div class="input-group">
525 <label class="pull-left" for="search_levels">' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.label.search_levels', TRUE) . ': </label>
526 ' . $lMenu . '
527 </div>
528 </div>
529 <div class="col-xs-3 col-md-3 col-lg-3">
530 <div class="input-group">
531 <label class="pull-left" for="showLimit">' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.label.limit', TRUE) . ': </label>
532 <input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.title.limit', TRUE) . '" name="showLimit" id="showLimit" value="' . htmlspecialchars(($this->showLimit ? $this->showLimit : '')) . '" />
533 </div>
534 </div>
535 <div class="col-xs-3 col-md-3 col-lg-3">
536 <div class="input-group">
537 <input type="submit" class="btn btn-default btn-block" name="search" value="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.search', TRUE) . '" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.title.search', TRUE) . '" />
538 </div>
539 </div>
540 </div>
541 ' . $formElements[1] . '</div></div>';
542 return $content;
543 }
544
545 /******************************
546 *
547 * Various helper functions
548 *
549 ******************************/
550 /**
551 * Setting the field names to display in extended list.
552 * Sets the internal variable $this->setFields
553 *
554 * @return void
555 */
556 public function setDispFields() {
557 $backendUser = $this->getBackendUserAuthentication();
558 // Getting from session:
559 $dispFields = $backendUser->getModuleData('list/displayFields');
560 // If fields has been inputted, then set those as the value and push it to session variable:
561 if (is_array($this->displayFields)) {
562 reset($this->displayFields);
563 $tKey = key($this->displayFields);
564 $dispFields[$tKey] = $this->displayFields[$tKey];
565 $backendUser->pushModuleData('list/displayFields', $dispFields);
566 }
567 // Setting result:
568 $this->setFields = $dispFields;
569 }
570
571 /**
572 * Create thumbnail code for record/field
573 *
574 * @param mixed[] $row Record array
575 * @param string $table Table (record is from)
576 * @param string $field Field name for which thumbnail are to be rendered.
577 * @return string HTML for thumbnails, if any.
578 */
579 public function thumbCode($row, $table, $field) {
580 return BackendUtility::thumbCode($row, $table, $field, $this->backPath);
581 }
582
583 /**
584 * Returns the SQL-query array to select the records from a table $table with pid = $id
585 *
586 * @param string $table Table name
587 * @param int $id Page id (NOT USED! $this->pidSelect is used instead)
588 * @param string $addWhere Additional part for where clause
589 * @param string $fieldList Field list to select, * for all (for "SELECT [fieldlist] FROM ...")
590 * @return string[] Returns query array
591 */
592 public function makeQueryArray($table, $id, $addWhere = '', $fieldList = '*') {
593 $hookObjectsArr = array();
594 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'])) {
595 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'] as $classRef) {
596 $hookObjectsArr[] = GeneralUtility::getUserObj($classRef);
597 }
598 }
599 // Set ORDER BY:
600 $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ? 'ORDER BY ' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
601 if ($this->sortField) {
602 if (in_array($this->sortField, $this->makeFieldList($table, 1))) {
603 $orderBy = 'ORDER BY ' . $this->sortField;
604 if ($this->sortRev) {
605 $orderBy .= ' DESC';
606 }
607 }
608 }
609 // Set LIMIT:
610 $limit = $this->iLimit ? ($this->firstElementNumber ? $this->firstElementNumber . ',' : '') . ($this->iLimit + 1) : '';
611 // Filtering on displayable pages (permissions):
612 $pC = $table == 'pages' && $this->perms_clause ? ' AND ' . $this->perms_clause : '';
613 // Adding search constraints:
614 $search = $this->makeSearchString($table, $id);
615 // Compiling query array:
616 $queryParts = array(
617 'SELECT' => $fieldList,
618 'FROM' => $table,
619 'WHERE' => $this->pidSelect . ' ' . $pC . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table) . ' ' . $addWhere . ' ' . $search,
620 'GROUPBY' => '',
621 'ORDERBY' => $this->getDatabaseConnection()->stripOrderBy($orderBy),
622 'LIMIT' => $limit
623 );
624 // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
625 if ((in_array($table, GeneralUtility::trimExplode(',', $this->hideTranslations)) || $this->hideTranslations === '*') && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) && $table !== 'pages_language_overlay') {
626 $queryParts['WHERE'] .= ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=0 ';
627 }
628 // Apply hook as requested in http://forge.typo3.org/issues/16634
629 foreach ($hookObjectsArr as $hookObj) {
630 if (method_exists($hookObj, 'makeQueryArray_post')) {
631 $_params = array(
632 'orderBy' => $orderBy,
633 'limit' => $limit,
634 'pC' => $pC,
635 'search' => $search
636 );
637 $hookObj->makeQueryArray_post($queryParts, $this, $table, $id, $addWhere, $fieldList, $_params);
638 }
639 }
640 // Return query:
641 return $queryParts;
642 }
643
644 /**
645 * Based on input query array (query for selecting count(*) from a table) it will select the number of records and set the value in $this->totalItems
646 *
647 * @param string[] $queryParts Query array
648 * @return void
649 * @see makeQueryArray()
650 */
651 public function setTotalItems($queryParts) {
652 $this->totalItems = $this->getDatabaseConnection()->exec_SELECTcountRows('*', $queryParts['FROM'], $queryParts['WHERE']);
653 }
654
655 /**
656 * Creates part of query for searching after a word ($this->searchString)
657 * fields in input table.
658 *
659 * @param string $table Table, in which the fields are being searched.
660 * @param int $currentPid Page id for the possible search limit. -1 only if called from an old XCLASS.
661 * @return string Returns part of WHERE-clause for searching, if applicable.
662 */
663 public function makeSearchString($table, $currentPid = -1) {
664 $result = '';
665 $currentPid = (int)$currentPid;
666 $tablePidField = $table === 'pages' ? 'uid' : 'pid';
667 // Make query, only if table is valid and a search string is actually defined:
668 if ($this->searchString) {
669 $result = ' AND 0=1';
670 $searchableFields = $this->getSearchFields($table);
671 if (count($searchableFields) > 0) {
672 if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
673 $whereParts = array(
674 'uid=' . $this->searchString
675 );
676 foreach ($searchableFields as $fieldName) {
677 if (isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
678 $fieldConfig = &$GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
679 $condition = $fieldName . '=' . $this->searchString;
680 if ($fieldConfig['type'] == 'input' && $fieldConfig['eval'] && GeneralUtility::inList($fieldConfig['eval'], 'int')) {
681 if (is_array($fieldConfig['search']) && in_array('pidonly', $fieldConfig['search']) && $currentPid > 0) {
682 $condition = '(' . $condition . ' AND ' . $tablePidField . '=' . $currentPid . ')';
683 }
684 $whereParts[] = $condition;
685 } elseif (
686 $fieldConfig['type'] == 'text' ||
687 $fieldConfig['type'] == 'flex' ||
688 ($fieldConfig['type'] == 'input' && (!$fieldConfig['eval'] || !preg_match('/date|time|int/', $fieldConfig['eval'])))) {
689 $condition = $fieldName . ' LIKE \'%' . $this->searchString . '%\'';
690 $whereParts[] = $condition;
691 }
692 }
693 }
694 } else {
695 $whereParts = array();
696 $db = $this->getDatabaseConnection();
697 $like = '\'%' . $db->quoteStr($db->escapeStrForLike($this->searchString, $table), $table) . '%\'';
698 foreach ($searchableFields as $fieldName) {
699 if (isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
700 $fieldConfig = &$GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
701 $format = 'LCASE(%s) LIKE LCASE(%s)';
702 if (is_array($fieldConfig['search'])) {
703 if (in_array('case', $fieldConfig['search'])) {
704 $format = '%s LIKE %s';
705 }
706 if (in_array('pidonly', $fieldConfig['search']) && $currentPid > 0) {
707 $format = '(' . $format . ' AND ' . $tablePidField . '=' . $currentPid . ')';
708 }
709 if ($fieldConfig['search']['andWhere']) {
710 $format = '((' . $fieldConfig['search']['andWhere'] . ') AND (' . $format . '))';
711 }
712 }
713 if ($fieldConfig['type'] == 'text' || $fieldConfig['type'] == 'flex' || $fieldConfig['type'] == 'input' && (!$fieldConfig['eval'] || !preg_match('/date|time|int/', $fieldConfig['eval']))) {
714 $whereParts[] = sprintf($format, $fieldName, $like);
715 }
716 }
717 }
718 }
719 // If search-fields were defined (and there always are) we create the query:
720 if (count($whereParts)) {
721 $result = ' AND (' . implode(' OR ', $whereParts) . ')';
722 }
723 }
724 }
725 return $result;
726 }
727
728 /**
729 * Fetches a list of fields to use in the Backend search for the given table.
730 *
731 * @param string $tableName
732 * @return string[]
733 */
734 protected function getSearchFields($tableName) {
735 $fieldArray = array();
736 $fieldListWasSet = FALSE;
737 // Get fields from ctrl section of TCA first
738 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
739 $fieldArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], TRUE);
740 $fieldListWasSet = TRUE;
741 }
742 // Call hook to add or change the list
743 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'])) {
744 $hookParameters = array(
745 'tableHasSearchConfiguration' => $fieldListWasSet,
746 'tableName' => $tableName,
747 'searchFields' => &$fieldArray,
748 'searchString' => $this->searchString
749 );
750 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['mod_list']['getSearchFieldList'] as $hookFunction) {
751 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
752 }
753 }
754 return $fieldArray;
755 }
756
757 /**
758 * Returns the title (based on $code) of a table ($table) with the proper link around. For headers over tables.
759 * The link will cause the display of all extended mode or not for the table.
760 *
761 * @param string $table Table name
762 * @param string $code Table label
763 * @return string The linked table label
764 */
765 public function linkWrapTable($table, $code) {
766 if ($this->table !== $table) {
767 return '<a href="' . htmlspecialchars($this->listURL('', $table, 'firstElementNumber')) . '">' . $code . '</a>';
768 }
769 return '<a href="' . htmlspecialchars($this->listURL('', '', 'sortField,sortRev,table,firstElementNumber')) . '">' . $code . '</a>';
770 }
771
772 /**
773 * 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...)
774 *
775 * @param string $table Table name
776 * @param int $uid Item uid
777 * @param string $code Item title (not htmlspecialchars()'ed yet)
778 * @param mixed[] $row Item row
779 * @return string The item title. Ready for HTML output (is htmlspecialchars()'ed)
780 */
781 public function linkWrapItems($table, $uid, $code, $row) {
782 $lang = $this->getLanguageService();
783 $origCode = $code;
784 // If the title is blank, make a "no title" label:
785 if ((string)$code === '') {
786 $code = '<i>[' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title', 1) . ']</i> - ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(
787 BackendUtility::getRecordTitle($table, $row), $this->getBackendUserAuthentication()->uc['titleLen']));
788 } else {
789 $code = htmlspecialchars(GeneralUtility::fixed_lgd_cs($code, $this->fixedL), ENT_QUOTES, 'UTF-8', FALSE);
790 if ($code != htmlspecialchars($origCode)) {
791 $code = '<span title="' . htmlspecialchars($origCode, ENT_QUOTES, 'UTF-8', FALSE) . '">' . $code . '</span>';
792 }
793 }
794 switch ((string)$this->clickTitleMode) {
795 case 'edit':
796 // If the listed table is 'pages' we have to request the permission settings for each page:
797 if ($table == 'pages') {
798 $localCalcPerms = $this->getBackendUserAuthentication()->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
799 $permsEdit = $localCalcPerms & 2;
800 } else {
801 $permsEdit = $this->calcPerms & 16;
802 }
803 // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
804 if ($permsEdit) {
805 $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
806 $code = '<a href="#" onclick="' . htmlspecialchars(
807 BackendUtility::editOnClick($params, $this->backPath, -1)) . '" title="' . $lang->getLL('edit', TRUE) . '">' . $code . '</a>';
808 }
809 break;
810 case 'show':
811 // "Show" link (only pages and tt_content elements)
812 if ($table == 'pages' || $table == 'tt_content') {
813 $code = '<a href="#" onclick="' . htmlspecialchars(
814 BackendUtility::viewOnClick(($table == 'tt_content' ? $this->id . '#' . $row['uid'] : $row['uid']))) . '" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', TRUE) . '">' . $code . '</a>';
815 }
816 break;
817 case 'info':
818 // "Info": (All records)
819 $code = '<a href="#" onclick="' . htmlspecialchars(('top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;')) . '" title="' . $lang->getLL('showInfo', TRUE) . '">' . $code . '</a>';
820 break;
821 default:
822 // Output the label now:
823 if ($table == 'pages') {
824 $code = '<a href="' . htmlspecialchars($this->listURL($uid, '', 'firstElementNumber')) . '" onclick="setHighlight(' . $uid . ')">' . $code . '</a>';
825 } else {
826 $code = $this->linkUrlMail($code, $origCode);
827 }
828 }
829 return $code;
830 }
831
832 /**
833 * Wrapping input code in link to URL or email if $testString is either.
834 *
835 * @param string $code code to wrap
836 * @param string $testString String which is tested for being a URL or email and which will be used for the link if so.
837 * @return string Link-Wrapped $code value, if $testString was URL or email.
838 */
839 public function linkUrlMail($code, $testString) {
840 // Check for URL:
841 $schema = parse_url($testString);
842 if ($schema['scheme'] && GeneralUtility::inList('http,https,ftp', $schema['scheme'])) {
843 return '<a href="' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
844 }
845 // Check for email:
846 if (GeneralUtility::validEmail($testString)) {
847 return '<a href="mailto:' . htmlspecialchars($testString) . '" target="_blank">' . $code . '</a>';
848 }
849 // Return if nothing else...
850 return $code;
851 }
852
853 /**
854 * Creates the URL to this script, including all relevant GPvars
855 * Fixed GPvars are id, table, imagemode, returnUrl, search_field, search_levels and showLimit
856 * The GPvars "sortField" and "sortRev" are also included UNLESS they are found in the $exclList variable.
857 *
858 * @param string $altId Alternative id value. Enter blank string for the current id ($this->id)
859 * @param string $table Table name to display. Enter "-1" for the current table.
860 * @param string $exclList Comma separated list of fields NOT to include ("sortField", "sortRev" or "firstElementNumber")
861 * @return string URL
862 */
863 public function listURL($altId = '', $table = '-1', $exclList = '') {
864 $urlParameters = array();
865 if ((string)$altId !== '') {
866 $urlParameters['id'] = $altId;
867 } else {
868 $urlParameters['id'] = $this->id;
869 }
870 if ($table === '-1') {
871 $urlParameters['table'] = $this->table;
872 } else {
873 $urlParameters['table'] = $table;
874 }
875 if ($this->thumbs) {
876 $urlParameters['imagemode'] = $this->thumbs;
877 }
878 if ($this->returnUrl) {
879 $urlParameters['returnUrl'] = $this->returnUrl;
880 }
881 if ((!$exclList || !GeneralUtility::inList($exclList, 'search_field')) && $this->searchString) {
882 $urlParameters['search_field'] = $this->searchString;
883 }
884 if ($this->searchLevels) {
885 $urlParameters['search_levels'] = $this->searchLevels;
886 }
887 if ($this->showLimit) {
888 $urlParameters['showLimit'] = $this->showLimit;
889 }
890 if ((!$exclList || !GeneralUtility::inList($exclList, 'firstElementNumber')) && $this->firstElementNumber) {
891 $urlParameters['pointer'] = $this->firstElementNumber;
892 }
893 if ((!$exclList || !GeneralUtility::inList($exclList, 'sortField')) && $this->sortField) {
894 $urlParameters['sortField'] = $this->sortField;
895 }
896 if ((!$exclList || !GeneralUtility::inList($exclList, 'sortRev')) && $this->sortRev) {
897 $urlParameters['sortRev'] = $this->sortRev;
898 }
899
900 $urlParameters = array_merge_recursive($urlParameters, $this->overrideUrlParameters);
901
902 return BackendUtility::getModuleUrl(GeneralUtility::_GP('M'), $urlParameters);
903 }
904
905 /**
906 * Returns "requestUri" - which is basically listURL
907 *
908 * @return string Content of ->listURL()
909 */
910 public function requestUri() {
911 return $this->listURL();
912 }
913
914 /**
915 * Makes the list of fields to select for a table
916 *
917 * @param string $table Table name
918 * @param bool $dontCheckUser If set, users access to the field (non-exclude-fields) is NOT checked.
919 * @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)
920 * @return string[] Array, where values are fieldnames to include in query
921 */
922 public function makeFieldList($table, $dontCheckUser = FALSE, $addDateFields = FALSE) {
923 $backendUser = $this->getBackendUserAuthentication();
924 // Init fieldlist array:
925 $fieldListArr = array();
926 // Check table:
927 if (is_array($GLOBALS['TCA'][$table]) && isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
928 if (isset($GLOBALS['TCA'][$table]['columns']) && is_array($GLOBALS['TCA'][$table]['columns'])) {
929 // Traverse configured columns and add them to field array, if available for user.
930 foreach ($GLOBALS['TCA'][$table]['columns'] as $fN => $fieldValue) {
931 if ($dontCheckUser || (!$fieldValue['exclude'] || $backendUser->check('non_exclude_fields', $table . ':' . $fN)) && $fieldValue['config']['type'] != 'passthrough') {
932 $fieldListArr[] = $fN;
933 }
934 }
935
936 $fieldListArr[] = 'uid';
937 $fieldListArr[] = 'pid';
938
939 // Add date fields
940 if ($dontCheckUser || $backendUser->isAdmin() || $addDateFields) {
941 if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
942 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['tstamp'];
943 }
944 if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
945 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['crdate'];
946 }
947 }
948 // Add more special fields:
949 if ($dontCheckUser || $backendUser->isAdmin()) {
950 if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
951 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['cruser_id'];
952 }
953 if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
954 $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
955 }
956 if (ExtensionManagementUtility::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
957 $fieldListArr[] = 't3ver_id';
958 $fieldListArr[] = 't3ver_state';
959 $fieldListArr[] = 't3ver_wsid';
960 }
961 }
962 } else {
963 GeneralUtility::sysLog(sprintf('$TCA is broken for the table "%s": no required "columns" entry in $TCA.', $table), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
964 }
965 }
966 return $fieldListArr;
967 }
968
969 /**
970 * Get all allowed mount pages to be searched in.
971 *
972 * @param int $id Page id
973 * @param int $depth Depth to go down
974 * @param string $perms_clause select clause
975 * @return int[]
976 */
977 protected function getSearchableWebmounts($id, $depth, $perms_clause) {
978 $backendUser = $this->getBackendUserAuthentication();
979 /** @var PageTreeView $tree */
980 $tree = GeneralUtility::makeInstance(PageTreeView::class);
981 $tree->init('AND ' . $perms_clause);
982 $tree->makeHTML = 0;
983 $tree->fieldArray = array('uid', 'php_tree_stop');
984 $idList = array();
985
986 $allowedMounts = !$backendUser->isAdmin() && $id === 0
987 ? $backendUser->returnWebmounts()
988 : array($id);
989
990 foreach ($allowedMounts as $allowedMount) {
991 $idList[] = $allowedMount;
992 if ($depth) {
993 $tree->getTree($allowedMount, $depth, '');
994 }
995 $idList = array_merge($idList, $tree->ids);
996 }
997
998 return $idList;
999 }
1000
1001 /**
1002 * Redirects to TCEforms (alt_doc) if a record is just localized.
1003 *
1004 * @param string $justLocalized String with table, orig uid and language separated by ":
1005 * @return void
1006 */
1007 public function localizationRedirect($justLocalized) {
1008 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1009 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
1010 $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', $table, $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table));
1011 if (is_array($localizedRecord)) {
1012 // Create parameters and finally run the classic page module for creating a new page translation
1013 $url = substr($this->listURL(), strlen($this->backPath));
1014 $params = '&edit[' . $table . '][' . $localizedRecord['uid'] . ']=edit';
1015 $returnUrl = '&returnUrl=' . rawurlencode($url);
1016 $location = $GLOBALS['BACK_PATH'] . 'alt_doc.php?' . $params . $returnUrl;
1017 HttpUtility::redirect($location);
1018 }
1019 }
1020 }
1021
1022 /**
1023 * Set URL parameters to override or add in the listUrl() method.
1024 *
1025 * @param string[] $urlParameters
1026 * @return void
1027 */
1028 public function setOverrideUrlParameters(array $urlParameters) {
1029 $this->overrideUrlParameters = $urlParameters;
1030 }
1031
1032 /**
1033 * @return BackendUserAuthentication
1034 */
1035 protected function getBackendUserAuthentication() {
1036 return $GLOBALS['BE_USER'];
1037 }
1038
1039 /**
1040 * @return DatabaseConnection
1041 */
1042 protected function getDatabaseConnection() {
1043 return $GLOBALS['TYPO3_DB'];
1044 }
1045 }