[TASK] Re-work/simplify copyright header in PHP files - Part 8
[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 * Index search abstraction to search through the index
18 *
19 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
20 * @author Christian Jul Jensen <christian@typo3.com>
21 * @author Benjamin Mack <benni@typo3.org>
22 */
23 class IndexSearchRepository {
24
25 /**
26 * Indexer object
27 *
28 * @var \TYPO3\CMS\IndexedSearch\Indexer
29 */
30 protected $indexerObj;
31
32 protected $externalParsers = array();
33
34 protected $frontendUserGroupList = '';
35
36 // formally known as $this->piVars['sections']
37 protected $sections = NULL;
38
39 // formally known as $this->piVars['type']
40 protected $searchType = NULL;
41
42 // formally known as $this->piVars['lang']
43 protected $languageUid = NULL;
44
45 // formally known as $this->piVars['media']
46 protected $mediaType = NULL;
47
48 // formally known as $this->piVars['sort_order']
49 protected $sortOrder = NULL;
50
51 // formally known as $this->piVars['desc']
52 protected $descendingSortOrderFlag = NULL;
53
54 // formally known as $this->piVars['pointer']
55 protected $resultpagePointer = 0;
56
57 // formally known as $this->piVars['result']
58 protected $numberOfResults = 10;
59
60 // list of all root pages that will be used
61 protected $searchRootPageIdList;
62
63 // formally known as $conf['search.']['searchSkipExtendToSubpagesChecking']
64 // enabled through settings.searchSkipExtendToSubpagesChecking
65 protected $joinPagesForQuery = FALSE;
66
67 // Select clauses for individual words,
68 // will be filled during the search
69 protected $wSelClauses = array();
70
71 // formally known as $conf['search.']['exactCount']
72 // Continue counting and checking of results even if we are sure
73 // they are not displayed in this request. This will slow down your
74 // page rendering, but it allows precise search result counters.
75 // enabled through settings.exactCount
76 protected $useExactCount = FALSE;
77
78 // formally known as $this->conf['show.']['forbiddenRecords']
79 // enabled through settings.displayForbiddenRecords
80 protected $displayForbiddenRecords = FALSE;
81
82 // constants to help where to use wildcards in SQL like queries
83 const WILDCARD_LEFT = 1;
84 const WILDCARD_RIGHT = 2;
85 /**
86 * initialize all options that are necessary for the search
87 *
88 * @param array $settings the extbase plugin settings
89 * @param array $searchData the search data
90 * @param array $externalParsers
91 * @param mixed $searchRootPageIdList
92 * @return void
93 */
94 public function initialize($settings, $searchData, $externalParsers, $searchRootPageIdList) {
95 // Initialize the indexer-class - just to use a few function (for making hashes)
96 $this->indexerObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\IndexedSearch\\Indexer');
97 $this->externalParsers = $externalParsers;
98 $this->searchRootPageIdList = $searchRootPageIdList;
99 $this->frontendUserGroupList = $GLOBALS['TSFE']->gr_list;
100 // Should we use joinPagesForQuery instead of long lists of uids?
101 if ($settings['searchSkipExtendToSubpagesChecking']) {
102 $this->joinPagesForQuery = 1;
103 }
104 if ($settings['exactCount']) {
105 $this->useExactCount = TRUE;
106 }
107 if ($settings['displayForbiddenRecords']) {
108 $this->displayForbiddenRecords = TRUE;
109 }
110 $this->sections = $searchData['sections'];
111 $this->searchType = $searchData['searchType'];
112 $this->languageUid = $searchData['languageUid'];
113 $this->mediaType = isset($searchData['mediaType']) ? $searchData['mediaType'] : FALSE;
114 $this->sortOrder = $searchData['sortOrder'];
115 $this->descendingSortOrderFlag = $searchData['desc'];
116 $this->resultpagePointer = $searchData['pointer'];
117 if (isset($searchData['numberOfResults']) && is_numeric($searchData['numberOfResults'])) {
118 $this->numberOfResults = (int)$searchData['numberOfResults'];
119 }
120 }
121
122 /**
123 * Get search result rows / data from database. Returned as data in array.
124 *
125 * @param array $searchWords Search word array
126 * @param integer $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
127 * @return boolean|array FALSE if no result, otherwise an array with keys for first row, result rows and total number of results found.
128 */
129 public function doSearch($searchWords, $freeIndexUid = -1) {
130 // Getting SQL result pointer:
131 $GLOBALS['TT']->push('Searching result');
132 if ($hookObj = &$this->hookRequest('getResultRows_SQLpointer')) {
133 $res = $hookObj->getResultRows_SQLpointer($searchWords, $freeIndexUid);
134 } else {
135 $res = $this->getResultRows_SQLpointer($searchWords, $freeIndexUid);
136 }
137 $GLOBALS['TT']->pull();
138 // Organize and process result:
139 if ($res) {
140 // Total search-result count
141 $count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
142 // The pointer is set to the result page that is currently being viewed
143 $pointer = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->resultpagePointer, 0, floor($count / $this->numberOfResults));
144 // Initialize result accumulation variables:
145 $c = 0;
146 // Result pointer: Counts up the position in the current search-result
147 $grouping_phashes = array();
148 // Used to filter out duplicates.
149 $grouping_chashes = array();
150 // Used to filter out duplicates BASED ON cHash.
151 $firstRow = array();
152 // Will hold the first row in result - used to calculate relative hit-ratings.
153 $resultRows = array();
154 // Will hold the results rows for display.
155 // Now, traverse result and put the rows to be displayed into an array
156 // Each row should contain the fields from 'ISEC.*, IP.*' combined
157 // + artificial fields "show_resume" (boolean) and "result_number" (counter)
158 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
159 // Set first row
160 if (!$c) {
161 $firstRow = $row;
162 }
163 // Tells whether we can link directly to a document
164 // or not (depends on possible right problems)
165 $row['show_resume'] = $this->checkResume($row);
166 $phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
167 $chashGr = !in_array(($row['contentHash'] . '.' . $row['data_page_id']), $grouping_chashes);
168 if ($phashGr && $chashGr) {
169 // Only if the resume may be shown are we going to filter out duplicates...
170 if ($row['show_resume'] || $this->displayForbiddenRecords) {
171 // Only on documents which are not multiple pages documents
172 if (!$this->multiplePagesType($row['item_type'])) {
173 $grouping_phashes[] = $row['phash_grouping'];
174 }
175 $grouping_chashes[] = $row['contentHash'] . '.' . $row['data_page_id'];
176 // Increase the result pointer
177 $c++;
178 // All rows for display is put into resultRows[]
179 if ($c > $pointer * $this->numberOfResults && $c <= $pointer * $this->numberOfResults + $this->numberOfResults) {
180 $row['result_number'] = $c;
181 $resultRows[] = $row;
182 // This may lead to a problem: If the result check is not stopped here, the search will take longer.
183 // However the result counter will not filter out grouped cHashes/pHashes that were not processed yet.
184 // You can change this behavior using the "search.exactCount" property (see above).
185 if (!$this->useExactCount && $c + 1 > ($pointer + 1) * $this->numberOfResults) {
186 break;
187 }
188 }
189 } else {
190 // Skip this row if the user cannot
191 // view it (missing permission)
192 $count--;
193 }
194 } else {
195 // For each time a phash_grouping document is found
196 // (which is thus not displayed) the search-result count is reduced,
197 // so that it matches the number of rows displayed.
198 $count--;
199 }
200 }
201 return array(
202 'resultRows' => $resultRows,
203 'firstRow' => $firstRow,
204 'count' => $count
205 );
206 } else {
207 // No results found
208 return FALSE;
209 }
210 }
211
212 /**
213 * Gets a SQL result pointer to traverse for the search records.
214 *
215 * @param array $searchWords Search words
216 * @param integer $freeIndexUid Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
217 * @return boolean|pointer
218 */
219 protected function getResultRows_SQLpointer($searchWords, $freeIndexUid = -1) {
220 // This SEARCHES for the searchwords in $searchWords AND returns a
221 // COMPLETE list of phash-integers of the matches.
222 $list = $this->getPhashList($searchWords);
223 // Perform SQL Search / collection of result rows array:
224 if ($list) {
225 // Do the search:
226 $GLOBALS['TT']->push('execFinalQuery');
227 $res = $this->execFinalQuery($list, $freeIndexUid);
228 $GLOBALS['TT']->pull();
229 return $res;
230 } else {
231 return FALSE;
232 }
233 }
234
235 /***********************************
236 *
237 * Helper functions on searching (SQL)
238 *
239 ***********************************/
240 /**
241 * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the $searchWords array.
242 * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
243 *
244 * @param array $searchWords Search word array
245 * @return string List of integers
246 */
247 protected function getPhashList($searchWords) {
248 // Initialize variables:
249 $c = 0;
250 // This array accumulates the phash-values
251 $totalHashList = array();
252 // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
253 foreach ($searchWords as $k => $v) {
254 // Making the query for a single search word based on the search-type
255 $sWord = $v['sword'];
256 $theType = (string) $this->searchType;
257 // If there are spaces in the search-word, make a full text search instead.
258 if (strstr($sWord, ' ')) {
259 $theType = 20;
260 }
261 $GLOBALS['TT']->push('SearchWord "' . $sWord . '" - $theType=' . $theType);
262 $res = '';
263 $wSel = '';
264 // Perform search for word:
265 switch ($theType) {
266 case '1':
267 // Part of word
268 $res = $this->searchWord($sWord, self::WILDCARD_LEFT | self::WILDCARD_RIGHT);
269 break;
270 case '2':
271 // First part of word
272 $res = $this->searchWord($sWord, self::WILDCARD_RIGHT);
273 break;
274 case '3':
275 // Last part of word
276 $res = $this->searchWord($sWord, self::WILDCARD_LEFT);
277 break;
278 case '10':
279 // Sounds like
280 /**
281 * Indexer object
282 *
283 * @var \TYPO3\CMS\IndexedSearch\Indexer
284 */
285 $indexerObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\IndexedSearch\\Indexer');
286 // Perform metaphone search
287 $storeMetaphoneInfoAsWords = !$this->isTableUsed('index_words');
288 $res = $this->searchMetaphone($indexerObj->metaphone($sWord, $storeMetaphoneInfoAsWords));
289 unset($indexerObj);
290 break;
291 case '20':
292 // Sentence
293 $res = $this->searchSentence($sWord);
294 // If there is a fulltext search for a sentence there is
295 // a likeliness that sorting cannot be done by the rankings
296 // from the rel-table (because no relations will exist for the
297 // sentence in the word-table). So therefore mtime is used instead.
298 // It is not required, but otherwise some hits may be left out.
299 $this->sortOrder = 'mtime';
300 break;
301 default:
302 // Distinct word
303 $res = $this->searchDistinct($sWord);
304 }
305 // Accumulate the word-select clauses
306 $this->wSelClauses[] = $wSel;
307 // If there was a query to do, then select all phash-integers which resulted from this.
308 if ($res) {
309 // Get phash list by searching for it:
310 $phashList = array();
311 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
312 $phashList[] = $row['phash'];
313 }
314 $GLOBALS['TYPO3_DB']->sql_free_result($res);
315 // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
316 if ($c) {
317 switch ($v['oper']) {
318 case 'OR':
319 $totalHashList = array_unique(array_merge($phashList, $totalHashList));
320 break;
321 case 'AND NOT':
322 $totalHashList = array_diff($totalHashList, $phashList);
323 break;
324 default:
325 // AND...
326 $totalHashList = array_intersect($totalHashList, $phashList);
327 }
328 } else {
329 // First search
330 $totalHashList = $phashList;
331 }
332 }
333 $GLOBALS['TT']->pull();
334 $c++;
335 }
336 return implode(',', $totalHashList);
337 }
338
339 /**
340 * Returns a query which selects the search-word from the word/rel tables.
341 *
342 * @param string WHERE clause selecting the word from phash
343 * @param string Additional AND clause in the end of the query.
344 * @return pointer SQL result pointer
345 */
346 protected function execPHashListQuery($wordSel, $additionalWhereClause = '') {
347 return $GLOBALS['TYPO3_DB']->exec_SELECTquery('IR.phash', 'index_words IW,
348 index_rel IR,
349 index_section ISEC', $wordSel . '
350 AND IW.wid=IR.wid
351 AND ISEC.phash=IR.phash
352 ' . $this->sectionTableWhere() . '
353 ' . $additionalWhereClause, 'IR.phash');
354 }
355
356 /**
357 * Search for a word
358 *
359 * @param string the search word
360 * @param integer constant from this class to see if the wildcard should be left and/or right of the search string
361 * @return pointer SQL result pointer
362 */
363 protected function searchWord($sWord, $mode) {
364 $wildcard_left = $mode & self::WILDCARD_LEFT ? '%' : '';
365 $wildcard_right = $mode & self::WILDCARD_RIGHT ? '%' : '';
366 $wSel = 'IW.baseword LIKE \'' . $wildcard_left . $GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words') . $wildcard_right . '\'';
367 $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
368 return $res;
369 }
370
371 /**
372 * Search for one distinct word
373 *
374 * @param string the search word
375 * @return pointer SQL result pointer
376 */
377 protected function searchDistinct($sWord) {
378 $wSel = 'IW.wid=' . $this->md5inthash($sWord);
379 $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
380 return $res;
381 }
382
383 /**
384 * Search for a sentence
385 *
386 * @param string the search word
387 * @return pointer SQL result pointer
388 */
389 protected function searchSentence($sWord) {
390 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.phash', 'index_section ISEC, index_fulltext IFT', 'IFT.fulltextdata LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_fulltext') . '%\' AND
391 ISEC.phash = IFT.phash
392 ' . $this->sectionTableWhere(), 'ISEC.phash');
393 return $res;
394 }
395
396 /**
397 * Search for a metaphone word
398 *
399 * @param string the search word
400 * @return pointer SQL result pointer
401 */
402 protected function searchMetaphone($sWord) {
403 $wSel = 'IW.metaphone=' . $sWord;
404 $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
405 }
406
407 /**
408 * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
409 *
410 * @return string AND clause for selection of section in database.
411 */
412 protected function sectionTableWhere() {
413 $whereClause = '';
414 $match = FALSE;
415 if (!($this->searchRootPageIdList < 0)) {
416 $whereClause = ' AND ISEC.rl0 IN (' . $this->searchRootPageIdList . ') ';
417 }
418 if (substr($this->sections, 0, 4) == 'rl1_') {
419 $list = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', substr($this->sections, 4)));
420 $whereClause .= ' AND ISEC.rl1 IN (' . $list . ')';
421 $match = TRUE;
422 } elseif (substr($this->sections, 0, 4) == 'rl2_') {
423 $list = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', substr($this->sections, 4)));
424 $whereClause .= ' AND ISEC.rl2 IN (' . $list . ')';
425 $match = TRUE;
426 } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
427 // Traversing user configured fields to see if any of those are used to limit search to a section:
428 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
429 if (substr($this->sections, 0, strlen($fieldName) + 1) == $fieldName . '_') {
430 $list = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', substr($this->sections, strlen($fieldName) + 1)));
431 $whereClause .= ' AND ISEC.' . $fieldName . ' IN (' . $list . ')';
432 $match = TRUE;
433 break;
434 }
435 }
436 }
437 // If no match above, test the static types:
438 if (!$match) {
439 switch ((string) $this->sections) {
440 case '-1':
441 $whereClause .= ' AND ISEC.page_id=' . $GLOBALS['TSFE']->id;
442 break;
443 case '-2':
444 $whereClause .= ' AND ISEC.rl2=0';
445 break;
446 case '-3':
447 $whereClause .= ' AND ISEC.rl2>0';
448 break;
449 }
450 }
451 return $whereClause;
452 }
453
454 /**
455 * Returns AND statement for selection of media type
456 *
457 * @return string AND statement for selection of media type
458 */
459 public function mediaTypeWhere() {
460 $whereClause = '';
461 switch ($this->mediaType) {
462 case '0':
463 // '0' => 'Kun TYPO3 sider',
464 $whereClause = ' AND IP.item_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');
465 break;
466 case '-2':
467 // All external documents
468 $whereClause = ' AND IP.item_type!=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');
469 break;
470 case FALSE:
471
472 case '-1':
473 // All content
474 $whereClause = '';
475 break;
476 default:
477 $whereClause = ' AND IP.item_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->mediaType, 'index_phash');
478 }
479 return $whereClause;
480 }
481
482 /**
483 * Returns AND statement for selection of langauge
484 *
485 * @return string AND statement for selection of langauge
486 */
487 public function languageWhere() {
488 // -1 is the same as ALL language.
489 if ($this->languageUid >= 0) {
490 return ' AND IP.sys_language_uid=' . (int)$this->languageUid;
491 }
492 }
493
494 /**
495 * Where-clause for free index-uid value.
496 *
497 * @param integer Free Index UID value to limit search to.
498 * @return string WHERE SQL clause part.
499 */
500 public function freeIndexUidWhere($freeIndexUid) {
501 $freeIndexUid = (int)$freeIndexUid;
502 if ($freeIndexUid >= 0) {
503 // First, look if the freeIndexUid is a meta configuration:
504 $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('indexcfgs', 'index_config', 'type=5 AND uid=' . $freeIndexUid . $this->enableFields('index_config'));
505 if (is_array($indexCfgRec)) {
506 $refs = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $indexCfgRec['indexcfgs']);
507 // Default value to protect against empty array.
508 $list = array(-99);
509 foreach ($refs as $ref) {
510 list($table, $uid) = \TYPO3\CMS\Core\Utility\GeneralUtility::revExplode('_', $ref, 2);
511 $uid = (int)$uid;
512 switch ($table) {
513 case 'index_config':
514 $idxRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', 'index_config', 'uid=' . $uid . $this->enableFields('index_config'));
515 if ($idxRec) {
516 $list[] = $uid;
517 }
518 break;
519 case 'pages':
520 $indexCfgRecordsFromPid = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', 'index_config', 'pid=' . $uid . $this->enableFields('index_config'));
521 foreach ($indexCfgRecordsFromPid as $idxRec) {
522 $list[] = $idxRec['uid'];
523 }
524 break;
525 }
526 }
527 $list = array_unique($list);
528 } else {
529 $list = array($freeIndexUid);
530 }
531 return ' AND IP.freeIndexUid IN (' . implode(',', $list) . ')';
532 }
533 }
534
535 /**
536 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
537 *
538 * @param string List of phash integers which match the search.
539 * @param integer Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
540 * @return pointer Query result pointer
541 */
542 protected function execFinalQuery($list, $freeIndexUid = -1) {
543 // Setting up methods of filtering results
544 // based on page types, access, etc.
545 $page_join = '';
546 $page_where = '';
547 // Indexing configuration clause:
548 $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
549 // Calling hook for alternative creation of page ID list
550 if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
551 $page_where = $hookObj->execFinalQuery_idList($list);
552 }
553 // Alternative to getting all page ids by ->getTreeList() where
554 // "excludeSubpages" is NOT respected.
555 if ($this->joinPagesForQuery) {
556 $page_join = ',
557 pages';
558 $page_where = ' AND pages.uid = ISEC.page_id
559 ' . $this->enableFields('pages') . '
560 AND pages.no_search=0
561 AND pages.doktype<200
562 ';
563 } elseif ($this->searchRootPageIdList >= 0) {
564 // Collecting all pages IDs in which to search;
565 // filtering out ALL pages that are not accessible due to enableFields.
566 // Does NOT look for "no_search" field!
567 $siteIdNumbers = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->searchRootPageIdList);
568 $pageIdList = array();
569 foreach ($siteIdNumbers as $rootId) {
570 $pageIdList[] = $GLOBALS['TSFE']->cObj->getTreeList(-1 * $rootId, 9999);
571 }
572 $page_where = ' AND ISEC.page_id IN (' . implode(',', $pageIdList) . ')';
573 }
574 // otherwise select all / disable everything
575 // If any of the ranking sortings are selected, we must make a
576 // join with the word/rel-table again, because we need to
577 // calculate ranking based on all search-words found.
578 if (substr($this->sortOrder, 0, 5) == 'rank_') {
579 switch ($this->sortOrder) {
580 case 'rank_flag':
581 // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
582 // The ordering is refined with the frequency sum as well.
583 $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
584 $orderBy = 'order_val1' . $this->getDescendingSortOrderFlag() . ', order_val2' . $this->getDescendingSortOrderFlag();
585 break;
586 case 'rank_first':
587 // Results in average position of search words on page.
588 // Must be inversely sorted (low numbers are closer to top)
589 $grsel = 'AVG(IR.first) AS order_val';
590 $orderBy = 'order_val' . $this->getDescendingSortOrderFlag(TRUE);
591 break;
592 case 'rank_count':
593 // Number of words found
594 $grsel = 'SUM(IR.count) AS order_val';
595 $orderBy = 'order_val' . $this->getDescendingSortOrderFlag();
596 break;
597 default:
598 // Frequency sum. I'm not sure if this is the best way to do
599 // it (make a sum...). Or should it be the average?
600 $grsel = 'SUM(IR.freq) AS order_val';
601 $orderBy = 'order_val' . $this->getDescendingSortOrderFlag();
602 }
603 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.*, IP.*, ' . $grsel, 'index_words IW,
604 index_rel IR,
605 index_section ISEC,
606 index_phash IP' . $page_join, 'IP.phash IN (' . $list . ') ' . $this->mediaTypeWhere() . $this->languageWhere() . $freeIndexUidClause . '
607 AND IW.wid=IR.wid
608 AND ISEC.phash = IR.phash
609 AND IP.phash = IR.phash' . $page_where, 'IP.phash,ISEC.phash,ISEC.phash_t3,ISEC.rl0,ISEC.rl1,ISEC.rl2 ,ISEC.page_id,ISEC.uniqid,IP.phash_grouping,IP.data_filename ,IP.data_page_id ,IP.data_page_reg1,IP.data_page_type,IP.data_page_mp,IP.gr_list,IP.item_type,IP.item_title,IP.item_description,IP.item_mtime,IP.tstamp,IP.item_size,IP.contentHash,IP.crdate,IP.parsetime,IP.sys_language_uid,IP.item_crdate,IP.cHashParams,IP.externalUrl,IP.recordUid,IP.freeIndexUid,IP.freeIndexSetId', $orderBy);
610 } else {
611 // Otherwise, if sorting are done with the pages table or other fields,
612 // there is no need for joining with the rel/word tables:
613 $orderBy = '';
614 switch ((string) $this->sortOrder) {
615 case 'title':
616 $orderBy = 'IP.item_title' . $this->getDescendingSortOrderFlag();
617 break;
618 case 'crdate':
619 $orderBy = 'IP.item_crdate' . $this->getDescendingSortOrderFlag();
620 break;
621 case 'mtime':
622 $orderBy = 'IP.item_mtime' . $this->getDescendingSortOrderFlag();
623 break;
624 }
625 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.*, IP.*', 'index_phash IP,index_section ISEC' . $page_join, 'IP.phash IN (' . $list . ') ' . $this->mediaTypeWhere() . $this->languageWhere() . $freeIndexUidClause . '
626 AND IP.phash = ISEC.phash' . $page_where, 'IP.phash,ISEC.phash,ISEC.phash_t3,ISEC.rl0,ISEC.rl1,ISEC.rl2 ,ISEC.page_id,ISEC.uniqid,IP.phash_grouping,IP.data_filename ,IP.data_page_id ,IP.data_page_reg1,IP.data_page_type,IP.data_page_mp,IP.gr_list,IP.item_type,IP.item_title,IP.item_description,IP.item_mtime,IP.tstamp,IP.item_size,IP.contentHash,IP.crdate,IP.parsetime,IP.sys_language_uid,IP.item_crdate,IP.cHashParams,IP.externalUrl,IP.recordUid,IP.freeIndexUid,IP.freeIndexSetId', $orderBy);
627 }
628 return $res;
629 }
630
631 /**
632 * Checking if the resume can be shown for the search result
633 * (depending on whether the rights are OK)
634 * ? Should it also check for gr_list "0,-1"?
635 *
636 * @param array $row Result row array.
637 * @return boolean Returns TRUE if resume can safely be shown
638 */
639 protected function checkResume($row) {
640 // If the record is indexed by an indexing configuration, just show it.
641 // At least this is needed for external URLs and files.
642 // For records we might need to extend this - for instance block display if record is access restricted.
643 if ($row['freeIndexUid']) {
644 return TRUE;
645 }
646 // Evaluate regularly indexed pages based on item_type:
647 // External media:
648 if ($row['item_type']) {
649 // For external media we will check the access of the parent page on which the media was linked from.
650 // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
651 // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
652 // If this is NOT found, there is still a theoretical possibility that another user accessible page would display a link, so maybe the resume of such a document here may be unjustified hidden. But better safe than sorry.
653 if ($this->isTableUsed('index_grlist')) {
654 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash_t3'] . ' AND gr_list=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->frontendUserGroupList, 'index_grlist'));
655 } else {
656 $res = FALSE;
657 }
658 if ($res && $GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
659 return TRUE;
660 } else {
661 return FALSE;
662 }
663 } else {
664 // Ordinary TYPO3 pages:
665 if ((string)$row['gr_list'] !== (string)$this->frontendUserGroupList) {
666 // Selecting for the grlist records belonging to the phash-row where the current users gr_list exists. If it is found it is proof that this user has direct access to the phash-rows content although he did not himself initiate the indexing...
667 if ($this->isTableUsed('index_grlist')) {
668 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash'] . ' AND gr_list=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->frontendUserGroupList, 'index_grlist'));
669 } else {
670 $res = FALSE;
671 }
672 if ($res && $GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
673 return TRUE;
674 } else {
675 return FALSE;
676 }
677 } else {
678 return TRUE;
679 }
680 }
681 }
682
683 /**
684 * Returns "DESC" or "" depending on the settings of the incoming
685 * highest/lowest result order (piVars['desc'])
686 *
687 * @param boolean $inverse If TRUE, inverse the order which is defined by piVars['desc']
688 * @return string " DESC" or
689 * @formallyknownas tx_indexedsearch_pi->isDescending
690 */
691 protected function getDescendingSortOrderFlag($inverse = FALSE) {
692 $desc = $this->descendingSortOrderFlag;
693 if ($inverse) {
694 $desc = !$desc;
695 }
696 return !$desc ? ' DESC' : '';
697 }
698
699 /**
700 * Returns a part of a WHERE clause which will filter out records with start/end times or hidden/fe_groups fields
701 * set to values that should de-select them according to the current time, preview settings or user login.
702 * Definitely a frontend function.
703 * THIS IS A VERY IMPORTANT FUNCTION: Basically you must add the output from this function for EVERY select query you create
704 * for selecting records of tables in your own applications - thus they will always be filtered according to the "enablefields"
705 * configured in TCA
706 * Simply calls \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() BUT will send the show_hidden flag along!
707 * This means this function will work in conjunction with the preview facilities of the frontend engine/Admin Panel.
708 *
709 * @param string The table for which to get the where clause
710 * @param boolean If set, then you want NOT to filter out hidden records. Otherwise hidden record are filtered based on the current preview settings.
711 * @return string The part of the where clause on the form " AND [fieldname]=0 AND ...". Eg. " AND hidden=0 AND starttime < 123345567
712 * @see \TYPO3\CMS\Frontend\Page\PageRepository::enableFields()
713 */
714 protected function enableFields($table) {
715 return $GLOBALS['TSFE']->sys_page->enableFields($table, $table == 'pages' ? $GLOBALS['TSFE']->showHiddenPage : $GLOBALS['TSFE']->showHiddenRecords);
716 }
717
718 /**
719 * Returns if an item type is a multipage item type
720 *
721 * @param string Item type
722 * @return boolean TRUE if multipage capable
723 */
724 protected function multiplePagesType($itemType) {
725 return is_object($this->externalParsers[$itemType]) && $this->externalParsers[$itemType]->isMultiplePageExtension($itemType);
726 }
727
728 /**
729 * md5 integer hash
730 * Using 7 instead of 8 just because that makes the integers lower than
731 * 32 bit (28 bit) and so they do not interfere with UNSIGNED integers
732 * or PHP-versions which has varying output from the hexdec function.
733 *
734 * @param string $str String to hash
735 * @return integer Integer intepretation of the md5 hash of input string.
736 */
737 protected function md5inthash($str) {
738 return \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::md5inthash($str);
739 }
740
741 /**
742 * Check if the tables provided are configured for usage.
743 * This becomes necessary for extensions that provide additional database
744 * functionality like indexed_search_mysql.
745 *
746 * @param string $table_list Comma-separated list of tables
747 * @return boolean TRUE if given tables are enabled
748 */
749 protected function isTableUsed($table_list) {
750 return \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed($table_list);
751 }
752
753 /**
754 * Returns an object reference to the hook object if any
755 *
756 * @param string $functionName Name of the function you want to call / hook key
757 * @return object Hook object, if any. Otherwise NULL.
758 */
759 public function hookRequest($functionName) {
760 // Hook: menuConfig_preProcessModMenu
761 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
762 $hookObj = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
763 if (method_exists($hookObj, $functionName)) {
764 $hookObj->pObj = $this;
765 return $hookObj;
766 }
767 }
768 }
769
770 }