2 /***************************************************************
5 * (c) 2001-2005 Kasper Skaarhoj (kasperYYYY@typo3.com)
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.
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.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * Index search frontend
32 * Creates a searchform for indexed search. Indexing must be enabled
33 * for this to make sense.
35 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
36 * @co-author Christian Jul Jensen <christian@typo3.com>
39 * [CLASS/FUNCTION INDEX of SCRIPT]
43 * 121: class tx_indexedsearch extends tslib_pibase
44 * 165: function main($content, $conf)
45 * 198: function initialize()
46 * 381: function getSearchWords($defOp)
47 * 415: function procSearchWordsByLexer($SWArr)
49 * SECTION: Main functions
50 * 459: function doSearch($sWArr)
51 * 492: function getResultRows($sWArr)
52 * 562: function getResultRows_SQLpointer($sWArr)
53 * 582: function getDisplayResults($sWArr, $resData)
54 * 633: function compileResult($resultRows)
56 * SECTION: Searching functions (SQL)
57 * 726: function getPhashList($sWArr)
58 * 827: function execPHashListQuery($wordSel,$plusQ='')
59 * 847: function sectionTableWhere()
60 * 894: function mediaTypeWhere()
61 * 919: function languageWhere()
62 * 931: function execFinalQuery($list)
63 * 1069: function checkResume($row)
64 * 1116: function isDescending($inverse=FALSE)
65 * 1130: function writeSearchStat($sWArr,$count,$pt)
67 * SECTION: HTML output functions
68 * 1181: function makeSearchForm($optValues)
69 * 1253: function printRules()
70 * 1269: function printResultSectionLinks()
71 * 1291: function makeSectionHeader($id,$sectionTitleLinked,$countResultRows)
72 * 1307: function printResultRow($row, $headerOnly=0)
73 * 1382: function pi_list_browseresults($showResultCount=1,$addString='',$addPart='')
75 * SECTION: Support functions for HTML output (with a minimum of fixed markup)
76 * 1445: function prepareResultRowTemplateData($row, $headerOnly)
77 * 1489: function tellUsWhatIsSeachedFor($sWArr)
78 * 1523: function wrapSW($str)
79 * 1535: function renderSelectBox($name,$value,$optValues)
80 * 1558: function makePointerSelector_link($str,$p)
81 * 1571: function makeItemTypeIcon($it,$alt='',$specRowConf)
82 * 1613: function makeRating($row)
83 * 1658: function makeDescription($row,$noMarkup=0,$lgd=180)
84 * 1688: function markupSWpartsOfString($str)
85 * 1768: function makeTitle($row)
86 * 1792: function makeInfo($row,$tmplArray)
87 * 1817: function getSpecialConfigForRow($row)
88 * 1841: function makeLanguageIndication($row)
89 * 1878: function makeAccessIndication($id)
90 * 1892: function linkPage($id,$str,$row=array())
91 * 1935: function getRootLine($id,$pathMP='')
92 * 1950: function getFirstSysDomainRecordForPage($id)
93 * 1963: function getPathFromPageId($id,$pathMP='')
94 * 2015: function getMenu($id)
95 * 2034: function multiplePagesType($item_type)
96 * 2044: function utf8_to_currentCharset($str)
97 * 2054: function &hookRequest($functionName)
100 * (This index is automatically created/updated by the extension "extdeveval")
106 require_once(PATH_tslib
.'class.tslib_pibase.php');
107 require_once(PATH_tslib
.'class.tslib_search.php');
108 require_once(t3lib_extMgm
::extPath('indexed_search').'class.indexer.php');
112 * Index search frontend
114 * Creates a searchform for indexed search. Indexing must be enabled
115 * for this to make sense.
118 * @subpackage tx_indexedsearch
119 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
121 class tx_indexedsearch
extends tslib_pibase
{
122 var $prefixId = 'tx_indexedsearch'; // Same as class name
123 var $scriptRelPath = 'pi/class.tx_indexedsearch.php'; // Path to this script relative to the extension dir.
124 var $extKey = 'indexed_search'; // The extension key.
126 var $join_pages = 0; // See document for info about this flag...
127 var $defaultResultNumber = 10;
129 var $operator_translate_table = Array ( // case-sensitive. Defines the words, which will be operators between words
132 Array ('-' , 'AND NOT'),
134 # Array ('AND' , 'AND'),
135 # Array ('OR' , 'OR'),
136 # Array ('NOT' , 'AND NOT'),
140 var $wholeSiteIdList = 0; // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
143 var $sWArr = array(); // Search Words and operators
144 var $optValues = array(); // Selector box values for search configuration form
145 var $firstRow = Array(); // Will hold the first row in result - used to calculate relative hit-ratings.
147 var $cache_path = array(); // Caching of page path
148 var $cache_rl = array(); // Caching of root line data
149 var $fe_groups_required = array(); // Required fe_groups memberships for display of a result.
150 var $domain_records = array(); // Domain records (?)
151 var $wSelClauses = array(); // Select clauses for individual words
152 var $resultSections = array(); // Page tree sections for search result.
153 var $external_parsers = array(); // External parser objects
154 var $iconFileNameCache = array(); // Storage of icons....
155 var $lexerObj; // Lexer object
156 var $templateCode; // Will hold the content of $conf['templateFile']
160 * Main function, called from TypoScript as a USER_INT object.
162 * @param string Content input, ignore (just put blank string)
163 * @param array TypoScript configuration of the plugin!
164 * @return string HTML code for the search form / result display.
166 function main($content, $conf) {
171 $this->pi_setPiVarDefaults();
173 // Initialize the indexer-class - just to use a few function (for making hashes)
174 $this->indexerObj
= t3lib_div
::makeInstance('tx_indexedsearch_indexer');
180 // If there were any search words entered...
181 if (is_array($this->sWArr
)) {
182 $content = $this->doSearch($this->sWArr
);
185 // Finally compile all the content, form, messages and results:
186 $content = $this->makeSearchForm($this->optValues
).
190 return $this->pi_wrapInBaseClass($content);
194 * Initialize internal variables, especially selector box values for the search form and search words
198 function initialize() {
199 global $TYPO3_CONF_VARS;
201 // Initialize external document parsers for icon display and other soft operations
202 if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'])) {
203 foreach($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
204 $this->external_parsers
[$extension] = &t3lib_div
::getUserObj($_objRef);
206 // Init parser and if it returns false, unset its entry again:
207 if (!$this->external_parsers
[$extension]->softInit($extension)) {
208 unset($this->external_parsers
[$extension]);
213 // Init lexer (used to post-processing of search words)
214 $lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ?
215 $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] :
216 'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
217 $this->lexerObj
= &t3lib_div
::getUserObj($lexerObjRef);
219 // If "_sections" is set, this value overrides any existing value.
220 if ($this->piVars
['_sections']) $this->piVars
['sections'] = $this->piVars
['_sections'];
222 // Add previous search words to current
223 if ($this->piVars
['sword_prev_include'] && $this->piVars
['sword_prev']) {
224 $this->piVars
['sword'] = trim($this->piVars
['sword_prev']).' '.$this->piVars
['sword'];
227 $this->piVars
['results'] = t3lib_div
::intInRange($this->piVars
['results'],1,100000,$this->defaultResultNumber
);
229 // Selector-box values defined here:
230 $this->optValues
= Array(
232 '0' => $this->pi_getLL('opt_type_0'),
233 '1' => $this->pi_getLL('opt_type_1'),
234 '2' => $this->pi_getLL('opt_type_2'),
235 '3' => $this->pi_getLL('opt_type_3'),
236 '10' => $this->pi_getLL('opt_type_10'),
237 '20' => $this->pi_getLL('opt_type_20'),
240 '0' => $this->pi_getLL('opt_defOp_0'),
241 '1' => $this->pi_getLL('opt_defOp_1'),
244 '0' => $this->pi_getLL('opt_sections_0'),
245 '-1' => $this->pi_getLL('opt_sections_-1'),
246 '-2' => $this->pi_getLL('opt_sections_-2'),
247 '-3' => $this->pi_getLL('opt_sections_-3'),
248 // Here values like "rl1_" and "rl2_" + a rootlevel 1/2 id can be added to perform searches in rootlevel 1+2 specifically. The id-values can even be commaseparated. Eg. "rl1_1,2" would search for stuff inside pages on menu-level 1 which has the uid's 1 and 2.
251 '-1' => $this->pi_getLL('opt_media_-1'),
252 '0' => $this->pi_getLL('opt_media_0'),
253 '-2' => $this->pi_getLL('opt_media_-2'),
256 'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
257 'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
258 'rank_first' => $this->pi_getLL('opt_order_rank_first'),
259 'rank_count' => $this->pi_getLL('opt_order_rank_count'),
260 'mtime' => $this->pi_getLL('opt_order_mtime'),
261 'title' => $this->pi_getLL('opt_order_title'),
262 'crdate' => $this->pi_getLL('opt_order_crdate'),
265 'sections' => $this->pi_getLL('opt_group_sections'),
266 'flat' => $this->pi_getLL('opt_group_flat'),
269 -1 => $this->pi_getLL('opt_lang_-1'),
270 0 => $this->pi_getLL('opt_lang_0'),
273 '0' => $this->pi_getLL('opt_desc_0'),
274 '1' => $this->pi_getLL('opt_desc_1'),
284 // Add media to search in:
285 if (strlen(trim($this->conf
['search.']['mediaList']))) {
286 $mediaList = implode(',', t3lib_div
::trimExplode(',', $this->conf
['search.']['mediaList'], 1));
288 foreach($this->external_parsers
as $extension => $obj) {
289 // Skip unwanted extensions
290 if ($mediaList && !t3lib_div
::inList($mediaList, $extension)) { continue; }
292 if ($name = $obj->searchTypeMediaTitle($extension)) {
293 $this->optValues
['media'][$extension] = $this->pi_getLL('opt_sections_'.$extension,$name);
297 // Add operators for various languages
298 // Converts the operators to UTF-8 and lowercase
299 $this->operator_translate_table
[] = Array($GLOBALS['TSFE']->csConvObj
->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj
->utf8_encode($this->pi_getLL('local_operator_AND'), $GLOBALS['TSFE']->renderCharset
),'toLower') , 'AND');
300 $this->operator_translate_table
[] = Array($GLOBALS['TSFE']->csConvObj
->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj
->utf8_encode($this->pi_getLL('local_operator_OR'), $GLOBALS['TSFE']->renderCharset
),'toLower') , 'OR');
301 $this->operator_translate_table
[] = Array($GLOBALS['TSFE']->csConvObj
->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj
->utf8_encode($this->pi_getLL('local_operator_NOT'), $GLOBALS['TSFE']->renderCharset
),'toLower') , 'AND NOT');
303 // This is the id of the site root. This value may be a commalist of integer (prepared for this)
304 $this->wholeSiteIdList
= intval($GLOBALS['TSFE']->config
['rootLine'][0]['uid']);
306 // Creating levels for section menu:
307 // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
308 if ($this->conf
['show.']['L1sections']) {
309 $firstLevelMenu = $this->getMenu($this->wholeSiteIdList
);
310 while(list($kk,$mR) = each($firstLevelMenu)) {
311 if ($mR['doktype']!=5) {
312 $this->optValues
['sections']['rl1_'.$mR['uid']] = trim($this->pi_getLL('opt_RL1').' '.$mR['title']);
313 if ($this->conf
['show.']['L2sections']) {
314 $secondLevelMenu = $this->getMenu($mR['uid']);
315 while(list($kk2,$mR2) = each($secondLevelMenu)) {
316 if ($mR['doktype']!=5) {
317 $this->optValues
['sections']['rl2_'.$mR2['uid']] = trim($this->pi_getLL('opt_RL2').' '.$mR2['title']);
318 } else unset($secondLevelMenu[$kk2]);
320 $this->optValues
['sections']['rl2_'.implode(',',array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
322 } else unset($firstLevelMenu[$kk]);
324 $this->optValues
['sections']['rl1_'.implode(',',array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
327 // Setting the list of root PIDs for the search. Notice, these page IDs MUST have a TypoScript template with root flag on them! Basically this list is used to select on the "rl0" field and page ids are registered as "rl0" only if a TypoScript template record with root flag is there.
328 // This happens AFTER the use of $this->wholeSiteIdList above because the above will then fetch the menu for the CURRENT site - regardless of this kind of searching here. Thus a general search will lookup in the WHOLE database while a specific section search will take the current sections...
329 if ($this->conf
['search.']['rootPidList']) {
330 $this->wholeSiteIdList
= implode(',',t3lib_div
::intExplode(',',$this->conf
['search.']['rootPidList']));
334 $this->templateCode
= $this->cObj
->fileResource($this->conf
['templateFile']);
336 // Add search languages:
337 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1'.$this->cObj
->enableFields('sys_language'));
338 while($lR = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
339 $this->optValues
['lang'][$lR['uid']] = $lR['title'];
342 // Calling hook for modification of initialized content
343 if ($hookObj = &$this->hookRequest('initialize_postProc')) {
344 $hookObj->initialize_postProc();
347 // Default values set:
348 // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
349 foreach($this->optValues
as $kk => $vv) {
350 if (!isset($this->piVars
[$kk])) {
352 $this->piVars
[$kk] = key($vv);
357 if (is_array($this->conf
['blind.'])) {
358 foreach($this->conf
['blind.'] as $kk => $vv) {
360 foreach($vv as $kkk => $vvv) {
361 if (!is_array($vvv) && $vvv && is_array($this->optValues
[substr($kk,0,-1)])) {
362 unset($this->optValues
[substr($kk,0,-1)][$kkk]);
365 } elseif ($vv) { // If value is not set, unset the option array.
366 unset($this->optValues
[$kk]);
371 // This gets the search-words into the $sWArr:
372 $this->sWArr
= $this->getSearchWords($this->piVars
['defOp']);
376 * Splits the search word input into an array where each word is represented by an array with key "sword" holding the search word and key "oper" holds the SQL operator (eg. AND, OR)
378 * Only words with 2 or more characters are accepted
379 * Max 200 chars total
380 * Space is used to split words, "" can be used search for a whole string (not indexed search then)
381 * AND, OR and NOT are prefix words, overruling the default operator
382 * +/|/- equals AND, OR and NOT as operators.
383 * All search words are converted to lowercase.
385 * $defOp is the default operator. 1=OR, 0=AND
387 * @param boolean If true, the default operator will be OR, not AND
388 * @return array Returns array with search words if any found
390 function getSearchWords($defOp) {
391 // Shorten search-word string to max 200 bytes (does NOT take multibyte charsets into account - but never mind, shortening the string here is only a run-away feature!)
392 $inSW = substr($this->piVars
['sword'],0,200);
394 // Convert to UTF-8 + conv. entities (was also converted during indexing!)
395 $inSW = $GLOBALS['TSFE']->csConvObj
->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset
);
396 $inSW = $GLOBALS['TSFE']->csConvObj
->entities_to_utf8($inSW,TRUE);
398 if ($hookObj = &$this->hookRequest('getSearchWords')) {
399 return $hookObj->getSearchWords_splitSWords($inSW, $defOp);
402 if ($this->piVars
['type']==20) {
403 return array(array('sword'=>trim($inSW), 'oper'=>'AND'));
405 $search = t3lib_div
::makeInstance('tslib_search');
406 $search->default_operator
= $defOp==1 ?
'OR' : 'AND';
407 $search->operator_translate_table
= $this->operator_translate_table
;
408 $search->register_and_explode_search_string($inSW);
410 if (is_array($search->sword_array
)) {
411 return $this->procSearchWordsByLexer($search->sword_array
);
418 * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
419 * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
421 * @param array Search word array
422 * @return array Search word array, processed through lexer
424 function procSearchWordsByLexer($SWArr) {
426 // Init output variable:
429 // Traverse the search word array:
430 foreach($SWArr as $wordDef) {
431 if (!strstr($wordDef['sword'],' ')) { // No space in word (otherwise it might be a sentense in quotes like "there is").
432 // SPlit the search word by lexer:
433 $res = $this->lexerObj
->split2Words($wordDef['sword']);
435 // Traverse lexer result and add all words again:
436 foreach($res as $word) {
437 $newSWArr[] = array('sword'=>$word, 'oper'=>$wordDef['oper']);
440 $newSWArr[] = $wordDef;
456 /*****************************
460 *****************************/
463 * Performs the search, the display and writing stats
465 * @param array Search words in array, see ->getSearchWords() for details
466 * @return string HTML for result display.
468 function doSearch($sWArr) {
471 $pt1 = t3lib_div
::milliseconds();
472 if ($hookObj = &$this->hookRequest('getResultRows')) {
473 $resData = $hookObj->getResultRows($sWArr);
475 $resData = $this->getResultRows($sWArr);
478 // Display search results:
479 $pt2 = t3lib_div
::milliseconds();
480 if ($hookObj = &$this->hookRequest('getDisplayResults')) {
481 $content = $hookObj->getDisplayResults($sWArr, $resData);
483 $content = $this->getDisplayResults($sWArr, $resData);
486 $pt3 = t3lib_div
::milliseconds();
488 // Write search statistics
489 $this->writeSearchStat($sWArr,$resData['count'],array($pt1,$pt2,$pt3));
496 * Get search result rows / data from database. Returned as data in array.
498 * @param array Search word array
499 * @return array False if no result, otherwise an array with keys for first row, result rows and total number of results found.
501 function getResultRows($sWArr) {
503 // Getting SQL result pointer:
504 $GLOBALS['TT']->push('Searching result');
505 $res = $this->getResultRows_SQLpointer($sWArr);
506 $GLOBALS['TT']->pull();
508 // Organize and process result:
511 $count = $GLOBALS['TYPO3_DB']->sql_num_rows($res); // Total search-result count
512 $pointer = t3lib_div
::intInRange($this->piVars
['pointer'], 0, floor($count/$this->piVars
['results'])); // The pointer is set to the result page that is currently being viewed
514 // Initialize result accumulation variables:
515 $c = 0; // Result pointer: Counts up the position in the current search-result
516 $grouping_phashes = array(); // Used to filter out duplicates.
517 $grouping_chashes = array(); // Used to filter out duplicates BASED ON cHash.
518 $firstRow = Array(); // Will hold the first row in result - used to calculate relative hit-ratings.
519 $resultRows = Array(); // Will hold the results rows for display.
521 // Now, traverse result and put the rows to be displayed into an array
522 // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
523 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
530 $row['show_resume'] = $this->checkResume($row); // Tells whether we can link directly to a document or not (depends on possible right problems)
532 $phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
533 $chashGr = !in_array($row['contentHash'].'.'.$row['data_page_id'], $grouping_chashes);
534 if ($phashGr && $chashGr) {
535 if ($row['show_resume'] ||
$this->conf
['show.']['forbiddenRecords']) { // Only if the resume may be shown are we going to filter out duplicates...
536 if (!$this->multiplePagesType($row['item_type'])) { // Only on documents which are not multiple pages documents
537 $grouping_phashes[] = $row['phash_grouping'];
539 $grouping_chashes[] = $row['contentHash'].'.'.$row['data_page_id'];
541 $c++
; // Increase the result pointer
543 // All rows for display is put into resultRows[]
544 if ($c > $pointer * $this->piVars
['results']) {
545 $row['result_number'] = $c;
546 $resultRows[] = $row;
547 // This may lead to a problem: If the result check is not stopped here, the search will take longer. However the result counter will not filter out grouped cHashes/pHashes that were not processed yet.
548 if (($c+
1) > ($pointer+
1)*$this->piVars
['results']) break;
551 $count--; // Skip this row if the user cannot view it (missing permission)
554 $count--; // For each time a phash_grouping document is found (which is thus not displayed) the search-result count is reduced, so that it matches the number of rows displayed.
559 'resultRows' => $resultRows,
560 'firstRow' => $firstRow,
563 } else { // No results found:
569 * Gets a SQL result pointer to traverse for the search records.
571 * @param array Search words
574 function getResultRows_SQLpointer($sWArr) {
575 // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
576 $list = $this->getPhashList($sWArr);
578 // Perform SQL Search / collection of result rows array:
581 $GLOBALS['TT']->push('execFinalQuery');
582 $res = $this->execFinalQuery($list);
583 $GLOBALS['TT']->pull();
591 * Compiles the HTML display of the incoming array of result rows.
593 * @param array Search words array (for display of text describing what was searched for)
594 * @param array Array with result rows, count, first row.
595 * @return string HTML content to display result.
597 function getDisplayResults($sWArr, $resData) {
598 // Perform display of result rows array:
600 $GLOBALS['TT']->push('Display Final result');
602 // Set first selected row (for calculation of ranking later)
603 $this->firstRow
= $resData['firstRow'];
605 // Result display here:
607 $rowcontent.= $this->compileResult($resData['resultRows']);
610 if ($resData['count']) {
611 $this->internal
['res_count'] = $resData['count'];
612 $this->internal
['results_at_a_time'] = $this->piVars
['results'];
613 $this->internal
['maxPages'] = t3lib_div
::intInRange($this->conf
['search.']['page_links'],1,100,10);
614 $addString = ($resData['count']&&$this->piVars
['group']=='sections' ?
' '.sprintf($this->pi_getLL(count($this->resultSections
)>1?
'inNsections':'inNsection'),count($this->resultSections
)):'');
615 $browseBox1 = $this->pi_list_browseresults(1,$addString,$this->printResultSectionLinks());
616 $browseBox2 = $this->pi_list_browseresults(0);
619 // Browsing nav, bottom.
620 if ($resData['count']) {
621 $content = $browseBox1.$rowcontent.$browseBox2;
623 $content = '<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
626 $GLOBALS['TT']->pull();
628 $content.='<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
631 // Print a message telling which words we searched for, and in which sections etc.
632 $what = $this->tellUsWhatIsSeachedFor($sWArr).
633 (substr($this->piVars
['sections'],0,2)=='rl'?
' '.$this->pi_getLL('inSection','',1).' "'.substr($this->getPathFromPageId(substr($this->piVars
['sections'],4)),1).'"':'');
634 $what = '<div'.$this->pi_classParam('whatis').'><p>'.$what.'</p></div>';
635 $content = $what.$content;
642 * Takes the array with resultrows as input and returns the result-HTML-code
643 * Takes the "group" var into account: Makes a "section" or "flat" display.
645 * @param array Result rows
646 * @return string HTML
648 function compileResult($resultRows) {
651 // Transfer result rows to new variable, performing some mapping of sub-results etc.
652 $newResultRows = array();
653 foreach($resultRows as $row) {
654 $id = md5($row['phash_grouping']);
655 if (is_array($newResultRows[$id])) {
656 if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) { // swapping:
659 $subrows = $newResultRows[$id]['_sub'];
660 unset($newResultRows[$id]['_sub']);
661 $subrows[] = $newResultRows[$id];
664 $newResultRows[$id] = $row;
665 $newResultRows[$id]['_sub'] = $subrows;
666 } else $newResultRows[$id]['_sub'][] = $row;
668 $newResultRows[$id] = $row;
671 $resultRows = $newResultRows;
674 switch($this->piVars
['group']) {
677 $rl2flag = substr($this->piVars
['sections'],0,2)=='rl';
679 foreach($resultRows as $row) {
680 $id = $row['rl0'].'-'.$row['rl1'].($rl2flag?
'-'.$row['rl2']:'');
681 $sections[$id][] = $row;
684 $this->resultSections
= array();
686 foreach($sections as $id => $resultRows) {
687 $rlParts = explode('-',$id);
688 $theId = $rlParts[2] ?
$rlParts[2] : ($rlParts[1]?
$rlParts[1]:$rlParts[0]);
689 $theRLid = $rlParts[2] ?
'rl2_'.$rlParts[2]:($rlParts[1]?
'rl1_'.$rlParts[1]:'0');
691 $sectionName = substr($this->getPathFromPageId($theId),1);
692 if (!trim($sectionName)) {
693 $sectionTitleLinked = $this->pi_getLL('unnamedSection','',1).':';
695 $onclick = 'document.'.$this->prefixId
.'[\''.$this->prefixId
.'[_sections]\'].value=\''.$theRLid.'\';document.'.$this->prefixId
.'.submit();return false;';
696 $sectionTitleLinked = '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.htmlspecialchars($sectionName).':</a>';
698 $this->resultSections
[$id] = array($sectionName,count($resultRows));
700 // Add content header:
701 $content.= $this->makeSectionHeader($id,$sectionTitleLinked,count($resultRows));
703 // Render result rows:
704 foreach($resultRows as $row) {
705 $content.= $this->printResultRow($row);
710 foreach($resultRows as $row) {
711 $content.= $this->printResultRow($row);
715 return '<div'.$this->pi_classParam('res').'>'.$content.'</div>';
728 /***********************************
730 * Searching functions (SQL)
732 ***********************************/
735 * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the sWArr array.
736 * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
738 * @param array Search word array
739 * @return string List of integers
741 function getPhashList($sWArr) {
743 // Initialize variables:
745 $totalHashList = array(); // This array accumulates the phash-values
746 $this->wSelClauses
= array();
748 // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
749 foreach($sWArr as $k => $v) {
750 // Making the query for a single search word based on the search-type
751 $sWord = $v['sword']; // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$v['sword'],'toLower'); // lower-case all of them...
752 $theType = (string)$this->piVars
['type'];
753 if (strstr($sWord,' ')) $theType = 20; // If there are spaces in the search-word, make a full text search instead.
755 $GLOBALS['TT']->push('SearchWord "'.$sWord.'" - $theType='.$theType);
760 // Perform search for word:
763 $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
764 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
767 $wSel = "IW.baseword LIKE '".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
768 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
771 $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."'";
772 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
775 $wSel = 'IW.metaphone = '.$this->indexerObj
->metaphone($sWord);
776 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
779 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
781 'index_section ISEC, index_fulltext IFT',
782 'IFT.fulltextdata LIKE \'%'.$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_fulltext').'%\' AND
783 ISEC.phash = IFT.phash
784 '.$this->sectionTableWhere(),
789 if ($this->piVars
['type']==20) $this->piVars
['order'] = 'mtime'; // If there is a fulltext search for a sentence there is a likeliness that sorting cannot be done by the rankings from the rel-table (because no relations will exist for the sentence in the word-table). So therefore mtime is used instaed. It is not required, but otherwise some hits may be left out.
792 $wSel = 'IW.wid = '.$hash = $this->indexerObj
->md5inthash($sWord);
793 $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
797 // Accumulate the word-select clauses
798 $this->wSelClauses
[] = $wSel;
800 // If there was a query to do, then select all phash-integers which resulted from this.
803 // Get phash list by searching for it:
804 $phashList = array();
805 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
806 $phashList[] = $row['phash'];
808 $GLOBALS['TYPO3_DB']->sql_free_result($res);
810 // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
814 $totalHashList = array_unique(array_merge($phashList,$totalHashList));
817 $totalHashList = array_diff($totalHashList,$phashList);
820 $totalHashList = array_intersect($totalHashList,$phashList);
824 $totalHashList = $phashList; // First search
828 $GLOBALS['TT']->pull();
832 return implode(',',$totalHashList);
836 * Returns a query which selects the search-word from the word/rel tables.
838 * @param string WHERE clause selecting the word from phash
839 * @param string Additional AND clause in the end of the query.
840 * @return pointer SQL result pointer
842 function execPHashListQuery($wordSel,$plusQ='') {
843 return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
850 AND ISEC.phash = IR.phash
851 '.$this->sectionTableWhere().'
858 * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
860 * @return string AND clause for selection of section in database.
862 function sectionTableWhere() {
863 $out = $this->wholeSiteIdList
<0 ?
'' : 'AND ISEC.rl0 IN ('.$this->wholeSiteIdList
.')';
866 if (substr($this->piVars
['sections'],0,4)=='rl1_') {
867 $list = implode(',',t3lib_div
::intExplode(',',substr($this->piVars
['sections'],4)));
868 $out.= 'AND ISEC.rl1 IN ('.$list.')';
870 } elseif (substr($this->piVars
['sections'],0,4)=='rl2_') {
871 $list = implode(',',t3lib_div
::intExplode(',',substr($this->piVars
['sections'],4)));
872 $out.= 'AND ISEC.rl2 IN ('.$list.')';
874 } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
875 // Traversing user configured fields to see if any of those are used to limit search to a section:
876 foreach($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
877 if (substr($this->piVars
['sections'],0,strlen($fieldName)+
1)==$fieldName.'_') {
878 $list = implode(',',t3lib_div
::intExplode(',',substr($this->piVars
['sections'],strlen($fieldName)+
1)));
879 $out.= 'AND ISEC.'.$fieldName.' IN ('.$list.')';
886 // If no match above, test the static types:
888 switch((string)$this->piVars
['sections']) {
889 case '-1': // '-1' => 'Only this page',
890 $out.= ' AND ISEC.page_id='.$GLOBALS['TSFE']->id
;
892 case '-2': // '-2' => 'Top + level 1',
893 $out.= ' AND ISEC.rl2=0';
895 case '-3': // '-3' => 'Level 2 and out',
896 $out.= ' AND ISEC.rl2>0';
905 * Returns AND statement for selection of media type
907 * @return string AND statement for selection of media type
909 function mediaTypeWhere() {
911 switch((string)$this->piVars
['media']) {
912 case '0': // '0' => 'Kun TYPO3 sider',
913 $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
915 case '-2': // All external documents
916 $out = 'AND IP.item_type!='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
918 case '-1': // All content
922 $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars
['media'], 'index_phash');
930 * Returns AND statement for selection of langauge
932 * @return string AND statement for selection of langauge
934 function languageWhere() {
935 if ($this->piVars
['lang']>=0) { // -1 is the same as ALL language.
936 return 'AND IP.sys_language_uid='.intval($this->piVars
['lang']);
941 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
943 * @param string List of phash integers which match the search.
944 * @return pointer Query result pointer
946 function execFinalQuery($list) {
948 // Setting up methods of filtering results based on page types, access, etc.
952 // Calling hook for alternative creation of page ID list
953 if ($hookObj = &$this->hookRequest('execFinalQuery_idList')) {
954 $page_where = $hookObj->execFinalQuery_idList($list);
955 } elseif ($this->join_pages
) { // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
958 $page_where = 'pages.uid = ISEC.page_id
959 '.$this->cObj
->enableFields('pages').'
960 AND pages.no_search=0
961 AND pages.doktype<200
963 } elseif ($this->wholeSiteIdList
>=0) { // Collecting all pages IDs in which to search; filtering out ALL pages that are not accessible due to enableFields. Does NOT look for "no_search" field!
964 $siteIdNumbers = t3lib_div
::intExplode(',',$this->wholeSiteIdList
);
966 while(list(,$rootId)=each($siteIdNumbers)) {
967 $id_list[] = $this->cObj
->getTreeList($rootId,9999,0,0,'','').$rootId;
969 $page_where = 'ISEC.page_id IN ('.implode(',',$id_list).')';
970 } else { // Disable everything... (select all)
971 $page_where = ' 1=1 ';
975 // If any of the ranking sortings are selected, we must make a join with the word/rel-table again, because we need to calculate ranking based on all search-words found.
976 if (substr($this->piVars
['order'],0,5)=='rank_') {
978 OK there were some fancy calculations promoted by Graeme Merrall:
980 "However, regarding relevance you probably want to look at something like
981 Salton's formula which is a good easy way to measure relevance.
982 Oracle Intermedia uses this and it's pretty simple:
983 Score can be between 0 and 100, but the top-scoring document in the query
984 will not necessarily have a score of 100 -- scoring is relative, not
985 absolute. This means that scores are not comparable across indexes, or even
986 across different queries on the same index. Score for each document is
987 computed using the standard Salton formula:
991 Where f is the frequency of the search term in the document, N is the total
992 number of rows in the table, and n is the number of rows which contain the
993 search term. This is converted into an integer in the range 0 - 100.
995 There's a good doc on it at
996 http://ls6-www.informatik.uni-dortmund.de/bib/fulltext/ir/Pfeifer:97/
997 although it may be a little complex for what you require so just pick the
1001 However I chose not to go with this for several reasons.
1002 I do not claim that my ways of calculating importance here is the best.
1003 ANY (better) suggestion for ranking calculation is accepted! (as long as they are shipped with tested code in exchange for this.)
1006 switch($this->piVars
['order']) {
1007 case 'rank_flag': // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1008 // The ordering is refined with the frequency sum as well.
1009 $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1010 $orderBy = 'order_val1'.$this->isDescending().',order_val2'.$this->isDescending();
1012 case 'rank_first': // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1013 $grsel = 'AVG(IR.first) AS order_val';
1014 $orderBy = 'order_val'.$this->isDescending(1);
1016 case 'rank_count': // Number of words found
1017 $grsel = 'SUM(IR.count) AS order_val';
1018 $orderBy = 'order_val'.$this->isDescending();
1020 default: // Frequency sum. I'm not sure if this is the best way to do it (make a sum...). Or should it be the average?
1021 $grsel = 'SUM(IR.freq) AS order_val';
1022 $orderBy = 'order_val'.$this->isDescending();
1026 // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1027 $wordSel='('.implode(' OR ',$this->wSelClauses
).') AND ';
1029 return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1038 IP.phash IN ('.$list.') '.
1039 $this->mediaTypeWhere().' '.
1040 $this->languageWhere().'
1042 AND ISEC.phash = IR.phash
1043 AND IP.phash = IR.phash
1045 'IP.phash,ISEC.phash,ISEC.phash_t3,ISEC.rl0,ISEC.rl1,ISEC.rl2 ,ISEC.page_id,ISEC.uniqid,IP.phash_grouping,IP.data_filename ,IP.data_page_id ,IP.data_page_reg1,IP.data_page_type,IP.data_page_mp,IP.gr_list,IP.item_type,IP.item_title,IP.item_description,IP.item_mtime,IP.tstamp,IP.item_size,IP.contentHash,IP.crdate,IP.parsetime,IP.sys_language_uid,IP.item_crdate,IP.cHashParams,IP.externalUrl,IP.recordUid,IP.freeIndexUid',
1048 } else { // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1051 switch((string)$this->piVars
['order']) {
1053 $orderBy = 'IP.item_title'.$this->isDescending();
1056 $orderBy = 'IP.item_crdate'.$this->isDescending();
1059 $orderBy = 'IP.item_mtime'.$this->isDescending();
1063 return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1065 'index_phash IP,index_section ISEC'.$page_join,
1066 'IP.phash IN ('.$list.') '.
1067 $this->mediaTypeWhere().' '.
1068 $this->languageWhere().'
1069 AND IP.phash = ISEC.phash
1071 'IP.phash,ISEC.phash,ISEC.phash_t3,ISEC.rl0,ISEC.rl1,ISEC.rl2 ,ISEC.page_id,ISEC.uniqid,IP.phash_grouping,IP.data_filename ,IP.data_page_id ,IP.data_page_reg1,IP.data_page_type,IP.data_page_mp,IP.gr_list,IP.item_type,IP.item_title,IP.item_description,IP.item_mtime,IP.tstamp,IP.item_size,IP.contentHash,IP.crdate,IP.parsetime,IP.sys_language_uid,IP.item_crdate,IP.cHashParams,IP.externalUrl,IP.recordUid,IP.freeIndexUid',
1078 * Checking if the resume can be shown for the search result (depending on whether the rights are OK)
1079 * ? Should it also check for gr_list "0,-1"?
1081 * @param array Result row array.
1082 * @return boolean Returns true if resume can safely be shown
1084 function checkResume($row) {
1086 // If the record is indexed by an indexing configuration, just show it.
1087 // At least this is needed for external URLs and files.
1088 // For records we might need to extend this - for instance block display if record is access restricted.
1089 if ($row['freeIndexUid']) {
1093 // Evaluate regularly indexed pages based on item_type:
1094 if ($row['item_type']) { // External media:
1095 // For external media we will check the access of the parent page on which the media was linked from.
1096 // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1097 // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1098 // If this is NOT found, there is still a theoretical possibility that another user accessible page would display a link, so maybe the resume of such a document here may be unjustified hidden. But better safe than sorry.
1099 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash='.intval($row['phash_t3']).' AND gr_list='.$GLOBALS['TYPO3_DB']->fullQuoteStr($GLOBALS['TSFE']->gr_list
, 'index_grlist'));
1100 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1101 #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' YES - ('.$GLOBALS['TSFE']->gr_list.")!",1);
1104 #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' NO - ('.$GLOBALS['TSFE']->gr_list.")!",1);
1107 } else { // Ordinary TYPO3 pages:
1108 if (strcmp($row['gr_list'],$GLOBALS['TSFE']->gr_list
)) {
1109 // Selecting for the grlist records belonging to the phash-row where the current users gr_list exists. If it is found it is proof that this user has direct access to the phash-rows content although he did not himself initiate the indexing...
1110 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash='.intval($row['phash']).' AND gr_list='.$GLOBALS['TYPO3_DB']->fullQuoteStr($GLOBALS['TSFE']->gr_list
, 'index_grlist'));
1111 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1112 #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash'].' - YES ('.$GLOBALS['TSFE']->gr_list.")",1);
1115 #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash']." - NOPE",1);
1119 #debug('Resume can be shown, because the document was in fact indexed by this combination of groups!'.$GLOBALS['TSFE']->gr_list.' - '.$row['item_title'].'/'.$row['phash'],1);
1126 * Returns "DESC" or "" depending on the settings of the incoming highest/lowest result order (piVars['desc']
1128 * @param boolean If true, inverse the order which is defined by piVars['desc']
1129 * @return string " DESC" or ""
1131 function isDescending($inverse=FALSE) {
1132 $desc = $this->piVars
['desc'];
1133 if ($inverse) $desc=!$desc;
1134 return !$desc ?
' DESC':'';
1138 * Write statistics information to database for the search operation
1140 * @param array Search Word array
1141 * @param integer Number of hits
1142 * @param integer Milliseconds the search took
1145 function writeSearchStat($sWArr,$count,$pt) {
1146 $insertFields = array(
1147 'searchstring' => $this->piVars
['sword'],
1148 'searchoptions' => serialize(array($this->piVars
,$sWArr,$pt)),
1149 'feuser_id' => intval($this->fe_user
->user
['uid']), // fe_user id, integer
1150 'cookie' => $this->fe_user
->id
, // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1151 'IP' => t3lib_div
::getIndpEnv('REMOTE_ADDR'), // Remote IP address
1152 'hits' => intval($count), // Number of hits on the search.
1153 'tstamp' => $GLOBALS['EXEC_TIME'] // Time stamp
1156 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
1157 $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
1160 foreach($sWArr as $val) {
1161 $insertFields = array(
1162 'word' => $val['sword'], // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $val['sword'], 'toLower'),
1163 'index_stat_search_id' => $newId,
1164 'tstamp' => $GLOBALS['EXEC_TIME'], // Time stamp
1165 'pageid' => $GLOBALS['TSFE']->id
//search page id for indexed search stats
1168 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
1185 /***********************************
1187 * HTML output functions
1189 ***********************************/
1192 * Make search form HTML
1194 * @param array Value/Labels pairs for search form selector boxes.
1195 * @return string Search form HTML
1197 function makeSearchForm($optValues) {
1198 $html = $this->cObj
->getSubpart($this->templateCode
, '###SEARCH_FORM###');
1200 // Multilangual text
1201 $substituteArray = array('searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style');
1202 foreach ($substituteArray as $marker) {
1203 $markerArray['###FORM_'.strtoupper($marker).'###'] = $this->pi_getLL('form_'.$marker);
1206 $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label');
1208 // Adding search field value
1209 $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars
['sword']);
1211 // Additonal keyword => "Add to current search words"
1212 if ($this->conf
['show.']['clearSearchBox'] && $this->conf
['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1213 $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf
['show.']['clearSearchBox'] ?
'' : $this->piVars
['sword']);
1214 $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars
['sword_prev_include'] ?
' checked="checked"':'';
1215 $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch');
1217 $html = $this->cObj
->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1220 $markerArray['###ACTION_URL###'] = $this->pi_getPageLink($GLOBALS['TSFE']->id
, $GLOBALS['TSFE']->sPre
);
1221 $markerArray['###HIDDEN_VALUE_TYPE###'] = $this->piVars
['type'] ?
1 : 0;
1222 $markerArray['###HIDDEN_VALUE_EXT###'] = $this->piVars
['ext'] ?
1 : 0;
1225 if ($this->piVars
['ext']) {
1228 if ((!is_array($optValues['type']) && !is_array($optValues['defOp'])) ||
($this->conf
['blind.']['type'] && $this->conf
['blind.']['defOp'])) {
1229 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1231 if (is_array($optValues['type']) && !$this->conf
['blind.']['type']) {
1232 $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['type'],$optValues['type']);
1234 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1237 if (is_array($optValues['defOp']) ||
!$this->conf
['blind.']['defOp']) {
1238 $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['defOp'],$optValues['defOp']);
1240 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1245 if ((!is_array($optValues['media']) && !is_array($optValues['lang'])) ||
($this->conf
['blind.']['media'] && $this->conf
['blind.']['lang'])) {
1246 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1248 if (is_array($optValues['media']) && !$this->conf
['blind.']['media']) {
1249 $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['media'],$optValues['media']);
1251 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1254 if (is_array($optValues['lang']) ||
!$this->conf
['blind.']['lang']) {
1255 $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['lang'],$optValues['lang']);
1257 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1262 if (!is_array($optValues['sections']) ||
$this->conf
['blind.']['sections']) {
1263 $html = $this->cObj
->substituteSubpart($html, '###SELECT_SECTION###', '');
1265 $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['sections'],$optValues['sections']);
1269 if (!is_array($optValues['order']) ||
!is_array($optValues['desc']) ||
$this->conf
['blind.']['order']) {
1270 $html = $this->cObj
->substituteSubpart($html, '###SELECT_ORDER###', '');
1272 $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['order'],$optValues['order']);
1273 $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['desc'],$optValues['desc']);
1274 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['results'],$optValues['results']);
1278 if (!is_array($optValues['results']) ||
!is_array($optValues['results']) ||
$this->conf
['blind.']['results']) {
1279 $html = $this->cObj
->substituteSubpart($html, '###SELECT_RESULTS###', '');
1281 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['results'],$optValues['results']);
1285 if (!is_array($optValues['group']) ||
$this->conf
['blind.']['group']) {
1286 $html = $this->cObj
->substituteSubpart($html, '###SELECT_GROUP###', '');
1288 $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars
['group'],$optValues['group']);
1291 if ($this->conf
['blind.']['extResume']) {
1292 $html = $this->cObj
->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1294 $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars
['extResume'] ?
' CHECKED' : '';
1297 } else { // Extended search
1298 $html = $this->cObj
->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1301 if($this->conf
['show.']['advancedSearchLink']) {
1302 $markerArray['###LINKTOOTHERMODE###'] = ($this->piVars
['ext'] ?
1303 '<a href="'.$this->pi_getPageLink($GLOBALS['TSFE']->id
,$GLOBALS['TSFE']->sPre
,array($this->prefixId
.'[ext]'=>0)).'">'.$this->pi_getLL('link_regularSearch').'</a>' :
1304 '<a href="'.$this->pi_getPageLink($GLOBALS['TSFE']->id
,$GLOBALS['TSFE']->sPre
,array($this->prefixId
.'[ext]'=>1)).'">'.$this->pi_getLL('link_advancedSearch').'</a>'
1307 $markerArray['###LINKTOOTHERMODE###'] = '';
1310 $substitutedContent = $this->cObj
->substituteMarkerArrayCached($html, $markerArray, array(), array());
1312 return $substitutedContent;
1315 function renderSelectBoxValues($value,$optValues) {
1316 if (is_array($optValues)) {
1319 foreach($optValues as $k=>$v) {
1320 $sel = (!strcmp($k,$value) ?
' selected' : '');
1321 if ($sel) { $isSelFlag++
; }
1322 $opt[]='<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1325 return implode('',$opt);
1330 * Print the searching rules
1332 * @return string Rules for the search
1334 function printRules() {
1335 if ($this->conf
['show.']['rules']) {
1337 $html = $this->cObj
->getSubpart($this->templateCode
, '###RULES###');
1339 $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header','',1);
1340 $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text','',1)));
1342 $substitutedContent = $this->cObj
->substituteMarkerArrayCached($html, $markerArray, array(), array());
1344 return '<div'.$this->pi_classParam('rules').'>'.$this->cObj
->stdWrap($substitutedContent, $this->conf
['rules_stdWrap.']).'</div>';
1349 * Returns the anchor-links to the sections inside the displayed result rows.
1353 function printResultSectionLinks() {
1354 if (count($this->resultSections
)) {
1357 $html = $this->cObj
->getSubpart($this->templateCode
, '###RESULT_SECTION_LINKS###');
1358 $item = $this->cObj
->getSubpart($this->templateCode
, '###RESULT_SECTION_LINKS_LINK###');
1360 foreach($this->resultSections
as $id => $dat) {
1361 $markerArray = array();
1363 $aBegin = '<a href="'.htmlspecialchars($GLOBALS['TSFE']->anchorPrefix
.'#'.md5($id)).'">';
1364 $aContent = htmlspecialchars(trim($dat[0]) ?
trim($dat[0]) : $this->pi_getLL('unnamedSection')).
1365 ' ('.$dat[1].' '.$this->pi_getLL($dat[1]>1 ?
'word_pages' : 'word_page','',1).')';
1368 $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1370 $links[] = $this->cObj
->substituteMarkerArrayCached($item, $markerArray, array(), array());
1373 $html = $this->cObj
->substituteMarkerArrayCached($html, array('###LINKS###' => implode(chr(10),$links)), array(), array());
1375 return '<div'.$this->pi_classParam('sectionlinks').'>'.$this->cObj
->stdWrap($html, $this->conf
['sectionlinks_stdWrap.']).'</div>';
1380 * Returns the section header of the search result.
1382 * @param string ID for the section (used for anchor link)
1383 * @param string Section title with linked wrapped around
1384 * @param integer Number of results in section
1385 * @return string HTML output
1387 function makeSectionHeader($id, $sectionTitleLinked, $countResultRows) {
1389 $html = $this->cObj
->getSubpart($this->templateCode
, '###SECTION_HEADER###');
1391 $markerArray['###ANCHOR_URL###'] = md5($id);
1392 $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1393 $markerArray['###RESULT_COUNT###'] = $countResultRows;
1394 $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page'.($countResultRows>1 ?
's' : ''));
1396 $substitutedContent = $this->cObj
->substituteMarkerArrayCached($html, $markerArray, array(), array());
1398 return $substitutedContent;
1402 * This prints a single result row, including a recursive call for subrows.
1404 * @param array Search result row
1405 * @param integer 1=Display only header (for sub-rows!), 2=nothing at all
1406 * @return string HTML code
1408 function printResultRow($row, $headerOnly=0) {
1410 // Get template content:
1411 $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1413 if ($hookObj = &$this->hookRequest('printResultRow')) {
1414 return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1417 $html = $this->cObj
->getSubpart($this->templateCode
, '###RESULT_OUTPUT###');
1419 if (!is_array($row['_sub'])) {
1420 $html = $this->cObj
->substituteSubpart($html, '###ROW_SUB###', '');
1424 $html = $this->cObj
->substituteSubpart($html, '###ROW_SHORT###', '');
1426 } elseif ($headerOnly==1) {
1427 $html = $this->cObj
->substituteSubpart($html, '###ROW_LONG###', '');
1428 } elseif ($headerOnly==2) {
1429 $html = $this->cObj
->substituteSubpart($html, '###ROW_SHORT###', '');
1430 $html = $this->cObj
->substituteSubpart($html, '###ROW_LONG###', '');
1433 if (is_array($tmplContent)) {
1434 foreach ($tmplContent AS $k => $v) {
1435 $markerArray['###'.strtoupper($k).'###'] = $v;
1440 $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size');
1441 $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created');
1442 $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified');
1443 $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path');
1445 $html = $this->cObj
->substituteMarkerArrayCached($html, $markerArray, array(), array());
1447 // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1448 if (is_array($row['_sub'])) {
1449 if ($this->multiplePagesType($row['item_type'])) {
1451 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching'), $html);
1453 foreach($row['_sub'] as $subRow) {
1454 $html .= $this->printResultRow($subRow,1);
1458 $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching');
1460 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell'), $html);
1469 * Returns a results browser
1471 * @param boolean Show result count
1472 * @param string String appended to "displaying results..." notice.
1473 * @param string String appended after section "displaying results..."
1474 * @return string HTML output
1476 function pi_list_browseresults($showResultCount=1,$addString='',$addPart='') {
1478 // Initializing variables:
1479 $pointer=$this->piVars
['pointer'];
1480 $count=$this->internal
['res_count'];
1481 $results_at_a_time = t3lib_div
::intInRange($this->internal
['results_at_a_time'],1,1000);
1482 $maxPages = t3lib_div
::intInRange($this->internal
['maxPages'],1,100);
1483 $pageCount = ceil($count/$results_at_a_time);
1486 if ($pageCount > 1) { // only show the result browser if more than one page is needed
1487 $pointer=intval($pointer);
1490 // Make browse-table/links:
1491 if ($pointer>0) { // all pages after the 1st one
1492 $links[]='<td><p>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev','< Previous',1),$pointer-1).'</p></td>';
1495 for($a=0;$a<$pageCount;$a++
) {
1496 $min = max(0, $pointer+
1-ceil($maxPages/2));
1497 $max = $min+
$maxPages;
1498 if($max>$pageCount) {
1499 $min = $min - ($max-$pageCount);
1502 if($a >= $min && $a < $max) {
1504 $links[]='<td'.($pointer==$a?
$this->pi_classParam('browsebox-SCell'):'').'><p><strong>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+
1)),$a).'</strong></p></td>';
1506 $links[]='<td'.($pointer==$a?
$this->pi_classParam('browsebox-SCell'):'').'><p>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+
1)),$a).'</p></td>';
1510 if ($pointer+
1 < $pageCount) {
1511 $links[]='<td><p>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next','Next >',1),$pointer+
1).'</p></td>';
1515 $pR1 = $pointer*$results_at_a_time+
1;
1516 $pR2 = $pointer*$results_at_a_time+
$results_at_a_time;
1517 if(is_array($links)) {
1520 <tr>'.implode('',$links).'</tr>
1524 $sTables = '<div'.$this->pi_classParam('browsebox').'>'.
1525 ($showResultCount ?
'<p>'.sprintf(
1526 str_replace('###SPAN_BEGIN###','<span'.$this->pi_classParam('browsebox-strong').'>',$this->pi_getLL('pi_list_browseresults_displays','Displaying results ###SPAN_BEGIN###%s to %s</span> out of ###SPAN_BEGIN###%s</span>')),
1528 min(array($this->internal
['res_count'],$pR2)),
1529 $this->internal
['res_count']
1530 ).$addString.'</p>':''
1531 ).$addPart.'</div>';
1547 /***********************************
1549 * Support functions for HTML output (with a minimum of fixed markup)
1551 ***********************************/
1554 * Preparing template data for the result row output
1556 * @param array Result row
1557 * @param boolean If set, display only header of result (for sub-results)
1558 * @return array Array with data to insert in result row template
1560 function prepareResultRowTemplateData($row, $headerOnly) {
1563 $specRowConf = $this->getSpecialConfigForRow($row);
1564 $CSSsuffix = $specRowConf['CSSsuffix']?
'-'.$specRowConf['CSSsuffix']:'';
1566 // If external media, link to the media-file instead.
1567 if ($row['item_type']) { // External media
1568 if ($row['show_resume']) { // Can link directly.
1569 $title = '<a href="'.htmlspecialchars($row['data_filename']).'">'.htmlspecialchars($this->makeTitle($row)).'</a>';
1570 } else { // Suspicious, so linking to page instead...
1572 unset($copy_row['cHashParams']);
1573 $title = $this->linkPage($row['page_id'],htmlspecialchars($this->makeTitle($row)),$copy_row);
1575 } else { // Else the page:
1577 // Prepare search words for markup in content:
1578 if ($this->conf
['forwardSearchWordsInResultLink']) {
1579 $markUpSwParams = array('no_cache' => 1);
1580 foreach($this->sWArr
as $d) {
1581 $markUpSwParams['sword_list'][] = $d['sword'];
1584 $markUpSwParams = array();
1586 $title = $this->linkPage($row['data_page_id'],htmlspecialchars($this->makeTitle($row)),$row,$markUpSwParams);
1589 $tmplContent = array();
1590 $tmplContent['title'] = $title;
1591 $tmplContent['result_number'] = $this->conf
['show.']['resultNumber'] ?
$row['result_number'].': ' : ' ';
1592 $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'],'',$specRowConf);
1593 $tmplContent['rating'] = $this->makeRating($row);
1594 $tmplContent['description'] = $this->makeDescription($row,$this->piVars
['extResume'] && !$headerOnly?
0:1);
1595 $tmplContent = $this->makeInfo($row,$tmplContent);
1596 $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1597 $tmplContent['language'] = $this->makeLanguageIndication($row);
1598 $tmplContent['CSSsuffix'] = $CSSsuffix;
1600 // Post processing with hook.
1601 if ($hookObj = &$this->hookRequest('prepareResultRowTemplateData_postProc')) {
1602 $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1605 return $tmplContent;
1609 * Returns a string that tells which search words are searched for.
1611 * @param array Array of search words
1612 * @return string HTML telling what is searched for.
1614 function tellUsWhatIsSeachedFor($sWArr) {
1620 // Traverse search words:
1621 foreach($sWArr as $k => $v) {
1623 switch($v['oper']) {
1625 $searchingFor.= ' '.$this->pi_getLL('searchFor_or','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1628 $searchingFor.= ' '.$this->pi_getLL('searchFor_butNot','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1631 $searchingFor.= ' '.$this->pi_getLL('searchFor_and','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1635 $searchingFor = $this->pi_getLL('searchFor','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1639 return $searchingFor;
1643 * Wraps the search words in the search-word list display (from ->tellUsWhatIsSeachedFor())
1645 * @param string search word to wrap (in local charset!)
1646 * @return string Search word wrapped in <span> tag.
1648 function wrapSW($str) {
1649 return '"<span'.$this->pi_classParam('sw').'>'.htmlspecialchars($str).'</span>"';
1653 * Makes a selector box
1655 * @param string Name of selector box
1656 * @param string Current value
1657 * @param array Array of options in the selector box (value => label pairs)
1658 * @return string HTML of selector box
1660 function renderSelectBox($name,$value,$optValues) {
1661 if (is_array($optValues)) {
1665 foreach($optValues as $k => $v) {
1666 $sel = (!strcmp($k,$value) ?
' selected="selected"' : '');
1667 if ($sel) $isSelFlag++
;
1668 $opt[] = '<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1671 return '<select name="'.$name.'">'.implode('',$opt).'</select>';
1676 * Used to make the link for the result-browser.
1677 * Notice how the links must resubmit the form after setting the new pointer-value in a hidden formfield.
1679 * @param string String to wrap in <a> tag
1680 * @param integer Pointer value
1681 * @return string Input string wrapped in <a> tag with onclick event attribute set.
1683 function makePointerSelector_link($str,$p) {
1684 $onclick = 'document.'.$this->prefixId
.'[\''.$this->prefixId
.'[pointer]\'].value=\''.$p.'\';document.'.$this->prefixId
.'.submit();return false;';
1685 return '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.$str.'</a>';
1689 * Return icon for file extension
1691 * @param string File extension / item type
1692 * @param string Title attribute value in icon.
1693 * @param array TypoScript configuration specifically for search result.
1694 * @return string <img> tag for icon
1696 function makeItemTypeIcon($it,$alt='',$specRowConf) {
1697 if (!isset($this->iconFileNameCache
[$it])) {
1698 $this->iconFileNameCache
[$it] = '';
1700 // If TypoScript is used to render the icon:
1701 if (is_array($this->conf
['iconRendering.'])) {
1702 $this->cObj
->setCurrentVal($it);
1703 $this->iconFileNameCache
[$it] = $this->cObj
->cObjGetSingle($this->conf
['iconRendering'],$this->conf
['iconRendering.']);
1704 } else { // ... otherwise, get flag from sys_language record:
1706 // Default creation / finding of icon:
1709 if (is_array($specRowConf['pageIcon.'])) {
1710 $this->iconFileNameCache
[$it] = $this->cObj
->IMAGE($specRowConf['pageIcon.']);
1712 $icon = 'EXT:indexed_search/pi/res/pages.gif';
1714 } elseif ($this->external_parsers
[$it]) {
1715 $icon = $this->external_parsers
[$it]->getIcon($it);
1719 $fullPath = t3lib_div
::getFileAbsFileName($icon);
1722 $info = @getimagesize
($fullPath);
1723 $iconPath = substr($fullPath,strlen(PATH_site
));
1724 $this->iconFileNameCache
[$it] = is_array($info) ?
'<img src="'.$iconPath.'" '.$info[3].' title="'.htmlspecialchars($alt).'" alt="" />' : '';
1729 return $this->iconFileNameCache
[$it];
1733 * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
1735 * @param array Result row array
1736 * @return string String showing ranking value
1738 function makeRating($row) {
1740 switch((string)$this->piVars
['order']) {
1741 case 'rank_count': // Number of occurencies on page
1742 return $row['order_val'].' '.$this->pi_getLL('maketitle_matches');
1744 case 'rank_first': // Close to top of page
1745 return ceil(t3lib_div
::intInRange(255-$row['order_val'],1,255)/255*100).'%';
1747 case 'rank_flag': // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1748 if ($this->firstRow
['order_val2']) {
1749 $base = $row['order_val1']*256; // (3 MSB bit, 224 is highest value of order_val1 currently)
1750 $freqNumber = $row['order_val2']/$this->firstRow
['order_val2']*pow(2,12); // 15-3 MSB = 12
1751 $total = t3lib_div
::intInRange($base+
$freqNumber,0,32767);
1753 return ceil(log($total)/log(32767)*100).'%';
1756 case 'rank_freq': // Based on frequency
1758 $total = t3lib_div
::intInRange($row['order_val'],0,$max);
1760 return ceil(log($total)/log($max)*100).'%';
1762 case 'crdate': // Based on creation date
1763 return $this->cObj
->calcAge(time()-$row['item_crdate'],0); // ,$conf['age']
1765 case 'mtime': // Based on modification time
1766 return $this->cObj
->calcAge(time()-$row['item_mtime'],0); // ,$conf['age']
1768 default: // fx. title
1775 * Returns the resume for the search-result.
1777 * @param array Search result row
1778 * @param boolean If noMarkup is false, then the index_fulltext table is used to select the content of the page, split it with regex to display the search words in the text.
1779 * @param integer String length
1780 * @return string HTML string ...
1782 function makeDescription($row,$noMarkup=0,$lgd=180) {
1783 if ($row['show_resume']) {
1786 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash='.intval($row['phash']));
1787 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1788 // Cut HTTP references after some length
1789 $content = preg_replace('/(http:\/\/[^\?]+)\?[^[:blank:]]*/i', '$1?...', $ftdrow['fulltextdata']);
1790 $markedSW = $this->markupSWpartsOfString($content);
1792 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1795 if (!trim($markedSW)) {
1796 $outputStr = $GLOBALS['TSFE']->csConvObj
->crop('utf-8',$row['item_description'],$lgd);
1797 $outputStr = htmlspecialchars($outputStr);
1799 $output = $this->utf8_to_currentCharset($outputStr ?
$outputStr : $markedSW);
1801 $output = '<span class="noResume">'.$this->pi_getLL('res_noResume','',1).'</span>';
1808 * Marks up the search words from $this->sWarr in the $str with a color.
1810 * @param string text in which to find and mark up search words. This text is assumed to be UTF-8 like the search words internally is.
1811 * @return string Processed content.
1813 function markupSWpartsOfString($str) {
1816 $str = str_replace(' ',' ',t3lib_parsehtml
::bidir_htmlspecialchars($str,-1));
1817 $str = preg_replace('/\s\s+/',' ',$str);
1818 $swForReg = array();
1820 // Prepare search words for regex:
1821 foreach($this->sWArr
as $d) {
1822 $swForReg[] = quotemeta($d['sword']);
1824 $regExString = '('.implode('|',$swForReg).')';
1826 // Split and combine:
1827 $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE
);
1828 #debug($parts,$regExString);
1832 $postPreLgd_offset = 5;
1835 $occurencies = (count($parts)-1)/2;
1837 $postPreLgd = t3lib_div
::intInRange($summaryMax/$occurencies,$postPreLgd,$summaryMax/2);
1844 // Shorten in-between strings:
1845 foreach($parts as $k => $strP) {
1848 // Find length of the summary part:
1849 $strLen = $GLOBALS['TSFE']->csConvObj
->strlen('utf-8', $parts[$k]);
1850 $output[$k] = $parts[$k];
1852 // Possibly shorten string:
1853 if (!$k) { // First entry at all (only cropped on the frontside)
1854 if ($strLen > $postPreLgd) {
1855 $output[$k] = $divider.ereg_replace('^[^[:space:]]+[[:space:]]','',$GLOBALS['TSFE']->csConvObj
->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
1857 } elseif ($summaryLgd > $summaryMax ||
!isset($parts[$k+
1])) { // In case summary length is exceed OR if there are no more entries at all:
1858 if ($strLen > $postPreLgd) {
1859 $output[$k] = ereg_replace('[[:space:]][^[:space:]]+$','',$GLOBALS['TSFE']->csConvObj
->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).$divider;
1861 } else { // In-between search words:
1862 if ($strLen > $postPreLgd*2) {
1863 $output[$k] = ereg_replace('[[:space:]][^[:space:]]+$','',$GLOBALS['TSFE']->csConvObj
->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).
1865 ereg_replace('^[^[:space:]]+[[:space:]]','',$GLOBALS['TSFE']->csConvObj
->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
1868 $summaryLgd+
= $GLOBALS['TSFE']->csConvObj
->strlen('utf-8', $output[$k]);;
1871 $output[$k] = htmlspecialchars($output[$k]);
1873 // If summary lgd is exceed, break the process:
1874 if ($summaryLgd > $summaryMax) {
1878 $summaryLgd+
= $GLOBALS['TSFE']->csConvObj
->strlen('utf-8',$strP);
1879 $output[$k] = '<span class="tx-indexedsearch-redMarkup">'.htmlspecialchars($parts[$k]).'</span>';
1884 return implode('',$output);
1888 * Returns the title of the search result row
1890 * @param array Result row
1891 * @return string Title from row
1893 function makeTitle($row) {
1896 if ($this->multiplePagesType($row['item_type'])) {
1897 $dat = unserialize($row['cHashParams']);
1899 $pp = explode('-',$dat['key']);
1900 if ($pp[0]!=$pp[1]) {
1901 $add=', '.$this->pi_getLL('word_pages').' '.$dat['key'];
1902 } else $add=', '.$this->pi_getLL('word_page').' '.$pp[0];
1905 $outputString = $GLOBALS['TSFE']->csConvObj
->crop('utf-8',$row['item_title'],50,'...');
1907 return $this->utf8_to_currentCharset(htmlspecialchars($outputString)).$add;
1911 * Returns the info-string in the bottom of the result-row display (size, dates, path)
1913 * @param array Result row
1914 * @param array Template array to modify
1915 * @return array Modified template array
1917 function makeInfo($row,$tmplArray) {
1918 $tmplArray['size'] = $this->pi_getLL('res_size','',1).' '.t3lib_div
::formatSize($row['item_size']).'';
1919 $tmplArray['created'] = $this->pi_getLL('res_created','',1).' '.date('d-m-y',$row['item_crdate']).'';
1920 $tmplArray['modified'] = $this->pi_getLL('res_modified','',1).' '.date('d-m-y H:i',$row['item_mtime']).'';
1922 $pathId = $row['data_page_id']?
$row['data_page_id']:$row['page_id'];
1923 $pathMP = $row['data_page_id']?
$row['data_page_mp']:'';
1925 $pI = parse_url($row['data_filename']);
1926 if ($pI['scheme']) {
1927 $tmplArray['path'] = '<a href="'.htmlspecialchars($row['data_filename']).'">'.htmlspecialchars($row['data_filename']).'</a>';
1929 $pathStr = htmlspecialchars($this->getPathFromPageId($pathId,$pathMP));
1930 $tmplArray['path'] = $this->linkPage($pathId,htmlspecialchars($pathStr),array(
1931 'data_page_type' => $row['data_page_type'],
1932 'data_page_mp' => $pathMP,
1933 'sys_language_uid' => $row['sys_language_uid'],
1941 * Returns configuration from TypoScript for result row based on ID / location in page tree!
1943 * @param array Result row
1944 * @return array Configuration array
1946 function getSpecialConfigForRow($row) {
1947 $pathId = $row['data_page_id'] ?
$row['data_page_id'] : $row['page_id'];
1948 $pathMP = $row['data_page_id'] ?
$row['data_page_mp'] : '';
1950 $rl = $this->getRootLine($pathId,$pathMP);
1951 $specConf = $this->conf
['specConfs.']['0.'];
1952 if (is_array($rl)) {
1953 foreach($rl as $dat) {
1954 if (is_array($this->conf
['specConfs.'][$dat['uid'].'.'])) {
1955 $specConf = $this->conf
['specConfs.'][$dat['uid'].'.'];
1965 * Returns the HTML code for language indication.
1967 * @param array Result row
1968 * @return string HTML code for result row.
1970 function makeLanguageIndication($row) {
1972 // If search result is a TYPO3 page:
1973 if ((string)$row['item_type']==='0') {
1975 // If TypoScript is used to render the flag:
1976 if (is_array($this->conf
['flagRendering.'])) {
1977 $this->cObj
->setCurrentVal($row['sys_language_uid']);
1978 return $this->cObj
->cObjGetSingle($this->conf
['flagRendering'],$this->conf
['flagRendering.']);
1979 } else { // ... otherwise, get flag from sys_language record:
1981 // Get sys_language record
1982 $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_language', 'uid='.intval($row['sys_language_uid']).' '.$this->cObj
->enableFields('sys_language'));
1985 $flag = $rowDat[0]['flag'];
1987 $file = 't3lib/gfx/flags/'.$flag;
1988 $imgInfo = @getimagesize
(PATH_site
.$file);
1990 if (is_array($imgInfo)) {
1991 $output = '<img src="'.$file.'" '.$imgInfo[3].' title="'.htmlspecialchars($rowDat[0]['title']).'" alt="'.htmlspecialchars($rowDat[0]['title']).'" />';
2001 * Returns the HTML code for the locking symbol.
2002 * NOTICE: Requires a call to ->getPathFromPageId() first in order to work (done in ->makeInfo() by calling that first)
2004 * @param integer Page id for which to find answer
2005 * @return string <img> tag if access is limited.
2007 function makeAccessIndication($id) {
2008 if (is_array($this->fe_groups_required
[$id]) && count($this->fe_groups_required
[$id])) {
2009 return '<img src="'.t3lib_extMgm
::siteRelPath('indexed_search').'pi/res/locked.gif" width="12" height="15" vspace="5" title="'.sprintf($this->pi_getLL('res_memberGroups','',1),implode(',',array_unique($this->fe_groups_required
[$id]))).'" alt="" />';
2014 * Links the $str to page $id
2016 * @param integer Page id
2017 * @param string Title String to link
2018 * @param array Result row
2019 * @return string <A> tag wrapped title string.
2021 function linkPage($id,$str,$row=array(),$markUpSwParams=array()) {
2023 // Parameters for link:
2024 $urlParameters = (array)unserialize($row['cHashParams']);
2026 // Add &type and &MP variable:
2027 if ($row['data_page_type']) $urlParameters['type'] = $row['data_page_type'];
2028 if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
2029 if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
2032 $urlParameters = array_merge($urlParameters, $markUpSwParams);
2034 // This will make sure that the path is retrieved if it hasn't been already. Used only for the sake of the domain_record thing...
2035 if (!is_array($this->domain_records
[$id])) {
2036 $this->getPathFromPageId($id);
2039 // If external domain, then link to that:
2040 if (count($this->domain_records
[$id])) {
2041 reset($this->domain_records
[$id]);
2042 $firstDom = current($this->domain_records
[$id]);
2043 $scheme = t3lib_div
::getIndpEnv('TYPO3_SSL') ?
'https://' : 'http://';
2046 if (is_array($urlParameters)) {
2047 if (count($urlParameters)) {
2048 $addParams.= t3lib_div
::implodeArrayForUrl('',$urlParameters);
2052 return '<a href="'.$scheme.$firstDom.'/index.php?id='.$id.$addParams.'" target="'.$this->conf
['search.']['detect_sys_domain_records.']['target'].'">'.htmlspecialchars($str).'</a>';
2054 return $this->pi_linkToPage($str,$id,$this->conf
['result_link_target'],$urlParameters);
2059 * Returns the path to the page $id
2061 * @param integer Page ID
2062 * @param string MP variable content.
2063 * @return string Root line for result.
2065 function getRootLine($id,$pathMP='') {
2066 $identStr = $id.'|'.$pathMP;
2068 if (!isset($this->cache_path
[$identStr])) {
2069 $this->cache_rl
[$identStr] = $GLOBALS['TSFE']->sys_page
->getRootLine($id,$pathMP);
2071 return $this->cache_rl
[$identStr];
2075 * Gets the first sys_domain record for the page, $id
2077 * @param integer Page id
2078 * @return string Domain name
2080 function getFirstSysDomainRecordForPage($id) {
2081 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', 'pid='.intval($id).$this->cObj
->enableFields('sys_domain'), '', 'sorting');
2082 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
2083 return ereg_replace('\/$','',$row['domainName']);
2087 * Returns the path to the page $id
2089 * @param integer Page ID
2090 * @param string MP variable content
2091 * @return string Path
2093 function getPathFromPageId($id,$pathMP='') {
2095 $identStr = $id.'|'.$pathMP;
2097 if (!isset($this->cache_path
[$identStr])) {
2098 $this->fe_groups_required
[$id] = array();
2099 $this->domain_records
[$id] = array();
2100 $rl = $this->getRootLine($id,$pathMP);
2103 if (is_array($rl) && count($rl)) {
2105 while(list($k,$v)=each($rl)) {
2107 if ($v['fe_group'] && ($v['uid']==$id ||
$v['extendToSubpages'])) {
2108 $this->fe_groups_required
[$id][]=$v['fe_group'];
2110 // Check sys_domain.
2111 if ($this->conf
['search.']['detect_sys_domain_records']) {
2112 $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2114 $this->domain_records
[$id][] = $sysDName;
2115 // Set path accordingly:
2116 $path = $sysDName.$path;
2121 // Stop, if we find that the current id is the current root page.
2122 if ($v['uid']==$GLOBALS['TSFE']->config
['rootLine'][0]['uid']) {
2125 $path = '/'.$v['title'].$path;
2129 $this->cache_path
[$identStr] = $path;
2131 if (is_array($this->conf
['path_stdWrap.'])) {
2132 $this->cache_path
[$identStr] = $this->cObj
->stdWrap($this->cache_path
[$identStr], $this->conf
['path_stdWrap.']);
2136 return $this->cache_path
[$identStr];
2140 * Return the menu of pages used for the selector.
2142 * @param integer Page ID for which to return menu
2143 * @return array Menu items (for making the section selector box)
2145 function getMenu($id) {
2146 if ($this->conf
['show.']['LxALLtypes']) {
2148 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', 'pid='.intval($id).$this->cObj
->enableFields('pages'), '', 'sorting');
2149 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2150 $output[$row['uid']] = $GLOBALS['TSFE']->sys_page
->getPageOverlay($row);
2154 return $GLOBALS['TSFE']->sys_page
->getMenu($id);
2159 * Returns if an item type is a multipage item type
2161 * @param string Item type
2162 * @return boolean True if multipage capable
2164 function multiplePagesType($item_type) {
2165 return is_object($this->external_parsers
[$item_type]) && $this->external_parsers
[$item_type]->isMultiplePageExtension($item_type);
2169 * Converts the input string from utf-8 to the backend charset.
2171 * @param string String to convert (utf-8)
2172 * @return string Converted string (backend charset if different from utf-8)
2174 function utf8_to_currentCharset($str) {
2175 return $GLOBALS['TSFE']->csConv($str,'utf-8');
2179 * Returns an object reference to the hook object if any
2181 * @param string Name of the function you want to call / hook key
2182 * @return object Hook object, if any. Otherwise null.
2184 function &hookRequest($functionName) {
2185 global $TYPO3_CONF_VARS;
2187 // Hook: menuConfig_preProcessModMenu
2188 if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2189 $hookObj = &t3lib_div
::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2190 if (method_exists ($hookObj, $functionName)) {
2191 $hookObj->pObj
= &$this;
2199 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']) {
2200 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']);