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