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