class.tx_indexedsearch.php

Administrator Admin, 2006-04-05 20:46

Download (91.1 KB)

 
1
<?php
2
/***************************************************************
3
*  Copyright notice
4
*
5
*  (c) 2001-2006 Kasper Skaarhoj (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
 * $Id: class.tx_indexedsearch.php 1791 2006-11-16 20:47:27Z masi $
31
 *
32
 * Creates a searchform for indexed search. Indexing must be enabled
33
 * for this to make sense.
34
 *
35
 * @author        Kasper Skaarhoj <kasperYYYY@typo3.com>
36
 * @co-author        Christian Jul Jensen <christian@typo3.com>
37
 */
38
/**
39
 * [CLASS/FUNCTION INDEX of SCRIPT]
40
 *
41
 *
42
 *
43
 *  123: class tx_indexedsearch extends tslib_pibase
44
 *  168:     function main($content, $conf)
45
 *  200:     function initialize()
46
 *  413:     function getSearchWords($defOp)
47
 *  447:     function procSearchWordsByLexer($SWArr)
48
 *
49
 *              SECTION: Main functions
50
 *  491:     function doSearch($sWArr)
51
 *  549:     function getResultRows($sWArr,$freeIndexUid=-1)
52
 *  623:     function getResultRows_SQLpointer($sWArr,$freeIndexUid=-1)
53
 *  647:     function getDisplayResults($sWArr, $resData, $freeIndexUid=-1)
54
 *  699:     function compileResult($resultRows, $freeIndexUid=-1)
55
 *
56
 *              SECTION: Searching functions (SQL)
57
 *  800:     function getPhashList($sWArr)
58
 *  901:     function execPHashListQuery($wordSel,$plusQ='')
59
 *  921:     function sectionTableWhere()
60
 *  968:     function mediaTypeWhere()
61
 *  993:     function languageWhere()
62
 * 1005:     function freeIndexUidWhere($freeIndexUid)
63
 * 1046:     function execFinalQuery($list,$freeIndexUid=-1)
64
 * 1189:     function checkResume($row)
65
 * 1236:     function isDescending($inverse=FALSE)
66
 * 1250:     function writeSearchStat($sWArr,$count,$pt)
67
 *
68
 *              SECTION: HTML output functions
69
 * 1302:     function makeSearchForm($optValues)
70
 * 1436:     function renderSelectBoxValues($value,$optValues)
71
 * 1455:     function printRules()
72
 * 1474:     function printResultSectionLinks()
73
 * 1508:     function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
74
 * 1529:     function printResultRow($row, $headerOnly=0)
75
 * 1598:     function pi_list_browseresults($showResultCount=1,$addString='',$addPart='',$freeIndexUid=-1)
76
 *
77
 *              SECTION: Support functions for HTML output (with a minimum of fixed markup)
78
 * 1686:     function prepareResultRowTemplateData($row, $headerOnly)
79
 * 1740:     function tellUsWhatIsSeachedFor($sWArr)
80
 * 1774:     function wrapSW($str)
81
 * 1786:     function renderSelectBox($name,$value,$optValues)
82
 * 1810:     function makePointerSelector_link($str,$p,$freeIndexUid)
83
 * 1825:     function makeItemTypeIcon($it,$alt='',$specRowConf)
84
 * 1867:     function makeRating($row)
85
 * 1911:     function makeDescription($row,$noMarkup=0,$lgd=180)
86
 * 1942:     function markupSWpartsOfString($str)
87
 * 2022:     function makeTitle($row)
88
 * 2046:     function makeInfo($row,$tmplArray)
89
 * 2075:     function getSpecialConfigForRow($row)
90
 * 2099:     function makeLanguageIndication($row)
91
 * 2142:     function makeAccessIndication($id)
92
 * 2157:     function linkPage($id,$str,$row=array(),$markUpSwParams=array())
93
 * 2201:     function getRootLine($id,$pathMP='')
94
 * 2216:     function getFirstSysDomainRecordForPage($id)
95
 * 2229:     function getPathFromPageId($id,$pathMP='')
96
 * 2281:     function getMenu($id)
97
 * 2300:     function multiplePagesType($item_type)
98
 * 2310:     function utf8_to_currentCharset($str)
99
 * 2320:     function &hookRequest($functionName)
100
 *
101
 * TOTAL FUNCTIONS: 48
102
 * (This index is automatically created/updated by the extension "extdeveval")
103
 *
104
 */
105

    
106

    
107

    
108
require_once(PATH_tslib.'class.tslib_pibase.php');
109
require_once(PATH_tslib.'class.tslib_search.php');
110
require_once(t3lib_extMgm::extPath('indexed_search').'class.indexer.php');
111

    
112

    
113
/**
114
 * Index search frontend
115
 *
116
 * Creates a searchform for indexed search. Indexing must be enabled
117
 * for this to make sense.
118
 *
119
 * @package TYPO3
120
 * @subpackage tx_indexedsearch
121
 * @author        Kasper Skaarhoj <kasperYYYY@typo3.com>
122
 */
123
class tx_indexedsearch extends tslib_pibase {
124
        var $prefixForm = 'tx_indexedsearch_form';        // Form name
125
        var $prefixId = 'tx_indexedsearch';        // Same as class name
126
        var $scriptRelPath = 'pi/class.tx_indexedsearch.php';    // Path to this script relative to the extension dir.
127
        var $extKey = 'indexed_search';    // The extension key.
128

    
129
        var $join_pages = 0;        // See document for info about this flag...
130
        var $defaultResultNumber = 10;
131

    
132
        var $operator_translate_table = Array (                // case-sensitive. Defines the words, which will be operators between words
133
                Array ('+' , 'AND'),
134
                Array ('|' , 'OR'),
135
                Array ('-' , 'AND NOT'),
136
                        // english
137
#                Array ('AND' , 'AND'),
138
#                Array ('OR' , 'OR'),
139
#                Array ('NOT' , 'AND NOT'),
140
        );
141

    
142
                // Internal variable
143
        var $wholeSiteIdList = 0;                // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
144

    
145
                // Internals:
146
        var $sWArr = array();                        // Search Words and operators
147
        var $optValues = array();                // Selector box values for search configuration form
148
        var $firstRow = Array();                // Will hold the first row in result - used to calculate relative hit-ratings.
149

    
150
        var $cache_path = array();                // Caching of page path
151
        var $cache_rl = array();                // Caching of root line data
152
        var $fe_groups_required = array();        // Required fe_groups memberships for display of a result.
153
        var $domain_records = array();                // Domain records (?)
154
        var $wSelClauses = array();                // Select clauses for individual words
155
        var $resultSections = array();                // Page tree sections for search result.
156
        var $external_parsers = array();        // External parser objects
157
        var $iconFileNameCache = array();        // Storage of icons....
158
        var $lexerObj;                                // Lexer object
159
        var $templateCode;                        // Will hold the content of $conf['templateFile']
160
        var $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
161

    
162

    
163
        /**
164
         * Main function, called from TypoScript as a USER_INT object.
165
         *
166
         * @param        string                Content input, ignore (just put blank string)
167
         * @param        array                TypoScript configuration of the plugin!
168
         * @return        string                HTML code for the search form / result display.
169
         */
170
        function main($content, $conf)    {
171

    
172
                        // Initialize:
173
                $this->conf = $conf;
174
                $this->pi_loadLL();
175
                $this->pi_setPiVarDefaults();
176

    
177
                        // Initialize the indexer-class - just to use a few function (for making hashes)
178
                $this->indexerObj = t3lib_div::makeInstance('tx_indexedsearch_indexer');
179

    
180
                        // Initialize:
181
                $this->initialize();
182

    
183
                        // Do search:
184
                        // If there were any search words entered...
185
                if (is_array($this->sWArr))        {
186
                        $content = $this->doSearch($this->sWArr);
187
                }
188

    
189
                        // Finally compile all the content, form, messages and results:
190
                $content = $this->makeSearchForm($this->optValues).
191
                        $this->printRules().
192
                        $content;
193

    
194
        return $this->pi_wrapInBaseClass($content);
195
    }
196

    
197
        /**
198
         * Initialize internal variables, especially selector box values for the search form and search words
199
         *
200
         * @return        void
201
         */
202
        function initialize()        {
203
                global $TYPO3_CONF_VARS;
204

    
205
                        // Initialize external document parsers for icon display and other soft operations
206
                if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers']))        {
207
                        foreach ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef)        {
208
                                $this->external_parsers[$extension] = &t3lib_div::getUserObj($_objRef);
209

    
210
                                        // Init parser and if it returns false, unset its entry again:
211
                                if (!$this->external_parsers[$extension]->softInit($extension))        {
212
                                        unset($this->external_parsers[$extension]);
213
                                }
214
                        }
215
                }
216

    
217
                        // Init lexer (used to post-processing of search words)
218
                $lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ?
219
                                                $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] :
220
                                                'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
221
                $this->lexerObj = &t3lib_div::getUserObj($lexerObjRef);
222

    
223
                        // If "_sections" is set, this value overrides any existing value.
224
                if ($this->piVars['_sections'])                $this->piVars['sections'] = $this->piVars['_sections'];
225

    
226
                        // If "_sections" is set, this value overrides any existing value.
227
                if ($this->piVars['_freeIndexUid']!=='_')                $this->piVars['freeIndexUid'] = $this->piVars['_freeIndexUid'];
228

    
229
                        // Add previous search words to current
230
                if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev'])        {
231
                        $this->piVars['sword'] = trim($this->piVars['sword_prev']).' '.$this->piVars['sword'];
232
                }
233

    
234
                $this->piVars['results'] = t3lib_div::intInRange($this->piVars['results'],1,100000,$this->defaultResultNumber);
235

    
236
                        // Selector-box values defined here:
237
                $this->optValues = Array(
238
                        'type' => Array(
239
                                '0' => $this->pi_getLL('opt_type_0'),
240
                                '1' => $this->pi_getLL('opt_type_1'),
241
                                '2' => $this->pi_getLL('opt_type_2'),
242
                                '3' => $this->pi_getLL('opt_type_3'),
243
                                '10' => $this->pi_getLL('opt_type_10'),
244
                                '20' => $this->pi_getLL('opt_type_20'),
245
                        ),
246
                        'defOp' => Array(
247
                                '0' => $this->pi_getLL('opt_defOp_0'),
248
                                '1' => $this->pi_getLL('opt_defOp_1'),
249
                        ),
250
                        'sections' => Array(
251
                                '0' => $this->pi_getLL('opt_sections_0'),
252
                                '-1' => $this->pi_getLL('opt_sections_-1'),
253
                                '-2' => $this->pi_getLL('opt_sections_-2'),
254
                                '-3' => $this->pi_getLL('opt_sections_-3'),
255
                                // 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.
256
                        ),
257
                        'freeIndexUid' => Array(
258
                                '-1' => $this->pi_getLL('opt_freeIndexUid_-1'),
259
                                '-2' => $this->pi_getLL('opt_freeIndexUid_-2'),
260
                                '0' => $this->pi_getLL('opt_freeIndexUid_0'),
261
                        ),
262
                        'media' => Array(
263
                                '-1' => $this->pi_getLL('opt_media_-1'),
264
                                '0' => $this->pi_getLL('opt_media_0'),
265
                                '-2' => $this->pi_getLL('opt_media_-2'),
266
                        ),
267
                        'order' => Array(
268
                                'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
269
                                'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
270
                                'rank_first' => $this->pi_getLL('opt_order_rank_first'),
271
                                'rank_count' => $this->pi_getLL('opt_order_rank_count'),
272
                                'mtime' => $this->pi_getLL('opt_order_mtime'),
273
                                'title' => $this->pi_getLL('opt_order_title'),
274
                                'crdate' => $this->pi_getLL('opt_order_crdate'),
275
                        ),
276
                        'group' => Array (
277
                                'sections' => $this->pi_getLL('opt_group_sections'),
278
                                'flat' => $this->pi_getLL('opt_group_flat'),
279
                        ),
280
                        'lang' => Array (
281
                                -1 => $this->pi_getLL('opt_lang_-1'),
282
                                0 => $this->pi_getLL('opt_lang_0'),
283
                        ),
284
                        'desc' => Array (
285
                                '0' => $this->pi_getLL('opt_desc_0'),
286
                                '1' => $this->pi_getLL('opt_desc_1'),
287
                        ),
288
                        'results' => Array (
289
                                '10' => '10',
290
                                '20' => '20',
291
                                '50' => '50',
292
                                '100' => '100',
293
                        )
294
                );
295

    
296
                        // Free Index Uid:
297
                if ($this->conf['search.']['defaultFreeIndexUidList'])        {
298
                        $uidList = t3lib_div::intExplode(',', $this->conf['search.']['defaultFreeIndexUidList']);
299
                        $indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title','index_config','uid IN ('.implode(',',$uidList).')'.$this->cObj->enableFields('index_config'),'','','','uid');
300

    
301
                        foreach ($uidList as $uidValue)        {
302
                                if (is_array($indexCfgRecords[$uidValue]))        {
303
                                        $this->optValues['freeIndexUid'][$uidValue] = $indexCfgRecords[$uidValue]['title'];
304
                                }
305
                        }
306
                }
307

    
308

    
309
                        // Add media to search in:
310
                if (strlen(trim($this->conf['search.']['mediaList'])))        {
311
                        $mediaList = implode(',', t3lib_div::trimExplode(',', $this->conf['search.']['mediaList'], 1));
312
                }
313
                foreach ($this->external_parsers as $extension => $obj)        {
314
                                // Skip unwanted extensions
315
                        if ($mediaList && !t3lib_div::inList($mediaList, $extension))        { continue; }
316

    
317
                        if ($name = $obj->searchTypeMediaTitle($extension))        {
318
                                $this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_'.$extension,$name);
319
                        }
320
                }
321

    
322
                        // Add operators for various languages
323
                        // Converts the operators to UTF-8 and lowercase
324
                $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');
325
                $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');
326
                $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');
327

    
328
                        // This is the id of the site root. This value may be a commalist of integer (prepared for this)
329
                $this->wholeSiteIdList = intval($GLOBALS['TSFE']->config['rootLine'][0]['uid']);
330

    
331
                        // Creating levels for section menu:
332
                        // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
333
                if ($this->conf['show.']['L1sections'])        {
334
                        $firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
335
                        while(list($kk,$mR) = each($firstLevelMenu))        {
336
                                if ($mR['doktype']!=5)        {
337
                                        $this->optValues['sections']['rl1_'.$mR['uid']] = trim($this->pi_getLL('opt_RL1').' '.$mR['title']);
338
                                        if ($this->conf['show.']['L2sections'])        {
339
                                                $secondLevelMenu = $this->getMenu($mR['uid']);
340
                                                while(list($kk2,$mR2) = each($secondLevelMenu))        {
341
                                                        if ($mR['doktype']!=5)        {
342
                                                                $this->optValues['sections']['rl2_'.$mR2['uid']] = trim($this->pi_getLL('opt_RL2').' '.$mR2['title']);
343
                                                        } else unset($secondLevelMenu[$kk2]);
344
                                                }
345
                                                $this->optValues['sections']['rl2_'.implode(',',array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
346
                                        }
347
                                } else unset($firstLevelMenu[$kk]);
348
                        }
349
                        $this->optValues['sections']['rl1_'.implode(',',array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
350
                }
351

    
352
                        // 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.
353
                        // 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...
354
                if ($this->conf['search.']['rootPidList'])        {
355
                        $this->wholeSiteIdList = implode(',',t3lib_div::intExplode(',',$this->conf['search.']['rootPidList']));
356
                }
357

    
358
                        // Load the template
359
                $this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
360

    
361
                        // Add search languages:
362
                $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1'.$this->cObj->enableFields('sys_language'));
363
                while($lR = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))        {
364
                        $this->optValues['lang'][$lR['uid']] = $lR['title'];
365
                }
366

    
367
                        // Calling hook for modification of initialized content
368
                if ($hookObj = &$this->hookRequest('initialize_postProc'))        {
369
                        $hookObj->initialize_postProc();
370
                }
371

    
372
                        // Default values set:
373
                        // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
374
                foreach ($this->optValues as $kk => $vv)        {
375
                        if (!isset($this->piVars[$kk]))        {
376
                                reset($vv);
377
                                $this->piVars[$kk] = key($vv);
378
                        }
379
                }
380

    
381
                        // Blind selectors:
382
                if (is_array($this->conf['blind.']))        {
383
                        foreach ($this->conf['blind.'] as $kk => $vv)        {
384
                                if (is_array($vv))        {
385
                                        foreach ($vv as $kkk => $vvv)        {
386
                                                if (!is_array($vvv) && $vvv && is_array($this->optValues[substr($kk,0,-1)]))        {
387
                                                        unset($this->optValues[substr($kk,0,-1)][$kkk]);
388
                                                }
389
                                        }
390
                                } elseif ($vv) {        // If value is not set, unset the option array.
391
                                        unset($this->optValues[$kk]);
392
                                }
393
                        }
394
                }
395

    
396
                        // This gets the search-words into the $sWArr:
397
                $this->sWArr = $this->getSearchWords($this->piVars['defOp']);
398
        }
399

    
400
        /**
401
         * Splits the search word input into an array where each word is represented by an array with key "sword" holding the search word and key "oper" holds the SQL operator (eg. AND, OR)
402
         *
403
         * Only words with 2 or more characters are accepted
404
         * Max 200 chars total
405
         * Space is used to split words, "" can be used search for a whole string (not indexed search then)
406
         * AND, OR and NOT are prefix words, overruling the default operator
407
         * +/|/- equals AND, OR and NOT as operators.
408
         * All search words are converted to lowercase.
409
         *
410
         * $defOp is the default operator. 1=OR, 0=AND
411
         *
412
         * @param        boolean                If true, the default operator will be OR, not AND
413
         * @return        array                Returns array with search words if any found
414
         */
415
        function getSearchWords($defOp)        {
416
                        // 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!)
417
                $inSW = substr($this->piVars['sword'],0,200);
418

    
419
                        // Convert to UTF-8 + conv. entities (was also converted during indexing!)
420
                $inSW = $GLOBALS['TSFE']->csConvObj->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset);
421
                $inSW = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($inSW,TRUE);
422

    
423
                if ($hookObj = &$this->hookRequest('getSearchWords'))        {
424
                        return $hookObj->getSearchWords_splitSWords($inSW, $defOp);
425
                } else {
426

    
427
                        if ($this->piVars['type']==20)        {
428
                                return array(array('sword'=>trim($inSW), 'oper'=>'AND'));
429
                        } else {
430
                                $search = t3lib_div::makeInstance('tslib_search');
431
                                $search->default_operator = $defOp==1 ? 'OR' : 'AND';
432
                                $search->operator_translate_table = $this->operator_translate_table;
433
                                $search->register_and_explode_search_string($inSW);
434

    
435
                                if (is_array($search->sword_array))        {
436
                                        return $this->procSearchWordsByLexer($search->sword_array);
437
                                }
438
                        }
439
                }
440
        }
441

    
442
        /**
443
         * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
444
         * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
445
         *
446
         * @param        array                Search word array
447
         * @return        array                Search word array, processed through lexer
448
         */
449
        function procSearchWordsByLexer($SWArr)        {
450

    
451
                        // Init output variable:
452
                $newSWArr = array();
453

    
454
                        // Traverse the search word array:
455
                foreach ($SWArr as $wordDef)        {
456
                        if (!strstr($wordDef['sword'],' '))        {        // No space in word (otherwise it might be a sentense in quotes like "there is").
457
                                        // Split the search word by lexer:
458
                                $res = $this->lexerObj->split2Words($wordDef['sword']);
459

    
460
                                        // Traverse lexer result and add all words again:
461
                                foreach ($res as $word)        {
462
                                        $newSWArr[] = array('sword'=>$word, 'oper'=>$wordDef['oper']);
463
                                }
464
                        } else {
465
                                $newSWArr[] = $wordDef;
466
                        }
467
                }
468

    
469
                        // Return result:
470
                return $newSWArr;
471
        }
472

    
473

    
474

    
475

    
476

    
477

    
478

    
479

    
480

    
481
        /*****************************
482
         *
483
         * Main functions
484
         *
485
         *****************************/
486

    
487
        /**
488
         * Performs the search, the display and writing stats
489
         *
490
         * @param        array                Search words in array, see ->getSearchWords() for details
491
         * @return        string                HTML for result display.
492
         */
493
        function doSearch($sWArr)        {
494

    
495
                        // Find free index uid:
496
                $freeIndexUid = $this->piVars['freeIndexUid'];
497
                if ($freeIndexUid==-2)        {
498
                        $freeIndexUid = $this->conf['search.']['defaultFreeIndexUidList'];
499
                }
500

    
501
                $indexCfgs = t3lib_div::intExplode(',',$freeIndexUid);
502
                $accumulatedContent = '';
503

    
504
                foreach ($indexCfgs as $freeIndexUid)        {
505
                                // Get result rows:
506
                        $pt1 = t3lib_div::milliseconds();
507
                        if ($hookObj = &$this->hookRequest('getResultRows'))        {
508
                                $resData = $hookObj->getResultRows($sWArr,$freeIndexUid);
509
                        } else {
510
                                $resData = $this->getResultRows($sWArr,$freeIndexUid);
511
                        }
512

    
513
                                // Display search results:
514
                        $pt2 = t3lib_div::milliseconds();
515
                        if ($hookObj = &$this->hookRequest('getDisplayResults'))        {
516
                                $content = $hookObj->getDisplayResults($sWArr, $resData, $freeIndexUid);
517
                        } else {
518
                                $content = $this->getDisplayResults($sWArr, $resData, $freeIndexUid);
519
                        }
520

    
521
                        $pt3 = t3lib_div::milliseconds();
522

    
523
                                // Create header if we are searching more than one indexing configuration:
524
                        if (count($indexCfgs)>1)        {
525
                                if ($freeIndexUid>0)        {
526
                                        list($indexCfgRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('title','index_config','uid='.intval($freeIndexUid).$this->cObj->enableFields('index_config'));
527
                                        $titleString = $indexCfgRec['title'];
528
                                } else {
529
                                        $titleString = $this->pi_getLL('opt_freeIndexUid_header_'.$freeIndexUid);
530
                                }
531
                                $content = '<h1 class="tx-indexedsearch-category">'.htmlspecialchars($titleString).'</h1>'.$content;
532
                        }
533

    
534
                        $accumulatedContent.=$content;
535
                }
536

    
537
                        // Write search statistics
538
                $this->writeSearchStat($sWArr,$resData['count'],array($pt1,$pt2,$pt3));
539

    
540
                        // Return content:
541
                return $accumulatedContent;
542
        }
543

    
544
        /**
545
         * Get search result rows / data from database. Returned as data in array.
546
         *
547
         * @param        array                Search word array
548
         * @param        integer                Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
549
         * @return        array                False if no result, otherwise an array with keys for first row, result rows and total number of results found.
550
         */
551
        function getResultRows($sWArr,$freeIndexUid=-1)        {
552

    
553
                        // Getting SQL result pointer:
554
                        $GLOBALS['TT']->push('Searching result');
555
                $res = $this->getResultRows_SQLpointer($sWArr,$freeIndexUid);
556
                        $GLOBALS['TT']->pull();
557

    
558
                        // Organize and process result:
559
                if ($res)        {
560

    
561
                        $count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);        // Total search-result count
562
                        $pointer = t3lib_div::intInRange($this->piVars['pointer'], 0, floor($count/$this->piVars['results']));        // The pointer is set to the result page that is currently being viewed
563

    
564
                                // Initialize result accumulation variables:
565
                        $c = 0;        // Result pointer: Counts up the position in the current search-result
566
                        $grouping_phashes = array();        // Used to filter out duplicates.
567
                        $grouping_chashes = array();        // Used to filter out duplicates BASED ON cHash.
568
                        $firstRow = Array();        // Will hold the first row in result - used to calculate relative hit-ratings.
569
                        $resultRows = Array();        // Will hold the results rows for display.
570

    
571
                                // Now, traverse result and put the rows to be displayed into an array
572
                                // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
573
                        while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))        {
574

    
575
                                        // Set first row:
576
                                if (!$c)        {
577
                                        $firstRow = $row;
578
                                }
579

    
580
                                $row['show_resume'] = $this->checkResume($row);        // Tells whether we can link directly to a document or not (depends on possible right problems)
581

    
582
                                $phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
583
                                $chashGr = !in_array($row['contentHash'].'.'.$row['data_page_id'], $grouping_chashes);
584
                                if ($phashGr && $chashGr)        {
585
                                        if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords'])        {        // Only if the resume may be shown are we going to filter out duplicates...
586
                                                if (!$this->multiplePagesType($row['item_type']))        {        // Only on documents which are not multiple pages documents
587
                                                        $grouping_phashes[] = $row['phash_grouping'];
588
                                                }
589
                                                $grouping_chashes[] = $row['contentHash'].'.'.$row['data_page_id'];
590

    
591
                                                $c++;        // Increase the result pointer
592

    
593
                                                        // All rows for display is put into resultRows[]
594
                                                if ($c > $pointer * $this->piVars['results'])        {
595
                                                        $row['result_number'] = $c;
596
                                                        $resultRows[] = $row;
597
                                                                // This may lead to a problem: If the result check is not stopped here, the search will take longer. However the result counter will not filter out grouped cHashes/pHashes that were not processed yet.
598
                                                        if (($c+1) > ($pointer+1)*$this->piVars['results'])        break;
599
                                                }
600
                                        } else {
601
                                                $count--;        // Skip this row if the user cannot view it (missing permission)
602
                                        }
603
                                } else {
604
                                        $count--;        // For each time a phash_grouping document is found (which is thus not displayed) the search-result count is reduced, so that it matches the number of rows displayed.
605
                                }
606
                        }
607

    
608
                        return array(
609
                                                'resultRows' => $resultRows,
610
                                                'firstRow' => $firstRow,
611
                                                'count' => $count
612
                                        );
613
                } else {        // No results found:
614
                        return FALSE;
615
                }
616
        }
617

    
618
        /**
619
         * Gets a SQL result pointer to traverse for the search records.
620
         *
621
         * @param        array                Search words
622
         * @param        integer                Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
623
         * @return        pointer
624
         */
625
        function getResultRows_SQLpointer($sWArr,$freeIndexUid=-1)        {
626
                                // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
627
                $list = $this->getPhashList($sWArr);
628

    
629
                        // Perform SQL Search / collection of result rows array:
630
                if ($list)        {
631
                                // Do the search:
632
                        $GLOBALS['TT']->push('execFinalQuery');
633
                        $res = $this->execFinalQuery($list,$freeIndexUid);
634
                        $GLOBALS['TT']->pull();
635
                        return $res;
636
                } else {
637
                        return FALSE;
638
                }
639
        }
640

    
641
        /**
642
         * Compiles the HTML display of the incoming array of result rows.
643
         *
644
         * @param        array                Search words array (for display of text describing what was searched for)
645
         * @param        array                Array with result rows, count, first row.
646
         * @param        integer                Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
647
         * @return        string                HTML content to display result.
648
         */
649
        function getDisplayResults($sWArr, $resData, $freeIndexUid=-1)        {
650
                        // Perform display of result rows array:
651
                if ($resData)        {
652
                        $GLOBALS['TT']->push('Display Final result');
653

    
654
                                // Set first selected row (for calculation of ranking later)
655
                        $this->firstRow = $resData['firstRow'];
656

    
657
                                // Result display here:
658
                        $rowcontent = '';
659
                        $rowcontent.= $this->compileResult($resData['resultRows'], $freeIndexUid);
660

    
661
                                // Browsing box:
662
                        if ($resData['count'])        {
663
                                $this->internal['res_count'] = $resData['count'];
664
                                $this->internal['results_at_a_time'] = $this->piVars['results'];
665
                                $this->internal['maxPages'] = t3lib_div::intInRange($this->conf['search.']['page_links'],1,100,10);
666
                                $addString = ($resData['count']&&$this->piVars['group']=='sections'&&$freeIndexUid<=0 ? ' '.sprintf($this->pi_getLL(count($this->resultSections)>1?'inNsections':'inNsection'),count($this->resultSections)):'');
667
                                $browseBox1 = $this->pi_list_browseresults(1,$addString,$this->printResultSectionLinks(),$freeIndexUid);
668
                                $browseBox2 = $this->pi_list_browseresults(0,'','',$freeIndexUid);
669
                        }
670

    
671
                                // Browsing nav, bottom.
672
                        if ($resData['count'])        {
673
                                $content = $browseBox1.$rowcontent.$browseBox2;
674
                        } else {
675
                                $content = '<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
676
                        }
677

    
678
                        $GLOBALS['TT']->pull();
679
                } else {
680
                        $content.='<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
681
                }
682

    
683
                        // Print a message telling which words we searched for, and in which sections etc.
684
                $what = $this->tellUsWhatIsSeachedFor($sWArr).
685
                                (substr($this->piVars['sections'],0,2)=='rl'?' '.$this->pi_getLL('inSection','',1).' "'.substr($this->getPathFromPageId(substr($this->piVars['sections'],4)),1).'"':'');
686
                $what = '<div'.$this->pi_classParam('whatis').'><p>'.$what.'</p></div>';
687
                $content = $what.$content;
688

    
689
                        // Return content:
690
                return $content;
691
        }
692

    
693
        /**
694
         * Takes the array with resultrows as input and returns the result-HTML-code
695
         * Takes the "group" var into account: Makes a "section" or "flat" display.
696
         *
697
         * @param        array                Result rows
698
         * @param        integer                Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
699
         * @return        string                HTML
700
         */
701
        function compileResult($resultRows, $freeIndexUid=-1)        {
702
                $content = '';
703

    
704
                        // Transfer result rows to new variable, performing some mapping of sub-results etc.
705
                $newResultRows = array();
706
                foreach ($resultRows as $row)        {
707
                        $id = md5($row['phash_grouping']);
708
                        if (is_array($newResultRows[$id]))        {
709
                                if (!$newResultRows[$id]['show_resume'] && $row['show_resume'])        {        // swapping:
710

    
711
                                                // Remove old
712
                                        $subrows = $newResultRows[$id]['_sub'];
713
                                        unset($newResultRows[$id]['_sub']);
714
                                        $subrows[] = $newResultRows[$id];
715

    
716
                                                // Insert new:
717
                                        $newResultRows[$id] = $row;
718
                                        $newResultRows[$id]['_sub'] = $subrows;
719
                                } else $newResultRows[$id]['_sub'][] = $row;
720
                        } else {
721
                                $newResultRows[$id] = $row;
722
                        }
723
                }
724
                $resultRows = $newResultRows;
725
                $this->resultSections = array();
726

    
727
                if ($freeIndexUid<=0)        {
728
                        switch($this->piVars['group'])        {
729
                                case 'sections':
730

    
731
                                        $rl2flag = substr($this->piVars['sections'],0,2)=='rl';
732
                                        $sections = array();
733
                                        foreach ($resultRows as $row)        {
734
                                                $id = $row['rl0'].'-'.$row['rl1'].($rl2flag?'-'.$row['rl2']:'');
735
                                                $sections[$id][] = $row;
736
                                        }
737

    
738
                                        $this->resultSections = array();
739

    
740
                                        foreach ($sections as $id => $resultRows)        {
741
                                                $rlParts = explode('-',$id);
742
                                                $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1]?$rlParts[1]:$rlParts[0]);
743
                                                $theRLid = $rlParts[2] ? 'rl2_'.$rlParts[2]:($rlParts[1]?'rl1_'.$rlParts[1]:'0');
744

    
745
                                                $sectionName = $this->getPathFromPageId($theId);
746
                                                if ($sectionName{0} == '/') $sectionName = substr($sectionName,1);
747

    
748
                                                if (!trim($sectionName))        {
749
                                                        $sectionTitleLinked = $this->pi_getLL('unnamedSection','',1).':';
750
                                                } else {
751
                                                        $onclick = 'document.'.$this->prefixForm.'[\''.$this->prefixId.'[_sections]\'].value=\''.$theRLid.'\';document.'.$this->prefixForm.'.submit();return false;';
752
                                                        $sectionTitleLinked = '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.htmlspecialchars($sectionName).':</a>';
753
                                                }
754
                                                $this->resultSections[$id] = array($sectionName,count($resultRows));
755

    
756
                                                        // Add content header:
757
                                                $content.= $this->makeSectionHeader($id,$sectionTitleLinked,count($resultRows));
758

    
759
                                                        // Render result rows:
760
                                                foreach ($resultRows as $row)        {
761
                                                        $content.= $this->printResultRow($row);
762
                                                }
763
                                        }
764
                                break;
765
                                default:        // flat:
766
                                        foreach ($resultRows as $row)        {
767
                                                $content.= $this->printResultRow($row);
768
                                        }
769
                                break;
770
                        }
771
                } else {
772
                        foreach ($resultRows as $row)        {
773
                                $content.= $this->printResultRow($row);
774
                        }
775
                }
776

    
777
                return '<div'.$this->pi_classParam('res').'>'.$content.'</div>';
778
        }
779

    
780

    
781

    
782

    
783

    
784

    
785

    
786

    
787

    
788

    
789

    
790
        /***********************************
791
         *
792
         *        Searching functions (SQL)
793
         *
794
         ***********************************/
795

    
796
        /**
797
         * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the sWArr array.
798
         * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
799
         *
800
         * @param        array                Search word array
801
         * @return        string                List of integers
802
         */
803
        function getPhashList($sWArr)        {
804

    
805
                        // Initialize variables:
806
                $c=0;
807
                $totalHashList = array();        // This array accumulates the phash-values
808
                $this->wSelClauses = array();
809

    
810
                        // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
811
                foreach ($sWArr as $k => $v)        {
812
                                // Making the query for a single search word based on the search-type
813
                        $sWord = $v['sword'];        // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$v['sword'],'toLower');        // lower-case all of them...
814
                        $theType = (string)$this->piVars['type'];
815
                        if (strstr($sWord,' '))        $theType = 20;        // If there are spaces in the search-word, make a full text search instead.
816

    
817
                        $GLOBALS['TT']->push('SearchWord "'.$sWord.'" - $theType='.$theType);
818

    
819
                        $res = '';
820
                        $wSel='';
821

    
822
                                // Perform search for word:
823
                        switch($theType)        {
824
                                case '1':
825
                                        $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
826
                                        $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
827
                                break;
828
                                case '2':
829
                                        $wSel = "IW.baseword LIKE '".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
830
                                        $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
831
                                break;
832
                                case '3':
833
                                        $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."'";
834
                                        $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
835
                                break;
836
                                case '10':
837
                                        $wSel = 'IW.metaphone = '.$this->indexerObj->metaphone($sWord);
838
                                        $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
839
                                break;
840
                                case '20':
841
                                        $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
842
                                                                'ISEC.phash',
843
                                                                'index_section ISEC, index_fulltext IFT',
844
                                                                'IFT.fulltextdata LIKE \'%'.$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_fulltext').'%\' AND
845
                                                                        ISEC.phash = IFT.phash
846
                                                                        '.$this->sectionTableWhere(),
847
                                                                'ISEC.phash'
848
                                                        );
849
                                        $wSel = '1=1';
850

    
851
                                        if ($this->piVars['type']==20)        $this->piVars['order'] = 'mtime';                // If there is a fulltext search for a sentence there is a likeliness that sorting cannot be done by the rankings from the rel-table (because no relations will exist for the sentence in the word-table). So therefore mtime is used instaed. It is not required, but otherwise some hits may be left out.
852
                                break;
853
                                default:
854
                                        $wSel = 'IW.wid = '.$hash = $this->indexerObj->md5inthash($sWord);
855
                                        $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
856
                                break;
857
                        }
858

    
859
                                // Accumulate the word-select clauses
860
                        $this->wSelClauses[] = $wSel;
861

    
862
                                // If there was a query to do, then select all phash-integers which resulted from this.
863
                        if ($res)        {
864

    
865
                                        // Get phash list by searching for it:
866
                                $phashList = array();
867
                                while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))        {
868
                                        $phashList[] = $row['phash'];
869
                                }
870
                                $GLOBALS['TYPO3_DB']->sql_free_result($res);
871

    
872
                                        // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
873
                                if ($c) {
874
                                        switch($v['oper'])        {
875
                                                case 'OR':
876
                                                        $totalHashList = array_unique(array_merge($phashList,$totalHashList));
877
                                                break;
878
                                                case 'AND NOT':
879
                                                        $totalHashList = array_diff($totalHashList,$phashList);
880
                                                break;
881
                                                default:        // AND...
882
                                                        $totalHashList = array_intersect($totalHashList,$phashList);
883
                                                break;
884
                                        }
885
                                } else {
886
                                        $totalHashList = $phashList;        // First search
887
                                }
888
                        }
889

    
890
                        $GLOBALS['TT']->pull();
891
                        $c++;
892
                }
893

    
894
                return implode(',',$totalHashList);
895
        }
896

    
897
        /**
898
         * Returns a query which selects the search-word from the word/rel tables.
899
         *
900
         * @param        string                WHERE clause selecting the word from phash
901
         * @param        string                Additional AND clause in the end of the query.
902
         * @return        pointer                SQL result pointer
903
         */
904
        function execPHashListQuery($wordSel,$plusQ='')        {
905
                return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
906
                                        'IR.phash',
907
                                        'index_words IW,
908
                                                index_rel IR,
909
                                                index_section ISEC',
910
                                        $wordSel.'
911
                                                AND IW.wid=IR.wid
912
                                                AND ISEC.phash = IR.phash
913
                                                '.$this->sectionTableWhere().'
914
                                                '.$plusQ,
915
                                        'IR.phash'
916
                                );
917
        }
918

    
919
        /**
920
         * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
921
         *
922
         * @return        string                AND clause for selection of section in database.
923
         */
924
        function sectionTableWhere()        {
925
                $out = $this->wholeSiteIdList<0 ? '' : 'AND ISEC.rl0 IN ('.$this->wholeSiteIdList.')';
926

    
927
                $match = '';
928
                if (substr($this->piVars['sections'],0,4)=='rl1_')        {
929
                        $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
930
                        $out.= 'AND ISEC.rl1 IN ('.$list.')';
931
                        $match = TRUE;
932
                } elseif (substr($this->piVars['sections'],0,4)=='rl2_')        {
933
                        $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
934
                        $out.= 'AND ISEC.rl2 IN ('.$list.')';
935
                        $match = TRUE;
936
                } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields']))        {
937
                                // Traversing user configured fields to see if any of those are used to limit search to a section:
938
                        foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel)        {
939
                                if (substr($this->piVars['sections'],0,strlen($fieldName)+1)==$fieldName.'_')        {
940
                                        $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],strlen($fieldName)+1)));
941
                                        $out.= 'AND ISEC.'.$fieldName.' IN ('.$list.')';
942
                                        $match = TRUE;
943
                                        break;
944
                                }
945
                        }
946
                }
947

    
948
                        // If no match above, test the static types:
949
                if (!$match)        {
950
                        switch((string)$this->piVars['sections'])        {
951
                                case '-1':                // '-1' => 'Only this page',
952
                                        $out.= ' AND ISEC.page_id='.$GLOBALS['TSFE']->id;
953
                                break;
954
                                case '-2':                // '-2' => 'Top + level 1',
955
                                        $out.= ' AND ISEC.rl2=0';
956
                                break;
957
                                case '-3':                // '-3' => 'Level 2 and out',
958
                                        $out.= ' AND ISEC.rl2>0';
959
                                break;
960
                        }
961
                }
962

    
963
                return $out;
964
        }
965

    
966
        /**
967
         * Returns AND statement for selection of media type
968
         *
969
         * @return        string                AND statement for selection of media type
970
         */
971
        function mediaTypeWhere()        {
972

    
973
                switch((string)$this->piVars['media'])        {
974
                        case '0':                // '0' => 'Kun TYPO3 sider',
975
                                $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
976
                        break;
977
                        case '-2':                // All external documents
978
                                $out = 'AND IP.item_type!='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
979
                        break;
980
                        case '-1':        // All content
981
                                $out='';
982
                        break;
983
                        default:
984
                                $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['media'], 'index_phash');
985
                        break;
986
                }
987

    
988
                return $out;
989
        }
990

    
991
        /**
992
         * Returns AND statement for selection of langauge
993
         *
994
         * @return        string                AND statement for selection of langauge
995
         */
996
        function languageWhere()        {
997
                if ($this->piVars['lang']>=0)        {        // -1 is the same as ALL language.
998
                        return 'AND IP.sys_language_uid='.intval($this->piVars['lang']);
999
                }
1000
        }
1001

    
1002
        /**
1003
         * Where-clause for free index-uid value.
1004
         *
1005
         * @param        integer                Free Index UID value to limit search to.
1006
         * @return        string                WHERE SQL clause part.
1007
         */
1008
        function freeIndexUidWhere($freeIndexUid)        {
1009

    
1010
                if ($freeIndexUid>=0)        {
1011

    
1012
                                // First, look if the freeIndexUid is a meta configuration:
1013
                        list($indexCfgRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('indexcfgs','index_config','type=5 AND uid='.intval($freeIndexUid).$this->cObj->enableFields('index_config'));
1014
                        if (is_array($indexCfgRec))        {
1015
                                $refs = t3lib_div::trimExplode(',',$indexCfgRec['indexcfgs']);
1016
                                $list = array(-99);        // Default value to protect against empty array.
1017
                                foreach ($refs as $ref)        {
1018
                                        list($table,$uid) = t3lib_div::revExplode('_',$ref,2);
1019
                                        switch ($table)        {
1020
                                                case 'index_config':
1021
                                                        list($idxRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid','index_config','uid='.intval($uid).$this->cObj->enableFields('index_config'));
1022
                                                        if ($idxRec)        $list[] = $uid;
1023
                                                break;
1024
                                                case 'pages':
1025
                                                        $indexCfgRecordsFromPid = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid','index_config','pid='.intval($uid).$this->cObj->enableFields('index_config'));
1026
                                                        foreach ($indexCfgRecordsFromPid as $idxRec)        {
1027
                                                                $list[] = $idxRec['uid'];
1028
                                                        }
1029
                                                break;
1030
                                        }
1031
                                }
1032

    
1033
                                $list = array_unique($list);
1034
                        } else {
1035
                                $list = array(intval($freeIndexUid));
1036
                        }
1037

    
1038
                        return ' AND IP.freeIndexUid IN ('.implode(',',$list).')';
1039
                }
1040
        }
1041

    
1042
        /**
1043
         * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
1044
         *
1045
         * @param        string                List of phash integers which match the search.
1046
         * @param        integer                Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
1047
         * @return        pointer                Query result pointer
1048
         */
1049
        function execFinalQuery($list,$freeIndexUid=-1)        {
1050

    
1051
                        // Setting up methods of filtering results based on page types, access, etc.
1052
                $page_join = '';
1053
                $page_where = '';
1054

    
1055
                        // Indexing configuration clause:
1056
                $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
1057

    
1058
                        // Calling hook for alternative creation of page ID list
1059
                if ($hookObj = &$this->hookRequest('execFinalQuery_idList'))        {
1060
                        $page_where = $hookObj->execFinalQuery_idList($list);
1061
                } elseif ($this->join_pages)        {        // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
1062
                        $page_join = ',
1063
                                pages';
1064
                        $page_where = 'pages.uid = ISEC.page_id
1065
                                '.$this->cObj->enableFields('pages').'
1066
                                AND pages.no_search=0
1067
                                AND pages.doktype<200
1068
                        ';
1069
                } 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!
1070
                        $siteIdNumbers = t3lib_div::intExplode(',',$this->wholeSiteIdList);
1071
                        $id_list=array();
1072
                        while(list(,$rootId)=each($siteIdNumbers))        {
1073
                                $id_list[] = $this->cObj->getTreeList($rootId,9999,0,0,'','').$rootId;
1074
                        }
1075
                        $page_where = 'ISEC.page_id IN ('.implode(',',$id_list).')';
1076
                } else {        // Disable everything... (select all)
1077
                        $page_where = ' 1=1 ';
1078
                }
1079

    
1080

    
1081
                        // 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.
1082
                if (substr($this->piVars['order'],0,5)=='rank_')        {
1083
                                /*
1084
                                         OK there were some fancy calculations promoted by Graeme Merrall:
1085

1086
                                        "However, regarding relevance you probably want to look at something like
1087
                                        Salton's formula which is a good easy way to measure relevance.
1088
                                        Oracle Intermedia uses this and it's pretty simple:
1089
                                        Score can be between 0 and 100, but the top-scoring document in the query
1090
                                        will not necessarily have a score of 100 -- scoring is relative, not
1091
                                        absolute. This means that scores are not comparable across indexes, or even
1092
                                        across different queries on the same index. Score for each document is
1093
                                        computed using the standard Salton formula:
1094

1095
                                            3f(1+log(N/n))
1096

1097
                                        Where f is the frequency of the search term in the document, N is the total
1098
                                        number of rows in the table, and n is the number of rows which contain the
1099
                                        search term. This is converted into an integer in the range 0 - 100.
1100

1101
                                        There's a good doc on it at
1102
                                        http://ls6-www.informatik.uni-dortmund.de/bib/fulltext/ir/Pfeifer:97/
1103
                                        although it may be a little complex for what you require so just pick the
1104
                                        relevant parts out.
1105
                                        "
1106

1107
                                        However I chose not to go with this for several reasons.
1108
                                        I do not claim that my ways of calculating importance here is the best.
1109
                                        ANY (better) suggestion for ranking calculation is accepted! (as long as they are shipped with tested code in exchange for this.)
1110
                                */
1111

    
1112
                        switch($this->piVars['order'])        {
1113
                                case 'rank_flag':        // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1114
                                                                        // The ordering is refined with the frequency sum as well.
1115
                                        $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1116
                                        $orderBy = 'order_val1'.$this->isDescending().',order_val2'.$this->isDescending();
1117
                                break;
1118
                                case 'rank_first':        // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1119
                                        $grsel = 'AVG(IR.first) AS order_val';
1120
                                        $orderBy = 'order_val'.$this->isDescending(1);
1121
                                break;
1122
                                case 'rank_count':        // Number of words found
1123
                                        $grsel = 'SUM(IR.count) AS order_val';
1124
                                        $orderBy = 'order_val'.$this->isDescending();
1125
                                break;
1126
                                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?
1127
                                        $grsel = 'SUM(IR.freq) AS order_val';
1128
                                        $orderBy = 'order_val'.$this->isDescending();
1129
                                break;
1130
                        }
1131

    
1132
                                // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1133
                        $wordSel='('.implode(' OR ',$this->wSelClauses).') AND ';
1134

    
1135
                        return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1136
                                                'ISEC.*, IP.*, '
1137
                                                .$grsel,
1138
                                                'index_words IW,
1139
                                                        index_rel IR,
1140
                                                        index_section ISEC,
1141
                                                        index_phash IP'.
1142
                                                        $page_join,
1143
                                                $wordSel.'
1144
                                                        IP.phash IN ('.$list.') '.
1145
                                                        $this->mediaTypeWhere().' '.
1146
                                                        $this->languageWhere().
1147
                                                        $freeIndexUidClause.'
1148
                                                        AND IW.wid=IR.wid
1149
                                                        AND ISEC.phash = IR.phash
1150
                                                        AND IP.phash = IR.phash
1151
                                                        AND        '.$page_where,
1152
                                                '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',
1153
                                                $orderBy
1154
                                        );
1155
                } else {        // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1156

    
1157
                        $orderBy = '';
1158
                        switch((string)$this->piVars['order'])        {
1159
                                case 'title':
1160
                                        $orderBy = 'IP.item_title'.$this->isDescending();
1161
                                break;
1162
                                case 'crdate':
1163
                                        $orderBy = 'IP.item_crdate'.$this->isDescending();
1164
                                break;
1165
                                case 'mtime':
1166
                                        $orderBy = 'IP.item_mtime'.$this->isDescending();
1167
                                break;
1168
                        }
1169

    
1170
                        return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1171
                                                'ISEC.*, IP.*',
1172
                                                'index_phash IP,index_section ISEC'.$page_join,
1173
                                                'IP.phash IN ('.$list.') '.
1174
                                                        $this->mediaTypeWhere().' '.
1175
                                                        $this->languageWhere().
1176
                                                        $freeIndexUidClause.'
1177
                                                        AND IP.phash = ISEC.phash
1178
                                                        AND '.$page_where,
1179
                                                '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',
1180
                                                $orderBy
1181
                                        );
1182
                }
1183
        }
1184

    
1185
        /**
1186
         * Checking if the resume can be shown for the search result (depending on whether the rights are OK)
1187
         * ? Should it also check for gr_list "0,-1"?
1188
         *
1189
         * @param        array                Result row array.
1190
         * @return        boolean                Returns true if resume can safely be shown
1191
         */
1192
        function checkResume($row)        {
1193

    
1194
                        // If the record is indexed by an indexing configuration, just show it.
1195
                        // At least this is needed for external URLs and files.
1196
                        // For records we might need to extend this - for instance block display if record is access restricted.
1197
                if ($row['freeIndexUid'])        {
1198
                        return TRUE;
1199
                }
1200

    
1201
                        // Evaluate regularly indexed pages based on item_type:
1202
                if ($row['item_type'])        {        // External media:
1203
                                // For external media we will check the access of the parent page on which the media was linked from.
1204
                                // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1205
                                // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1206
                                // 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.
1207
                        $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'));
1208
                        if ($GLOBALS['TYPO3_DB']->sql_num_rows($res))        {
1209
                                #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' YES - ('.$GLOBALS['TSFE']->gr_list.")!",1);
1210
                                return TRUE;
1211
                        } else {
1212
                                #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' NO - ('.$GLOBALS['TSFE']->gr_list.")!",1);
1213
                                return FALSE;
1214
                        }
1215
                } else {        // Ordinary TYPO3 pages:
1216
                        if (strcmp($row['gr_list'],$GLOBALS['TSFE']->gr_list))        {
1217
                                        // 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...
1218
                                $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'));
1219
                                if ($GLOBALS['TYPO3_DB']->sql_num_rows($res))        {
1220
                                        #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash'].' - YES ('.$GLOBALS['TSFE']->gr_list.")",1);
1221
                                        return TRUE;
1222
                                } else {
1223
                                        #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash']." - NOPE",1);
1224
                                        return FALSE;
1225
                                }
1226
                        } else {
1227
                                        #debug('Resume can be shown, because the document was in fact indexed by this combination of groups!'.$GLOBALS['TSFE']->gr_list.' - '.$row['item_title'].'/'.$row['phash'],1);
1228
                                return TRUE;
1229
                        }
1230
                }
1231
        }
1232

    
1233
        /**
1234
         * Returns "DESC" or "" depending on the settings of the incoming highest/lowest result order (piVars['desc']
1235
         *
1236
         * @param        boolean                If true, inverse the order which is defined by piVars['desc']
1237
         * @return        string                " DESC" or ""
1238
         */
1239
        function isDescending($inverse=FALSE)        {
1240
                $desc = $this->piVars['desc'];
1241
                if ($inverse)        $desc=!$desc;
1242
                return !$desc ? ' DESC':'';
1243
        }
1244

    
1245
        /**
1246
         * Write statistics information to database for the search operation
1247
         *
1248
         * @param        array                Search Word array
1249
         * @param        integer                Number of hits
1250
         * @param        integer                Milliseconds the search took
1251
         * @return        void
1252
         */
1253
        function writeSearchStat($sWArr,$count,$pt)        {
1254
                $insertFields = array(
1255
                        'searchstring' => $this->piVars['sword'],
1256
                        'searchoptions' => serialize(array($this->piVars,$sWArr,$pt)),
1257
                        'feuser_id' => intval($this->fe_user->user['uid']),                        // fe_user id, integer
1258
                        'cookie' => $this->fe_user->id,                                                // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1259
                        'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'),                // Remote IP address
1260
                        'hits' => intval($count),                                                        // Number of hits on the search.
1261
                        'tstamp' => $GLOBALS['EXEC_TIME']                                        // Time stamp
1262
                );
1263

    
1264
                $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
1265
                $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
1266

    
1267
                if ($newId)        {
1268
                        foreach ($sWArr as $val)        {
1269
                                $insertFields = array(
1270
                                        'word' => $val['sword'],                // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $val['sword'], 'toLower'),
1271
                                        'index_stat_search_id' => $newId,
1272
                                        'tstamp' => $GLOBALS['EXEC_TIME'],                // Time stamp
1273
                                        'pageid' => $GLOBALS['TSFE']->id        //search page id for indexed search stats
1274
                                );
1275

    
1276
                                $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
1277
                        }
1278
                }
1279
        }
1280

    
1281

    
1282

    
1283

    
1284

    
1285

    
1286

    
1287

    
1288

    
1289

    
1290

    
1291

    
1292

    
1293
        /***********************************
1294
         *
1295
         * HTML output functions
1296
         *
1297
         ***********************************/
1298

    
1299
        /**
1300
         * Make search form HTML
1301
         *
1302
         * @param        array                Value/Labels pairs for search form selector boxes.
1303
         * @return        string                Search form HTML
1304
         */
1305
        function makeSearchForm($optValues)        {
1306
                $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
1307

    
1308
                        // Multilangual text
1309
                $substituteArray = array('searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style', 'freeIndexUid');
1310
                foreach ($substituteArray as $marker)        {
1311
                        $markerArray['###FORM_'.strtoupper($marker).'###'] = $this->pi_getLL('form_'.$marker,'',1);
1312
                }
1313

    
1314
                $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label','',1);
1315

    
1316
                        // Adding search field value
1317
                $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1318

    
1319
                        // Additonal keyword => "Add to current search words"
1320
                if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox'])        {
1321
                        $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1322
                        $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"':'';
1323
                        $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch','',1);
1324
                } else {
1325
                        $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1326
                }
1327

    
1328
                $markerArray['###ACTION_URL###'] = $this->pi_getPageLink($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sPre);
1329

    
1330
                $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
1331
                $hiddenFieldCode = preg_replace('/^\n\t(.+)/ms', '$1', $hiddenFieldCode);                // Remove first newline and tab (cosmetical issue)
1332
                $hiddenFieldArr = array();
1333

    
1334
                foreach (t3lib_div::trimExplode(',',$this->hiddenFieldList) as $fieldName)        {
1335
                        $hiddenFieldMarkerArray = array();
1336
                        $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = $this->prefixId.'['.$fieldName.']';
1337
                        $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string)$this->piVars[$fieldName]);
1338

    
1339
                        $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, array(), array());
1340
                }
1341

    
1342
                        // Extended search
1343
                if ($this->piVars['ext'])        {
1344

    
1345
                                // Search for
1346
                        if ((!is_array($optValues['type']) && !is_array($optValues['defOp'])) || ($this->conf['blind.']['type'] && $this->conf['blind.']['defOp']))        {
1347
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1348
                        } else {
1349
                                if (is_array($optValues['type']) && !$this->conf['blind.']['type'])        {
1350
                                        unset($hiddenFieldArr['type']);
1351
                                        $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'],$optValues['type']);
1352
                                } else {
1353
                                        $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1354
                                }
1355

    
1356
                                if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp'])        {
1357
                                        $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'],$optValues['defOp']);
1358
                                } else {
1359
                                        $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1360
                                }
1361
                        }
1362

    
1363
                                // Search in
1364
                        if ((!is_array($optValues['media']) && !is_array($optValues['lang'])) || ($this->conf['blind.']['media'] && $this->conf['blind.']['lang']))        {
1365
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1366
                        } else {
1367
                                if (is_array($optValues['media']) && !$this->conf['blind.']['media'])        {
1368
                                        unset($hiddenFieldArr['media']);
1369
                                        $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'],$optValues['media']);
1370
                                } else {
1371
                                        $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1372
                                }
1373

    
1374
                                if (is_array($optValues['lang']) || !$this->conf['blind.']['lang'])        {
1375
                                        unset($hiddenFieldArr['lang']);
1376
                                        $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'],$optValues['lang']);
1377
                                } else {
1378
                                        $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1379
                                }
1380
                        }
1381

    
1382
                                // Sections
1383
                        if (!is_array($optValues['sections']) || $this->conf['blind.']['sections'])        {
1384
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1385
                        } else {
1386
                                $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'],$optValues['sections']);
1387
                        }
1388

    
1389
                                // Free Indexing Configurations:
1390
                        if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid'])        {
1391
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
1392
                        } else {
1393
                                $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'],$optValues['freeIndexUid']);
1394
                        }
1395

    
1396
                                // Sorting
1397
                        if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order'])        {
1398
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1399
                        } else {
1400
                                unset($hiddenFieldArr['order']);
1401
                                unset($hiddenFieldArr['desc']);
1402
                                unset($hiddenFieldArr['results']);
1403
                                $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'],$optValues['order']);
1404
                                $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'],$optValues['desc']);
1405
                                $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
1406
                        }
1407

    
1408
                                // Limits
1409
                        if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results'])        {
1410
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1411
                        } else {
1412
                                $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
1413
                        }
1414

    
1415
                                // Grouping
1416
                        if (!is_array($optValues['group']) || $this->conf['blind.']['group'])        {
1417
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1418
                        } else {
1419
                                unset($hiddenFieldArr['group']);
1420
                                $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'],$optValues['group']);
1421
                        }
1422

    
1423
                        if ($this->conf['blind.']['extResume'])        {
1424
                                $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1425
                        } else {
1426
                                $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
1427
                        }
1428

    
1429
                } else {        // Extended search
1430
                        $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1431
                }
1432

    
1433
                if($this->conf['show.']['advancedSearchLink'])        {
1434
                        $linkToOtherMode = ($this->piVars['ext'] ?
1435
                                $this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>0)) :
1436
                                $this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>1))
1437
                        );
1438

    
1439
                        $markerArray['###LINKTOOTHERMODE###'] = '<a href="'.htmlspecialchars($linkToOtherMode).'">'.$this->pi_getLL($this->piVars['ext']?'link_regularSearch':'link_advancedSearch', '', 1).'</a>';
1440
                } else {
1441
                        $markerArray['###LINKTOOTHERMODE###'] = '';
1442
                }
1443

    
1444
                        // Write all hidden fields
1445
                $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('',$hiddenFieldArr));
1446

    
1447
                $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1448

    
1449
                return $substitutedContent;
1450
        }
1451

    
1452
        /**
1453
         * Function, rendering selector box values.
1454
         *
1455
         * @param        string                Current value
1456
         * @param        array                Array with the options as key=>value pairs
1457
         * @return        string                <options> imploded.
1458
         */
1459
        function renderSelectBoxValues($value,$optValues)        {
1460
                if (is_array($optValues))        {
1461
                        $opt=array();
1462
                        $isSelFlag=0;
1463
                        foreach ($optValues as $k=>$v)        {
1464
                                $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
1465
                                if ($sel)        { $isSelFlag++; }
1466
                                $opt[]='<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1467
                        }
1468

    
1469
                        return implode('',$opt);
1470
                }
1471
        }
1472

    
1473
        /**
1474
         * Print the searching rules
1475
         *
1476
         * @return        string                Rules for the search
1477
         */
1478
        function printRules()        {
1479
                if ($this->conf['show.']['rules'])        {
1480

    
1481
                        $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1482

    
1483
                        $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header','',1);
1484
                        $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text','',1)));
1485

    
1486
                        $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1487

    
1488
                        return '<div'.$this->pi_classParam('rules').'>'.$this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']).'</div>';
1489
                }
1490
        }
1491

    
1492
        /**
1493
         * Returns the anchor-links to the sections inside the displayed result rows.
1494
         *
1495
         * @return        string
1496
         */
1497
        function printResultSectionLinks()        {
1498
                if (count($this->resultSections))        {
1499
                        $lines = array();
1500

    
1501
                        $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1502
                        $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1503

    
1504
                        foreach ($this->resultSections as $id => $dat)        {
1505
                                $markerArray = array();
1506

    
1507
                                $aBegin = '<a href="'.htmlspecialchars($GLOBALS['TSFE']->anchorPrefix.'#anchor_'.md5($id)).'">';
1508
                                $aContent = htmlspecialchars(trim($dat[0]) ? trim($dat[0]) : $this->pi_getLL('unnamedSection')).
1509
                                                ' ('.$dat[1].' '.$this->pi_getLL($dat[1]>1 ? 'word_pages' : 'word_page','',1).')';
1510
                                $aEnd = '</a>';
1511

    
1512
                                $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1513

    
1514
                                $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
1515
                        }
1516

    
1517
                        $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode('',$links)), array(), array());
1518

    
1519
                        return '<div'.$this->pi_classParam('sectionlinks').'>'.$this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']).'</div>';
1520
                }
1521
        }
1522

    
1523
        /**
1524
         * Returns the section header of the search result.
1525
         *
1526
         * @param        string                ID for the section (used for anchor link)
1527
         * @param        string                Section title with linked wrapped around
1528
         * @param        integer                Number of results in section
1529
         * @return        string                HTML output
1530
         */
1531
        function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)        {
1532

    
1533
                $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1534

    
1535
                $markerArray['###ANCHOR_URL###'] = 'anchor_'.md5($id);
1536
                $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1537
                $markerArray['###RESULT_COUNT###'] = $countResultRows;
1538
                $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page'.($countResultRows>1 ? 's' : ''));
1539

    
1540
                $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1541

    
1542
                return $substitutedContent;
1543
        }
1544

    
1545
        /**
1546
         * This prints a single result row, including a recursive call for subrows.
1547
         *
1548
         * @param        array                Search result row
1549
         * @param        integer                1=Display only header (for sub-rows!), 2=nothing at all
1550
         * @return        string                HTML code
1551
         */
1552
        function printResultRow($row, $headerOnly=0)        {
1553

    
1554
                        // Get template content:
1555
                $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1556

    
1557
                if ($hookObj = &$this->hookRequest('printResultRow'))        {
1558
                        return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1559
                } else {
1560

    
1561
                        $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1562

    
1563
                        if (!is_array($row['_sub']))        {
1564
                                $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1565
                        }
1566

    
1567
                        if (!$headerOnly)        {
1568
                                $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1569

    
1570
                        } elseif ($headerOnly==1) {
1571
                                $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1572
                        } elseif ($headerOnly==2) {
1573
                                $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1574
                                $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1575
                        }
1576

    
1577
                        if (is_array($tmplContent))        {
1578
                                foreach ($tmplContent AS $k => $v)        {
1579
                                        $markerArray['###'.strtoupper($k).'###'] = $v;
1580
                                }
1581
                        }
1582

    
1583
                                // Description text
1584
                        $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size','',1);
1585
                        $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created','',1);
1586
                        $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified','',1);
1587
                        $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path','',1);
1588

    
1589
                        $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1590

    
1591
                                // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1592
                        if (is_array($row['_sub']))        {
1593
                                if ($this->multiplePagesType($row['item_type']))        {
1594

    
1595
                                        $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching','',1), $html);
1596

    
1597
                                        foreach ($row['_sub'] as $subRow)        {
1598
                                                $html .= $this->printResultRow($subRow,1);
1599
                                        }
1600
                                } else {
1601

    
1602
                                        $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching','',1);
1603

    
1604
                                        $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell','',1), $html);
1605
                                }
1606
                        }
1607

    
1608
                        return $html;
1609
                }
1610
        }
1611

    
1612
        /**
1613
         * Returns a results browser
1614
         *
1615
         * @param        boolean                Show result count
1616
         * @param        string                String appended to "displaying results..." notice.
1617
         * @param        string                String appended after section "displaying results..."
1618
         * @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!
1619
         * @return        string                HTML output
1620
         */
1621
        function pi_list_browseresults($showResultCount=1,$addString='',$addPart='',$freeIndexUid=-1)        {
1622

    
1623
                        // Initializing variables:
1624
                $pointer=$this->piVars['pointer'];
1625
                $count=$this->internal['res_count'];
1626
                $results_at_a_time = t3lib_div::intInRange($this->internal['results_at_a_time'],1,1000);
1627
                $maxPages = t3lib_div::intInRange($this->internal['maxPages'],1,100);
1628
                $pageCount = ceil($count/$results_at_a_time);
1629
                $sTables = '';
1630

    
1631
                if ($pageCount > 1)        {        // only show the result browser if more than one page is needed
1632
                        $pointer=intval($pointer);
1633
                        $links=array();
1634

    
1635
                                // Make browse-table/links:
1636
                        if ($pointer>0)        {        // all pages after the 1st one
1637
                                $links[]='<li>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev','< Previous',1),$pointer-1,$freeIndexUid).'</li>';
1638
                        }
1639

    
1640
                        for($a=0;$a<$pageCount;$a++)        {
1641
                                $min = max(0, $pointer+1-ceil($maxPages/2));
1642
                                $max = $min+$maxPages;
1643
                                if($max>$pageCount)        {
1644
                                        $min = $min - ($max-$pageCount);
1645
                                }
1646

    
1647
                                if($a >= $min && $a < $max)        {
1648
                                        if($a==$pointer)        {
1649
                                                $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>';
1650
                                        } else {
1651
                                                $links[]='<li>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+1)),$a,$freeIndexUid).'</li>';
1652
                                        }
1653
                                }
1654
                        }
1655
                        if ($pointer+1 < $pageCount)        {
1656
                                $links[]='<li>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next','Next >',1),$pointer+1,$freeIndexUid).'</li>';
1657
                        }
1658
                }
1659

    
1660
                $pR1 = $pointer*$results_at_a_time+1;
1661
                $pR2 = $pointer*$results_at_a_time+$results_at_a_time;
1662
                if(is_array($links))        {
1663
                        $addPart .= '
1664
                <ul class="browsebox">
1665
                        '.implode('',$links).'
1666
                </ul>';
1667
                }
1668

    
1669
                $label = $this->pi_getLL('pi_list_browseresults_display','Displaying results ###TAG_BEGIN###%s to %s###TAG_END### out of ###TAG_BEGIN###%s###TAG_END###');
1670
                $label = str_replace('###TAG_BEGIN###','<strong>',$label);
1671
                $label = str_replace('###TAG_END###','</strong>',$label);
1672

    
1673
                $sTables = '<div'.$this->pi_classParam('browsebox').'>'.
1674
                        ($showResultCount ? '<p>'.sprintf(
1675
                                $label,
1676
                                $pR1,
1677
                                min(array($this->internal['res_count'],$pR2)),
1678
                                $this->internal['res_count']
1679
                                ).$addString.'</p>':''
1680
                        ).$addPart.'</div>';
1681

    
1682
                return $sTables;
1683
        }
1684

    
1685

    
1686

    
1687

    
1688

    
1689

    
1690

    
1691

    
1692

    
1693

    
1694

    
1695

    
1696
        /***********************************
1697
         *
1698
         * Support functions for HTML output (with a minimum of fixed markup)
1699
         *
1700
         ***********************************/
1701

    
1702
        /**
1703
         * Preparing template data for the result row output
1704
         *
1705
         * @param        array                Result row
1706
         * @param        boolean                If set, display only header of result (for sub-results)
1707
         * @return        array                Array with data to insert in result row template
1708
         */
1709
        function prepareResultRowTemplateData($row, $headerOnly)        {
1710

    
1711
                        // Initialize:
1712
                $specRowConf = $this->getSpecialConfigForRow($row);
1713
                $CSSsuffix = $specRowConf['CSSsuffix']?'-'.$specRowConf['CSSsuffix']:'';
1714

    
1715
                        // If external media, link to the media-file instead.
1716
                if ($row['item_type'])        {                // External media
1717
                        if ($row['show_resume'])        {        // Can link directly.
1718
                                $title = '<a href="'.htmlspecialchars($row['data_filename']).'">'.htmlspecialchars($this->makeTitle($row)).'</a>';
1719
                        } else {        // Suspicious, so linking to page instead...
1720
                                $copy_row = $row;
1721
                                unset($copy_row['cHashParams']);
1722
                                $title = $this->linkPage($row['page_id'],htmlspecialchars($this->makeTitle($row)),$copy_row);
1723
                        }
1724
                } else {        // Else the page:
1725

    
1726
                                // Prepare search words for markup in content:
1727
                        if ($this->conf['forwardSearchWordsInResultLink'])        {
1728
                                $markUpSwParams = array('no_cache' => 1);
1729
                                foreach ($this->sWArr as $d)        {
1730
                                        $markUpSwParams['sword_list'][] = $d['sword'];
1731
                                }
1732
                        } else {
1733
                                $markUpSwParams = array();
1734
                        }
1735
                        $title = $this->linkPage($row['data_page_id'],htmlspecialchars($this->makeTitle($row)),$row,$markUpSwParams);
1736
                }
1737

    
1738
                $tmplContent = array();
1739
                $tmplContent['title'] = $title;
1740
                $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'].': ' : '&nbsp;';
1741
                $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'],'',$specRowConf);
1742
                $tmplContent['rating'] = $this->makeRating($row);
1743
                $tmplContent['description'] = $this->makeDescription($row,$this->piVars['extResume'] && !$headerOnly?0:1);
1744
                $tmplContent = $this->makeInfo($row,$tmplContent);
1745
                $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1746
                $tmplContent['language'] = $this->makeLanguageIndication($row);
1747
                $tmplContent['CSSsuffix'] = $CSSsuffix;
1748

    
1749
                        // Post processing with hook.
1750
                if ($hookObj = &$this->hookRequest('prepareResultRowTemplateData_postProc'))        {
1751
                        $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1752
                }
1753

    
1754
                return $tmplContent;
1755
        }
1756

    
1757
        /**
1758
         * Returns a string that tells which search words are searched for.
1759
         *
1760
         * @param        array                Array of search words
1761
         * @return        string                HTML telling what is searched for.
1762
         */
1763
        function tellUsWhatIsSeachedFor($sWArr)        {
1764

    
1765
                        // Init:
1766
                $searchingFor = '';
1767
                $c=0;
1768

    
1769
                        // Traverse search words:
1770
                foreach ($sWArr as $k => $v)        {
1771
                        if ($c)        {
1772
                                switch($v['oper'])        {
1773
                                        case 'OR':
1774
                                                $searchingFor.= ' '.$this->pi_getLL('searchFor_or','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1775
                                        break;
1776
                                        case 'AND NOT':
1777
                                                $searchingFor.= ' '.$this->pi_getLL('searchFor_butNot','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1778
                                        break;
1779
                                        default:        // AND...
1780
                                                $searchingFor.= ' '.$this->pi_getLL('searchFor_and','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1781
                                        break;
1782
                                }
1783
                        } else {
1784
                                $searchingFor = $this->pi_getLL('searchFor','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1785
                        }
1786
                        $c++;
1787
                }
1788
                return $searchingFor;
1789
        }
1790

    
1791
        /**
1792
         * Wraps the search words in the search-word list display (from ->tellUsWhatIsSeachedFor())
1793
         *
1794
         * @param        string                search word to wrap (in local charset!)
1795
         * @return        string                Search word wrapped in <span> tag.
1796
         */
1797
        function wrapSW($str)        {
1798
                return '"<span'.$this->pi_classParam('sw').'>'.htmlspecialchars($str).'</span>"';
1799
        }
1800

    
1801
        /**
1802
         * Makes a selector box
1803
         *
1804
         * @param        string                Name of selector box
1805
         * @param        string                Current value
1806
         * @param        array                Array of options in the selector box (value => label pairs)
1807
         * @return        string                HTML of selector box
1808
         */
1809
        function renderSelectBox($name,$value,$optValues)        {
1810
                if (is_array($optValues))        {
1811
                        $opt = array();
1812
                        $isSelFlag = 0;
1813

    
1814
                        foreach ($optValues as $k => $v)        {
1815
                                $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
1816
                                if ($sel)        $isSelFlag++;
1817
                                $opt[] = '<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
1818
                        }
1819

    
1820
                        return '<select name="'.$name.'">'.implode('',$opt).'</select>';
1821
                }
1822
        }
1823

    
1824
        /**
1825
         * Used to make the link for the result-browser.
1826
         * Notice how the links must resubmit the form after setting the new pointer-value in a hidden formfield.
1827
         *
1828
         * @param        string                String to wrap in <a> tag
1829
         * @param        integer                Pointer value
1830
         * @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!
1831
         * @return        string                Input string wrapped in <a> tag with onclick event attribute set.
1832
         */
1833
        function makePointerSelector_link($str,$p,$freeIndexUid)        {
1834
                $onclick = 'document.'.$this->prefixForm.'[\''.$this->prefixId.'[pointer]\'].value=\''.$p.'\';'.
1835
                                        'document.'.$this->prefixForm.'[\''.$this->prefixId.'[_freeIndexUid]\'].value=\''.rawurlencode($freeIndexUid).'\';'.
1836
                                        'document.'.$this->prefixForm.'.submit();return false;';
1837
                return '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.$str.'</a>';
1838
        }
1839

    
1840
        /**
1841
         * Return icon for file extension
1842
         *
1843
         * @param        string                File extension / item type
1844
         * @param        string                Title attribute value in icon.
1845
         * @param        array                TypoScript configuration specifically for search result.
1846
         * @return        string                <img> tag for icon
1847
         */
1848
        function makeItemTypeIcon($it,$alt='',$specRowConf)        {
1849
                if (!isset($this->iconFileNameCache[$it]))        {
1850
                        $this->iconFileNameCache[$it] = '';
1851

    
1852
                                // If TypoScript is used to render the icon:
1853
                        if (is_array($this->conf['iconRendering.']))        {
1854
                                $this->cObj->setCurrentVal($it);
1855
                                $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'],$this->conf['iconRendering.']);
1856
                        } else { // ... otherwise, get flag from sys_language record:
1857

    
1858
                                        // Default creation / finding of icon:
1859
                                $icon = '';
1860
                                if ($it==='0')        {
1861
                                        if (is_array($specRowConf['pageIcon.']))        {
1862
                                                $this->iconFileNameCache[$it] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
1863
                                        } else {
1864
                                                $icon = 'EXT:indexed_search/pi/res/pages.gif';
1865
                                        }
1866
                                } elseif ($this->external_parsers[$it]) {
1867
                                        $icon = $this->external_parsers[$it]->getIcon($it);
1868
                                }
1869

    
1870
                                if ($icon)        {
1871
                                        $fullPath = t3lib_div::getFileAbsFileName($icon);
1872

    
1873
                                        if ($fullPath)        {
1874
                                                $info = @getimagesize($fullPath);
1875
                                                $iconPath = substr($fullPath,strlen(PATH_site));
1876
                                                $this->iconFileNameCache[$it] = is_array($info) ? '<img src="'.$iconPath.'" '.$info[3].' title="'.htmlspecialchars($alt).'" alt="" />' : '';
1877
                                        }
1878
                                }
1879
                        }
1880
                }
1881
                return $this->iconFileNameCache[$it];
1882
        }
1883

    
1884
        /**
1885
         * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
1886
         *
1887
         * @param        array                Result row array
1888
         * @return        string                String showing ranking value
1889
         */
1890
        function makeRating($row)        {
1891

    
1892
                switch((string)$this->piVars['order'])        {
1893
                        case 'rank_count':                // Number of occurencies on page
1894
                                return $row['order_val'].' '.$this->pi_getLL('maketitle_matches');
1895
                        break;
1896
                        case 'rank_first':        // Close to top of page
1897
                                return ceil(t3lib_div::intInRange(255-$row['order_val'],1,255)/255*100).'%';
1898
                        break;
1899
                        case 'rank_flag':        // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1900
                                if ($this->firstRow['order_val2'])        {
1901
                                        $base = $row['order_val1']*256; // (3 MSB bit, 224 is highest value of order_val1 currently)
1902
                                        $freqNumber = $row['order_val2']/$this->firstRow['order_val2']*pow(2,12);        // 15-3 MSB = 12
1903
                                        $total = t3lib_div::intInRange($base+$freqNumber,0,32767);
1904
                                        #debug($total);
1905
                                        return ceil(log($total)/log(32767)*100).'%';
1906
                                }
1907
                        break;
1908
                        case 'rank_freq':        // Based on frequency
1909
                                $max = 10000;
1910
                                $total = t3lib_div::intInRange($row['order_val'],0,$max);
1911
#                                debug($total);
1912
                                return ceil(log($total)/log($max)*100).'%';
1913
                        break;
1914
                        case 'crdate':        // Based on creation date
1915
                                return $this->cObj->calcAge(time()-$row['item_crdate'],0); // ,$conf['age']
1916
                        break;
1917
                        case 'mtime':        // Based on modification time
1918
                                return $this->cObj->calcAge(time()-$row['item_mtime'],0); // ,$conf['age']
1919
                        break;
1920
                        default:        // fx. title
1921
                                return '&nbsp;';
1922
                        break;
1923
                }
1924
        }
1925

    
1926
        /**
1927
         * Returns the resume for the search-result.
1928
         *
1929
         * @param        array                Search result row
1930
         * @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.
1931
         * @param        integer                String length
1932
         * @return        string                HTML string                ...
1933
         */
1934
        function makeDescription($row,$noMarkup=0,$lgd=180)        {
1935
                if ($row['show_resume'])        {
1936
                        if (!$noMarkup)        {
1937
                                $markedSW = '';
1938
                                $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash='.intval($row['phash']));
1939
                                if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))        {
1940
                                                // Cut HTTP references after some length
1941
                                        $content = preg_replace('/(http:\/\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
1942
                                        $markedSW = $this->markupSWpartsOfString($content);
1943
                                }
1944
                                $GLOBALS['TYPO3_DB']->sql_free_result($res);
1945
                        }
1946

    
1947
                        if (!trim($markedSW))        {
1948
                                $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_description'],$lgd);
1949
                                $outputStr = htmlspecialchars($outputStr);
1950
                        }
1951
                        $output = $this->utf8_to_currentCharset($outputStr ? $outputStr : $markedSW);
1952
                } else {
1953
                        $output = '<span class="noResume">'.$this->pi_getLL('res_noResume','',1).'</span>';
1954
                }
1955

    
1956
                return $output;
1957
        }
1958

    
1959
        /**
1960
         * Marks up the search words from $this->sWarr in the $str with a color.
1961
         *
1962
         * @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.
1963
         * @return        string                Processed content.
1964
         */
1965
        function markupSWpartsOfString($str)        {
1966

    
1967
                        // Init:
1968
                $str = str_replace('&nbsp;',' ',t3lib_parsehtml::bidir_htmlspecialchars($str,-1));
1969
                $str = preg_replace('/\s\s+/',' ',$str);
1970
                $swForReg = array();
1971

    
1972
                        // Prepare search words for regex:
1973
                foreach ($this->sWArr as $d)        {
1974
                        $swForReg[] = preg_quote($d['sword'],'/');
1975
                }
1976
                $regExString = '('.implode('|',$swForReg).')';
1977

    
1978
                        // Split and combine:
1979
                $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
1980
// debug($parts,$regExString);
1981
                        // Constants:
1982
                $summaryMax = 300;
1983
                $postPreLgd = 60;
1984
                $postPreLgd_offset = 5;
1985
                $divider = ' ... ';
1986

    
1987
                $occurencies = (count($parts)-1)/2;
1988
                if ($occurencies)        {
1989
                        $postPreLgd = t3lib_div::intInRange($summaryMax/$occurencies,$postPreLgd,$summaryMax/2);
1990
                }
1991

    
1992
                        // Variable:
1993
                $summaryLgd = 0;
1994
                $output = array();
1995

    
1996
                        // Shorten in-between strings:
1997
                foreach ($parts as $k => $strP)        {
1998
                        if (($k%2)==0)        {
1999

    
2000
                                        // Find length of the summary part:
2001
                                $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
2002
                                $output[$k] = $parts[$k];
2003

    
2004
                                        // Possibly shorten string:
2005
                                if (!$k)        {        // First entry at all (only cropped on the frontside)
2006
                                        if ($strLen > $postPreLgd)        {
2007
                                                $output[$k] = $divider.ereg_replace('^[^[:space:]]+[[:space:]]','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
2008
                                        }
2009
                                } elseif ($summaryLgd > $summaryMax || !isset($parts[$k+1])) {        // In case summary length is exceed OR if there are no more entries at all:
2010
                                        if ($strLen > $postPreLgd)        {
2011
                                                $output[$k] = ereg_replace('[[:space:]][^[:space:]]+$','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).$divider;
2012
                                        }
2013
                                } else {        // In-between search words:
2014
                                        if ($strLen > $postPreLgd*2)        {
2015
                                                $output[$k] = ereg_replace('[[:space:]][^[:space:]]+$','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).
2016
                                                                                $divider.
2017
                                                                                ereg_replace('^[^[:space:]]+[[:space:]]','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
2018
                                        }
2019
                                }
2020
                                $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);;
2021

    
2022
                                        // Protect output:
2023
                                $output[$k] = htmlspecialchars($output[$k]);
2024

    
2025
                                        // If summary lgd is exceed, break the process:
2026
                                if ($summaryLgd > $summaryMax)        {
2027
                                        break;
2028
                                }
2029
                        } else {
2030
                                $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8',$strP);
2031
                                $output[$k] = '<strong class="tx-indexedsearch-redMarkup">'.htmlspecialchars($parts[$k]).'</strong>';
2032
                        }
2033
                }
2034

    
2035
                        // Return result:
2036
                return implode('',$output);
2037
        }
2038

    
2039
        /**
2040
         * Returns the title of the search result row
2041
         *
2042
         * @param        array                Result row
2043
         * @return        string                Title from row
2044
         */
2045
        function makeTitle($row)        {
2046
                $add = '';
2047

    
2048
                if ($this->multiplePagesType($row['item_type']))        {
2049
                        $dat = unserialize($row['cHashParams']);
2050

    
2051
                        $pp = explode('-',$dat['key']);
2052
                        if ($pp[0]!=$pp[1])        {
2053
                                $add=', '.$this->pi_getLL('word_pages').' '.$dat['key'];
2054
                        } else $add=', '.$this->pi_getLL('word_page').' '.$pp[0];
2055
                }
2056

    
2057
                $outputString = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_title'],50,'...');
2058

    
2059
                return $this->utf8_to_currentCharset($outputString).$add;
2060
        }
2061

    
2062
        /**
2063
         * Returns the info-string in the bottom of the result-row display (size, dates, path)
2064
         *
2065
         * @param        array                Result row
2066
         * @param        array                Template array to modify
2067
         * @return        array                Modified template array
2068
         */
2069
        function makeInfo($row,$tmplArray)        {
2070
                $tmplArray['size'] = t3lib_div::formatSize($row['item_size']);
2071
                $tmplArray['created'] = date('d-m-y',$row['item_crdate']);
2072
                $tmplArray['modified'] = date('d-m-y H:i',$row['item_mtime']);
2073

    
2074
                $pathId = $row['data_page_id']?$row['data_page_id']:$row['page_id'];
2075
                $pathMP = $row['data_page_id']?$row['data_page_mp']:'';
2076

    
2077
                $pI = parse_url($row['data_filename']);
2078
                if ($pI['scheme'])        {
2079
                        $tmplArray['path'] = '<a href="'.htmlspecialchars($row['data_filename']).'">'.htmlspecialchars($row['data_filename']).'</a>';
2080
                } else {
2081
                        $pathStr = htmlspecialchars($this->getPathFromPageId($pathId,$pathMP));
2082
                        $tmplArray['path'] = $this->linkPage($pathId,$pathStr,array(
2083
                                'data_page_type' => $row['data_page_type'],
2084
                                'data_page_mp' => $pathMP,
2085
                                'sys_language_uid' => $row['sys_language_uid'],
2086
                        ));
2087
                }
2088

    
2089
                return $tmplArray;
2090
        }
2091

    
2092
        /**
2093
         * Returns configuration from TypoScript for result row based on ID / location in page tree!
2094
         *
2095
         * @param        array                Result row
2096
         * @return        array                Configuration array
2097
         */
2098
        function getSpecialConfigForRow($row)        {
2099
                $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
2100
                $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2101

    
2102
                $rl = $this->getRootLine($pathId,$pathMP);
2103
                $specConf = $this->conf['specConfs.']['0.'];
2104
                if (is_array($rl))        {
2105
                        foreach ($rl as $dat)        {
2106
                                if (is_array($this->conf['specConfs.'][$dat['uid'].'.']))        {
2107
                                        $specConf = $this->conf['specConfs.'][$dat['uid'].'.'];
2108
                                        break;
2109
                                }
2110
                        }
2111
                }
2112

    
2113
                return $specConf;
2114
        }
2115

    
2116
        /**
2117
         * Returns the HTML code for language indication.
2118
         *
2119
         * @param        array                Result row
2120
         * @return        string                HTML code for result row.
2121
         */
2122
        function makeLanguageIndication($row)        {
2123

    
2124
                        // If search result is a TYPO3 page:
2125
                if ((string)$row['item_type']==='0')        {
2126

    
2127
                                // If TypoScript is used to render the flag:
2128
                        if (is_array($this->conf['flagRendering.']))        {
2129
                                $this->cObj->setCurrentVal($row['sys_language_uid']);
2130
                                return $this->cObj->cObjGetSingle($this->conf['flagRendering'],$this->conf['flagRendering.']);
2131
                        } else { // ... otherwise, get flag from sys_language record:
2132

    
2133
                                        // Get sys_language record
2134
                                $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_language', 'uid='.intval($row['sys_language_uid']).' '.$this->cObj->enableFields('sys_language'));
2135

    
2136
                                        // Flag code:
2137
                                $flag = $rowDat[0]['flag'];
2138
                                if ($flag)        {
2139

    
2140
// FIXME not all flags from typo3/gfx/flags are available in media/flags/
2141
                                        $file = substr(PATH_tslib,strlen(PATH_site)).'media/flags/flag_'.$flag;
2142
                                        $imgInfo = @getimagesize(PATH_site.$file);
2143

    
2144
// original
2145
#                                        $file = TYPO3_mainDir.'gfx/flags/'.$flag;
2146
#                                        $imgInfo = @getimagesize(PATH_site.$file);
2147

    
2148
                                        if (is_array($imgInfo))        {
2149
                                                $output = '<img src="'.$file.'" '.$imgInfo[3].' title="'.htmlspecialchars($rowDat[0]['title']).'" alt="'.htmlspecialchars($rowDat[0]['title']).'" />';
2150
                                                return $output;
2151
                                        }
2152
                                }
2153
                        }
2154
                }
2155
                return '&nbsp;';
2156
        }
2157

    
2158
        /**
2159
         * Returns the HTML code for the locking symbol.
2160
         * NOTICE: Requires a call to ->getPathFromPageId() first in order to work (done in ->makeInfo() by calling that first)
2161
         *
2162
         * @param        integer                Page id for which to find answer
2163
         * @return        string                <img> tag if access is limited.
2164
         */
2165
        function makeAccessIndication($id)        {
2166
                if (is_array($this->fe_groups_required[$id]) && count($this->fe_groups_required[$id]))        {
2167
                        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="" />';
2168
                }
2169
        }
2170

    
2171
        /**
2172
         * Links the $str to page $id
2173
         *
2174
         * @param        integer                Page id
2175
         * @param        string                Title String to link
2176
         * @param        array                Result row
2177
         * @param        array                Additional parameters for marking up seach words
2178
         * @return        string                <A> tag wrapped title string.
2179
         */
2180
        function linkPage($id,$str,$row=array(),$markUpSwParams=array())        {
2181

    
2182
                        // Parameters for link:
2183
                $urlParameters = (array)unserialize($row['cHashParams']);
2184

    
2185
                        // Add &type and &MP variable:
2186
                if ($row['data_page_type']) $urlParameters['type'] = $row['data_page_type'];
2187
                if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
2188
                if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
2189

    
2190
                        // markup-GET vars:
2191
                $urlParameters = array_merge($urlParameters, $markUpSwParams);
2192

    
2193
                        // 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...
2194
                if (!is_array($this->domain_records[$id]))        {
2195
                        $this->getPathFromPageId($id);
2196
                }
2197

    
2198
                        // If external domain, then link to that:
2199
                if (count($this->domain_records[$id]))        {
2200
                        reset($this->domain_records[$id]);
2201
                        $firstDom = current($this->domain_records[$id]);
2202
                        $scheme = t3lib_div::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2203

    
2204
                        $addParams = '';
2205
                        if (is_array($urlParameters))        {
2206
                                if (count($urlParameters))        {
2207
                                        $addParams.= t3lib_div::implodeArrayForUrl('',$urlParameters);
2208
                                }
2209
                        }
2210

    
2211
                        if ($target=$this->conf['search.']['detect_sys_domain_records.']['target'])        {
2212
                                $target = ' target="'.$target.'"';
2213
                        }
2214
                        return '<a href="'.htmlspecialchars($scheme.$firstDom.'/index.php?id='.$id.$addParams).'"'.$target.'>'.htmlspecialchars($str).'</a>';
2215
                } else {
2216
                        return $this->pi_linkToPage($str,$id,$this->conf['result_link_target'],$urlParameters);
2217
                }
2218
        }
2219

    
2220
        /**
2221
         * Returns the path to the page $id
2222
         *
2223
         * @param        integer                Page ID
2224
         * @param        string                MP variable content.
2225
         * @return        string                Root line for result.
2226
         */
2227
        function getRootLine($id,$pathMP='')        {
2228
                $identStr = $id.'|'.$pathMP;
2229

    
2230
                if (!isset($this->cache_path[$identStr]))        {
2231
                        $this->cache_rl[$identStr] = $GLOBALS['TSFE']->sys_page->getRootLine($id,$pathMP);
2232
                }
2233
                return $this->cache_rl[$identStr];
2234
        }
2235

    
2236
        /**
2237
         * Gets the first sys_domain record for the page, $id
2238
         *
2239
         * @param        integer                Page id
2240
         * @return        string                Domain name
2241
         */
2242
        function getFirstSysDomainRecordForPage($id)        {
2243
                $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', 'pid='.intval($id).$this->cObj->enableFields('sys_domain'), '', 'sorting');
2244
                $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
2245
                return ereg_replace('\/$','',$row['domainName']);
2246
        }
2247

    
2248
        /**
2249
         * Returns the path to the page $id
2250
         *
2251
         * @param        integer                Page ID
2252
         * @param        string                MP variable content
2253
         * @return        string                Path
2254
         */
2255
        function getPathFromPageId($id,$pathMP='')        {
2256

    
2257
                $identStr = $id.'|'.$pathMP;
2258

    
2259
                if (!isset($this->cache_path[$identStr]))        {
2260
                        $this->fe_groups_required[$id] = array();
2261
                        $this->domain_records[$id] = array();
2262
                        $rl = $this->getRootLine($id,$pathMP);
2263
                        $hitRoot = 0;
2264
                        $path = '';
2265
                        if (is_array($rl) && count($rl))        {
2266
                                reset($rl);
2267
                                while(list($k,$v)=each($rl))        {
2268
                                                // Check fe_user
2269
                                        if ($v['fe_group'] && ($v['uid']==$id || $v['extendToSubpages']))        {
2270
                                                $this->fe_groups_required[$id][]=$v['fe_group'];
2271
                                        }
2272
                                                // Check sys_domain.
2273
                                        if ($this->conf['search.']['detect_sys_domain_records'])        {
2274
                                                $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2275
                                                if ($sysDName)        {
2276
                                                        $this->domain_records[$id][] = $sysDName;
2277
                                                                // Set path accordingly:
2278
                                                        $path = $sysDName.$path;
2279
                                                        break;
2280
                                                }
2281
                                        }
2282

    
2283
                                                // Stop, if we find that the current id is the current root page.
2284
                                        if ($v['uid']==$GLOBALS['TSFE']->config['rootLine'][0]['uid'])                {
2285
                                                break;
2286
                                        }
2287
                                        $path = '/'.$v['title'].$path;
2288
                                }
2289
                        }
2290

    
2291
                        $this->cache_path[$identStr] = $path;
2292

    
2293
                        if (is_array($this->conf['path_stdWrap.']))        {
2294
                                $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2295
                        }
2296
                }
2297

    
2298
                return $this->cache_path[$identStr];
2299
        }
2300

    
2301
        /**
2302
         * Return the menu of pages used for the selector.
2303
         *
2304
         * @param        integer                Page ID for which to return menu
2305
         * @return        array                Menu items (for making the section selector box)
2306
         */
2307
        function getMenu($id)        {
2308
                if ($this->conf['show.']['LxALLtypes'])        {
2309
                        $output = Array();
2310
                        $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', 'pid='.intval($id).$this->cObj->enableFields('pages'), '', 'sorting');
2311
                        while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))        {
2312
                                $output[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
2313
                        }
2314
                        return $output;
2315
                } else {
2316
                        return $GLOBALS['TSFE']->sys_page->getMenu($id);
2317
                }
2318
        }
2319

    
2320
        /**
2321
         * Returns if an item type is a multipage item type
2322
         *
2323
         * @param        string                Item type
2324
         * @return        boolean                True if multipage capable
2325
         */
2326
        function multiplePagesType($item_type)        {
2327
                return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2328
        }
2329

    
2330
        /**
2331
         * Converts the input string from utf-8 to the backend charset.
2332
         *
2333
         * @param        string                String to convert (utf-8)
2334
         * @return        string                Converted string (backend charset if different from utf-8)
2335
         */
2336
        function utf8_to_currentCharset($str)        {
2337
                return $GLOBALS['TSFE']->csConv($str,'utf-8');
2338
        }
2339

    
2340
        /**
2341
         * Returns an object reference to the hook object if any
2342
         *
2343
         * @param        string                Name of the function you want to call / hook key
2344
         * @return        object                Hook object, if any. Otherwise null.
2345
         */
2346
        function &hookRequest($functionName)        {
2347
                global $TYPO3_CONF_VARS;
2348

    
2349
                        // Hook: menuConfig_preProcessModMenu
2350
                if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2351
                        $hookObj = &t3lib_div::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2352
                        if (method_exists ($hookObj, $functionName)) {
2353
                                $hookObj->pObj = &$this;
2354
                                return $hookObj;
2355
                        }
2356
                }
2357
        }
2358
}
2359

    
2360

    
2361
if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php'])        {
2362
        include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']);
2363
}
2364
?>