PageRenderTime 204ms CodeModel.GetById 81ms app.highlight 76ms RepoModel.GetById 24ms app.codeStats 2ms

/typo3/sysext/indexed_search/pi/class.tx_indexedsearch.php

https://bitbucket.org/linxpinx/mercurial
PHP | 2479 lines | 1427 code | 351 blank | 701 comment | 269 complexity | 5d691465b1ddef0298db4af2feb34412 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/***************************************************************
   3*  Copyright notice
   4*
   5*  (c) 2001-2010 Kasper Skaarhoj (kasperYYYY@typo3.com)
   6*  All rights reserved
   7*
   8*  This script is part of the TYPO3 project. The TYPO3 project is
   9*  free software; you can redistribute it and/or modify
  10*  it under the terms of the GNU General Public License as published by
  11*  the Free Software Foundation; either version 2 of the License, or
  12*  (at your option) any later version.
  13*
  14*  The GNU General Public License can be found at
  15*  http://www.gnu.org/copyleft/gpl.html.
  16*  A copy is found in the textfile GPL.txt and important notices to the license
  17*  from the author is found in LICENSE.txt distributed with these scripts.
  18*
  19*
  20*  This script is distributed in the hope that it will be useful,
  21*  but WITHOUT ANY WARRANTY; without even the implied warranty of
  22*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23*  GNU General Public License for more details.
  24*
  25*  This copyright notice MUST APPEAR in all copies of the script!
  26***************************************************************/
  27/**
  28 * Index search frontend
  29 *
  30 * $Id: class.tx_indexedsearch.php 8162 2010-07-12 13:22:14Z dmitry $
  31 *
  32 * Creates a searchform for indexed search. Indexing must be enabled
  33 * for this to make sense.
  34 *
  35 * @author	Kasper Skaarhoj <kasperYYYY@typo3.com>
  36 * @co-author	Christian Jul Jensen <christian@typo3.com>
  37 */
  38/**
  39 * [CLASS/FUNCTION INDEX of SCRIPT]
  40 *
  41 *
  42 *
  43 *  123: class tx_indexedsearch extends tslib_pibase
  44 *  168:     function main($content, $conf)
  45 *  200:     function initialize()
  46 *  413:     function getSearchWords($defOp)
  47 *  447:     function procSearchWordsByLexer($SWArr)
  48 *
  49 *              SECTION: Main functions
  50 *  491:     function doSearch($sWArr)
  51 *  549:     function getResultRows($sWArr,$freeIndexUid=-1)
  52 *  623:     function getResultRows_SQLpointer($sWArr,$freeIndexUid=-1)
  53 *  647:     function getDisplayResults($sWArr, $resData, $freeIndexUid=-1)
  54 *  699:     function compileResult($resultRows, $freeIndexUid=-1)
  55 *
  56 *              SECTION: Searching functions (SQL)
  57 *  800:     function getPhashList($sWArr)
  58 *  901:     function execPHashListQuery($wordSel,$plusQ='')
  59 *  921:     function sectionTableWhere()
  60 *  968:     function mediaTypeWhere()
  61 *  993:     function languageWhere()
  62 * 1005:     function freeIndexUidWhere($freeIndexUid)
  63 * 1046:     function execFinalQuery($list,$freeIndexUid=-1)
  64 * 1189:     function checkResume($row)
  65 * 1236:     function isDescending($inverse=FALSE)
  66 * 1250:     function writeSearchStat($sWArr,$count,$pt)
  67 *
  68 *              SECTION: HTML output functions
  69 * 1302:     function makeSearchForm($optValues)
  70 * 1436:     function renderSelectBoxValues($value,$optValues)
  71 * 1455:     function printRules()
  72 * 1474:     function printResultSectionLinks()
  73 * 1508:     function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
  74 * 1529:     function printResultRow($row, $headerOnly=0)
  75 * 1598:     function pi_list_browseresults($showResultCount=1,$addString='',$addPart='',$freeIndexUid=-1)
  76 *
  77 *              SECTION: Support functions for HTML output (with a minimum of fixed markup)
  78 * 1686:     function prepareResultRowTemplateData($row, $headerOnly)
  79 * 1740:     function tellUsWhatIsSeachedFor($sWArr)
  80 * 1774:     function wrapSW($str)
  81 * 1786:     function renderSelectBox($name,$value,$optValues)
  82 * 1810:     function makePointerSelector_link($str,$p,$freeIndexUid)
  83 * 1825:     function makeItemTypeIcon($it,$alt='',$specRowConf)
  84 * 1867:     function makeRating($row)
  85 * 1911:     function makeDescription($row,$noMarkup=0,$lgd=180)
  86 * 1942:     function markupSWpartsOfString($str)
  87 * 2022:     function makeTitle($row)
  88 * 2046:     function makeInfo($row,$tmplArray)
  89 * 2075:     function getSpecialConfigForRow($row)
  90 * 2099:     function makeLanguageIndication($row)
  91 * 2142:     function makeAccessIndication($id)
  92 * 2157:     function linkPage($id,$str,$row=array(),$markUpSwParams=array())
  93 * 2201:     function getRootLine($id,$pathMP='')
  94 * 2216:     function getFirstSysDomainRecordForPage($id)
  95 * 2229:     function getPathFromPageId($id,$pathMP='')
  96 * 2281:     function getMenu($id)
  97 * 2300:     function multiplePagesType($item_type)
  98 * 2310:     function utf8_to_currentCharset($str)
  99 * 2320:     function &hookRequest($functionName)
 100 *
 101 * TOTAL FUNCTIONS: 48
 102 * (This index is automatically created/updated by the extension "extdeveval")
 103 *
 104 */
 105
 106require_once(t3lib_extMgm::extPath('indexed_search').'class.indexer.php');
 107
 108
 109/**
 110 * Index search frontend
 111 *
 112 * Creates a searchform for indexed search. Indexing must be enabled
 113 * for this to make sense.
 114 *
 115 * @package TYPO3
 116 * @subpackage tx_indexedsearch
 117 * @author	Kasper Skaarhoj <kasperYYYY@typo3.com>
 118 */
 119class tx_indexedsearch extends tslib_pibase {
 120	var $prefixId = 'tx_indexedsearch';        // Same as class name
 121	var $scriptRelPath = 'pi/class.tx_indexedsearch.php';    // Path to this script relative to the extension dir.
 122	var $extKey = 'indexed_search';    // The extension key.
 123
 124	var $join_pages = 0;	// See document for info about this flag...
 125	var $defaultResultNumber = 10;
 126
 127	var $operator_translate_table = Array (		// case-sensitive. Defines the words, which will be operators between words
 128		Array ('+' , 'AND'),
 129		Array ('|' , 'OR'),
 130		Array ('-' , 'AND NOT'),
 131			// english
 132#		Array ('AND' , 'AND'),
 133#		Array ('OR' , 'OR'),
 134#		Array ('NOT' , 'AND NOT'),
 135	);
 136
 137		// Internal variable
 138	var $wholeSiteIdList = 0;		// Root-page PIDs to search in (rl0 field where clause, see initialize() function)
 139
 140		// Internals:
 141	var $sWArr = array();			// Search Words and operators
 142	var $optValues = array();		// Selector box values for search configuration form
 143	var $firstRow = Array();		// Will hold the first row in result - used to calculate relative hit-ratings.
 144
 145	var $cache_path = array();		// Caching of page path
 146	var $cache_rl = array();		// Caching of root line data
 147	var $fe_groups_required = array();	// Required fe_groups memberships for display of a result.
 148	var $domain_records = array();		// Domain records (?)
 149	var $wSelClauses = array();		// Select clauses for individual words
 150	var $resultSections = array();		// Page tree sections for search result.
 151	var $external_parsers = array();	// External parser objects
 152	var $iconFileNameCache = array();	// Storage of icons....
 153
 154	/**
 155	 * Lexer object
 156	 *
 157	 * @var tx_indexedsearch_lexer
 158	 */
 159	var $lexerObj;
 160
 161	/**
 162	 * Indexer object
 163	 *
 164	 * @var tx_indexedsearch_indexer
 165	 */
 166	var $indexerObj;
 167	var $templateCode;			// Will hold the content of $conf['templateFile']
 168	var $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
 169
 170
 171	/**
 172	 * Main function, called from TypoScript as a USER_INT object.
 173	 *
 174	 * @param	string		Content input, ignore (just put blank string)
 175	 * @param	array		TypoScript configuration of the plugin!
 176	 * @return	string		HTML code for the search form / result display.
 177	 */
 178	function main($content, $conf)    {
 179
 180			// Initialize:
 181		$this->conf = $conf;
 182		$this->pi_loadLL();
 183		$this->pi_setPiVarDefaults();
 184
 185			// Initialize the indexer-class - just to use a few function (for making hashes)
 186		$this->indexerObj = t3lib_div::makeInstance('tx_indexedsearch_indexer');
 187
 188			// Initialize:
 189		$this->initialize();
 190
 191			// Do search:
 192			// If there were any search words entered...
 193		if (is_array($this->sWArr))	{
 194			$content = $this->doSearch($this->sWArr);
 195		}
 196
 197			// Finally compile all the content, form, messages and results:
 198		$content = $this->makeSearchForm($this->optValues).
 199			$this->printRules().
 200			$content;
 201
 202        return $this->pi_wrapInBaseClass($content);
 203    }
 204
 205	/**
 206	 * Initialize internal variables, especially selector box values for the search form and search words
 207	 *
 208	 * @return	void
 209	 */
 210	function initialize()	{
 211		global $TYPO3_CONF_VARS;
 212
 213			// Initialize external document parsers for icon display and other soft operations
 214		if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers']))	{
 215			foreach ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef)	{
 216				$this->external_parsers[$extension] = t3lib_div::getUserObj($_objRef);
 217
 218					// Init parser and if it returns false, unset its entry again:
 219				if (!$this->external_parsers[$extension]->softInit($extension))	{
 220					unset($this->external_parsers[$extension]);
 221				}
 222			}
 223		}
 224
 225			// Init lexer (used to post-processing of search words)
 226		$lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ?
 227						$TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] :
 228						'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
 229		$this->lexerObj = t3lib_div::getUserObj($lexerObjRef);
 230
 231			// If "_sections" is set, this value overrides any existing value.
 232		if ($this->piVars['_sections'])		$this->piVars['sections'] = $this->piVars['_sections'];
 233
 234			// If "_sections" is set, this value overrides any existing value.
 235		if ($this->piVars['_freeIndexUid']!=='_')		$this->piVars['freeIndexUid'] = $this->piVars['_freeIndexUid'];
 236
 237			// Add previous search words to current
 238		if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev'])	{
 239			$this->piVars['sword'] = trim($this->piVars['sword_prev']).' '.$this->piVars['sword'];
 240		}
 241
 242		$this->piVars['results'] = t3lib_div::intInRange($this->piVars['results'],1,100000,$this->defaultResultNumber);
 243
 244			// Selector-box values defined here:
 245		$this->optValues = Array(
 246			'type' => Array(
 247				'0' => $this->pi_getLL('opt_type_0'),
 248				'1' => $this->pi_getLL('opt_type_1'),
 249				'2' => $this->pi_getLL('opt_type_2'),
 250				'3' => $this->pi_getLL('opt_type_3'),
 251				'10' => $this->pi_getLL('opt_type_10'),
 252				'20' => $this->pi_getLL('opt_type_20'),
 253			),
 254			'defOp' => Array(
 255				'0' => $this->pi_getLL('opt_defOp_0'),
 256				'1' => $this->pi_getLL('opt_defOp_1'),
 257			),
 258			'sections' => Array(
 259				'0' => $this->pi_getLL('opt_sections_0'),
 260				'-1' => $this->pi_getLL('opt_sections_-1'),
 261				'-2' => $this->pi_getLL('opt_sections_-2'),
 262				'-3' => $this->pi_getLL('opt_sections_-3'),
 263				// 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.
 264			),
 265			'freeIndexUid' => Array(
 266				'-1' => $this->pi_getLL('opt_freeIndexUid_-1'),
 267				'-2' => $this->pi_getLL('opt_freeIndexUid_-2'),
 268				'0' => $this->pi_getLL('opt_freeIndexUid_0'),
 269			),
 270			'media' => Array(
 271				'-1' => $this->pi_getLL('opt_media_-1'),
 272				'0' => $this->pi_getLL('opt_media_0'),
 273				'-2' => $this->pi_getLL('opt_media_-2'),
 274			),
 275			'order' => Array(
 276				'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
 277				'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
 278				'rank_first' => $this->pi_getLL('opt_order_rank_first'),
 279				'rank_count' => $this->pi_getLL('opt_order_rank_count'),
 280				'mtime' => $this->pi_getLL('opt_order_mtime'),
 281				'title' => $this->pi_getLL('opt_order_title'),
 282				'crdate' => $this->pi_getLL('opt_order_crdate'),
 283			),
 284			'group' => Array (
 285				'sections' => $this->pi_getLL('opt_group_sections'),
 286				'flat' => $this->pi_getLL('opt_group_flat'),
 287			),
 288			'lang' => Array (
 289				-1 => $this->pi_getLL('opt_lang_-1'),
 290				0 => $this->pi_getLL('opt_lang_0'),
 291			),
 292			'desc' => Array (
 293				'0' => $this->pi_getLL('opt_desc_0'),
 294				'1' => $this->pi_getLL('opt_desc_1'),
 295			),
 296			'results' => Array (
 297				'10' => '10',
 298				'20' => '20',
 299				'50' => '50',
 300				'100' => '100',
 301			)
 302		);
 303
 304			// Free Index Uid:
 305		if ($this->conf['search.']['defaultFreeIndexUidList'])	{
 306			$uidList = t3lib_div::intExplode(',', $this->conf['search.']['defaultFreeIndexUidList']);
 307			$indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title','index_config','uid IN ('.implode(',',$uidList).')'.$this->cObj->enableFields('index_config'),'','','','uid');
 308
 309			foreach ($uidList as $uidValue)	{
 310				if (is_array($indexCfgRecords[$uidValue]))	{
 311					$this->optValues['freeIndexUid'][$uidValue] = $indexCfgRecords[$uidValue]['title'];
 312				}
 313			}
 314		}
 315
 316			// Should we use join_pages instead of long lists of uids?
 317		if ($this->conf['search.']['skipExtendToSubpagesChecking'])	{
 318			$this->join_pages = 1;
 319		}
 320
 321			// Add media to search in:
 322		if (strlen(trim($this->conf['search.']['mediaList'])))	{
 323			$mediaList = implode(',', t3lib_div::trimExplode(',', $this->conf['search.']['mediaList'], 1));
 324		}
 325		foreach ($this->external_parsers as $extension => $obj)	{
 326				// Skip unwanted extensions
 327			if ($mediaList && !t3lib_div::inList($mediaList, $extension))	{ continue; }
 328
 329			if ($name = $obj->searchTypeMediaTitle($extension))	{
 330				$this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_'.$extension,$name);
 331			}
 332		}
 333
 334			// Add operators for various languages
 335			// Converts the operators to UTF-8 and lowercase
 336		$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');
 337		$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');
 338		$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');
 339
 340			// This is the id of the site root. This value may be a commalist of integer (prepared for this)
 341		$this->wholeSiteIdList = intval($GLOBALS['TSFE']->config['rootLine'][0]['uid']);
 342
 343			// Creating levels for section menu:
 344			// This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
 345		if ($this->conf['show.']['L1sections'])	{
 346			$firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
 347			foreach ($firstLevelMenu as $kk => $mR) {
 348					// @TODO: RFC #7370: doktype 2&5 are deprecated since TYPO3 4.2-beta1
 349				if ($mR['doktype']!=5 && !$mR['nav_hide']) {
 350					$this->optValues['sections']['rl1_'.$mR['uid']] = trim($this->pi_getLL('opt_RL1').' '.$mR['title']);
 351					if ($this->conf['show.']['L2sections'])	{
 352						$secondLevelMenu = $this->getMenu($mR['uid']);
 353						foreach ($secondLevelMenu as $kk2 => $mR2) {
 354								// @TODO: RFC #7370: doktype 2&5 are deprecated since TYPO3 4.2-beta1
 355							if ($mR2['doktype']!=5 && !$mR2['nav_hide']) {
 356								$this->optValues['sections']['rl2_'.$mR2['uid']] = trim($this->pi_getLL('opt_RL2').' '.$mR2['title']);
 357							} else unset($secondLevelMenu[$kk2]);
 358						}
 359						$this->optValues['sections']['rl2_'.implode(',',array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
 360					}
 361				} else unset($firstLevelMenu[$kk]);
 362			}
 363			$this->optValues['sections']['rl1_'.implode(',',array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
 364		}
 365
 366			// 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.
 367			// 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...
 368		if ($this->conf['search.']['rootPidList'])	{
 369			$this->wholeSiteIdList = implode(',',t3lib_div::intExplode(',',$this->conf['search.']['rootPidList']));
 370		}
 371
 372			// Load the template
 373		$this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
 374
 375			// Add search languages:
 376		$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1'.$this->cObj->enableFields('sys_language'));
 377		while($lR = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))	{
 378			$this->optValues['lang'][$lR['uid']] = $lR['title'];
 379		}
 380
 381			// Calling hook for modification of initialized content
 382		if ($hookObj = $this->hookRequest('initialize_postProc')) {
 383			$hookObj->initialize_postProc();
 384		}
 385
 386			// Default values set:
 387			// Setting first values in optValues as default values IF there is not corresponding piVar value set already.
 388		foreach ($this->optValues as $kk => $vv)	{
 389			if (!isset($this->piVars[$kk]))	{
 390				reset($vv);
 391				$this->piVars[$kk] = key($vv);
 392			}
 393		}
 394
 395			// Blind selectors:
 396		if (is_array($this->conf['blind.']))	{
 397			foreach ($this->conf['blind.'] as $kk => $vv)	{
 398				if (is_array($vv))	{
 399					foreach ($vv as $kkk => $vvv)	{
 400						if (!is_array($vvv) && $vvv && is_array($this->optValues[substr($kk,0,-1)]))	{
 401							unset($this->optValues[substr($kk,0,-1)][$kkk]);
 402						}
 403					}
 404				} elseif ($vv) {	// If value is not set, unset the option array.
 405					unset($this->optValues[$kk]);
 406				}
 407			}
 408		}
 409
 410			// This gets the search-words into the $sWArr:
 411		$this->sWArr = $this->getSearchWords($this->piVars['defOp']);
 412	}
 413
 414	/**
 415	 * 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)
 416	 *
 417	 * Only words with 2 or more characters are accepted
 418	 * Max 200 chars total
 419	 * Space is used to split words, "" can be used search for a whole string (not indexed search then)
 420	 * AND, OR and NOT are prefix words, overruling the default operator
 421	 * +/|/- equals AND, OR and NOT as operators.
 422	 * All search words are converted to lowercase.
 423	 *
 424	 * $defOp is the default operator. 1=OR, 0=AND
 425	 *
 426	 * @param	boolean		If true, the default operator will be OR, not AND
 427	 * @return	array		Returns array with search words if any found
 428	 */
 429	function getSearchWords($defOp)	{
 430			// 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!)
 431		$inSW = substr($this->piVars['sword'],0,200);
 432
 433			// Convert to UTF-8 + conv. entities (was also converted during indexing!)
 434		$inSW = $GLOBALS['TSFE']->csConvObj->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset);
 435		$inSW = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($inSW,TRUE);
 436
 437		if ($hookObj = $this->hookRequest('getSearchWords')) {
 438			return $hookObj->getSearchWords_splitSWords($inSW, $defOp);
 439		} else {
 440
 441			if ($this->piVars['type']==20)	{
 442				return array(array('sword'=>trim($inSW), 'oper'=>'AND'));
 443			} else {
 444				$search = t3lib_div::makeInstance('tslib_search');
 445				$search->default_operator = $defOp==1 ? 'OR' : 'AND';
 446				$search->operator_translate_table = $this->operator_translate_table;
 447				$search->register_and_explode_search_string($inSW);
 448
 449				if (is_array($search->sword_array))	{
 450					return $this->procSearchWordsByLexer($search->sword_array);
 451				}
 452			}
 453		}
 454	}
 455
 456	/**
 457	 * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
 458	 * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
 459	 *
 460	 * @param	array		Search word array
 461	 * @return	array		Search word array, processed through lexer
 462	 */
 463	function procSearchWordsByLexer($SWArr)	{
 464
 465			// Init output variable:
 466		$newSWArr = array();
 467
 468			// Traverse the search word array:
 469		foreach ($SWArr as $wordDef)	{
 470			if (!strstr($wordDef['sword'],' '))	{	// No space in word (otherwise it might be a sentense in quotes like "there is").
 471					// Split the search word by lexer:
 472				$res = $this->lexerObj->split2Words($wordDef['sword']);
 473
 474					// Traverse lexer result and add all words again:
 475				foreach ($res as $word)	{
 476					$newSWArr[] = array('sword'=>$word, 'oper'=>$wordDef['oper']);
 477				}
 478			} else {
 479				$newSWArr[] = $wordDef;
 480			}
 481		}
 482
 483			// Return result:
 484		return $newSWArr;
 485	}
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495	/*****************************
 496	 *
 497	 * Main functions
 498	 *
 499	 *****************************/
 500
 501	/**
 502	 * Performs the search, the display and writing stats
 503	 *
 504	 * @param	array		Search words in array, see ->getSearchWords() for details
 505	 * @return	string		HTML for result display.
 506	 */
 507	function doSearch($sWArr)	{
 508
 509			// Find free index uid:
 510		$freeIndexUid = $this->piVars['freeIndexUid'];
 511		if ($freeIndexUid==-2)	{
 512			$freeIndexUid = $this->conf['search.']['defaultFreeIndexUidList'];
 513		}
 514
 515		$indexCfgs = t3lib_div::intExplode(',',$freeIndexUid);
 516		$accumulatedContent = '';
 517
 518		foreach ($indexCfgs as $freeIndexUid)	{
 519				// Get result rows:
 520			$pt1 = t3lib_div::milliseconds();
 521			if ($hookObj = $this->hookRequest('getResultRows')) {
 522				$resData = $hookObj->getResultRows($sWArr,$freeIndexUid);
 523			} else {
 524				$resData = $this->getResultRows($sWArr,$freeIndexUid);
 525			}
 526
 527				// Display search results:
 528			$pt2 = t3lib_div::milliseconds();
 529			if ($hookObj = $this->hookRequest('getDisplayResults')) {
 530				$content = $hookObj->getDisplayResults($sWArr, $resData, $freeIndexUid);
 531			} else {
 532				$content = $this->getDisplayResults($sWArr, $resData, $freeIndexUid);
 533			}
 534
 535			$pt3 = t3lib_div::milliseconds();
 536
 537				// Create header if we are searching more than one indexing configuration:
 538			if (count($indexCfgs)>1)	{
 539				if ($freeIndexUid>0)	{
 540					list($indexCfgRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('title','index_config','uid='.intval($freeIndexUid).$this->cObj->enableFields('index_config'));
 541					$titleString = $indexCfgRec['title'];
 542				} else {
 543					$titleString = $this->pi_getLL('opt_freeIndexUid_header_'.$freeIndexUid);
 544				}
 545				$content = '<h1 class="tx-indexedsearch-category">'.htmlspecialchars($titleString).'</h1>'.$content;
 546			}
 547
 548			$accumulatedContent.=$content;
 549		}
 550
 551			// Write search statistics
 552		$this->writeSearchStat($sWArr,$resData['count'],array($pt1,$pt2,$pt3));
 553
 554			// Return content:
 555		return $accumulatedContent;
 556	}
 557
 558	/**
 559	 * Get search result rows / data from database. Returned as data in array.
 560	 *
 561	 * @param	array		Search word array
 562	 * @param	integer		Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
 563	 * @return	array		False if no result, otherwise an array with keys for first row, result rows and total number of results found.
 564	 */
 565	function getResultRows($sWArr,$freeIndexUid=-1)	{
 566
 567			// Getting SQL result pointer:
 568			$GLOBALS['TT']->push('Searching result');
 569		$res = $this->getResultRows_SQLpointer($sWArr,$freeIndexUid);
 570			$GLOBALS['TT']->pull();
 571
 572			// Organize and process result:
 573		if ($res)	{
 574
 575			$count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);	// Total search-result count
 576			$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
 577
 578				// Initialize result accumulation variables:
 579			$c = 0;	// Result pointer: Counts up the position in the current search-result
 580			$grouping_phashes = array();	// Used to filter out duplicates.
 581			$grouping_chashes = array();	// Used to filter out duplicates BASED ON cHash.
 582			$firstRow = array();	// Will hold the first row in result - used to calculate relative hit-ratings.
 583			$resultRows = array();	// Will hold the results rows for display.
 584
 585			$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.
 586
 587				// Now, traverse result and put the rows to be displayed into an array
 588				// Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
 589			while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))	{
 590
 591					// Set first row:
 592				if (!$c)	{
 593					$firstRow = $row;
 594				}
 595
 596				$row['show_resume'] = $this->checkResume($row);	// Tells whether we can link directly to a document or not (depends on possible right problems)
 597
 598				$phashGr = !in_array($row['phash_grouping'], $grouping_phashes);
 599				$chashGr = !in_array($row['contentHash'].'.'.$row['data_page_id'], $grouping_chashes);
 600				if ($phashGr && $chashGr)	{
 601					if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords'])	{	// Only if the resume may be shown are we going to filter out duplicates...
 602						if (!$this->multiplePagesType($row['item_type']))	{	// Only on documents which are not multiple pages documents
 603							$grouping_phashes[] = $row['phash_grouping'];
 604						}
 605						$grouping_chashes[] = $row['contentHash'].'.'.$row['data_page_id'];
 606
 607						$c++;	// Increase the result pointer
 608
 609							// All rows for display is put into resultRows[]
 610						if ($c > $pointer * $this->piVars['results'] && $c <= ($pointer * $this->piVars['results'] + $this->piVars['results']))	{
 611							$row['result_number'] = $c;
 612							$resultRows[] = $row;
 613								// 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).
 614							if (!$exactCount && (($c+1) > ($pointer+1)*$this->piVars['results']))	{ break; }
 615						}
 616					} else {
 617						$count--;	// Skip this row if the user cannot view it (missing permission)
 618					}
 619				} else {
 620					$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.
 621				}
 622			}
 623
 624			return array(
 625						'resultRows' => $resultRows,
 626						'firstRow' => $firstRow,
 627						'count' => $count
 628					);
 629		} else {	// No results found:
 630			return FALSE;
 631		}
 632	}
 633
 634	/**
 635	 * Gets a SQL result pointer to traverse for the search records.
 636	 *
 637	 * @param	array		Search words
 638	 * @param	integer		Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
 639	 * @return	pointer
 640	 */
 641	function getResultRows_SQLpointer($sWArr,$freeIndexUid=-1)	{
 642				// This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
 643		$list = $this->getPhashList($sWArr);
 644
 645			// Perform SQL Search / collection of result rows array:
 646		if ($list)	{
 647				// Do the search:
 648			$GLOBALS['TT']->push('execFinalQuery');
 649			$res = $this->execFinalQuery($list,$freeIndexUid);
 650			$GLOBALS['TT']->pull();
 651			return $res;
 652		} else {
 653			return FALSE;
 654		}
 655	}
 656
 657	/**
 658	 * Compiles the HTML display of the incoming array of result rows.
 659	 *
 660	 * @param	array		Search words array (for display of text describing what was searched for)
 661	 * @param	array		Array with result rows, count, first row.
 662	 * @param	integer		Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
 663	 * @return	string		HTML content to display result.
 664	 */
 665	function getDisplayResults($sWArr, $resData, $freeIndexUid=-1)	{
 666			// Perform display of result rows array:
 667		if ($resData)	{
 668			$GLOBALS['TT']->push('Display Final result');
 669
 670				// Set first selected row (for calculation of ranking later)
 671			$this->firstRow = $resData['firstRow'];
 672
 673				// Result display here:
 674			$rowcontent = '';
 675			$rowcontent.= $this->compileResult($resData['resultRows'], $freeIndexUid);
 676
 677				// Browsing box:
 678			if ($resData['count'])	{
 679				$this->internal['res_count'] = $resData['count'];
 680				$this->internal['results_at_a_time'] = $this->piVars['results'];
 681				$this->internal['maxPages'] = t3lib_div::intInRange($this->conf['search.']['page_links'],1,100,10);
 682				$addString = ($resData['count']&&$this->piVars['group']=='sections'&&$freeIndexUid<=0 ? ' '.sprintf($this->pi_getLL(count($this->resultSections)>1?'inNsections':'inNsection'),count($this->resultSections)):'');
 683				$browseBox1 = $this->pi_list_browseresults(1,$addString,$this->printResultSectionLinks(),$freeIndexUid);
 684				$browseBox2 = $this->pi_list_browseresults(0,'','',$freeIndexUid);
 685			}
 686
 687				// Browsing nav, bottom.
 688			if ($resData['count'])	{
 689				$content = $browseBox1.$rowcontent.$browseBox2;
 690			} else {
 691				$content = '<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
 692			}
 693
 694			$GLOBALS['TT']->pull();
 695		} else {
 696			$content.='<p'.$this->pi_classParam('noresults').'>'.$this->pi_getLL('noResults','',1).'</p>';
 697		}
 698
 699			// Print a message telling which words we searched for, and in which sections etc.
 700		$what = $this->tellUsWhatIsSeachedFor($sWArr).
 701				(substr($this->piVars['sections'],0,2)=='rl'?' '.$this->pi_getLL('inSection','',1).' "'.substr($this->getPathFromPageId(substr($this->piVars['sections'],4)),1).'"':'');
 702		$what = '<div'.$this->pi_classParam('whatis').'>'.$this->cObj->stdWrap($what, $this->conf['whatis_stdWrap.']).'</div>';
 703		$content = $what.$content;
 704
 705			// Return content:
 706		return $content;
 707	}
 708
 709	/**
 710	 * Takes the array with resultrows as input and returns the result-HTML-code
 711	 * Takes the "group" var into account: Makes a "section" or "flat" display.
 712	 *
 713	 * @param	array		Result rows
 714	 * @param	integer		Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
 715	 * @return	string		HTML
 716	 */
 717	function compileResult($resultRows, $freeIndexUid=-1)	{
 718		$content = '';
 719
 720			// Transfer result rows to new variable, performing some mapping of sub-results etc.
 721		$newResultRows = array();
 722		foreach ($resultRows as $row)	{
 723			$id = md5($row['phash_grouping']);
 724			if (is_array($newResultRows[$id]))	{
 725				if (!$newResultRows[$id]['show_resume'] && $row['show_resume'])	{	// swapping:
 726
 727						// Remove old
 728					$subrows = $newResultRows[$id]['_sub'];
 729					unset($newResultRows[$id]['_sub']);
 730					$subrows[] = $newResultRows[$id];
 731
 732						// Insert new:
 733					$newResultRows[$id] = $row;
 734					$newResultRows[$id]['_sub'] = $subrows;
 735				} else $newResultRows[$id]['_sub'][] = $row;
 736			} else {
 737				$newResultRows[$id] = $row;
 738			}
 739		}
 740		$resultRows = $newResultRows;
 741		$this->resultSections = array();
 742
 743		if ($freeIndexUid<=0)	{
 744			switch($this->piVars['group'])	{
 745				case 'sections':
 746
 747					$rl2flag = substr($this->piVars['sections'],0,2)=='rl';
 748					$sections = array();
 749					foreach ($resultRows as $row)	{
 750						$id = $row['rl0'].'-'.$row['rl1'].($rl2flag?'-'.$row['rl2']:'');
 751						$sections[$id][] = $row;
 752					}
 753
 754					$this->resultSections = array();
 755
 756					foreach ($sections as $id => $resultRows)	{
 757						$rlParts = explode('-',$id);
 758						$theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1]?$rlParts[1]:$rlParts[0]);
 759						$theRLid = $rlParts[2] ? 'rl2_'.$rlParts[2]:($rlParts[1]?'rl1_'.$rlParts[1]:'0');
 760
 761						$sectionName = $this->getPathFromPageId($theId);
 762						if ($sectionName{0} == '/') $sectionName = substr($sectionName,1);
 763
 764						if (!trim($sectionName))	{
 765							$sectionTitleLinked = $this->pi_getLL('unnamedSection','',1).':';
 766						} else {
 767							$onclick = 'document.'.$this->prefixId.'[\''.$this->prefixId.'[_sections]\'].value=\''.$theRLid.'\';document.'.$this->prefixId.'.submit();return false;';
 768							$sectionTitleLinked = '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.htmlspecialchars($sectionName).':</a>';
 769						}
 770						$this->resultSections[$id] = array($sectionName,count($resultRows));
 771
 772							// Add content header:
 773						$content.= $this->makeSectionHeader($id,$sectionTitleLinked,count($resultRows));
 774
 775							// Render result rows:
 776						foreach ($resultRows as $row)	{
 777							$content.= $this->printResultRow($row);
 778						}
 779					}
 780				break;
 781				default:	// flat:
 782					foreach ($resultRows as $row)	{
 783						$content.= $this->printResultRow($row);
 784					}
 785				break;
 786			}
 787		} else {
 788			foreach ($resultRows as $row)	{
 789				$content.= $this->printResultRow($row);
 790			}
 791		}
 792
 793		return '<div'.$this->pi_classParam('res').'>'.$content.'</div>';
 794	}
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806	/***********************************
 807	 *
 808	 *	Searching functions (SQL)
 809	 *
 810	 ***********************************/
 811
 812	/**
 813	 * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the sWArr array.
 814	 * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
 815	 *
 816	 * @param	array		Search word array
 817	 * @return	string		List of integers
 818	 */
 819	function getPhashList($sWArr)	{
 820
 821			// Initialize variables:
 822		$c=0;
 823		$totalHashList = array();	// This array accumulates the phash-values
 824		$this->wSelClauses = array();
 825
 826			// Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
 827		foreach ($sWArr as $k => $v)	{
 828				// Making the query for a single search word based on the search-type
 829			$sWord = $v['sword'];	// $GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$v['sword'],'toLower');	// lower-case all of them...
 830			$theType = (string)$this->piVars['type'];
 831			if (strstr($sWord,' '))	$theType = 20;	// If there are spaces in the search-word, make a full text search instead.
 832
 833			$GLOBALS['TT']->push('SearchWord "'.$sWord.'" - $theType='.$theType);
 834
 835			$res = '';
 836			$wSel='';
 837
 838				// Perform search for word:
 839			switch($theType)	{
 840				case '1':	// Part of word
 841					$wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
 842					$res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
 843				break;
 844				case '2':	// First part of word
 845					$wSel = "IW.baseword LIKE '".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."%'";
 846					$res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
 847				break;
 848				case '3':	// Last part of word
 849					$wSel = "IW.baseword LIKE '%".$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')."'";
 850					$res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
 851				break;
 852				case '10':	// Sounds like
 853					$wSel = 'IW.metaphone = '.$this->indexerObj->metaphone($sWord);
 854					$res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
 855				break;
 856				case '20':	// Sentence
 857					$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
 858								'ISEC.phash',
 859								'index_section ISEC, index_fulltext IFT',
 860								'IFT.fulltextdata LIKE \'%'.$GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_fulltext').'%\' AND
 861									ISEC.phash = IFT.phash
 862									'.$this->sectionTableWhere(),
 863								'ISEC.phash'
 864							);
 865					$wSel = '1=1';
 866
 867					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.
 868				break;
 869				default:	// Distinct word
 870					$wSel = 'IW.wid = '.$hash = $this->indexerObj->md5inthash($sWord);
 871					$res = $this->execPHashListQuery($wSel,' AND is_stopword=0');
 872				break;
 873			}
 874
 875				// Accumulate the word-select clauses
 876			$this->wSelClauses[] = $wSel;
 877
 878				// If there was a query to do, then select all phash-integers which resulted from this.
 879			if ($res)	{
 880
 881					// Get phash list by searching for it:
 882				$phashList = array();
 883				while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))	{
 884					$phashList[] = $row['phash'];
 885				}
 886				$GLOBALS['TYPO3_DB']->sql_free_result($res);
 887
 888					// Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
 889				if ($c) {
 890					switch($v['oper'])	{
 891						case 'OR':
 892							$totalHashList = array_unique(array_merge($phashList,$totalHashList));
 893						break;
 894						case 'AND NOT':
 895							$totalHashList = array_diff($totalHashList,$phashList);
 896						break;
 897						default:	// AND...
 898							$totalHashList = array_intersect($totalHashList,$phashList);
 899						break;
 900					}
 901				} else {
 902					$totalHashList = $phashList;	// First search
 903				}
 904			}
 905
 906			$GLOBALS['TT']->pull();
 907			$c++;
 908		}
 909
 910		return implode(',',$totalHashList);
 911	}
 912
 913	/**
 914	 * Returns a query which selects the search-word from the word/rel tables.
 915	 *
 916	 * @param	string		WHERE clause selecting the word from phash
 917	 * @param	string		Additional AND clause in the end of the query.
 918	 * @return	pointer		SQL result pointer
 919	 */
 920	function execPHashListQuery($wordSel,$plusQ='')	{
 921		return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
 922					'IR.phash',
 923					'index_words IW,
 924						index_rel IR,
 925						index_section ISEC',
 926					$wordSel.'
 927						AND IW.wid=IR.wid
 928						AND ISEC.phash = IR.phash
 929						'.$this->sectionTableWhere().'
 930						'.$plusQ,
 931					'IR.phash'
 932				);
 933	}
 934
 935	/**
 936	 * Returns AND statement for selection of section in database. (rootlevel 0-2 + page_id)
 937	 *
 938	 * @return	string		AND clause for selection of section in database.
 939	 */
 940	function sectionTableWhere()	{
 941		$out = $this->wholeSiteIdList<0 ? '' : 'AND ISEC.rl0 IN ('.$this->wholeSiteIdList.')';
 942
 943		$match = '';
 944		if (substr($this->piVars['sections'],0,4)=='rl1_')	{
 945			$list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
 946			$out.= 'AND ISEC.rl1 IN ('.$list.')';
 947			$match = TRUE;
 948		} elseif (substr($this->piVars['sections'],0,4)=='rl2_')	{
 949			$list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],4)));
 950			$out.= 'AND ISEC.rl2 IN ('.$list.')';
 951			$match = TRUE;
 952		} elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields']))	{
 953				// Traversing user configured fields to see if any of those are used to limit search to a section:
 954			foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel)	{
 955				if (substr($this->piVars['sections'],0,strlen($fieldName)+1)==$fieldName.'_')	{
 956					$list = implode(',',t3lib_div::intExplode(',',substr($this->piVars['sections'],strlen($fieldName)+1)));
 957					$out.= 'AND ISEC.'.$fieldName.' IN ('.$list.')';
 958					$match = TRUE;
 959					break;
 960				}
 961			}
 962		}
 963
 964			// If no match above, test the static types:
 965		if (!$match)	{
 966			switch((string)$this->piVars['sections'])	{
 967				case '-1':		// '-1' => 'Only this page',
 968					$out.= ' AND ISEC.page_id='.$GLOBALS['TSFE']->id;
 969				break;
 970				case '-2':		// '-2' => 'Top + level 1',
 971					$out.= ' AND ISEC.rl2=0';
 972				break;
 973				case '-3':		// '-3' => 'Level 2 and out',
 974					$out.= ' AND ISEC.rl2>0';
 975				break;
 976			}
 977		}
 978
 979		return $out;
 980	}
 981
 982	/**
 983	 * Returns AND statement for selection of media type
 984	 *
 985	 * @return	string		AND statement for selection of media type
 986	 */
 987	function mediaTypeWhere()	{
 988
 989		switch((string)$this->piVars['media'])	{
 990			case '0':		// '0' => 'Kun TYPO3 sider',
 991				$out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
 992			break;
 993			case '-2':		// All external documents
 994				$out = 'AND IP.item_type!='.$GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');;
 995			break;
 996			case '-1':	// All content
 997				$out='';
 998			break;
 999			default:
1000				$out = 'AND IP.item_type='.$GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['media'], 'index_phash');
1001			break;
1002		}
1003
1004		return $out;
1005	}
1006
1007	/**
1008	 * Returns AND statement for selection of langauge
1009	 *
1010	 * @return	string		AND statement for selection of langauge
1011	 */
1012	function languageWhere()	{
1013		if ($this->piVars['lang']>=0)	{	// -1 is the same as ALL language.
1014			return 'AND IP.sys_language_uid='.intval($this->piVars['lang']);
1015		}
1016	}
1017
1018	/**
1019	 * Where-clause for free index-uid value.
1020	 *
1021	 * @param	integer		Free Index UID value to limit search to.
1022	 * @return	string		WHERE SQL clause part.
1023	 */
1024	function freeIndexUidWhere($freeIndexUid)	{
1025
1026		if ($freeIndexUid>=0)	{
1027
1028				// First, look if the freeIndexUid is a meta configuration:
1029			list($indexCfgRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('indexcfgs','index_config','type=5 AND uid='.intval($freeIndexUid).$this->cObj->enableFields('index_config'));
1030			if (is_array($indexCfgRec))	{
1031				$refs = t3lib_div::trimExplode(',',$indexCfgRec['indexcfgs']);
1032				$list = array(-99);	// Default value to protect against empty array.
1033				foreach ($refs as $ref)	{
1034					list($table,$uid) = t3lib_div::revExplode('_',$ref,2);
1035					switch ($table)	{
1036						case 'index_config':
1037							list($idxRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid','index_config','uid='.intval($uid).$this->cObj->enableFields('index_config'));
1038							if ($idxRec)	$list[] = $uid;
1039						break;
1040						case 'pages':
1041							$indexCfgRecordsFromPid = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid','index_config','pid='.intval($uid).$this->cObj->enableFields('index_config'));
1042							foreach ($indexCfgRecordsFromPid as $idxRec)	{
1043								$list[] = $idxRec['uid'];
1044							}
1045						break;
1046					}
1047				}
1048
1049				$list = array_unique($list);
1050			} else {
1051				$list = array(intval($freeIndexUid));
1052			}
1053
1054			return ' AND IP.freeIndexUid IN ('.implode(',',$list).')';
1055		}
1056	}
1057
1058	/**
1059	 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
1060	 *
1061	 * @param	string		List of phash integers which match the search.
1062	 * @param	integer		Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
1063	 * @return	pointer		Query result pointer
1064	 */
1065	function execFinalQuery($list,$freeIndexUid=-1)	{
1066
1067			// Setting up methods of filtering results based on page types, access, etc.
1068		$page_join = '';
1069		$page_where = '';
1070
1071			// Indexing configuration clause:
1072		$freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
1073
1074			// Calling hook for alternative creation of page ID list
1075		if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
1076			$page_where = $hookObj->execFinalQuery_idList($list);
1077		} elseif ($this->join_pages)	{	// Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
1078			$page_join = ',
1079				pages';
1080			$page_where = 'pages.uid = ISEC.page_id
1081				'.$this->cObj->enableFields('pages').'
1082				AND pages.no_search=0
1083				AND pages.doktype<200
1084			';
1085		} 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!
1086			$siteIdNumbers = t3lib_div::intExplode(',',$this->wholeSiteIdList);
1087			$id_list=array();
1088			foreach ($siteIdNumbers as $rootId) {
1089				$id_list[] = $this->cObj->getTreeList($rootId,9999,0,0,'','').$rootId;
1090			}
1091			$page_where = 'ISEC.page_id IN ('.implode(',',$id_list).')';
1092		} else {	// Disable everything... (select all)
1093			$page_where = ' 1=1 ';
1094		}
1095
1096
1097			// 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.
1098		if (substr($this->piVars['order'],0,5)=='rank_')	{
1099				/*
1100					 OK there were some fancy calculations promoted by Graeme Merrall:
1101
1102					"However, regarding relevance you probably want to look at something like
1103					Salton's formula which is a good easy way to measure relevance.
1104					Oracle Intermedia uses this and it's pretty simple:
1105					Score can be between 0 and 100, but the top-scoring document in the query
1106					will not necessarily have a score of 100 -- scoring is relative, not
1107					absolute. This means that scores are not comparable across indexes, or even
1108					across different queries on the same index. Score for each document is
1109					computed using the standard Salton formula:
1110
1111					    3f(1+log(N/n))
1112
1113					Where f is the frequency of the search term in the document, N is the total
1114					number of rows in the table, and n is the number of rows which contain the
1115					search term. This is converted into an integer in the range 0 - 100.
1116
1117					There's a good doc on it at
1118					http://ls6-www.informatik.uni-dortmund.de/bib/fulltext/ir/Pfeifer:97/
1119					although it may be a little complex for what you require so just pick the
1120					relevant parts out.
1121					"
1122
1123					However I chose not to go with this for several reasons.
1124					I do not claim that my ways of calculating importance here is the best.
1125					ANY (better) suggestion for ranking calculation is accepted! (as long as they are shipped with tested code in exchange for this.)
1126				*/
1127
1128			switch($this->piVars['order'])	{
1129				case 'rank_flag':	// This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1130									// The ordering is refined with the frequency sum as well.
1131					$grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1132					$orderBy = 'order_val1'.$this->isDescending().',order_val2'.$this->isDescending();
1133				break;
1134				case 'rank_first':	// Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1135					$grsel = 'AVG(IR.first) AS order_val';
1136					$orderBy = 'order_val'.$this->isDescending(1);
1137				break;
1138				case 'rank_count':	// Number of words found
1139					$grsel = 'SUM(IR.count) AS order_val';
1140					$orderBy = 'order_val'.$this->isDescending();
1141				break;
1142				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?
1143					$grsel = 'SUM(IR.freq) AS order_val';
1144					$orderBy = 'order_val'.$this->isDescending();
1145				break;
1146			}
1147
1148				// So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1149			$wordSel='('.implode(' OR ',$this->wSelClauses).') AND ';
1150
1151			return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1152						'ISEC.*, IP.*, '
1153						.$grsel,
1154						'index_words IW,
1155							index_rel IR,
1156							index_section ISEC,
1157							index_phash IP'.
1158							$page_join,
1159						$wordSel.'
1160							IP.phash IN ('.$list.') '.
1161							$this->mediaTypeWhere().' '.
1162							$this->languageWhere().
1163							$freeIndexUidClause.'
1164							AND IW.wid=IR.wid
1165							AND ISEC.phash = IR.phash
1166							AND IP.phash = IR.phash
1167							AND	'.$page_where,
1168						'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',
1169						$orderBy
1170					);
1171		} else {	// Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1172
1173			$orderBy = '';
1174			switch((string)$this->piVars['order'])	{
1175				case 'title':
1176					$orderBy = 'IP.item_title'.$this->isDescending();
1177				break;
1178				case 'crdate':
1179					$orderBy = 'IP.item_crdate'.$this->isDescending();
1180				break;
1181				case 'mtime':
1182					$orderBy = 'IP.item_mtime'.$this->isDescending();
1183				break;
1184			}
1185
1186			return $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1187						'ISEC.*, IP.*',
1188						'index_phash IP,index_section ISEC'.$page_join,
1189						'IP.phash IN ('.$list.') '.
1190							$this->mediaTypeWhere().' '.
1191							$this->languageWhere().
1192							$freeIndexUidClause.'
1193							AND IP.phash = ISEC.phash
1194							AND '.$page_where,
1195						'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',
1196						$orderBy
1197					);
1198		}
1199	}
1200
1201	/**
1202	 * Checking if the resume can be shown for the search result (depending on whether the rights are OK)
1203	 * ? Should it also check for gr_list "0,-1"?
1204	 *
1205	 * @param	array		Result row array.
1206	 * @return	boolean		Returns true if resume can safely be shown
1207	 */
1208	function checkResume($row)	{
1209
1210			// If the record is indexed by an indexing configuration, just show it.
1211			// At least this is needed for external URLs and files.
1212			// For records we might need to extend this - for instance block display if record is access restricted.
1213		if ($row['freeIndexUid'])	{
1214			return TRUE;
1215		}
1216
1217			// Evaluate regularly indexed pages based on item_type:
1218		if ($row['item_type'])	{	// External media:
1219				// For external media we will check the access of the parent page on which the media was linked from.
1220				// "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1221				// So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1222				// 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.
1223			$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'));
1224			if ($GLOBALS['TYPO3_DB']->sql_num_rows($res))	{
1225				#debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' YES - ('.$GLOBALS['TSFE']->gr_list.")!");
1226				return TRUE;
1227			} else {
1228				#debug("Look up for external media '".$row['data_filename']."': phash:".$row['phash_t3'].' NO - ('.$GLOBALS['TSFE']->gr_list.")!");
1229				return FALSE;
1230			}
1231		} else {	// Ordinary TYPO3 pages:
1232			if (strcmp($row['gr_list'],$GLOBALS['TSFE']->gr_list))	{
1233					// 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...
1234				$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash='.intval($row['phash']).' AND gr_l…

Large files files are truncated, but you can click here to view the full file