ea6dc5b8ec68a085bc1e9bd0aa95475b83259b48
[Packages/TYPO3.CMS.git] / typo3 / sysext / indexed_search / Classes / Domain / Repository / IndexSearchRepository.php
1 <?php
2 namespace TYPO3\CMS\IndexedSearch\Domain\Repository;
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\Driver\Statement;
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\QueryHelper;
21 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
22 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25 use TYPO3\CMS\IndexedSearch\Indexer;
26 use TYPO3\CMS\IndexedSearch\Utility;
27
28 /**
29 * Index search abstraction to search through the index
30 */
31 class IndexSearchRepository
32 {
33 /**
34 * Indexer object
35 *
36 * @var Indexer
37 */
38 protected $indexerObj;
39
40 /**
41 * External Parsers
42 *
43 * @var array
44 */
45 protected $externalParsers = [];
46
47 /**
48 * Frontend User Group List
49 *
50 * @var string
51 */
52 protected $frontendUserGroupList = '';
53
54 /**
55 * Sections
56 * formally known as $this->piVars['sections']
57 *
58 * @var string
59 */
60 protected $sections = null;
61
62 /**
63 * Search type
64 * formally known as $this->piVars['type']
65 *
66 * @var string
67 */
68 protected $searchType = null;
69
70 /**
71 * Language uid
72 * formally known as $this->piVars['lang']
73 *
74 * @var int
75 */
76 protected $languageUid = null;
77
78 /**
79 * Media type
80 * formally known as $this->piVars['media']
81 *
82 * @var int
83 */
84 protected $mediaType = null;
85
86 /**
87 * Sort order
88 * formally known as $this->piVars['sort_order']
89 *
90 * @var string
91 */
92 protected $sortOrder = null;
93
94 /**
95 * Descending sort order flag
96 * formally known as $this->piVars['desc']
97 *
98 * @var bool
99 */
100 protected $descendingSortOrderFlag = null;
101
102 /**
103 * Result page pointer
104 * formally known as $this->piVars['pointer']
105 *
106 * @var int
107 */
108 protected $resultpagePointer = 0;
109
110 /**
111 * Number of results
112 * formally known as $this->piVars['result']
113 *
114 * @var int
115 */
116 protected $numberOfResults = 10;
117
118 /**
119 * list of all root pages that will be used
120 * If this value is set to less than zero (eg. -1) searching will happen
121 * in ALL of the page tree with no regard to branches at all.
122 *
123 * @var string
124 */
125 protected $searchRootPageIdList;
126
127 /**
128 * formally known as $conf['search.']['searchSkipExtendToSubpagesChecking']
129 * enabled through settings.searchSkipExtendToSubpagesChecking
130 *
131 * @var bool
132 */
133 protected $joinPagesForQuery = false;
134
135 /**
136 * Select clauses for individual words, will be filled during the search
137 *
138 * @var array
139 */
140 protected $wSelClauses = [];
141
142 /**
143 * Flag for exact search count
144 * formally known as $conf['search.']['exactCount']
145 *
146 * Continue counting and checking of results even if we are sure
147 * they are not displayed in this request. This will slow down your
148 * page rendering, but it allows precise search result counters.
149 * enabled through settings.exactCount
150 *
151 * @var bool
152 */
153 protected $useExactCount = false;
154
155 /**
156 * Display forbidden records
157 * formally known as $this->conf['show.']['forbiddenRecords']
158 *
159 * enabled through settings.displayForbiddenRecords
160 *
161 * @var bool
162 */
163 protected $displayForbiddenRecords = false;
164
165 /**
166 * initialize all options that are necessary for the search
167 *
168 * @param array $settings the extbase plugin settings
169 * @param array $searchData the search data
170 * @param array $externalParsers
171 * @param string $searchRootPageIdList
172 * @return void
173 */
174 public function initialize($settings, $searchData, $externalParsers, $searchRootPageIdList)
175 {
176 // Initialize the indexer-class - just to use a few function (for making hashes)
177 $this->indexerObj = GeneralUtility::makeInstance(Indexer::class);
178 $this->externalParsers = $externalParsers;
179 $this->searchRootPageIdList = $searchRootPageIdList;
180 $this->frontendUserGroupList = $this->getTypoScriptFrontendController()->gr_list;
181 // Should we use joinPagesForQuery instead of long lists of uids?
182 if ($settings['searchSkipExtendToSubpagesChecking']) {
183 $this->joinPagesForQuery = 1;
184 }
185 if ($settings['exactCount']) {
186 $this->useExactCount = true;
187 }
188 if ($settings['displayForbiddenRecords']) {
189 $this->displayForbiddenRecords = true;
190 }
191 $this->sections = $searchData['sections'];
192 $this->searchType = $searchData['searchType'];
193 $this->languageUid = $searchData['languageUid'];
194 $this->mediaType = isset($searchData['mediaType']) ? $searchData['mediaType'] : false;
195 $this->sortOrder = $searchData['sortOrder'];
196 $this->descendingSortOrderFlag = $searchData['desc'];
197 $this->resultpagePointer = $searchData['pointer'];
198 if (isset($searchData['numberOfResults']) && is_numeric($searchData['numberOfResults'])) {
199 $this->numberOfResults = (int)$searchData['numberOfResults'];
200 }
201 }
202
203 /**
204 * Get search result rows / data from database. Returned as data in array.
205 *
206 * @param array $searchWords Search word array
207 * @param int $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
208 * @return bool|array FALSE if no result, otherwise an array with keys for first row, result rows and total number of results found.
209 */
210 public function doSearch($searchWords, $freeIndexUid = -1)
211 {
212 // unserializing the configuration so we can use it here:
213 $extConf = [];
214 if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'])) {
215 $extConf = unserialize(
216 $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search'],
217 ['allowed_classes' => false]
218 );
219 }
220
221 // Getting SQL result pointer:
222 $this->getTimeTracker()->push('Searching result');
223 if ($hookObj = &$this->hookRequest('getResultRows_SQLpointer')) {
224 $result = $hookObj->getResultRows_SQLpointer($searchWords, $freeIndexUid);
225 } elseif (isset($extConf['useMysqlFulltext']) && $extConf['useMysqlFulltext'] === '1') {
226 $result = $this->getResultRows_SQLpointerMysqlFulltext($searchWords, $freeIndexUid);
227 } else {
228 $result = $this->getResultRows_SQLpointer($searchWords, $freeIndexUid);
229 }
230 $this->getTimeTracker()->pull();
231 // Organize and process result:
232 if ($result) {
233 // Total search-result count
234 $count = $result->rowCount();
235 // The pointer is set to the result page that is currently being viewed
236 $pointer = MathUtility::forceIntegerInRange($this->resultpagePointer, 0, floor($count / $this->numberOfResults));
237 // Initialize result accumulation variables:
238 $c = 0;
239 // Result pointer: Counts up the position in the current search-result
240 $grouping_phashes = [];
241 // Used to filter out duplicates.
242 $grouping_chashes = [];
243 // Used to filter out duplicates BASED ON cHash.
244 $firstRow = [];
245 // Will hold the first row in result - used to calculate relative hit-ratings.
246 $resultRows = [];
247 // Will hold the results rows for display.
248 // Now, traverse result and put the rows to be displayed into an array
249 // Each row should contain the fields from 'ISEC.*, IP.*' combined
250 // + artificial fields "show_resume" (bool) and "result_number" (counter)
251 while ($row = $result->fetch()) {
252 // Set first row
253 if (!$c) {
254 $firstRow = $row;
255 }
256 // Tells whether we can link directly to a document
257 // or not (depends on possible right problems)
258 $row['show_resume'] = $this->checkResume($row);
259 $phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
260 $chashGr = !in_array(($row['contentHash'] . '.' . $row['data_page_id']), $grouping_chashes);
261 if ($phashGr && $chashGr) {
262 // Only if the resume may be shown are we going to filter out duplicates...
263 if ($row['show_resume'] || $this->displayForbiddenRecords) {
264 // Only on documents which are not multiple pages documents
265 if (!$this->multiplePagesType($row['item_type'])) {
266 $grouping_phashes[] = $row['phash_grouping'];
267 }
268 $grouping_chashes[] = $row['contentHash'] . '.' . $row['data_page_id'];
269 // Increase the result pointer
270 $c++;
271 // All rows for display is put into resultRows[]
272 if ($c > $pointer * $this->numberOfResults && $c <= $pointer * $this->numberOfResults + $this->numberOfResults) {
273 $row['result_number'] = $c;
274 $resultRows[] = $row;
275 // This may lead to a problem: If the result check is not stopped here, the search will take longer.
276 // However the result counter will not filter out grouped cHashes/pHashes that were not processed yet.
277 // You can change this behavior using the "search.exactCount" property (see above).
278 if (!$this->useExactCount && $c + 1 > ($pointer + 1) * $this->numberOfResults) {
279 break;
280 }
281 }
282 } else {
283 // Skip this row if the user cannot
284 // view it (missing permission)
285 $count--;
286 }
287 } else {
288 // For each time a phash_grouping document is found
289 // (which is thus not displayed) the search-result count is reduced,
290 // so that it matches the number of rows displayed.
291 $count--;
292 }
293 }
294
295 $result->closeCursor();
296
297 return [
298 'resultRows' => $resultRows,
299 'firstRow' => $firstRow,
300 'count' => $count
301 ];
302 } else {
303 // No results found
304 return false;
305 }
306 }
307
308 /**
309 * Gets a SQL result pointer to traverse for the search records.
310 *
311 * @param array $searchWords Search words
312 * @param int $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
313 * @return Statement
314 */
315 protected function getResultRows_SQLpointer($searchWords, $freeIndexUid = -1)
316 {
317 // This SEARCHES for the searchwords in $searchWords AND returns a
318 // COMPLETE list of phash-integers of the matches.
319 $list = $this->getPhashList($searchWords);
320 // Perform SQL Search / collection of result rows array:
321 if ($list) {
322 // Do the search:
323 $this->getTimeTracker()->push('execFinalQuery');
324 $res = $this->execFinalQuery($list, $freeIndexUid);
325 $this->getTimeTracker()->pull();
326 return $res;
327 } else {
328 return false;
329 }
330 }
331
332 /**
333 * Gets a SQL result pointer to traverse for the search records.
334 *
335 * mysql fulltext specific version triggered by ext_conf_template setting 'useMysqlFulltext'
336 *
337 * @param array $searchWordsArray Search words
338 * @param int $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
339 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
340 */
341 protected function getResultRows_SQLpointerMysqlFulltext($searchWordsArray, $freeIndexUid = -1)
342 {
343 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('index_fulltext');
344 if (strpos($connection->getServerVersion(), 'MySQL') !== 0) {
345 throw new \RuntimeException(
346 'Extension indexed_search is configured to use mysql fulltext, but table \'index_fulltext\''
347 . ' is running on a different DBMS.',
348 1472585525
349 );
350 }
351 // Build the search string, detect which fulltext index to use, and decide whether boolean search is needed or not
352 $searchData = $this->getSearchString($searchWordsArray);
353 // Perform SQL Search / collection of result rows array:
354 $resource = false;
355 if ($searchData) {
356 /** @var TimeTracker $timeTracker */
357 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
358 // Do the search:
359 $timeTracker->push('execFinalQuery');
360 $resource = $this->execFinalQuery_fulltext($searchData, $freeIndexUid);
361 $timeTracker->pull();
362 }
363 return $resource;
364 }
365
366 /**
367 * Returns a search string for use with MySQL FULLTEXT query
368 *
369 * mysql fulltext specific helper method
370 *
371 * @param array $searchWordArray Search word array
372 * @return string Search string
373 */
374 protected function getSearchString($searchWordArray)
375 {
376 // Initialize variables:
377 $count = 0;
378 // Change this to TRUE to force BOOLEAN SEARCH MODE (useful if fulltext index is still empty)
379 $searchBoolean = false;
380 $fulltextIndex = 'index_fulltext.fulltextdata';
381 // This holds the result if the search is natural (doesn't contain any boolean operators)
382 $naturalSearchString = '';
383 // This holds the result if the search is boolen (contains +/-/| operators)
384 $booleanSearchString = '';
385
386 $searchType = (string)$this->getSearchType();
387
388 // Traverse searchwords and prefix them with corresponding operator
389 foreach ($searchWordArray as $searchWordData) {
390 // Making the query for a single search word based on the search-type
391 $searchWord = $searchWordData['sword'];
392 $wildcard = '';
393 if (strstr($searchWord, ' ')) {
394 $searchType = '20';
395 }
396 switch ($searchType) {
397 case '1':
398 case '2':
399 case '3':
400 // First part of word
401 $wildcard = '*';
402 // Part-of-word search requires boolean mode!
403 $searchBoolean = true;
404 break;
405 case '10':
406 $indexerObj = GeneralUtility::makeInstance(Indexer::class);
407 // Initialize the indexer-class
408 /** @var Indexer $indexerObj */
409 $searchWord = $indexerObj->metaphone($searchWord, $indexerObj->storeMetaphoneInfoAsWords);
410 unset($indexerObj);
411 $fulltextIndex = 'index_fulltext.metaphonedata';
412 break;
413 case '20':
414 $searchBoolean = true;
415 // Remove existing quotes and fix misplaced quotes.
416 $searchWord = trim(str_replace('"', ' ', $searchWord));
417 break;
418 }
419 // Perform search for word:
420 switch ($searchWordData['oper']) {
421 case 'AND NOT':
422 $booleanSearchString .= ' -' . $searchWord . $wildcard;
423 $searchBoolean = true;
424 break;
425 case 'OR':
426 $booleanSearchString .= ' ' . $searchWord . $wildcard;
427 $searchBoolean = true;
428 break;
429 default:
430 $booleanSearchString .= ' +' . $searchWord . $wildcard;
431 $naturalSearchString .= ' ' . $searchWord;
432 }
433 $count++;
434 }
435 if ($searchType == '20') {
436 $searchString = '"' . trim($naturalSearchString) . '"';
437 } elseif ($searchBoolean) {
438 $searchString = trim($booleanSearchString);
439 } else {
440 $searchString = trim($naturalSearchString);
441 }
442 return [
443 'searchBoolean' => $searchBoolean,
444 'searchString' => $searchString,
445 'fulltextIndex' => $fulltextIndex
446 ];
447 }
448
449 /**
450 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
451 *
452 * mysql fulltext specific helper method
453 *
454 * @param array $searchData Array with search string, boolean indicator, and fulltext index reference
455 * @param int $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
456 * @return Statement
457 */
458 protected function execFinalQuery_fulltext($searchData, $freeIndexUid = -1)
459 {
460 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_fulltext');
461 $queryBuilder->getRestrictions()->removeAll();
462 $queryBuilder->select('index_fulltext.*', 'ISEC.*', 'IP.*')
463 ->from('index_fulltext')
464 ->join(
465 'index_fulltext',
466 'index_phash',
467 'IP',
468 $queryBuilder->expr()->eq('index_fulltext.phash', $queryBuilder->quoteIdentifier('IP.phash'))
469 )
470 ->join(
471 'IP',
472 'index_section',
473 'ISEC',
474 $queryBuilder->expr()->eq('IP.phash', $queryBuilder->quoteIdentifier('ISEC.phash'))
475 );
476
477 // Calling hook for alternative creation of page ID list
478 $searchRootPageIdList = $this->getSearchRootPageIdList();
479 if ($hookObj = &$this->hookRequest('execFinalQuery_idList')) {
480 $pageWhere = $hookObj->execFinalQuery_idList('');
481 $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($pageWhere));
482 } elseif ($this->getJoinPagesForQuery()) {
483 // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
484 $queryBuilder
485 ->join(
486 'ISEC',
487 'pages',
488 'pages',
489 $queryBuilder->expr()->eq('ISEC.page_id', $queryBuilder->quoteIdentifier('pages.uid'))
490 )
491 ->andWhere(
492 $queryBuilder->expr()->eq(
493 'pages.no_search',
494 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
495 )
496 )
497 ->andWhere(
498 $queryBuilder->expr()->lt(
499 'pages.doktype',
500 $queryBuilder->createNamedParameter(200, \PDO::PARAM_INT)
501 )
502 );
503 $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
504 } elseif ($searchRootPageIdList[0] >= 0) {
505 // Collecting all pages IDs in which to search;
506 // filtering out ALL pages that are not accessible due to enableFields. Does NOT look for "no_search" field!
507 $idList = [];
508 foreach ($searchRootPageIdList as $rootId) {
509 /** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj */
510 $cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
511 $idList[] = $cObj->getTreeList(-1 * $rootId, 9999);
512 }
513 $idList = GeneralUtility::intExplode(',', implode(',', $idList));
514 $queryBuilder->andWhere(
515 $queryBuilder->expr()->in(
516 'ISEC.page_id',
517 $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY)
518 )
519 );
520 }
521
522 $searchBoolean = '';
523 if ($searchData['searchBoolean']) {
524 $searchBoolean = ' IN BOOLEAN MODE';
525 }
526 $queryBuilder->andWhere(
527 'MATCH (' . $queryBuilder->quoteIdentifier($searchData['fulltextIndex']) . ')'
528 . ' AGAINST (' . $queryBuilder->createNamedParameter($searchData['searchString'])
529 . $searchBoolean
530 . ')'
531 );
532
533 $queryBuilder->andWhere(
534 QueryHelper::stripLogicalOperatorPrefix($this->mediaTypeWhere()),
535 QueryHelper::stripLogicalOperatorPrefix($this->languageWhere()),
536 QueryHelper::stripLogicalOperatorPrefix($this->freeIndexUidWhere($freeIndexUid)),
537 QueryHelper::stripLogicalOperatorPrefix($this->sectionTableWhere())
538 );
539
540 $queryBuilder->groupBy(
541 'IP.phash',
542 'ISEC.phash',
543 'ISEC.phash_t3',
544 'ISEC.rl0',
545 'ISEC.rl1',
546 'ISEC.rl2',
547 'ISEC.page_id',
548 'ISEC.uniqid',
549 'IP.phash_grouping',
550 'IP.data_filename',
551 'IP.data_page_id',
552 'IP.data_page_reg1',
553 'IP.data_page_type',
554 'IP.data_page_mp',
555 'IP.gr_list',
556 'IP.item_type',
557 'IP.item_title',
558 'IP.item_description',
559 'IP.item_mtime',
560 'IP.tstamp',
561 'IP.item_size',
562 'IP.contentHash',
563 'IP.crdate',
564 'IP.parsetime',
565 'IP.sys_language_uid',
566 'IP.item_crdate',
567 'IP.cHashParams',
568 'IP.externalUrl',
569 'IP.recordUid',
570 'IP.freeIndexUid',
571 'IP.freeIndexSetId'
572 );
573
574 return $queryBuilder->execute();
575 }
576
577 /***********************************
578 *
579 * Helper functions on searching (SQL)
580 *
581 ***********************************/
582 /**
583 * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the $searchWords array.
584 * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
585 *
586 * @param array $searchWords Search word array
587 * @return string List of integers
588 */
589 protected function getPhashList($searchWords)
590 {
591 // Initialize variables:
592 $c = 0;
593 // This array accumulates the phash-values
594 $totalHashList = [];
595 $this->wSelClauses = [];
596 // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
597 foreach ($searchWords as $k => $v) {
598 // Making the query for a single search word based on the search-type
599 $sWord = $v['sword'];
600 $theType = (string)$this->searchType;
601 // If there are spaces in the search-word, make a full text search instead.
602 if (strstr($sWord, ' ')) {
603 $theType = 20;
604 }
605 $this->getTimeTracker()->push('SearchWord "' . $sWord . '" - $theType=' . $theType);
606 // Perform search for word:
607 switch ($theType) {
608 case '1':
609 // Part of word
610 $res = $this->searchWord($sWord, Utility\LikeWildcard::BOTH);
611 break;
612 case '2':
613 // First part of word
614 $res = $this->searchWord($sWord, Utility\LikeWildcard::RIGHT);
615 break;
616 case '3':
617 // Last part of word
618 $res = $this->searchWord($sWord, Utility\LikeWildcard::LEFT);
619 break;
620 case '10':
621 // Sounds like
622 /**
623 * Indexer object
624 *
625 * @var Indexer
626 */
627 $indexerObj = GeneralUtility::makeInstance(Indexer::class);
628 // Perform metaphone search
629 $storeMetaphoneInfoAsWords = !$this->isTableUsed('index_words');
630 $res = $this->searchMetaphone($indexerObj->metaphone($sWord, $storeMetaphoneInfoAsWords));
631 unset($indexerObj);
632 break;
633 case '20':
634 // Sentence
635 $res = $this->searchSentence($sWord);
636 // If there is a fulltext search for a sentence there is
637 // a likeliness that sorting cannot be done by the rankings
638 // from the rel-table (because no relations will exist for the
639 // sentence in the word-table). So therefore mtime is used instead.
640 // It is not required, but otherwise some hits may be left out.
641 $this->sortOrder = 'mtime';
642 break;
643 default:
644 // Distinct word
645 $res = $this->searchDistinct($sWord);
646 }
647 // If there was a query to do, then select all phash-integers which resulted from this.
648 if ($res) {
649 // Get phash list by searching for it:
650 $phashList = [];
651 while ($row = $res->fetch()) {
652 $phashList[] = $row['phash'];
653 }
654 // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
655 if ($c) {
656 switch ($v['oper']) {
657 case 'OR':
658 $totalHashList = array_unique(array_merge($phashList, $totalHashList));
659 break;
660 case 'AND NOT':
661 $totalHashList = array_diff($totalHashList, $phashList);
662 break;
663 default:
664 // AND...
665 $totalHashList = array_intersect($totalHashList, $phashList);
666 }
667 } else {
668 // First search
669 $totalHashList = $phashList;
670 }
671 }
672 $this->getTimeTracker()->pull();
673 $c++;
674 }
675 return implode(',', $totalHashList);
676 }
677
678 /**
679 * Returns a query which selects the search-word from the word/rel tables.
680 *
681 * @param string $wordSel WHERE clause selecting the word from phash
682 * @param string $additionalWhereClause Additional AND clause in the end of the query.
683 * @return Statement
684 */
685 protected function execPHashListQuery($wordSel, $additionalWhereClause = '')
686 {
687 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_words');
688 $queryBuilder->select('IR.phash')
689 ->from('index_words', 'IW')
690 ->from('index_rel', 'IR')
691 ->from('index_section', 'ISEC')
692 ->where(
693 QueryHelper::stripLogicalOperatorPrefix($wordSel),
694 $queryBuilder->expr()->eq('IW.wid', $queryBuilder->quoteIdentifier('IR.wid')),
695 $queryBuilder->expr()->eq('ISEC.phash', $queryBuilder->quoteIdentifier('IR.phash')),
696 QueryHelper::stripLogicalOperatorPrefix($this->sectionTableWhere()),
697 QueryHelper::stripLogicalOperatorPrefix($additionalWhereClause)
698 )
699 ->groupBy('IR.phash');
700
701 return $queryBuilder->execute();
702 }
703
704 /**
705 * Search for a word
706 *
707 * @param string $sWord the search word
708 * @param int $wildcard Bit-field of Utility\LikeWildcard
709 * @return Statement
710 */
711 protected function searchWord($sWord, $wildcard)
712 {
713 $likeWildcard = Utility\LikeWildcard::cast($wildcard);
714 $wSel = $likeWildcard->getLikeQueryPart(
715 'index_words',
716 'IW.baseword',
717 $sWord
718 );
719 $this->wSelClauses[] = $wSel;
720 return $this->execPHashListQuery($wSel, ' AND is_stopword=0');
721 }
722
723 /**
724 * Search for one distinct word
725 *
726 * @param string $sWord the search word
727 * @return Statement
728 */
729 protected function searchDistinct($sWord)
730 {
731 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
732 ->getQueryBuilderForTable('index_words')
733 ->expr();
734 $wSel = $expressionBuilder->eq('IW.wid', $this->md5inthash($sWord));
735 $this->wSelClauses[] = $wSel;
736 return $this->execPHashListQuery($wSel, $expressionBuilder->eq('is_stopword', 0));
737 }
738
739 /**
740 * Search for a sentence
741 *
742 * @param string $sWord the search word
743 * @return Statement
744 */
745 protected function searchSentence($sWord)
746 {
747 $this->wSelClauses[] = '1=1';
748 $likeWildcard = Utility\LikeWildcard::cast(Utility\LikeWildcard::BOTH);
749 $likePart = $likeWildcard->getLikeQueryPart(
750 'index_fulltext',
751 'IFT.fulltextdata',
752 $sWord
753 );
754
755 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_section');
756 return $queryBuilder->select('ISEC.phash')
757 ->from('index_section', 'ISEC')
758 ->from('index_fulltext', 'IFT')
759 ->where(
760 QueryHelper::stripLogicalOperatorPrefix($likePart),
761 $queryBuilder->expr()->eq('ISEC.phash', $queryBuilder->quoteIdentifier(('IFT.phash'))),
762 QueryHelper::stripLogicalOperatorPrefix($this->sectionTableWhere())
763 )
764 ->groupBy('ISEC.phash')
765 ->execute();
766 }
767
768 /**
769 * Search for a metaphone word
770 *
771 * @param string $sWord the search word
772 * @return Statement
773 */
774 protected function searchMetaphone($sWord)
775 {
776 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
777 ->getQueryBuilderForTable('index_words')
778 ->expr();
779 $wSel = $expressionBuilder->eq('IW.metaphone', $expressionBuilder->literal($sWord));
780 $this->wSelClauses[] = $wSel;
781 return $this->execPHashListQuery($wSel, $expressionBuilder->eq('is_stopword', 0));
782 }
783
784 /**
785 * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
786 *
787 * @return string AND clause for selection of section in database.
788 */
789 public function sectionTableWhere()
790 {
791 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
792 ->getQueryBuilderForTable('index_section')
793 ->expr();
794
795 $whereClause = $expressionBuilder->andX();
796 $match = false;
797 if (!($this->searchRootPageIdList < 0)) {
798 $whereClause->add(
799 $expressionBuilder->in('ISEC.rl0', GeneralUtility::intExplode(',', $this->searchRootPageIdList, true))
800 );
801 }
802 if (substr($this->sections, 0, 4) == 'rl1_') {
803 $whereClause->add(
804 $expressionBuilder->in('ISEC.rl1', GeneralUtility::intExplode(',', substr($this->sections, 4)))
805 );
806 $match = true;
807 } elseif (substr($this->sections, 0, 4) == 'rl2_') {
808 $whereClause->add(
809 $expressionBuilder->in('ISEC.rl2', GeneralUtility::intExplode(',', substr($this->sections, 4)))
810 );
811 $match = true;
812 } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
813 // Traversing user configured fields to see if any of those are used to limit search to a section:
814 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
815 if (substr($this->sections, 0, strlen($fieldName) + 1) == $fieldName . '_') {
816 $whereClause->add(
817 $expressionBuilder->in(
818 'ISEC.' . $fieldName,
819 GeneralUtility::intExplode(',', substr($this->sections, strlen($fieldName) + 1))
820 )
821 );
822 $match = true;
823 break;
824 }
825 }
826 }
827 // If no match above, test the static types:
828 if (!$match) {
829 switch ((string)$this->sections) {
830 case '-1':
831 $whereClause->add(
832 $expressionBuilder->eq('ISEC.page_id', (int)$this->getTypoScriptFrontendController()->id)
833 );
834 break;
835 case '-2':
836 $whereClause->add($expressionBuilder->eq('ISEC.rl2', 0));
837 break;
838 case '-3':
839 $whereClause->add($expressionBuilder->gt('ISEC.rl2', 0));
840 break;
841 }
842 }
843
844 return $whereClause->count() ? ' AND ' . $whereClause : '';
845 }
846
847 /**
848 * Returns AND statement for selection of media type
849 *
850 * @return string AND statement for selection of media type
851 */
852 public function mediaTypeWhere()
853 {
854 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
855 ->getQueryBuilderForTable('index_phash')
856 ->expr();
857 switch ($this->mediaType) {
858 case '0':
859 // '0' => 'only TYPO3 pages',
860 $whereClause = $expressionBuilder->eq('IP.item_type', $expressionBuilder->literal('0'));
861 break;
862 case '-2':
863 // All external documents
864 $whereClause = $expressionBuilder->neq('IP.item_type', $expressionBuilder->literal('0'));
865 break;
866 case false:
867 // Intentional fall-through
868 case '-1':
869 // All content
870 $whereClause = '';
871 break;
872 default:
873 $whereClause = $expressionBuilder->eq('IP.item_type', $expressionBuilder->literal($this->mediaType));
874 }
875 return $whereClause ? ' AND ' . $whereClause : '';
876 }
877
878 /**
879 * Returns AND statement for selection of language
880 *
881 * @return string AND statement for selection of language
882 */
883 public function languageWhere()
884 {
885 // -1 is the same as ALL language.
886 if ($this->languageUid < 0) {
887 return '';
888 }
889
890 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
891 ->getQueryBuilderForTable('index_phash')
892 ->expr();
893
894 return ' AND ' . $expressionBuilder->eq('IP.sys_language_uid', (int)$this->languageUid);
895 }
896
897 /**
898 * Where-clause for free index-uid value.
899 *
900 * @param int $freeIndexUid Free Index UID value to limit search to.
901 * @return string WHERE SQL clause part.
902 */
903 public function freeIndexUidWhere($freeIndexUid)
904 {
905 $freeIndexUid = (int)$freeIndexUid;
906 if ($freeIndexUid < 0) {
907 return '';
908 }
909 // First, look if the freeIndexUid is a meta configuration:
910 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
911 ->getQueryBuilderForTable('index_config');
912 $queryBuilder->getRestrictions()->removeAll();
913 $indexCfgRec = $queryBuilder->select('indexcfgs')
914 ->from('index_config')
915 ->where(
916 $queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(5, \PDO::PARAM_INT)),
917 $queryBuilder->expr()->eq(
918 'uid',
919 $queryBuilder->createNamedParameter($freeIndexUid, \PDO::PARAM_INT)
920 ),
921 QueryHelper::stripLogicalOperatorPrefix($this->enableFields('index_config'))
922 )
923 ->execute()
924 ->fetch();
925
926 if (is_array($indexCfgRec)) {
927 $refs = GeneralUtility::trimExplode(',', $indexCfgRec['indexcfgs']);
928 // Default value to protect against empty array.
929 $list = [-99];
930 foreach ($refs as $ref) {
931 list($table, $uid) = GeneralUtility::revExplode('_', $ref, 2);
932 $uid = (int)$uid;
933 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
934 ->getQueryBuilderForTable('index_config');
935 $queryBuilder->getRestrictions()->removeAll();
936 $queryBuilder->select('uid')
937 ->from('index_config')
938 ->where(QueryHelper::stripLogicalOperatorPrefix($this->enableFields('index_config')));
939 switch ($table) {
940 case 'index_config':
941 $idxRec = $queryBuilder
942 ->andWhere(
943 $queryBuilder->expr()->eq(
944 'uid',
945 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
946 )
947 )
948 ->execute()
949 ->fetch();
950 if ($idxRec) {
951 $list[] = $uid;
952 }
953 break;
954 case 'pages':
955 $indexCfgRecordsFromPid = $queryBuilder
956 ->andWhere(
957 $queryBuilder->expr()->eq(
958 'pid',
959 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
960 )
961 )
962 ->execute();
963 while ($idxRec = $indexCfgRecordsFromPid->fetch()) {
964 $list[] = $idxRec['uid'];
965 }
966 break;
967 }
968 }
969 $list = array_unique($list);
970 } else {
971 $list = [$freeIndexUid];
972 }
973
974 $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
975 ->getQueryBuilderForTable('index_phash')
976 ->expr();
977 return ' AND ' . $expressionBuilder->in('IP.freeIndexUid', array_map('intval', $list));
978 }
979
980 /**
981 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
982 *
983 * @param string $list List of phash integers which match the search.
984 * @param int $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
985 * @return Statement
986 */
987 protected function execFinalQuery($list, $freeIndexUid = -1)
988 {
989 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('index_words');
990 $queryBuilder->select('ISEC.*', 'IP.*')
991 ->from('index_phash', 'IP')
992 ->from('index_section', 'ISEC')
993 ->where(
994 $queryBuilder->expr()->in(
995 'IP.phash',
996 $queryBuilder->createNamedParameter(
997 GeneralUtility::intExplode(',', $list, true),
998 Connection::PARAM_INT_ARRAY
999 )
1000 ),
1001 QueryHelper::stripLogicalOperatorPrefix($this->mediaTypeWhere()),
1002 QueryHelper::stripLogicalOperatorPrefix($this->languageWhere()),
1003 QueryHelper::stripLogicalOperatorPrefix($this->freeIndexUidWhere($freeIndexUid)),
1004 $queryBuilder->expr()->eq('ISEC.phash', $queryBuilder->quoteIdentifier('IP.phash'))
1005 )
1006 ->groupBy(
1007 'IP.phash',
1008 'ISEC.phash',
1009 'ISEC.phash_t3',
1010 'ISEC.rl0',
1011 'ISEC.rl1',
1012 'ISEC.rl2',
1013 'ISEC.page_id',
1014 'ISEC.uniqid',
1015 'IP.phash_grouping',
1016 'IP.data_filename',
1017 'IP.data_page_id',
1018 'IP.data_page_reg1',
1019 'IP.data_page_type',
1020 'IP.data_page_mp',
1021 'IP.gr_list',
1022 'IP.item_type',
1023 'IP.item_title',
1024 'IP.item_description',
1025 'IP.item_mtime',
1026 'IP.tstamp',
1027 'IP.item_size',
1028 'IP.contentHash',
1029 'IP.crdate',
1030 'IP.parsetime',
1031 'IP.sys_language_uid',
1032 'IP.item_crdate',
1033 'IP.cHashParams',
1034 'IP.externalUrl',
1035 'IP.recordUid',
1036 'IP.freeIndexUid',
1037 'IP.freeIndexSetId'
1038 );
1039
1040 // Setting up methods of filtering results
1041 // based on page types, access, etc.
1042 if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
1043 // Calling hook for alternative creation of page ID list
1044 $hookWhere = QueryHelper::stripLogicalOperatorPrefix($hookObj->execFinalQuery_idList($list));
1045 if (!empty($hookWhere)) {
1046 $queryBuilder->andWhere($hookWhere);
1047 }
1048 } elseif ($this->joinPagesForQuery) {
1049 // Alternative to getting all page ids by ->getTreeList() where
1050 // "excludeSubpages" is NOT respected.
1051 $queryBuilder->getRestrictions()->removeAll();
1052 $queryBuilder->from('pages');
1053 $queryBuilder->andWhere(
1054 $queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('ISEC.page_id')),
1055 QueryHelper::stripLogicalOperatorPrefix($this->enableFields('pages')),
1056 $queryBuilder->expr()->eq(
1057 'pages.no_search',
1058 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1059 ),
1060 $queryBuilder->expr()->lt(
1061 'pages.doktype',
1062 $queryBuilder->createNamedParameter(200, \PDO::PARAM_INT)
1063 )
1064 );
1065 } elseif ($this->searchRootPageIdList >= 0) {
1066 // Collecting all pages IDs in which to search;
1067 // filtering out ALL pages that are not accessible due to enableFields.
1068 // Does NOT look for "no_search" field!
1069 $siteIdNumbers = GeneralUtility::intExplode(',', $this->searchRootPageIdList);
1070 $pageIdList = [];
1071 foreach ($siteIdNumbers as $rootId) {
1072 $pageIdList[] = $this->getTypoScriptFrontendController()->cObj->getTreeList(-1 * $rootId, 9999);
1073 }
1074 $queryBuilder->andWhere(
1075 $queryBuilder->expr()->in(
1076 'ISEC.page_id',
1077 $queryBuilder->createNamedParameter(
1078 array_unique(GeneralUtility::intExplode(',', implode(',', $pageIdList), true)),
1079 Connection::PARAM_INT_ARRAY
1080 )
1081 )
1082 );
1083 }
1084 // otherwise select all / disable everything
1085 // If any of the ranking sortings are selected, we must make a
1086 // join with the word/rel-table again, because we need to
1087 // calculate ranking based on all search-words found.
1088 if (substr($this->sortOrder, 0, 5) === 'rank_') {
1089 $queryBuilder
1090 ->from('index_words', 'IW')
1091 ->from('index_rel', 'IR')
1092 ->andWhere(
1093 $queryBuilder->expr()->eq('IW.wid', $queryBuilder->quoteIdentifier('IR.wid')),
1094 $queryBuilder->expr()->eq('ISEC.phash', $queryBuilder->quoteIdentifier('IR.phash'))
1095 );
1096 switch ($this->sortOrder) {
1097 case 'rank_flag':
1098 // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1099 // The ordering is refined with the frequency sum as well.
1100 $queryBuilder
1101 ->addSelectLiteral(
1102 $queryBuilder->expr()->max('IR.flags', 'order_val1'),
1103 $queryBuilder->expr()->sum('IR.freq', 'order_val2')
1104 )
1105 ->orderBy('order_val1', $this->getDescendingSortOrderFlag())
1106 ->addOrderBy('order_val2', $this->getDescendingSortOrderFlag());
1107 break;
1108 case 'rank_first':
1109 // Results in average position of search words on page.
1110 // Must be inversely sorted (low numbers are closer to top)
1111 $queryBuilder
1112 ->addSelectLiteral($queryBuilder->expr()->avg('IR.first', 'order_val'))
1113 ->orderBy('order_val', $this->getDescendingSortOrderFlag(true));
1114 break;
1115 case 'rank_count':
1116 // Number of words found
1117 $queryBuilder
1118 ->addSelectLiteral($queryBuilder->expr()->sum('IR.count', 'order_val'))
1119 ->orderBy('order_val', $this->getDescendingSortOrderFlag());
1120 break;
1121 default:
1122 // Frequency sum. I'm not sure if this is the best way to do
1123 // it (make a sum...). Or should it be the average?
1124 $queryBuilder
1125 ->addSelectLiteral($queryBuilder->expr()->sum('IR.freq', 'order_val'))
1126 ->orderBy('order_val', $this->getDescendingSortOrderFlag());
1127 }
1128
1129 if (!empty($this->wSelClauses)) {
1130 // So, words are combined in an OR statement
1131 // (no "sentence search" should be done here - may deselect results)
1132 $wordSel = $queryBuilder->expr()->orX();
1133 foreach ($this->wSelClauses as $wSelClause) {
1134 $wordSel->add(QueryHelper::stripLogicalOperatorPrefix($wSelClause));
1135 }
1136 $queryBuilder->andWhere($wordSel);
1137 }
1138 } else {
1139 // Otherwise, if sorting are done with the pages table or other fields,
1140 // there is no need for joining with the rel/word tables:
1141 switch ((string)$this->sortOrder) {
1142 case 'title':
1143 $queryBuilder->orderBy('IP.item_title', $this->getDescendingSortOrderFlag());
1144 break;
1145 case 'crdate':
1146 $queryBuilder->orderBy('IP.item_crdate', $this->getDescendingSortOrderFlag());
1147 break;
1148 case 'mtime':
1149 $queryBuilder->orderBy('IP.item_mtime', $this->getDescendingSortOrderFlag());
1150 break;
1151 }
1152 }
1153
1154 return $queryBuilder->execute();
1155 }
1156
1157 /**
1158 * Checking if the resume can be shown for the search result
1159 * (depending on whether the rights are OK)
1160 * ? Should it also check for gr_list "0,-1"?
1161 *
1162 * @param array $row Result row array.
1163 * @return bool Returns TRUE if resume can safely be shown
1164 */
1165 protected function checkResume($row)
1166 {
1167 // If the record is indexed by an indexing configuration, just show it.
1168 // At least this is needed for external URLs and files.
1169 // For records we might need to extend this - for instance block display if record is access restricted.
1170 if ($row['freeIndexUid']) {
1171 return true;
1172 }
1173 // Evaluate regularly indexed pages based on item_type:
1174 // External media:
1175 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('index_grlist');
1176 if ($row['item_type']) {
1177 // For external media we will check the access of the parent page on which the media was linked from.
1178 // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents
1179 // in this section. So, selecting for the grlist records belonging to the parent phash-row where the
1180 // current users gr_list exists will help us to know. If this is NOT found, there is still a theoretical
1181 // possibility that another user accessible page would display a link, so maybe the resume of such a
1182 // document here may be unjustified hidden. But better safe than sorry.
1183 if (!$this->isTableUsed('index_grlist')) {
1184 return false;
1185 }
1186
1187 return (bool)$connection->count(
1188 'phash',
1189 'index_grlist',
1190 [
1191 'phash' => (int)$row['phash_t3'],
1192 'gr_list' => $this->frontendUserGroupList
1193 ]
1194 );
1195 } else {
1196 // Ordinary TYPO3 pages:
1197 if ((string)$row['gr_list'] !== (string)$this->frontendUserGroupList) {
1198 // Selecting for the grlist records belonging to the phash-row where the current users gr_list exists.
1199 // If it is found it is proof that this user has direct access to the phash-rows content although
1200 // he did not himself initiate the indexing...
1201 if (!$this->isTableUsed('index_grlist')) {
1202 return false;
1203 }
1204
1205 return (bool)$connection->count(
1206 'phash',
1207 'index_grlist',
1208 [
1209 'phash' => (int)$row['phash'],
1210 'gr_list' => $this->frontendUserGroupList
1211 ]
1212 );
1213 } else {
1214 return true;
1215 }
1216 }
1217 }
1218
1219 /**
1220 * Returns "DESC" or "" depending on the settings of the incoming
1221 * highest/lowest result order (piVars['desc'])
1222 *
1223 * @param bool $inverse If TRUE, inverse the order which is defined by piVars['desc']
1224 * @return string " DESC" or
1225 * @formallyknownas tx_indexedsearch_pi->isDescending
1226 */
1227 protected function getDescendingSortOrderFlag($inverse = false)
1228 {
1229 $desc = $this->descendingSortOrderFlag;
1230 if ($inverse) {
1231 $desc = !$desc;
1232 }
1233 return !$desc ? ' DESC' : '';
1234 }
1235
1236 /**
1237 * Returns a part of a WHERE clause which will filter out records with start/end times or hidden/fe_groups fields
1238 * set to values that should de-select them according to the current time, preview settings or user login.
1239 * Definitely a frontend function.
1240 * THIS IS A VERY IMPORTANT FUNCTION: Basically you must add the output from this function for EVERY select query you create
1241 * for selecting records of tables in your own applications - thus they will always be filtered according to the "enablefields"
1242 * configured in TCA
1243 * Simply calls \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() BUT will send the show_hidden flag along!
1244 * This means this function will work in conjunction with the preview facilities of the frontend engine/Admin Panel.
1245 *
1246 * @param string $table The table for which to get the where clause
1247 * @return string The part of the where clause on the form " AND [fieldname]=0 AND ...". Eg. " AND hidden=0 AND starttime < 123345567
1248 * @see \TYPO3\CMS\Frontend\Page\PageRepository::enableFields()
1249 */
1250 protected function enableFields($table)
1251 {
1252 return $this->getTypoScriptFrontendController()->sys_page->enableFields($table, $table === 'pages' ? $this->getTypoScriptFrontendController()->showHiddenPage : $this->getTypoScriptFrontendController()->showHiddenRecords);
1253 }
1254
1255 /**
1256 * Returns if an item type is a multipage item type
1257 *
1258 * @param string $itemType Item type
1259 * @return bool TRUE if multipage capable
1260 */
1261 protected function multiplePagesType($itemType)
1262 {
1263 /** @var \TYPO3\CMS\IndexedSearch\FileContentParser $fileContentParser */
1264 $fileContentParser = $this->externalParsers[$itemType];
1265 return is_object($fileContentParser) && $fileContentParser->isMultiplePageExtension($itemType);
1266 }
1267
1268 /**
1269 * md5 integer hash
1270 * Using 7 instead of 8 just because that makes the integers lower than
1271 * 32 bit (28 bit) and so they do not interfere with UNSIGNED integers
1272 * or PHP-versions which has varying output from the hexdec function.
1273 *
1274 * @param string $str String to hash
1275 * @return int Integer intepretation of the md5 hash of input string.
1276 */
1277 protected function md5inthash($str)
1278 {
1279 return Utility\IndexedSearchUtility::md5inthash($str);
1280 }
1281
1282 /**
1283 * Check if the tables provided are configured for usage.
1284 * This becomes necessary for extensions that provide additional database
1285 * functionality like indexed_search_mysql.
1286 *
1287 * @param string $table_list Comma-separated list of tables
1288 * @return bool TRUE if given tables are enabled
1289 */
1290 protected function isTableUsed($table_list)
1291 {
1292 return Utility\IndexedSearchUtility::isTableUsed($table_list);
1293 }
1294
1295 /**
1296 * Returns an object reference to the hook object if any
1297 *
1298 * @param string $functionName Name of the function you want to call / hook key
1299 * @return object|NULL Hook object, if any. Otherwise NULL.
1300 */
1301 public function hookRequest($functionName)
1302 {
1303 // Hook: menuConfig_preProcessModMenu
1304 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
1305 $hookObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
1306 if (method_exists($hookObj, $functionName)) {
1307 $hookObj->pObj = $this;
1308 return $hookObj;
1309 }
1310 }
1311 return null;
1312 }
1313
1314 /**
1315 * Search type
1316 * e.g. sentence (20), any part of the word (1)
1317 *
1318 * @return int
1319 */
1320 public function getSearchType()
1321 {
1322 return (int)$this->searchType;
1323 }
1324
1325 /**
1326 * A list of integer which should be root-pages to search from
1327 *
1328 * @return int[]
1329 */
1330 public function getSearchRootPageIdList()
1331 {
1332 return GeneralUtility::intExplode(',', $this->searchRootPageIdList);
1333 }
1334
1335 /**
1336 * Getter for joinPagesForQuery flag
1337 * enabled through TypoScript 'settings.skipExtendToSubpagesChecking'
1338 *
1339 * @return bool
1340 */
1341 public function getJoinPagesForQuery()
1342 {
1343 return $this->joinPagesForQuery;
1344 }
1345
1346 /**
1347 * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
1348 */
1349 protected function getTypoScriptFrontendController()
1350 {
1351 return $GLOBALS['TSFE'];
1352 }
1353
1354 /**
1355 * @return TimeTracker
1356 */
1357 protected function getTimeTracker()
1358 {
1359 return GeneralUtility::makeInstance(TimeTracker::class);
1360 }
1361 }