[TASK] Clean up unused / dead code
[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\Core\Environment;
21 use TYPO3\CMS\Core\Database\Query\QueryHelper;
22 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Localization\LanguageService;
26 use TYPO3\CMS\Core\Messaging\FlashMessage;
27 use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
28 use TYPO3\CMS\Core\Messaging\FlashMessageService;
29 use TYPO3\CMS\Core\Type\Bitmask\Permission;
30 use TYPO3\CMS\Core\Utility\CsvUtility;
31 use TYPO3\CMS\Core\Utility\DebugUtility;
32 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
33 use TYPO3\CMS\Core\Utility\GeneralUtility;
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 $excapedLikeString = '%' . $queryBuilder->escapeLikeWildcards($swords) . '%';
626 foreach ($fields as $field) {
627 $likes[] = $queryBuilder->expr()->like(
628 $field,
629 $queryBuilder->createNamedParameter($excapedLikeString, \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($excapedLikeString, \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 . GeneralUtility::implodeArrayForUrl('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 $fields['type'] = 'files';
818 if ($fields['internal_type'] === 'db') {
819 $fields['type'] = 'relation';
820 }
821 break;
822 case 'user':
823 case 'flex':
824 case 'passthrough':
825 case 'none':
826 case 'text':
827 default:
828 $fields['type'] = 'text';
829 }
830 } else {
831 $fields['label'] = '[FIELD: ' . $fieldName . ']';
832 switch ($fieldName) {
833 case 'pid':
834 $fields['type'] = 'relation';
835 $fields['allowed'] = 'pages';
836 break;
837 case 'cruser_id':
838 $fields['type'] = 'relation';
839 $fields['allowed'] = 'be_users';
840 break;
841 case 'tstamp':
842 case 'crdate':
843 $fields['type'] = 'time';
844 break;
845 default:
846 $fields['type'] = 'number';
847 }
848 }
849 }
850 switch ($fields['type']) {
851 case 'date':
852 if ($fieldValue != -1) {
853 $out = strftime('%d-%m-%Y', $fieldValue);
854 }
855 break;
856 case 'time':
857 if ($fieldValue != -1) {
858 if ($splitString === '<br />') {
859 $out = strftime('%H:%M' . $splitString . '%d-%m-%Y', $fieldValue);
860 } else {
861 $out = strftime('%H:%M %d-%m-%Y', $fieldValue);
862 }
863 }
864 break;
865 case 'multiple':
866 case 'binary':
867 case 'relation':
868 $out = $this->makeValueList($fieldName, $fieldValue, $fields, $table, $splitString);
869 break;
870 case 'boolean':
871 $out = $fieldValue ? 'True' : 'False';
872 break;
873 case 'files':
874 default:
875 $out = htmlspecialchars($fieldValue);
876 }
877 return $out;
878 }
879
880 /**
881 * Get tree list
882 *
883 * @param int $id
884 * @param int $depth
885 * @param int $begin
886 * @param string $permsClause
887 *
888 * @return string
889 */
890 public function getTreeList($id, $depth, $begin = 0, $permsClause = null)
891 {
892 $depth = (int)$depth;
893 $begin = (int)$begin;
894 $id = (int)$id;
895 if ($id < 0) {
896 $id = abs($id);
897 }
898 if ($begin == 0) {
899 $theList = $id;
900 } else {
901 $theList = '';
902 }
903 if ($id && $depth > 0) {
904 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
905 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
906 $statement = $queryBuilder->select('uid')
907 ->from('pages')
908 ->where(
909 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
910 QueryHelper::stripLogicalOperatorPrefix($permsClause)
911 )
912 ->execute();
913 while ($row = $statement->fetch()) {
914 if ($begin <= 0) {
915 $theList .= ',' . $row['uid'];
916 }
917 if ($depth > 1) {
918 $theList .= $this->getTreeList($row['uid'], $depth - 1, $begin - 1, $permsClause);
919 }
920 }
921 }
922 return $theList;
923 }
924
925 /**
926 * Make value list
927 *
928 * @param string $fieldName
929 * @param string $fieldValue
930 * @param array $conf
931 * @param string $table
932 * @param string $splitString
933 * @return string
934 */
935 public function makeValueList($fieldName, $fieldValue, $conf, $table, $splitString)
936 {
937 $fieldSetup = $conf;
938 $out = '';
939 if ($fieldSetup['type'] === 'files') {
940 $d = dir(Environment::getPublicPath() . '/' . $fieldSetup['uploadfolder']);
941 while (false !== ($entry = $d->read())) {
942 if ($entry === '.' || $entry === '..') {
943 continue;
944 }
945 $fileArray[] = $entry;
946 }
947 $d->close();
948 natcasesort($fileArray);
949 foreach ($fileArray as $fileName) {
950 if (GeneralUtility::inList($fieldValue, $fileName) || $fieldValue == $fileName) {
951 if ($out !== '') {
952 $out .= $splitString;
953 }
954 $out .= htmlspecialchars($fileName);
955 }
956 }
957 }
958 if ($fieldSetup['type'] === 'multiple') {
959 foreach ($fieldSetup['items'] as $key => $val) {
960 if (substr($val[0], 0, 4) === 'LLL:') {
961 $value = $this->languageService->sL($val[0]);
962 } else {
963 $value = $val[0];
964 }
965 if (GeneralUtility::inList($fieldValue, $val[1]) || $fieldValue == $val[1]) {
966 if ($out !== '') {
967 $out .= $splitString;
968 }
969 $out .= htmlspecialchars($value);
970 }
971 }
972 }
973 if ($fieldSetup['type'] === 'binary') {
974 foreach ($fieldSetup['items'] as $Key => $val) {
975 if (substr($val[0], 0, 4) === 'LLL:') {
976 $value = $this->languageService->sL($val[0]);
977 } else {
978 $value = $val[0];
979 }
980 if ($out !== '') {
981 $out .= $splitString;
982 }
983 $out .= htmlspecialchars($value);
984 }
985 }
986 if ($fieldSetup['type'] === 'relation') {
987 $dontPrefixFirstTable = 0;
988 $useTablePrefix = 0;
989 if ($fieldSetup['items']) {
990 foreach ($fieldSetup['items'] as $key => $val) {
991 if (substr($val[0], 0, 4) === 'LLL:') {
992 $value = $this->languageService->sL($val[0]);
993 } else {
994 $value = $val[0];
995 }
996 if (GeneralUtility::inList($fieldValue, $value) || $fieldValue == $value) {
997 if ($out !== '') {
998 $out .= $splitString;
999 }
1000 $out .= htmlspecialchars($value);
1001 }
1002 }
1003 }
1004 if (stristr($fieldSetup['allowed'], ',')) {
1005 $from_table_Arr = explode(',', $fieldSetup['allowed']);
1006 $useTablePrefix = 1;
1007 if (!$fieldSetup['prepend_tname']) {
1008 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1009 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1010 $statement = $queryBuilder->select($fieldName)->from($table)->execute();
1011 while ($row = $statement->fetch()) {
1012 if (stristr($row[$fieldName], ',')) {
1013 $checkContent = explode(',', $row[$fieldName]);
1014 foreach ($checkContent as $singleValue) {
1015 if (!stristr($singleValue, '_')) {
1016 $dontPrefixFirstTable = 1;
1017 }
1018 }
1019 } else {
1020 $singleValue = $row[$fieldName];
1021 if ($singleValue !== '' && !stristr($singleValue, '_')) {
1022 $dontPrefixFirstTable = 1;
1023 }
1024 }
1025 }
1026 }
1027 } else {
1028 $from_table_Arr[0] = $fieldSetup['allowed'];
1029 }
1030 if ($fieldSetup['prepend_tname']) {
1031 $useTablePrefix = 1;
1032 }
1033 if ($fieldSetup['foreign_table']) {
1034 $from_table_Arr[0] = $fieldSetup['foreign_table'];
1035 }
1036 $counter = 0;
1037 $useSelectLabels = 0;
1038 $useAltSelectLabels = 0;
1039 $tablePrefix = '';
1040 $labelFieldSelect = [];
1041 foreach ($from_table_Arr as $from_table) {
1042 if ($useTablePrefix && !$dontPrefixFirstTable && $counter != 1 || $counter == 1) {
1043 $tablePrefix = $from_table . '_';
1044 }
1045 $counter = 1;
1046 if (is_array($GLOBALS['TCA'][$from_table])) {
1047 $labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'];
1048 $altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'];
1049 if ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items']) {
1050 $items = $GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'];
1051 foreach ($items as $labelArray) {
1052 if (substr($labelArray[0], 0, 4) === 'LLL:') {
1053 $labelFieldSelect[$labelArray[1]] = $this->languageService->sL($labelArray[0]);
1054 } else {
1055 $labelFieldSelect[$labelArray[1]] = $labelArray[0];
1056 }
1057 }
1058 $useSelectLabels = 1;
1059 }
1060 $altLabelFieldSelect = [];
1061 if ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items']) {
1062 $items = $GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'];
1063 foreach ($items as $altLabelArray) {
1064 if (substr($altLabelArray[0], 0, 4) === 'LLL:') {
1065 $altLabelFieldSelect[$altLabelArray[1]] = $this->languageService->sL($altLabelArray[0]);
1066 } else {
1067 $altLabelFieldSelect[$altLabelArray[1]] = $altLabelArray[0];
1068 }
1069 }
1070 $useAltSelectLabels = 1;
1071 }
1072
1073 if (!$this->tableArray[$from_table]) {
1074 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
1075 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1076 $selectFields = ['uid', $labelField];
1077 if ($altLabelField) {
1078 $selectFields[] = $altLabelField;
1079 }
1080 $queryBuilder->select(...$selectFields)
1081 ->from($from_table)
1082 ->orderBy('uid');
1083 if (!$this->backendUserAuthentication->isAdmin() && $GLOBALS['TYPO3_CONF_VARS']['BE']['lockBeUserToDBmounts']) {
1084 $webMounts = $this->backendUserAuthentication->returnWebmounts();
1085 $perms_clause = $this->backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
1086 $webMountPageTree = '';
1087 $webMountPageTreePrefix = '';
1088 foreach ($webMounts as $webMount) {
1089 if ($webMountPageTree) {
1090 $webMountPageTreePrefix = ',';
1091 }
1092 $webMountPageTree .= $webMountPageTreePrefix
1093 . $this->getTreeList($webMount, 999, $begin = 0, $perms_clause);
1094 }
1095 if ($from_table === 'pages') {
1096 $queryBuilder->where(
1097 QueryHelper::stripLogicalOperatorPrefix($perms_clause),
1098 $queryBuilder->expr()->in(
1099 'uid',
1100 $queryBuilder->createNamedParameter(
1101 GeneralUtility::intExplode(',', $webMountPageTree),
1102 Connection::PARAM_INT_ARRAY
1103 )
1104 )
1105 );
1106 } else {
1107 $queryBuilder->where(
1108 $queryBuilder->expr()->in(
1109 'pid',
1110 $queryBuilder->createNamedParameter(
1111 GeneralUtility::intExplode(',', $webMountPageTree),
1112 Connection::PARAM_INT_ARRAY
1113 )
1114 )
1115 );
1116 }
1117 }
1118 $statement = $queryBuilder->execute();
1119 $this->tableArray[$from_table] = [];
1120 while ($row = $statement->fetch()) {
1121 $this->tableArray[$from_table][] = $row;
1122 }
1123 }
1124
1125 foreach ($this->tableArray[$from_table] as $key => $val) {
1126 $this->settings['labels_noprefix'] =
1127 $this->settings['labels_noprefix'] == 1
1128 ? 'on'
1129 : $this->settings['labels_noprefix'];
1130 $prefixString =
1131 $this->settings['labels_noprefix'] === 'on'
1132 ? ''
1133 : ' [' . $tablePrefix . $val['uid'] . '] ';
1134 if ($out !== '') {
1135 $out .= $splitString;
1136 }
1137 if (GeneralUtility::inList($fieldValue, $tablePrefix . $val['uid'])
1138 || $fieldValue == $tablePrefix . $val['uid']) {
1139 if ($useSelectLabels) {
1140 $out .= htmlspecialchars($prefixString . $labelFieldSelect[$val[$labelField]]);
1141 } elseif ($val[$labelField]) {
1142 $out .= htmlspecialchars($prefixString . $val[$labelField]);
1143 } elseif ($useAltSelectLabels) {
1144 $out .= htmlspecialchars($prefixString . $altLabelFieldSelect[$val[$altLabelField]]);
1145 } else {
1146 $out .= htmlspecialchars($prefixString . $val[$altLabelField]);
1147 }
1148 }
1149 }
1150 }
1151 }
1152 }
1153 return $out;
1154 }
1155
1156 /**
1157 * Render table header
1158 *
1159 * @param array $row Table columns
1160 * @param array $conf Table TCA
1161 * @return string HTML of table header
1162 */
1163 public function resultRowTitles($row, $conf)
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 * @return string
1198 * @todo Unused?
1199 */
1200 public function csvRowTitles($row, $conf)
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 }