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