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