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