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