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