[BUGFIX] Wrong file and classname in indexed_search
[Packages/TYPO3.CMS.git] / typo3 / sysext / indexed_search / Classes / Controller / SearchFormController.php
1 <?php
2 namespace TYPO3\CMS\IndexedSearch\Controller;
3
4 /**
5 * Index search frontend
6 *
7 * Creates a searchform for indexed search. Indexing must be enabled
8 * for this to make sense.
9 *
10 * @package TYPO3
11 * @subpackage tx_indexedsearch
12 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
13 */
14 class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin {
15
16 /**
17 * @todo Define visibility
18 */
19 public $prefixId = 'tx_indexedsearch';
20
21 // Same as class name
22 /**
23 * @todo Define visibility
24 */
25 public $scriptRelPath = 'pi/class.tx_indexedsearch.php';
26
27 // Path to this script relative to the extension dir.
28 /**
29 * @todo Define visibility
30 */
31 public $extKey = 'indexed_search';
32
33 // The extension key.
34 /**
35 * @todo Define visibility
36 */
37 public $join_pages = 0;
38
39 // See document for info about this flag...
40 /**
41 * @todo Define visibility
42 */
43 public $defaultResultNumber = 10;
44
45 /**
46 * @todo Define visibility
47 */
48 public $operator_translate_table = array(array('+', 'AND'), array('|', 'OR'), array('-', 'AND NOT'));
49
50 // Internal variable
51 /**
52 * @todo Define visibility
53 */
54 public $wholeSiteIdList = 0;
55
56 // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
57 // Internals:
58 /**
59 * @todo Define visibility
60 */
61 public $sWArr = array();
62
63 // Search Words and operators
64 /**
65 * @todo Define visibility
66 */
67 public $optValues = array();
68
69 // Selector box values for search configuration form
70 /**
71 * @todo Define visibility
72 */
73 public $firstRow = array();
74
75 // Will hold the first row in result - used to calculate relative hit-ratings.
76 /**
77 * @todo Define visibility
78 */
79 public $cache_path = array();
80
81 // Caching of page path
82 /**
83 * @todo Define visibility
84 */
85 public $cache_rl = array();
86
87 // Caching of root line data
88 /**
89 * @todo Define visibility
90 */
91 public $fe_groups_required = array();
92
93 // Required fe_groups memberships for display of a result.
94 /**
95 * @todo Define visibility
96 */
97 public $domain_records = array();
98
99 // Domain records (?)
100 /**
101 * @todo Define visibility
102 */
103 public $resultSections = array();
104
105 // Page tree sections for search result.
106 /**
107 * @todo Define visibility
108 */
109 public $external_parsers = array();
110
111 // External parser objects
112 /**
113 * @todo Define visibility
114 */
115 public $iconFileNameCache = array();
116
117 // Storage of icons....
118 /**
119 * @todo Define visibility
120 */
121 public $templateCode;
122
123 // Will hold the content of $conf['templateFile']
124 /**
125 * @todo Define visibility
126 */
127 public $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
128
129 /**
130 * @todo Define visibility
131 */
132 public $indexerConfig = array();
133
134 // Indexer configuration, coming from $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']
135 /**
136 * @todo Define visibility
137 */
138 public $enableMetaphoneSearch = FALSE;
139
140 /**
141 * @todo Define visibility
142 */
143 public $storeMetaphoneInfoAsWords;
144
145 /**
146 * Lexer object
147 *
148 * @var \TYPO3\CMS\IndexedSearch\Lexer
149 * @todo Define visibility
150 */
151 public $lexerObj;
152
153 const WILDCARD_LEFT = 1;
154 const WILDCARD_RIGHT = 2;
155 /**
156 * Main function, called from TypoScript as a USER_INT object.
157 *
158 * @param string Content input, ignore (just put blank string)
159 * @param array TypoScript configuration of the plugin!
160 * @return string HTML code for the search form / result display.
161 * @todo Define visibility
162 */
163 public function main($content, $conf) {
164 // Initialize:
165 $this->conf = $conf;
166 $this->pi_loadLL();
167 $this->pi_setPiVarDefaults();
168 // Initialize:
169 $this->initialize();
170 // Do search:
171 // If there were any search words entered...
172 if (is_array($this->sWArr)) {
173 $content = $this->doSearch($this->sWArr);
174 }
175 // Finally compile all the content, form, messages and results:
176 $content = ($this->makeSearchForm($this->optValues) . $this->printRules()) . $content;
177 return $this->pi_wrapInBaseClass($content);
178 }
179
180 /**
181 * Initialize internal variables, especially selector box values for the search form and search words
182 *
183 * @return void
184 * @todo Define visibility
185 */
186 public function initialize() {
187 global $TYPO3_CONF_VARS;
188 // Indexer configuration from Extension Manager interface:
189 $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']);
190 $this->enableMetaphoneSearch = $this->indexerConfig['enableMetaphoneSearch'] ? TRUE : FALSE;
191 $this->storeMetaphoneInfoAsWords = \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_words') ? FALSE : TRUE;
192 // Initialize external document parsers for icon display and other soft operations
193 if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'])) {
194 foreach ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
195 $this->external_parsers[$extension] = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($_objRef);
196 // Init parser and if it returns FALSE, unset its entry again:
197 if (!$this->external_parsers[$extension]->softInit($extension)) {
198 unset($this->external_parsers[$extension]);
199 }
200 }
201 }
202 // Init lexer (used to post-processing of search words)
203 $lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ? $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] : 'EXT:indexed_search/Classes/Lexer.php:&TYPO3\\CMS\\IndexedSearch\\Lexer';
204 $this->lexerObj = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($lexerObjRef);
205 // If "_sections" is set, this value overrides any existing value.
206 if ($this->piVars['_sections']) {
207 $this->piVars['sections'] = $this->piVars['_sections'];
208 }
209 // If "_sections" is set, this value overrides any existing value.
210 if ($this->piVars['_freeIndexUid'] !== '_') {
211 $this->piVars['freeIndexUid'] = $this->piVars['_freeIndexUid'];
212 }
213 // Add previous search words to current
214 if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev']) {
215 $this->piVars['sword'] = (trim($this->piVars['sword_prev']) . ' ') . $this->piVars['sword'];
216 }
217 $this->piVars['results'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->piVars['results'], 1, 100000, $this->defaultResultNumber);
218 // Selector-box values defined here:
219 $this->optValues = array(
220 'type' => array(
221 '0' => $this->pi_getLL('opt_type_0'),
222 '1' => $this->pi_getLL('opt_type_1'),
223 '2' => $this->pi_getLL('opt_type_2'),
224 '3' => $this->pi_getLL('opt_type_3'),
225 '10' => $this->pi_getLL('opt_type_10'),
226 '20' => $this->pi_getLL('opt_type_20')
227 ),
228 'defOp' => array(
229 '0' => $this->pi_getLL('opt_defOp_0'),
230 '1' => $this->pi_getLL('opt_defOp_1')
231 ),
232 'sections' => array(
233 '0' => $this->pi_getLL('opt_sections_0'),
234 '-1' => $this->pi_getLL('opt_sections_-1'),
235 '-2' => $this->pi_getLL('opt_sections_-2'),
236 '-3' => $this->pi_getLL('opt_sections_-3')
237 ),
238 'freeIndexUid' => array(
239 '-1' => $this->pi_getLL('opt_freeIndexUid_-1'),
240 '-2' => $this->pi_getLL('opt_freeIndexUid_-2'),
241 '0' => $this->pi_getLL('opt_freeIndexUid_0')
242 ),
243 'media' => array(
244 '-1' => $this->pi_getLL('opt_media_-1'),
245 '0' => $this->pi_getLL('opt_media_0'),
246 '-2' => $this->pi_getLL('opt_media_-2')
247 ),
248 'order' => array(
249 'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
250 'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
251 'rank_first' => $this->pi_getLL('opt_order_rank_first'),
252 'rank_count' => $this->pi_getLL('opt_order_rank_count'),
253 'mtime' => $this->pi_getLL('opt_order_mtime'),
254 'title' => $this->pi_getLL('opt_order_title'),
255 'crdate' => $this->pi_getLL('opt_order_crdate')
256 ),
257 'group' => array(
258 'sections' => $this->pi_getLL('opt_group_sections'),
259 'flat' => $this->pi_getLL('opt_group_flat')
260 ),
261 'lang' => array(
262 -1 => $this->pi_getLL('opt_lang_-1'),
263 0 => $this->pi_getLL('opt_lang_0')
264 ),
265 'desc' => array(
266 '0' => $this->pi_getLL('opt_desc_0'),
267 '1' => $this->pi_getLL('opt_desc_1')
268 ),
269 'results' => array(
270 '10' => '10',
271 '20' => '20',
272 '50' => '50',
273 '100' => '100'
274 )
275 );
276 // Remove this option if metaphone search is disabled)
277 if (!$this->enableMetaphoneSearch) {
278 unset($this->optValues['type']['10']);
279 }
280 // Free Index Uid:
281 if ($this->conf['search.']['defaultFreeIndexUidList']) {
282 $uidList = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->conf['search.']['defaultFreeIndexUidList']);
283 $indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title', 'index_config', (('uid IN (' . implode(',', $uidList)) . ')') . $this->cObj->enableFields('index_config'), '', '', '', 'uid');
284 foreach ($uidList as $uidValue) {
285 if (is_array($indexCfgRecords[$uidValue])) {
286 $this->optValues['freeIndexUid'][$uidValue] = $indexCfgRecords[$uidValue]['title'];
287 }
288 }
289 }
290 // Should we use join_pages instead of long lists of uids?
291 if ($this->conf['search.']['skipExtendToSubpagesChecking']) {
292 $this->join_pages = 1;
293 }
294 // Add media to search in:
295 if (strlen(trim($this->conf['search.']['mediaList']))) {
296 $mediaList = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->conf['search.']['mediaList'], 1));
297 }
298 foreach ($this->external_parsers as $extension => $obj) {
299 // Skip unwanted extensions
300 if ($mediaList && !\TYPO3\CMS\Core\Utility\GeneralUtility::inList($mediaList, $extension)) {
301 continue;
302 }
303 if ($name = $obj->searchTypeMediaTitle($extension)) {
304 $this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_' . $extension, $name);
305 }
306 }
307 // Add operators for various languages
308 // Converts the operators to UTF-8 and lowercase
309 $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');
310 $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');
311 $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');
312 // This is the id of the site root. This value may be a commalist of integer (prepared for this)
313 $this->wholeSiteIdList = intval($GLOBALS['TSFE']->config['rootLine'][0]['uid']);
314 // Creating levels for section menu:
315 // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
316 if ($this->conf['show.']['L1sections']) {
317 $firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
318 foreach ($firstLevelMenu as $optionName => $mR) {
319 if (!$mR['nav_hide']) {
320 $this->optValues['sections']['rl1_' . $mR['uid']] = trim(($this->pi_getLL('opt_RL1') . ' ') . $mR['title']);
321 if ($this->conf['show.']['L2sections']) {
322 $secondLevelMenu = $this->getMenu($mR['uid']);
323 foreach ($secondLevelMenu as $kk2 => $mR2) {
324 if (!$mR2['nav_hide']) {
325 $this->optValues['sections']['rl2_' . $mR2['uid']] = trim(($this->pi_getLL('opt_RL2') . ' ') . $mR2['title']);
326 } else {
327 unset($secondLevelMenu[$kk2]);
328 }
329 }
330 $this->optValues['sections']['rl2_' . implode(',', array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
331 }
332 } else {
333 unset($firstLevelMenu[$optionName]);
334 }
335 }
336 $this->optValues['sections']['rl1_' . implode(',', array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
337 }
338 // 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.
339 // 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...
340 if ($this->conf['search.']['rootPidList']) {
341 $this->wholeSiteIdList = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->conf['search.']['rootPidList']));
342 }
343 // Load the template
344 $this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
345 // Add search languages:
346 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1' . $this->cObj->enableFields('sys_language'));
347 while (FALSE !== ($data = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
348 $this->optValues['lang'][$data['uid']] = $data['title'];
349 }
350 $GLOBALS['TYPO3_DB']->sql_free_result($res);
351 // Calling hook for modification of initialized content
352 if ($hookObj = $this->hookRequest('initialize_postProc')) {
353 $hookObj->initialize_postProc();
354 }
355 // Default values set:
356 // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
357 foreach ($this->optValues as $optionName => $optionValue) {
358 if (!isset($this->piVars[$optionName])) {
359 reset($optionValue);
360 $this->piVars[$optionName] = key($optionValue);
361 }
362 }
363 // Blind selectors:
364 if (is_array($this->conf['blind.'])) {
365 foreach ($this->conf['blind.'] as $optionName => $optionValue) {
366 if (is_array($optionValue)) {
367 foreach ($optionValue as $optionValueSubKey => $optionValueSubValue) {
368 if ((!is_array($optionValueSubValue) && $optionValueSubValue) && is_array($this->optValues[substr($optionName, 0, -1)])) {
369 unset($this->optValues[substr($optionName, 0, -1)][$optionValueSubKey]);
370 }
371 }
372 } elseif ($optionValue) {
373 // If value is not set, unset the option array
374 unset($this->optValues[$optionName]);
375 }
376 }
377 }
378 // This gets the search-words into the $sWArr:
379 $this->sWArr = $this->getSearchWords($this->piVars['defOp']);
380 }
381
382 /**
383 * Splits the search word input into an array where each word is represented by an array with key "sword" holding the search word and key "oper" holding the SQL operator (eg. AND, OR)
384 *
385 * Only words with 2 or more characters are accepted
386 * Max 200 chars total
387 * Space is used to split words, "" can be used search for a whole string
388 * AND, OR and NOT are prefix words, overruling the default operator
389 * +/|/- equals AND, OR and NOT as operators.
390 * All search words are converted to lowercase.
391 *
392 * $defOp is the default operator. 1=OR, 0=AND
393 *
394 * @param boolean If TRUE, the default operator will be OR, not AND
395 * @return array Returns array with search words if any found
396 * @todo Define visibility
397 */
398 public function getSearchWords($defOp) {
399 // 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!)
400 $inSW = substr($this->piVars['sword'], 0, 200);
401 // Convert to UTF-8 + conv. entities (was also converted during indexing!)
402 $inSW = $GLOBALS['TSFE']->csConvObj->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset);
403 $inSW = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($inSW, TRUE);
404 $sWordArray = FALSE;
405 if ($hookObj = $this->hookRequest('getSearchWords')) {
406 $sWordArray = $hookObj->getSearchWords_splitSWords($inSW, $defOp);
407 } else {
408 if ($this->piVars['type'] == 20) {
409 // type = Sentence
410 $sWordArray = array(array('sword' => trim($inSW), 'oper' => 'AND'));
411 } else {
412 $search = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\SearchResultContentObject');
413 $search->default_operator = $defOp == 1 ? 'OR' : 'AND';
414 $search->operator_translate_table = $this->operator_translate_table;
415 $search->register_and_explode_search_string($inSW);
416 if (is_array($search->sword_array)) {
417 $sWordArray = $this->procSearchWordsByLexer($search->sword_array);
418 }
419 }
420 }
421 return $sWordArray;
422 }
423
424 /**
425 * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
426 * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
427 *
428 * @param array Search word array
429 * @return array Search word array, processed through lexer
430 * @todo Define visibility
431 */
432 public function procSearchWordsByLexer($SWArr) {
433 // Init output variable:
434 $newSWArr = array();
435 // Traverse the search word array:
436 foreach ($SWArr as $wordDef) {
437 if (!strstr($wordDef['sword'], ' ')) {
438 // No space in word (otherwise it might be a sentense in quotes like "there is").
439 // Split the search word by lexer:
440 $res = $this->lexerObj->split2Words($wordDef['sword']);
441 // Traverse lexer result and add all words again:
442 foreach ($res as $word) {
443 $newSWArr[] = array('sword' => $word, 'oper' => $wordDef['oper']);
444 }
445 } else {
446 $newSWArr[] = $wordDef;
447 }
448 }
449 // Return result:
450 return $newSWArr;
451 }
452
453 /*****************************
454 *
455 * Main functions
456 *
457 *****************************/
458 /**
459 * Performs the search, the display and writing stats
460 *
461 * @param array Search words in array, see ->getSearchWords() for details
462 * @return string HTML for result display.
463 * @todo Define visibility
464 */
465 public function doSearch($sWArr) {
466 // Find free index uid:
467 $freeIndexUid = $this->piVars['freeIndexUid'];
468 if ($freeIndexUid == -2) {
469 $freeIndexUid = $this->conf['search.']['defaultFreeIndexUidList'];
470 }
471 $indexCfgs = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $freeIndexUid);
472 $accumulatedContent = '';
473 foreach ($indexCfgs as $freeIndexUid) {
474 // Get result rows:
475 $pt1 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
476 if ($hookObj = $this->hookRequest('getResultRows')) {
477 $resData = $hookObj->getResultRows($sWArr, $freeIndexUid);
478 } else {
479 $resData = $this->getResultRows($sWArr, $freeIndexUid);
480 }
481 // Display search results:
482 $pt2 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
483 if ($hookObj = $this->hookRequest('getDisplayResults')) {
484 $content = $hookObj->getDisplayResults($sWArr, $resData, $freeIndexUid);
485 } else {
486 $content = $this->getDisplayResults($sWArr, $resData, $freeIndexUid);
487 }
488 $pt3 = \TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds();
489 // Create header if we are searching more than one indexing configuration:
490 if (count($indexCfgs) > 1) {
491 if ($freeIndexUid > 0) {
492 $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('title', 'index_config', ('uid=' . intval($freeIndexUid)) . $this->cObj->enableFields('index_config'));
493 $titleString = $indexCfgRec['title'];
494 } else {
495 $titleString = $this->pi_getLL('opt_freeIndexUid_header_' . $freeIndexUid);
496 }
497 $content = (('<h1 class="tx-indexedsearch-category">' . htmlspecialchars($titleString)) . '</h1>') . $content;
498 }
499 $accumulatedContent .= $content;
500 }
501 // Write search statistics
502 $this->writeSearchStat($sWArr, $resData['count'], array($pt1, $pt2, $pt3));
503 // Return content:
504 return $accumulatedContent;
505 }
506
507 /**
508 * Get search result rows / data from database. Returned as data in array.
509 *
510 * @param array $searchWordArray Search word array
511 * @param int Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
512 * @return array False if no result, otherwise an array with keys for first row, result rows and total number of results found.
513 * @todo Define visibility
514 */
515 public function getResultRows($searchWordArray, $freeIndexUid = -1) {
516 // Getting SQL result pointer. This fetches ALL results (1,000,000 if found)
517 $GLOBALS['TT']->push('Searching result');
518 if ($hookObj =& $this->hookRequest('getResultRows_SQLpointer')) {
519 $res = $hookObj->getResultRows_SQLpointer($searchWordArray, $freeIndexUid);
520 } else {
521 $res = $this->getResultRows_SQLpointer($searchWordArray, $freeIndexUid);
522 }
523 $GLOBALS['TT']->pull();
524 // Organize and process result:
525 $result = FALSE;
526 if ($res) {
527 $totalSearchResultCount = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
528 // Total search-result count
529 $currentPageNumber = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->piVars['pointer'], 0, floor($totalSearchResultCount / $this->piVars['results']));
530 // The pointer is set to the result page that is currently being viewed
531 // Initialize result accumulation variables:
532 $positionInSearchResults = 0;
533 $groupingPhashes = array();
534 // Used for filtering out duplicates
535 $groupingChashes = array();
536 // Used for filtering out duplicates BASED ON cHash
537 $firstRow = array();
538 // Will hold the first row in result - used to calculate relative hit-ratings.
539 $resultRows = array();
540 // Will hold the results rows for display.
541 // Should we continue counting and checking of results even if
542 // we are sure they are not displayed in this request?
543 // This will slow down your page rendering, but it allows
544 // precise search result counters.
545 $calculateExactCount = (bool) $this->conf['search.']['exactCount'];
546 $lastResultNumberOnPreviousPage = $currentPageNumber * $this->piVars['results'];
547 $firstResultNumberOnNextPage = ($currentPageNumber + 1) * $this->piVars['results'];
548 $lastResultNumberToAnalyze = ($currentPageNumber + 1) * $this->piVars['results'] + $this->piVars['results'];
549 // Now, traverse result and put the rows to be displayed into an array
550 // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
551 while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
552 if (!$this->checkExistance($row)) {
553 // Check if the record is still available or if it has been deleted meanwhile.
554 // Currently this works for files only, since extending it to content elements would cause a lot of overhead...
555 // Otherwise, skip the row.
556 $totalSearchResultCount--;
557 continue;
558 }
559 // Set first row:
560 if ($positionInSearchResults === 0) {
561 $firstRow = $row;
562 }
563 $row['show_resume'] = $this->checkResume($row);
564 // Tells whether we can link directly to a document or not (depends on possible right problems)
565 $phashGr = !in_array($row['phash_grouping'], $groupingPhashes);
566 $chashGr = !in_array((($row['contentHash'] . '.') . $row['data_page_id']), $groupingChashes);
567 if ($phashGr && $chashGr) {
568 if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords']) {
569 // Only if the resume may be shown are we going to filter out duplicates...
570 if (!$this->multiplePagesType($row['item_type'])) {
571 // Only on documents which are not multiple pages documents
572 $groupingPhashes[] = $row['phash_grouping'];
573 }
574 $groupingChashes[] = ($row['contentHash'] . '.') . $row['data_page_id'];
575 $positionInSearchResults++;
576 // Check if we are inside result range for current page
577 if ($positionInSearchResults > $lastResultNumberOnPreviousPage && $positionInSearchResults <= $lastResultNumberToAnalyze) {
578 // Collect results to display
579 $row['result_number'] = $positionInSearchResults;
580 $resultRows[] = $row;
581 // This may lead to a problem: If the result
582 // check is not stopped here, the search will
583 // take longer. However the result counter
584 // will not filter out grouped cHashes/pHashes
585 // that were not processed yet. You can change
586 // this behavior using the "search.exactCount"
587 // property (see above).
588 $nextResultPosition = $positionInSearchResults + 1;
589 if (!$calculateExactCount && $nextResultPosition > $firstResultNumberOnNextPage) {
590 break;
591 }
592 }
593 } else {
594 // Skip this row if the user cannot view it (missing permission)
595 $totalSearchResultCount--;
596 }
597 } else {
598 // For each time a phash_grouping document is found
599 // (which is thus not displayed) the search-result count
600 // is reduced, so that it matches the number of rows displayed.
601 $totalSearchResultCount--;
602 }
603 }
604 $GLOBALS['TYPO3_DB']->sql_free_result($res);
605 $result = array(
606 'resultRows' => $resultRows,
607 'firstRow' => $firstRow,
608 'count' => $totalSearchResultCount
609 );
610 }
611 return $result;
612 }
613
614 /**
615 * Gets a SQL result pointer to traverse for the search records.
616 *
617 * @param array Search words
618 * @param integer Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
619 * @return pointer
620 * @todo Define visibility
621 */
622 public function getResultRows_SQLpointer($sWArr, $freeIndexUid = -1) {
623 // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
624 $list = $this->getPhashList($sWArr);
625 // Perform SQL Search / collection of result rows array:
626 if ($list) {
627 // Do the search:
628 $GLOBALS['TT']->push('execFinalQuery');
629 $res = $this->execFinalQuery($list, $freeIndexUid);
630 $GLOBALS['TT']->pull();
631 return $res;
632 } else {
633 return FALSE;
634 }
635 }
636
637 /**
638 * Compiles the HTML display of the incoming array of result rows.
639 *
640 * @param array Search words array (for display of text describing what was searched for)
641 * @param array Array with result rows, count, first row.
642 * @param integer Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
643 * @return string HTML content to display result.
644 * @todo Define visibility
645 */
646 public function getDisplayResults($sWArr, $resData, $freeIndexUid = -1) {
647 // Perform display of result rows array:
648 if ($resData) {
649 $GLOBALS['TT']->push('Display Final result');
650 // Set first selected row (for calculation of ranking later)
651 $this->firstRow = $resData['firstRow'];
652 // Result display here:
653 $rowcontent = '';
654 $rowcontent .= $this->compileResult($resData['resultRows'], $freeIndexUid);
655 // Browsing box:
656 if ($resData['count']) {
657 $this->internal['res_count'] = $resData['count'];
658 $this->internal['results_at_a_time'] = $this->piVars['results'];
659 $this->internal['maxPages'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->conf['search.']['page_links'], 1, 100, 10);
660 $addString = ($resData['count'] && $this->piVars['group'] == 'sections') && $freeIndexUid <= 0 ? ' ' . sprintf($this->pi_getLL((count($this->resultSections) > 1 ? 'inNsections' : 'inNsection')), count($this->resultSections)) : '';
661 $browseBox1 = $this->pi_list_browseresults(1, $addString, $this->printResultSectionLinks(), $freeIndexUid);
662 $browseBox2 = $this->pi_list_browseresults(0, '', '', $freeIndexUid);
663 }
664 // Browsing nav, bottom.
665 if ($resData['count']) {
666 $content = ($browseBox1 . $rowcontent) . $browseBox2;
667 } else {
668 $content = ((('<p' . $this->pi_classParam('noresults')) . '>') . $this->pi_getLL('noResults', '', 1)) . '</p>';
669 }
670 $GLOBALS['TT']->pull();
671 } else {
672 $content .= ((('<p' . $this->pi_classParam('noresults')) . '>') . $this->pi_getLL('noResults', '', 1)) . '</p>';
673 }
674 // Print a message telling which words we searched for, and in which sections etc.
675 $what = $this->tellUsWhatIsSeachedFor($sWArr) . (substr($this->piVars['sections'], 0, 2) == 'rl' ? (((' ' . $this->pi_getLL('inSection', '', 1)) . ' "') . substr($this->getPathFromPageId(substr($this->piVars['sections'], 4)), 1)) . '"' : '');
676 $what = ((('<div' . $this->pi_classParam('whatis')) . '>') . $this->cObj->stdWrap($what, $this->conf['whatis_stdWrap.'])) . '</div>';
677 $content = $what . $content;
678 // Return content:
679 return $content;
680 }
681
682 /**
683 * Takes the array with resultrows as input and returns the result-HTML-code
684 * Takes the "group" var into account: Makes a "section" or "flat" display.
685 *
686 * @param array Result rows
687 * @param integer Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
688 * @return string HTML
689 * @todo Define visibility
690 */
691 public function compileResult($resultRows, $freeIndexUid = -1) {
692 $content = '';
693 // Transfer result rows to new variable, performing some mapping of sub-results etc.
694 $newResultRows = array();
695 foreach ($resultRows as $row) {
696 $id = md5($row['phash_grouping']);
697 if (is_array($newResultRows[$id])) {
698 if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) {
699 // swapping:
700 // Remove old
701 $subrows = $newResultRows[$id]['_sub'];
702 unset($newResultRows[$id]['_sub']);
703 $subrows[] = $newResultRows[$id];
704 // Insert new:
705 $newResultRows[$id] = $row;
706 $newResultRows[$id]['_sub'] = $subrows;
707 } else {
708 $newResultRows[$id]['_sub'][] = $row;
709 }
710 } else {
711 $newResultRows[$id] = $row;
712 }
713 }
714 $resultRows = $newResultRows;
715 $this->resultSections = array();
716 if ($freeIndexUid <= 0) {
717 switch ($this->piVars['group']) {
718 case 'sections':
719 $rl2flag = substr($this->piVars['sections'], 0, 2) == 'rl';
720 $sections = array();
721 foreach ($resultRows as $row) {
722 $id = (($row['rl0'] . '-') . $row['rl1']) . ($rl2flag ? '-' . $row['rl2'] : '');
723 $sections[$id][] = $row;
724 }
725 $this->resultSections = array();
726 foreach ($sections as $id => $resultRows) {
727 $rlParts = explode('-', $id);
728 $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1] ? $rlParts[1] : $rlParts[0]);
729 $theRLid = $rlParts[2] ? 'rl2_' . $rlParts[2] : ($rlParts[1] ? 'rl1_' . $rlParts[1] : '0');
730 $sectionName = $this->getPathFromPageId($theId);
731 if ($sectionName[0] == '/') {
732 $sectionName = substr($sectionName, 1);
733 }
734 if (!trim($sectionName)) {
735 $sectionTitleLinked = $this->pi_getLL('unnamedSection', '', 1) . ':';
736 } else {
737 $onclick = ((((((('document.' . $this->prefixId) . '[\'') . $this->prefixId) . '[_sections]\'].value=\'') . $theRLid) . '\';document.') . $this->prefixId) . '.submit();return false;';
738 $sectionTitleLinked = ((('<a href="#" onclick="' . htmlspecialchars($onclick)) . '">') . htmlspecialchars($sectionName)) . ':</a>';
739 }
740 $this->resultSections[$id] = array($sectionName, count($resultRows));
741 // Add content header:
742 $content .= $this->makeSectionHeader($id, $sectionTitleLinked, count($resultRows));
743 // Render result rows:
744 foreach ($resultRows as $row) {
745 $content .= $this->printResultRow($row);
746 }
747 }
748 break;
749 default:
750 // flat:
751 foreach ($resultRows as $row) {
752 $content .= $this->printResultRow($row);
753 }
754 break;
755 }
756 } else {
757 foreach ($resultRows as $row) {
758 $content .= $this->printResultRow($row);
759 }
760 }
761 return ((('<div' . $this->pi_classParam('res')) . '>') . $content) . '</div>';
762 }
763
764 /***********************************
765 *
766 * Searching functions (SQL)
767 *
768 ***********************************/
769 /**
770 * Returns a COMPLETE list of phash-integers matching the search-result composed of the search-words in the sWArr array.
771 * The list of phash integers are unsorted and should be used for subsequent selection of index_phash records for display of the result.
772 *
773 * @param array Search word array
774 * @return string List of integers
775 * @todo Define visibility
776 */
777 public function getPhashList($sWArr) {
778 // Initialize variables:
779 $c = 0;
780 $totalHashList = array();
781 // This array accumulates the phash-values
782 // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
783 foreach ($sWArr as $k => $v) {
784 // Making the query for a single search word based on the search-type
785 $sWord = $v['sword'];
786 $theType = (string) $this->piVars['type'];
787 if (strstr($sWord, ' ')) {
788 // If there are spaces in the search-word, make a full text search instead.
789 $theType = 20;
790 }
791 $GLOBALS['TT']->push((('SearchWord "' . $sWord) . '" - $theType=') . $theType);
792 // Perform search for word:
793 switch ($theType) {
794 case '1':
795 // Part of word
796 $res = $this->searchWord($sWord, self::WILDCARD_LEFT | self::WILDCARD_RIGHT);
797 break;
798 case '2':
799 // First part of word
800 $res = $this->searchWord($sWord, self::WILDCARD_RIGHT);
801 break;
802 case '3':
803 // Last part of word
804 $res = $this->searchWord($sWord, self::WILDCARD_LEFT);
805 break;
806 case '10':
807 // Sounds like
808 /**
809 * Indexer object
810 *
811 * @var \TYPO3\CMS\IndexedSearch\Indexer
812 */
813 // Initialize the indexer-class
814 $indexerObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\IndexedSearch\\Indexer');
815 // Perform metaphone search
816 $res = $this->searchMetaphone($indexerObj->metaphone($sWord, $this->storeMetaphoneInfoAsWords));
817 unset($indexerObj);
818 break;
819 case '20':
820 // Sentence
821 $res = $this->searchSentence($sWord);
822 $this->piVars['order'] = 'mtime';
823 // If there is a fulltext search for a sentence there is a likeliness that sorting cannot be done by the rankings from the rel-table (because no relations will exist for the sentence in the word-table). So therefore mtime is used instead. It is not required, but otherwise some hits may be left out.
824 break;
825 default:
826 // Distinct word
827 $res = $this->searchDistinct($sWord);
828 break;
829 }
830 // If there was a query to do, then select all phash-integers which resulted from this.
831 if ($res) {
832 // Get phash list by searching for it:
833 $phashList = array();
834 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
835 $phashList[] = $row['phash'];
836 }
837 $GLOBALS['TYPO3_DB']->sql_free_result($res);
838 // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
839 if ($c) {
840 switch ($v['oper']) {
841 case 'OR':
842 $totalHashList = array_unique(array_merge($phashList, $totalHashList));
843 break;
844 case 'AND NOT':
845 $totalHashList = array_diff($totalHashList, $phashList);
846 break;
847 default:
848 // AND...
849 $totalHashList = array_intersect($totalHashList, $phashList);
850 break;
851 }
852 } else {
853 $totalHashList = $phashList;
854 }
855 }
856 $GLOBALS['TT']->pull();
857 $c++;
858 }
859 return implode(',', $totalHashList);
860 }
861
862 /**
863 * Returns a query which selects the search-word from the word/rel tables.
864 *
865 * @param string WHERE clause selecting the word from phash
866 * @param string Additional AND clause in the end of the query.
867 * @return pointer SQL result pointer
868 * @todo Define visibility
869 */
870 public function execPHashListQuery($wordSel, $plusQ = '') {
871 return $GLOBALS['TYPO3_DB']->exec_SELECTquery('IR.phash', 'index_words IW,
872 index_rel IR,
873 index_section ISEC', ((($wordSel . '
874 AND IW.wid=IR.wid
875 AND ISEC.phash = IR.phash
876 ') . $this->sectionTableWhere()) . '
877 ') . $plusQ, 'IR.phash');
878 }
879
880 /**
881 * Search for a word
882 *
883 * @param string $sWord Word to search for
884 * @param integer $mode Bit-field which can contain WILDCARD_LEFT and/or WILDCARD_RIGHT
885 * @return pointer SQL result pointer
886 * @todo Define visibility
887 */
888 public function searchWord($sWord, $mode) {
889 $wildcard_left = $mode & self::WILDCARD_LEFT ? '%' : '';
890 $wildcard_right = $mode & self::WILDCARD_RIGHT ? '%' : '';
891 $wSel = ((('IW.baseword LIKE \'' . $wildcard_left) . $GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words')) . $wildcard_right) . '\'';
892 $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
893 return $res;
894 }
895
896 /**
897 * Search for one distinct word
898 *
899 * @param string $sWord Word to search for
900 * @return pointer SQL result pointer
901 * @todo Define visibility
902 */
903 public function searchDistinct($sWord) {
904 $wSel = 'IW.wid=' . \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::md5inthash($sWord);
905 $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
906 return $res;
907 }
908
909 /**
910 * Search for a sentence
911 *
912 * @param string $sSentence Sentence to search for
913 * @return pointer SQL result pointer
914 * @todo Define visibility
915 */
916 public function searchSentence($sSentence) {
917 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.phash', 'index_section ISEC, index_fulltext IFT', (('IFT.fulltextdata LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($sSentence, 'index_fulltext')) . '%\' AND
918 ISEC.phash = IFT.phash
919 ') . $this->sectionTableWhere(), 'ISEC.phash');
920 return $res;
921 }
922
923 /**
924 * Search for a metaphone word
925 *
926 * @param string $sWord Word to search for
927 * @return pointer SQL result pointer
928 * @todo Define visibility
929 */
930 public function searchMetaphone($sWord) {
931 $wSel = 'IW.metaphone=' . $sWord;
932 $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
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 * @todo Define visibility
940 */
941 public function sectionTableWhere() {
942 $out = $this->wholeSiteIdList < 0 ? '' : (' AND ISEC.rl0 IN (' . $this->wholeSiteIdList) . ')';
943 $match = '';
944 if (substr($this->piVars['sections'], 0, 4) == 'rl1_') {
945 $list = implode(',', \TYPO3\CMS\Core\Utility\GeneralUtility::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(',', \TYPO3\CMS\Core\Utility\GeneralUtility::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(',', \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', substr($this->piVars['sections'], strlen($fieldName) + 1)));
957 $out .= (((' AND ISEC.' . $fieldName) . ' IN (') . $list) . ')';
958 $match = TRUE;
959 break;
960 }
961 }
962 }
963 // If no match above, test the static types:
964 if (!$match) {
965 switch ((string) $this->piVars['sections']) {
966 case '-1':
967 // '-1' => 'Only this page',
968 $out .= ' AND ISEC.page_id=' . $GLOBALS['TSFE']->id;
969 break;
970 case '-2':
971 // '-2' => 'Top + level 1',
972 $out .= ' AND ISEC.rl2=0';
973 break;
974 case '-3':
975 // '-3' => 'Level 2 and out',
976 $out .= ' AND ISEC.rl2>0';
977 break;
978 }
979 }
980 return $out;
981 }
982
983 /**
984 * Returns AND statement for selection of media type
985 *
986 * @return string AND statement for selection of media type
987 * @todo Define visibility
988 */
989 public function mediaTypeWhere() {
990 switch ((string) $this->piVars['media']) {
991 case '0':
992 // '0' => 'Kun TYPO3 sider',
993 $out = ' AND IP.item_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');
994 break;
995 case '-2':
996 // All external documents
997 $out = ' AND IP.item_type<>' . $GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');
998 break;
999 case '-1':
1000 // All content
1001 $out = '';
1002 break;
1003 default:
1004 $out = ' AND IP.item_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['media'], 'index_phash');
1005 }
1006 return $out;
1007 }
1008
1009 /**
1010 * Returns AND statement for selection of langauge
1011 *
1012 * @return string AND statement for selection of langauge
1013 * @todo Define visibility
1014 */
1015 public function languageWhere() {
1016 if ($this->piVars['lang'] >= 0) {
1017 // -1 is the same as ALL language.
1018 return 'AND IP.sys_language_uid=' . intval($this->piVars['lang']);
1019 }
1020 }
1021
1022 /**
1023 * Where-clause for free index-uid value.
1024 *
1025 * @param integer Free Index UID value to limit search to.
1026 * @return string WHERE SQL clause part.
1027 * @todo Define visibility
1028 */
1029 public function freeIndexUidWhere($freeIndexUid) {
1030 if ($freeIndexUid >= 0) {
1031 // First, look if the freeIndexUid is a meta configuration:
1032 $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('indexcfgs', 'index_config', ('type=5 AND uid=' . intval($freeIndexUid)) . $this->cObj->enableFields('index_config'));
1033 if (is_array($indexCfgRec)) {
1034 $refs = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $indexCfgRec['indexcfgs']);
1035 $list = array(-99);
1036 // Default value to protect against empty array.
1037 foreach ($refs as $ref) {
1038 list($table, $uid) = \TYPO3\CMS\Core\Utility\GeneralUtility::revExplode('_', $ref, 2);
1039 switch ($table) {
1040 case 'index_config':
1041 $idxRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', 'index_config', ('uid=' . intval($uid)) . $this->cObj->enableFields('index_config'));
1042 if ($idxRec) {
1043 $list[] = $uid;
1044 }
1045 break;
1046 case 'pages':
1047 $indexCfgRecordsFromPid = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', 'index_config', ('pid=' . intval($uid)) . $this->cObj->enableFields('index_config'));
1048 foreach ($indexCfgRecordsFromPid as $idxRec) {
1049 $list[] = $idxRec['uid'];
1050 }
1051 break;
1052 }
1053 }
1054 $list = array_unique($list);
1055 } else {
1056 $list = array(intval($freeIndexUid));
1057 }
1058 return (' AND IP.freeIndexUid IN (' . implode(',', $list)) . ')';
1059 }
1060 }
1061
1062 /**
1063 * Execute final query, based on phash integer list. The main point is sorting the result in the right order.
1064 *
1065 * @param string List of phash integers which match the search.
1066 * @param integer Pointer to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
1067 * @return pointer Query result pointer
1068 * @todo Define visibility
1069 */
1070 public function execFinalQuery($list, $freeIndexUid = -1) {
1071 // Setting up methods of filtering results based on page types, access, etc.
1072 $page_join = '';
1073 $page_where = '';
1074 // Indexing configuration clause:
1075 $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
1076 // Calling hook for alternative creation of page ID list
1077 if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
1078 $page_where = $hookObj->execFinalQuery_idList($list);
1079 } elseif ($this->join_pages) {
1080 // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
1081 $page_join = ',
1082 pages';
1083 $page_where = ('pages.uid = ISEC.page_id
1084 ' . $this->cObj->enableFields('pages')) . '
1085 AND pages.no_search=0
1086 AND pages.doktype<200
1087 ';
1088 } elseif ($this->wholeSiteIdList >= 0) {
1089 // 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!
1090 $siteIdNumbers = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->wholeSiteIdList);
1091 $id_list = array();
1092 foreach ($siteIdNumbers as $rootId) {
1093 $id_list[] = $this->cObj->getTreeList($rootId, 9999, 0, 0, '', '') . $rootId;
1094 }
1095 $page_where = (' ISEC.page_id IN (' . implode(',', $id_list)) . ')';
1096 } else {
1097 // Disable everything... (select all)
1098 $page_where = ' 1=1 ';
1099 }
1100 // 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.
1101 if (substr($this->piVars['order'], 0, 5) == 'rank_') {
1102 switch ($this->piVars['order']) {
1103 case 'rank_flag':
1104 // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1105 // The ordering is refined with the frequency sum as well.
1106 $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1107 $orderBy = (('order_val1' . $this->isDescending()) . ',order_val2') . $this->isDescending();
1108 break;
1109 case 'rank_first':
1110 // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1111 $grsel = 'AVG(IR.first) AS order_val';
1112 $orderBy = 'order_val' . $this->isDescending(1);
1113 break;
1114 case 'rank_count':
1115 // Number of words found
1116 $grsel = 'SUM(IR.count) AS order_val';
1117 $orderBy = 'order_val' . $this->isDescending();
1118 break;
1119 default:
1120 // Frequency sum. I'm not sure if this is the best way to do it (make a sum...). Or should it be the average?
1121 $grsel = 'SUM(IR.freq) AS order_val';
1122 $orderBy = 'order_val' . $this->isDescending();
1123 break;
1124 }
1125 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.*, IP.*, ' . $grsel, 'index_words IW,
1126 index_rel IR,
1127 index_section ISEC,
1128 index_phash IP' . $page_join, ((((((('IP.phash IN (' . $list) . ') ') . $this->mediaTypeWhere()) . ' ') . $this->languageWhere()) . $freeIndexUidClause) . '
1129 AND IW.wid=IR.wid
1130 AND ISEC.phash = IR.phash
1131 AND IP.phash = IR.phash
1132 AND ') . $page_where, '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', $orderBy);
1133 } else {
1134 // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1135 $orderBy = '';
1136 switch ((string) $this->piVars['order']) {
1137 case 'title':
1138 $orderBy = 'IP.item_title' . $this->isDescending();
1139 break;
1140 case 'crdate':
1141 $orderBy = 'IP.item_crdate' . $this->isDescending();
1142 break;
1143 case 'mtime':
1144 $orderBy = 'IP.item_mtime' . $this->isDescending();
1145 break;
1146 }
1147 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.*, IP.*', 'index_phash IP,index_section ISEC' . $page_join, ((((((('IP.phash IN (' . $list) . ') ') . $this->mediaTypeWhere()) . ' ') . $this->languageWhere()) . $freeIndexUidClause) . '
1148 AND IP.phash = ISEC.phash
1149 AND ') . $page_where, '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', $orderBy);
1150 }
1151 return $res;
1152 }
1153
1154 /**
1155 * Checking if the resume can be shown for the search result (depending on whether the rights are OK)
1156 * ? Should it also check for gr_list "0,-1"?
1157 *
1158 * @param array Result row array.
1159 * @return boolean Returns TRUE if resume can safely be shown
1160 * @todo Define visibility
1161 */
1162 public function checkResume($row) {
1163 // If the record is indexed by an indexing configuration, just show it.
1164 // At least this is needed for external URLs and files.
1165 // For records we might need to extend this - for instance block display if record is access restricted.
1166 if ($row['freeIndexUid']) {
1167 return TRUE;
1168 }
1169 // Evaluate regularly indexed pages based on item_type:
1170 if ($row['item_type']) {
1171 // External media:
1172 // For external media we will check the access of the parent page on which the media was linked from.
1173 // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1174 // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1175 // 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.
1176 if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1177 $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'));
1178 } else {
1179 $res = FALSE;
1180 }
1181 if ($res && $GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1182 return TRUE;
1183 } else {
1184 return FALSE;
1185 }
1186 } else {
1187 // Ordinary TYPO3 pages:
1188 if (strcmp($row['gr_list'], $GLOBALS['TSFE']->gr_list)) {
1189 // 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...
1190 if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1191 $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'));
1192 } else {
1193 $res = FALSE;
1194 }
1195 if ($res && $GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1196 return TRUE;
1197 } else {
1198 return FALSE;
1199 }
1200 } else {
1201 return TRUE;
1202 }
1203 }
1204 }
1205
1206 /**
1207 * Check if the record is still available or if it has been deleted meanwhile.
1208 * Currently this works for files only, since extending it to page content would cause a lot of overhead.
1209 *
1210 * @param array Result row array
1211 * @return boolean Returns TRUE if record is still available
1212 * @todo Define visibility
1213 */
1214 public function checkExistance($row) {
1215 $recordExists = TRUE;
1216 // Always expect that page content exists
1217 if ($row['item_type']) {
1218 // External media:
1219 if (!is_file($row['data_filename']) || !file_exists($row['data_filename'])) {
1220 $recordExists = FALSE;
1221 }
1222 }
1223 return $recordExists;
1224 }
1225
1226 /**
1227 * Returns "DESC" or "" depending on the settings of the incoming highest/lowest result order (piVars['desc']
1228 *
1229 * @param boolean If TRUE, inverse the order which is defined by piVars['desc']
1230 * @return string " DESC" or
1231 * @todo Define visibility
1232 */
1233 public function isDescending($inverse = FALSE) {
1234 $desc = $this->piVars['desc'];
1235 if ($inverse) {
1236 $desc = !$desc;
1237 }
1238 return !$desc ? ' DESC' : '';
1239 }
1240
1241 /**
1242 * Write statistics information to database for the search operation
1243 *
1244 * @param array Search Word array
1245 * @param integer Number of hits
1246 * @param integer Milliseconds the search took
1247 * @return void
1248 * @todo Define visibility
1249 */
1250 public function writeSearchStat($sWArr, $count, $pt) {
1251 $insertFields = array(
1252 'searchstring' => $this->piVars['sword'],
1253 'searchoptions' => serialize(array($this->piVars, $sWArr, $pt)),
1254 'feuser_id' => intval($this->fe_user->user['uid']),
1255 // fe_user id, integer
1256 'cookie' => $this->fe_user->id,
1257 // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1258 'IP' => \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1259 // Remote IP address
1260 'hits' => intval($count),
1261 // Number of hits on the search.
1262 'tstamp' => $GLOBALS['EXEC_TIME']
1263 );
1264 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
1265 $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
1266 if ($newId) {
1267 foreach ($sWArr as $val) {
1268 $insertFields = array(
1269 'word' => $val['sword'],
1270 'index_stat_search_id' => $newId,
1271 'tstamp' => $GLOBALS['EXEC_TIME'],
1272 // Time stamp
1273 'pageid' => $GLOBALS['TSFE']->id
1274 );
1275 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
1276 }
1277 }
1278 }
1279
1280 /***********************************
1281 *
1282 * HTML output functions
1283 *
1284 ***********************************/
1285 /**
1286 * Make search form HTML
1287 *
1288 * @param array Value/Labels pairs for search form selector boxes.
1289 * @return string Search form HTML
1290 * @todo Define visibility
1291 */
1292 public function makeSearchForm($optValues) {
1293 $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
1294 // Multilangual text
1295 $substituteArray = array('legend', 'searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style', 'freeIndexUid');
1296 foreach ($substituteArray as $marker) {
1297 $markerArray[('###FORM_' . \TYPO3\CMS\Core\Utility\GeneralUtility::strtoupper($marker)) . '###'] = $this->pi_getLL('form_' . $marker, '', 1);
1298 }
1299 $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label', '', 1);
1300 // Adding search field value
1301 $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1302 // Additonal keyword => "Add to current search words"
1303 if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1304 $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1305 $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"' : '';
1306 $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch', '', 1);
1307 } else {
1308 $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1309 }
1310 $markerArray['###ACTION_URL###'] = htmlspecialchars($this->getSearchFormActionURL());
1311 $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
1312 $hiddenFieldCode = preg_replace('/^\\n\\t(.+)/ms', '$1', $hiddenFieldCode);
1313 // Remove first newline and tab (cosmetical issue)
1314 $hiddenFieldArr = array();
1315 foreach (\TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $this->hiddenFieldList) as $fieldName) {
1316 $hiddenFieldMarkerArray = array();
1317 $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = (($this->prefixId . '[') . $fieldName) . ']';
1318 $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string) $this->piVars[$fieldName]);
1319 $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, array(), array());
1320 }
1321 // Extended search
1322 if ($this->piVars['ext']) {
1323 // Search for
1324 if (!is_array($optValues['type']) && !is_array($optValues['defOp']) || $this->conf['blind.']['type'] && $this->conf['blind.']['defOp']) {
1325 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1326 } else {
1327 if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
1328 unset($hiddenFieldArr['type']);
1329 $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'], $optValues['type']);
1330 } else {
1331 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1332 }
1333 if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp']) {
1334 $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'], $optValues['defOp']);
1335 } else {
1336 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1337 }
1338 }
1339 // Search in
1340 if (!is_array($optValues['media']) && !is_array($optValues['lang']) || $this->conf['blind.']['media'] && $this->conf['blind.']['lang']) {
1341 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1342 } else {
1343 if (is_array($optValues['media']) && !$this->conf['blind.']['media']) {
1344 unset($hiddenFieldArr['media']);
1345 $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'], $optValues['media']);
1346 } else {
1347 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1348 }
1349 if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
1350 unset($hiddenFieldArr['lang']);
1351 $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'], $optValues['lang']);
1352 } else {
1353 $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1354 }
1355 }
1356 // Sections
1357 if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
1358 $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1359 } else {
1360 $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'], $optValues['sections']);
1361 }
1362 // Free Indexing Configurations:
1363 if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid']) {
1364 $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
1365 } else {
1366 $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'], $optValues['freeIndexUid']);
1367 }
1368 // Sorting
1369 if ((!is_array($optValues['order']) || !is_array($optValues['desc'])) || $this->conf['blind.']['order']) {
1370 $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1371 } else {
1372 unset($hiddenFieldArr['order']);
1373 unset($hiddenFieldArr['desc']);
1374 unset($hiddenFieldArr['results']);
1375 $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'], $optValues['order']);
1376 $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'], $optValues['desc']);
1377 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1378 }
1379 // Limits
1380 if ((!is_array($optValues['results']) || !is_array($optValues['results'])) || $this->conf['blind.']['results']) {
1381 $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1382 } else {
1383 $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1384 }
1385 // Grouping
1386 if (!is_array($optValues['group']) || $this->conf['blind.']['group']) {
1387 $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1388 } else {
1389 unset($hiddenFieldArr['group']);
1390 $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'], $optValues['group']);
1391 }
1392 if ($this->conf['blind.']['extResume']) {
1393 $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1394 } else {
1395 $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
1396 }
1397 } else {
1398 // Extended search
1399 $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1400 }
1401 if ($this->conf['show.']['advancedSearchLink']) {
1402 $linkToOtherMode = $this->piVars['ext'] ? $this->pi_getPageLink($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sPre, array($this->prefixId . '[ext]' => 0)) : $this->pi_getPageLink($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sPre, array($this->prefixId . '[ext]' => 1));
1403 $markerArray['###LINKTOOTHERMODE###'] = ((('<a href="' . htmlspecialchars($linkToOtherMode)) . '">') . $this->pi_getLL(($this->piVars['ext'] ? 'link_regularSearch' : 'link_advancedSearch'), '', 1)) . '</a>';
1404 } else {
1405 $markerArray['###LINKTOOTHERMODE###'] = '';
1406 }
1407 // Write all hidden fields
1408 $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('', $hiddenFieldArr));
1409 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1410 return $substitutedContent;
1411 }
1412
1413 /**
1414 * Function, rendering selector box values.
1415 *
1416 * @param string Current value
1417 * @param array Array with the options as key=>value pairs
1418 * @return string <options> imploded.
1419 * @todo Define visibility
1420 */
1421 public function renderSelectBoxValues($value, $optValues) {
1422 if (is_array($optValues)) {
1423 $opt = array();
1424 $isSelFlag = 0;
1425 foreach ($optValues as $k => $v) {
1426 $sel = !strcmp($k, $value) ? ' selected="selected"' : '';
1427 if ($sel) {
1428 $isSelFlag++;
1429 }
1430 $opt[] = ((((('<option value="' . htmlspecialchars($k)) . '"') . $sel) . '>') . htmlspecialchars($v)) . '</option>';
1431 }
1432 return implode('', $opt);
1433 }
1434 }
1435
1436 /**
1437 * Print the searching rules
1438 *
1439 * @return string Rules for the search
1440 * @todo Define visibility
1441 */
1442 public function printRules() {
1443 if ($this->conf['show.']['rules']) {
1444 $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1445 $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header', '', 1);
1446 $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text', '', 1)));
1447 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1448 return $this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']);
1449 }
1450 }
1451
1452 /**
1453 * Returns the anchor-links to the sections inside the displayed result rows.
1454 *
1455 * @return string
1456 * @todo Define visibility
1457 */
1458 public function printResultSectionLinks() {
1459 if (count($this->resultSections)) {
1460 $lines = array();
1461 $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1462 $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1463 foreach ($this->resultSections as $id => $dat) {
1464 $markerArray = array();
1465 $aBegin = ('<a href="' . htmlspecialchars((($GLOBALS['TSFE']->anchorPrefix . '#anchor_') . md5($id)))) . '">';
1466 $aContent = ((((htmlspecialchars((trim($dat[0]) ? trim($dat[0]) : $this->pi_getLL('unnamedSection'))) . ' (') . $dat[1]) . ' ') . $this->pi_getLL(($dat[1] > 1 ? 'word_pages' : 'word_page'), '', 1)) . ')';
1467 $aEnd = '</a>';
1468 $markerArray['###LINK###'] = ($aBegin . $aContent) . $aEnd;
1469 $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
1470 }
1471 $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode('', $links)), array(), array());
1472 return ((('<div' . $this->pi_classParam('sectionlinks')) . '>') . $this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.'])) . '</div>';
1473 }
1474 }
1475
1476 /**
1477 * Returns the section header of the search result.
1478 *
1479 * @param string ID for the section (used for anchor link)
1480 * @param string Section title with linked wrapped around
1481 * @param integer Number of results in section
1482 * @return string HTML output
1483 * @todo Define visibility
1484 */
1485 public function makeSectionHeader($id, $sectionTitleLinked, $countResultRows) {
1486 $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1487 $markerArray['###ANCHOR_URL###'] = 'anchor_' . md5($id);
1488 $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1489 $markerArray['###RESULT_COUNT###'] = $countResultRows;
1490 $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page' . ($countResultRows > 1 ? 's' : ''));
1491 $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1492 return $substitutedContent;
1493 }
1494
1495 /**
1496 * This prints a single result row, including a recursive call for subrows.
1497 *
1498 * @param array Search result row
1499 * @param integer 1=Display only header (for sub-rows!), 2=nothing at all
1500 * @return string HTML code
1501 * @todo Define visibility
1502 */
1503 public function printResultRow($row, $headerOnly = 0) {
1504 // Get template content:
1505 $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1506 if ($hookObj = $this->hookRequest('printResultRow')) {
1507 return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1508 } else {
1509 $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1510 if (!is_array($row['_sub'])) {
1511 $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1512 }
1513 if (!$headerOnly) {
1514 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1515 } elseif ($headerOnly == 1) {
1516 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1517 } elseif ($headerOnly == 2) {
1518 $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1519 $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1520 }
1521 if (is_array($tmplContent)) {
1522 foreach ($tmplContent as $k => $v) {
1523 $markerArray[('###' . \TYPO3\CMS\Core\Utility\GeneralUtility::strtoupper($k)) . '###'] = $v;
1524 }
1525 }
1526 // Description text
1527 $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size', '', 1);
1528 $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created', '', 1);
1529 $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified', '', 1);
1530 $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path', '', 1);
1531 $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1532 // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1533 if (is_array($row['_sub'])) {
1534 if ($this->multiplePagesType($row['item_type'])) {
1535 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching', '', 1), $html);
1536 foreach ($row['_sub'] as $subRow) {
1537 $html .= $this->printResultRow($subRow, 1);
1538 }
1539 } else {
1540 $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching', '', 1);
1541 $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell', '', 1), $html);
1542 }
1543 }
1544 return $html;
1545 }
1546 }
1547
1548 /**
1549 * Returns a results browser
1550 *
1551 * @param boolean Show result count
1552 * @param string String appended to "displaying results..." notice.
1553 * @param string String appended after section "displaying results...
1554 * @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!
1555 * @return string HTML output
1556 * @todo Define visibility
1557 */
1558 public function pi_list_browseresults($showResultCount = 1, $addString = '', $addPart = '', $freeIndexUid = -1) {
1559 // Initializing variables:
1560 $pointer = $this->piVars['pointer'];
1561 $count = $this->internal['res_count'];
1562 $results_at_a_time = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->internal['results_at_a_time'], 1, 1000);
1563 $maxPages = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->internal['maxPages'], 1, 100);
1564 $pageCount = ceil($count / $results_at_a_time);
1565 $sTables = '';
1566 if ($pageCount > 1) {
1567 // only show the result browser if more than one page is needed
1568 $pointer = intval($pointer);
1569 $links = array();
1570 // Make browse-table/links:
1571 if ($pointer > 0) {
1572 // all pages after the 1st one
1573 $links[] = ('<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev', '< Previous', 1), ($pointer - 1), $freeIndexUid)) . '</li>';
1574 }
1575 for ($a = 0; $a < $pageCount; $a++) {
1576 $min = max(0, ($pointer + 1) - ceil($maxPages / 2));
1577 $max = $min + $maxPages;
1578 if ($max > $pageCount) {
1579 $min = $min - ($max - $pageCount);
1580 }
1581 if ($a >= $min && $a < $max) {
1582 if ($a == $pointer) {
1583 $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>';
1584 } else {
1585 $links[] = ('<li>' . $this->makePointerSelector_link(trim((($this->pi_getLL('pi_list_browseresults_page', 'Page', 1) . ' ') . ($a + 1))), $a, $freeIndexUid)) . '</li>';
1586 }
1587 }
1588 }
1589 if ($pointer + 1 < $pageCount) {
1590 $links[] = ('<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next', 'Next >', 1), ($pointer + 1), $freeIndexUid)) . '</li>';
1591 }
1592 }
1593 $pR1 = $pointer * $results_at_a_time + 1;
1594 $pR2 = $pointer * $results_at_a_time + $results_at_a_time;
1595 if (is_array($links)) {
1596 $addPart .= ('
1597 <ul class="browsebox">
1598 ' . implode('', $links)) . '
1599 </ul>';
1600 }
1601 $label = $this->pi_getLL('pi_list_browseresults_display', 'Displaying results ###TAG_BEGIN###%s to %s###TAG_END### out of ###TAG_BEGIN###%s###TAG_END###');
1602 $label = str_replace('###TAG_BEGIN###', '<strong>', $label);
1603 $label = str_replace('###TAG_END###', '</strong>', $label);
1604 $sTables = (((('<div' . $this->pi_classParam('browsebox')) . '>') . ($showResultCount ? (('<p>' . sprintf($label, $pR1, min(array($this->internal['res_count'], $pR2)), $this->internal['res_count'])) . $addString) . '</p>' : '')) . $addPart) . '</div>';
1605 return $sTables;
1606 }
1607
1608 /***********************************
1609 *
1610 * Support functions for HTML output (with a minimum of fixed markup)
1611 *
1612 ***********************************/
1613 /**
1614 * Preparing template data for the result row output
1615 *
1616 * @param array Result row
1617 * @param boolean If set, display only header of result (for sub-results)
1618 * @return array Array with data to insert in result row template
1619 * @todo Define visibility
1620 */
1621 public function prepareResultRowTemplateData($row, $headerOnly) {
1622 // Initialize:
1623 $specRowConf = $this->getSpecialConfigForRow($row);
1624 $CSSsuffix = $specRowConf['CSSsuffix'] ? '-' . $specRowConf['CSSsuffix'] : '';
1625 // If external media, link to the media-file instead.
1626 if ($row['item_type']) {
1627 // External media
1628 if ($row['show_resume']) {
1629 // Can link directly.
1630 $targetAttribute = '';
1631 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
1632 $targetAttribute = (' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget'])) . '"';
1633 }
1634 $title = ((((('<a href="' . htmlspecialchars($row['data_filename'])) . '"') . $targetAttribute) . '>') . htmlspecialchars($this->makeTitle($row))) . '</a>';
1635 } else {
1636 // Suspicious, so linking to page instead...
1637 $copy_row = $row;
1638 unset($copy_row['cHashParams']);
1639 $title = $this->linkPage($row['page_id'], htmlspecialchars($this->makeTitle($row)), $copy_row);
1640 }
1641 } else {
1642 // Else the page:
1643 // Prepare search words for markup in content:
1644 if ($this->conf['forwardSearchWordsInResultLink']) {
1645 $markUpSwParams = array('no_cache' => 1);
1646 foreach ($this->sWArr as $d) {
1647 $markUpSwParams['sword_list'][] = $d['sword'];
1648 }
1649 } else {
1650 $markUpSwParams = array();
1651 }
1652 $title = $this->linkPage($row['data_page_id'], htmlspecialchars($this->makeTitle($row)), $row, $markUpSwParams);
1653 }
1654 $tmplContent = array();
1655 $tmplContent['title'] = $title;
1656 $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'] . ': ' : '&nbsp;';
1657 $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'], '', $specRowConf);
1658 $tmplContent['rating'] = $this->makeRating($row);
1659 $tmplContent['description'] = $this->makeDescription($row, $this->piVars['extResume'] && !$headerOnly ? 0 : 1);
1660 $tmplContent = $this->makeInfo($row, $tmplContent);
1661 $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1662 $tmplContent['language'] = $this->makeLanguageIndication($row);
1663 $tmplContent['CSSsuffix'] = $CSSsuffix;
1664 // Post processing with hook.
1665 if ($hookObj = $this->hookRequest('prepareResultRowTemplateData_postProc')) {
1666 $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1667 }
1668 return $tmplContent;
1669 }
1670
1671 /**
1672 * Returns a string that tells which search words are searched for.
1673 *
1674 * @param array Array of search words
1675 * @return string HTML telling what is searched for.
1676 * @todo Define visibility
1677 */
1678 public function tellUsWhatIsSeachedFor($sWArr) {
1679 // Init:
1680 $searchingFor = '';
1681 $c = 0;
1682 // Traverse search words:
1683 foreach ($sWArr as $k => $v) {
1684 if ($c) {
1685 switch ($v['oper']) {
1686 case 'OR':
1687 $searchingFor .= ((' ' . $this->pi_getLL('searchFor_or', '', 1)) . ' ') . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1688 break;
1689 case 'AND NOT':
1690 $searchingFor .= ((' ' . $this->pi_getLL('searchFor_butNot', '', 1)) . ' ') . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1691 break;
1692 default:
1693 // AND...
1694 $searchingFor .= ((' ' . $this->pi_getLL('searchFor_and', '', 1)) . ' ') . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1695 break;
1696 }
1697 } else {
1698 $searchingFor = ($this->pi_getLL('searchFor', '', 1) . ' ') . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1699 }
1700 $c++;
1701 }
1702 return $searchingFor;
1703 }
1704
1705 /**
1706 * Wraps the search words in the search-word list display (from ->tellUsWhatIsSeachedFor())
1707 *
1708 * @param string search word to wrap (in local charset!)
1709 * @return string Search word wrapped in <span> tag.
1710 * @todo Define visibility
1711 */
1712 public function wrapSW($str) {
1713 return ((('"<span' . $this->pi_classParam('sw')) . '>') . htmlspecialchars($str)) . '</span>"';
1714 }
1715
1716 /**
1717 * Makes a selector box
1718 *
1719 * @param string Name of selector box
1720 * @param string Current value
1721 * @param array Array of options in the selector box (value => label pairs)
1722 * @return string HTML of selector box
1723 * @todo Define visibility
1724 */
1725 public function renderSelectBox($name, $value, $optValues) {
1726 if (is_array($optValues)) {
1727 $opt = array();
1728 $isSelFlag = 0;
1729 foreach ($optValues as $k => $v) {
1730 $sel = !strcmp($k, $value) ? ' selected="selected"' : '';
1731 if ($sel) {
1732 $isSelFlag++;
1733 }
1734 $opt[] = ((((('<option value="' . htmlspecialchars($k)) . '"') . $sel) . '>') . htmlspecialchars($v)) . '</option>';
1735 }
1736 return ((('<select name="' . $name) . '">') . implode('', $opt)) . '</select>';
1737 }
1738 }
1739
1740 /**
1741 * Used to make the link for the result-browser.
1742 * Notice how the links must resubmit the form after setting the new pointer-value in a hidden formfield.
1743 *
1744 * @param string String to wrap in <a> tag
1745 * @param integer Pointer value
1746 * @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!
1747 * @return string Input string wrapped in <a> tag with onclick event attribute set.
1748 * @todo Define visibility
1749 */
1750 public function makePointerSelector_link($str, $p, $freeIndexUid) {
1751 $onclick = ((((((((((('document.getElementById(\'' . $this->prefixId) . '_pointer\').value=\'') . $p) . '\';') . 'document.getElementById(\'') . $this->prefixId) . '_freeIndexUid\').value=\'') . rawurlencode($freeIndexUid)) . '\';') . 'document.getElementById(\'') . $this->prefixId) . '\').submit();return false;';
1752 return ((('<a href="#" onclick="' . htmlspecialchars($onclick)) . '">') . $str) . '</a>';
1753 }
1754
1755 /**
1756 * Return icon for file extension
1757 *
1758 * @param string File extension / item type
1759 * @param string Title attribute value in icon.
1760 * @param array TypoScript configuration specifically for search result.
1761 * @return string <img> tag for icon
1762 * @todo Define visibility
1763 */
1764 public function makeItemTypeIcon($it, $alt = '', $specRowConf) {
1765 // Build compound key if item type is 0, iconRendering is not used
1766 // and specConfs.[pid].pageIcon was set in TS
1767 if ((($it === '0' && $specRowConf['_pid']) && is_array($specRowConf['pageIcon.'])) && !is_array($this->conf['iconRendering.'])) {
1768 $it .= ':' . $specRowConf['_pid'];
1769 }
1770 if (!isset($this->iconFileNameCache[$it])) {
1771 $this->iconFileNameCache[$it] = '';
1772 // If TypoScript is used to render the icon:
1773 if (is_array($this->conf['iconRendering.'])) {
1774 $this->cObj->setCurrentVal($it);
1775 $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'], $this->conf['iconRendering.']);
1776 } else {
1777 // Default creation / finding of icon:
1778 $icon = '';
1779 if ($it === '0' || substr($it, 0, 2) == '0:') {
1780 if (is_array($specRowConf['pageIcon.'])) {
1781 $this->iconFileNameCache[$it] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
1782 } else {
1783 $icon = 'EXT:indexed_search/pi/res/pages.gif';
1784 }
1785 } elseif ($this->external_parsers[$it]) {
1786 $icon = $this->external_parsers[$it]->getIcon($it);
1787 }
1788 if ($icon) {
1789 $fullPath = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($icon);
1790 if ($fullPath) {
1791 $info = @getimagesize($fullPath);
1792 $iconPath = substr($fullPath, strlen(PATH_site));
1793 $this->iconFileNameCache[$it] = is_array($info) ? ((((('<img src="' . $iconPath) . '" ') . $info[3]) . ' title="') . htmlspecialchars($alt)) . '" alt="" />' : '';
1794 }
1795 }
1796 }
1797 }
1798 return $this->iconFileNameCache[$it];
1799 }
1800
1801 /**
1802 * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
1803 *
1804 * @param array Result row array
1805 * @return string String showing ranking value
1806 * @todo Define visibility
1807 */
1808 public function makeRating($row) {
1809 switch ((string) $this->piVars['order']) {
1810 case 'rank_count':
1811 // Number of occurencies on page
1812 return ($row['order_val'] . ' ') . $this->pi_getLL('maketitle_matches');
1813 break;
1814 case 'rank_first':
1815 // Close to top of page
1816 return ceil((\TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange((255 - $row['order_val']), 1, 255) / 255) * 100) . '%';
1817 break;
1818 case 'rank_flag':
1819 // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1820 if ($this->firstRow['order_val2']) {
1821 $base = $row['order_val1'] * 256;
1822 // (3 MSB bit, 224 is highest value of order_val1 currently)
1823 $freqNumber = ($row['order_val2'] / $this->firstRow['order_val2']) * pow(2, 12);
1824 // 15-3 MSB = 12
1825 $total = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($base + $freqNumber, 0, 32767);
1826 return ceil((log($total) / log(32767)) * 100) . '%';
1827 }
1828 break;
1829 case 'rank_freq':
1830 // Based on frequency
1831 $max = 10000;
1832 $total = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($row['order_val'], 0, $max);
1833 return ceil((log($total) / log($max)) * 100) . '%';
1834 break;
1835 case 'crdate':
1836 // Based on creation date
1837 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'], 0);
1838 break;
1839 case 'mtime':
1840 // Based on modification time
1841 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'], 0);
1842 break;
1843 default:
1844 // fx. title
1845 return '&nbsp;';
1846 break;
1847 }
1848 }
1849
1850 /**
1851 * Returns the resume for the search-result.
1852 *
1853 * @param array Search result row
1854 * @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.
1855 * @param integer String length
1856 * @return string HTML string ...
1857 * @todo Define visibility
1858 */
1859 public function makeDescription($row, $noMarkup = 0, $lgd = 180) {
1860 if ($row['show_resume']) {
1861 if (!$noMarkup) {
1862 $markedSW = '';
1863 if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_fulltext')) {
1864 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash=' . intval($row['phash']));
1865 } else {
1866 $res = FALSE;
1867 }
1868 if ($res) {
1869 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1870 // Cut HTTP references after some length
1871 $content = preg_replace('/(http:\\/\\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
1872 $markedSW = $this->markupSWpartsOfString($content);
1873 }
1874 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1875 }
1876 }
1877 if (!trim($markedSW)) {
1878 $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8', $row['item_description'], $lgd);
1879 $outputStr = htmlspecialchars($outputStr);
1880 }
1881 $output = $this->utf8_to_currentCharset($outputStr ? $outputStr : $markedSW);
1882 } else {
1883 $output = ('<span class="noResume">' . $this->pi_getLL('res_noResume', '', 1)) . '</span>';
1884 }
1885 return $output;
1886 }
1887
1888 /**
1889 * Marks up the search words from $this->sWarr in the $str with a color.
1890 *
1891 * @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.
1892 * @return string Processed content.
1893 * @todo Define visibility
1894 */
1895 public function markupSWpartsOfString($str) {
1896 // Init:
1897 $str = str_replace('&nbsp;', ' ', \TYPO3\CMS\Core\Html\HtmlParser::bidir_htmlspecialchars($str, -1));
1898 $str = preg_replace('/\\s\\s+/', ' ', $str);
1899 $swForReg = array();
1900 // Prepare search words for regex:
1901 foreach ($this->sWArr as $d) {
1902 $swForReg[] = preg_quote($d['sword'], '/');
1903 }
1904 $regExString = ('(' . implode('|', $swForReg)) . ')';
1905 // Split and combine:
1906 $parts = preg_split(('/' . $regExString) . '/i', (' ' . $str) . ' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
1907 // Constants:
1908 $summaryMax = 300;
1909 $postPreLgd = 60;
1910 $postPreLgd_offset = 5;
1911 $divider = ' ... ';
1912 $occurencies = (count($parts) - 1) / 2;
1913 if ($occurencies) {
1914 $postPreLgd = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($summaryMax / $occurencies, $postPreLgd, $summaryMax / 2);
1915 }
1916 // Variable:
1917 $summaryLgd = 0;
1918 $output = array();
1919 // Shorten in-between strings:
1920 foreach ($parts as $k => $strP) {
1921 if ($k % 2 == 0) {
1922 // Find length of the summary part:
1923 $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
1924 $output[$k] = $parts[$k];
1925 // Possibly shorten string:
1926 if (!$k) {
1927 // First entry at all (only cropped on the frontside)
1928 if ($strLen > $postPreLgd) {
1929 $output[$k] = $divider . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
1930 }
1931 } elseif ($summaryLgd > $summaryMax || !isset($parts[($k + 1)])) {
1932 // In case summary length is exceed OR if there are no more entries at all:
1933 if ($strLen > $postPreLgd) {
1934 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider;
1935 }
1936 } else {
1937 // In-between search words:
1938 if ($strLen > $postPreLgd * 2) {
1939 $output[$k] = (preg_replace('/[[:space:]][^[:space:]]+$/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider) . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
1940 }
1941 }
1942 $summaryLgd += $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);
1943 // Protect output:
1944 $output[$k] = htmlspecialchars($output[$k]);
1945 // If summary lgd is exceed, break the process:
1946 if ($summaryLgd > $summaryMax) {
1947 break;
1948 }
1949 } else {
1950 $summaryLgd += $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $strP);
1951 $output[$k] = ('<strong class="tx-indexedsearch-redMarkup">' . htmlspecialchars($parts[$k])) . '</strong>';
1952 }
1953 }
1954 // Return result:
1955 return implode('', $output);
1956 }
1957
1958 /**
1959 * Returns the title of the search result row
1960 *
1961 * @param array Result row
1962 * @return string Title from row
1963 * @todo Define visibility
1964 */
1965 public function makeTitle($row) {
1966 $add = '';
1967 if ($this->multiplePagesType($row['item_type'])) {
1968 $dat = unserialize($row['cHashParams']);
1969 $pp = explode('-', $dat['key']);
1970 if ($pp[0] != $pp[1]) {
1971 $add = ((', ' . $this->pi_getLL('word_pages')) . ' ') . $dat['key'];
1972 } else {
1973 $add = ((', ' . $this->pi_getLL('word_page')) . ' ') . $pp[0];
1974 }
1975 }
1976 $outputString = $GLOBALS['TSFE']->csConvObj->crop('utf-8', $row['item_title'], 50, '...');
1977 return $this->utf8_to_currentCharset($outputString) . $add;
1978 }
1979
1980 /**
1981 * Returns the info-string in the bottom of the result-row display (size, dates, path)
1982 *
1983 * @param array Result row
1984 * @param array Template array to modify
1985 * @return array Modified template array
1986 * @todo Define visibility
1987 */
1988 public function makeInfo($row, $tmplArray) {
1989 $tmplArray['size'] = \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize($row['item_size']);
1990 $tmplArray['created'] = $this->formatCreatedDate($row['item_crdate']);
1991 $tmplArray['modified'] = $this->formatModifiedDate($row['item_mtime']);
1992 $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
1993 $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
1994 $pI = parse_url($row['data_filename']);
1995 if ($pI['scheme']) {
1996 $targetAttribute = '';
1997 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
1998 $targetAttribute = (' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget'])) . '"';
1999 }
2000 $tmplArray['path'] = ((((('<a href="' . htmlspecialchars($row['data_filename'])) . '"') . $targetAttribute) . '>') . htmlspecialchars($row['data_filename'])) . '</a>';
2001 } else {
2002 $pathStr = htmlspecialchars($this->getPathFromPageId($pathId, $pathMP));
2003 $tmplArray['path'] = $this->linkPage($pathId, $pathStr, array(
2004 'cHashParams' => $row['cHashParams'],
2005 'data_page_type' => $row['data_page_type'],
2006 'data_page_mp' => $pathMP,
2007 'sys_language_uid' => $row['sys_language_uid']
2008 ));
2009 }
2010 return $tmplArray;
2011 }
2012
2013 /**
2014 * Returns configuration from TypoScript for result row based on ID / location in page tree!
2015 *
2016 * @param array Result row
2017 * @return array Configuration array
2018 * @todo Define visibility
2019 */
2020 public function getSpecialConfigForRow($row) {
2021 $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
2022 $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2023 $rl = $this->getRootLine($pathId, $pathMP);
2024 $specConf = $this->conf['specConfs.']['0.'];
2025 if (is_array($rl)) {
2026 foreach ($rl as $dat) {
2027 if (is_array($this->conf['specConfs.'][$dat['uid'] . '.'])) {
2028 $specConf = $this->conf['specConfs.'][$dat['uid'] . '.'];
2029 $specConf['_pid'] = $dat['uid'];
2030 break;
2031 }
2032 }
2033 }
2034 return $specConf;
2035 }
2036
2037 /**
2038 * Returns the HTML code for language indication.
2039 *
2040 * @param array Result row
2041 * @return string HTML code for result row.
2042 * @todo Define visibility
2043 */
2044 public function makeLanguageIndication($row) {
2045 // If search result is a TYPO3 page:
2046 if ((string) $row['item_type'] === '0') {
2047 // If TypoScript is used to render the flag:
2048 if (is_array($this->conf['flagRendering.'])) {
2049 $this->cObj->setCurrentVal($row['sys_language_uid']);
2050 return $this->cObj->cObjGetSingle($this->conf['flagRendering'], $this->conf['flagRendering.']);
2051 } else {
2052 // ... otherwise, get flag from sys_language record:
2053 // Get sys_language record
2054 $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_language', (('uid=' . intval($row['sys_language_uid'])) . ' ') . $this->cObj->enableFields('sys_language'));
2055 // Flag code:
2056 $flag = $rowDat['flag'];
2057 if ($flag) {
2058 // FIXME not all flags from typo3/gfx/flags are available in media/flags/
2059 $file = (substr(PATH_tslib, strlen(PATH_site)) . 'media/flags/flag_') . $flag;
2060 $imgInfo = @getimagesize((PATH_site . $file));
2061 if (is_array($imgInfo)) {
2062 $output = ((((((('<img src="' . $file) . '" ') . $imgInfo[3]) . ' title="') . htmlspecialchars($rowDat['title'])) . '" alt="') . htmlspecialchars($rowDat['title'])) . '" />';
2063 return $output;
2064 }
2065 }
2066 }
2067 }
2068 return '&nbsp;';
2069 }
2070
2071 /**
2072 * Returns the HTML code for the locking symbol.
2073 * NOTICE: Requires a call to ->getPathFromPageId() first in order to work (done in ->makeInfo() by calling that first)
2074 *
2075 * @param integer Page id for which to find answer
2076 * @return string <img> tag if access is limited.
2077 * @todo Define visibility
2078 */
2079 public function makeAccessIndication($id) {
2080 if (is_array($this->fe_groups_required[$id]) && count($this->fe_groups_required[$id])) {
2081 return ((('<img src="' . \TYPO3\CMS\Core\Extension\ExtensionManager::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="" />';
2082 }
2083 }
2084
2085 /**
2086 * Links the $str to page $id
2087 *
2088 * @param integer Page id
2089 * @param string Title String to link
2090 * @param array Result row
2091 * @param array Additional parameters for marking up seach words
2092 * @return string <A> tag wrapped title string.
2093 * @todo Define visibility
2094 */
2095 public function linkPage($id, $str, $row = array(), $markUpSwParams = array()) {
2096 // Parameters for link:
2097 $urlParameters = (array) unserialize($row['cHashParams']);
2098 // Add &type and &MP variable:
2099 if ($row['data_page_type']) {
2100 $urlParameters['type'] = $row['data_page_type'];
2101 }
2102 if ($row['data_page_mp']) {
2103 $urlParameters['MP'] = $row['data_page_mp'];
2104 }
2105 if ($row['sys_language_uid']) {
2106 $urlParameters['L'] = $row['sys_language_uid'];
2107 }
2108 // markup-GET vars:
2109 $urlParameters = array_merge($urlParameters, $markUpSwParams);
2110 // 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...
2111 if (!is_array($this->domain_records[$id])) {
2112 $this->getPathFromPageId($id);
2113 }
2114 // If external domain, then link to that:
2115 if (count($this->domain_records[$id])) {
2116 reset($this->domain_records[$id]);
2117 $firstDom = current($this->domain_records[$id]);
2118 $scheme = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2119 $addParams = '';
2120 if (is_array($urlParameters)) {
2121 if (count($urlParameters)) {
2122 $addParams .= \TYPO3\CMS\Core\Utility\GeneralUtility::implodeArrayForUrl('', $urlParameters);
2123 }
2124 }
2125 if ($target = $this->conf['search.']['detect_sys_domain_records.']['target']) {
2126 $target = (' target="' . $target) . '"';
2127 }
2128 return ((((('<a href="' . htmlspecialchars((((($scheme . $firstDom) . '/index.php?id=') . $id) . $addParams))) . '"') . $target) . '>') . htmlspecialchars($str)) . '</a>';
2129 } else {
2130 return $this->pi_linkToPage($str, $id, $this->conf['result_link_target'], $urlParameters);
2131 }
2132 }
2133
2134 /**
2135 * Returns the path to the page $id
2136 *
2137 * @param integer Page ID
2138 * @param string MP variable content.
2139 * @return string Root line for result.
2140 * @todo Define visibility
2141 */
2142 public function getRootLine($id, $pathMP = '') {
2143 $identStr = ($id . '|') . $pathMP;
2144 if (!isset($this->cache_path[$identStr])) {
2145 $this->cache_rl[$identStr] = $GLOBALS['TSFE']->sys_page->getRootLine($id, $pathMP);
2146 }
2147 return $this->cache_rl[$identStr];
2148 }
2149
2150 /**
2151 * Gets the first sys_domain record for the page, $id
2152 *
2153 * @param integer Page id
2154 * @return string Domain name
2155 * @todo Define visibility
2156 */
2157 public function getFirstSysDomainRecordForPage($id) {
2158 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', ('pid=' . intval($id)) . $this->cObj->enableFields('sys_domain'), '', 'sorting');
2159 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
2160 return rtrim($row['domainName'], '/');
2161 }
2162
2163 /**
2164 * Returns the path to the page $id
2165 *
2166 * @param integer Page ID
2167 * @param string MP variable content
2168 * @return string Path
2169 * @todo Define visibility
2170 */
2171 public function getPathFromPageId($id, $pathMP = '') {
2172 $identStr = ($id . '|') . $pathMP;
2173 if (!isset($this->cache_path[$identStr])) {
2174 $this->fe_groups_required[$id] = array();
2175 $this->domain_records[$id] = array();
2176 $rl = $this->getRootLine($id, $pathMP);
2177 $hitRoot = 0;
2178 $path = '';
2179 if (is_array($rl) && count($rl)) {
2180 foreach ($rl as $k => $v) {
2181 // Check fe_user
2182 if ($v['fe_group'] && ($v['uid'] == $id || $v['extendToSubpages'])) {
2183 $this->fe_groups_required[$id][] = $v['fe_group'];
2184 }
2185 // Check sys_domain.
2186 if ($this->conf['search.']['detect_sys_domain_records']) {
2187 $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2188 if ($sysDName) {
2189 $this->domain_records[$id][] = $sysDName;
2190 // Set path accordingly:
2191 $path = $sysDName . $path;
2192 break;
2193 }
2194 }
2195 // Stop, if we find that the current id is the current root page.
2196 if ($v['uid'] == $GLOBALS['TSFE']->config['rootLine'][0]['uid']) {
2197 break;
2198 }
2199 $path = ('/' . $v['title']) . $path;
2200 }
2201 }
2202 $this->cache_path[$identStr] = $path;
2203 if (is_array($this->conf['path_stdWrap.'])) {
2204 $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2205 }
2206 }
2207 return $this->cache_path[$identStr];
2208 }
2209
2210 /**
2211 * Return the menu of pages used for the selector.
2212 *
2213 * @param integer Page ID for which to return menu
2214 * @return array Menu items (for making the section selector box)
2215 * @todo Define visibility
2216 */
2217 public function getMenu($id) {
2218 if ($this->conf['show.']['LxALLtypes']) {
2219 $output = array();
2220 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', ('pid=' . intval($id)) . $this->cObj->enableFields('pages'), '', 'sorting');
2221 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2222 $output[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
2223 }
2224 $GLOBALS['TYPO3_DB']->sql_free_result($res);
2225 return $output;
2226 } else {
2227 return $GLOBALS['TSFE']->sys_page->getMenu($id);
2228 }
2229 }
2230
2231 /**
2232 * Returns if an item type is a multipage item type
2233 *
2234 * @param string Item type
2235 * @return boolean TRUE if multipage capable
2236 * @todo Define visibility
2237 */
2238 public function multiplePagesType($item_type) {
2239 return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2240 }
2241
2242 /**
2243 * Converts the input string from utf-8 to the backend charset.
2244 *
2245 * @param string String to convert (utf-8)
2246 * @return string Converted string (backend charset if different from utf-8)
2247 * @todo Define visibility
2248 */
2249 public function utf8_to_currentCharset($str) {
2250 return $GLOBALS['TSFE']->csConv($str, 'utf-8');
2251 }
2252
2253 /**
2254 * Returns an object reference to the hook object if any
2255 *
2256 * @param string Name of the function you want to call / hook key
2257 * @return object Hook object, if any. Otherwise NULL.
2258 * @todo Define visibility
2259 */
2260 public function hookRequest($functionName) {
2261 global $TYPO3_CONF_VARS;
2262 // Hook: menuConfig_preProcessModMenu
2263 if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2264 $hookObj = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2265 if (method_exists($hookObj, $functionName)) {
2266 $hookObj->pObj = $this;
2267 return $hookObj;
2268 }
2269 }
2270 }
2271
2272 /**
2273 * Obtains the URL of the search target page
2274 *
2275 * @return string
2276 */
2277 protected function getSearchFormActionURL() {
2278 $targetUrlPid = $this->getSearchFormActionPidFromTS();
2279 if ($targetUrlPid == 0) {
2280 $targetUrlPid = $GLOBALS['TSFE']->id;
2281 }
2282 return $this->pi_getPageLink($targetUrlPid, $GLOBALS['TSFE']->sPre);
2283 }
2284
2285 /**
2286 * Obtains search form target pid from the TypoScript configuration
2287 *
2288 * @return int
2289 */
2290 protected function getSearchFormActionPidFromTS() {
2291 $result = 0;
2292 if (isset($this->conf['search.']['targetPid']) || isset($this->conf['search.']['targetPid.'])) {
2293 if (is_array($this->conf['search.']['targetPid.'])) {
2294 $result = $this->cObj->stdWrap($this->conf['search.']['targetPid'], $this->conf['search.']['targetPid.']);
2295 } else {
2296 $result = $this->conf['search.']['targetPid'];
2297 }
2298 $result = intval($result);
2299 }
2300 return $result;
2301 }
2302
2303 /**
2304 * Formats date as 'created' date
2305 *
2306 * @param int $date
2307 * @param string $defaultFormat
2308 * @return string
2309 */
2310 protected function formatCreatedDate($date) {
2311 $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2312 return $this->formatDate($date, 'created', $defaultFormat);
2313 }
2314
2315 /**
2316 * Formats date as 'modified' date
2317 *
2318 * @param int $date
2319 * @param string $defaultFormat
2320 * @return string
2321 */
2322 protected function formatModifiedDate($date) {
2323 $defaultFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ') . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2324 return $this->formatDate($date, 'modified', $defaultFormat);
2325 }
2326
2327 /**
2328 * Formats the date using format string from TypoScript or default format
2329 * if TypoScript format is not set
2330 *
2331 * @param int $date
2332 * @param string $tsKey
2333 * @param string $defaultFormat
2334 * @return string
2335 */
2336 protected function formatDate($date, $tsKey, $defaultFormat) {
2337 $strftimeFormat = $this->conf['dateFormat.'][$tsKey];
2338 if ($strftimeFormat) {
2339 $result = strftime($strftimeFormat, $date);
2340 } else {
2341 $result = date($defaultFormat, $date);
2342 }
2343 return $result;
2344 }
2345
2346 }
2347
2348
2349 ?>