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