6d84f92d8b983f7a17eac4c016fea97ccb57587c
[Packages/TYPO3.CMS.git] / typo3 / sysext / indexed_search / Classes / Controller / SearchController.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2011 Benjamin Mack (benni@typo3.org)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Index search frontend
29 *
30 * Creates a searchform for indexed search. Indexing must be enabled
31 * for this to make sense.
32 *
33 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
34 * @author Christian Jul Jensen <christian@typo3.com>
35 * @author Benjamin Mack <benni@typo3.org>
36 *
37 * The following variables can be sent from the form
38 *
39 * _sections
40 * _freeIndexUid
41 * pointer
42 * sword
43 * sword_prev
44 * sword_prev_include
45 * type
46 * defOp
47 * media
48 * lang
49 * sections
50 * freeIndexUid
51 * order
52 * desc
53 * results
54 * group
55 * extResume
56 * submit_button
57 */
58 class Tx_IndexedSearch_Controller_SearchController extends Tx_Extbase_MVC_Controller_ActionController {
59
60
61 // previously known as $this->piVars['sword']
62 protected $sword = NULL;
63 protected $searchWords = array();
64 protected $searchData;
65
66 // This is the id of the site root.
67 // This value may be a commalist of integer (prepared for this)
68 // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
69 protected $searchRootPageIdList = 0;
70
71 protected $defaultResultNumber = 10;
72
73
74 /**
75 * Lexer object
76 *
77 * @var Tx_IndexedSearch_Domain_Repository_IndexSearchRepository
78 */
79 protected $searchRepository = NULL;
80
81 /**
82 * Lexer object
83 *
84 * @var tx_indexedsearch_lexer
85 */
86 protected $lexerObj;
87
88 // External parser objects
89 protected $externalParsers = array();
90
91 // Will hold the first row in result - used to calculate relative hit-ratings.
92 protected $firstRow = array();
93
94 // Domain records (needed ?)
95 protected $domainRecords = array();
96 // Required fe_groups memberships for display of a result.
97 protected $requiredFrontendUsergroups = array();
98 // Page tree sections for search result.
99 protected $resultSections = array();
100 // Caching of page path
101 protected $pathCache = array();
102 // Storage of icons
103 protected $iconFileNameCache = array();
104 // Indexer configuration, coming from $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']
105 protected $indexerConfig = array();
106
107 /**
108 * sets up all necessary object for searching
109 *
110 * @param array $searchData The incoming search parameters
111 * @return array Search parameters
112 */
113 public function initialize($searchData = array()) {
114 if (!is_array($searchData)) {
115 $searchData = array();
116 }
117
118 // setting default values
119 if (is_array($this->settings['defaultOptions'])) {
120 $searchData = array_merge($this->settings['defaultOptions'], $searchData);
121 }
122
123
124 // Indexer configuration from Extension Manager interface:
125 $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']);
126 $this->enableMetaphoneSearch = $this->indexerConfig['enableMetaphoneSearch'] ? TRUE : FALSE;
127
128 $this->initializeExternalParsers();
129
130 // If "_sections" is set, this value overrides any existing value.
131 if ($searchData['_sections']) {
132 $searchData['sections'] = $searchData['_sections'];
133 }
134
135 // If "_sections" is set, this value overrides any existing value.
136 if ($searchData['_freeIndexUid'] !== '' && $searchData['_freeIndexUid'] !== '_') {
137 $searchData['freeIndexUid'] = $searchData['_freeIndexUid'];
138 }
139
140 $searchData['results'] = t3lib_utility_Math::forceIntegerInRange($searchData['results'], 1, 100000, $this->defaultResultNumber);
141
142
143 // This gets the search-words into the $searchWordArray
144 $this->sword = $searchData['sword'];
145
146 // Add previous search words to current
147 if ($searchData['sword_prev_include'] && $searchData['sword_prev']) {
148 $this->sword = trim($searchData['sword_prev']) . ' ' . $this->sword;
149 }
150
151 $this->searchWords = $this->getSearchWords($searchData['defaultOperand']);
152
153
154 // This is the id of the site root.
155 // This value may be a commalist of integer (prepared for this)
156 $this->searchRootPageIdList = intval($GLOBALS['TSFE']->config['rootLine'][0]['uid']);
157
158 // Setting the list of root PIDs for the search. Notice, these page IDs MUST
159 // have a TypoScript template with root flag on them! Basically this list is used
160 // to select on the "rl0" field and page ids are registered as "rl0" only if
161 // a TypoScript template record with root flag is there.
162 // This happens AFTER the use of $this->searchRootPageIdList above because
163 // the above will then fetch the menu for the CURRENT site - regardless
164 // of this kind of searching here. Thus a general search will lookup in
165 // the WHOLE database while a specific section search will take the current sections.
166 if ($this->settings['rootPidList']) {
167 $this->searchRootPageIdList = implode(',', t3lib_div::intExplode(',', $this->settings['rootPidList']));
168 }
169
170 $this->searchRepository = t3lib_div::makeInstance('Tx_IndexedSearch_Domain_Repository_IndexSearchRepository');
171 $this->searchRepository->initialize($this->settings, $searchData, $this->externalParsers, $this->searchRootPageIdList);
172
173 $this->searchData = $searchData;
174
175 // Calling hook for modification of initialized content
176 if ($hookObj = $this->hookRequest('initialize_postProc')) {
177 $hookObj->initialize_postProc();
178 }
179 return $searchData;
180 }
181
182
183 /**
184 * Performs the search, the display and writing stats
185 *
186 * @param array $search the search parameters, an associative array
187 * @return void
188 * @dontvalidate $search
189 */
190 public function searchAction($search = array()) {
191 $searchData = $this->initialize($search);
192
193 // Find free index uid:
194 $freeIndexUid = $searchData['freeIndexUid'];
195 if ($freeIndexUid == -2) {
196 $freeIndexUid = $this->settings['defaultFreeIndexUidList'];
197 } elseif (!isset($searchData['freeIndexUid'])) {
198 // index configuration is disabled
199 $freeIndexUid = -1;
200 }
201
202 $indexCfgs = t3lib_div::intExplode(',', $freeIndexUid);
203
204 $resultsets = array();
205 foreach ($indexCfgs as $freeIndexUid) {
206
207 // Get result rows
208 $tstamp1 = t3lib_div::milliseconds();
209 if ($hookObj = $this->hookRequest('getResultRows')) {
210 $resultData = $hookObj->getResultRows($this->searchWords, $freeIndexUid);
211 } else {
212 $resultData = $this->searchRepository->doSearch($this->searchWords, $freeIndexUid);
213 }
214
215 // Display search results
216 $tstamp2 = t3lib_div::milliseconds();
217 if ($hookObj = $this->hookRequest('getDisplayResults')) {
218 $resultsets[$freeIndexUid] = $hookObj->getDisplayResults($this->searchWords, $resultData, $freeIndexUid);
219 } else {
220 $resultsets[$freeIndexUid] = $this->getDisplayResults($this->searchWords, $resultData, $freeIndexUid);
221 }
222
223 $tstamp3 = t3lib_div::milliseconds();
224
225 // Create header if we are searching more than one indexing configuration
226 if (count($indexCfgs) > 1) {
227 if ($freeIndexUid > 0) {
228 $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
229 'title',
230 'index_config',
231 'uid=' . intval($freeIndexUid) . $GLOBALS['TSFE']->cObj->enableFields('index_config')
232 );
233 $categoryTitle = $indexCfgRec['title'];
234 } else {
235 $categoryTitle = Tx_Extbase_Utility_Localization::translate('indexingConfigurationHeader.' . $freeIndexUid, 'indexed_search');
236 }
237 $resultsets[$freeIndexUid]['categoryTitle'] = $categoryTitle;
238 }
239
240 // Write search statistics
241 $this->writeSearchStat($searchData, $this->searchWords, $resultData['count'], array($tstamp1, $tstamp2, $tstamp3));
242 }
243 $this->view->assign('resultsets', $resultsets);
244 $this->view->assign('searchParams', $searchData);
245 $this->view->assign('searchWords', $this->searchWords);
246 }
247
248
249
250 /****************************************
251 * functions to make the result rows and result sets
252 * ready for the output
253 ***************************************/
254
255
256 /**
257 * Compiles the HTML display of the incoming array of result rows.
258 *
259 * @param array $searchWords Search words array (for display of text describing what was searched for)
260 * @param array $resultData Array with result rows, count, first row.
261 * @param integer $freeIndexUid Pointing to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
262 * @return array
263 */
264 protected function getDisplayResults($searchWords, $resultData, $freeIndexUid = -1) {
265 $result = array(
266 'count' => $resultData['count'],
267 'searchWords' => $searchWords
268 );
269
270 // Perform display of result rows array
271 if ($resultData) {
272 // Set first selected row (for calculation of ranking later)
273 $this->firstRow = $resultData['firstRow'];
274
275 // Result display here
276 $result['rows'] = $this->compileResultRows($resultData['resultRows'], $freeIndexUid);
277 $result['affectedSections'] = $this->resultSections;
278 // Browsing box
279 if ($resultData['count']) {
280
281 // could we get this in the view?
282 if ($this->searchData['group'] == 'sections' && $freeIndexUid <= 0) {
283 $result['sectionText'] = sprintf(Tx_Extbase_Utility_Localization::translate('result.' . (count($this->resultSections) > 1 ? 'inNsections' : 'inNsection'), 'indexed_search'), count($this->resultSections));
284 }
285 }
286 }
287
288 // Print a message telling which words in which sections we searched for
289 if (substr($this->searchData['sections'], 0, 2) == 'rl') {
290 $result['searchedInSectionInfo'] = Tx_Extbase_Utility_Localization::translate('result.inSection', 'indexed_search')
291 . ' "' . substr($this->getPathFromPageId(substr($this->searchData['sections'], 4)), 1) . '"';
292 }
293 return $result;
294 }
295
296 /**
297 * Takes the array with resultrows as input and returns the result-HTML-code
298 * Takes the "group" var into account: Makes a "section" or "flat" display.
299 *
300 * @param array $resultRows Result rows
301 * @param integer $freeIndexUid Pointing to which indexing configuration you want to search in. -1 means no filtering. 0 means only regular indexed content.
302 * @return string HTML
303 */
304 protected function compileResultRows($resultRows, $freeIndexUid = -1) {
305 $finalResultRows = array();
306
307 // Transfer result rows to new variable,
308 // performing some mapping of sub-results etc.
309 $newResultRows = array();
310 foreach ($resultRows as $row) {
311 $id = md5($row['phash_grouping']);
312 if (is_array($newResultRows[$id])) {
313 // swapping:
314 if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) {
315 // Remove old
316 $subrows = $newResultRows[$id]['_sub'];
317 unset($newResultRows[$id]['_sub']);
318 $subrows[] = $newResultRows[$id];
319
320 // Insert new:
321 $newResultRows[$id] = $row;
322 $newResultRows[$id]['_sub'] = $subrows;
323 } else {
324 $newResultRows[$id]['_sub'][] = $row;
325 }
326 } else {
327 $newResultRows[$id] = $row;
328 }
329 }
330 $resultRows = $newResultRows;
331 $this->resultSections = array();
332
333 if ($freeIndexUid <= 0 && $this->searchData['group'] == 'sections') {
334 $rl2flag = substr($this->searchData['sections'], 0, 2) == 'rl';
335 $sections = array();
336 foreach ($resultRows as $row) {
337 $id = $row['rl0'].'-'.$row['rl1'].($rl2flag?'-'.$row['rl2']:'');
338 $sections[$id][] = $row;
339 }
340
341 $this->resultSections = array();
342
343 foreach ($sections as $id => $resultRows) {
344 $rlParts = explode('-', $id);
345 if ($rlParts[2]) {
346 $theId = $rlParts[2];
347 $theRLid = 'rl2_' . $rlParts[2];
348 } elseif ($rlParts[1]) {
349 $theId = $rlParts[1];
350 $theRLid = 'rl1_' . $rlParts[1];
351 } else {
352 $theId = $rlParts[0];
353 $theRLid = '0';
354 }
355
356 $sectionName = $this->getPathFromPageId($theId);
357 $sectionName = ltrim($sectionName, '/');
358
359 if (!trim($sectionName)) {
360 $sectionTitleLinked = Tx_Extbase_Utility_Localization::translate('result.unnamedSection', 'indexed_search') . ':';
361 } else {
362 $onclick = 'document.'.$this->prefixId.'[\''.$this->prefixId.'[_sections]\'].value=\''.$theRLid.'\';document.'.$this->prefixId.'.submit();return false;';
363 $sectionTitleLinked = '<a href="#" onclick="'.htmlspecialchars($onclick).'">'.htmlspecialchars($sectionName).':</a>';
364 }
365 $this->resultSections[$id] = array($sectionName, count($resultRows));
366
367 // Add section header
368 $finalResultRows[] = array(
369 'isSectionHeader' => TRUE,
370 'numResultRows' => count($resultRows),
371 'anchorName' => 'anchor_' . md5($id),
372 'sectionTitle' => $sectionTitleLinked
373 );
374
375 // Render result rows
376 foreach ($resultRows as $row) {
377 $finalResultRows[] = $this->compileSingleResultRow($row);
378 }
379 }
380 } else {
381 // flat mode or no sections at all
382 foreach ($resultRows as $row) {
383 $finalResultRows[] = $this->compileSingleResultRow($row);
384 }
385 }
386 return $finalResultRows;
387 }
388
389
390
391 /**
392 * This prints a single result row, including a recursive call for subrows.
393 *
394 * @param array $row Search result row
395 * @param integer $headerOnly 1=Display only header (for sub-rows!), 2=nothing at all
396 * @return string HTML code
397 */
398 protected function compileSingleResultRow($row, $headerOnly = 0) {
399 $specRowConf = $this->getSpecialConfigForResultRow($row);
400
401 $resultData = $row;
402 $resultData['headerOnly'] = $headerOnly;
403 $resultData['CSSsuffix'] = $specRowConf['CSSsuffix'] ? '-' . $specRowConf['CSSsuffix'] : '';
404
405 if ($this->multiplePagesType($row['item_type'])) {
406 $dat = unserialize($row['cHashParams']);
407 $pp = explode('-', $dat['key']);
408 if ($pp[0] != $pp[1]) {
409 $resultData['titleaddition'] = ', ' . Tx_Extbase_Utility_Localization::translate('result.page', 'indexed_search') . ' ' . $dat['key'];
410 } else {
411 $resultData['titleaddition'] = ', ' . Tx_Extbase_Utility_Localization::translate('result.pages', 'indexed_search') . ' ' . $pp[0];
412 }
413 }
414 $title = $resultData['item_title'] . $resultData['titleaddition'];
415 $title = htmlspecialchars($title);
416
417
418 // If external media, link to the media-file instead.
419 if ($row['item_type']) {
420 if ($row['show_resume']) {
421 // Can link directly.
422 $targetAttribute = '';
423 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
424 $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
425 }
426 $title = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' .
427 $title .
428 '</a>';
429 } else {
430 // Suspicious, so linking to page instead...
431 $copiedRow = $row;
432 unset($copiedRow['cHashParams']);
433 $title = $this->linkPage($row['page_id'], $title, $copiedRow);
434 }
435 } else {
436 // Else the page:
437
438 // Prepare search words for markup in content:
439 if ($this->settings['forwardSearchWordsInResultLink']) {
440 $markUpSwParams = array('no_cache' => 1);
441 foreach ($this->sWArr as $d) {
442 $markUpSwParams['sword_list'][] = $d['sword'];
443 }
444 } else {
445 $markUpSwParams = array();
446 }
447 $title = $this->linkPage($row['data_page_id'], $title, $row, $markUpSwParams);
448 }
449
450 $resultData['title'] = $title;
451 $resultData['icon'] = $this->makeItemTypeIcon($row['item_type'], '', $specRowConf);
452 $resultData['rating'] = $this->makeRating($row);
453 $resultData['description'] = $this->makeDescription($row, ($this->searchData['extResume'] && !$headerOnly) ? 0 : 1);
454 $resultData['language'] = $this->makeLanguageIndication($row);
455
456 $resultData['size'] = t3lib_div::formatSize($row['item_size']);
457 $resultData['created'] = $row['item_crdate'];
458 $resultData['modified'] = $row['item_mtime'];
459
460 $pI = parse_url($row['data_filename']);
461 if ($pI['scheme']) {
462 $targetAttribute = '';
463 if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
464 $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
465 }
466 $resultData['path'] = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' .
467 htmlspecialchars($row['data_filename']) .
468 '</a>';
469 } else {
470 $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
471 $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
472 $pathStr = htmlspecialchars($this->getPathFromPageId($pathId, $pathMP));
473 $resultData['path'] = $this->linkPage($pathId, $pathStr, array(
474 'cHashParams' => $row['cHashParams'],
475 'data_page_type' => $row['data_page_type'],
476 'data_page_mp' => $pathMP,
477 'sys_language_uid' => $row['sys_language_uid'],
478 ));
479
480 // check if the access is restricted
481 if (is_array($this->requiredFrontendUsergroups[$id]) && count($this->requiredFrontendUsergroups[$id])) {
482 $resultData['access'] = '<img src="' . t3lib_extMgm::siteRelPath('indexed_search') . 'pi/res/locked.gif" width="12" height="15" vspace="5" title="' . sprintf(Tx_Extbase_Utility_Localization::translate('result.memberGroups', 'indexed_search'), implode(',', array_unique($this->requiredFrontendUsergroups[$id]))) . '" alt="" />';
483 }
484 }
485
486 // If there are subrows (eg. subpages in a PDF-file or if a duplicate page
487 // is selected due to user-login (phash_grouping))
488 if (is_array($row['_sub'])) {
489 $resultData['subresults'] = array();
490 if ($this->multiplePagesType($row['item_type'])) {
491 $resultData['subresults']['header'] = Tx_Extbase_Utility_Localization::translate('result.otherMatching', 'indexed_search');
492 foreach ($row['_sub'] as $subRow) {
493 $resultData['subresults']['items'][] = $this->compileSingleResultRow($subRow, 1);
494 }
495 } else {
496 $resultData['subresults']['header'] = Tx_Extbase_Utility_Localization::translate('result.otherMatching', 'indexed_search');
497 $resultData['subresults']['info'] = Tx_Extbase_Utility_Localization::translate('result.otherPageAsWell', 'indexed_search');
498 }
499 }
500
501 return $resultData;
502 }
503
504 /**
505 * Returns configuration from TypoScript for result row based
506 * on ID / location in page tree!
507 *
508 * @param array $row Result row
509 * @return array Configuration array
510 */
511 protected function getSpecialConfigForResultRow($row) {
512 $pathId = $row['data_page_id'] ? $row['data_page_id'] : $row['page_id'];
513 $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
514
515 $rl = $GLOBALS['TSFE']->sys_page->getRootLine($pathId, $pathMP);
516 $specConf = $this->settings['specialConfiguration.']['0.'];
517 if (is_array($rl)) {
518 foreach ($rl as $dat) {
519 if (is_array($this->conf['specialConfiguration.'][$dat['uid'] . '.'])) {
520 $specConf = $this->conf['specialConfiguration.'][$dat['uid'] . '.'];
521 $specConf['_pid'] = $dat['uid'];
522 break;
523 }
524 }
525 }
526
527 return $specConf;
528 }
529
530 /**
531 * Return the rating-HTML code for the result row. This makes use of the $this->firstRow
532 *
533 * @param array $row Result row array
534 * @return string String showing ranking value
535 * @todo can this be a ViewHelper?
536 */
537 protected function makeRating($row) {
538
539 switch((string) $this->searchData['sortOrder']) {
540
541 // Number of occurencies on page
542 case 'rank_count':
543 return $row['order_val'] . ' ' . Tx_Extbase_Utility_Localization::translate('result.ratingMatches', 'indexed_search');
544 break;
545
546 // Close to top of page
547 case 'rank_first':
548 return ceil(t3lib_utility_Math::forceIntegerInRange(255 - $row['order_val'], 1, 255) / 255 * 100) . '%';
549 break;
550
551 // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
552 case 'rank_flag':
553 if ($this->firstRow['order_val2']) {
554 // (3 MSB bit, 224 is highest value of order_val1 currently)
555 $base = $row['order_val1'] * 256;
556 // 15-3 MSB = 12
557 $freqNumber = $row['order_val2'] / $this->firstRow['order_val2'] * pow(2, 12);
558 $total = t3lib_utility_Math::forceIntegerInRange($base + $freqNumber, 0, 32767);
559 return ceil(log($total) / log(32767) * 100) . '%';
560 }
561 break;
562
563 // Based on frequency
564 case 'rank_freq':
565 $max = 10000;
566 $total = t3lib_utility_Math::forceIntegerInRange($row['order_val'], 0, $max);
567 return ceil(log($total) / log($max) * 100) . '%';
568 break;
569
570 // Based on creation date
571 case 'crdate':
572 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'], 0);
573 break;
574
575 // Based on modification time
576 case 'mtime':
577 return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'], 0);
578 break;
579
580 // fx. title
581 default:
582 return ' ';
583 break;
584 }
585 }
586
587
588
589 /**
590 * Returns the HTML code for language indication.
591 *
592 * @param array $row Result row
593 * @return string HTML code for result row.
594 */
595 protected function makeLanguageIndication($row) {
596 $output = '&nbsp;';
597
598 // If search result is a TYPO3 page:
599 if ((string) $row['item_type'] === '0') {
600
601 // If TypoScript is used to render the flag:
602 if (is_array($this->settings['flagRendering.'])) {
603 $cObj = t3lib_div::makeInstance('tslib_cObj');
604 $cObj->setCurrentVal($row['sys_language_uid']);
605 $output = $cObj->cObjGetSingle($this->settings['flagRendering'], $this->settings['flagRendering.']);
606 } else {
607 // ... otherwise, get flag from sys_language record:
608 $languageRow = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
609 'flag, title',
610 'sys_language',
611 'uid=' . intval($row['sys_language_uid'])
612 . $GLOBALS['TSFE']->cObj->enableFields('sys_language')
613 );
614
615 // Flag code:
616 $flag = $languageRow['flag'];
617 if ($flag) {
618
619 // FIXME not all flags from typo3/gfx/flags
620 // are available in media/flags/
621 $file = substr(PATH_tslib, strlen(PATH_site)) . 'media/flags/flag_' . $flag;
622 $imgInfo = @getimagesize(PATH_site . $file);
623
624 if (is_array($imgInfo)) {
625 $output = '<img src="' . $file . '" ' . $imgInfo[3] . ' title="' . htmlspecialchars($languageRow['title']) . '" alt="' . htmlspecialchars($languageRow['title']) . '" />';
626 }
627 }
628 }
629 }
630 return $output;
631 }
632
633
634
635 /**
636 * Return icon for file extension
637 *
638 * @param string $imageType File extension / item type
639 * @param string $alt Title attribute value in icon.
640 * @param array $specRowConf TypoScript configuration specifically for search result.
641 * @return string <img> tag for icon
642 */
643 function makeItemTypeIcon($imageType, $alt, $specRowConf) {
644
645 // Build compound key if item type is 0, iconRendering is not used
646 // and specConfs.[pid].pageIcon was set in TS
647 if ($imageType === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->settings['iconRendering.'])) {
648 $imageType .= ':' . $specRowConf['_pid'];
649 }
650
651 if (!isset($this->iconFileNameCache[$imageType])) {
652 $this->iconFileNameCache[$imageType] = '';
653
654 // If TypoScript is used to render the icon:
655 if (is_array($this->settings['iconRendering.'])) {
656 $this->cObj->setCurrentVal($imageType);
657 $this->iconFileNameCache[$imageType] = $this->cObj->cObjGetSingle($this->settings['iconRendering'], $this->settings['iconRendering.']);
658
659 // ... otherwise, get flag from sys_language record:
660 } else {
661
662 // Default creation / finding of icon:
663 $icon = '';
664 if ($imageType === '0' || substr($imageType, 0, 2) == '0:') {
665 if (is_array($specRowConf['pageIcon.'])) {
666 $this->iconFileNameCache[$imageType] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
667 } else {
668 $icon = 'EXT:indexed_search/pi/res/pages.gif';
669 }
670 } elseif ($this->externalParsers[$imageType]) {
671 $icon = $this->externalParsers[$imageType]->getIcon($imageType);
672 }
673
674 if ($icon) {
675 $fullPath = t3lib_div::getFileAbsFileName($icon);
676
677 if ($fullPath) {
678 $info = @getimagesize($fullPath);
679 $iconPath = substr($fullPath, strlen(PATH_site));
680 $this->iconFileNameCache[$imageType] = (is_array($info)) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
681 }
682 }
683 }
684 }
685 return $this->iconFileNameCache[$imageType];
686 }
687
688
689
690 /**
691 * Returns the resume for the search-result.
692 *
693 * @param array $row Search result row
694 * @param boolean $noMarkup 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.
695 * @param integer $length String length
696 * @return string HTML string
697 * @todo overwork this
698 */
699 protected function makeDescription($row, $noMarkup = FALSE, $lgd = 180) {
700 if ($row['show_resume']) {
701 if (!$noMarkup) {
702 $markedSW = '';
703 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash=' . intval($row['phash']));
704 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
705 // Cut HTTP references after some length
706 $content = preg_replace('/(http:\/\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
707 $markedSW = $this->markupSWpartsOfString($content);
708 }
709 $GLOBALS['TYPO3_DB']->sql_free_result($res);
710 }
711
712 if (!trim($markedSW)) {
713 $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8', $row['item_description'], $length);
714 $outputStr = htmlspecialchars($outputStr);
715 }
716 $output = $outputStr ? $outputStr : $markedSW;
717 $output = $GLOBALS['TSFE']->csConv($output, 'utf-8');
718 } else {
719 $output = '<span class="noResume">' . Tx_Extbase_Utility_Localization::translate('result.noResume', 'indexed_search') . '</span>';
720 }
721
722 return $output;
723 }
724
725 /**
726 * Marks up the search words from $this->sWarr in the $str with a color.
727 *
728 * @param string $str Text in which to find and mark up search words. This text is assumed to be UTF-8 like the search words internally is.
729 * @return string Processed content
730 */
731 protected function markupSWpartsOfString($str) {
732
733 // Init:
734 $str = str_replace('&nbsp;',' ',t3lib_parsehtml::bidir_htmlspecialchars($str,-1));
735 $str = preg_replace('/\s\s+/',' ',$str);
736 $swForReg = array();
737
738 // Prepare search words for regex:
739 foreach ($this->sWArr as $d) {
740 $swForReg[] = preg_quote($d['sword'],'/');
741 }
742 $regExString = '('.implode('|',$swForReg).')';
743
744 // Split and combine:
745 $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
746 // Constants:
747 $summaryMax = 300;
748 $postPreLgd = 60;
749 $postPreLgd_offset = 5;
750 $divider = ' ... ';
751
752 $occurencies = (count($parts)-1)/2;
753 if ($occurencies) {
754 $postPreLgd = t3lib_utility_Math::forceIntegerInRange($summaryMax / $occurencies, $postPreLgd, $summaryMax / 2);
755 }
756
757 // Variable:
758 $summaryLgd = 0;
759 $output = array();
760
761 // Shorten in-between strings:
762 foreach ($parts as $k => $strP) {
763 if (($k % 2) == 0) {
764
765 // Find length of the summary part:
766 $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
767 $output[$k] = $parts[$k];
768
769 // Possibly shorten string:
770 if (!$k) { // First entry at all (only cropped on the frontside)
771 if ($strLen > $postPreLgd) {
772 $output[$k] = $divider.preg_replace('/^[^[:space:]]+[[:space:]]/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
773 }
774 } elseif ($summaryLgd > $summaryMax || !isset($parts[$k+1])) { // In case summary length is exceed OR if there are no more entries at all:
775 if ($strLen > $postPreLgd) {
776 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], $postPreLgd - $postPreLgd_offset)) . $divider;
777 }
778 } else { // In-between search words:
779 if ($strLen > $postPreLgd * 2) {
780 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], $postPreLgd - $postPreLgd_offset)).
781 $divider.
782 preg_replace('/^[^[:space:]]+[[:space:]]/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
783 }
784 }
785 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);
786
787 // Protect output:
788 $output[$k] = htmlspecialchars($output[$k]);
789
790 // If summary lgd is exceed, break the process:
791 if ($summaryLgd > $summaryMax) {
792 break;
793 }
794 } else {
795 $summaryLgd += $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $strP);
796 $output[$k] = '<strong class="tx-indexedsearch-redMarkup">' . htmlspecialchars($parts[$k]) . '</strong>';
797 }
798 }
799
800 // Return result:
801 return implode('', $output);
802 }
803
804
805
806 /**
807 * Write statistics information to database for the search operation
808 *
809 * @param array search params
810 * @param array Search Word array
811 * @param integer Number of hits
812 * @param integer Milliseconds the search took
813 * @return void
814 */
815 protected function writeSearchStat($searchParams, $searchWords, $count, $pt) {
816 $insertFields = array(
817 'searchstring' => $this->sword,
818 'searchoptions' => serialize(array($searchParams, $searchWords, $pt)),
819 'feuser_id' => intval($GLOBALS['TSFE']->fe_user->user['uid']),
820 // cookie as set or retrieved. If people has cookies disabled this will vary all the time
821 'cookie' => $GLOBALS['TSFE']->fe_user->id,
822 // Remote IP address
823 'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'),
824 // Number of hits on the search
825 'hits' => intval($count),
826 // Time stamp
827 'tstamp' => $GLOBALS['EXEC_TIME']
828 );
829
830 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
831 $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
832
833 if ($newId) {
834 foreach ($searchWords as $val) {
835 $insertFields = array(
836 'word' => $val['sword'],
837 'index_stat_search_id' => $newId,
838 // Time stamp
839 'tstamp' => $GLOBALS['EXEC_TIME'],
840 // search page id for indexed search stats
841 'pageid' => $GLOBALS['TSFE']->id
842 );
843
844 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
845 }
846 }
847 }
848
849
850
851
852 /**
853 * 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)
854 *
855 * Only words with 2 or more characters are accepted
856 * Max 200 chars total
857 * Space is used to split words, "" can be used search for a whole string
858 * AND, OR and NOT are prefix words, overruling the default operator
859 * +/|/- equals AND, OR and NOT as operators.
860 * All search words are converted to lowercase.
861 *
862 * $defOp is the default operator. 1=OR, 0=AND
863 *
864 * @param boolean $defaultOperator If TRUE, the default operator will be OR, not AND
865 * @return array Search words if any found
866 */
867 protected function getSearchWords($defaultOperator) {
868 // 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!)
869 $searchWords = substr($this->sword, 0, 200);
870
871 // Convert to UTF-8 + conv. entities (was also converted during indexing!)
872 $searchWords = $GLOBALS['TSFE']->csConvObj->utf8_encode($searchWords, $GLOBALS['TSFE']->metaCharset);
873 $searchWords = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($searchWords, TRUE);
874
875 $sWordArray = FALSE;
876 if ($hookObj = $this->hookRequest('getSearchWords')) {
877 $sWordArray = $hookObj->getSearchWords_splitSWords($searchWords, $defaultOperator);
878 } else {
879 // sentence
880 if ($this->searchDat['searchType'] == 20) {
881 $sWordArray = array(
882 array(
883 'sword' => trim($searchWords),
884 'oper' => 'AND'
885 )
886 );
887 } else {
888
889 // case-sensitive. Defines the words, which will be
890 // operators between words
891 $operatorTranslateTable = array(
892 array('+', 'AND'),
893 array('|', 'OR'),
894 array('-', 'AND NOT'),
895 // Add operators for various languages
896 // Converts the operators to UTF-8 and lowercase
897 array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj->utf8_encode(Tx_Extbase_Utility_Localization::translate('localizedOperandAnd', 'indexed_search'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'AND'),
898 array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj->utf8_encode(Tx_Extbase_Utility_Localization::translate('localizedOperandOr', 'indexed_search'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'OR'),
899 array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8',$GLOBALS['TSFE']->csConvObj->utf8_encode(Tx_Extbase_Utility_Localization::translate('localizedOperandNot', 'indexed_search'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'AND NOT'),
900 );
901
902 $search = t3lib_div::makeInstance('tslib_search');
903 $search->default_operator = $defaultOperator == 1 ? 'OR' : 'AND';
904 $search->operator_translate_table = $operatorTranslateTable;
905 $search->register_and_explode_search_string($searchWords);
906
907 if (is_array($search->sword_array)) {
908 $sWordArray = $this->procSearchWordsByLexer($search->sword_array);
909 }
910 }
911 }
912 return $sWordArray;
913 }
914
915 /**
916 * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
917 * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
918 *
919 * @param array $searchWords Search word array
920 * @return array Search word array, processed through lexer
921 */
922 protected function procSearchWordsByLexer($searchWords) {
923 $newSearchWords = array();
924
925 // Init lexer (used to post-processing of search words)
926 $lexerObjRef = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['lexer'] ?
927 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['lexer'] :
928 'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
929 $this->lexerObj = t3lib_div::getUserObj($lexerObjRef);
930
931 // Traverse the search word array
932 foreach ($searchWords as $wordDef) {
933 // No space in word (otherwise it might be a sentense in quotes like "there is").
934 if (strpos($wordDef['sword'], ' ') === FALSE) {
935 // Split the search word by lexer:
936 $res = $this->lexerObj->split2Words($wordDef['sword']);
937
938 // Traverse lexer result and add all words again:
939 foreach ($res as $word) {
940 $newSearchWords[] = array(
941 'sword' => $word,
942 'oper' => $wordDef['oper']
943 );
944 }
945 } else {
946 $newSearchWords[] = $wordDef;
947 }
948 }
949 return $newSearchWords;
950 }
951
952
953
954
955 /**
956 * Sort options about the search form
957 *
958 * @param array $search The search data / params
959 * @return void
960 * @dontvalidate $search
961 */
962 public function formAction($search = array()) {
963 $this->initialize($search);
964
965 // Adding search field value
966 $this->view->assign('sword', $this->sword);
967
968 // Additonal keyword => "Add to current search words"
969 $showAdditionalKeywordSearch = ($this->settings['clearSearchBox'] && $this->settings['clearSearchBox.']['enableSubSearchCheckBox']);
970 if ($showAdditionalKeywordSearch) {
971 $this->view->assign('previousSearchWord', ($this->settings['clearSearchBox'] ? '' : $this->sword));
972 }
973 $this->view->assign('showAdditionalKeywordSearch', $showAdditionalKeywordSearch);
974
975 // Extended search
976 if ($search['extendedSearch']) {
977
978 // "Search for"
979 $allSearchTypes = $this->getAllAvailableSearchTypeOptions();
980 $this->view->assign('allSearchTypes', $allSearchTypes);
981
982 $allDefaultOperands = $this->getAllAvailableOperandsOptions();
983 $this->view->assign('allDefaultOperands', $allDefaultOperands);
984
985 $showTypeSearch = (count($allSearchTypes) || count($allDefaultOperands));
986 $this->view->assign('showTypeSearch', $showTypeSearch);
987
988
989 // "Search in"
990 $allMediaTypes = $this->getAllAvailableMediaTypesOptions();
991 $this->view->assign('allMediaTypes', $allMediaTypes);
992
993 $allLanguageUids = $this->getAllAvailableLanguageOptions();
994 $this->view->assign('allLanguageUids', $allLanguageUids);
995
996 $showMediaAndLanguageSearch = (count($allMediaTypes) || count($allLanguageUids));
997 $this->view->assign('showMediaAndLanguageSearch', $showMediaAndLanguageSearch);
998
999
1000 // Sections
1001 $allSections = $this->getAllAvailableSectionsOptions();
1002 $this->view->assign('allSections', $allSections);
1003
1004 // Free Indexing Configurations
1005 $allIndexConfigurations = $this->getAllAvailableIndexConfigurationsOptions();
1006 $this->view->assign('allIndexConfigurations', $allIndexConfigurations);
1007
1008 // Sorting
1009 $allSortOrders = $this->getAllAvailableSortOrderOptions();
1010 $this->view->assign('allSortOrders', $allSortOrders);
1011 $allSortDescendings = $this->getAllAvailableSortDescendingOptions();
1012 $this->view->assign('allSortDescendings', $allSortDescendings);
1013 $showSortOrders = (count($allSortOrders) || count($allSortDescendings));
1014 $this->view->assign('showSortOrders', $showSortOrders);
1015
1016 // Limits
1017 $allNumberOfResults = $this->getAllAvailableNumberOfResultsOptions();
1018 $this->view->assign('allNumberOfResults', $allNumberOfResults);
1019
1020 $allGroups = $this->getAllAvailableGroupOptions();
1021 $this->view->assign('allGroups', $allGroups);
1022 }
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032 /****************************************
1033 * building together the available options for every dropdown
1034 ***************************************/
1035
1036 /**
1037 * get the values for the "type" selector
1038 *
1039 * @return array Associative array with options
1040 */
1041 protected function getAllAvailableSearchTypeOptions() {
1042 $allOptions = array();
1043 $types = array(0, 1, 2, 3, 10, 20);
1044
1045 $blindSettings = $this->settings['blind.'];
1046 if (!$blindSettings['searchType']) {
1047 foreach ($types as $typeNum) {
1048 $allOptions[$typeNum] = Tx_Extbase_Utility_Localization::translate('searchTypes.' . $typeNum, 'indexed_search');
1049 }
1050 }
1051
1052 // Remove this option if metaphone search is disabled)
1053 if (!$this->enableMetaphoneSearch) {
1054 unset ($allOptions[10]);
1055 }
1056
1057 // disable single entries by TypoScript
1058 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['searchType.']);
1059 return $allOptions;
1060 }
1061
1062
1063 /**
1064 * get the values for the "defaultOperand" selector
1065 *
1066 * @return array Associative array with options
1067 */
1068 protected function getAllAvailableOperandsOptions() {
1069 $allOptions = array();
1070
1071 $blindSettings = $this->settings['blind.'];
1072 if (!$blindSettings['defaultOperand']) {
1073 $allOptions = array(
1074 0 => Tx_Extbase_Utility_Localization::translate('defaultOperands.0', 'indexed_search'),
1075 1 => Tx_Extbase_Utility_Localization::translate('defaultOperands.1', 'indexed_search')
1076 );
1077 }
1078
1079 // disable single entries by TypoScript
1080 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['defaultOperand.']);
1081 return $allOptions;
1082 }
1083
1084 /**
1085 * get the values for the "media type" selector
1086 *
1087 * @return array Associative array with options
1088 */
1089 protected function getAllAvailableMediaTypesOptions() {
1090 $allOptions = array();
1091 $mediaTypes = array(-1, 0, -2);
1092
1093 $blindSettings = $this->settings['blind.'];
1094 if (!$blindSettings['mediaType']) {
1095 foreach ($mediaTypes as $mediaType) {
1096 $allOptions[$mediaType] = Tx_Extbase_Utility_Localization::translate('mediaTypes.' . $mediaType, 'indexed_search');
1097 }
1098
1099 // Add media to search in:
1100 $additionalMedia = trim($this->settings['mediaList']);
1101 if (strlen($additionalMedia) > 0) {
1102 $additionalMedia = t3lib_div::trimExplode(',', $additionalMedia, TRUE);
1103 }
1104
1105 foreach ($this->externalParsers as $extension => $obj) {
1106 // Skip unwanted extensions
1107 if (count($additionalMedia) && !in_array($extension, $additionalMedia)) {
1108 continue;
1109 }
1110
1111 if ($name = $obj->searchTypeMediaTitle($extension)) {
1112 $allOptions[$extension] = Tx_Extbase_Utility_Localization::translate('mediaTypes.' . $extension, $name);
1113 }
1114 }
1115 }
1116
1117 // disable single entries by TypoScript
1118 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['mediaType.']);
1119 return $allOptions;
1120 }
1121
1122
1123
1124 /**
1125 * get the values for the "language" selector
1126 *
1127 * @return array Associative array with options
1128 */
1129 protected function getAllAvailableLanguageOptions() {
1130 $allOptions = array(
1131 '-1' => Tx_Extbase_Utility_Localization::translate('languageUids.-1', 'indexed_search'),
1132 '0' => Tx_Extbase_Utility_Localization::translate('languageUids.0', 'indexed_search')
1133 );
1134
1135 $blindSettings = $this->settings['blind.'];
1136 if (!$blindSettings['languageUid']) {
1137
1138 // Add search languages
1139 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1140 '*',
1141 'sys_language',
1142 '1=1' . $GLOBALS['TSFE']->cObj->enableFields('sys_language')
1143 );
1144 if ($res) {
1145 while ($lang = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1146 $allOptions[$lang['uid']] = $lang['title'];
1147 }
1148 }
1149
1150 // disable single entries by TypoScript
1151 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['languageUid.']);
1152
1153 } else {
1154 $allOptions = array();
1155 }
1156 return $allOptions;
1157 }
1158
1159
1160 /**
1161 * get the values for the "section" selector
1162 * Here values like "rl1_" and "rl2_" + a rootlevel 1/2 id can be added
1163 * to perform searches in rootlevel 1+2 specifically. The id-values can even
1164 * be commaseparated. Eg. "rl1_1,2" would search for stuff inside pages on
1165 * menu-level 1 which has the uid's 1 and 2.
1166 *
1167 * @return array Associative array with options
1168 */
1169 protected function getAllAvailableSectionsOptions() {
1170 $allOptions = array();
1171 $sections = array(0, -1, -2, -3);
1172
1173 $blindSettings = $this->settings['blind.'];
1174 if (!$blindSettings['sections']) {
1175 foreach ($sections as $section) {
1176 $allOptions[$section] = Tx_Extbase_Utility_Localization::translate('sections.' . $section, 'indexed_search');
1177 }
1178 }
1179
1180 // Creating levels for section menu:
1181 // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
1182 if ($this->settings['displayLevel1Sections']) {
1183 $firstLevelMenu = $this->getMenuOfPages($this->searchRootPageIdList);
1184 $labelLevel1 = Tx_Extbase_Utility_Localization::translate('sections.Rl1', 'indexed_search');
1185 $labelLevel2 = Tx_Extbase_Utility_Localization::translate('sections.Rl2', 'indexed_search');
1186 foreach ($firstLevelMenu as $firstLevelKey => $menuItem) {
1187 if (!$menuItem['nav_hide']) {
1188 $allOptions['rl1_' . $menuItem['uid']] = trim($labelLevel1 . ' ' . $menuItem['title']);
1189
1190 if ($this->settings['displayLevel2Sections']) {
1191 $secondLevelMenu = $this->getMenuOfPages($menuItem['uid']);
1192 foreach ($secondLevelMenu as $secondLevelKey => $menuItemLevel2) {
1193 if (!$menuItemLevel2['nav_hide']) {
1194 $allOptions['rl2_' . $menuItemLevel2['uid']] = trim($labelLevel2 . ' ' . $menuItemLevel2['title']);
1195 } else {
1196 unset($secondLevelMenu[$secondLevelKey]);
1197 }
1198 }
1199 $allOptions['rl2_' . implode(',', array_keys($secondLevelMenu))] = Tx_Extbase_Utility_Localization::translate('sections.Rl2All', 'indexed_search');
1200 }
1201 } else {
1202 unset($firstLevelMenu[$firstLevelKey]);
1203 }
1204 }
1205 $allOptions['rl1_' . implode(',', array_keys($firstLevelMenu))] = Tx_Extbase_Utility_Localization::translate('sections.Rl1All', 'indexed_search');
1206 }
1207
1208 // disable single entries by TypoScript
1209 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['sections.']);
1210 return $allOptions;
1211 }
1212
1213
1214 /**
1215 * get the values for the "freeIndexUid" selector
1216 *
1217 * @return array Associative array with options
1218 */
1219 protected function getAllAvailableIndexConfigurationsOptions() {
1220 $allOptions = array(
1221 '-1' => Tx_Extbase_Utility_Localization::translate('indexingConfigurations.-1', 'indexed_search'),
1222 '-2' => Tx_Extbase_Utility_Localization::translate('indexingConfigurations.-2', 'indexed_search'),
1223 '0' => Tx_Extbase_Utility_Localization::translate('indexingConfigurations.0', 'indexed_search')
1224 );
1225 $blindSettings = $this->settings['blind.'];
1226 if (!$blindSettings['indexingConfigurations']) {
1227
1228 // add an additional index configuration
1229 if ($this->settings['defaultFreeIndexUidList']) {
1230 $uidList = t3lib_div::intExplode(',', $this->settings['defaultFreeIndexUidList']);
1231 $indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
1232 'uid,title',
1233 'index_config',
1234 'uid IN (' . implode(',', $uidList). ')'
1235 . $GLOBALS['TSFE']->cObj->enableFields('index_config'),
1236 '',
1237 '',
1238 '',
1239 'uid'
1240 );
1241
1242 foreach ($uidList as $uidValue) {
1243 if (is_array($indexCfgRecords[$uidValue])) {
1244 $allOptions[$uidValue] = $indexCfgRecords[$uidValue]['title'];
1245 }
1246 }
1247 }
1248
1249 // disable single entries by TypoScript
1250 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['indexingConfigurations.']);
1251 } else {
1252 $allOptions = array();
1253 }
1254 return $allOptions;
1255 }
1256
1257 /**
1258 * get the values for the "section" selector
1259 * Here values like "rl1_" and "rl2_" + a rootlevel 1/2 id can be added
1260 * to perform searches in rootlevel 1+2 specifically. The id-values can even
1261 * be commaseparated. Eg. "rl1_1,2" would search for stuff inside pages on
1262 * menu-level 1 which has the uid's 1 and 2.
1263 *
1264 * @return array Associative array with options
1265 */
1266 protected function getAllAvailableSortOrderOptions() {
1267 $allOptions = array();
1268 $sortOrders = array('rank_flag', 'rank_freq', 'rank_first', 'rank_count', 'mtime', 'title', 'crdate');
1269 $blindSettings = $this->settings['blind.'];
1270 if (!$blindSettings['sortOrder']) {
1271 foreach ($sortOrders as $order) {
1272 $allOptions[$order] = Tx_Extbase_Utility_Localization::translate('sortOrders.' . $order, 'indexed_search');
1273 }
1274 }
1275
1276 // disable single entries by TypoScript
1277 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['sortOrder.']);
1278 return $allOptions;
1279 }
1280
1281 /**
1282 * get the values for the "group" selector
1283 *
1284 * @return array Associative array with options
1285 */
1286 protected function getAllAvailableGroupOptions() {
1287 $allOptions = array();
1288 $blindSettings = $this->settings['blind.'];
1289 if (!$blindSettings['groupBy']) {
1290 $allOptions = array(
1291 'sections' => Tx_Extbase_Utility_Localization::translate('groupBy.sections', 'indexed_search'),
1292 'flat' => Tx_Extbase_Utility_Localization::translate('groupBy.flat', 'indexed_search')
1293 );
1294 }
1295
1296 // disable single entries by TypoScript
1297 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['groupBy.']);
1298 return $allOptions;
1299 }
1300
1301 /**
1302 * get the values for the "sortDescending" selector
1303 *
1304 * @return array Associative array with options
1305 */
1306 protected function getAllAvailableSortDescendingOptions() {
1307 $allOptions = array();
1308 $blindSettings = $this->settings['blind.'];
1309 if (!$blindSettings['descending']) {
1310 $allOptions = array(
1311 0 => Tx_Extbase_Utility_Localization::translate('sortOrders.descending', 'indexed_search'),
1312 1 => Tx_Extbase_Utility_Localization::translate('sortOrders.ascending', 'indexed_search')
1313 );
1314 }
1315
1316 // disable single entries by TypoScript
1317 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['descending.']);
1318 return $allOptions;
1319 }
1320
1321 /**
1322 * get the values for the "results" selector
1323 *
1324 * @return array Associative array with options
1325 */
1326 protected function getAllAvailableNumberOfResultsOptions() {
1327 $allOptions = array();
1328 $blindSettings = $this->settings['blind.'];
1329 if (!$blindSettings['numberOfResults']) {
1330 $allOptions = array(
1331 10 => 10,
1332 25 => 25,
1333 50 => 50,
1334 100 => 100
1335 );
1336 }
1337
1338 // disable single entries by TypoScript
1339 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['numberOfResults.']);
1340 return $allOptions;
1341 }
1342
1343
1344 /**
1345 * removes blinding entries from the option list of a selector
1346 *
1347 * @param array $allOptions associative array containing all options
1348 * @param array $blindOptions associative array containing the optionkey as they key and the value = 1 if it should be removed
1349 * @return array Options from $allOptions with some options removed
1350 */
1351 protected function removeOptionsFromOptionList($allOptions, $blindOptions) {
1352 if (is_array($blindOptions)) {
1353 foreach ($blindOptions as $key => $val) {
1354 if ($val == 1) {
1355 unset($allOptions[$key]);
1356 }
1357 }
1358 }
1359 return $allOptions;
1360 }
1361
1362
1363
1364 /**
1365 * Links the $linkText to page $pageUid
1366 *
1367 * @param integer $pageUid Page id
1368 * @param string $linkText Title String to link
1369 * @param array $row Result row
1370 * @param array $markUpSwParams Additional parameters for marking up seach words
1371 * @return string <A> tag wrapped title string.
1372 * @todo make use of the UriBuilder
1373 */
1374 protected function linkPage($pageUid, $linkText, $row=array(), $markUpSwParams = array()) {
1375
1376 // Parameters for link
1377 $urlParameters = (array) unserialize($row['cHashParams']);
1378
1379 // Add &type and &MP variable:
1380 if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
1381 if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
1382
1383 // markup-GET vars:
1384 $urlParameters = array_merge($urlParameters, $markUpSwParams);
1385
1386 // This will make sure that the path is retrieved if it hasn't been
1387 // already. Used only for the sake of the domain_record thing.
1388 if (!is_array($this->domainRecords[$pageUid])) {
1389 $this->getPathFromPageId($pageUid);
1390 }
1391
1392
1393 // If external domain, then link to that:
1394 if (count($this->domainRecords[$pageUid])) {
1395 $scheme = t3lib_div::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
1396 $firstDomain = reset($this->domainRecords[$pageUid]);
1397
1398 $additionalParams = '';
1399 if (is_array($urlParameters)) {
1400 if (count($urlParameters)) {
1401 $additionalParams = t3lib_div::implodeArrayForUrl('', $urlParameters);
1402 }
1403 }
1404
1405 $uri = $scheme . $firstDomain . '/index.php?id=' . $pageUid . $additionalParams;
1406
1407 if ($target = $this->settings['detectDomainRecords.']['target']) {
1408 $target = ' target="' . $target . '"';
1409 }
1410 } else {
1411 $uriBuilder = $this->controllerContext->getUriBuilder();
1412 $uri = $uriBuilder
1413 ->setTargetPageUid($pageUid)
1414 ->setTargetPageType($row['data_page_type'])
1415 ->setUseCacheHash(TRUE)
1416 ->setArguments($urlParameters)
1417 ->build();
1418 }
1419 return '<a href="' . htmlspecialchars($uri) . '"' . $target . '>' . htmlspecialchars($linkText) . '</a>';
1420 }
1421
1422
1423
1424 /**
1425 * Return the menu of pages used for the selector.
1426 *
1427 * @param integer $pageUid Page ID for which to return menu
1428 * @return array Menu items (for making the section selector box)
1429 */
1430 protected function getMenuOfPages($pageUid) {
1431 if ($this->settings['displayLevelxAllTypes']) {
1432 $menu = array();
1433 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1434 'title,uid',
1435 'pages',
1436 'pid=' . intval($pageUid)
1437 . $GLOBALS['TSFE']->cObj->enableFields('pages'),
1438 '',
1439 'sorting'
1440 );
1441 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1442 $menu[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
1443 }
1444 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1445 } else {
1446 $menu = $GLOBALS['TSFE']->sys_page->getMenu($pageUid);
1447 }
1448 return $menu;
1449 }
1450
1451
1452 /**
1453 * Returns the path to the page $id
1454 *
1455 * @param integer $id Page ID
1456 * @param string MP variable content
1457 * @return string Path
1458 */
1459 protected function getPathFromPageId($id, $pathMP = '') {
1460 $identStr = $id . '|' . $pathMP;
1461
1462 if (!isset($this->pathCache[$identStr])) {
1463 $this->requiredFrontendUsergroups[$id] = array();
1464 $this->domainRecords[$id] = array();
1465 $rl = $GLOBALS['TSFE']->sys_page->getRootLine($id, $pathMP);
1466 $path = '';
1467 if (is_array($rl) && count($rl)) {
1468 foreach ($rl as $k => $v) {
1469 // Check fe_user
1470 if ($v['fe_group'] && ($v['uid'] == $id || $v['extendToSubpages'])) {
1471 $this->requiredFrontendUsergroups[$id][] = $v['fe_group'];
1472 }
1473
1474 // Check sys_domain
1475 if ($this->settings['detectDomainRcords']) {
1476 $domainName = $this->getFirstSysDomainRecordForPage($v['uid']);
1477 if ($domainName) {
1478 $this->domainRecords[$id][] = $domainName;
1479 // Set path accordingly
1480 $path = $domainName . $path;
1481 break;
1482 }
1483 }
1484
1485 // Stop, if we find that the current id is the current root page.
1486 if ($v['uid'] == $GLOBALS['TSFE']->config['rootLine'][0]['uid']) {
1487 break;
1488 }
1489 $path = '/' . $v['title'] . $path;
1490 }
1491 }
1492
1493 $this->pathCache[$identStr] = $path;
1494 }
1495
1496 return $this->pathCache[$identStr];
1497 }
1498
1499
1500 /**
1501 * Gets the first sys_domain record for the page, $id
1502 *
1503 * @param integer $id Page id
1504 * @return string Domain name
1505 */
1506 protected function getFirstSysDomainRecordForPage($id) {
1507 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1508 'domainName',
1509 'sys_domain',
1510 'pid=' . intval($id)
1511 . $GLOBALS['TSFE']->cObj->enableFields('sys_domain'),
1512 '',
1513 'sorting'
1514 );
1515 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
1516 return rtrim($row['domainName'], '/');
1517 }
1518
1519
1520 /**
1521 * simple function to initialize possible external parsers
1522 * feeds the $this->externalParsers array
1523 *
1524 * @return void
1525 */
1526 protected function initializeExternalParsers() {
1527 // Initialize external document parsers for icon display and other soft operations
1528 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'])) {
1529 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
1530 $this->externalParsers[$extension] = t3lib_div::getUserObj($_objRef);
1531
1532 // Init parser and if it returns FALSE, unset its entry again
1533 if (!$this->externalParsers[$extension]->softInit($extension)) {
1534 unset($this->externalParsers[$extension]);
1535 }
1536 }
1537 }
1538 }
1539
1540
1541 /**
1542 * Returns an object reference to the hook object if any
1543 *
1544 * @param string $functionName Name of the function you want to call / hook key
1545 * @return object Hook object, if any. Otherwise NULL.
1546 */
1547 protected function hookRequest($functionName) {
1548 // Hook: menuConfig_preProcessModMenu
1549 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
1550 $hookObj = t3lib_div::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
1551 if (method_exists($hookObj, $functionName)) {
1552 $hookObj->pObj = $this;
1553 return $hookObj;
1554 }
1555 }
1556 return NULL;
1557 }
1558
1559
1560 /**
1561 * Returns if an item type is a multipage item type
1562 *
1563 * @param string $item_type Item type
1564 * @return boolean TRUE if multipage capable
1565 */
1566 protected function multiplePagesType($item_type) {
1567 return is_object($this->externalParsers[$item_type]) && $this->externalParsers[$item_type]->isMultiplePageExtension($item_type);
1568 }
1569 }
1570 ?>