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