TYPO3 API  SVNRelease
class.tx_indexedsearch.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2001-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *  A copy is found in the textfile GPL.txt and important notices to the license
00017 *  from the author is found in LICENSE.txt distributed with these scripts.
00018 *
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 /**
00028  * Index search frontend
00029  *
00030  * $Id: class.tx_indexedsearch.php 10120 2011-01-18 20:03:36Z ohader $
00031  *
00032  * Creates a searchform for indexed search. Indexing must be enabled
00033  * for this to make sense.
00034  *
00035  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00036  * @co-author   Christian Jul Jensen <christian@typo3.com>
00037  */
00038 /**
00039  * [CLASS/FUNCTION INDEX of SCRIPT]
00040  *
00041  *
00042  *
00043  *  123: class tx_indexedsearch extends tslib_pibase
00044  *  168:     function main($content, $conf)
00045  *  200:     function initialize()
00046  *  413:     function getSearchWords($defOp)
00047  *  447:     function procSearchWordsByLexer($SWArr)
00048  *
00049  *              SECTION: Main functions
00050  *  491:     function doSearch($sWArr)
00051  *  549:     function getResultRows($sWArr,$freeIndexUid=-1)
00052  *  623:     function getResultRows_SQLpointer($sWArr,$freeIndexUid=-1)
00053  *  647:     function getDisplayResults($sWArr, $resData, $freeIndexUid=-1)
00054  *  699:     function compileResult($resultRows, $freeIndexUid=-1)
00055  *
00056  *              SECTION: Searching functions (SQL)
00057  *  800:     function getPhashList($sWArr)
00058  *  901:     function execPHashListQuery($wordSel,$plusQ='')
00059  *  921:     function sectionTableWhere()
00060  *  968:     function mediaTypeWhere()
00061  *  993:     function languageWhere()
00062  * 1005:     function freeIndexUidWhere($freeIndexUid)
00063  * 1046:     function execFinalQuery($list,$freeIndexUid=-1)
00064  * 1189:     function checkResume($row)
00065  * 1236:     function isDescending($inverse=FALSE)
00066  * 1250:     function writeSearchStat($sWArr,$count,$pt)
00067  *
00068  *              SECTION: HTML output functions
00069  * 1302:     function makeSearchForm($optValues)
00070  * 1436:     function renderSelectBoxValues($value,$optValues)
00071  * 1455:     function printRules()
00072  * 1474:     function printResultSectionLinks()
00073  * 1508:     function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
00074  * 1529:     function printResultRow($row, $headerOnly=0)
00075  * 1598:     function pi_list_browseresults($showResultCount=1,$addString='',$addPart='',$freeIndexUid=-1)
00076  *
00077  *              SECTION: Support functions for HTML output (with a minimum of fixed markup)
00078  * 1686:     function prepareResultRowTemplateData($row, $headerOnly)
00079  * 1740:     function tellUsWhatIsSeachedFor($sWArr)
00080  * 1774:     function wrapSW($str)
00081  * 1786:     function renderSelectBox($name,$value,$optValues)
00082  * 1810:     function makePointerSelector_link($str,$p,$freeIndexUid)
00083  * 1825:     function makeItemTypeIcon($it,$alt='',$specRowConf)
00084  * 1867:     function makeRating($row)
00085  * 1911:     function makeDescription($row,$noMarkup=0,$lgd=180)
00086  * 1942:     function markupSWpartsOfString($str)
00087  * 2022:     function makeTitle($row)
00088  * 2046:     function makeInfo($row,$tmplArray)
00089  * 2075:     function getSpecialConfigForRow($row)
00090  * 2099:     function makeLanguageIndication($row)
00091  * 2142:     function makeAccessIndication($id)
00092  * 2157:     function linkPage($id,$str,$row=array(),$markUpSwParams=array())
00093  * 2201:     function getRootLine($id,$pathMP='')
00094  * 2216:     function getFirstSysDomainRecordForPage($id)
00095  * 2229:     function getPathFromPageId($id,$pathMP='')
00096  * 2281:     function getMenu($id)
00097  * 2300:     function multiplePagesType($item_type)
00098  * 2310:     function utf8_to_currentCharset($str)
00099  * 2320:     function &hookRequest($functionName)
00100  *
00101  * TOTAL FUNCTIONS: 48
00102  * (This index is automatically created/updated by the extension "extdeveval")
00103  *
00104  */
00105 
00106 require_once(t3lib_extMgm::extPath('indexed_search').'class.indexer.php');
00107 
00108 
00109 /**
00110  * Index search frontend
00111  *
00112  * Creates a searchform for indexed search. Indexing must be enabled
00113  * for this to make sense.
00114  *
00115  * @package TYPO3
00116  * @subpackage tx_indexedsearch
00117  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00118  */
00119 class tx_indexedsearch extends tslib_pibase {
00120     var $prefixId = 'tx_indexedsearch';        // Same as class name
00121     var $scriptRelPath = 'pi/class.tx_indexedsearch.php';    // Path to this script relative to the extension dir.
00122     var $extKey = 'indexed_search';    // The extension key.
00123 
00124     var $join_pages = 0;    // See document for info about this flag...
00125     var $defaultResultNumber = 10;
00126 
00127     var $operator_translate_table = Array (     // case-sensitive. Defines the words, which will be operators between words
00128         Array ('+' , 'AND'),
00129         Array ('|' , 'OR'),
00130         Array ('-' , 'AND NOT'),
00131             // english
00132 #       Array ('AND' , 'AND'),
00133 #       Array ('OR' , 'OR'),
00134 #       Array ('NOT' , 'AND NOT'),
00135     );
00136 
00137         // Internal variable
00138     var $wholeSiteIdList = 0;       // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
00139 
00140         // Internals:
00141     var $sWArr = array();           // Search Words and operators
00142     var $optValues = array();       // Selector box values for search configuration form
00143     var $firstRow = Array();        // Will hold the first row in result - used to calculate relative hit-ratings.
00144 
00145     var $cache_path = array();      // Caching of page path
00146     var $cache_rl = array();        // Caching of root line data
00147     var $fe_groups_required = array();  // Required fe_groups memberships for display of a result.
00148     var $domain_records = array();      // Domain records (?)
00149     var $wSelClauses = array();     // Select clauses for individual words
00150     var $resultSections = array();      // Page tree sections for search result.
00151     var $external_parsers = array();    // External parser objects
00152     var $iconFileNameCache = array();   // Storage of icons....
00153 
00154     /**
00155      * Lexer object
00156      *
00157      * @var tx_indexedsearch_lexer
00158      */
00159     var $lexerObj;
00160 
00161     /**
00162      * Indexer object
00163      *
00164      * @var tx_indexedsearch_indexer
00165      */
00166     var $indexerObj;
00167     var $templateCode;          // Will hold the content of $conf['templateFile']
00168     var $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
00169 
00170 
00171     /**
00172      * Main function, called from TypoScript as a USER_INT object.
00173      *
00174      * @param   string      Content input, ignore (just put blank string)
00175      * @param   array       TypoScript configuration of the plugin!
00176      * @return  string      HTML code for the search form / result display.
00177      */
00178     function main($content, $conf)    {
00179 
00180             // Initialize:
00181         $this->conf = $conf;
00182         $this->pi_loadLL();
00183         $this->pi_setPiVarDefaults();
00184 
00185             // Initialize the indexer-class - just to use a few function (for making hashes)
00186         $this->indexerObj = t3lib_div::makeInstance('tx_indexedsearch_indexer');
00187 
00188             // Initialize:
00189         $this->initialize();
00190 
00191             // Do search:
00192             // If there were any search words entered...
00193         if (is_array($this->sWArr)) {
00194             $content = $this->doSearch($this->sWArr);
00195         }
00196 
00197             // Finally compile all the content, form, messages and results:
00198         $content = $this->makeSearchForm($this->optValues).
00199             $this->printRules().
00200             $content;
00201 
00202         return $this->pi_wrapInBaseClass($content);
00203     }
00204 
00205     /**
00206      * Initialize internal variables, especially selector box values for the search form and search words
00207      *
00208      * @return  void
00209      */
00210     function initialize()   {
00211         global $TYPO3_CONF_VARS;
00212 
00213             // Initialize external document parsers for icon display and other soft operations
00214         if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers']))    {
00215             foreach ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef)   {
00216                 $this->external_parsers[$extension] = t3lib_div::getUserObj($_objRef);
00217 
00218                     // Init parser and if it returns false, unset its entry again:
00219                 if (!$this->external_parsers[$extension]->softInit($extension)) {
00220                     unset($this->external_parsers[$extension]);
00221                 }
00222             }
00223         }
00224 
00225             // Init lexer (used to post-processing of search words)
00226         $lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ?
00227                         $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] :
00228                         'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
00229         $this->lexerObj = t3lib_div::getUserObj($lexerObjRef);
00230 
00231             // If "_sections" is set, this value overrides any existing value.
00232         if ($this->piVars['_sections'])     $this->piVars['sections'] = $this->piVars['_sections'];
00233 
00234             // If "_sections" is set, this value overrides any existing value.
00235         if ($this->piVars['_freeIndexUid']!=='_')       $this->piVars['freeIndexUid'] = $this->piVars['_freeIndexUid'];
00236 
00237             // Add previous search words to current
00238         if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev']) {
00239             $this->piVars['sword'] = trim($this->piVars['sword_prev']).' '.$this->piVars['sword'];
00240         }
00241 
00242         $this->piVars['results'] = t3lib_div::intInRange($this->piVars['results'],1,100000,$this->defaultResultNumber);
00243 
00244             // Selector-box values defined here:
00245         $this->optValues = Array(
00246             'type' => Array(
00247                 '0' => $this->pi_getLL('opt_type_0'),
00248                 '1' => $this->pi_getLL('opt_type_1'),
00249                 '2' => $this->pi_getLL('opt_type_2'),
00250                 '3' => $this->pi_getLL('opt_type_3'),
00251                 '10' => $this->pi_getLL('opt_type_10'),
00252                 '20' => $this->pi_getLL('opt_type_20'),
00253             ),
00254             'defOp' => Array(
00255                 '0' => $this->pi_getLL('opt_defOp_0'),
00256                 '1' => $this->pi_getLL('opt_defOp_1'),
00257             ),
00258             'sections' => Array(
00259                 '0' => $this->pi_getLL('opt_sections_0'),
00260                 '-1' => $this->pi_getLL('opt_sections_-1'),
00261                 '-2' => $this->pi_getLL('opt_sections_-2'),
00262                 '-3' => $this->pi_getLL('opt_sections_-3'),
00263                 // 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.
00264             ),
00265             'freeIndexUid' => Array(
00266                 '-1' => $this->pi_getLL('opt_freeIndexUid_-1'),
00267                 '-2' => $this->pi_getLL('opt_freeIndexUid_-2'),
00268                 '0' => $this->pi_getLL('opt_freeIndexUid_0'),
00269             ),
00270             'media' => Array(
00271                 '-1' => $this->pi_getLL('opt_media_-1'),
00272                 '0' => $this->pi_getLL('opt_media_0'),
00273                 '-2' => $this->pi_getLL('opt_media_-2'),
00274             ),
00275             'order' => Array(
00276                 'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
00277                 'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
00278                 'rank_first' => $this->pi_getLL('opt_order_rank_first'),
00279                 'rank_count' => $this->pi_getLL('opt_order_rank_count'),
00280                 'mtime' => $this->pi_getLL('opt_order_mtime'),
00281                 'title' => $this->pi_getLL('opt_order_title'),
00282                 'crdate' => $this->pi_getLL('opt_order_crdate'),
00283             ),
00284             'group' => Array (
00285                 'sections' => $this->pi_getLL('opt_group_sections'),
00286                 'flat' => $this->pi_getLL('opt_group_flat'),
00287             ),
00288             'lang' => Array (
00289                 -1 => $this->pi_getLL('opt_lang_-1'),
00290                 0 => $this->pi_getLL('opt_lang_0'),
00291             ),
00292             'desc' => Array (
00293                 '0' => $this->pi_getLL('opt_desc_0'),
00294                 '1' => $this->pi_getLL('opt_desc_1'),
00295             ),
00296             'results' => Array (
00297                 '10' => '10',
00298                 '20' => '20',
00299                 '50' => '50',
00300                 '100' => '100',
00301             )
00302         );
00303 
00304             // Free Index Uid:
00305         if ($this->conf['search.']['defaultFreeIndexUidList'])  {
00306             $uidList = t3lib_div::intExplode(',', $this->conf['search.']['defaultFreeIndexUidList']);
00307             $indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title','index_config','uid IN ('.implode(',',$uidList).')'.$this->cObj->enableFields('index_config'),'','','','uid');
00308 
00309             foreach ($uidList as $uidValue) {
00310                 if (is_array($indexCfgRecords[$uidValue]))  {
00311                     $this->optValues['freeIndexUid'][$uidValue] = $indexCfgRecords[$uidValue]['title'];
00312                 }
00313             }
00314         }
00315 
00316             // Should we use join_pages instead of long lists of uids?
00317         if ($this->conf['search.']['skipExtendToSubpagesChecking']) {
00318             $this->join_pages = 1;
00319         }
00320 
00321             // Add media to search in:
00322         if (strlen(trim($this->conf['search.']['mediaList'])))  {
00323             $mediaList = implode(',', t3lib_div::trimExplode(',', $this->conf['search.']['mediaList'], 1));
00324         }
00325         foreach ($this->external_parsers as $extension => $obj) {
00326                 // Skip unwanted extensions
00327             if ($mediaList && !t3lib_div::inList($mediaList, $extension))   { continue; }
00328 
00329             if ($name = $obj->searchTypeMediaTitle($extension)) {
00330                 $this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_'.$extension,$name);
00331             }
00332         }
00333 
00334             // Add operators for various languages
00335             // Converts the operators to UTF-8 and lowercase
00336         $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');
00337         $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');
00338         $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');
00339 
00340             // This is the id of the site root. This value may be a commalist of integer (prepared for this)
00341         $this->wholeSiteIdList = intval($GLOBALS['TSFE']->config['rootLine'][0]['uid']);
00342 
00343             // Creating levels for section menu:
00344             // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
00345         if ($this->conf['show.']['L1sections']) {
00346             $firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
00347             foreach ($firstLevelMenu as $kk => $mR) {
00348                     // @TODO: RFC #7370: doktype 2&5 are deprecated since TYPO3 4.2-beta1
00349                 if ($mR['doktype'] != t3lib_pageSelect::DOKTYPE_HIDE_IN_MENU && !$mR['nav_hide']) {
00350                     $this->optValues['sections']['rl1_'.$mR['uid']] = trim($this->pi_getLL('opt_RL1').' '.$mR['title']);
00351                     if ($this->conf['show.']['L2sections']) {
00352                         $secondLevelMenu = $this->getMenu($mR['uid']);
00353                         foreach ($secondLevelMenu as $kk2 => $mR2) {
00354                                 // @TODO: RFC #7370: doktype 2&5 are deprecated since TYPO3 4.2-beta1
00355                             if ($mR2['doktype'] != t3lib_pageSelect::DOKTYPE_HIDE_IN_MENU && !$mR2['nav_hide']) {
00356                                 $this->optValues['sections']['rl2_'.$mR2['uid']] = trim($this->pi_getLL('opt_RL2').' '.$mR2['title']);
00357                             } else unset($secondLevelMenu[$kk2]);
00358                         }
00359                         $this->optValues['sections']['rl2_'.implode(',',array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
00360                     }
00361                 } else unset($firstLevelMenu[$kk]);
00362             }
00363             $this->optValues['sections']['rl1_'.implode(',',array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
00364         }
00365 
00366             // 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.
00367             // 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...
00368         if ($this->conf['search.']['rootPidList'])  {
00369             $this->wholeSiteIdList = implode(',',t3lib_div::intExplode(',',$this->conf['search.']['rootPidList']));
00370         }
00371 
00372             // Load the template
00373         $this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
00374 
00375             // Add search languages:
00376         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1'.$this->cObj->enableFields('sys_language'));
00377         while($lR = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))    {
00378             $this->optValues['lang'][$lR['uid']] = $lR['title'];
00379         }
00380 
00381             // Calling hook for modification of initialized content
00382         if ($hookObj = $this->hookRequest('initialize_postProc')) {
00383             $hookObj->initialize_postProc();
00384         }
00385 
00386             // Default values set:
00387             // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
00388         foreach ($this->optValues as $kk => $vv)    {
00389             if (!isset($this->piVars[$kk])) {
00390                 reset($vv);
00391                 $this->piVars[$kk] = key($vv);
00392             }
00393         }
00394 
00395             // Blind selectors:
00396         if (is_array($this->conf['blind.']))    {
00397             foreach ($this->conf['blind.'] as $kk => $vv)   {
00398                 if (is_array($vv))  {
00399                     foreach ($vv as $kkk => $vvv)   {
00400                         if (!is_array($vvv) && $vvv && is_array($this->optValues[substr($kk,0,-1)]))    {
00401                             unset($this->optValues[substr($kk,0,-1)][$kkk]);
00402                         }
00403                     }
00404                 } elseif ($vv) {    // If value is not set, unset the option array.
00405                     unset($this->optValues[$kk]);
00406                 }
00407             }
00408         }
00409 
00410             // This gets the search-words into the $sWArr:
00411         $this->sWArr = $this->getSearchWords($this->piVars['defOp']);
00412     }
00413 
00414     /**
00415      * 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)
00416      *
00417      * Only words with 2 or more characters are accepted
00418      * Max 200 chars total
00419      * Space is used to split words, "" can be used search for a whole string (not indexed search then)
00420      * AND, OR and NOT are prefix words, overruling the default operator
00421      * +/|/- equals AND, OR and NOT as operators.
00422      * All search words are converted to lowercase.
00423      *
00424      * $defOp is the default operator. 1=OR, 0=AND
00425      *
00426      * @param   boolean     If true, the default operator will be OR, not AND
00427      * @return  array       Returns array with search words if any found
00428      */
00429     function getSearchWords($defOp) {
00430             // 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!)
00431         $inSW = substr($this->piVars['sword'],0,200);
00432 
00433             // Convert to UTF-8 + conv. entities (was also converted during indexing!)
00434         $inSW = $GLOBALS['TSFE']->csConvObj->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset);
00435         $inSW = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($inSW,TRUE);
00436 
00437         if ($hookObj = $this->hookRequest('getSearchWords')) {
00438             return $hookObj->getSearchWords_splitSWords($inSW, $defOp);
00439         } else {
00440 
00441             if ($this->piVars['type']==20)  {
00442                 return array(array('sword'=>trim($inSW), 'oper'=>'AND'));
00443             } else {
00444                 $search = t3lib_div::makeInstance('tslib_search');
00445                 $search->default_operator = $defOp==1 ? 'OR' : 'AND';
00446                 $search->operator_translate_table = $this->operator_translate_table;
00447                 $search->register_and_explode_search_string($inSW);
00448 
00449                 if (is_array($search->sword_array)) {
00450                     return $this->procSearchWordsByLexer($search->sword_array);
00451                 }
00452             }
00453         }
00454     }
00455 
00456     /**
00457      * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
00458      * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
00459      *
00460      * @param   array       Search word array
00461      * @return  array       Search word array, processed through lexer
00462      */
00463     function procSearchWordsByLexer($SWArr) {
00464 
00465             // Init output variable:
00466         $newSWArr = array();
00467 
00468             // Traverse the search word array:
00469         foreach ($SWArr as $wordDef)    {
00470             if (!strstr($wordDef['sword'],' ')) {   // No space in word (otherwise it might be a sentense in quotes like "there is").
00471                     // Split the search word by lexer:
00472                 $res = $this->lexerObj->split2Words($wordDef['sword']);
00473 
00474                     // Traverse lexer result and add all words again:
00475                 foreach ($res as $word) {
00476                     $newSWArr[] = array('sword'=>$word, 'oper'=>$wordDef['oper']);
00477                 }
00478             } else {
00479                 $newSWArr[] = $wordDef;
00480             }
00481         }
00482 
00483             // Return result:
00484         return $newSWArr;
00485     }
00486 
00487 
00488 
00489 
00490 
00491 
00492 
00493 
00494 
00495     /*****************************
00496      *
00497      * Main functions
00498      *
00499      *****************************/
00500 
00501     /**
00502      * Performs the search, the display and writing stats
00503      *
00504      * @param   array       Search words in array, see ->getSearchWords() for details
00505      * @return  string      HTML for result display.
00506      */
00507     function doSearch($sWArr)   {
00508 
00509             // Find free index uid:
00510         $freeIndexUid = $this->piVars['freeIndexUid'];
00511         if ($freeIndexUid==-2)  {
00512             $freeIndexUid = $this->conf['search.']['defaultFreeIndexUidList'];
00513         }
00514 
00515         $indexCfgs = t3lib_div::intExplode(',',$freeIndexUid);
00516         $accumulatedContent = '';
00517 
00518         foreach ($indexCfgs as $freeIndexUid)   {
00519                 // Get result rows:
00520             $pt1 = t3lib_div::milliseconds();
00521             if ($hookObj = $this->hookRequest('getResultRows')) {
00522                 $resData = $hookObj->getResultRows($sWArr,$freeIndexUid);
00523             } else {
00524                 $resData = $this->getResultRows($sWArr,$freeIndexUid);
00525             }
00526 
00527                 // Display search results:
00528             $pt2 = t3lib_div::milliseconds();
00529             if ($hookObj = $this->hookRequest('getDisplayResults')) {
00530                 $content = $hookObj->getDisplayResults($sWArr, $resData, $freeIndexUid);
00531             } else {
00532                 $content = $this->getDisplayResults($sWArr, $resData, $freeIndexUid);
00533             }
00534 
00535             $pt3 = t3lib_div::milliseconds();
00536 
00537                 // Create header if we are searching more than one indexing configuration:
00538             if (count($indexCfgs)>1)    {
00539                 if ($freeIndexUid>0)    {
00540                     $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('title', 'index_config', 'uid=' . intval($freeIndexUid) . $this->cObj->enableFields('index_config'));
00541                     $titleString = $indexCfgRec['title'];
00542                 } else {
00543                     $titleString = $this->pi_getLL('opt_freeIndexUid_header_'.$freeIndexUid);
00544                 }
00545                 $content = '<h1 class="tx-indexedsearch-category">'.htmlspecialchars($titleString).'</h1>'.$content;
00546             }
00547 
00548             $accumulatedContent.=$content;
00549         }
00550 
00551             // Write search statistics
00552         $this->writeSearchStat($sWArr,$resData['count'],array($pt1,$pt2,$pt3));
00553 
00554             // Return content:
00555         return $accumulatedContent;
00556     }
00557 
00558     /**
00559      * Get search result rows / data from database. Returned as data in array.
00560      *
00561      * @param   array       Search word array
00562      * @param   integer     Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
00563      * @return  array       False if no result, otherwise an array with keys for first row, result rows and total number of results found.
00564      */
00565     function getResultRows($sWArr,$freeIndexUid=-1) {
00566 
00567             // Getting SQL result pointer:
00568             $GLOBALS['TT']->push('Searching result');
00569         $res = $this->getResultRows_SQLpointer($sWArr,$freeIndexUid);
00570             $GLOBALS['TT']->pull();
00571 
00572             // Organize and process result:
00573         if ($res)   {
00574 
00575             $count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);  // Total search-result count
00576             $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
00577 
00578                 // Initialize result accumulation variables:
00579             $c = 0; // Result pointer: Counts up the position in the current search-result
00580             $grouping_phashes = array();    // Used to filter out duplicates.
00581             $grouping_chashes = array();    // Used to filter out duplicates BASED ON cHash.
00582             $firstRow = array();    // Will hold the first row in result - used to calculate relative hit-ratings.
00583             $resultRows = array();  // Will hold the results rows for display.
00584 
00585             $exactCount = $this->conf['search.']['exactCount']; // Continue counting and checking of results even if we are sure they are not displayed in this request. This will slow down your page rendering, but it allows precise search result counters.
00586 
00587                 // Now, traverse result and put the rows to be displayed into an array
00588                 // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
00589             while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))   {
00590 
00591                     // Set first row:
00592                 if (!$c)    {
00593                     $firstRow = $row;
00594                 }
00595 
00596                 $row['show_resume'] = $this->checkResume($row); // Tells whether we can link directly to a document or not (depends on possible right problems)
00597 
00598                 $phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
00599                 $chashGr = !in_array($row['contentHash'].'.'.$row['data_page_id'], $grouping_chashes);
00600                 if ($phashGr && $chashGr)   {
00601                     if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords'])    {   // Only if the resume may be shown are we going to filter out duplicates...
00602                         if (!$this->multiplePagesType($row['item_type']))   {   // Only on documents which are not multiple pages documents
00603                             $grouping_phashes[] = $row['phash_grouping'];
00604                         }
00605                         $grouping_chashes[] = $row['contentHash'].'.'.$row['data_page_id'];
00606 
00607                         $c++;   // Increase the result pointer
00608 
00609                             // All rows for display is put into resultRows[]
00610                         if ($c > $pointer * $this->piVars['results'] && $c <= ($pointer * $this->piVars['results'] + $this->piVars['results'])) {
00611                             $row['result_number'] = $c;
00612                             $resultRows[] = $row;
00613                                 // This may lead to a problem: If the result check is not stopped here, the search will take longer. However the result counter will not filter out grouped cHashes/pHashes that were not processed yet. You can change this behavior using the "search.exactCount" property (see above).
00614                             if (!$exactCount && (($c+1) > ($pointer+1)*$this->piVars['results']))   { break; }
00615                         }
00616                     } else {
00617                         $count--;   // Skip this row if the user cannot view it (missing permission)
00618                     }
00619                 } else {
00620                     $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.
00621                 }
00622             }
00623 
00624             return array(
00625                         'resultRows' => $resultRows,
00626                         'firstRow' => $firstRow,
00627                         'count' => $count
00628                     );
00629         } else {    // No results found:
00630             return FALSE;
00631         }
00632     }
00633 
00634     /**
00635      * Gets a SQL result pointer to traverse for the search records.
00636      *
00637      * @param   array       Search words
00638      * @param   integer     Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
00639      * @return  pointer
00640      */
00641     function getResultRows_SQLpointer($sWArr,$freeIndexUid=-1)  {
00642                 // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
00643         $list = $this->getPhashList($sWArr);
00644 
00645             // Perform SQL Search / collection of result rows array:
00646         if ($list)  {
00647                 // Do the search:
00648             $GLOBALS['TT']->push('execFinalQuery');
00649             $res = $this->execFinalQuery($list,$freeIndexUid);
00650             $GLOBALS['TT']->pull();
00651             return $res;
00652         } else {
00653             return FALSE;
00654         }
00655     }
00656 
00657     /**
00658      * Compiles the HTML display of the incoming array of result rows.
00659      *
00660      * @param   array       Search words array (for display of text describing what was searched for)
00661      * @param   array       Array with result rows, count, first row.
00662      * @param   integer     Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
00663      * @return  string      HTML content to display result.
00664      */
00665     function getDisplayResults($sWArr, $resData, $freeIndexUid=-1)  {
00666             // Perform display of result rows array:
00667         if ($resData)   {
00668             $GLOBALS['TT']->push('Display Final result');
00669 
00670                 // Set first selected row (for calculation of ranking later)
00671             $this->firstRow = $resData['firstRow'];
00672 
00673                 // Result display here:
00674             $rowcontent = '';
00675             $rowcontent.= $this->compileResult($resData['resultRows'], $freeIndexUid);
00676 
00677                 // Browsing box:
00678             if ($resData['count'])  {
00679                 $this->internal['res_count'] = $resData['count'];
00680                 $this->internal['results_at_a_time'] = $this->piVars['results'];
00681                 $this->internal['maxPages'] = t3lib_div::intInRange($this->conf['search.']['page_links'],1,100,10);
00682                 $addString = ($resData['count']&&$this->piVars['group']=='sections'&&$freeIndexUid<=0 ? ' '.sprintf($this->pi_getLL(count($this->resultSections)>1?'inNsections':'inNsection'),count($this->resultSections)):'');
00683                 $browseBox1 = $this->pi_list_browseresults(1,$addString,$this->printResultSectionLinks(),$freeIndexUid);
00684                 $browseBox2 = $this->pi_list_browseresults(0,'','',$freeIndexUid);
00685             }
00686 
00687                 // Browsing nav, bottom.
00688             if ($resData['count'])  {
00689                 $content = $browseBox1.$rowcontent.$browseBox2;
00690             } else {
00691                 $content = '<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
00692             }
00693 
00694             $GLOBALS['TT']->pull();
00695         } else {
00696             $content.='<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
00697         }
00698 
00699             // Print a message telling which words we searched for, and in which sections etc.
00700         $what = $this->tellUsWhatIsSeachedFor($sWArr).
00701                 (substr($this->piVars['sections'],0,2)=='rl'?' '.$this->pi_getLL('inSection','',1).' "'.substr($this->getPathFromPageId(substr($this->piVars['sections'],4)),1).'"':'');
00702         $what = '<div'.$this->pi_classParam('whatis').'>'.$this->cObj->stdWrap($what, $this->conf['whatis_stdWrap.']).'</div>';
00703         $content = $what.$content;
00704 
00705             // Return content:
00706         return $content;
00707     }
00708 
00709     /**
00710      * Takes the array with resultrows as input and returns the result-HTML-code
00711      * Takes the "group" var into account: Makes a "section" or "flat" display.
00712      *
00713      * @param   array       Result rows
00714      * @param   integer     Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
00715      * @return  string      HTML
00716      */
00717     function compileResult($resultRows, $freeIndexUid=-1)   {
00718         $content = '';
00719 
00720             // Transfer result rows to new variable, performing some mapping of sub-results etc.
00721         $newResultRows = array();
00722         foreach ($resultRows as $row)   {
00723             $id = md5($row['phash_grouping']);
00724             if (is_array($newResultRows[$id]))  {
00725                 if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) {   // swapping:
00726 
00727                         // Remove old
00728                     $subrows = $newResultRows[$id]['_sub'];
00729                     unset($newResultRows[$id]['_sub']);
00730                     $subrows[] = $newResultRows[$id];
00731 
00732                         // Insert new:
00733                     $newResultRows[$id] = $row;
00734                     $newResultRows[$id]['_sub'] = $subrows;
00735                 } else $newResultRows[$id]['_sub'][] = $row;
00736             } else {
00737                 $newResultRows[$id] = $row;
00738             }
00739         }
00740         $resultRows = $newResultRows;
00741         $this->resultSections = array();
00742 
00743         if ($freeIndexUid<=0)   {
00744             switch($this->piVars['group'])  {
00745                 case 'sections':
00746 
00747                     $rl2flag = substr($this->piVars['sections'],0,2)=='rl';
00748                     $sections = array();
00749                     foreach ($resultRows as $row)   {
00750                         $id = $row['rl0'].'-'.$row['rl1'].($rl2flag?'-'.$row['rl2']:'');
00751                         $sections[$id][] = $row;
00752                     }
00753 
00754                     $this->resultSections = array();
00755 
00756                     foreach ($sections as $id => $resultRows)   {
00757                         $rlParts = explode('-',$id);
00758                         $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1]?$rlParts[1]:$rlParts[0]);
00759                         $theRLid = $rlParts[2] ? 'rl2_'.$rlParts[2]:($rlParts[1]?'rl1_'.$rlParts[1]:'0');
00760 
00761                         $sectionName = $this->getPathFromPageId($theId);
00762                         if ($sectionName{0} == '/') $sectionName = substr($sectionName,1);
00763 
00764                         if (!trim($sectionName))    {
00765                             $sectionTitleLinked = $this->pi_getLL('unnamedSection','',1).':';
00766                         } else {
00767                             $onclick = 'document.'.$this->prefixId.'[\''.$this->prefixId.'[_sections]\'].value=\''.$theRLid.'\';document.'.$this->prefixId.'.submit();return false;';
00768                             $sectionTitleLinked = '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.htmlspecialchars($sectionName).':</a>';
00769                         }
00770                         $this->resultSections[$id] = array($sectionName,count($resultRows));
00771 
00772                             // Add content header:
00773                         $content.= $this->makeSectionHeader($id,$sectionTitleLinked,count($resultRows));
00774 
00775                             // Render result rows:
00776                         foreach ($resultRows as $row)   {
00777                             $content.= $this->printResultRow($row);
00778                         }
00779                     }
00780                 break;
00781                 default:    // flat:
00782                     foreach ($resultRows as $row)   {
00783                         $content.= $this->printResultRow($row);
00784                     }
00785                 break;
00786             }
00787         } else {
00788             foreach ($resultRows as $row)   {
00789                 $content.= $this->printResultRow($row);
00790             }
00791         }
00792 
00793         return '<div'.$this->pi_classParam('res').'>'.$content.'</div>';
00794     }
00795 
00796 
00797 
00798 
00799 
00800 
00801 
00802 
00803 
00804 
00805 
00806     /***********************************
00807      *
00808      *  Searching functions (SQL)
00809      *
00810      ***********************************/
00811 
00812     /**
00813      * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the sWArr array.
00814      * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
00815      *
00816      * @param   array       Search word array
00817      * @return  string      List of integers
00818      */
00819     function getPhashList($sWArr)   {
00820 
00821             // Initialize variables:
00822         $c=0;
00823         $totalHashList = array();   // This array accumulates the phash-values
00824         $this->wSelClauses = array();
00825 
00826             // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
00827         foreach ($sWArr as $k => $v)    {
00828                 // Making the query for a single search word based on the search-type
00829             $sWord = $v['sword'];   // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$v['sword'],'toLower');   // lower-case all of them...
00830             $theType = (string)$this->piVars['type'];
00831             if (strstr($sWord,' ')) $theType = 20;  // If there are spaces in the search-word, make a full text search instead.
00832 
00833             $GLOBALS['TT']->push('SearchWord "'.$sWord.'" - $theType='.$theType);
00834 
00835             $res = '';
00836             $wSel='';
00837 
00838                 // Perform search for word:
00839             switch($theType)    {
00840                 case '1':   // Part of word
00841                     $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
00842                     $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
00843                 break;
00844                 case '2':   // First part of word
00845                     $wSel = "IW.baseword LIKE '".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
00846                     $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
00847                 break;
00848                 case '3':   // Last part of word
00849                     $wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."'";
00850                     $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
00851                 break;
00852                 case '10':  // Sounds like
00853                     $wSel = 'IW.metaphone = '.$this->indexerObj->metaphone($sWord);
00854                     $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
00855                 break;
00856                 case '20':  // Sentence
00857                     $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00858                                 'ISEC.phash',
00859                                 'index_section ISEC, index_fulltext IFT',
00860                                 'IFT.fulltextdata LIKE \'%'.$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_fulltext').'%\' AND
00861                                     ISEC.phash = IFT.phash
00862                                     '.$this->sectionTableWhere(),
00863                                 'ISEC.phash'
00864                             );
00865                     $wSel = '1=1';
00866 
00867                     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.
00868                 break;
00869                 default:    // Distinct word
00870                     $wSel = 'IW.wid = '.$hash = $this->indexerObj->md5inthash($sWord);
00871                     $res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
00872                 break;
00873             }
00874 
00875                 // Accumulate the word-select clauses
00876             $this->wSelClauses[] = $wSel;
00877 
00878                 // If there was a query to do, then select all phash-integers which resulted from this.
00879             if ($res)   {
00880 
00881                     // Get phash list by searching for it:
00882                 $phashList = array();
00883                 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))   {
00884                     $phashList[] = $row['phash'];
00885                 }
00886                 $GLOBALS['TYPO3_DB']->sql_free_result($res);
00887 
00888                     // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
00889                 if ($c) {
00890                     switch($v['oper'])  {
00891                         case 'OR':
00892                             $totalHashList = array_unique(array_merge($phashList,$totalHashList));
00893                         break;
00894                         case 'AND NOT':
00895                             $totalHashList = array_diff($totalHashList,$phashList);
00896                         break;
00897                         default:    // AND...
00898                             $totalHashList = array_intersect($totalHashList,$phashList);
00899                         break;
00900                     }
00901                 } else {
00902                     $totalHashList = $phashList;    // First search
00903                 }
00904             }
00905 
00906             $GLOBALS['TT']->pull();
00907             $c++;
00908         }
00909 
00910         return implode(',',$totalHashList);
00911     }
00912 
00913     /**
00914      * Returns a query which selects the search-word from the word/rel tables.
00915      *
00916      * @param   string      WHERE clause selecting the word from phash
00917      * @param   string      Additional AND clause in the end of the query.
00918      * @return  pointer     SQL result pointer
00919      */
00920     function execPHashListQuery($wordSel,$plusQ='') {
00921         return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00922                     'IR.phash',
00923                     'index_words IW,
00924                         index_rel IR,
00925                         index_section ISEC',
00926                     $wordSel.'
00927                         AND IW.wid=IR.wid
00928                         AND ISEC.phash = IR.phash
00929                         '.$this->sectionTableWhere().'
00930                         '.$plusQ,
00931                     'IR.phash'
00932                 );
00933     }
00934 
00935     /**
00936      * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
00937      *
00938      * @return  string      AND clause for selection of section in database.
00939      */
00940     function sectionTableWhere()    {
00941         $out = $this->wholeSiteIdList<0 ? '' : 'AND ISEC.rl0 IN ('.$this->wholeSiteIdList.')';
00942 
00943         $match = '';
00944         if (substr($this->piVars['sections'],0,4)=='rl1_')  {
00945             $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
00946             $out.= 'AND ISEC.rl1 IN ('.$list.')';
00947             $match = TRUE;
00948         } elseif (substr($this->piVars['sections'],0,4)=='rl2_')    {
00949             $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
00950             $out.= 'AND ISEC.rl2 IN ('.$list.')';
00951             $match = TRUE;
00952         } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields']))  {
00953                 // Traversing user configured fields to see if any of those are used to limit search to a section:
00954             foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
00955                 if (substr($this->piVars['sections'],0,strlen($fieldName)+1)==$fieldName.'_')   {
00956                     $list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],strlen($fieldName)+1)));
00957                     $out.= 'AND ISEC.'.$fieldName.' IN ('.$list.')';
00958                     $match = TRUE;
00959                     break;
00960                 }
00961             }
00962         }
00963 
00964             // If no match above, test the static types:
00965         if (!$match)    {
00966             switch((string)$this->piVars['sections'])   {
00967                 case '-1':      // '-1' => 'Only this page',
00968                     $out.= ' AND ISEC.page_id='.$GLOBALS['TSFE']->id;
00969                 break;
00970                 case '-2':      // '-2' => 'Top + level 1',
00971                     $out.= ' AND ISEC.rl2=0';
00972                 break;
00973                 case '-3':      // '-3' => 'Level 2 and out',
00974                     $out.= ' AND ISEC.rl2>0';
00975                 break;
00976             }
00977         }
00978 
00979         return $out;
00980     }
00981 
00982     /**
00983      * Returns AND statement for selection of media type
00984      *
00985      * @return  string      AND statement for selection of media type
00986      */
00987     function mediaTypeWhere()   {
00988 
00989         switch((string)$this->piVars['media'])  {
00990             case '0':       // '0' => 'Kun TYPO3 sider',
00991                 $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
00992             break;
00993             case '-2':      // All external documents
00994                 $out = 'AND IP.item_type!='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
00995             break;
00996             case '-1':  // All content
00997                 $out='';
00998             break;
00999             default:
01000                 $out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['media'], 'index_phash');
01001             break;
01002         }
01003 
01004         return $out;
01005     }
01006 
01007     /**
01008      * Returns AND statement for selection of langauge
01009      *
01010      * @return  string      AND statement for selection of langauge
01011      */
01012     function languageWhere()    {
01013         if ($this->piVars['lang']>=0)   {   // -1 is the same as ALL language.
01014             return 'AND IP.sys_language_uid='.intval($this->piVars['lang']);
01015         }
01016     }
01017 
01018     /**
01019      * Where-clause for free index-uid value.
01020      *
01021      * @param   integer     Free Index UID value to limit search to.
01022      * @return  string      WHERE SQL clause part.
01023      */
01024     function freeIndexUidWhere($freeIndexUid)   {
01025 
01026         if ($freeIndexUid>=0)   {
01027 
01028                 // First, look if the freeIndexUid is a meta configuration:
01029             $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('indexcfgs', 'index_config', 'type=5 AND uid=' . intval($freeIndexUid) . $this->cObj->enableFields('index_config'));
01030             if (is_array($indexCfgRec)) {
01031                 $refs = t3lib_div::trimExplode(',',$indexCfgRec['indexcfgs']);
01032                 $list = array(-99); // Default value to protect against empty array.
01033                 foreach ($refs as $ref) {
01034                     list($table,$uid) = t3lib_div::revExplode('_',$ref,2);
01035                     switch ($table) {
01036                         case 'index_config':
01037                             $idxRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', 'index_config', 'uid=' . intval($uid) . $this->cObj->enableFields('index_config'));
01038                             if ($idxRec)    $list[] = $uid;
01039                         break;
01040                         case 'pages':
01041                             $indexCfgRecordsFromPid = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid','index_config','pid='.intval($uid).$this->cObj->enableFields('index_config'));
01042                             foreach ($indexCfgRecordsFromPid as $idxRec)    {
01043                                 $list[] = $idxRec['uid'];
01044                             }
01045                         break;
01046                     }
01047                 }
01048 
01049                 $list = array_unique($list);
01050             } else {
01051                 $list = array(intval($freeIndexUid));
01052             }
01053 
01054             return ' AND IP.freeIndexUid IN ('.implode(',',$list).')';
01055         }
01056     }
01057 
01058     /**
01059      * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
01060      *
01061      * @param   string      List of phash integers which match the search.
01062      * @param   integer     Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
01063      * @return  pointer     Query result pointer
01064      */
01065     function execFinalQuery($list,$freeIndexUid=-1) {
01066 
01067             // Setting up methods of filtering results based on page types, access, etc.
01068         $page_join = '';
01069         $page_where = '';
01070 
01071             // Indexing configuration clause:
01072         $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
01073 
01074             // Calling hook for alternative creation of page ID list
01075         if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
01076             $page_where = $hookObj->execFinalQuery_idList($list);
01077         } elseif ($this->join_pages)    {   // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
01078             $page_join = ',
01079                 pages';
01080             $page_where = 'pages.uid = ISEC.page_id
01081                 '.$this->cObj->enableFields('pages').'
01082                 AND pages.no_search=0
01083                 AND pages.doktype<200
01084             ';
01085         } 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!
01086             $siteIdNumbers = t3lib_div::intExplode(',',$this->wholeSiteIdList);
01087             $id_list=array();
01088             foreach ($siteIdNumbers as $rootId) {
01089                 $id_list[] = $this->cObj->getTreeList($rootId,9999,0,0,'','').$rootId;
01090             }
01091             $page_where = 'ISEC.page_id IN ('.implode(',',$id_list).')';
01092         } else {    // Disable everything... (select all)
01093             $page_where = ' 1=1 ';
01094         }
01095 
01096 
01097             // 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.
01098         if (substr($this->piVars['order'],0,5)=='rank_')    {
01099                 /*
01100                      OK there were some fancy calculations promoted by Graeme Merrall:
01101 
01102                     "However, regarding relevance you probably want to look at something like
01103                     Salton's formula which is a good easy way to measure relevance.
01104                     Oracle Intermedia uses this and it's pretty simple:
01105                     Score can be between 0 and 100, but the top-scoring document in the query
01106                     will not necessarily have a score of 100 -- scoring is relative, not
01107                     absolute. This means that scores are not comparable across indexes, or even
01108                     across different queries on the same index. Score for each document is
01109                     computed using the standard Salton formula:
01110 
01111                         3f(1+log(N/n))
01112 
01113                     Where f is the frequency of the search term in the document, N is the total
01114                     number of rows in the table, and n is the number of rows which contain the
01115                     search term. This is converted into an integer in the range 0 - 100.
01116 
01117                     There's a good doc on it at
01118                     http://ls6-www.informatik.uni-dortmund.de/bib/fulltext/ir/Pfeifer:97/
01119                     although it may be a little complex for what you require so just pick the
01120                     relevant parts out.
01121                     "
01122 
01123                     However I chose not to go with this for several reasons.
01124                     I do not claim that my ways of calculating importance here is the best.
01125                     ANY (better) suggestion for ranking calculation is accepted! (as long as they are shipped with tested code in exchange for this.)
01126                 */
01127 
01128             switch($this->piVars['order'])  {
01129                 case 'rank_flag':   // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
01130                                     // The ordering is refined with the frequency sum as well.
01131                     $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
01132                     $orderBy = 'order_val1'.$this->isDescending().',order_val2'.$this->isDescending();
01133                 break;
01134                 case 'rank_first':  // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
01135                     $grsel = 'AVG(IR.first) AS order_val';
01136                     $orderBy = 'order_val'.$this->isDescending(1);
01137                 break;
01138                 case 'rank_count':  // Number of words found
01139                     $grsel = 'SUM(IR.count) AS order_val';
01140                     $orderBy = 'order_val'.$this->isDescending();
01141                 break;
01142                 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?
01143                     $grsel = 'SUM(IR.freq) AS order_val';
01144                     $orderBy = 'order_val'.$this->isDescending();
01145                 break;
01146             }
01147 
01148                 // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
01149             $wordSel='('.implode(' OR ',$this->wSelClauses).') AND ';
01150 
01151             return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
01152                         'ISEC.*, IP.*, '
01153                         .$grsel,
01154                         'index_words IW,
01155                             index_rel IR,
01156                             index_section ISEC,
01157                             index_phash IP'.
01158                             $page_join,
01159                         $wordSel.'
01160                             IP.phash IN ('.$list.') '.
01161                             $this->mediaTypeWhere().' '.
01162                             $this->languageWhere().
01163                             $freeIndexUidClause.'
01164                             AND IW.wid=IR.wid
01165                             AND ISEC.phash = IR.phash
01166                             AND IP.phash = IR.phash
01167                             AND '.$page_where,
01168                         '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',
01169                         $orderBy
01170                     );
01171         } else {    // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
01172 
01173             $orderBy = '';
01174             switch((string)$this->piVars['order'])  {
01175                 case 'title':
01176                     $orderBy = 'IP.item_title'.$this->isDescending();
01177                 break;
01178                 case 'crdate':
01179                     $orderBy = 'IP.item_crdate'.$this->isDescending();
01180                 break;
01181                 case 'mtime':
01182                     $orderBy = 'IP.item_mtime'.$this->isDescending();
01183                 break;
01184             }
01185 
01186             return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
01187                         'ISEC.*, IP.*',
01188                         'index_phash IP,index_section ISEC'.$page_join,
01189                         'IP.phash IN ('.$list.') '.
01190                             $this->mediaTypeWhere().' '.
01191                             $this->languageWhere().
01192                             $freeIndexUidClause.'
01193                             AND IP.phash = ISEC.phash
01194                             AND '.$page_where,
01195                         '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',
01196                         $orderBy
01197                     );
01198         }
01199     }
01200 
01201     /**
01202      * Checking if the resume can be shown for the search result (depending on whether the rights are OK)
01203      * ? Should it also check for gr_list "0,-1"?
01204      *
01205      * @param   array       Result row array.
01206      * @return  boolean     Returns true if resume can safely be shown
01207      */
01208     function checkResume($row)  {
01209 
01210             // If the record is indexed by an indexing configuration, just show it.
01211             // At least this is needed for external URLs and files.
01212             // For records we might need to extend this - for instance block display if record is access restricted.
01213         if ($row['freeIndexUid'])   {
01214             return TRUE;
01215         }
01216 
01217             // Evaluate regularly indexed pages based on item_type:
01218         if ($row['item_type'])  {   // External media:
01219                 // For external media we will check the access of the parent page on which the media was linked from.
01220                 // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
01221                 // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
01222                 // 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.
01223             $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'));
01224             if ($GLOBALS['TYPO3_DB']->sql_num_rows($res))   {
01225                 #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' YES - ('.$GLOBALS['TSFE']->gr_list.")!");
01226                 return TRUE;
01227             } else {
01228                 #debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' NO - ('.$GLOBALS['TSFE']->gr_list.")!");
01229                 return FALSE;
01230             }
01231         } else {    // Ordinary TYPO3 pages:
01232             if (strcmp($row['gr_list'],$GLOBALS['TSFE']->gr_list))  {
01233                     // 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...
01234                 $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'));
01235                 if ($GLOBALS['TYPO3_DB']->sql_num_rows($res))   {
01236                     #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash'].' - YES ('.$GLOBALS['TSFE']->gr_list.")");
01237                     return TRUE;
01238                 } else {
01239                     #debug('Checking on it ...'.$row['item_title'].'/'.$row['phash']." - NOPE");
01240                     return FALSE;
01241                 }
01242             } else {
01243                     #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']);
01244                 return TRUE;
01245             }
01246         }
01247     }
01248 
01249     /**
01250      * Returns "DESC" or "" depending on the settings of the incoming highest/lowest result order (piVars['desc']
01251      *
01252      * @param   boolean     If true, inverse the order which is defined by piVars['desc']
01253      * @return  string      " DESC" or ""
01254      */
01255     function isDescending($inverse=FALSE)   {
01256         $desc = $this->piVars['desc'];
01257         if ($inverse)   $desc=!$desc;
01258         return !$desc ? ' DESC':'';
01259     }
01260 
01261     /**
01262      * Write statistics information to database for the search operation
01263      *
01264      * @param   array       Search Word array
01265      * @param   integer     Number of hits
01266      * @param   integer     Milliseconds the search took
01267      * @return  void
01268      */
01269     function writeSearchStat($sWArr,$count,$pt) {
01270         $insertFields = array(
01271             'searchstring' => $this->piVars['sword'],
01272             'searchoptions' => serialize(array($this->piVars,$sWArr,$pt)),
01273             'feuser_id' => intval($this->fe_user->user['uid']),         // fe_user id, integer
01274             'cookie' => $this->fe_user->id,                     // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
01275             'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'),       // Remote IP address
01276             'hits' => intval($count),                           // Number of hits on the search.
01277             'tstamp' => $GLOBALS['EXEC_TIME']                   // Time stamp
01278         );
01279 
01280         $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
01281         $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
01282 
01283         if ($newId) {
01284             foreach ($sWArr as $val)    {
01285                 $insertFields = array(
01286                     'word' => $val['sword'],        // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $val['sword'], 'toLower'),
01287                     'index_stat_search_id' => $newId,
01288                     'tstamp' => $GLOBALS['EXEC_TIME'],      // Time stamp
01289                     'pageid' => $GLOBALS['TSFE']->id    //search page id for indexed search stats
01290                 );
01291 
01292                 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
01293             }
01294         }
01295     }
01296 
01297 
01298 
01299 
01300 
01301 
01302 
01303 
01304 
01305 
01306 
01307 
01308 
01309     /***********************************
01310      *
01311      * HTML output functions
01312      *
01313      ***********************************/
01314 
01315     /**
01316      * Make search form HTML
01317      *
01318      * @param   array       Value/Labels pairs for search form selector boxes.
01319      * @return  string      Search form HTML
01320      */
01321     function makeSearchForm($optValues) {
01322         $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
01323 
01324             // Multilangual text
01325         $substituteArray = array('legend', 'searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style', 'freeIndexUid');
01326         foreach ($substituteArray as $marker)   {
01327             $markerArray['###FORM_'.t3lib_div::strtoupper($marker).'###'] = $this->pi_getLL('form_'.$marker,'',1);
01328         }
01329 
01330         $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label','',1);
01331 
01332             // Adding search field value
01333         $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
01334 
01335             // Additonal keyword => "Add to current search words"
01336         if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox'])   {
01337             $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
01338             $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"':'';
01339             $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch','',1);
01340         } else {
01341             $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
01342         }
01343 
01344         $markerArray['###ACTION_URL###'] = htmlspecialchars($this->getSearchFormActionURL());
01345 
01346         $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
01347         $hiddenFieldCode = preg_replace('/^\n\t(.+)/ms', '$1', $hiddenFieldCode);       // Remove first newline and tab (cosmetical issue)
01348         $hiddenFieldArr = array();
01349 
01350         foreach (t3lib_div::trimExplode(',',$this->hiddenFieldList) as $fieldName)  {
01351             $hiddenFieldMarkerArray = array();
01352             $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = $this->prefixId.'['.$fieldName.']';
01353             $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string)$this->piVars[$fieldName]);
01354 
01355             $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, array(), array());
01356         }
01357 
01358             // Extended search
01359         if ($this->piVars['ext'])   {
01360 
01361                 // Search for
01362             if ((!is_array($optValues['type']) && !is_array($optValues['defOp'])) || ($this->conf['blind.']['type'] && $this->conf['blind.']['defOp'])) {
01363                 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
01364             } else {
01365                 if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
01366                     unset($hiddenFieldArr['type']);
01367                     $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'],$optValues['type']);
01368                 } else {
01369                     $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
01370                 }
01371 
01372                 if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp'])   {
01373                     $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'],$optValues['defOp']);
01374                 } else {
01375                     $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
01376                 }
01377             }
01378 
01379                 // Search in
01380             if ((!is_array($optValues['media']) && !is_array($optValues['lang'])) || ($this->conf['blind.']['media'] && $this->conf['blind.']['lang'])) {
01381                 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
01382             } else {
01383                 if (is_array($optValues['media']) && !$this->conf['blind.']['media'])   {
01384                     unset($hiddenFieldArr['media']);
01385                     $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'],$optValues['media']);
01386                 } else {
01387                     $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
01388                 }
01389 
01390                 if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
01391                     unset($hiddenFieldArr['lang']);
01392                     $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'],$optValues['lang']);
01393                 } else {
01394                     $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
01395                 }
01396             }
01397 
01398                 // Sections
01399             if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
01400                 $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
01401             } else {
01402                 $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'],$optValues['sections']);
01403             }
01404 
01405                 // Free Indexing Configurations:
01406             if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid']) {
01407                 $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
01408             } else {
01409                 $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'],$optValues['freeIndexUid']);
01410             }
01411 
01412                 // Sorting
01413             if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order'])  {
01414                 $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
01415             } else {
01416                 unset($hiddenFieldArr['order']);
01417                 unset($hiddenFieldArr['desc']);
01418                 unset($hiddenFieldArr['results']);
01419                 $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'],$optValues['order']);
01420                 $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'],$optValues['desc']);
01421                 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
01422             }
01423 
01424                 // Limits
01425             if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results'])   {
01426                 $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
01427             } else {
01428                 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'],$optValues['results']);
01429             }
01430 
01431                 // Grouping
01432             if (!is_array($optValues['group']) || $this->conf['blind.']['group'])   {
01433                 $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
01434             } else {
01435                 unset($hiddenFieldArr['group']);
01436                 $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'],$optValues['group']);
01437             }
01438 
01439             if ($this->conf['blind.']['extResume']) {
01440                 $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
01441             } else {
01442                 $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
01443             }
01444 
01445         } else {    // Extended search
01446             $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
01447         }
01448 
01449         if($this->conf['show.']['advancedSearchLink'])  {
01450             $linkToOtherMode = ($this->piVars['ext'] ?
01451                 $this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>0)) :
01452                 $this->pi_getPageLink($GLOBALS['TSFE']->id,$GLOBALS['TSFE']->sPre,array($this->prefixId.'[ext]'=>1))
01453             );
01454 
01455             $markerArray['###LINKTOOTHERMODE###'] = '<a href="'.htmlspecialchars($linkToOtherMode).'">'.$this->pi_getLL($this->piVars['ext']?'link_regularSearch':'link_advancedSearch', '', 1).'</a>';
01456         } else {
01457             $markerArray['###LINKTOOTHERMODE###'] = '';
01458         }
01459 
01460             // Write all hidden fields
01461         $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('',$hiddenFieldArr));
01462 
01463         $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
01464 
01465         return $substitutedContent;
01466     }
01467 
01468     /**
01469      * Function, rendering selector box values.
01470      *
01471      * @param   string      Current value
01472      * @param   array       Array with the options as key=>value pairs
01473      * @return  string      <options> imploded.
01474      */
01475     function renderSelectBoxValues($value,$optValues)   {
01476         if (is_array($optValues))   {
01477             $opt=array();
01478             $isSelFlag=0;
01479             foreach ($optValues as $k=>$v)  {
01480                 $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
01481                 if ($sel)   { $isSelFlag++; }
01482                 $opt[]='<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
01483             }
01484 
01485             return implode('',$opt);
01486         }
01487     }
01488 
01489     /**
01490      * Print the searching rules
01491      *
01492      * @return  string      Rules for the search
01493      */
01494     function printRules()   {
01495         if ($this->conf['show.']['rules'])  {
01496 
01497             $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
01498 
01499             $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header','',1);
01500             $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text','',1)));
01501 
01502             $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
01503 
01504             return $this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']);
01505         }
01506     }
01507 
01508     /**
01509      * Returns the anchor-links to the sections inside the displayed result rows.
01510      *
01511      * @return  string
01512      */
01513     function printResultSectionLinks()  {
01514         if (count($this->resultSections))   {
01515             $lines = array();
01516 
01517             $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
01518             $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
01519 
01520             foreach ($this->resultSections as $id => $dat)  {
01521                 $markerArray = array();
01522 
01523                 $aBegin = '<a href="'.htmlspecialchars($GLOBALS['TSFE']->anchorPrefix.'#anchor_'.md5($id)).'">';
01524                 $aContent = htmlspecialchars(trim($dat[0]) ? trim($dat[0]) : $this->pi_getLL('unnamedSection')).
01525                         ' ('.$dat[1].' '.$this->pi_getLL($dat[1]>1 ? 'word_pages' : 'word_page','',1).')';
01526                 $aEnd = '</a>';
01527 
01528                 $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
01529 
01530                 $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
01531             }
01532 
01533             $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode('',$links)), array(), array());
01534 
01535             return '<div'.$this->pi_classParam('sectionlinks').'>'.$this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']).'</div>';
01536         }
01537     }
01538 
01539     /**
01540      * Returns the section header of the search result.
01541      *
01542      * @param   string      ID for the section (used for anchor link)
01543      * @param   string      Section title with linked wrapped around
01544      * @param   integer     Number of results in section
01545      * @return  string      HTML output
01546      */
01547     function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)  {
01548 
01549         $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
01550 
01551         $markerArray['###ANCHOR_URL###'] = 'anchor_'.md5($id);
01552         $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
01553         $markerArray['###RESULT_COUNT###'] = $countResultRows;
01554         $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page'.($countResultRows>1 ? 's' : ''));
01555 
01556         $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
01557 
01558         return $substitutedContent;
01559     }
01560 
01561     /**
01562      * This prints a single result row, including a recursive call for subrows.
01563      *
01564      * @param   array       Search result row
01565      * @param   integer     1=Display only header (for sub-rows!), 2=nothing at all
01566      * @return  string      HTML code
01567      */
01568     function printResultRow($row, $headerOnly=0)    {
01569 
01570             // Get template content:
01571         $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
01572 
01573         if ($hookObj = $this->hookRequest('printResultRow')) {
01574             return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
01575         } else {
01576 
01577             $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
01578 
01579             if (!is_array($row['_sub']))    {
01580                 $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
01581             }
01582 
01583             if (!$headerOnly)   {
01584                 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
01585 
01586             } elseif ($headerOnly==1) {
01587                 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
01588             } elseif ($headerOnly==2) {
01589                 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
01590                 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
01591             }
01592 
01593             if (is_array($tmplContent)) {
01594                 foreach ($tmplContent AS $k => $v)  {
01595                     $markerArray['###'.t3lib_div::strtoupper($k).'###'] = $v;
01596                 }
01597             }
01598 
01599                 // Description text
01600             $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size','',1);
01601             $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created','',1);
01602             $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified','',1);
01603             $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path','',1);
01604 
01605             $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
01606 
01607                 // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
01608             if (is_array($row['_sub'])) {
01609                 if ($this->multiplePagesType($row['item_type']))    {
01610 
01611                     $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching','',1), $html);
01612 
01613                     foreach ($row['_sub'] as $subRow)   {
01614                         $html .= $this->printResultRow($subRow,1);
01615                     }
01616                 } else {
01617 
01618                     $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching','',1);
01619 
01620                     $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell','',1), $html);
01621                 }
01622             }
01623 
01624             return $html;
01625         }
01626     }
01627 
01628     /**
01629      * Returns a results browser
01630      *
01631      * @param   boolean     Show result count
01632      * @param   string      String appended to "displaying results..." notice.
01633      * @param   string      String appended after section "displaying results..."
01634      * @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!
01635      * @return  string      HTML output
01636      */
01637     function pi_list_browseresults($showResultCount=1,$addString='',$addPart='',$freeIndexUid=-1)   {
01638 
01639             // Initializing variables:
01640         $pointer=$this->piVars['pointer'];
01641         $count=$this->internal['res_count'];
01642         $results_at_a_time = t3lib_div::intInRange($this->internal['results_at_a_time'],1,1000);
01643         $maxPages = t3lib_div::intInRange($this->internal['maxPages'],1,100);
01644         $pageCount = ceil($count/$results_at_a_time);
01645         $sTables = '';
01646 
01647         if ($pageCount > 1) {   // only show the result browser if more than one page is needed
01648             $pointer=intval($pointer);
01649             $links=array();
01650 
01651                 // Make browse-table/links:
01652             if ($pointer>0) {   // all pages after the 1st one
01653                 $links[]='<li>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev','< Previous',1),$pointer-1,$freeIndexUid).'</li>';
01654             }
01655 
01656             for($a=0;$a<$pageCount;$a++)    {
01657                 $min = max(0, $pointer+1-ceil($maxPages/2));
01658                 $max = $min+$maxPages;
01659                 if($max>$pageCount) {
01660                     $min = $min - ($max-$pageCount);
01661                 }
01662 
01663                 if($a >= $min && $a < $max) {
01664                     if($a==$pointer)    {
01665                         $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>';
01666                     } else {
01667                         $links[]='<li>'.$this->makePointerSelector_link(trim($this->pi_getLL('pi_list_browseresults_page','Page',1).' '.($a+1)),$a,$freeIndexUid).'</li>';
01668                     }
01669                 }
01670             }
01671             if ($pointer+1 < $pageCount)    {
01672                 $links[]='<li>'.$this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next','Next >',1),$pointer+1,$freeIndexUid).'</li>';
01673             }
01674         }
01675 
01676         $pR1 = $pointer*$results_at_a_time+1;
01677         $pR2 = $pointer*$results_at_a_time+$results_at_a_time;
01678         if(is_array($links))    {
01679             $addPart .= '
01680         <ul class="browsebox">
01681             '.implode('',$links).'
01682         </ul>';
01683         }
01684 
01685         $label = $this->pi_getLL('pi_list_browseresults_display','Displaying results ###TAG_BEGIN###%s to %s###TAG_END### out of ###TAG_BEGIN###%s###TAG_END###');
01686         $label = str_replace('###TAG_BEGIN###','<strong>',$label);
01687         $label = str_replace('###TAG_END###','</strong>',$label);
01688 
01689         $sTables = '<div'.$this->pi_classParam('browsebox').'>'.
01690             ($showResultCount ? '<p>'.sprintf(
01691                 $label,
01692                 $pR1,
01693                 min(array($this->internal['res_count'],$pR2)),
01694                 $this->internal['res_count']
01695                 ).$addString.'</p>':''
01696             ).$addPart.'</div>';
01697 
01698         return $sTables;
01699     }
01700 
01701 
01702 
01703 
01704 
01705 
01706 
01707 
01708 
01709 
01710 
01711 
01712     /***********************************
01713      *
01714      * Support functions for HTML output (with a minimum of fixed markup)
01715      *
01716      ***********************************/
01717 
01718     /**
01719      * Preparing template data for the result row output
01720      *
01721      * @param   array       Result row
01722      * @param   boolean     If set, display only header of result (for sub-results)
01723      * @return  array       Array with data to insert in result row template
01724      */
01725     function prepareResultRowTemplateData($row, $headerOnly)    {
01726 
01727             // Initialize:
01728         $specRowConf = $this->getSpecialConfigForRow($row);
01729         $CSSsuffix = $specRowConf['CSSsuffix']?'-'.$specRowConf['CSSsuffix']:'';
01730 
01731             // If external media, link to the media-file instead.
01732         if ($row['item_type'])  {       // External media
01733             if ($row['show_resume'])    {   // Can link directly.
01734                 $targetAttribute = '';
01735                 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
01736                     $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
01737                 }
01738                 $title = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' .
01739                     htmlspecialchars($this->makeTitle($row)) .
01740                     '</a>';
01741             } else {    // Suspicious, so linking to page instead...
01742                 $copy_row = $row;
01743                 unset($copy_row['cHashParams']);
01744                 $title = $this->linkPage($row['page_id'],htmlspecialchars($this->makeTitle($row)),$copy_row);
01745             }
01746         } else {    // Else the page:
01747 
01748                 // Prepare search words for markup in content:
01749             if ($this->conf['forwardSearchWordsInResultLink'])  {
01750                 $markUpSwParams = array('no_cache' => 1);
01751                 foreach ($this->sWArr as $d)    {
01752                     $markUpSwParams['sword_list'][] = $d['sword'];
01753                 }
01754             } else {
01755                 $markUpSwParams = array();
01756             }
01757             $title = $this->linkPage($row['data_page_id'],htmlspecialchars($this->makeTitle($row)),$row,$markUpSwParams);
01758         }
01759 
01760         $tmplContent = array();
01761         $tmplContent['title'] = $title;
01762         $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'].': ' : '&nbsp;';
01763         $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'],'',$specRowConf);
01764         $tmplContent['rating'] = $this->makeRating($row);
01765         $tmplContent['description'] = $this->makeDescription($row,$this->piVars['extResume'] && !$headerOnly?0:1);
01766         $tmplContent = $this->makeInfo($row,$tmplContent);
01767         $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
01768         $tmplContent['language'] = $this->makeLanguageIndication($row);
01769         $tmplContent['CSSsuffix'] = $CSSsuffix;
01770 
01771             // Post processing with hook.
01772         if ($hookObj = $this->hookRequest('prepareResultRowTemplateData_postProc')) {
01773             $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
01774         }
01775 
01776         return $tmplContent;
01777     }
01778 
01779     /**
01780      * Returns a string that tells which search words are searched for.
01781      *
01782      * @param   array       Array of search words
01783      * @return  string      HTML telling what is searched for.
01784      */
01785     function tellUsWhatIsSeachedFor($sWArr) {
01786 
01787             // Init:
01788         $searchingFor = '';
01789         $c=0;
01790 
01791             // Traverse search words:
01792         foreach ($sWArr as $k => $v)    {
01793             if ($c) {
01794                 switch($v['oper'])  {
01795                     case 'OR':
01796                         $searchingFor.= ' '.$this->pi_getLL('searchFor_or','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
01797                     break;
01798                     case 'AND NOT':
01799                         $searchingFor.= ' '.$this->pi_getLL('searchFor_butNot','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
01800                     break;
01801                     default:    // AND...
01802                         $searchingFor.= ' '.$this->pi_getLL('searchFor_and','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
01803                     break;
01804                 }
01805             } else {
01806                 $searchingFor = $this->pi_getLL('searchFor','',1).' '.$this->wrapSW($this->utf8_to_currentCharset($v['sword']));
01807             }
01808             $c++;
01809         }
01810         return $searchingFor;
01811     }
01812 
01813     /**
01814      * Wraps the search words in the search-word list display (from ->tellUsWhatIsSeachedFor())
01815      *
01816      * @param   string      search word to wrap (in local charset!)
01817      * @return  string      Search word wrapped in <span> tag.
01818      */
01819     function wrapSW($str)   {
01820         return '"<span'.$this->pi_classParam('sw').'>'.htmlspecialchars($str).'</span>"';
01821     }
01822 
01823     /**
01824      * Makes a selector box
01825      *
01826      * @param   string      Name of selector box
01827      * @param   string      Current value
01828      * @param   array       Array of options in the selector box (value => label pairs)
01829      * @return  string      HTML of selector box
01830      */
01831     function renderSelectBox($name,$value,$optValues)   {
01832         if (is_array($optValues))   {
01833             $opt = array();
01834             $isSelFlag = 0;
01835 
01836             foreach ($optValues as $k => $v)    {
01837                 $sel = (!strcmp($k,$value) ? ' selected="selected"' : '');
01838                 if ($sel)   $isSelFlag++;
01839                 $opt[] = '<option value="'.htmlspecialchars($k).'"'.$sel.'>'.htmlspecialchars($v).'</option>';
01840             }
01841 
01842             return '<select name="'.$name.'">'.implode('',$opt).'</select>';
01843         }
01844     }
01845 
01846     /**
01847      * Used to make the link for the result-browser.
01848      * Notice how the links must resubmit the form after setting the new pointer-value in a hidden formfield.
01849      *
01850      * @param   string      String to wrap in <a> tag
01851      * @param   integer     Pointer value
01852      * @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!
01853      * @return  string      Input string wrapped in <a> tag with onclick event attribute set.
01854      */
01855     function makePointerSelector_link($str,$p,$freeIndexUid)    {
01856         $onclick = 'document.getElementById(\'' . $this->prefixId . '_pointer\').value=\'' . $p . '\';' .
01857                     'document.getElementById(\'' . $this->prefixId . '_freeIndexUid\').value=\'' . rawurlencode($freeIndexUid) . '\';' .
01858                     'document.getElementById(\'' . $this->prefixId . '\').submit();return false;';
01859         return '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.$str.'</a>';
01860     }
01861 
01862     /**
01863      * Return icon for file extension
01864      *
01865      * @param   string      File extension / item type
01866      * @param   string      Title attribute value in icon.
01867      * @param   array       TypoScript configuration specifically for search result.
01868      * @return  string      <img> tag for icon
01869      */
01870     function makeItemTypeIcon($it, $alt='', $specRowConf) {
01871 
01872             // Build compound key if item type is 0, iconRendering is not used
01873             // and specConfs.[pid].pageIcon was set in TS
01874         if ($it === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->conf['iconRendering.'])) {
01875             $it .= ':' . $specRowConf['_pid'];
01876         }
01877         if (!isset($this->iconFileNameCache[$it]))  {
01878             $this->iconFileNameCache[$it] = '';
01879 
01880                 // If TypoScript is used to render the icon:
01881             if (is_array($this->conf['iconRendering.']))    {
01882                 $this->cObj->setCurrentVal($it);
01883                 $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'], $this->conf['iconRendering.']);
01884                 // ... otherwise, get flag from sys_language record:
01885             } else {
01886 
01887                     // Default creation / finding of icon:
01888                 $icon = '';
01889                 if ($it === '0' || substr($it, 0, 2) == '0:')   {
01890                     if (is_array($specRowConf['pageIcon.']))    {
01891                         $this->iconFileNameCache[$it] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
01892                     } else {
01893                         $icon = 'EXT:indexed_search/pi/res/pages.gif';
01894                     }
01895                 } elseif ($this->external_parsers[$it]) {
01896                     $icon = $this->external_parsers[$it]->getIcon($it);
01897                 }
01898 
01899                 if ($icon)  {
01900                     $fullPath = t3lib_div::getFileAbsFileName($icon);
01901 
01902                     if ($fullPath)  {
01903                         $info = @getimagesize($fullPath);
01904                         $iconPath = substr($fullPath, strlen(PATH_site));
01905                         $this->iconFileNameCache[$it] = (is_array($info)) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
01906                     }
01907                 }
01908             }
01909         }
01910         return $this->iconFileNameCache[$it];
01911     }
01912 
01913     /**
01914      * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
01915      *
01916      * @param   array       Result row array
01917      * @return  string      String showing ranking value
01918      */
01919     function makeRating($row)   {
01920 
01921         switch((string)$this->piVars['order'])  {
01922             case 'rank_count':      // Number of occurencies on page
01923                 return $row['order_val'].' '.$this->pi_getLL('maketitle_matches');
01924             break;
01925             case 'rank_first':  // Close to top of page
01926                 return ceil(t3lib_div::intInRange(255-$row['order_val'],1,255)/255*100).'%';
01927             break;
01928             case 'rank_flag':   // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
01929                 if ($this->firstRow['order_val2'])  {
01930                     $base = $row['order_val1']*256; // (3 MSB bit, 224 is highest value of order_val1 currently)
01931                     $freqNumber = $row['order_val2']/$this->firstRow['order_val2']*pow(2,12);   // 15-3 MSB = 12
01932                     $total = t3lib_div::intInRange($base+$freqNumber,0,32767);
01933                     #debug($total);
01934                     return ceil(log($total)/log(32767)*100).'%';
01935                 }
01936             break;
01937             case 'rank_freq':   // Based on frequency
01938                 $max = 10000;
01939                 $total = t3lib_div::intInRange($row['order_val'],0,$max);
01940 #               debug($total);
01941                 return ceil(log($total)/log($max)*100).'%';
01942             break;
01943             case 'crdate':  // Based on creation date
01944                 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'],0); // ,$conf['age']
01945             break;
01946             case 'mtime':   // Based on modification time
01947                 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'],0); // ,$conf['age']
01948             break;
01949             default:    // fx. title
01950                 return '&nbsp;';
01951             break;
01952         }
01953     }
01954 
01955     /**
01956      * Returns the resume for the search-result.
01957      *
01958      * @param   array       Search result row
01959      * @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.
01960      * @param   integer     String length
01961      * @return  string      HTML string     ...
01962      */
01963     function makeDescription($row,$noMarkup=0,$lgd=180) {
01964         if ($row['show_resume'])    {
01965             if (!$noMarkup) {
01966                 $markedSW = '';
01967                 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash='.intval($row['phash']));
01968                 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))  {
01969                         // Cut HTTP references after some length
01970                     $content = preg_replace('/(http:\/\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
01971                     $markedSW = $this->markupSWpartsOfString($content);
01972                 }
01973                 $GLOBALS['TYPO3_DB']->sql_free_result($res);
01974             }
01975 
01976             if (!trim($markedSW))   {
01977                 $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_description'],$lgd);
01978                 $outputStr = htmlspecialchars($outputStr);
01979             }
01980             $output = $this->utf8_to_currentCharset($outputStr ? $outputStr : $markedSW);
01981         } else {
01982             $output = '<span class="noResume">'.$this->pi_getLL('res_noResume','',1).'</span>';
01983         }
01984 
01985         return $output;
01986     }
01987 
01988     /**
01989      * Marks up the search words from $this->sWarr in the $str with a color.
01990      *
01991      * @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.
01992      * @return  string      Processed content.
01993      */
01994     function markupSWpartsOfString($str)    {
01995 
01996             // Init:
01997         $str = str_replace('&nbsp;',' ',t3lib_parsehtml::bidir_htmlspecialchars($str,-1));
01998         $str = preg_replace('/\s\s+/',' ',$str);
01999         $swForReg = array();
02000 
02001             // Prepare search words for regex:
02002         foreach ($this->sWArr as $d)    {
02003             $swForReg[] = preg_quote($d['sword'],'/');
02004         }
02005         $regExString = '('.implode('|',$swForReg).')';
02006 
02007             // Split and combine:
02008         $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
02009 // debug($parts,$regExString);
02010             // Constants:
02011         $summaryMax = 300;
02012         $postPreLgd = 60;
02013         $postPreLgd_offset = 5;
02014         $divider = ' ... ';
02015 
02016         $occurencies = (count($parts)-1)/2;
02017         if ($occurencies)   {
02018             $postPreLgd = t3lib_div::intInRange($summaryMax/$occurencies,$postPreLgd,$summaryMax/2);
02019         }
02020 
02021             // Variable:
02022         $summaryLgd = 0;
02023         $output = array();
02024 
02025             // Shorten in-between strings:
02026         foreach ($parts as $k => $strP) {
02027             if (($k%2)==0)  {
02028 
02029                     // Find length of the summary part:
02030                 $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
02031                 $output[$k] = $parts[$k];
02032 
02033                     // Possibly shorten string:
02034                 if (!$k)    {   // First entry at all (only cropped on the frontside)
02035                     if ($strLen > $postPreLgd)  {
02036                         $output[$k] = $divider.preg_replace('/^[^[:space:]]+[[:space:]]/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
02037                     }
02038                 } elseif ($summaryLgd > $summaryMax || !isset($parts[$k+1])) {  // In case summary length is exceed OR if there are no more entries at all:
02039                     if ($strLen > $postPreLgd)  {
02040                         $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).$divider;
02041                     }
02042                 } else {    // In-between search words:
02043                     if ($strLen > $postPreLgd*2)    {
02044                         $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],$postPreLgd-$postPreLgd_offset)).
02045                                         $divider.
02046                                         preg_replace('/^[^[:space:]]+[[:space:]]/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8',$parts[$k],-($postPreLgd-$postPreLgd_offset)));
02047                     }
02048                 }
02049                 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);;
02050 
02051                     // Protect output:
02052                 $output[$k] = htmlspecialchars($output[$k]);
02053 
02054                     // If summary lgd is exceed, break the process:
02055                 if ($summaryLgd > $summaryMax)  {
02056                     break;
02057                 }
02058             } else {
02059                 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8',$strP);
02060                 $output[$k] = '<strong class="tx-indexedsearch-redMarkup">'.htmlspecialchars($parts[$k]).'</strong>';
02061             }
02062         }
02063 
02064             // Return result:
02065         return implode('',$output);
02066     }
02067 
02068     /**
02069      * Returns the title of the search result row
02070      *
02071      * @param   array       Result row
02072      * @return  string      Title from row
02073      */
02074     function makeTitle($row)    {
02075         $add = '';
02076 
02077         if ($this->multiplePagesType($row['item_type']))    {
02078             $dat = unserialize($row['cHashParams']);
02079 
02080             $pp = explode('-',$dat['key']);
02081             if ($pp[0]!=$pp[1]) {
02082                 $add=', '.$this->pi_getLL('word_pages').' '.$dat['key'];
02083             } else $add=', '.$this->pi_getLL('word_page').' '.$pp[0];
02084         }
02085 
02086         $outputString = $GLOBALS['TSFE']->csConvObj->crop('utf-8',$row['item_title'],50,'...');
02087 
02088         return $this->utf8_to_currentCharset($outputString).$add;
02089     }
02090 
02091     /**
02092      * Returns the info-string in the bottom of the result-row display (size, dates, path)
02093      *
02094      * @param   array       Result row
02095      * @param   array       Template array to modify
02096      * @return  array       Modified template array
02097      */
02098     function makeInfo($row,$tmplArray)  {
02099         $tmplArray['size'] = t3lib_div::formatSize($row['item_size']);
02100         $tmplArray['created'] = $this->formatCreatedDate($row['item_crdate']);
02101         $tmplArray['modified'] = $this->formatModifiedDate($row['item_mtime']);
02102 
02103         $pathId = $row['data_page_id']?$row['data_page_id']:$row['page_id'];
02104         $pathMP = $row['data_page_id']?$row['data_page_mp']:'';
02105 
02106         $pI = parse_url($row['data_filename']);
02107         if ($pI['scheme'])  {
02108             $targetAttribute = '';
02109             if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
02110                 $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
02111             }
02112             $tmplArray['path'] = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' .
02113                 htmlspecialchars($row['data_filename']) .
02114                 '</a>';
02115         } else {
02116             $pathStr = htmlspecialchars($this->getPathFromPageId($pathId,$pathMP));
02117             $tmplArray['path'] = $this->linkPage($pathId,$pathStr,array(
02118                 'cHashParams' => $row['cHashParams'],
02119                 'data_page_type' => $row['data_page_type'],
02120                 'data_page_mp' => $pathMP,
02121                 'sys_language_uid' => $row['sys_language_uid'],
02122             ));
02123         }
02124 
02125         return $tmplArray;
02126     }
02127 
02128     /**
02129      * Returns configuration from TypoScript for result row based on ID / location in page tree!
02130      *
02131      * @param   array       Result row
02132      * @return  array       Configuration array
02133      */
02134     function getSpecialConfigForRow($row)   {
02135         $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
02136         $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
02137 
02138         $rl = $this->getRootLine($pathId,$pathMP);
02139         $specConf = $this->conf['specConfs.']['0.'];
02140         if (is_array($rl))  {
02141             foreach ($rl as $dat)   {
02142                 if (is_array($this->conf['specConfs.'][$dat['uid'].'.']))   {
02143                     $specConf = $this->conf['specConfs.'][$dat['uid'].'.'];
02144                     $specConf['_pid'] = $dat['uid'];
02145                     break;
02146                 }
02147             }
02148         }
02149 
02150         return $specConf;
02151     }
02152 
02153     /**
02154      * Returns the HTML code for language indication.
02155      *
02156      * @param   array       Result row
02157      * @return  string      HTML code for result row.
02158      */
02159     function makeLanguageIndication($row)   {
02160 
02161             // If search result is a TYPO3 page:
02162         if ((string)$row['item_type']==='0')    {
02163 
02164                 // If TypoScript is used to render the flag:
02165             if (is_array($this->conf['flagRendering.']))    {
02166                 $this->cObj->setCurrentVal($row['sys_language_uid']);
02167                 return $this->cObj->cObjGetSingle($this->conf['flagRendering'],$this->conf['flagRendering.']);
02168             } else { // ... otherwise, get flag from sys_language record:
02169 
02170                     // Get sys_language record
02171                 $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_language', 'uid=' . intval($row['sys_language_uid']) . ' ' . $this->cObj->enableFields('sys_language'));
02172 
02173                     // Flag code:
02174                 $flag = $rowDat['flag'];
02175                 if ($flag)  {
02176 
02177 // FIXME not all flags from typo3/gfx/flags are available in media/flags/
02178                     $file = substr(PATH_tslib,strlen(PATH_site)).'media/flags/flag_'.$flag;
02179                     $imgInfo = @getimagesize(PATH_site.$file);
02180 
02181 // original
02182 #                   $file = TYPO3_mainDir.'gfx/flags/'.$flag;
02183 #                   $imgInfo = @getimagesize(PATH_site.$file);
02184 
02185                     if (is_array($imgInfo)) {
02186                         $output = '<img src="' . $file . '" ' . $imgInfo[3] . ' title="' . htmlspecialchars($rowDat['title']) . '" alt="' . htmlspecialchars($rowDat['title']) . '" />';
02187                         return $output;
02188                     }
02189                 }
02190             }
02191         }
02192         return '&nbsp;';
02193     }
02194 
02195     /**
02196      * Returns the HTML code for the locking symbol.
02197      * NOTICE: Requires a call to ->getPathFromPageId() first in order to work (done in ->makeInfo() by calling that first)
02198      *
02199      * @param   integer     Page id for which to find answer
02200      * @return  string      <img> tag if access is limited.
02201      */
02202     function makeAccessIndication($id)  {
02203         if (is_array($this->fe_groups_required[$id]) && count($this->fe_groups_required[$id]))  {
02204             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="" />';
02205         }
02206     }
02207 
02208     /**
02209      * Links the $str to page $id
02210      *
02211      * @param   integer     Page id
02212      * @param   string      Title String to link
02213      * @param   array       Result row
02214      * @param   array       Additional parameters for marking up seach words
02215      * @return  string      <A> tag wrapped title string.
02216      */
02217     function linkPage($id,$str,$row=array(),$markUpSwParams=array())    {
02218 
02219             // Parameters for link:
02220         $urlParameters = (array)unserialize($row['cHashParams']);
02221 
02222             // Add &type and &MP variable:
02223         if ($row['data_page_type']) $urlParameters['type'] = $row['data_page_type'];
02224         if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
02225         if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
02226 
02227             // markup-GET vars:
02228         $urlParameters = array_merge($urlParameters, $markUpSwParams);
02229 
02230             // 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...
02231         if (!is_array($this->domain_records[$id]))  {
02232             $this->getPathFromPageId($id);
02233         }
02234 
02235             // If external domain, then link to that:
02236         if (count($this->domain_records[$id]))  {
02237             reset($this->domain_records[$id]);
02238             $firstDom = current($this->domain_records[$id]);
02239             $scheme = t3lib_div::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
02240 
02241             $addParams = '';
02242             if (is_array($urlParameters))   {
02243                 if (count($urlParameters))  {
02244                     $addParams.= t3lib_div::implodeArrayForUrl('',$urlParameters);
02245                 }
02246             }
02247 
02248             if ($target=$this->conf['search.']['detect_sys_domain_records.']['target']) {
02249                 $target = ' target="'.$target.'"';
02250             }
02251             return '<a href="'.htmlspecialchars($scheme.$firstDom.'/index.php?id='.$id.$addParams).'"'.$target.'>'.htmlspecialchars($str).'</a>';
02252         } else {
02253             return $this->pi_linkToPage($str,$id,$this->conf['result_link_target'],$urlParameters);
02254         }
02255     }
02256 
02257     /**
02258      * Returns the path to the page $id
02259      *
02260      * @param   integer     Page ID
02261      * @param   string      MP variable content.
02262      * @return  string      Root line for result.
02263      */
02264     function getRootLine($id,$pathMP='')    {
02265         $identStr = $id.'|'.$pathMP;
02266 
02267         if (!isset($this->cache_path[$identStr]))   {
02268             $this->cache_rl[$identStr] = $GLOBALS['TSFE']->sys_page->getRootLine($id,$pathMP);
02269         }
02270         return $this->cache_rl[$identStr];
02271     }
02272 
02273     /**
02274      * Gets the first sys_domain record for the page, $id
02275      *
02276      * @param   integer     Page id
02277      * @return  string      Domain name
02278      */
02279     function getFirstSysDomainRecordForPage($id)    {
02280         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', 'pid='.intval($id).$this->cObj->enableFields('sys_domain'), '', 'sorting');
02281         $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
02282         return rtrim($row['domainName'], '/');
02283     }
02284 
02285     /**
02286      * Returns the path to the page $id
02287      *
02288      * @param   integer     Page ID
02289      * @param   string      MP variable content
02290      * @return  string      Path
02291      */
02292     function getPathFromPageId($id,$pathMP='')  {
02293 
02294         $identStr = $id.'|'.$pathMP;
02295 
02296         if (!isset($this->cache_path[$identStr]))   {
02297             $this->fe_groups_required[$id] = array();
02298             $this->domain_records[$id] = array();
02299             $rl = $this->getRootLine($id,$pathMP);
02300             $hitRoot = 0;
02301             $path = '';
02302             if (is_array($rl) && count($rl))    {
02303                 foreach ($rl as $k => $v) {
02304                         // Check fe_user
02305                     if ($v['fe_group'] && ($v['uid']==$id || $v['extendToSubpages']))   {
02306                         $this->fe_groups_required[$id][]=$v['fe_group'];
02307                     }
02308                         // Check sys_domain.
02309                     if ($this->conf['search.']['detect_sys_domain_records'])    {
02310                         $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
02311                         if ($sysDName)  {
02312                             $this->domain_records[$id][] = $sysDName;
02313                                 // Set path accordingly:
02314                             $path = $sysDName.$path;
02315                             break;
02316                         }
02317                     }
02318 
02319                         // Stop, if we find that the current id is the current root page.
02320                     if ($v['uid']==$GLOBALS['TSFE']->config['rootLine'][0]['uid'])      {
02321                         break;
02322                     }
02323                     $path = '/'.$v['title'].$path;
02324                 }
02325             }
02326 
02327             $this->cache_path[$identStr] = $path;
02328 
02329             if (is_array($this->conf['path_stdWrap.'])) {
02330                 $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
02331             }
02332         }
02333 
02334         return $this->cache_path[$identStr];
02335     }
02336 
02337     /**
02338      * Return the menu of pages used for the selector.
02339      *
02340      * @param   integer     Page ID for which to return menu
02341      * @return  array       Menu items (for making the section selector box)
02342      */
02343     function getMenu($id)   {
02344         if ($this->conf['show.']['LxALLtypes']) {
02345             $output = Array();
02346             $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', 'pid='.intval($id).$this->cObj->enableFields('pages'), '', 'sorting');
02347             while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))   {
02348                 $output[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
02349             }
02350             $GLOBALS['TYPO3_DB']->sql_free_result($res);
02351             return $output;
02352         } else {
02353             return $GLOBALS['TSFE']->sys_page->getMenu($id);
02354         }
02355     }
02356 
02357     /**
02358      * Returns if an item type is a multipage item type
02359      *
02360      * @param   string      Item type
02361      * @return  boolean     True if multipage capable
02362      */
02363     function multiplePagesType($item_type)  {
02364         return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
02365     }
02366 
02367     /**
02368      * Converts the input string from utf-8 to the backend charset.
02369      *
02370      * @param   string      String to convert (utf-8)
02371      * @return  string      Converted string (backend charset if different from utf-8)
02372      */
02373     function utf8_to_currentCharset($str)   {
02374         return $GLOBALS['TSFE']->csConv($str,'utf-8');
02375     }
02376 
02377     /**
02378      * Returns an object reference to the hook object if any
02379      *
02380      * @param   string      Name of the function you want to call / hook key
02381      * @return  object      Hook object, if any. Otherwise null.
02382      */
02383     function hookRequest($functionName) {
02384         global $TYPO3_CONF_VARS;
02385 
02386             // Hook: menuConfig_preProcessModMenu
02387         if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
02388             $hookObj = t3lib_div::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
02389             if (method_exists ($hookObj, $functionName)) {
02390                 $hookObj->pObj = $this;
02391                 return $hookObj;
02392             }
02393         }
02394     }
02395 
02396     /**
02397      * Obtains the URL of the search target page
02398      *
02399      * @return string
02400      */
02401     protected function getSearchFormActionURL() {
02402         $targetUrlPid = $this->getSearchFormActionPidFromTS();
02403         if ($targetUrlPid == 0) {
02404             $targetUrlPid = $GLOBALS['TSFE']->id;
02405         }
02406         return $this->pi_getPageLink($targetUrlPid, $GLOBALS['TSFE']->sPre);
02407     }
02408 
02409     /**
02410      * Obtains search form target pid from the TypoScript configuration
02411      *
02412      * @return int
02413      */
02414     protected function getSearchFormActionPidFromTS() {
02415         $result = 0;
02416         if (isset($this->conf['search.']['targetPid']) || isset($this->conf['search.']['targetPid.'])) {
02417             if (is_array($this->conf['search.']['targetPid.'])) {
02418                 $result = $this->cObj->stdWrap($this->conf['search.']['targetPid'], $this->conf['search.']['targetPid.']);
02419             }
02420             else {
02421                 $result = $this->conf['search.']['targetPid'];
02422             }
02423             $result = intval($result);
02424         }
02425         return $result;
02426     }
02427 
02428     /**
02429      * Formats date as 'created' date
02430      *
02431      * @param int $date
02432      * @param string $defaultFormat
02433      * @return string
02434      */
02435     protected function formatCreatedDate($date) {
02436         $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
02437         return $this->formatDate($date, 'created', $defaultFormat);
02438     }
02439 
02440     /**
02441      * Formats date as 'modified' date
02442      *
02443      * @param int $date
02444      * @param string $defaultFormat
02445      * @return string
02446      */
02447     protected function formatModifiedDate($date) {
02448         $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' .
02449             $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
02450         return $this->formatDate($date, 'modified', $defaultFormat);
02451     }
02452 
02453     /**
02454      * Formats the date using format string from TypoScript or default format
02455      * if TypoScript format is not set
02456      *
02457      * @param int $date
02458      * @param string $tsKey
02459      * @param string $defaultFormat
02460      * @return string
02461      */
02462     protected function formatDate($date, $tsKey, $defaultFormat) {
02463         $strftimeFormat = $this->conf['dateFormat.'][$tsKey];
02464         if ($strftimeFormat) {
02465             $result = strftime($strftimeFormat, $date);
02466         }
02467         else {
02468             $result = date($defaultFormat, $date);
02469         }
02470         return $result;
02471     }
02472 }
02473 
02474 
02475 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php'])) {
02476     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/indexed_search/pi/class.tx_indexedsearch.php']);
02477 }
02478 
02479 ?>