|
TYPO3 API
SVNRelease
|
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'].': ' : ' '; 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 ' '; 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(' ',' ',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 ' '; 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 ?>
1.8.0