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