[TASK] Protect user TSconfig properties in BackendUserAuthentication
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / QueryView.php
1 <?php
2 namespace TYPO3\CMS\Core\Database;
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 Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Database\Query\QueryHelper;
21 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
22 use TYPO3\CMS\Core\Imaging\Icon;
23 use TYPO3\CMS\Core\Imaging\IconFactory;
24 use TYPO3\CMS\Core\Localization\LanguageService;
25 use TYPO3\CMS\Core\Messaging\FlashMessage;
26 use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
27 use TYPO3\CMS\Core\Messaging\FlashMessageService;
28 use TYPO3\CMS\Core\Type\Bitmask\Permission;
29 use TYPO3\CMS\Core\Utility\CsvUtility;
30 use TYPO3\CMS\Core\Utility\DebugUtility;
31 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
32 use TYPO3\CMS\Core\Utility\GeneralUtility;
33
34 /**
35 * Class used in module tools/dbint (advanced search) and which may hold code specific for that module
36 * However the class has a general principle in it which may be used in the web/export module.
37 */
38 class QueryView
39 {
40 /**
41 * @var string
42 */
43 public $storeList = 'search_query_smallparts,search_result_labels,labels_noprefix,show_deleted,queryConfig,queryTable,queryFields,queryLimit,queryOrder,queryOrderDesc,queryOrder2,queryOrder2Desc,queryGroup,search_query_makeQuery';
44
45 /**
46 * @var string
47 */
48 public $downloadScript = 'index.php';
49
50 /**
51 * @var int
52 */
53 public $formW = 48;
54
55 /**
56 * @var int
57 */
58 public $noDownloadB = 0;
59
60 /**
61 * @var array
62 */
63 public $hookArray = [];
64
65 /**
66 * @var string
67 */
68 protected $formName = '';
69
70 /**
71 * @var \TYPO3\CMS\Core\Imaging\IconFactory
72 */
73 protected $iconFactory;
74
75 /**
76 * @var array
77 */
78 protected $tableArray = [];
79
80 /**
81 * @var LanguageService
82 */
83 protected $languageService;
84
85 /**
86 * @var BackendUserAuthentication
87 */
88 protected $backendUserAuthentication;
89
90 /**
91 * Settings, usually from the controller (previously known from $GLOBALS['SOBE']->MOD_SETTINGS
92 * @var array
93 */
94 protected $settings = [];
95
96 /**
97 * @var array information on the menu of this module
98 */
99 protected $menuItems = [];
100
101 /**
102 * @var string
103 */
104 protected $moduleName;
105
106 /**
107 * @param array $settings previously stored in $GLOBALS['SOBE']->MOD_SETTINGS
108 * @param array $menuItems previously stored in $GLOBALS['SOBE']->MOD_MENU
109 * @param string $moduleName previously stored in $GLOBALS['SOBE']->moduleName
110 */
111 public function __construct(array $settings = null, $menuItems = null, $moduleName = null)
112 {
113 $this->backendUserAuthentication = $GLOBALS['BE_USER'];
114 $this->languageService = $GLOBALS['LANG'];
115 $this->languageService->includeLLFile('EXT:core/Resources/Private/Language/locallang_t3lib_fullsearch.xlf');
116 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
117 $this->settings = $settings ?: $GLOBALS['SOBE']->MOD_SETTINGS;
118 $this->menuItems = $menuItems ?: $GLOBALS['SOBE']->MOD_MENU;
119 $this->moduleName = $moduleName ?: $GLOBALS['SOBE']->moduleName;
120 }
121
122 /**
123 * Get form
124 *
125 * @return string
126 */
127 public function form()
128 {
129 $markup = [];
130 $markup[] = '<div class="form-group">';
131 $markup[] = '<input placeholder="Search Word" class="form-control" type="search" name="SET[sword]" value="'
132 . htmlspecialchars($this->settings['sword']) . '">';
133 $markup[] = '</div>';
134 $markup[] = '<div class="form-group">';
135 $markup[] = '<input class="btn btn-default" type="submit" name="submit" value="Search All Records">';
136 $markup[] = '</div>';
137 return implode(LF, $markup);
138 }
139
140 /**
141 * Make store control
142 *
143 * @return string
144 */
145 public function makeStoreControl()
146 {
147 // Load/Save
148 $storeArray = $this->initStoreArray();
149
150 $opt = [];
151 foreach ($storeArray as $k => $v) {
152 $opt[] = '<option value="' . htmlspecialchars($k) . '">' . htmlspecialchars($v) . '</option>';
153 }
154 // Actions:
155 if (ExtensionManagementUtility::isLoaded('sys_action') && $this->backendUserAuthentication->isAdmin()) {
156 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_action');
157 $queryBuilder->getRestrictions()->removeAll();
158 $statement = $queryBuilder->select('uid', 'title')
159 ->from('sys_action')
160 ->where($queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(2, \PDO::PARAM_INT)))
161 ->orderBy('title')
162 ->execute();
163 $opt[] = '<option value="0">__Save to Action:__</option>';
164 while ($row = $statement->fetch()) {
165 $opt[] = '<option value="-' . (int)$row['uid'] . '">' . htmlspecialchars($row['title']
166 . ' [' . (int)$row['uid'] . ']') . '</option>';
167 }
168 }
169 $markup = [];
170 $markup[] = '<div class="load-queries">';
171 $markup[] = ' <div class="form-inline">';
172 $markup[] = ' <div class="form-group">';
173 $markup[] = ' <select class="form-control" name="storeControl[STORE]" onChange="document.forms[0]'
174 . '[\'storeControl[title]\'].value= this.options[this.selectedIndex].value!=0 '
175 . '? this.options[this.selectedIndex].text : \'\';">' . implode(LF, $opt) . '</select>';
176 $markup[] = ' <input class="form-control" name="storeControl[title]" value="" type="text" max="80">';
177 $markup[] = ' <input class="btn btn-default" type="submit" name="storeControl[LOAD]" value="Load">';
178 $markup[] = ' <input class="btn btn-default" type="submit" name="storeControl[SAVE]" value="Save">';
179 $markup[] = ' <input class="btn btn-default" type="submit" name="storeControl[REMOVE]" value="Remove">';
180 $markup[] = ' </div>';
181 $markup[] = ' </div>';
182 $markup[] = '</div>';
183
184 return implode(LF, $markup);
185 }
186
187 /**
188 * Init store array
189 *
190 * @return array
191 */
192 public function initStoreArray()
193 {
194 $storeArray = [
195 '0' => '[New]'
196 ];
197 $savedStoreArray = unserialize($this->settings['storeArray']);
198 if (is_array($savedStoreArray)) {
199 $storeArray = array_merge($storeArray, $savedStoreArray);
200 }
201 return $storeArray;
202 }
203
204 /**
205 * Clean store query configs
206 *
207 * @param array $storeQueryConfigs
208 * @param array $storeArray
209 * @return array
210 */
211 public function cleanStoreQueryConfigs($storeQueryConfigs, $storeArray)
212 {
213 if (is_array($storeQueryConfigs)) {
214 foreach ($storeQueryConfigs as $k => $v) {
215 if (!isset($storeArray[$k])) {
216 unset($storeQueryConfigs[$k]);
217 }
218 }
219 }
220 return $storeQueryConfigs;
221 }
222
223 /**
224 * Add to store query configs
225 *
226 * @param array $storeQueryConfigs
227 * @param int $index
228 * @return array
229 */
230 public function addToStoreQueryConfigs($storeQueryConfigs, $index)
231 {
232 $keyArr = explode(',', $this->storeList);
233 $storeQueryConfigs[$index] = [];
234 foreach ($keyArr as $k) {
235 $storeQueryConfigs[$index][$k] = $this->settings[$k];
236 }
237 return $storeQueryConfigs;
238 }
239
240 /**
241 * Save query in action
242 *
243 * @param int $uid
244 * @return int
245 */
246 public function saveQueryInAction($uid)
247 {
248 if (ExtensionManagementUtility::isLoaded('sys_action')) {
249 $keyArr = explode(',', $this->storeList);
250 $saveArr = [];
251 foreach ($keyArr as $k) {
252 $saveArr[$k] = $this->settings[$k];
253 }
254 // Show query
255 if ($saveArr['queryTable']) {
256 /** @var \TYPO3\CMS\Core\Database\QueryGenerator */
257 $queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
258 $queryGenerator->init('queryConfig', $saveArr['queryTable']);
259 $queryGenerator->makeSelectorTable($saveArr);
260 $queryGenerator->enablePrefix = 1;
261 $queryString = $queryGenerator->getQuery($queryGenerator->queryConfig);
262
263 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
264 ->getQueryBuilderForTable($queryGenerator->table);
265 $queryBuilder->getRestrictions()->removeAll()
266 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
267 $rowCount = $queryBuilder->count('*')
268 ->from($queryGenerator->table)
269 ->where(QueryHelper::stripLogicalOperatorPrefix($queryString))
270 ->execute()->fetchColumn(0);
271
272 $t2DataValue = [
273 'qC' => $saveArr,
274 'qCount' => $rowCount,
275 'qSelect' => $queryGenerator->getSelectQuery($queryString),
276 'qString' => $queryString
277 ];
278 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_action')
279 ->update(
280 'sys_action',
281 ['t2_data' => serialize($t2DataValue)],
282 ['uid' => (int)$uid],
283 ['t2_data' => Connection::PARAM_LOB]
284 );
285 }
286 return 1;
287 }
288 return null;
289 }
290
291 /**
292 * Load store query configs
293 *
294 * @param array $storeQueryConfigs
295 * @param int $storeIndex
296 * @param array $writeArray
297 * @return array
298 */
299 public function loadStoreQueryConfigs($storeQueryConfigs, $storeIndex, $writeArray)
300 {
301 if ($storeQueryConfigs[$storeIndex]) {
302 $keyArr = explode(',', $this->storeList);
303 foreach ($keyArr as $k) {
304 $writeArray[$k] = $storeQueryConfigs[$storeIndex][$k];
305 }
306 }
307 return $writeArray;
308 }
309
310 /**
311 * Process store control
312 *
313 * @return string
314 */
315 public function procesStoreControl()
316 {
317 $storeArray = $this->initStoreArray();
318 $storeQueryConfigs = unserialize($this->settings['storeQueryConfigs']);
319 $storeControl = GeneralUtility::_GP('storeControl');
320 $storeIndex = (int)$storeControl['STORE'];
321 $saveStoreArray = 0;
322 $writeArray = [];
323 $msg = '';
324 if (is_array($storeControl)) {
325 if ($storeControl['LOAD']) {
326 if ($storeIndex > 0) {
327 $writeArray = $this->loadStoreQueryConfigs($storeQueryConfigs, $storeIndex, $writeArray);
328 $saveStoreArray = 1;
329 $flashMessage = GeneralUtility::makeInstance(
330 FlashMessage::class,
331 sprintf($this->languageService->getLL('query_loaded'), $storeArray[$storeIndex])
332 );
333 } elseif ($storeIndex < 0 && ExtensionManagementUtility::isLoaded('sys_action')) {
334 $actionRecord = BackendUtility::getRecord('sys_action', abs($storeIndex));
335 if (is_array($actionRecord)) {
336 $dA = unserialize($actionRecord['t2_data']);
337 $dbSC = [];
338 if (is_array($dA['qC'])) {
339 $dbSC[0] = $dA['qC'];
340 }
341 $writeArray = $this->loadStoreQueryConfigs($dbSC, '0', $writeArray);
342 $saveStoreArray = 1;
343 $flashMessage = GeneralUtility::makeInstance(
344 FlashMessage::class,
345 sprintf($this->languageService->getLL('query_from_action_loaded'), $actionRecord['title'])
346 );
347 }
348 }
349 } elseif ($storeControl['SAVE']) {
350 if ($storeIndex < 0) {
351 $qOK = $this->saveQueryInAction(abs($storeIndex));
352 if ($qOK) {
353 $flashMessage = GeneralUtility::makeInstance(
354 FlashMessage::class,
355 $this->languageService->getLL('query_saved')
356 );
357 } else {
358 $flashMessage = GeneralUtility::makeInstance(
359 FlashMessage::class,
360 $this->languageService->getLL('query_notsaved'),
361 '',
362 FlashMessage::ERROR
363 );
364 }
365 } else {
366 if (trim($storeControl['title'])) {
367 if ($storeIndex > 0) {
368 $storeArray[$storeIndex] = $storeControl['title'];
369 } else {
370 $storeArray[] = $storeControl['title'];
371 end($storeArray);
372 $storeIndex = key($storeArray);
373 }
374 $storeQueryConfigs = $this->addToStoreQueryConfigs($storeQueryConfigs, $storeIndex);
375 $saveStoreArray = 1;
376 $flashMessage = GeneralUtility::makeInstance(
377 FlashMessage::class,
378 $this->languageService->getLL('query_saved')
379 );
380 }
381 }
382 } elseif ($storeControl['REMOVE']) {
383 if ($storeIndex > 0) {
384 $flashMessage = GeneralUtility::makeInstance(
385 FlashMessage::class,
386 sprintf($this->languageService->getLL('query_removed'), $storeArray[$storeControl['STORE']])
387 );
388 // Removing
389 unset($storeArray[$storeControl['STORE']]);
390 $saveStoreArray = 1;
391 }
392 }
393 if (!empty($flashMessage)) {
394 $msg = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)
395 ->resolve()
396 ->render([$flashMessage]);
397 }
398 }
399 if ($saveStoreArray) {
400 // Making sure, index 0 is not set!
401 unset($storeArray[0]);
402 $writeArray['storeArray'] = serialize($storeArray);
403 $writeArray['storeQueryConfigs'] =
404 serialize($this->cleanStoreQueryConfigs($storeQueryConfigs, $storeArray));
405 $this->settings = BackendUtility::getModuleData(
406 $this->menuItems,
407 $writeArray,
408 $this->moduleName,
409 'ses'
410 );
411 }
412 return $msg;
413 }
414
415 /**
416 * Query marker
417 *
418 * @return string
419 */
420 public function queryMaker()
421 {
422 $output = '';
423 $this->hookArray = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3lib_fullsearch'] ?? [];
424 $msg = $this->procesStoreControl();
425 $userTsConfig = $this->backendUserAuthentication->getTSConfig();
426 if (!$userTsConfig['mod.']['dbint.']['disableStoreControl']) {
427 $output .= '<h2>Load/Save Query</h2>';
428 $output .= '<div>' . $this->makeStoreControl() . '</div>';
429 $output .= $msg;
430 }
431 // Query Maker:
432 $queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
433 $queryGenerator->init('queryConfig', $this->settings['queryTable']);
434 if ($this->formName) {
435 $queryGenerator->setFormName($this->formName);
436 }
437 $tmpCode = $queryGenerator->makeSelectorTable($this->settings);
438 $output .= '<div id="query"></div><h2>Make query</h2><div>' . $tmpCode . '</div>';
439 $mQ = $this->settings['search_query_makeQuery'];
440 // Make form elements:
441 if ($queryGenerator->table && is_array($GLOBALS['TCA'][$queryGenerator->table])) {
442 if ($mQ) {
443 // Show query
444 $queryGenerator->enablePrefix = 1;
445 $queryString = $queryGenerator->getQuery($queryGenerator->queryConfig);
446 $selectQueryString = $queryGenerator->getSelectQuery($queryString);
447 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($queryGenerator->table);
448
449 $isConnectionMysql = strpos($connection->getServerVersion(), 'MySQL') === 0;
450 $fullQueryString = '';
451 try {
452 if ($mQ === 'explain' && $isConnectionMysql) {
453 // EXPLAIN is no ANSI SQL, for now this is only executed on mysql
454 // @todo: Move away from getSelectQuery() or model differently
455 $fullQueryString = 'EXPLAIN ' . $selectQueryString;
456 $dataRows = $connection->executeQuery('EXPLAIN ' . $selectQueryString)->fetchAll();
457 } elseif ($mQ === 'count') {
458 $queryBuilder = $connection->createQueryBuilder();
459 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
460 $dataRows = $queryBuilder->count('*')
461 ->from($queryGenerator->table)
462 ->where(QueryHelper::stripLogicalOperatorPrefix($queryString));
463 $fullQueryString = $queryBuilder->getSQL();
464 $dataRows = [$queryBuilder->execute()->fetchColumn(0)];
465 } else {
466 $fullQueryString = $selectQueryString;
467 $dataRows = $connection->executeQuery($selectQueryString)->fetchAll();
468 }
469 if (!$userTsConfig['mod.']['dbint.']['disableShowSQLQuery']) {
470 $output .= '<h2>SQL query</h2><div><pre>' . htmlspecialchars($fullQueryString) . '</pre></div>';
471 }
472 $cPR = $this->getQueryResultCode($mQ, $dataRows, $queryGenerator->table);
473 $output .= '<h2>' . $cPR['header'] . '</h2><div>' . $cPR['content'] . '</div>';
474 } catch (DBALException $e) {
475 if (!$userTsConfig['mod.']['dbint.']['disableShowSQLQuery']) {
476 $output .= '<h2>SQL query</h2><div><pre>' . htmlspecialchars($fullQueryString) . '</pre></div>';
477 }
478 $out = '<p><strong>Error: <span class="text-danger">'
479 . $e->getMessage()
480 . '</span></strong></p>';
481 $output .= '<h2>SQL error</h2><div>' . $out . '</div>';
482 }
483 }
484 }
485 return '<div class="query-builder">' . $output . '</div>';
486 }
487
488 /**
489 * Get query result code
490 *
491 * @param string $type
492 * @param array $dataRows Rows to display
493 * @param string $table
494 * @return array HTML-code for "header" and "content"
495 * @throws \TYPO3\CMS\Core\Exception
496 */
497 public function getQueryResultCode($type, array $dataRows, $table)
498 {
499 $out = '';
500 $cPR = [];
501 switch ($type) {
502 case 'count':
503 $cPR['header'] = 'Count';
504 $cPR['content'] = '<BR><strong>' . (int)$dataRows[0] . '</strong> records selected.';
505 break;
506 case 'all':
507 $rowArr = [];
508 $dataRow = null;
509 foreach ($dataRows as $dataRow) {
510 $rowArr[] = $this->resultRowDisplay($dataRow, $GLOBALS['TCA'][$table], $table);
511 }
512 if (is_array($this->hookArray['beforeResultTable'])) {
513 foreach ($this->hookArray['beforeResultTable'] as $_funcRef) {
514 $out .= GeneralUtility::callUserFunction($_funcRef, $this->settings, $this);
515 }
516 }
517 if (!empty($rowArr)) {
518 $cPR['header'] = 'Result';
519 $out .= '<table class="table table-striped table-hover">'
520 . $this->resultRowTitles($dataRow, $GLOBALS['TCA'][$table], $table) . implode(LF, $rowArr)
521 . '</table>';
522 } else {
523 $this->renderNoResultsFoundMessage();
524 }
525
526 $cPR['content'] = $out;
527 break;
528 case 'csv':
529 $rowArr = [];
530 $first = 1;
531 foreach ($dataRows as $dataRow) {
532 if ($first) {
533 $rowArr[] = $this->csvValues(array_keys($dataRow), ',', '');
534 $first = 0;
535 }
536 $rowArr[] = $this->csvValues($dataRow, ',', '"', $GLOBALS['TCA'][$table], $table);
537 }
538 if (!empty($rowArr)) {
539 $cPR['header'] = 'Result';
540 $out .= '<textarea name="whatever" rows="20" class="text-monospace" style="width:100%">'
541 . htmlspecialchars(implode(LF, $rowArr))
542 . '</textarea>';
543 if (!$this->noDownloadB) {
544 $out .= '<br><input class="btn btn-default" type="submit" name="download_file" '
545 . 'value="Click to download file" onClick="window.location.href=' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->downloadScript))
546 . ';">';
547 }
548 // Downloads file:
549 // @todo: args. routing anyone?
550 if (GeneralUtility::_GP('download_file')) {
551 $filename = 'TYPO3_' . $table . '_export_' . date('dmy-Hi') . '.csv';
552 $mimeType = 'application/octet-stream';
553 header('Content-Type: ' . $mimeType);
554 header('Content-Disposition: attachment; filename=' . $filename);
555 echo implode(CRLF, $rowArr);
556 die;
557 }
558 } else {
559 $this->renderNoResultsFoundMessage();
560 }
561 $cPR['content'] = $out;
562 break;
563 case 'explain':
564 default:
565 foreach ($dataRows as $dataRow) {
566 $out .= '<br />' . DebugUtility::viewArray($dataRow);
567 }
568 $cPR['header'] = 'Explain SQL query';
569 $cPR['content'] = $out;
570 }
571 return $cPR;
572 }
573
574 /**
575 * CSV values
576 *
577 * @param array $row
578 * @param string $delim
579 * @param string $quote
580 * @param array $conf
581 * @param string $table
582 * @return string A single line of CSV
583 */
584 public function csvValues($row, $delim = ',', $quote = '"', $conf = [], $table = '')
585 {
586 $valueArray = $row;
587 if ($this->settings['search_result_labels'] && $table) {
588 foreach ($valueArray as $key => $val) {
589 $valueArray[$key] = $this->getProcessedValueExtra($table, $key, $val, $conf, ';');
590 }
591 }
592 return CsvUtility::csvValues($valueArray, $delim, $quote);
593 }
594
595 /**
596 * Search
597 *
598 * @return string
599 */
600 public function search()
601 {
602 $swords = $this->settings['sword'];
603 $out = '';
604 if ($swords) {
605 foreach ($GLOBALS['TCA'] as $table => $value) {
606 // Get fields list
607 $conf = $GLOBALS['TCA'][$table];
608 // Avoid querying tables with no columns
609 if (empty($conf['columns'])) {
610 continue;
611 }
612 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
613 $tableColumns = $connection->getSchemaManager()->listTableColumns($table);
614 $fieldsInDatabase = [];
615 foreach ($tableColumns as $column) {
616 $fieldsInDatabase[] = $column->getName();
617 }
618 $fields = array_intersect(array_keys($conf['columns']), $fieldsInDatabase);
619
620 $queryBuilder = $connection->createQueryBuilder();
621 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
622 $queryBuilder->count('*')->from($table);
623 $likes = [];
624 $excapedLikeString = '%' . $queryBuilder->escapeLikeWildcards($swords) . '%';
625 foreach ($fields as $field) {
626 $likes[] = $queryBuilder->expr()->like(
627 $field,
628 $queryBuilder->createNamedParameter($excapedLikeString, \PDO::PARAM_STR)
629 );
630 }
631 $count = $queryBuilder->orWhere(...$likes)->execute()->fetchColumn(0);
632
633 if ($count > 0) {
634 $queryBuilder = $connection->createQueryBuilder();
635 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
636 $queryBuilder->select('uid', $conf['ctrl']['label'])
637 ->from($table)
638 ->setMaxResults(200);
639 $likes = [];
640 foreach ($fields as $field) {
641 $likes[] = $queryBuilder->expr()->like(
642 $field,
643 $queryBuilder->createNamedParameter($excapedLikeString, \PDO::PARAM_STR)
644 );
645 }
646 $statement = $queryBuilder->orWhere(...$likes)->execute();
647 $lastRow = null;
648 $rowArr = [];
649 while ($row = $statement->fetch()) {
650 $rowArr[] = $this->resultRowDisplay($row, $conf, $table);
651 $lastRow = $row;
652 }
653 $markup = [];
654 $markup[] = '<div class="panel panel-default">';
655 $markup[] = ' <div class="panel-heading">';
656 $markup[] = htmlspecialchars($this->languageService->sL($conf['ctrl']['title'])) . ' (' . $count . ')';
657 $markup[] = ' </div>';
658 $markup[] = ' <table class="table table-striped table-hover">';
659 $markup[] = $this->resultRowTitles($lastRow, $conf, $table);
660 $markup[] = implode(LF, $rowArr);
661 $markup[] = ' </table>';
662 $markup[] = '</div>';
663
664 $out .= implode(LF, $markup);
665 }
666 }
667 }
668 return $out;
669 }
670
671 /**
672 * Result row display
673 *
674 * @param array $row
675 * @param array $conf
676 * @param string $table
677 * @return string
678 */
679 public function resultRowDisplay($row, $conf, $table)
680 {
681 $out = '<tr>';
682 foreach ($row as $fieldName => $fieldValue) {
683 if (GeneralUtility::inList($this->settings['queryFields'], $fieldName)
684 || !$this->settings['queryFields']
685 && $fieldName !== 'pid'
686 && $fieldName !== 'deleted'
687 ) {
688 if ($this->settings['search_result_labels']) {
689 $fVnew = $this->getProcessedValueExtra($table, $fieldName, $fieldValue, $conf, '<br />');
690 } else {
691 $fVnew = htmlspecialchars($fieldValue);
692 }
693 $out .= '<td>' . $fVnew . '</td>';
694 }
695 }
696 $out .= '<td>';
697 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
698 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
699
700 if (!$row['deleted']) {
701 $out .= '<div class="btn-group" role="group">';
702 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
703 'edit' => [
704 $table => [
705 $row['uid'] => 'edit'
706 ]
707 ],
708 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
709 . GeneralUtility::implodeArrayForUrl('SET', (array)GeneralUtility::_POST('SET'))
710 ]);
711 $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url) . '">'
712 . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
713 $out .= '</div><div class="btn-group" role="group">';
714 $out .= '<a class="btn btn-default" href="#" onClick="top.TYPO3.InfoWindow.showItem(\'' . $table . '\',' . $row['uid']
715 . ');return false;">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render()
716 . '</a>';
717 $out .= '</div>';
718 } else {
719 $out .= '<div class="btn-group" role="group">';
720 $out .= '<a class="btn btn-default" href="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute('tce_db', [
721 'cmd' => [
722 $table => [
723 $row['uid'] => [
724 'undelete' => 1
725 ]
726 ]
727 ],
728 'redirect' => GeneralUtility::linkThisScript()
729 ])) . '" title="' . htmlspecialchars($this->languageService->getLL('undelete_only')) . '">';
730 $out .= $this->iconFactory->getIcon('actions-edit-restore', Icon::SIZE_SMALL)->render() . '</a>';
731 $formEngineParameters = [
732 'edit' => [
733 $table => [
734 $row['uid'] => 'edit'
735 ]
736 ],
737 'returnUrl' => GeneralUtility::linkThisScript()
738 ];
739 $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $formEngineParameters);
740 $out .= '<a class="btn btn-default" href="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute('tce_db', [
741 'cmd' => [
742 $table => [
743 $row['uid'] => [
744 'undelete' => 1
745 ]
746 ]
747 ],
748 'redirect' => $redirectUrl
749 ])) . '" title="' . htmlspecialchars($this->languageService->getLL('undelete_and_edit')) . '">';
750 $out .= $this->iconFactory->getIcon('actions-edit-restore-edit', Icon::SIZE_SMALL)->render() . '</a>';
751 $out .= '</div>';
752 }
753 $_params = [$table => $row];
754 if (is_array($this->hookArray['additionalButtons'])) {
755 foreach ($this->hookArray['additionalButtons'] as $_funcRef) {
756 $out .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
757 }
758 }
759 $out .= '</td></tr>';
760 return $out;
761 }
762
763 /**
764 * Get processed value extra
765 *
766 * @param string $table
767 * @param string $fieldName
768 * @param string $fieldValue
769 * @param array $conf Not used
770 * @param string $splitString
771 * @return string
772 */
773 public function getProcessedValueExtra($table, $fieldName, $fieldValue, $conf, $splitString)
774 {
775 $out = '';
776 $fields = [];
777 // Analysing the fields in the table.
778 if (is_array($GLOBALS['TCA'][$table])) {
779 $fC = $GLOBALS['TCA'][$table]['columns'][$fieldName];
780 $fields = $fC['config'];
781 $fields['exclude'] = $fC['exclude'];
782 if (is_array($fC) && $fC['label']) {
783 $fields['label'] = preg_replace('/:$/', '', trim($this->languageService->sL($fC['label'])));
784 switch ($fields['type']) {
785 case 'input':
786 if (preg_match('/int|year/i', $fields['eval'])) {
787 $fields['type'] = 'number';
788 } elseif (preg_match('/time/i', $fields['eval'])) {
789 $fields['type'] = 'time';
790 } elseif (preg_match('/date/i', $fields['eval'])) {
791 $fields['type'] = 'date';
792 } else {
793 $fields['type'] = 'text';
794 }
795 break;
796 case 'check':
797 if (!$fields['items']) {
798 $fields['type'] = 'boolean';
799 } else {
800 $fields['type'] = 'binary';
801 }
802 break;
803 case 'radio':
804 $fields['type'] = 'multiple';
805 break;
806 case 'select':
807 $fields['type'] = 'multiple';
808 if ($fields['foreign_table']) {
809 $fields['type'] = 'relation';
810 }
811 if ($fields['special']) {
812 $fields['type'] = 'text';
813 }
814 break;
815 case 'group':
816 $fields['type'] = 'files';
817 if ($fields['internal_type'] === 'db') {
818 $fields['type'] = 'relation';
819 }
820 break;
821 case 'user':
822 case 'flex':
823 case 'passthrough':
824 case 'none':
825 case 'text':
826 default:
827 $fields['type'] = 'text';
828 }
829 } else {
830 $fields['label'] = '[FIELD: ' . $fieldName . ']';
831 switch ($fieldName) {
832 case 'pid':
833 $fields['type'] = 'relation';
834 $fields['allowed'] = 'pages';
835 break;
836 case 'cruser_id':
837 $fields['type'] = 'relation';
838 $fields['allowed'] = 'be_users';
839 break;
840 case 'tstamp':
841 case 'crdate':
842 $fields['type'] = 'time';
843 break;
844 default:
845 $fields['type'] = 'number';
846 }
847 }
848 }
849 switch ($fields['type']) {
850 case 'date':
851 if ($fieldValue != -1) {
852 $out = strftime('%d-%m-%Y', $fieldValue);
853 }
854 break;
855 case 'time':
856 if ($fieldValue != -1) {
857 if ($splitString === '<br />') {
858 $out = strftime('%H:%M' . $splitString . '%d-%m-%Y', $fieldValue);
859 } else {
860 $out = strftime('%H:%M %d-%m-%Y', $fieldValue);
861 }
862 }
863 break;
864 case 'multiple':
865 case 'binary':
866 case 'relation':
867 $out = $this->makeValueList($fieldName, $fieldValue, $fields, $table, $splitString);
868 break;
869 case 'boolean':
870 $out = $fieldValue ? 'True' : 'False';
871 break;
872 case 'files':
873 default:
874 $out = htmlspecialchars($fieldValue);
875 }
876 return $out;
877 }
878
879 /**
880 * Get tree list
881 *
882 * @param int $id
883 * @param int $depth
884 * @param int $begin
885 * @param string $permsClause
886 *
887 * @return string
888 */
889 public function getTreeList($id, $depth, $begin = 0, $permsClause = null)
890 {
891 $depth = (int)$depth;
892 $begin = (int)$begin;
893 $id = (int)$id;
894 if ($id < 0) {
895 $id = abs($id);
896 }
897 if ($begin == 0) {
898 $theList = $id;
899 } else {
900 $theList = '';
901 }
902 if ($id && $depth > 0) {
903 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
904 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
905 $statement = $queryBuilder->select('uid')
906 ->from('pages')
907 ->where(
908 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
909 QueryHelper::stripLogicalOperatorPrefix($permsClause)
910 )
911 ->execute();
912 while ($row = $statement->fetch()) {
913 if ($begin <= 0) {
914 $theList .= ',' . $row['uid'];
915 }
916 if ($depth > 1) {
917 $theList .= $this->getTreeList($row['uid'], $depth - 1, $begin - 1, $permsClause);
918 }
919 }
920 }
921 return $theList;
922 }
923
924 /**
925 * Make value list
926 *
927 * @param string $fieldName
928 * @param string $fieldValue
929 * @param array $conf
930 * @param string $table
931 * @param string $splitString
932 * @return string
933 */
934 public function makeValueList($fieldName, $fieldValue, $conf, $table, $splitString)
935 {
936 $fieldSetup = $conf;
937 $out = '';
938 if ($fieldSetup['type'] === 'files') {
939 $d = dir(PATH_site . $fieldSetup['uploadfolder']);
940 while (false !== ($entry = $d->read())) {
941 if ($entry === '.' || $entry === '..') {
942 continue;
943 }
944 $fileArray[] = $entry;
945 }
946 $d->close();
947 natcasesort($fileArray);
948 foreach ($fileArray as $fileName) {
949 if (GeneralUtility::inList($fieldValue, $fileName) || $fieldValue == $fileName) {
950 if ($out !== '') {
951 $out .= $splitString;
952 }
953 $out .= htmlspecialchars($fileName);
954 }
955 }
956 }
957 if ($fieldSetup['type'] === 'multiple') {
958 foreach ($fieldSetup['items'] as $key => $val) {
959 if (substr($val[0], 0, 4) === 'LLL:') {
960 $value = $this->languageService->sL($val[0]);
961 } else {
962 $value = $val[0];
963 }
964 if (GeneralUtility::inList($fieldValue, $val[1]) || $fieldValue == $val[1]) {
965 if ($out !== '') {
966 $out .= $splitString;
967 }
968 $out .= htmlspecialchars($value);
969 }
970 }
971 }
972 if ($fieldSetup['type'] === 'binary') {
973 foreach ($fieldSetup['items'] as $Key => $val) {
974 if (substr($val[0], 0, 4) === 'LLL:') {
975 $value = $this->languageService->sL($val[0]);
976 } else {
977 $value = $val[0];
978 }
979 if ($out !== '') {
980 $out .= $splitString;
981 }
982 $out .= htmlspecialchars($value);
983 }
984 }
985 if ($fieldSetup['type'] === 'relation') {
986 $dontPrefixFirstTable = 0;
987 $useTablePrefix = 0;
988 if ($fieldSetup['items']) {
989 foreach ($fieldSetup['items'] as $key => $val) {
990 if (substr($val[0], 0, 4) === 'LLL:') {
991 $value = $this->languageService->sL($val[0]);
992 } else {
993 $value = $val[0];
994 }
995 if (GeneralUtility::inList($fieldValue, $value) || $fieldValue == $value) {
996 if ($out !== '') {
997 $out .= $splitString;
998 }
999 $out .= htmlspecialchars($value);
1000 }
1001 }
1002 }
1003 if (stristr($fieldSetup['allowed'], ',')) {
1004 $from_table_Arr = explode(',', $fieldSetup['allowed']);
1005 $useTablePrefix = 1;
1006 if (!$fieldSetup['prepend_tname']) {
1007 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1008 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1009 $statement = $queryBuilder->select($fieldName)->from($table)->execute();
1010 while ($row = $statement->fetch()) {
1011 if (stristr($row[$fieldName], ',')) {
1012 $checkContent = explode(',', $row[$fieldName]);
1013 foreach ($checkContent as $singleValue) {
1014 if (!stristr($singleValue, '_')) {
1015 $dontPrefixFirstTable = 1;
1016 }
1017 }
1018 } else {
1019 $singleValue = $row[$fieldName];
1020 if ($singleValue !== '' && !stristr($singleValue, '_')) {
1021 $dontPrefixFirstTable = 1;
1022 }
1023 }
1024 }
1025 }
1026 } else {
1027 $from_table_Arr[0] = $fieldSetup['allowed'];
1028 }
1029 if ($fieldSetup['prepend_tname']) {
1030 $useTablePrefix = 1;
1031 }
1032 if ($fieldSetup['foreign_table']) {
1033 $from_table_Arr[0] = $fieldSetup['foreign_table'];
1034 }
1035 $counter = 0;
1036 $useSelectLabels = 0;
1037 $useAltSelectLabels = 0;
1038 $tablePrefix = '';
1039 $labelFieldSelect = [];
1040 foreach ($from_table_Arr as $from_table) {
1041 if ($useTablePrefix && !$dontPrefixFirstTable && $counter != 1 || $counter == 1) {
1042 $tablePrefix = $from_table . '_';
1043 }
1044 $counter = 1;
1045 if (is_array($GLOBALS['TCA'][$from_table])) {
1046 $labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'];
1047 $altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'];
1048 if ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items']) {
1049 $items = $GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'];
1050 foreach ($items as $labelArray) {
1051 if (substr($labelArray[0], 0, 4) === 'LLL:') {
1052 $labelFieldSelect[$labelArray[1]] = $this->languageService->sL($labelArray[0]);
1053 } else {
1054 $labelFieldSelect[$labelArray[1]] = $labelArray[0];
1055 }
1056 }
1057 $useSelectLabels = 1;
1058 }
1059 $altLabelFieldSelect = [];
1060 if ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items']) {
1061 $items = $GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'];
1062 foreach ($items as $altLabelArray) {
1063 if (substr($altLabelArray[0], 0, 4) === 'LLL:') {
1064 $altLabelFieldSelect[$altLabelArray[1]] = $this->languageService->sL($altLabelArray[0]);
1065 } else {
1066 $altLabelFieldSelect[$altLabelArray[1]] = $altLabelArray[0];
1067 }
1068 }
1069 $useAltSelectLabels = 1;
1070 }
1071
1072 if (!$this->tableArray[$from_table]) {
1073 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
1074 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1075 $selectFields = ['uid', $labelField];
1076 if ($altLabelField) {
1077 $selectFields[] = $altLabelField;
1078 }
1079 $queryBuilder->select(...$selectFields)
1080 ->from($from_table)
1081 ->orderBy('uid');
1082 if (!$this->backendUserAuthentication->isAdmin() && $GLOBALS['TYPO3_CONF_VARS']['BE']['lockBeUserToDBmounts']) {
1083 $webMounts = $this->backendUserAuthentication->returnWebmounts();
1084 $perms_clause = $this->backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
1085 $webMountPageTree = '';
1086 $webMountPageTreePrefix = '';
1087 foreach ($webMounts as $webMount) {
1088 if ($webMountPageTree) {
1089 $webMountPageTreePrefix = ',';
1090 }
1091 $webMountPageTree .= $webMountPageTreePrefix
1092 . $this->getTreeList($webMount, 999, $begin = 0, $perms_clause);
1093 }
1094 if ($from_table === 'pages') {
1095 $queryBuilder->where(
1096 QueryHelper::stripLogicalOperatorPrefix($perms_clause),
1097 $queryBuilder->expr()->in(
1098 'uid',
1099 $queryBuilder->createNamedParameter(
1100 GeneralUtility::intExplode(',', $webMountPageTree),
1101 Connection::PARAM_INT_ARRAY
1102 )
1103 )
1104 );
1105 } else {
1106 $queryBuilder->where(
1107 $queryBuilder->expr()->in(
1108 'pid',
1109 $queryBuilder->createNamedParameter(
1110 GeneralUtility::intExplode(',', $webMountPageTree),
1111 Connection::PARAM_INT_ARRAY
1112 )
1113 )
1114 );
1115 }
1116 }
1117 $statement = $queryBuilder->execute();
1118 $this->tableArray[$from_table] = [];
1119 while ($row = $statement->fetch()) {
1120 $this->tableArray[$from_table][] = $row;
1121 }
1122 }
1123
1124 foreach ($this->tableArray[$from_table] as $key => $val) {
1125 $this->settings['labels_noprefix'] =
1126 $this->settings['labels_noprefix'] == 1
1127 ? 'on'
1128 : $this->settings['labels_noprefix'];
1129 $prefixString =
1130 $this->settings['labels_noprefix'] === 'on'
1131 ? ''
1132 : ' [' . $tablePrefix . $val['uid'] . '] ';
1133 if ($out !== '') {
1134 $out .= $splitString;
1135 }
1136 if (GeneralUtility::inList($fieldValue, $tablePrefix . $val['uid'])
1137 || $fieldValue == $tablePrefix . $val['uid']) {
1138 if ($useSelectLabels) {
1139 $out .= htmlspecialchars($prefixString . $labelFieldSelect[$val[$labelField]]);
1140 } elseif ($val[$labelField]) {
1141 $out .= htmlspecialchars($prefixString . $val[$labelField]);
1142 } elseif ($useAltSelectLabels) {
1143 $out .= htmlspecialchars($prefixString . $altLabelFieldSelect[$val[$altLabelField]]);
1144 } else {
1145 $out .= htmlspecialchars($prefixString . $val[$altLabelField]);
1146 }
1147 }
1148 }
1149 }
1150 }
1151 }
1152 return $out;
1153 }
1154
1155 /**
1156 * Render table header
1157 *
1158 * @param array $row Table columns
1159 * @param array $conf Table TCA
1160 * @param string $table Table name
1161 * @return string HTML of table header
1162 */
1163 public function resultRowTitles($row, $conf, $table)
1164 {
1165 $tableHeader = [];
1166 // Start header row
1167 $tableHeader[] = '<thead><tr>';
1168 // Iterate over given columns
1169 foreach ($row as $fieldName => $fieldValue) {
1170 if (GeneralUtility::inList($this->settings['queryFields'], $fieldName)
1171 || !$this->settings['queryFields']
1172 && $fieldName !== 'pid'
1173 && $fieldName !== 'deleted'
1174 ) {
1175 if ($this->settings['search_result_labels']) {
1176 $title = $this->languageService->sL($conf['columns'][$fieldName]['label']
1177 ? $conf['columns'][$fieldName]['label']
1178 : $fieldName);
1179 } else {
1180 $title = $this->languageService->sL($fieldName);
1181 }
1182 $tableHeader[] = '<th>' . htmlspecialchars($title) . '</th>';
1183 }
1184 }
1185 // Add empty icon column
1186 $tableHeader[] = '<th></th>';
1187 // Close header row
1188 $tableHeader[] = '</tr></thead>';
1189 return implode(LF, $tableHeader);
1190 }
1191
1192 /**
1193 * CSV row titles
1194 *
1195 * @param array $row
1196 * @param array $conf
1197 * @param mixed $table Not used
1198 * @return string
1199 */
1200 public function csvRowTitles($row, $conf, $table)
1201 {
1202 $out = '';
1203 foreach ($row as $fieldName => $fieldValue) {
1204 if (GeneralUtility::inList($this->settings['queryFields'], $fieldName)
1205 || !$this->settings['queryFields'] && $fieldName !== 'pid') {
1206 if ($out !== '') {
1207 $out .= ',';
1208 }
1209 if ($this->settings['search_result_labels']) {
1210 $out .= htmlspecialchars(
1211 $this->languageService->sL(
1212 $conf['columns'][$fieldName]['label']
1213 ? $conf['columns'][$fieldName]['label']
1214 : $fieldName
1215 )
1216 );
1217 } else {
1218 $out .= htmlspecialchars($this->languageService->sL($fieldName));
1219 }
1220 }
1221 }
1222 return $out;
1223 }
1224
1225 /**
1226 * Sets the current name of the input form.
1227 *
1228 * @param string $formName The name of the form.
1229 */
1230 public function setFormName($formName)
1231 {
1232 $this->formName = trim($formName);
1233 }
1234
1235 /**
1236 * @throws \InvalidArgumentException
1237 * @throws \TYPO3\CMS\Core\Exception
1238 */
1239 private function renderNoResultsFoundMessage()
1240 {
1241 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, 'No rows selected!', '', FlashMessage::INFO);
1242 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1243 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1244 $defaultFlashMessageQueue->enqueue($flashMessage);
1245 }
1246 }