* Added template support to indexed_search. Many thanks to Udo von Eynern for his...
[Packages/TYPO3.CMS.git] / typo3 / sysext / indexed_search / pi / class.tx_indexedsearch.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2001-2005 Kasper Skaarhoj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Index search frontend
29 *
30 * $Id$
31 *
32 * Creates a searchform for indexed search. Indexing must be enabled
33 * for this to make sense.
34 *
35 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
36 * @co-author Christian Jul Jensen <christian@typo3.com>
37 */
38 /**
39 * [CLASS/FUNCTION INDEX of SCRIPT]
40 *
41 *
42 *
43 * 121: class tx_indexedsearch extends tslib_pibase
44 * 165: function main($content, $conf)
45 * 198: function initialize()
46 * 381: function getSearchWords($defOp)
47 * 415: function procSearchWordsByLexer($SWArr)
48 *
49 * SECTION: Main functions
50 * 459: function doSearch($sWArr)
51 * 492: function getResultRows($sWArr)
52 * 562: function getResultRows_SQLpointer($sWArr)
53 * 582: function getDisplayResults($sWArr, $resData)
54 * 633: function compileResult($resultRows)
55 *
56 * SECTION: Searching functions (SQL)
57 * 726: function getPhashList($sWArr)
58 * 827: function execPHashListQuery($wordSel,$plusQ='')
59 * 847: function sectionTableWhere()
60 * 894: function mediaTypeWhere()
61 * 919: function languageWhere()
62 * 931: function execFinalQuery($list)
63 * 1069: function checkResume($row)
64 * 1116: function isDescending($inverse=FALSE)
65 * 1130: function writeSearchStat($sWArr,$count,$pt)
66 *
67 * SECTION: HTML output functions
68 * 1181: function makeSearchForm($optValues)
69 * 1253: function printRules()
70 * 1269: function printResultSectionLinks()
71 * 1291: function makeSectionHeader($id,$sectionTitleLinked,$countResultRows)
72 * 1307: function printResultRow($row, $headerOnly=0)
73 * 1382: function pi_list_browseresults($showResultCount=1,$addString='',$addPart='')
74 *
75 * SECTION: Support functions for HTML output (with a minimum of fixed markup)
76 * 1445: function prepareResultRowTemplateData($row, $headerOnly)
77 * 1489: function tellUsWhatIsSeachedFor($sWArr)
78 * 1523: function wrapSW($str)
79 * 1535: function renderSelectBox($name,$value,$optValues)
80 * 1558: function makePointerSelector_link($str,$p)
81 * 1571: function makeItemTypeIcon($it,$alt='',$specRowConf)
82 * 1613: function makeRating($row)
83 * 1658: function makeDescription($row,$noMarkup=0,$lgd=180)
84 * 1688: function markupSWpartsOfString($str)
85 * 1768: function makeTitle($row)
86 * 1792: function makeInfo($row,$tmplArray)
87 * 1817: function getSpecialConfigForRow($row)
88 * 1841: function makeLanguageIndication($row)
89 * 1878: function makeAccessIndication($id)
90 * 1892: function linkPage($id,$str,$row=array())
91 * 1935: function getRootLine($id,$pathMP='')
92 * 1950: function getFirstSysDomainRecordForPage($id)
93 * 1963: function getPathFromPageId($id,$pathMP='')
94 * 2015: function getMenu($id)
95 * 2034: function multiplePagesType($item_type)
96 * 2044: function utf8_to_currentCharset($str)
97 * 2054: function &hookRequest($functionName)
98 *
99 * TOTAL FUNCTIONS: 46
100 * (This index is automatically created/updated by the extension "extdeveval")
101 *
102 */
103
104
105
106 require_once(PATH_tslib.'class.tslib_pibase.php');
107 require_once(PATH_tslib.'class.tslib_search.php');
108 require_once(t3lib_extMgm::extPath('indexed_search').'class.indexer.php');
109
110
111 /**
112 * Index search frontend
113 *
114 * Creates a searchform for indexed search. Indexing must be enabled
115 * for this to make sense.
116 *
117 * @package TYPO3
118 * @subpackage tx_indexedsearch
119 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
120 */
121 class tx_indexedsearch extends tslib_pibase {
122 var $prefixId = 'tx_indexedsearch'; // Same as class name
123 var $scriptRelPath = 'pi/class.tx_indexedsearch.php'; // Path to this script relative to the extension dir.
124 var $extKey = 'indexed_search'; // The extension key.
125
126 var $join_pages = 0; // See document for info about this flag...
127 var $defaultResultNumber = 10;
128
129 var $operator_translate_table = Array ( // case-sensitive. Defines the words, which will be operators between words
130 Array ('+' , 'AND'),
131 Array ('|' , 'OR'),
132 Array ('-' , 'AND NOT'),
133 // english
134 # Array ('AND' , 'AND'),
135 # Array ('OR' , 'OR'),
136 # Array ('NOT' , 'AND NOT'),
137 );
138
139 // Internal variable
140 var $wholeSiteIdList = 0; // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
141
142 // Internals:
143 var $sWArr = array(); // Search Words and operators
144 var $optValues = array(); // Selector box values for search configuration form
145 var $firstRow = Array(); // Will hold the first row in result - used to calculate relative hit-ratings.
146
147 var $cache_path = array(); // Caching of page path
148 var $cache_rl = array(); // Caching of root line data
149 var $fe_groups_required = array(); // Required fe_groups memberships for display of a result.
150 var $domain_records = array(); // Domain records (?)
151 var $wSelClauses = array(); // Select clauses for individual words
152 var $resultSections = array(); // Page tree sections for search result.
153 var $external_parsers = array(); // External parser objects
154 var $iconFileNameCache = array(); // Storage of icons....
155 var $lexerObj; // Lexer object
156 var $templateCode; // Will hold the content of $conf['templateFile']
157
158
159 /**
160 * Main function, called from TypoScript as a USER_INT object.
161 *
162 * @param string Content input, ignore (just put blank string)
163 * @param array TypoScript configuration of the plugin!
164 * @return string HTML code for the search form / result display.
165 */
166 function main($content, $conf) {
167
168 // Initialize:
169 $this->conf = $conf;
170 $this->pi_loadLL();
171 $this->pi_setPiVarDefaults();
172
173 // Initialize the indexer-class - just to use a few function (for making hashes)
174 $this->indexerObj = t3lib_div::makeInstance('tx_indexedsearch_indexer');
175
176 // Initialize:
177 $this->initialize();
178
179 // Do search:
180 // If there were any search words entered...
181 if (is_array($this->sWArr)) {
182 $content = $this->doSearch($this->sWArr);
183 }
184
185 // Finally compile all the content, form, messages and results:
186 $content = $this->makeSearchForm($this->optValues).
187 $this->printRules().
188 $content;
189
190 return $this->pi_wrapInBaseClass($content);
191 }
192
193 /**
194 * Initialize internal variables, especially selector box values for the search form and search words
195 *
196 * @return void
197 */
198 function initialize() {
199 global $TYPO3_CONF_VARS;
200
201 // Initialize external document parsers for icon display and other soft operations
202 if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'])) {
203 foreach($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
204 $this->external_parsers[$extension] = &t3lib_div::getUserObj($_objRef);
205
206 // Init parser and if it returns false, unset its entry again:
207 if (!$this->external_parsers[$extension]->softInit($extension)) {
208 unset($this->external_parsers[$extension]);
209 }
210 }
211 }
212
213 // Init lexer (used to post-processing of search words)
214 $lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ?
215 $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] :
216 'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
217 $this->lexerObj = &t3lib_div::getUserObj($lexerObjRef);
218
219 // If "_sections" is set, this value overrides any existing value.
220 if ($this->piVars['_sections']) $this->piVars['sections'] = $this->piVars['_sections'];
221
222 // Add previous search words to current
223 if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev']) {
224 $this->piVars['sword'] = trim($this->piVars['sword_prev']).' '.$this->piVars['sword'];
225 }
226
227 $this->piVars['results'] = t3lib_div::intInRange($this->piVars['results'],1,100000,$this->defaultResultNumber);
228
229 // Selector-box values defined here:
230 $this->optValues = Array(
231 'type' => Array(
232 '0' => $this->pi_getLL('opt_type_0'),
233 '1' => $this->pi_getLL('opt_type_1'),
234 '2' => $this->pi_getLL('opt_type_2'),
235 '3' => $this->pi_getLL('opt_type_3'),
236 '10' => $this->pi_getLL('opt_type_10'),
237 '20' => $this->pi_getLL('opt_type_20'),
238 ),
239 'defOp' => Array(
240 '0' => $this->pi_getLL('opt_defOp_0'),
241 '1' => $this->pi_getLL('opt_defOp_1'),
242 ),
243 'sections' => Array(
244 '0' => $this->pi_getLL('opt_sections_0'),
245 '-1' => $this->pi_getLL('opt_sections_-1'),
246 '-2' => $this->pi_getLL('opt_sections_-2'),
247 '-3' => $this->pi_getLL('opt_sections_-3'),
248 // Here values like "rl1_" and "rl2_" + a rootlevel 1/2 id can be added to perform searches in rootlevel 1+2 specifically. The id-values can even be commaseparated. Eg. "rl1_1,2" would search for stuff inside pages on menu-level 1 which has the uid's 1 and 2.
249 ),
250 'media' => Array(
251 '-1' => $this->pi_getLL('opt_media_-1'),
252 '0' => $this->pi_getLL('opt_media_0'),
253 '-2' => $this->pi_getLL('opt_media_-2'),
254 ),
255 'order' => Array(
256 'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
257 'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
258 'rank_first' => $this->pi_getLL('opt_order_rank_first'),
259 'rank_count' => $this->pi_getLL('opt_order_rank_count'),
260 'mtime' => $this->pi_getLL('opt_order_mtime'),
261 'title' => $this->pi_getLL('opt_order_title'),
262 'crdate' => $this->pi_getLL('opt_order_crdate'),
263 ),
264 'group' => Array (
265 'sections' => $this->pi_getLL('opt_group_sections'),
266 'flat' => $this->pi_getLL('opt_group_flat'),
267 ),
268 'lang' => Array (
269 -1 => $this->pi_getLL('opt_lang_-1'),
270 0 => $this->pi_getLL('opt_lang_0'),
271 ),
272 'desc' => Array (
273 '0' => $this->pi_getLL('opt_desc_0'),
274 '1' => $this->pi_getLL('opt_desc_1'),
275 ),
276 'results' => Array (
277 '10' => '10',
278 '20' => '20',
279 '50' => '50',
280 '100' => '100',
281 )
282 );
283
284 // Add media to search in:
285 if (strlen(trim($this->conf['search.']['mediaList']))) {
286 $mediaList = implode(',', t3lib_div::trimExplode(',', $this->conf['search.']['mediaList'], 1));
287 }
288 foreach($this->external_parsers as $extension => $obj) {
289 // Skip unwanted extensions
290 if ($mediaList && !t3lib_div::inList($mediaList, $extension)) { continue; }
291
292 if ($name = $obj->searchTypeMediaTitle($extension)) {
293 $this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_'.$extension,$name);
294 }
295 }
296
297 // Add operators for various languages
298 // Converts the operators to UTF-8 and lowercase
299 $this->operator_translate_table[] = Array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj->utf8_encode($this->pi_getLL('local_operator_AND'), $GLOBALS['TSFE']->renderCharset),'toLower') , 'AND');
300 $this->operator_translate_table[] = Array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj->utf8_encode($this->pi_getLL('local_operator_OR'), $GLOBALS['TSFE']->renderCharset),'toLower') , 'OR');
301 $this->operator_translate_table[] = Array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj->utf8_encode($this->pi_getLL('local_operator_NOT'), $GLOBALS['TSFE']->renderCharset),'toLower') , 'AND NOT');
302
303 // This is the id of the site root. This value may be a commalist of integer (prepared for this)
304 $this->wholeSiteIdList = intval($GLOBALS['TSFE']->config['rootLine'][0]['uid']);
305
306 // Creating levels for section menu:
307 // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
308 if ($this->conf['show.']['L1sections']) {
309 $firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
310 while(list($kk,$mR) = each($firstLevelMenu)) {
311 if ($mR['doktype']!=5) {
312 $this->optValues['sections']['rl1_'.$mR['uid']] = trim($this->pi_getLL('opt_RL1').' '.$mR['title']);
313 if ($this->conf['show.']['L2sections']) {
314 $secondLevelMenu = $this->getMenu($mR['uid']);
315 while(list($kk2,$mR2) = each($secondLevelMenu)) {
316 if ($mR['doktype']!=5) {
317 $this->optValues['sections']['rl2_'.$mR2['uid']] = trim($this->pi_getLL('opt_RL2').' '.$mR2['title']);
318 } else unset($secondLevelMenu[$kk2]);
319 }
320 $this->optValues['sections']['rl2_'.implode(',',array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
321 }
322 } else unset($firstLevelMenu[$kk]);
323 }
324 $this->optValues['sections']['rl1_'.implode(',',array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
325 }
326
327 // Setting the list of root PIDs for the search. Notice, these page IDs MUST have a TypoScript template with root flag on them! Basically this list is used to select on the "rl0" field and page ids are registered as "rl0" only if a TypoScript template record with root flag is there.
328 // This happens AFTER the use of $this->wholeSiteIdList above because the above will then fetch the menu for the CURRENT site - regardless of this kind of searching here. Thus a general search will lookup in the WHOLE database while a specific section search will take the current sections...
329 if ($this->conf['search.']['rootPidList']) {
330 $this->wholeSiteIdList = implode(',',t3lib_div::intExplode(',',$this->conf['search.']['rootPidList']));
331 }
332
333 // Load the template
334 $this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
335
336 // Add search languages:
337 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1'.$this->cObj->enableFields('sys_language'));
338 while($lR = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
339 $this->optValues['lang'][$lR['uid']] = $lR['title'];
340 }
341
342 // Calling hook for modification of initialized content
343 if ($hookObj = &$this->hookRequest('initialize_postProc')) {
344 $hookObj->initialize_postProc();
345 }
346
347 // Default values set:
348 // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
349 foreach($this->optValues as $kk => $vv) {
350 if (!isset($this->piVars[$kk])) {
351 reset($vv);
352 $this->piVars[$kk] = key($vv);
353 }
354 }
355
356 // Blind selectors:
357 if (is_array($this->conf['blind.'])) {
358 foreach($this->conf['blind.'] as $kk => $vv) {
359 if (is_array($vv)) {
360 foreach($vv as $kkk => $vvv) {
361 if (!is_array($vvv) && $vvv && is_array($this->optValues[substr($kk,0,-1)])) {
362 unset($this->optValues[substr($kk,0,-1)][$kkk]);
363 }
364 }
365 } elseif ($vv) { // If value is not set, unset the option array.
366 unset($this->optValues[$kk]);
367 }
368 }
369 }
370
371 // This gets the search-words into the $sWArr:
372 $this->sWArr = $this->getSearchWords($this->piVars['defOp']);
373 }
374
375 /**
376 * Splits the search word input into an array where each word is represented by an array with key "sword" holding the search word and key "oper" holds the SQL operator (eg. AND, OR)
377 *
378 * Only words with 2 or more characters are accepted
379 * Max 200 chars total
380 * Space is used to split words, "" can be used search for a whole string (not indexed search then)
381 * AND, OR and NOT are prefix words, overruling the default operator
382 * +/|/- equals AND, OR and NOT as operators.
383 * All search words are converted to lowercase.
384 *
385 * $defOp is the default operator. 1=OR, 0=AND
386 *
387 * @param boolean If true, the default operator will be OR, not AND
388 * @return array Returns array with search words if any found
389 */
390 function getSearchWords($defOp) {
391 // Shorten search-word string to max 200 bytes (does NOT take multibyte charsets into account - but never mind, shortening the string here is only a run-away feature!)
392 $inSW = substr($this->piVars['sword'],0,200);
393
394 // Convert to UTF-8 + conv. entities (was also converted during indexing!)
395 $inSW = $GLOBALS['TSFE']->csConvObj->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset);
396 $inSW = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($inSW,TRUE);
397
398 if ($hookObj = &$this->hookRequest('getSearchWords')) {
399 return $hookObj->getSearchWords_splitSWords($inSW, $defOp);
400 } else {
401
402 if ($this->piVars['type']==20) {
403 return array(array('sword'=>trim($inSW), 'oper'=>'AND'));
404 } else {
405 $search = t3lib_div::makeInstance('tslib_search');
406 $search->default_operator = $defOp==1 ? 'OR' : 'AND';
407 $search->operator_translate_table = $this->operator_translate_table;
408 $search->register_and_explode_search_string($inSW);
409
410 if (is_array($search->sword_array)) {
411 return $this->procSearchWordsByLexer($search->sword_array);
412 }
413 }
414 }
415 }
416
417 /**
418 * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
419 * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
420 *
421 * @param array Search word array
422 * @return array Search word array, processed through lexer
423 */
424 function procSearchWordsByLexer($SWArr) {
425
426 // Init output variable:
427 $newSWArr = array();
428
429 // Traverse the search word array:
430 foreach($SWArr as $wordDef) {
431 if (!strstr($wordDef['sword'],' ')) { // No space in word (otherwise it might be a sentense in quotes like "there is").
432 // SPlit the search word by lexer:
433 $res = $this->lexerObj->split2Words($wordDef['sword']);
434
435 // Traverse lexer result and add all words again:
436 foreach($res as $word) {
437 $newSWArr[] = array('sword'=>$word, 'oper'=>$wordDef['oper']);
438 }
439 } else {
440 $newSWArr[] = $wordDef;
441 }
442 }
443
444 // Return result:
445 return $newSWArr;
446 }
447
448
449
450
451
452
453
454
455
456 /*****************************
457 *
458 * Main functions
459 *
460 *****************************/
461
462 /**
463 * Performs the search, the display and writing stats
464 *
465 * @param array Search words in array, see ->getSearchWords() for details
466 * @return string HTML for result display.
467 */
468 function doSearch($sWArr) {
469
470 // Get result rows:
471 $pt1 = t3lib_div::milliseconds();
472 if ($hookObj = &$this->hookRequest('getResultRows')) {
473 $resData = $hookObj->getResultRows($sWArr);
474 } else {
475 $resData = $this->getResultRows($sWArr);
476 }
477
478 // Display search results:
479 $pt2 = t3lib_div::milliseconds();
480 if ($hookObj = &$this->hookRequest('getDisplayResults')) {
481 $content = $hookObj->getDisplayResults($sWArr, $resData);
482 } else {
483 $content = $this->getDisplayResults($sWArr, $resData);
484 }
485
486 $pt3 = t3lib_div::milliseconds();
487
488 // Write search statistics
489 $this->writeSearchStat($sWArr,$resData['count'],array($pt1,$pt2,$pt3));
490
491 // Return content:
492 return $content;
493 }
494
495 /**
496 * Get search result rows / data from database. Returned as data in array.
497 *
498 * @param array Search word array
499 * @return array False if no result, otherwise an array with keys for first row, result rows and total number of results found.
500 */
501 function getResultRows($sWArr) {
502
503 // Getting SQL result pointer:
504 $GLOBALS['TT']->push('Searching result');
505 $res = $this->getResultRows_SQLpointer($sWArr);
506 $GLOBALS['TT']->pull();
507
508 // Organize and process result:
509 if ($res) {
510
511 $count = $GLOBALS['TYPO3_DB']->sql_num_rows($res); // Total search-result count
512 $pointer = t3lib_div::intInRange($this->piVars['pointer'], 0, floor($count/$this->piVars['results'])); // The pointer is set to the result page that is currently being viewed
513
514 // Initialize result accumulation variables:
515 $c = 0; // Result pointer: Counts up the position in the current search-result
516 $grouping_phashes = array(); // Used to filter out duplicates.
517 $grouping_chashes = array(); // Used to filter out duplicates BASED ON cHash.
518 $firstRow = Array(); // Will hold the first row in result - used to calculate relative hit-ratings.
519 $resultRows = Array(); // Will hold the results rows for display.
520
521 // Now, traverse result and put the rows to be displayed into an array
522 // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
523 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
524
525 // Set first row:
526 if (!$c) {
527 $firstRow = $row;
528 }
529
530 $row['show_resume'] = $this->checkResume($row); // Tells whether we can link directly to a document or not (depends on possible right problems)
531
532 $phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
533 $chashGr = !in_array($row['contentHash'].'.'.$row['data_page_id'], $grouping_chashes);
534 if ($phashGr && $chashGr) {
535 if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords']) { // Only if the resume may be shown are we going to filter out duplicates...
536 if (!$this->multiplePagesType($row['item_type'])) { // Only on documents which are not multiple pages documents
537 $grouping_phashes[] = $row['phash_grouping'];
538 }
539 $grouping_chashes[] = $row['contentHash'].'.'.$row['data_page_id'];
540
541 $c++; // Increase the result pointer
542
543 // All rows for display is put into resultRows[]
544 if ($c > $pointer * $this->piVars['results']) {
545 $row['result_number'] = $c;
546 $resultRows[] = $row;
547 // This may lead to a problem: If the result check is not stopped here, the search will take longer. However the result counter will not filter out grouped cHashes/pHashes that were not processed yet.
548 if (($c+1) > ($pointer+1)*$this->piVars['results']) break;
549 }
550 } else {
551 $count--; // Skip this row if the user cannot view it (missing permission)
552 }
553 } else {
554 $count--; // For each time a phash_grouping document is found (which is thus not displayed) the search-result count is reduced, so that it matches the number of rows displayed.
555 }
556 }
557
558 return array(
559 'resultRows' => $resultRows,
560 'firstRow' => $firstRow,
561 'count' => $count
562 );
563 } else { // No results found:
564 return FALSE;
565 }
566 }
567
568 /**
569 * Gets a SQL result pointer to traverse for the search records.
570 *
571 * @param array Search words
572 * @return pointer
573 */
574 function getResultRows_SQLpointer($sWArr) {
575 // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
576 $list = $this->getPhashList($sWArr);
577
578 // Perform SQL Search / collection of result rows array:
579 if ($list) {
580 // Do the search:
581 $GLOBALS['TT']->push('execFinalQuery');
582 $res = $this->execFinalQuery($list);
583 $GLOBALS['TT']->pull();
584 return $res;
585 } else {
586 return FALSE;
587 }
588 }
589
590 /**
591 * Compiles the HTML display of the incoming array of result rows.
592 *
593 * @param array Search words array (for display of text describing what was searched for)
594 * @param array Array with result rows, count, first row.
595 * @return string HTML content to display result.
596 */
597 function getDisplayResults($sWArr, $resData) {
598 // Perform display of result rows array:
599 if ($resData) {
600 $GLOBALS['TT']->push('Display Final result');
601
602 // Set first selected row (for calculation of ranking later)
603 $this->firstRow = $resData['firstRow'];
604
605 // Result display here:
606 $rowcontent = '';
607 $rowcontent.= $this->compileResult($resData['resultRows']);
608
609 // Browsing box:
610 if ($resData['count']) {
611 $this->internal['res_count'] = $resData['count'];
612 $this->internal['results_at_a_time'] = $this->piVars['results'];
613 $this->internal['maxPages'] = t3lib_div::intInRange($this->conf['search.']['page_links'],1,100,10);
614 $addString = ($resData['count']&&$this->piVars['group']=='sections' ? ' '.sprintf($this->pi_getLL(count($this->resultSections)>1?'inNsections':'inNsection'),count($this->resultSections)):'');
615 $browseBox1 = $this->pi_list_browseresults(1,$addString,$this->printResultSectionLinks());
616 $browseBox2 = $this->pi_list_browseresults(0);
617 }
618
619 // Browsing nav, bottom.
620 if ($resData['count']) {
621 $content = $browseBox1.$rowcontent.$browseBox2;
622 } else {
623 $content = '<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
624 }
625
626 $GLOBALS['TT']->pull();
627 } else {
628 $content.='<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
629 }
630
631 // Print a message telling which words we searched for, and in which sections etc.
632 $what = $this->tellUsWhatIsSeachedFor($sWArr).
633 (substr($this->piVars['sections'],0,2)=='rl'?' '.$this->pi_getLL('inSection','',1).' "'.substr($this->getPathFromPageId(substr($this->piVars['sections'],4)),1).'"':'');
634 $what = '<div'.$this->pi_classParam('whatis').'><p>'.$what.'</p></div>';
635 $content = $what.$content;
636
637 // Return content:
638 return $content;
639 }
640
641 /**
642 * Takes the array with resultrows as input and returns the result-HTML-code
643 * Takes the "group" var into account: Makes a "section" or "flat" display.
644 *
645 * @param array Result rows
646 * @return string HTML
647 */
648 function compileResult($resultRows) {
649 $content = '';
650
651 // Transfer result rows to new variable, performing some mapping of sub-results etc.
652 $newResultRows = array();
653 foreach($resultRows as $row) {
654 $id = md5($row['phash_grouping']);
655 if (is_array($newResultRows[$id])) {
656 if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) { // swapping:
657
658 // Remove old
659 $subrows = $newResultRows[$id]['_sub'];
660 unset($newResultRows[$id]['_sub']);
661 $subrows[] = $newResultRows[$id];
662
663 // Insert new:
664 $newResultRows[$id] = $row;
665 $newResultRows[$id]['_sub'] = $subrows;
666 } else $newResultRows[$id]['_sub'][] = $row;
667 } else {
668 $newResultRows[$id] = $row;
669 }
670 }
671 $resultRows = $newResultRows;
672
673
674 switch($this->piVars['group']) {
675 case 'sections':
676
677 $rl2flag = substr($this->piVars['sections'],0,2)=='rl';
678 $sections = array();
679 foreach($resultRows as $row) {
680 $id = $row['rl0'].'-'.$row['rl1'].($rl2flag?'-'.$row['rl2']:'');
681 $sections[$id][] = $row;
682 }
683
684 $this->resultSections = array();
685
686 foreach($sections as $id => $resultRows) {
687 $rlParts = explode('-',$id);
688 $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1]?$rlParts[1]:$rlParts[0]);
689 $theRLid = $rlParts[2] ? 'rl2_'.$rlParts[2]:($rlParts[1]?'rl1_'.$rlParts[1]:'0');
690
691 $sectionName = substr($this->getPathFromPageId($theId),1);
692 if (!trim($sectionName)) {
693 $sectionTitleLinked = $this->pi_getLL('unnamedSection','',1).':';
694 } else {
695 $onclick = 'document.'.$this->prefixId.'[\''.$this->prefixId.'[_sections]\'].value=\''.$theRLid.'\';document.'.$this->prefixId.'.submit();return false;';
696 $sectionTitleLinked = '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.htmlspecialchars($sectionName).':</a>';
697 }
698 $this->resultSections[$id] = array($sectionName,count($resultRows));
699
700 // Add content header:
701 $content.= $this->makeSectionHeader($id,$sectionTitleLinked,count($resultRows));
702
703 // Render result rows:
704 foreach($resultRows as $row) {
705 $content.= $this->printResultRow($row);
706 }
707 }
708 break;
709 default: // flat:
710 foreach($resultRows as $row) {
711 $content.= $this->printResultRow($row);
712 }
713 break;
714 }
715 return '<div'.$this->pi_classParam('res').'>'.$content.'</div>';
716 }
717
718
719
720
721
722
723
724
725
726
727
728 /***********************************
729 *
730 * Searching functions (SQL)
731 *
732 ***********************************/
733
734 /**
735 * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the sWArr array.
736 * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
737 *
738 * @param array Search word array
739 * @return string List of integers
740 */
741 function getPhashList($sWArr) {
742
743 // Initialize variables:
744 $c=0;
745 $totalHashList = array(); // This array accumulates the phash-values
746 $this->wSelClauses = array();
747
748 // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
749 foreach($sWArr as $k => $v) {
750 // Making the query for a single search word based on the search-type
751 $sWord = $v['sword']; // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$v['sword'],'toLower'); // lower-case all of them...
752 $theType = (string)$this->piVars['type'];
753 if (strstr($sWord,' ')) $theType = 20; // If there are spaces in the search-word, make a full text search instead.
754
755 $GLOBALS['TT']->push('SearchWord "'.$sWord.'" - $theType='.$theType);
756
757 $res = '';
758 $wSel='';
759
760 // Perform search for word:
761 switch($theType) {
762 case '1':
763 $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
764 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
765 break;
766 case '2':
767 $wSel = "IW.baseword LIKE '".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
768 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
769 break;
770 case '3':
771 $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."'";
772 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
773 break;
774 case '10':
775 $wSel = 'IW.metaphone = '.$this->indexerObj->metaphone($sWord);
776 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
777 break;
778 case '20':
779 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
780 'ISEC.phash',
781 'index_section ISEC, index_fulltext IFT',
782 'IFT.fulltextdata LIKE \'%'.$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_fulltext').'%\' AND
783 ISEC.phash = IFT.phash
784 '.$this->sectionTableWhere(),
785 'ISEC.phash'
786 );
787 $wSel = '1=1';
788
789 if ($this->piVars['type']==20) $this->piVars['order'] = 'mtime'; // If there is a fulltext search for a sentence there is a likeliness that sorting cannot be done by the rankings from the rel-table (because no relations will exist for the sentence in the word-table). So therefore mtime is used instaed. It is not required, but otherwise some hits may be left out.
790 break;
791 default:
792 $wSel = 'IW.wid = '.$hash = $this->indexerObj->md5inthash($sWord);
793 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
794 break;
795 }
796
797 // Accumulate the word-select clauses
798 $this->wSelClauses[] = $wSel;
799
800 // If there was a query to do, then select all phash-integers which resulted from this.
801 if ($res) {
802
803 // Get phash list by searching for it:
804 $phashList = array();
805 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
806 $phashList[] = $row['phash'];
807 }
808 $GLOBALS['TYPO3_DB']->sql_free_result($res);
809
810 // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
811 if ($c) {
812 switch($v['oper']) {
813 case 'OR':
814 $totalHashList = array_unique(array_merge($phashList,$totalHashList));
815 break;
816 case 'AND NOT':
817 $totalHashList = array_diff($totalHashList,$phashList);
818 break;
819 default: // AND...
820 $totalHashList = array_intersect($totalHashList,$phashList);
821 break;
822 }
823 } else {
824 $totalHashList = $phashList; // First search
825 }
826 }
827
828 $GLOBALS['TT']->pull();
829 $c++;
830 }
831
832 return implode(',',$totalHashList);
833 }
834
835 /**
836 * Returns a query which selects the search-word from the word/rel tables.
837 *
838 * @param string WHERE clause selecting the word from phash
839 * @param string Additional AND clause in the end of the query.
840 * @return pointer SQL result pointer
841 */
842 function execPHashListQuery($wordSel,$plusQ='') {
843 return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
844 'IR.phash',
845 'index_words IW,
846 index_rel IR,
847 index_section ISEC',
848 $wordSel.'
849 AND IW.wid=IR.wid
850 AND ISEC.phash = IR.phash
851 '.$this->sectionTableWhere().'
852 '.$plusQ,
853 'IR.phash'
854 );
855 }
856
857 /**
858 * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
859 *
860 * @return string AND clause for selection of section in database.
861 */
862 function sectionTableWhere() {
863 $out = $this->wholeSiteIdList<0 ? '' : 'AND ISEC.rl0 IN ('.$this->wholeSiteIdList.')';
864
865 $match = '';
866 if (substr($this->piVars['sections'],0,4)=='rl1_') {
867 $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
868 $out.= 'AND ISEC.rl1 IN ('.$list.')';
869 $match = TRUE;
870 } elseif (substr($this->piVars['sections'],0,4)=='rl2_') {
871 $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
872 $out.= 'AND ISEC.rl2 IN ('.$list.')';
873 $match = TRUE;
874 } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
875 // Traversing user configured fields to see if any of those are used to limit search to a section:
876 foreach($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
877 if (substr($this->piVars['sections'],0,strlen($fieldName)+1)==$fieldName.'_') {
878 $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],strlen($fieldName)+1)));
879 $out.= 'AND ISEC.'.$fieldName.' IN ('.$list.')';
880 $match = TRUE;
881 break;
882 }
883 }
884 }
885
886 // If no match above, test the static types:
887 if (!$match) {
888 switch((string)$this->piVars['sections']) {
889 case '-1': // '-1' => 'Only this page',
890 $out.= ' AND ISEC.page_id='.$GLOBALS['TSFE']->id;
891 break;
892 case '-2': // '-2' => 'Top + level 1',
893 $out.= ' AND ISEC.rl2=0';
894 break;
895 case '-3': // '-3' => 'Level 2 and out',
896 $out.= ' AND ISEC.rl2>0';
897 break;
898 }
899 }
900
901 return $out;
902 }
903
904 /**
905 * Returns AND statement for selection of media type
906 *
907 * @return string AND statement for selection of media type
908 */
909 function mediaTypeWhere() {
910
911 switch((string)$this->piVars['media']) {
912 case '0': // '0' => 'Kun TYPO3 sider',
913 $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
914 break;
915 case '-2': // All external documents
916 $out = 'AND IP.item_type!='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
917 break;
918 case '-1': // All content
919 $out='';
920 break;
921 default:
922 $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['media'], 'index_phash');
923 break;
924 }
925
926 return $out;
927 }
928
929 /**
930 * Returns AND statement for selection of langauge
931 *
932 * @return string AND statement for selection of langauge
933 */
934 function languageWhere() {
935 if ($this->piVars['lang']>=0) { // -1 is the same as ALL language.
936 return 'AND IP.sys_language_uid='.intval($this->piVars['lang']);
937 }
938 }
939
940 /**
941 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
942 *
943 * @param string List of phash integers which match the search.
944 * @return pointer Query result pointer
945 */
946 function execFinalQuery($list) {
947
948 // Setting up methods of filtering results based on page types, access, etc.
949 $page_join = '';
950 $page_where = '';
951
952 // Calling hook for alternative creation of page ID list
953 if ($hookObj = &$this->hookRequest('execFinalQuery_idList')) {
954 $page_where = $hookObj->execFinalQuery_idList($list);
955 } elseif ($this->join_pages) { // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
956 $page_join = ',
957 pages';
958 $page_where = 'pages.uid = ISEC.page_id
959 '.$this->cObj->enableFields('pages').'
960 AND pages.no_search=0
961 AND pages.doktype<200
962 ';
963 } elseif ($this->wholeSiteIdList>=0) { // Collecting all pages IDs in which to search; filtering out ALL pages that are not accessible due to enableFields. Does NOT look for "no_search" field!
964 $siteIdNumbers = t3lib_div::intExplode(',',$this->wholeSiteIdList);
965 $id_list=array();
966 while(list(,$rootId)=each($siteIdNumbers)) {
967 $id_list[] = $this->cObj->getTreeList($rootId,9999,0,0,'','').$rootId;
968 }
969 $page_where = 'ISEC.page_id IN ('.implode(',',$id_list).')';
970 } else { // Disable everything... (select all)
971 $page_where = ' 1=1 ';
972 }
973
974
975 // If any of the ranking sortings are selected, we must make a join with the word/rel-table again, because we need to calculate ranking based on all search-words found.
976 if (substr($this->piVars['order'],0,5)=='rank_') {
977 /*
978 OK there were some fancy calculations promoted by Graeme Merrall:
979
980 "However, regarding relevance you probably want to look at something like
981 Salton's formula which is a good easy way to measure relevance.
982 Oracle Intermedia uses this and it's pretty simple:
983 Score can be between 0 and 100, but the top-scoring document in the query
984 will not necessarily have a score of 100 -- scoring is relative, not
985 absolute. This means that scores are not comparable across indexes, or even
986 across different queries on the same index. Score for each document is
987 computed using the standard Salton formula:
988
989 3f(1+log(N/n))
990
991 Where f is the frequency of the search term in the document, N is the total
992 number of rows in the table, and n is the number of rows which contain the
993 search term. This is converted into an integer in the range 0 - 100.
994
995 There's a good doc on it at
996 http://ls6-www.informatik.uni-dortmund.de/bib/fulltext/ir/Pfeifer:97/
997 although it may be a little complex for what you require so just pick the
998 relevant parts out.
999 "
1000
1001 However I chose not to go with this for several reasons.
1002 I do not claim that my ways of calculating importance here is the best.
1003 ANY (better) suggestion for ranking calculation is accepted! (as long as they are shipped with tested code in exchange for this.)
1004 */
1005
1006 switch($this->piVars['order']) {
1007 case 'rank_flag': // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1008 // The ordering is refined with the frequency sum as well.
1009 $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1010 $orderBy = 'order_val1'.$this->isDescending().',order_val2'.$this->isDescending();
1011 break;
1012 case 'rank_first': // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1013 $grsel = 'AVG(IR.first) AS order_val';
1014 $orderBy = 'order_val'.$this->isDescending(1);
1015 break;
1016 case 'rank_count': // Number of words found
1017 $grsel = 'SUM(IR.count) AS order_val';
1018 $orderBy = 'order_val'.$this->isDescending();
1019 break;
1020 default: // Frequency sum. I'm not sure if this is the best way to do it (make a sum...). Or should it be the average?
1021 $grsel = 'SUM(IR.freq) AS order_val';
1022 $orderBy = 'order_val'.$this->isDescending();
1023 break;
1024 }
1025
1026 // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1027 $wordSel='('.implode(' OR ',$this->wSelClauses).') AND ';
1028
1029 return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1030 'ISEC.*, IP.*, '
1031 .$grsel,
1032 'index_words IW,
1033 index_rel IR,
1034 index_section ISEC,
1035 index_phash IP'.
1036 $page_join,
1037 $wordSel.'
1038 IP.phash IN ('.$list.') '.
1039 $this->mediaTypeWhere().' '.
1040 $this->languageWhere().'
1041 AND IW.wid=IR.wid
1042 AND ISEC.phash = IR.phash
1043 AND IP.phash = IR.phash
1044 AND '.$page_where,
1045 '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',
1046 $orderBy
1047 );
1048 } else { // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1049
1050 $orderBy = '';
1051 switch((string)$this->piVars['order']) {
1052 case 'title':
1053 $orderBy = 'IP.item_title'.$this->isDescending();
1054 break;
1055 case 'crdate':
1056 $orderBy = 'IP.item_crdate'.$this->isDescending();
1057 break;
1058 case 'mtime':
1059 $orderBy = 'IP.item_mtime'.$this->isDescending();
1060 break;
1061 }
1062
1063 return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1064 'ISEC.*, IP.*',
1065 'index_phash IP,index_section ISEC'.$page_join,
1066 'IP.phash IN ('.$list.') '.
1067 $this->mediaTypeWhere().' '.
1068 $this->languageWhere().'
1069 AND IP.phash = ISEC.phash
1070 AND '.$page_where,
1071 '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',
1072 $orderBy
1073 );
1074 }
1075 }
1076
1077 /**
1078 * Checking if the resume can be shown for the search result (depending on whether the rights are OK)
1079 * ? Should it also check for gr_list "0,-1"?
1080 *
1081 * @param array Result row array.
1082 * @return boolean Returns true if resume can safely be shown
1083 */
1084 function checkResume($row) {
1085
1086 // If the record is indexed by an indexing configuration, just show it.
1087 // At least this is needed for external URLs and files.
1088 // For records we might need to extend this - for instance block display if record is access restricted.
1089 if ($row['freeIndexUid']) {
1090 return TRUE;
1091 }
1092
1093 // Evaluate regularly indexed pages based on item_type:
1094 if ($row['item_type']) { // External media:
1095 // For external media we will check the access of the parent page on which the media was linked from.
1096 // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1097 // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1098 // 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.
1099 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash='.intval($row['phash_t3']).' AND gr_list='.$GLOBALS['TYPO3_DB']->fullQuoteStr($GLOBALS['TSFE']->gr_list, 'index_grlist'));
1100 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1101 #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' YES - ('.$GLOBALS['TSFE']->gr_list.")!",1);
1102 return TRUE;
1103 } else {
1104 #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' NO - ('.$GLOBALS['TSFE']->gr_list.")!",1);
1105 return FALSE;
1106 }
1107 } else { // Ordinary TYPO3 pages:
1108 if (strcmp($row['gr_list'],$GLOBALS['TSFE']->gr_list)) {
1109 // 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...
1110 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash='.intval($row['phash']).' AND gr_list='.$GLOBALS['TYPO3_DB']->fullQuoteStr($GLOBALS['TSFE']->gr_list, 'index_grlist'));
1111 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1112 #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash'].' - YES ('.$GLOBALS['TSFE']->gr_list.")",1);
1113 return TRUE;
1114 } else {
1115 #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash']." - NOPE",1);
1116 return FALSE;
1117 }
1118 } else {
1119 #debug('Resume can be shown, because the document was in fact indexed by this combination of groups!'.$GLOBALS['TSFE']->gr_list.' - '.$row['item_title'].'/'.$row['phash'],1);
1120 return TRUE;
1121 }
1122 }
1123 }
1124
1125 /**
1126 * Returns "DESC" or "" depending on the settings of the incoming highest/lowest result order (piVars['desc']
1127 *
1128 * @param boolean If true, inverse the order which is defined by piVars['desc']
1129 * @return string " DESC" or ""
1130 */
1131 function isDescending($inverse=FALSE) {
1132 $desc = $this->piVars['desc'];
1133 if ($inverse) $desc=!$desc;
1134 return !$desc ? ' DESC':'';
1135 }
1136
1137 /**
1138 * Write statistics information to database for the search operation
1139 *
1140 * @param array Search Word array
1141 * @param integer Number of hits
1142 * @param integer Milliseconds the search took
1143 * @return void
1144 */
1145 function writeSearchStat($sWArr,$count,$pt) {
1146 $insertFields = array(
1147 'searchstring' => $this->piVars['sword'],
1148 'searchoptions' => serialize(array($this->piVars,$sWArr,$pt)),
1149 'feuser_id' => intval($this->fe_user->user['uid']), // fe_user id, integer
1150 'cookie' => $this->fe_user->id, // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1151 'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'), // Remote IP address
1152 'hits' => intval($count), // Number of hits on the search.
1153 'tstamp' => $GLOBALS['EXEC_TIME'] // Time stamp
1154 );
1155
1156 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
1157 $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
1158
1159 if ($newId) {
1160 foreach($sWArr as $val) {
1161 $insertFields = array(
1162 'word' => $val['sword'], // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $val['sword'], 'toLower'),
1163 'index_stat_search_id' => $newId,
1164 'tstamp' => $GLOBALS['EXEC_TIME'], // Time stamp
1165 'pageid' => $GLOBALS['TSFE']->id //search page id for indexed search stats
1166 );
1167
1168 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
1169 }
1170 }
1171 }
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185 /***********************************
1186 *
1187 * HTML output functions
1188 *
1189 ***********************************/
1190
1191 /**
1192 * Make search form HTML
1193 *
1194 * @param array Value/Labels pairs for search form selector boxes.
1195 * @return string Search form HTML
1196 */
1197 function makeSearchForm($optValues) {
1198 $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
1199
1200 // Multilangual text
1201 $substituteArray = array('searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style');
1202 foreach ($substituteArray as $marker) {
1203 $markerArray['###FORM_'.strtoupper($marker).'###'] = $this->pi_getLL('form_'.$marker);
1204 }
1205
1206 $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label');
1207
1208 // Adding search field value
1209 $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1210
1211 // Additonal keyword => "Add to current search words"
1212 if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1213 $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1214 $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"':'';
1215 $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch');
1216 } else {
1217 $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1218 }
1219
1220 $markerArray['###ACTION_URL###'] = $this->pi_getPageLink($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sPre);
1221 $markerArray['###HIDDEN_VALUE_TYPE###'] = $this->piVars['type'] ? 1 : 0;
1222 $markerArray['###HIDDEN_VALUE_EXT###'] = $this->piVars['ext'] ? 1 : 0;
1223
1224 // Extended search
1225 if ($this->piVars['ext']) {
1226
1227 // Search for
1228 if ((!is_array($optValues['type']) && !is_array($optValues['defOp'])) || ($this->conf['blind.']['type'] && $this->conf['blind.']['defOp'])) {
1229 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1230 } else {
1231 if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
1232 $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'],$optValues['type']);
1233 } else {
1234 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1235 }
1236
1237 if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp']) {
1238 $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'],$optValues['defOp']);
1239 } else {
1240 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1241 }
1242 }
1243
1244 // Search in
1245 if ((!is_array($optValues['media']) && !is_array($optValues['lang'])) || ($this->conf['blind.']['media'] && $this->conf['blind.']['lang'])) {
1246 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1247 } else {
1248 if (is_array($optValues['media']) && !$this->conf['blind.']['media']) {
1249 $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'],$optValues['media']);
1250 } else {
1251 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1252 }
1253
1254 if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
1255 $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'],$optValues['lang']);
1256 } else {
1257 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1258 }
1259 }
1260
1261 // Sections
1262 if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
1263 $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1264 } else {
1265 $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'],$optValues['sections']);
1266 }
1267
1268 // Sorting
1269 if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order']) {
1270 $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1271 } else {
1272 $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'],$optValues['order']);
1273 $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'],$optValues['desc']);
1274 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
1275 }
1276
1277 // Limits
1278 if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results']) {
1279 $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1280 } else {
1281 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
1282 }
1283
1284 // Grouping
1285 if (!is_array($optValues['group']) || $this->conf['blind.']['group']) {
1286 $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1287 } else {
1288 $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'],$optValues['group']);
1289 }
1290
1291 if ($this->conf['blind.']['extResume']) {
1292 $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1293 } else {
1294 $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' CHECKED' : '';
1295 }
1296
1297 } else { // Extended search
1298 $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1299 }
1300
1301 if($this->conf['show.']['advancedSearchLink']) {
1302 $markerArray['###LINKTOOTHERMODE###'] = ($this->piVars['ext'] ?
1303 '<a href="'.$this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>0)).'">'.$this->pi_getLL('link_regularSearch').'</a>' :
1304 '<a href="'.$this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>1)).'">'.$this->pi_getLL('link_advancedSearch').'</a>'
1305 );
1306 } else {
1307 $markerArray['###LINKTOOTHERMODE###'] = '';
1308 }
1309
1310 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1311
1312 return $substitutedContent;
1313 }
1314
1315 function renderSelectBoxValues($value,$optValues) {
1316 if (is_array($optValues)) {
1317 $opt=array();
1318 $isSelFlag=0;
1319 foreach($optValues as $k=>$v) {
1320 $sel = (!strcmp($k,$value) ? ' selected' : '');
1321 if ($sel) { $isSelFlag++; }
1322 $opt[]='<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1323 }
1324
1325 return implode('',$opt);
1326 }
1327 }
1328
1329 /**
1330 * Print the searching rules
1331 *
1332 * @return string Rules for the search
1333 */
1334 function printRules() {
1335 if ($this->conf['show.']['rules']) {
1336
1337 $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1338
1339 $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header','',1);
1340 $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text','',1)));
1341
1342 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1343
1344 return '<div'.$this->pi_classParam('rules').'>'.$this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']).'</div>';
1345 }
1346 }
1347
1348 /**
1349 * Returns the anchor-links to the sections inside the displayed result rows.
1350 *
1351 * @return string
1352 */
1353 function printResultSectionLinks() {
1354 if (count($this->resultSections)) {
1355 $lines = array();
1356
1357 $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1358 $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1359
1360 foreach($this->resultSections as $id => $dat) {
1361 $markerArray = array();
1362
1363 $aBegin = '<a href="'.htmlspecialchars($GLOBALS['TSFE']->anchorPrefix.'#'.md5($id)).'">';
1364 $aContent = htmlspecialchars(trim($dat[0]) ? trim($dat[0]) : $this->pi_getLL('unnamedSection')).
1365 ' ('.$dat[1].' '.$this->pi_getLL($dat[1]>1 ? 'word_pages' : 'word_page','',1).')';
1366 $aEnd = '</a>';
1367
1368 $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1369
1370 $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
1371 }
1372
1373 $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode(chr(10),$links)), array(), array());
1374
1375 return '<div'.$this->pi_classParam('sectionlinks').'>'.$this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']).'</div>';
1376 }
1377 }
1378
1379 /**
1380 * Returns the section header of the search result.
1381 *
1382 * @param string ID for the section (used for anchor link)
1383 * @param string Section title with linked wrapped around
1384 * @param integer Number of results in section
1385 * @return string HTML output
1386 */
1387 function makeSectionHeader($id, $sectionTitleLinked, $countResultRows) {
1388
1389 $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1390
1391 $markerArray['###ANCHOR_URL###'] = md5($id);
1392 $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1393 $markerArray['###RESULT_COUNT###'] = $countResultRows;
1394 $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page'.($countResultRows>1 ? 's' : ''));
1395
1396 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1397
1398 return $substitutedContent;
1399 }
1400
1401 /**
1402 * This prints a single result row, including a recursive call for subrows.
1403 *
1404 * @param array Search result row
1405 * @param integer 1=Display only header (for sub-rows!), 2=nothing at all
1406 * @return string HTML code
1407 */
1408 function printResultRow($row, $headerOnly=0) {
1409
1410 // Get template content:
1411 $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1412
1413 if ($hookObj = &$this->hookRequest('printResultRow')) {
1414 return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1415 } else {
1416
1417 $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1418
1419 if (!is_array($row['_sub'])) {
1420 $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1421 }
1422
1423 if (!$headerOnly) {
1424 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1425
1426 } elseif ($headerOnly==1) {
1427 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1428 } elseif ($headerOnly==2) {
1429 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1430 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1431 }
1432
1433 if (is_array($tmplContent)) {
1434 foreach ($tmplContent AS $k => $v) {
1435 $markerArray['###'.strtoupper($k).'###'] = $v;
1436 }
1437 }
1438
1439 // Description text
1440 $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size');
1441 $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created');
1442 $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified');
1443 $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path');
1444
1445 $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1446
1447 // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1448 if (is_array($row['_sub'])) {
1449 if ($this->multiplePagesType($row['item_type'])) {
1450
1451 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching'), $html);
1452
1453 foreach($row['_sub'] as $subRow) {
1454 $html .= $this->printResultRow($subRow,1);
1455 }
1456 } else {
1457
1458 $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching');
1459
1460 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell'), $html);
1461 }
1462 }
1463
1464 return $html;
1465 }
1466 }
1467
1468 /**
1469 * Returns a results browser
1470 *
1471 * @param boolean Show result count
1472 * @param string String appended to "displaying results..." notice.
1473 * @param string String appended after section "displaying results..."
1474 * @return string HTML output
1475 */
1476 function pi_list_browseresults($showResultCount=1,$addString='',$addPart='') {
1477
1478 // Initializing variables:
1479 $pointer=$this->piVars['pointer'];
1480 $count=$this->internal['res_count'];
1481 $results_at_a_time = t3lib_div::intInRange($this->internal['results_at_a_time'],1,1000);
1482 $maxPages = t3lib_div::intInRange($this->internal['maxPages'],1,100);
1483 $pageCount = ceil($count/$results_at_a_time);
1484 $sTables = '';
1485
1486 if ($pageCount > 1) { // only show the result browser if more than one page is needed
1487 $pointer=intval($pointer);
1488 $links=array();
1489
1490 // Make browse-table/links:
1491 if ($pointer>0) { // all pages after the 1st one
1492 $links[]='<td><p>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev','< Previous',1),$pointer-1).'</p></td>';
1493 }
1494
1495 for($a=0;$a<$pageCount;$a++) {
1496 $min = max(0, $pointer+1-ceil($maxPages/2));
1497 $max = $min+$maxPages;
1498 if($max>$pageCount) {
1499 $min = $min - ($max-$pageCount);
1500 }
1501
1502 if($a >= $min && $a < $max) {
1503 if($a==$pointer) {
1504 $links[]='<td'.($pointer==$a?$this->pi_classParam('browsebox-SCell'):'').'><p><strong>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+1)),$a).'</strong></p></td>';
1505 } else {
1506 $links[]='<td'.($pointer==$a?$this->pi_classParam('browsebox-SCell'):'').'><p>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+1)),$a).'</p></td>';
1507 }
1508 }
1509 }
1510 if ($pointer+1 < $pageCount) {
1511 $links[]='<td><p>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next','Next >',1),$pointer+1).'</p></td>';
1512 }
1513 }
1514
1515 $pR1 = $pointer*$results_at_a_time+1;
1516 $pR2 = $pointer*$results_at_a_time+$results_at_a_time;
1517 if(is_array($links)) {
1518 $addPart .= '
1519 <table>
1520 <tr>'.implode('',$links).'</tr>
1521 </table>';
1522 }
1523
1524 $sTables = '<div'.$this->pi_classParam('browsebox').'>'.
1525 ($showResultCount ? '<p>'.sprintf(
1526 str_replace('###SPAN_BEGIN###','<span'.$this->pi_classParam('browsebox-strong').'>',$this->pi_getLL('pi_list_browseresults_displays','Displaying results ###SPAN_BEGIN###%s to %s</span> out of ###SPAN_BEGIN###%s</span>')),
1527 $pR1,
1528 min(array($this->internal['res_count'],$pR2)),
1529 $this->internal['res_count']
1530 ).$addString.'</p>':''
1531 ).$addPart.'</div>';
1532
1533 return $sTables;
1534 }
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547 /***********************************
1548 *
1549 * Support functions for HTML output (with a minimum of fixed markup)
1550 *
1551 ***********************************/
1552
1553 /**
1554 * Preparing template data for the result row output
1555 *
1556 * @param array Result row
1557 * @param boolean If set, display only header of result (for sub-results)
1558 * @return array Array with data to insert in result row template
1559 */
1560 function prepareResultRowTemplateData($row, $headerOnly) {
1561
1562 // Initialize:
1563 $specRowConf = $this->getSpecialConfigForRow($row);
1564 $CSSsuffix = $specRowConf['CSSsuffix']?'-'.$specRowConf['CSSsuffix']:'';
1565
1566 // If external media, link to the media-file instead.
1567 if ($row['item_type']) { // External media
1568 if ($row['show_resume']) { // Can link directly.
1569 $title = '<a href="'.htmlspecialchars($row['data_filename']).'">'.htmlspecialchars($this->makeTitle($row)).'</a>';
1570 } else { // Suspicious, so linking to page instead...
1571 $copy_row = $row;
1572 unset($copy_row['cHashParams']);
1573 $title = $this->linkPage($row['page_id'],htmlspecialchars($this->makeTitle($row)),$copy_row);
1574 }
1575 } else { // Else the page:
1576
1577 // Prepare search words for markup in content:
1578 if ($this->conf['forwardSearchWordsInResultLink']) {
1579 $markUpSwParams = array('no_cache' => 1);
1580 foreach($this->sWArr as $d) {
1581 $markUpSwParams['sword_list'][] = $d['sword'];
1582 }
1583 } else {
1584 $markUpSwParams = array();
1585 }
1586 $title = $this->linkPage($row['data_page_id'],htmlspecialchars($this->makeTitle($row)),$row,$markUpSwParams);
1587 }
1588
1589 $tmplContent = array();
1590 $tmplContent['title'] = $title;
1591 $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'].': ' : '&nbsp;';
1592 $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'],'',$specRowConf);
1593 $tmplContent['rating'] = $this->makeRating($row);
1594 $tmplContent['description'] = $this->makeDescription($row,$this->piVars['extResume'] && !$headerOnly?0:1);
1595 $tmplContent = $this->makeInfo($row,$tmplContent);
1596 $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1597 $tmplContent['language'] = $this->makeLanguageIndication($row);
1598 $tmplContent['CSSsuffix'] = $CSSsuffix;
1599
1600 // Post processing with hook.
1601 if ($hookObj = &$this->hookRequest('prepareResultRowTemplateData_postProc')) {
1602 $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1603 }
1604
1605 return $tmplContent;
1606 }
1607
1608 /**
1609 * Returns a string that tells which search words are searched for.
1610 *
1611 * @param array Array of search words
1612 * @return string HTML telling what is searched for.
1613 */
1614 function tellUsWhatIsSeachedFor($sWArr) {
1615
1616 // Init:
1617 $searchingFor = '';
1618 $c=0;
1619
1620 // Traverse search words:
1621 foreach($sWArr as $k => $v) {
1622 if ($c) {
1623 switch($v['oper']) {
1624 case 'OR':
1625 $searchingFor.= ' '.$this->pi_getLL('searchFor_or','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1626 break;
1627 case 'AND NOT':
1628 $searchingFor.= ' '.$this->pi_getLL('searchFor_butNot','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1629 break;
1630 default: // AND...
1631 $searchingFor.= ' '.$this->pi_getLL('searchFor_and','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1632 break;
1633 }
1634 } else {
1635 $searchingFor = $this->pi_getLL('searchFor','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1636 }
1637 $c++;
1638 }
1639 return $searchingFor;
1640 }
1641
1642 /**
1643 * Wraps the search words in the search-word list display (from ->tellUsWhatIsSeachedFor())
1644 *
1645 * @param string search word to wrap (in local charset!)
1646 * @return string Search word wrapped in <span> tag.
1647 */
1648 function wrapSW($str) {
1649 return '"<span'.$this->pi_classParam('sw').'>'.htmlspecialchars($str).'</span>"';
1650 }
1651
1652 /**
1653 * Makes a selector box
1654 *
1655 * @param string Name of selector box
1656 * @param string Current value
1657 * @param array Array of options in the selector box (value => label pairs)
1658 * @return string HTML of selector box
1659 */
1660 function renderSelectBox($name,$value,$optValues) {
1661 if (is_array($optValues)) {
1662 $opt = array();
1663 $isSelFlag = 0;
1664
1665 foreach($optValues as $k => $v) {
1666 $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
1667 if ($sel) $isSelFlag++;
1668 $opt[] = '<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1669 }
1670
1671 return '<select name="'.$name.'">'.implode('',$opt).'</select>';
1672 }
1673 }
1674
1675 /**
1676 * Used to make the link for the result-browser.
1677 * Notice how the links must resubmit the form after setting the new pointer-value in a hidden formfield.
1678 *
1679 * @param string String to wrap in <a> tag
1680 * @param integer Pointer value
1681 * @return string Input string wrapped in <a> tag with onclick event attribute set.
1682 */
1683 function makePointerSelector_link($str,$p) {
1684 $onclick = 'document.'.$this->prefixId.'[\''.$this->prefixId.'[pointer]\'].value=\''.$p.'\';document.'.$this->prefixId.'.submit();return false;';
1685 return '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.$str.'</a>';
1686 }
1687
1688 /**
1689 * Return icon for file extension
1690 *
1691 * @param string File extension / item type
1692 * @param string Title attribute value in icon.
1693 * @param array TypoScript configuration specifically for search result.
1694 * @return string <img> tag for icon
1695 */
1696 function makeItemTypeIcon($it,$alt='',$specRowConf) {
1697 if (!isset($this->iconFileNameCache[$it])) {
1698 $this->iconFileNameCache[$it] = '';
1699
1700 // If TypoScript is used to render the icon:
1701 if (is_array($this->conf['iconRendering.'])) {
1702 $this->cObj->setCurrentVal($it);
1703 $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'],$this->conf['iconRendering.']);
1704 } else { // ... otherwise, get flag from sys_language record:
1705
1706 // Default creation / finding of icon:
1707 $icon = '';
1708 if ($it==='0') {
1709 if (is_array($specRowConf['pageIcon.'])) {
1710 $this->iconFileNameCache[$it] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
1711 } else {
1712 $icon = 'EXT:indexed_search/pi/res/pages.gif';
1713 }
1714 } elseif ($this->external_parsers[$it]) {
1715 $icon = $this->external_parsers[$it]->getIcon($it);
1716 }
1717
1718 if ($icon) {
1719 $fullPath = t3lib_div::getFileAbsFileName($icon);
1720
1721 if ($fullPath) {
1722 $info = @getimagesize($fullPath);
1723 $iconPath = substr($fullPath,strlen(PATH_site));
1724 $this->iconFileNameCache[$it] = is_array($info) ? '<img src="'.$iconPath.'" '.$info[3].' title="'.htmlspecialchars($alt).'" alt="" />' : '';
1725 }
1726 }
1727 }
1728 }
1729 return $this->iconFileNameCache[$it];
1730 }
1731
1732 /**
1733 * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
1734 *
1735 * @param array Result row array
1736 * @return string String showing ranking value
1737 */
1738 function makeRating($row) {
1739
1740 switch((string)$this->piVars['order']) {
1741 case 'rank_count': // Number of occurencies on page
1742 return $row['order_val'].' '.$this->pi_getLL('maketitle_matches');
1743 break;
1744 case 'rank_first': // Close to top of page
1745 return ceil(t3lib_div::intInRange(255-$row['order_val'],1,255)/255*100).'%';
1746 break;
1747 case 'rank_flag': // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1748 if ($this->firstRow['order_val2']) {
1749 $base = $row['order_val1']*256; // (3 MSB bit, 224 is highest value of order_val1 currently)
1750 $freqNumber = $row['order_val2']/$this->firstRow['order_val2']*pow(2,12); // 15-3 MSB = 12
1751 $total = t3lib_div::intInRange($base+$freqNumber,0,32767);
1752 #debug($total);
1753 return ceil(log($total)/log(32767)*100).'%';
1754 }
1755 break;
1756 case 'rank_freq': // Based on frequency
1757 $max = 10000;
1758 $total = t3lib_div::intInRange($row['order_val'],0,$max);
1759 # debug($total);
1760 return ceil(log($total)/log($max)*100).'%';
1761 break;
1762 case 'crdate': // Based on creation date
1763 return $this->cObj->calcAge(time()-$row['item_crdate'],0); // ,$conf['age']
1764 break;
1765 case 'mtime': // Based on modification time
1766 return $this->cObj->calcAge(time()-$row['item_mtime'],0); // ,$conf['age']
1767 break;
1768 default: // fx. title
1769 return '&nbsp;';
1770 break;
1771 }
1772 }
1773
1774 /**
1775 * Returns the resume for the search-result.
1776 *
1777 * @param array Search result row
1778 * @param boolean If noMarkup is false, then the index_fulltext table is used to select the content of the page, split it with regex to display the search words in the text.
1779 * @param integer String length
1780 * @return string HTML string ...
1781 */
1782 function makeDescription($row,$noMarkup=0,$lgd=180) {
1783 if ($row['show_resume']) {
1784 if (!$noMarkup) {
1785 $markedSW = '';
1786 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash='.intval($row['phash']));
1787 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1788 // Cut HTTP references after some length
1789 $content = preg_replace('/(http:\/\/[^\?]+)\?[^[:blank:]]*/i', '$1?...', $ftdrow['fulltextdata']);
1790 $markedSW = $this->markupSWpartsOfString($content);
1791 }
1792 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1793 }
1794
1795 if (!trim($markedSW)) {
1796 $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_description'],$lgd);
1797 $outputStr = htmlspecialchars($outputStr);
1798 }
1799 $output = $this->utf8_to_currentCharset($outputStr ? $outputStr : $markedSW);
1800 } else {
1801 $output = '<span class="noResume">'.$this->pi_getLL('res_noResume','',1).'</span>';
1802 }
1803
1804 return $output;
1805 }
1806
1807 /**
1808 * Marks up the search words from $this->sWarr in the $str with a color.
1809 *
1810 * @param string text in which to find and mark up search words. This text is assumed to be UTF-8 like the search words internally is.
1811 * @return string Processed content.
1812 */
1813 function markupSWpartsOfString($str) {
1814
1815 // Init:
1816 $str = str_replace('&nbsp;',' ',t3lib_parsehtml::bidir_htmlspecialchars($str,-1));
1817 $str = preg_replace('/\s\s+/',' ',$str);
1818 $swForReg = array();
1819
1820 // Prepare search words for regex:
1821 foreach($this->sWArr as $d) {
1822 $swForReg[] = quotemeta($d['sword']);
1823 }
1824 $regExString = '('.implode('|',$swForReg).')';
1825
1826 // Split and combine:
1827 $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
1828 #debug($parts,$regExString);
1829 // Constants:
1830 $summaryMax = 300;
1831 $postPreLgd = 60;
1832 $postPreLgd_offset = 5;
1833 $divider = ' ... ';
1834
1835 $occurencies = (count($parts)-1)/2;
1836 if ($occurencies) {
1837 $postPreLgd = t3lib_div::intInRange($summaryMax/$occurencies,$postPreLgd,$summaryMax/2);
1838 }
1839
1840 // Variable:
1841 $summaryLgd = 0;
1842 $output = array();
1843
1844 // Shorten in-between strings:
1845 foreach($parts as $k => $strP) {
1846 if (($k%2)==0) {
1847
1848 // Find length of the summary part:
1849 $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
1850 $output[$k] = $parts[$k];
1851
1852 // Possibly shorten string:
1853 if (!$k) { // First entry at all (only cropped on the frontside)
1854 if ($strLen > $postPreLgd) {
1855 $output[$k] = $divider.ereg_replace('^[^[:space:]]+[[:space:]]','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
1856 }
1857 } elseif ($summaryLgd > $summaryMax || !isset($parts[$k+1])) { // In case summary length is exceed OR if there are no more entries at all:
1858 if ($strLen > $postPreLgd) {
1859 $output[$k] = ereg_replace('[[:space:]][^[:space:]]+$','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).$divider;
1860 }
1861 } else { // In-between search words:
1862 if ($strLen > $postPreLgd*2) {
1863 $output[$k] = ereg_replace('[[:space:]][^[:space:]]+$','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).
1864 $divider.
1865 ereg_replace('^[^[:space:]]+[[:space:]]','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
1866 }
1867 }
1868 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);;
1869
1870 // Protect output:
1871 $output[$k] = htmlspecialchars($output[$k]);
1872
1873 // If summary lgd is exceed, break the process:
1874 if ($summaryLgd > $summaryMax) {
1875 break;
1876 }
1877 } else {
1878 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8',$strP);
1879 $output[$k] = '<span class="tx-indexedsearch-redMarkup">'.htmlspecialchars($parts[$k]).'</span>';
1880 }
1881 }
1882
1883 // Return result:
1884 return implode('',$output);
1885 }
1886
1887 /**
1888 * Returns the title of the search result row
1889 *
1890 * @param array Result row
1891 * @return string Title from row
1892 */
1893 function makeTitle($row) {
1894 $add = '';
1895
1896 if ($this->multiplePagesType($row['item_type'])) {
1897 $dat = unserialize($row['cHashParams']);
1898
1899 $pp = explode('-',$dat['key']);
1900 if ($pp[0]!=$pp[1]) {
1901 $add=', '.$this->pi_getLL('word_pages').' '.$dat['key'];
1902 } else $add=', '.$this->pi_getLL('word_page').' '.$pp[0];
1903 }
1904
1905 $outputString = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_title'],50,'...');
1906
1907 return $this->utf8_to_currentCharset(htmlspecialchars($outputString)).$add;
1908 }
1909
1910 /**
1911 * Returns the info-string in the bottom of the result-row display (size, dates, path)
1912 *
1913 * @param array Result row
1914 * @param array Template array to modify
1915 * @return array Modified template array
1916 */
1917 function makeInfo($row,$tmplArray) {
1918 $tmplArray['size'] = $this->pi_getLL('res_size','',1).' '.t3lib_div::formatSize($row['item_size']).'';
1919 $tmplArray['created'] = $this->pi_getLL('res_created','',1).' '.date('d-m-y',$row['item_crdate']).'';
1920 $tmplArray['modified'] = $this->pi_getLL('res_modified','',1).' '.date('d-m-y H:i',$row['item_mtime']).'';
1921
1922 $pathId = $row['data_page_id']?$row['data_page_id']:$row['page_id'];
1923 $pathMP = $row['data_page_id']?$row['data_page_mp']:'';
1924
1925 $pI = parse_url($row['data_filename']);
1926 if ($pI['scheme']) {
1927 $tmplArray['path'] = '<a href="'.htmlspecialchars($row['data_filename']).'">'.htmlspecialchars($row['data_filename']).'</a>';
1928 } else {
1929 $pathStr = htmlspecialchars($this->getPathFromPageId($pathId,$pathMP));
1930 $tmplArray['path'] = $this->linkPage($pathId,htmlspecialchars($pathStr),array(
1931 'data_page_type' => $row['data_page_type'],
1932 'data_page_mp' => $pathMP,
1933 'sys_language_uid' => $row['sys_language_uid'],
1934 ));
1935 }
1936
1937 return $tmplArray;
1938 }
1939
1940 /**
1941 * Returns configuration from TypoScript for result row based on ID / location in page tree!
1942 *
1943 * @param array Result row
1944 * @return array Configuration array
1945 */
1946 function getSpecialConfigForRow($row) {
1947 $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
1948 $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
1949
1950 $rl = $this->getRootLine($pathId,$pathMP);
1951 $specConf = $this->conf['specConfs.']['0.'];
1952 if (is_array($rl)) {
1953 foreach($rl as $dat) {
1954 if (is_array($this->conf['specConfs.'][$dat['uid'].'.'])) {
1955 $specConf = $this->conf['specConfs.'][$dat['uid'].'.'];
1956 break;
1957 }
1958 }
1959 }
1960
1961 return $specConf;
1962 }
1963
1964 /**
1965 * Returns the HTML code for language indication.
1966 *
1967 * @param array Result row
1968 * @return string HTML code for result row.
1969 */
1970 function makeLanguageIndication($row) {
1971
1972 // If search result is a TYPO3 page:
1973 if ((string)$row['item_type']==='0') {
1974
1975 // If TypoScript is used to render the flag:
1976 if (is_array($this->conf['flagRendering.'])) {
1977 $this->cObj->setCurrentVal($row['sys_language_uid']);
1978 return $this->cObj->cObjGetSingle($this->conf['flagRendering'],$this->conf['flagRendering.']);
1979 } else { // ... otherwise, get flag from sys_language record:
1980
1981 // Get sys_language record
1982 $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_language', 'uid='.intval($row['sys_language_uid']).' '.$this->cObj->enableFields('sys_language'));
1983
1984 // Flag code:
1985 $flag = $rowDat[0]['flag'];
1986 if ($flag) {
1987 $file = 't3lib/gfx/flags/'.$flag;
1988 $imgInfo = @getimagesize(PATH_site.$file);
1989
1990 if (is_array($imgInfo)) {
1991 $output = '<img src="'.$file.'" '.$imgInfo[3].' title="'.htmlspecialchars($rowDat[0]['title']).'" alt="'.htmlspecialchars($rowDat[0]['title']).'" />';
1992 return $output;
1993 }
1994 }
1995 }
1996 }
1997 return '&nbsp;';
1998 }
1999
2000 /**
2001 * Returns the HTML code for the locking symbol.
2002 * NOTICE: Requires a call to ->getPathFromPageId() first in order to work (done in ->makeInfo() by calling that first)
2003 *
2004 * @param integer Page id for which to find answer
2005 * @return string <img> tag if access is limited.
2006 */
2007 function makeAccessIndication($id) {
2008 if (is_array($this->fe_groups_required[$id]) && count($this->fe_groups_required[$id])) {
2009 return '<img src="'.t3lib_extMgm::siteRelPath('indexed_search').'pi/res/locked.gif" width="12" height="15" vspace="5" title="'.sprintf($this->pi_getLL('res_memberGroups','',1),implode(',',array_unique($this->fe_groups_required[$id]))).'" alt="" />';
2010 }
2011 }
2012
2013 /**
2014 * Links the $str to page $id
2015 *
2016 * @param integer Page id
2017 * @param string Title String to link
2018 * @param array Result row
2019 * @return string <A> tag wrapped title string.
2020 */
2021 function linkPage($id,$str,$row=array(),$markUpSwParams=array()) {
2022
2023 // Parameters for link:
2024 $urlParameters = (array)unserialize($row['cHashParams']);
2025
2026 // Add &type and &MP variable:
2027 if ($row['data_page_type']) $urlParameters['type'] = $row['data_page_type'];
2028 if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
2029 if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
2030
2031 // markup-GET vars:
2032 $urlParameters = array_merge($urlParameters, $markUpSwParams);
2033
2034 // This will make sure that the path is retrieved if it hasn't been already. Used only for the sake of the domain_record thing...
2035 if (!is_array($this->domain_records[$id])) {
2036 $this->getPathFromPageId($id);
2037 }
2038
2039 // If external domain, then link to that:
2040 if (count($this->domain_records[$id])) {
2041 reset($this->domain_records[$id]);
2042 $firstDom = current($this->domain_records[$id]);
2043 $scheme = t3lib_div::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2044
2045 $addParams = '';
2046 if (is_array($urlParameters)) {
2047 if (count($urlParameters)) {
2048 $addParams.= t3lib_div::implodeArrayForUrl('',$urlParameters);
2049 }
2050 }
2051
2052 return '<a href="'.$scheme.$firstDom.'/index.php?id='.$id.$addParams.'" target="'.$this->conf['search.']['detect_sys_domain_records.']['target'].'">'.htmlspecialchars($str).'</a>';
2053 } else {
2054 return $this->pi_linkToPage($str,$id,$this->conf['result_link_target'],$urlParameters);
2055 }
2056 }
2057
2058 /**
2059 * Returns the path to the page $id
2060 *
2061 * @param integer Page ID
2062 * @param string MP variable content.
2063 * @return string Root line for result.
2064 */
2065 function getRootLine($id,$pathMP='') {
2066 $identStr = $id.'|'.$pathMP;
2067
2068 if (!isset($this->cache_path[$identStr])) {
2069 $this->cache_rl[$identStr] = $GLOBALS['TSFE']->sys_page->getRootLine($id,$pathMP);
2070 }
2071 return $this->cache_rl[$identStr];
2072 }
2073
2074 /**
2075 * Gets the first sys_domain record for the page, $id
2076 *
2077 * @param integer Page id
2078 * @return string Domain name
2079 */
2080 function getFirstSysDomainRecordForPage($id) {
2081 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', 'pid='.intval($id).$this->cObj->enableFields('sys_domain'), '', 'sorting');
2082 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
2083 return ereg_replace('\/$','',$row['domainName']);
2084 }
2085
2086 /**
2087 * Returns the path to the page $id
2088 *
2089 * @param integer Page ID
2090 * @param string MP variable content
2091 * @return string Path
2092 */
2093 function getPathFromPageId($id,$pathMP='') {
2094
2095 $identStr = $id.'|'.$pathMP;
2096
2097 if (!isset($this->cache_path[$identStr])) {
2098 $this->fe_groups_required[$id] = array();
2099 $this->domain_records[$id] = array();
2100 $rl = $this->getRootLine($id,$pathMP);
2101 $hitRoot = 0;
2102 $path = '';
2103 if (is_array($rl) && count($rl)) {
2104 reset($rl);
2105 while(list($k,$v)=each($rl)) {
2106 // Check fe_user
2107 if ($v['fe_group'] && ($v['uid']==$id || $v['extendToSubpages'])) {
2108 $this->fe_groups_required[$id][]=$v['fe_group'];
2109 }
2110 // Check sys_domain.
2111 if ($this->conf['search.']['detect_sys_domain_records']) {
2112 $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2113 if ($sysDName) {
2114 $this->domain_records[$id][] = $sysDName;
2115 // Set path accordingly:
2116 $path = $sysDName.$path;
2117 break;
2118 }
2119 }
2120
2121 // Stop, if we find that the current id is the current root page.
2122 if ($v['uid']==$GLOBALS['TSFE']->config['rootLine'][0]['uid']) {
2123 break;
2124 }
2125 $path = '/'.$v['title'].$path;
2126 }
2127 }
2128
2129 $this->cache_path[$identStr] = $path;
2130
2131 if (is_array($this->conf['path_stdWrap.'])) {
2132 $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2133 }
2134 }
2135
2136 return $this->cache_path[$identStr];
2137 }
2138
2139 /**
2140 * Return the menu of pages used for the selector.
2141 *
2142 * @param integer Page ID for which to return menu
2143 * @return array Menu items (for making the section selector box)
2144 */
2145 function getMenu($id) {
2146 if ($this->conf['show.']['LxALLtypes']) {
2147 $output = Array();
2148 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', 'pid='.intval($id).$this->cObj->enableFields('pages'), '', 'sorting');
2149 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2150 $output[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
2151 }
2152 return $output;
2153 } else {
2154 return $GLOBALS['TSFE']->sys_page->getMenu($id);
2155 }
2156 }
2157
2158 /**
2159 * Returns if an item type is a multipage item type
2160 *
2161 * @param string Item type
2162 * @return boolean True if multipage capable
2163 */
2164 function multiplePagesType($item_type) {
2165 return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2166 }
2167
2168 /**
2169 * Converts the input string from utf-8 to the backend charset.
2170 *
2171 * @param string String to convert (utf-8)
2172 * @return string Converted string (backend charset if different from utf-8)
2173 */
2174 function utf8_to_currentCharset($str) {
2175 return $GLOBALS['TSFE']->csConv($str,'utf-8');
2176 }
2177
2178 /**
2179 * Returns an object reference to the hook object if any
2180 *
2181 * @param string Name of the function you want to call / hook key
2182 * @return object Hook object, if any. Otherwise null.
2183 */
2184 function &hookRequest($functionName) {
2185 global $TYPO3_CONF_VARS;
2186
2187 // Hook: menuConfig_preProcessModMenu
2188 if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2189 $hookObj = &t3lib_div::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2190 if (method_exists ($hookObj, $functionName)) {
2191 $hookObj->pObj = &$this;
2192 return $hookObj;
2193 }
2194 }
2195 }
2196 }
2197
2198
2199 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']) {
2200 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']);
2201 }
2202 ?>