Revert "[FEATURE] Indexed Search: add mySQL fulltext support"
[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 if (isset($this->piVars['sword']) && $this->piVars['sword'] != '') {
1262 $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1263 $markerArray['###PLACEHOLDER###'] = '';
1264 } else {
1265 // Add a HTML5 placeholder attribute if the configured doctype allows it
1266 if ($GLOBALS['TSFE']->config['config']['doctype'] === 'html5') {
1267 $markerArray['###SWORD_VALUE###'] = '';
1268 $markerArray['###PLACEHOLDER###'] = 'placeholder="' . $this->pi_getLL('default_search_word_entry') . '"';
1269 } else {
1270 $markerArray['###SWORD_VALUE###'] = $this->pi_getLL('default_search_word_entry');
1271 $markerArray['###PLACEHOLDER###'] = '';
1272 }
1273 }
1274
1275
1276 // Additonal keyword => "Add to current search words"
1277 if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1278 $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1279 $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"':'';
1280 $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch','',1);
1281 } else {
1282 $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1283 }
1284
1285 $markerArray['###ACTION_URL###'] = htmlspecialchars($this->getSearchFormActionURL());
1286
1287 $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
1288 $hiddenFieldCode = preg_replace('/^\n\t(.+)/ms', '$1', $hiddenFieldCode); // Remove first newline and tab (cosmetical issue)
1289 $hiddenFieldArr = array();
1290
1291 foreach (t3lib_div::trimExplode(',',$this->hiddenFieldList) as $fieldName) {
1292 $hiddenFieldMarkerArray = array();
1293 $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = $this->prefixId.'['.$fieldName.']';
1294 $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string)$this->piVars[$fieldName]);
1295
1296 $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, array(), array());
1297 }
1298
1299 // Extended search
1300 if ($this->piVars['ext']) {
1301
1302 // Search for
1303 if ((!is_array($optValues['type']) && !is_array($optValues['defOp'])) || ($this->conf['blind.']['type'] && $this->conf['blind.']['defOp'])) {
1304 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1305 } else {
1306 if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
1307 unset($hiddenFieldArr['type']);
1308 $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'],$optValues['type']);
1309 } else {
1310 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1311 }
1312
1313 if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp']) {
1314 $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'],$optValues['defOp']);
1315 } else {
1316 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1317 }
1318 }
1319
1320 // Search in
1321 if ((!is_array($optValues['media']) && !is_array($optValues['lang'])) || ($this->conf['blind.']['media'] && $this->conf['blind.']['lang'])) {
1322 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1323 } else {
1324 if (is_array($optValues['media']) && !$this->conf['blind.']['media']) {
1325 unset($hiddenFieldArr['media']);
1326 $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'],$optValues['media']);
1327 } else {
1328 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1329 }
1330
1331 if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
1332 unset($hiddenFieldArr['lang']);
1333 $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'],$optValues['lang']);
1334 } else {
1335 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1336 }
1337 }
1338
1339 // Sections
1340 if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
1341 $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1342 } else {
1343 $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'],$optValues['sections']);
1344 }
1345
1346 // Free Indexing Configurations:
1347 if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid']) {
1348 $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
1349 } else {
1350 $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'],$optValues['freeIndexUid']);
1351 }
1352
1353 // Sorting
1354 if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order']) {
1355 $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1356 } else {
1357 unset($hiddenFieldArr['order']);
1358 unset($hiddenFieldArr['desc']);
1359 unset($hiddenFieldArr['results']);
1360 $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'],$optValues['order']);
1361 $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'],$optValues['desc']);
1362 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
1363 }
1364
1365 // Limits
1366 if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results']) {
1367 $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1368 } else {
1369 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
1370 }
1371
1372 // Grouping
1373 if (!is_array($optValues['group']) || $this->conf['blind.']['group']) {
1374 $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1375 } else {
1376 unset($hiddenFieldArr['group']);
1377 $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'],$optValues['group']);
1378 }
1379
1380 if ($this->conf['blind.']['extResume']) {
1381 $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1382 } else {
1383 $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
1384 }
1385
1386 } else { // Extended search
1387 $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1388 }
1389
1390 if($this->conf['show.']['advancedSearchLink']) {
1391 $linkToOtherMode = ($this->piVars['ext'] ?
1392 $this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>0)) :
1393 $this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>1))
1394 );
1395
1396 $markerArray['###LINKTOOTHERMODE###'] = '<a href="'.htmlspecialchars($linkToOtherMode).'">'.$this->pi_getLL($this->piVars['ext']?'link_regularSearch':'link_advancedSearch', '', 1).'</a>';
1397 } else {
1398 $markerArray['###LINKTOOTHERMODE###'] = '';
1399 }
1400
1401 // Write all hidden fields
1402 $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('',$hiddenFieldArr));
1403
1404 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1405
1406 return $substitutedContent;
1407 }
1408
1409 /**
1410 * Function, rendering selector box values.
1411 *
1412 * @param string Current value
1413 * @param array Array with the options as key=>value pairs
1414 * @return string <options> imploded.
1415 */
1416 function renderSelectBoxValues($value,$optValues) {
1417 if (is_array($optValues)) {
1418 $opt=array();
1419 $isSelFlag=0;
1420 foreach ($optValues as $k=>$v) {
1421 $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
1422 if ($sel) { $isSelFlag++; }
1423 $opt[]='<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1424 }
1425
1426 return implode('',$opt);
1427 }
1428 }
1429
1430 /**
1431 * Print the searching rules
1432 *
1433 * @return string Rules for the search
1434 */
1435 function printRules() {
1436 if ($this->conf['show.']['rules']) {
1437
1438 $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1439
1440 $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header','',1);
1441 $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text','',1)));
1442
1443 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1444
1445 return $this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']);
1446 }
1447 }
1448
1449 /**
1450 * Returns the anchor-links to the sections inside the displayed result rows.
1451 *
1452 * @return string
1453 */
1454 function printResultSectionLinks() {
1455 if (count($this->resultSections)) {
1456 $lines = array();
1457
1458 $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1459 $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1460
1461 foreach ($this->resultSections as $id => $dat) {
1462 $markerArray = array();
1463
1464 $aBegin = '<a href="'.htmlspecialchars($GLOBALS['TSFE']->anchorPrefix.'#anchor_'.md5($id)).'">';
1465 $aContent = htmlspecialchars(trim($dat[0]) ? trim($dat[0]) : $this->pi_getLL('unnamedSection')).
1466 ' ('.$dat[1].' '.$this->pi_getLL($dat[1]>1 ? 'word_pages' : 'word_page','',1).')';
1467 $aEnd = '</a>';
1468
1469 $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1470
1471 $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
1472 }
1473
1474 $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode('',$links)), array(), array());
1475
1476 return '<div'.$this->pi_classParam('sectionlinks').'>'.$this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']).'</div>';
1477 }
1478 }
1479
1480 /**
1481 * Returns the section header of the search result.
1482 *
1483 * @param string ID for the section (used for anchor link)
1484 * @param string Section title with linked wrapped around
1485 * @param integer Number of results in section
1486 * @return string HTML output
1487 */
1488 function makeSectionHeader($id, $sectionTitleLinked, $countResultRows) {
1489
1490 $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1491
1492 $markerArray['###ANCHOR_URL###'] = 'anchor_'.md5($id);
1493 $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1494 $markerArray['###RESULT_COUNT###'] = $countResultRows;
1495 $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page'.($countResultRows>1 ? 's' : ''));
1496
1497 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1498
1499 return $substitutedContent;
1500 }
1501
1502 /**
1503 * This prints a single result row, including a recursive call for subrows.
1504 *
1505 * @param array Search result row
1506 * @param integer 1=Display only header (for sub-rows!), 2=nothing at all
1507 * @return string HTML code
1508 */
1509 function printResultRow($row, $headerOnly=0) {
1510
1511 // Get template content:
1512 $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1513
1514 if ($hookObj = $this->hookRequest('printResultRow')) {
1515 return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1516 } else {
1517
1518 $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1519
1520 if (!is_array($row['_sub'])) {
1521 $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1522 }
1523
1524 if (!$headerOnly) {
1525 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1526
1527 } elseif ($headerOnly==1) {
1528 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1529 } elseif ($headerOnly==2) {
1530 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1531 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1532 }
1533
1534 if (is_array($tmplContent)) {
1535 foreach ($tmplContent AS $k => $v) {
1536 $markerArray['###'.t3lib_div::strtoupper($k).'###'] = $v;
1537 }
1538 }
1539
1540 // Description text
1541 $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size','',1);
1542 $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created','',1);
1543 $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified','',1);
1544 $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path','',1);
1545
1546 $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1547
1548 // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1549 if (is_array($row['_sub'])) {
1550 if ($this->multiplePagesType($row['item_type'])) {
1551
1552 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching','',1), $html);
1553
1554 foreach ($row['_sub'] as $subRow) {
1555 $html .= $this->printResultRow($subRow,1);
1556 }
1557 } else {
1558
1559 $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching','',1);
1560
1561 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell','',1), $html);
1562 }
1563 }
1564
1565 return $html;
1566 }
1567 }
1568
1569 /**
1570 * Returns a results browser
1571 *
1572 * @param boolean Show result count
1573 * @param string String appended to "displaying results..." notice.
1574 * @param string String appended after section "displaying results..."
1575 * @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!
1576 * @return string HTML output
1577 */
1578 function pi_list_browseresults($showResultCount=1,$addString='',$addPart='',$freeIndexUid=-1) {
1579
1580 // Initializing variables:
1581 $pointer=$this->piVars['pointer'];
1582 $count=$this->internal['res_count'];
1583 $results_at_a_time = t3lib_utility_Math::forceIntegerInRange($this->internal['results_at_a_time'],1,1000);
1584 $maxPages = t3lib_utility_Math::forceIntegerInRange($this->internal['maxPages'],1,100);
1585 $pageCount = ceil($count/$results_at_a_time);
1586 $sTables = '';
1587
1588 if ($pageCount > 1) { // only show the result browser if more than one page is needed
1589 $pointer=intval($pointer);
1590 $links=array();
1591
1592 // Make browse-table/links:
1593 if ($pointer>0) { // all pages after the 1st one
1594 $links[]='<li>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev','< Previous',1),$pointer-1,$freeIndexUid).'</li>';
1595 }
1596
1597 for($a=0;$a<$pageCount;$a++) {
1598 $min = max(0, $pointer+1-ceil($maxPages/2));
1599 $max = $min+$maxPages;
1600 if($max>$pageCount) {
1601 $min = $min - ($max-$pageCount);
1602 }
1603
1604 if($a >= $min && $a < $max) {
1605 if($a==$pointer) {
1606 $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>';
1607 } else {
1608 $links[]='<li>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+1)),$a,$freeIndexUid).'</li>';
1609 }
1610 }
1611 }
1612 if ($pointer+1 < $pageCount) {
1613 $links[]='<li>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next','Next >',1),$pointer+1,$freeIndexUid).'</li>';
1614 }
1615 }
1616
1617 $pR1 = $pointer*$results_at_a_time+1;
1618 $pR2 = $pointer*$results_at_a_time+$results_at_a_time;
1619 if(is_array($links)) {
1620 $addPart .= '
1621 <ul class="browsebox">
1622 '.implode('',$links).'
1623 </ul>';
1624 }
1625
1626 $label = $this->pi_getLL('pi_list_browseresults_display','Displaying results ###TAG_BEGIN###%s to %s###TAG_END### out of ###TAG_BEGIN###%s###TAG_END###');
1627 $label = str_replace('###TAG_BEGIN###','<strong>',$label);
1628 $label = str_replace('###TAG_END###','</strong>',$label);
1629
1630 $sTables = '<div'.$this->pi_classParam('browsebox').'>'.
1631 ($showResultCount ? '<p>'.sprintf(
1632 $label,
1633 $pR1,
1634 min(array($this->internal['res_count'],$pR2)),
1635 $this->internal['res_count']
1636 ).$addString.'</p>':''
1637 ).$addPart.'</div>';
1638
1639 return $sTables;
1640 }
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653 /***********************************
1654 *
1655 * Support functions for HTML output (with a minimum of fixed markup)
1656 *
1657 ***********************************/
1658
1659 /**
1660 * Preparing template data for the result row output
1661 *
1662 * @param array Result row
1663 * @param boolean If set, display only header of result (for sub-results)
1664 * @return array Array with data to insert in result row template
1665 */
1666 function prepareResultRowTemplateData($row, $headerOnly) {
1667
1668 // Initialize:
1669 $specRowConf = $this->getSpecialConfigForRow($row);
1670 $CSSsuffix = $specRowConf['CSSsuffix']?'-'.$specRowConf['CSSsuffix']:'';
1671
1672 // If external media, link to the media-file instead.
1673 if ($row['item_type']) { // External media
1674 if ($row['show_resume']) { // Can link directly.
1675 $targetAttribute = '';
1676 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
1677 $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
1678 }
1679 $title = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' .
1680 htmlspecialchars($this->makeTitle($row)) .
1681 '</a>';
1682 } else { // Suspicious, so linking to page instead...
1683 $copy_row = $row;
1684 unset($copy_row['cHashParams']);
1685 $title = $this->linkPage($row['page_id'],htmlspecialchars($this->makeTitle($row)),$copy_row);
1686 }
1687 } else { // Else the page:
1688
1689 // Prepare search words for markup in content:
1690 if ($this->conf['forwardSearchWordsInResultLink']) {
1691 $markUpSwParams = array('no_cache' => 1);
1692 foreach ($this->sWArr as $d) {
1693 $markUpSwParams['sword_list'][] = $d['sword'];
1694 }
1695 } else {
1696 $markUpSwParams = array();
1697 }
1698 $title = $this->linkPage($row['data_page_id'],htmlspecialchars($this->makeTitle($row)),$row,$markUpSwParams);
1699 }
1700
1701 $tmplContent = array();
1702 $tmplContent['title'] = $title;
1703 $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'].': ' : '&nbsp;';
1704 $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'],'',$specRowConf);
1705 $tmplContent['rating'] = $this->makeRating($row);
1706 $tmplContent['description'] = $this->makeDescription($row,$this->piVars['extResume'] && !$headerOnly?0:1);
1707 $tmplContent = $this->makeInfo($row,$tmplContent);
1708 $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1709 $tmplContent['language'] = $this->makeLanguageIndication($row);
1710 $tmplContent['CSSsuffix'] = $CSSsuffix;
1711
1712 // Post processing with hook.
1713 if ($hookObj = $this->hookRequest('prepareResultRowTemplateData_postProc')) {
1714 $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1715 }
1716
1717 return $tmplContent;
1718 }
1719
1720 /**
1721 * Returns a string that tells which search words are searched for.
1722 *
1723 * @param array Array of search words
1724 * @return string HTML telling what is searched for.
1725 */
1726 function tellUsWhatIsSeachedFor($sWArr) {
1727
1728 // Init:
1729 $searchingFor = '';
1730 $c=0;
1731
1732 // Traverse search words:
1733 foreach ($sWArr as $k => $v) {
1734 if ($c) {
1735 switch($v['oper']) {
1736 case 'OR':
1737 $searchingFor.= ' '.$this->pi_getLL('searchFor_or','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1738 break;
1739 case 'AND NOT':
1740 $searchingFor.= ' '.$this->pi_getLL('searchFor_butNot','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1741 break;
1742 default: // AND...
1743 $searchingFor.= ' '.$this->pi_getLL('searchFor_and','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1744 break;
1745 }
1746 } else {
1747 $searchingFor = $this->pi_getLL('searchFor','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1748 }
1749 $c++;
1750 }
1751 return $searchingFor;
1752 }
1753
1754 /**
1755 * Wraps the search words in the search-word list display (from ->tellUsWhatIsSeachedFor())
1756 *
1757 * @param string search word to wrap (in local charset!)
1758 * @return string Search word wrapped in <span> tag.
1759 */
1760 function wrapSW($str) {
1761 return '"<span'.$this->pi_classParam('sw').'>'.htmlspecialchars($str).'</span>"';
1762 }
1763
1764 /**
1765 * Makes a selector box
1766 *
1767 * @param string Name of selector box
1768 * @param string Current value
1769 * @param array Array of options in the selector box (value => label pairs)
1770 * @return string HTML of selector box
1771 */
1772 function renderSelectBox($name,$value,$optValues) {
1773 if (is_array($optValues)) {
1774 $opt = array();
1775 $isSelFlag = 0;
1776
1777 foreach ($optValues as $k => $v) {
1778 $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
1779 if ($sel) $isSelFlag++;
1780 $opt[] = '<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1781 }
1782
1783 return '<select name="'.$name.'">'.implode('',$opt).'</select>';
1784 }
1785 }
1786
1787 /**
1788 * Used to make the link for the result-browser.
1789 * Notice how the links must resubmit the form after setting the new pointer-value in a hidden formfield.
1790 *
1791 * @param string String to wrap in <a> tag
1792 * @param integer Pointer value
1793 * @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!
1794 * @return string Input string wrapped in <a> tag with onclick event attribute set.
1795 */
1796 function makePointerSelector_link($str,$p,$freeIndexUid) {
1797 $onclick = 'document.getElementById(\'' . $this->prefixId . '_pointer\').value=\'' . $p . '\';' .
1798 'document.getElementById(\'' . $this->prefixId . '_freeIndexUid\').value=\'' . rawurlencode($freeIndexUid) . '\';' .
1799 'document.getElementById(\'' . $this->prefixId . '\').submit();return false;';
1800 return '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.$str.'</a>';
1801 }
1802
1803 /**
1804 * Return icon for file extension
1805 *
1806 * @param string File extension / item type
1807 * @param string Title attribute value in icon.
1808 * @param array TypoScript configuration specifically for search result.
1809 * @return string <img> tag for icon
1810 */
1811 function makeItemTypeIcon($it, $alt='', $specRowConf) {
1812
1813 // Build compound key if item type is 0, iconRendering is not used
1814 // and specConfs.[pid].pageIcon was set in TS
1815 if ($it === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->conf['iconRendering.'])) {
1816 $it .= ':' . $specRowConf['_pid'];
1817 }
1818 if (!isset($this->iconFileNameCache[$it])) {
1819 $this->iconFileNameCache[$it] = '';
1820
1821 // If TypoScript is used to render the icon:
1822 if (is_array($this->conf['iconRendering.'])) {
1823 $this->cObj->setCurrentVal($it);
1824 $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'], $this->conf['iconRendering.']);
1825 // ... otherwise, get flag from sys_language record:
1826 } else {
1827
1828 // Default creation / finding of icon:
1829 $icon = '';
1830 if ($it === '0' || substr($it, 0, 2) == '0:') {
1831 if (is_array($specRowConf['pageIcon.'])) {
1832 $this->iconFileNameCache[$it] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
1833 } else {
1834 $icon = 'EXT:indexed_search/pi/res/pages.gif';
1835 }
1836 } elseif ($this->external_parsers[$it]) {
1837 $icon = $this->external_parsers[$it]->getIcon($it);
1838 }
1839
1840 if ($icon) {
1841 $fullPath = t3lib_div::getFileAbsFileName($icon);
1842
1843 if ($fullPath) {
1844 $info = @getimagesize($fullPath);
1845 $iconPath = substr($fullPath, strlen(PATH_site));
1846 $this->iconFileNameCache[$it] = (is_array($info)) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
1847 }
1848 }
1849 }
1850 }
1851 return $this->iconFileNameCache[$it];
1852 }
1853
1854 /**
1855 * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
1856 *
1857 * @param array Result row array
1858 * @return string String showing ranking value
1859 */
1860 function makeRating($row) {
1861
1862 switch((string)$this->piVars['order']) {
1863 case 'rank_count': // Number of occurencies on page
1864 return $row['order_val'].' '.$this->pi_getLL('maketitle_matches');
1865 break;
1866 case 'rank_first': // Close to top of page
1867 return ceil(t3lib_utility_Math::forceIntegerInRange(255-$row['order_val'],1,255)/255*100).'%';
1868 break;
1869 case 'rank_flag': // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1870 if ($this->firstRow['order_val2']) {
1871 $base = $row['order_val1']*256; // (3 MSB bit, 224 is highest value of order_val1 currently)
1872 $freqNumber = $row['order_val2']/$this->firstRow['order_val2']*pow(2,12); // 15-3 MSB = 12
1873 $total = t3lib_utility_Math::forceIntegerInRange($base+$freqNumber,0,32767);
1874 #debug($total);
1875 return ceil(log($total)/log(32767)*100).'%';
1876 }
1877 break;
1878 case 'rank_freq': // Based on frequency
1879 $max = 10000;
1880 $total = t3lib_utility_Math::forceIntegerInRange($row['order_val'],0,$max);
1881 # debug($total);
1882 return ceil(log($total)/log($max)*100).'%';
1883 break;
1884 case 'crdate': // Based on creation date
1885 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'],0); // ,$conf['age']
1886 break;
1887 case 'mtime': // Based on modification time
1888 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'],0); // ,$conf['age']
1889 break;
1890 default: // fx. title
1891 return '&nbsp;';
1892 break;
1893 }
1894 }
1895
1896 /**
1897 * Returns the resume for the search-result.
1898 *
1899 * @param array Search result row
1900 * @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.
1901 * @param integer String length
1902 * @return string HTML string ...
1903 */
1904 function makeDescription($row,$noMarkup=0,$lgd=180) {
1905 if ($row['show_resume']) {
1906 if (!$noMarkup) {
1907 $markedSW = '';
1908 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash='.intval($row['phash']));
1909 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1910 // Cut HTTP references after some length
1911 $content = preg_replace('/(http:\/\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
1912 $markedSW = $this->markupSWpartsOfString($content);
1913 }
1914 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1915 }
1916
1917 if (!trim($markedSW)) {
1918 $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_description'],$lgd);
1919 $outputStr = htmlspecialchars($outputStr);
1920 }
1921 $output = $this->utf8_to_currentCharset($outputStr ? $outputStr : $markedSW);
1922 } else {
1923 $output = '<span class="noResume">'.$this->pi_getLL('res_noResume','',1).'</span>';
1924 }
1925
1926 return $output;
1927 }
1928
1929 /**
1930 * Marks up the search words from $this->sWarr in the $str with a color.
1931 *
1932 * @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.
1933 * @return string Processed content.
1934 */
1935 function markupSWpartsOfString($str) {
1936
1937 // Init:
1938 $str = str_replace('&nbsp;',' ',t3lib_parsehtml::bidir_htmlspecialchars($str,-1));
1939 $str = preg_replace('/\s\s+/',' ',$str);
1940 $swForReg = array();
1941
1942 // Prepare search words for regex:
1943 foreach ($this->sWArr as $d) {
1944 $swForReg[] = preg_quote($d['sword'],'/');
1945 }
1946 $regExString = '('.implode('|',$swForReg).')';
1947
1948 // Split and combine:
1949 $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
1950 // debug($parts,$regExString);
1951 // Constants:
1952 $summaryMax = 300;
1953 $postPreLgd = 60;
1954 $postPreLgd_offset = 5;
1955 $divider = ' ... ';
1956
1957 $occurencies = (count($parts)-1)/2;
1958 if ($occurencies) {
1959 $postPreLgd = t3lib_utility_Math::forceIntegerInRange($summaryMax/$occurencies,$postPreLgd,$summaryMax/2);
1960 }
1961
1962 // Variable:
1963 $summaryLgd = 0;
1964 $output = array();
1965
1966 // Shorten in-between strings:
1967 foreach ($parts as $k => $strP) {
1968 if (($k%2)==0) {
1969
1970 // Find length of the summary part:
1971 $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
1972 $output[$k] = $parts[$k];
1973
1974 // Possibly shorten string:
1975 if (!$k) { // First entry at all (only cropped on the frontside)
1976 if ($strLen > $postPreLgd) {
1977 $output[$k] = $divider.preg_replace('/^[^[:space:]]+[[:space:]]/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
1978 }
1979 } elseif ($summaryLgd > $summaryMax || !isset($parts[$k+1])) { // In case summary length is exceed OR if there are no more entries at all:
1980 if ($strLen > $postPreLgd) {
1981 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).$divider;
1982 }
1983 } else { // In-between search words:
1984 if ($strLen > $postPreLgd*2) {
1985 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).
1986 $divider.
1987 preg_replace('/^[^[:space:]]+[[:space:]]/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
1988 }
1989 }
1990 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);
1991
1992 // Protect output:
1993 $output[$k] = htmlspecialchars($output[$k]);
1994
1995 // If summary lgd is exceed, break the process:
1996 if ($summaryLgd > $summaryMax) {
1997 break;
1998 }
1999 } else {
2000 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8',$strP);
2001 $output[$k] = '<strong class="tx-indexedsearch-redMarkup">'.htmlspecialchars($parts[$k]).'</strong>';
2002 }
2003 }
2004
2005 // Return result:
2006 return implode('',$output);
2007 }
2008
2009 /**
2010 * Returns the title of the search result row
2011 *
2012 * @param array Result row
2013 * @return string Title from row
2014 */
2015 function makeTitle($row) {
2016 $add = '';
2017
2018 if ($this->multiplePagesType($row['item_type'])) {
2019 $dat = unserialize($row['cHashParams']);
2020
2021 $pp = explode('-',$dat['key']);
2022 if ($pp[0]!=$pp[1]) {
2023 $add=', '.$this->pi_getLL('word_pages').' '.$dat['key'];
2024 } else $add=', '.$this->pi_getLL('word_page').' '.$pp[0];
2025 }
2026
2027 $outputString = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_title'],50,'...');
2028
2029 return $this->utf8_to_currentCharset($outputString).$add;
2030 }
2031
2032 /**
2033 * Returns the info-string in the bottom of the result-row display (size, dates, path)
2034 *
2035 * @param array Result row
2036 * @param array Template array to modify
2037 * @return array Modified template array
2038 */
2039 function makeInfo($row,$tmplArray) {
2040 $tmplArray['size'] = t3lib_div::formatSize($row['item_size']);
2041 $tmplArray['created'] = $this->formatCreatedDate($row['item_crdate']);
2042 $tmplArray['modified'] = $this->formatModifiedDate($row['item_mtime']);
2043
2044 $pathId = $row['data_page_id']?$row['data_page_id']:$row['page_id'];
2045 $pathMP = $row['data_page_id']?$row['data_page_mp']:'';
2046
2047 $pI = parse_url($row['data_filename']);
2048 if ($pI['scheme']) {
2049 $targetAttribute = '';
2050 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
2051 $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
2052 }
2053 $tmplArray['path'] = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' .
2054 htmlspecialchars($row['data_filename']) .
2055 '</a>';
2056 } else {
2057 $pathStr = htmlspecialchars($this->getPathFromPageId($pathId,$pathMP));
2058 $tmplArray['path'] = $this->linkPage($pathId,$pathStr,array(
2059 'cHashParams' => $row['cHashParams'],
2060 'data_page_type' => $row['data_page_type'],
2061 'data_page_mp' => $pathMP,
2062 'sys_language_uid' => $row['sys_language_uid'],
2063 ));
2064 }
2065
2066 return $tmplArray;
2067 }
2068
2069 /**
2070 * Returns configuration from TypoScript for result row based on ID / location in page tree!
2071 *
2072 * @param array Result row
2073 * @return array Configuration array
2074 */
2075 function getSpecialConfigForRow($row) {
2076 $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
2077 $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2078
2079 $rl = $this->getRootLine($pathId,$pathMP);
2080 $specConf = $this->conf['specConfs.']['0.'];
2081 if (is_array($rl)) {
2082 foreach ($rl as $dat) {
2083 if (is_array($this->conf['specConfs.'][$dat['uid'].'.'])) {
2084 $specConf = $this->conf['specConfs.'][$dat['uid'].'.'];
2085 $specConf['_pid'] = $dat['uid'];
2086 break;
2087 }
2088 }
2089 }
2090
2091 return $specConf;
2092 }
2093
2094 /**
2095 * Returns the HTML code for language indication.
2096 *
2097 * @param array Result row
2098 * @return string HTML code for result row.
2099 */
2100 function makeLanguageIndication($row) {
2101
2102 // If search result is a TYPO3 page:
2103 if ((string)$row['item_type']==='0') {
2104
2105 // If TypoScript is used to render the flag:
2106 if (is_array($this->conf['flagRendering.'])) {
2107 $this->cObj->setCurrentVal($row['sys_language_uid']);
2108 return $this->cObj->cObjGetSingle($this->conf['flagRendering'],$this->conf['flagRendering.']);
2109 } else { // ... otherwise, get flag from sys_language record:
2110
2111 // Get sys_language record
2112 $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_language', 'uid=' . intval($row['sys_language_uid']) . ' ' . $this->cObj->enableFields('sys_language'));
2113
2114 // Flag code:
2115 $flag = $rowDat['flag'];
2116 if ($flag) {
2117
2118 // FIXME not all flags from typo3/gfx/flags are available in media/flags/
2119 $file = substr(PATH_tslib,strlen(PATH_site)).'media/flags/flag_'.$flag;
2120 $imgInfo = @getimagesize(PATH_site.$file);
2121
2122 // original
2123 # $file = TYPO3_mainDir.'gfx/flags/'.$flag;
2124 # $imgInfo = @getimagesize(PATH_site.$file);
2125
2126 if (is_array($imgInfo)) {
2127 $output = '<img src="' . $file . '" ' . $imgInfo[3] . ' title="' . htmlspecialchars($rowDat['title']) . '" alt="' . htmlspecialchars($rowDat['title']) . '" />';
2128 return $output;
2129 }
2130 }
2131 }
2132 }
2133 return '&nbsp;';
2134 }
2135
2136 /**
2137 * Returns the HTML code for the locking symbol.
2138 * NOTICE: Requires a call to ->getPathFromPageId() first in order to work (done in ->makeInfo() by calling that first)
2139 *
2140 * @param integer Page id for which to find answer
2141 * @return string <img> tag if access is limited.
2142 */
2143 function makeAccessIndication($id) {
2144 if (is_array($this->fe_groups_required[$id]) && count($this->fe_groups_required[$id])) {
2145 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="" />';
2146 }
2147 }
2148
2149 /**
2150 * Links the $str to page $id
2151 *
2152 * @param integer Page id
2153 * @param string Title String to link
2154 * @param array Result row
2155 * @param array Additional parameters for marking up seach words
2156 * @return string <A> tag wrapped title string.
2157 */
2158 function linkPage($id,$str,$row=array(),$markUpSwParams=array()) {
2159
2160 // Parameters for link:
2161 $urlParameters = (array)unserialize($row['cHashParams']);
2162
2163 // Add &type and &MP variable:
2164 if ($row['data_page_type']) $urlParameters['type'] = $row['data_page_type'];
2165 if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
2166 if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
2167
2168 // markup-GET vars:
2169 $urlParameters = array_merge($urlParameters, $markUpSwParams);
2170
2171 // 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...
2172 if (!is_array($this->domain_records[$id])) {
2173 $this->getPathFromPageId($id);
2174 }
2175
2176 // If external domain, then link to that:
2177 if (count($this->domain_records[$id])) {
2178 reset($this->domain_records[$id]);
2179 $firstDom = current($this->domain_records[$id]);
2180 $scheme = t3lib_div::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2181
2182 $addParams = '';
2183 if (is_array($urlParameters)) {
2184 if (count($urlParameters)) {
2185 $addParams.= t3lib_div::implodeArrayForUrl('',$urlParameters);
2186 }
2187 }
2188
2189 if ($target=$this->conf['search.']['detect_sys_domain_records.']['target']) {
2190 $target = ' target="'.$target.'"';
2191 }
2192 return '<a href="'.htmlspecialchars($scheme.$firstDom.'/index.php?id='.$id.$addParams).'"'.$target.'>'.htmlspecialchars($str).'</a>';
2193 } else {
2194 return $this->pi_linkToPage($str,$id,$this->conf['result_link_target'],$urlParameters);
2195 }
2196 }
2197
2198 /**
2199 * Returns the path to the page $id
2200 *
2201 * @param integer Page ID
2202 * @param string MP variable content.
2203 * @return string Root line for result.
2204 */
2205 function getRootLine($id,$pathMP='') {
2206 $identStr = $id.'|'.$pathMP;
2207
2208 if (!isset($this->cache_path[$identStr])) {
2209 $this->cache_rl[$identStr] = $GLOBALS['TSFE']->sys_page->getRootLine($id,$pathMP);
2210 }
2211 return $this->cache_rl[$identStr];
2212 }
2213
2214 /**
2215 * Gets the first sys_domain record for the page, $id
2216 *
2217 * @param integer Page id
2218 * @return string Domain name
2219 */
2220 function getFirstSysDomainRecordForPage($id) {
2221 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', 'pid='.intval($id).$this->cObj->enableFields('sys_domain'), '', 'sorting');
2222 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
2223 return rtrim($row['domainName'], '/');
2224 }
2225
2226 /**
2227 * Returns the path to the page $id
2228 *
2229 * @param integer Page ID
2230 * @param string MP variable content
2231 * @return string Path
2232 */
2233 function getPathFromPageId($id,$pathMP='') {
2234
2235 $identStr = $id.'|'.$pathMP;
2236
2237 if (!isset($this->cache_path[$identStr])) {
2238 $this->fe_groups_required[$id] = array();
2239 $this->domain_records[$id] = array();
2240 $rl = $this->getRootLine($id,$pathMP);
2241 $hitRoot = 0;
2242 $path = '';
2243 if (is_array($rl) && count($rl)) {
2244 foreach ($rl as $k => $v) {
2245 // Check fe_user
2246 if ($v['fe_group'] && ($v['uid']==$id || $v['extendToSubpages'])) {
2247 $this->fe_groups_required[$id][]=$v['fe_group'];
2248 }
2249 // Check sys_domain.
2250 if ($this->conf['search.']['detect_sys_domain_records']) {
2251 $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2252 if ($sysDName) {
2253 $this->domain_records[$id][] = $sysDName;
2254 // Set path accordingly:
2255 $path = $sysDName.$path;
2256 break;
2257 }
2258 }
2259
2260 // Stop, if we find that the current id is the current root page.
2261 if ($v['uid']==$GLOBALS['TSFE']->config['rootLine'][0]['uid']) {
2262 break;
2263 }
2264 $path = '/'.$v['title'].$path;
2265 }
2266 }
2267
2268 $this->cache_path[$identStr] = $path;
2269
2270 if (is_array($this->conf['path_stdWrap.'])) {
2271 $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2272 }
2273 }
2274
2275 return $this->cache_path[$identStr];
2276 }
2277
2278 /**
2279 * Return the menu of pages used for the selector.
2280 *
2281 * @param integer Page ID for which to return menu
2282 * @return array Menu items (for making the section selector box)
2283 */
2284 function getMenu($id) {
2285 if ($this->conf['show.']['LxALLtypes']) {
2286 $output = Array();
2287 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', 'pid='.intval($id).$this->cObj->enableFields('pages'), '', 'sorting');
2288 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2289 $output[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
2290 }
2291 $GLOBALS['TYPO3_DB']->sql_free_result($res);
2292 return $output;
2293 } else {
2294 return $GLOBALS['TSFE']->sys_page->getMenu($id);
2295 }
2296 }
2297
2298 /**
2299 * Returns if an item type is a multipage item type
2300 *
2301 * @param string Item type
2302 * @return boolean TRUE if multipage capable
2303 */
2304 function multiplePagesType($item_type) {
2305 return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2306 }
2307
2308 /**
2309 * Converts the input string from utf-8 to the backend charset.
2310 *
2311 * @param string String to convert (utf-8)
2312 * @return string Converted string (backend charset if different from utf-8)
2313 */
2314 function utf8_to_currentCharset($str) {
2315 return $GLOBALS['TSFE']->csConv($str,'utf-8');
2316 }
2317
2318 /**
2319 * Returns an object reference to the hook object if any
2320 *
2321 * @param string Name of the function you want to call / hook key
2322 * @return object Hook object, if any. Otherwise NULL.
2323 */
2324 function hookRequest($functionName) {
2325 global $TYPO3_CONF_VARS;
2326
2327 // Hook: menuConfig_preProcessModMenu
2328 if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2329 $hookObj = t3lib_div::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2330 if (method_exists ($hookObj, $functionName)) {
2331 $hookObj->pObj = $this;
2332 return $hookObj;
2333 }
2334 }
2335 }
2336
2337 /**
2338 * Obtains the URL of the search target page
2339 *
2340 * @return string
2341 */
2342 protected function getSearchFormActionURL() {
2343 $targetUrlPid = $this->getSearchFormActionPidFromTS();
2344 if ($targetUrlPid == 0) {
2345 $targetUrlPid = $GLOBALS['TSFE']->id;
2346 }
2347 return $this->pi_getPageLink($targetUrlPid, $GLOBALS['TSFE']->sPre);
2348 }
2349
2350 /**
2351 * Obtains search form target pid from the TypoScript configuration
2352 *
2353 * @return int
2354 */
2355 protected function getSearchFormActionPidFromTS() {
2356 $result = 0;
2357 if (isset($this->conf['search.']['targetPid']) || isset($this->conf['search.']['targetPid.'])) {
2358 if (is_array($this->conf['search.']['targetPid.'])) {
2359 $result = $this->cObj->stdWrap($this->conf['search.']['targetPid'], $this->conf['search.']['targetPid.']);
2360 }
2361 else {
2362 $result = $this->conf['search.']['targetPid'];
2363 }
2364 $result = intval($result);
2365 }
2366 return $result;
2367 }
2368
2369 /**
2370 * Formats date as 'created' date
2371 *
2372 * @param int $date
2373 * @param string $defaultFormat
2374 * @return string
2375 */
2376 protected function formatCreatedDate($date) {
2377 $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2378 return $this->formatDate($date, 'created', $defaultFormat);
2379 }
2380
2381 /**
2382 * Formats date as 'modified' date
2383 *
2384 * @param int $date
2385 * @param string $defaultFormat
2386 * @return string
2387 */
2388 protected function formatModifiedDate($date) {
2389 $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' .
2390 $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2391 return $this->formatDate($date, 'modified', $defaultFormat);
2392 }
2393
2394 /**
2395 * Formats the date using format string from TypoScript or default format
2396 * if TypoScript format is not set
2397 *
2398 * @param int $date
2399 * @param string $tsKey
2400 * @param string $defaultFormat
2401 * @return string
2402 */
2403 protected function formatDate($date, $tsKey, $defaultFormat) {
2404 $strftimeFormat = $this->conf['dateFormat.'][$tsKey];
2405 if ($strftimeFormat) {
2406 $result = strftime($strftimeFormat, $date);
2407 }
2408 else {
2409 $result = date($defaultFormat, $date);
2410 }
2411 return $result;
2412 }
2413 }
2414
2415
2416 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php'])) {
2417 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']);
2418 }
2419
2420 ?>