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