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