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