04d1db0a070f23ddaed2bf61e1a30b6af9f7858c
[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_div::intInRange($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_div::intInRange(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_div::intInRange($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_div::intInRange($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 // original
625 # $file = TYPO3_mainDir.'gfx/flags/'.$flag;
626 # $imgInfo = @getimagesize(PATH_site.$file);
627
628 if (is_array($imgInfo)) {
629 $output = '<img src="' . $file . '" ' . $imgInfo[3] . ' title="' . htmlspecialchars($languageRow['title']) . '" alt="' . htmlspecialchars($languageRow['title']) . '" />';
630 }
631 }
632 }
633 }
634 return $output;
635 }
636
637
638
639 /**
640 * Return icon for file extension
641 *
642 * @param string $imageType File extension / item type
643 * @param string $alt Title attribute value in icon.
644 * @param array $specRowConf TypoScript configuration specifically for search result.
645 * @return string <img> tag for icon
646 */
647 function makeItemTypeIcon($imageType, $alt, $specRowConf) {
648
649 // Build compound key if item type is 0, iconRendering is not used
650 // and specConfs.[pid].pageIcon was set in TS
651 if ($imageType === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->settings['iconRendering.'])) {
652 $imageType .= ':' . $specRowConf['_pid'];
653 }
654
655 if (!isset($this->iconFileNameCache[$imageType])) {
656 $this->iconFileNameCache[$imageType] = '';
657
658 // If TypoScript is used to render the icon:
659 if (is_array($this->settings['iconRendering.'])) {
660 $this->cObj->setCurrentVal($imageType);
661 $this->iconFileNameCache[$imageType] = $this->cObj->cObjGetSingle($this->settings['iconRendering'], $this->settings['iconRendering.']);
662
663 // ... otherwise, get flag from sys_language record:
664 } else {
665
666 // Default creation / finding of icon:
667 $icon = '';
668 if ($imageType === '0' || substr($imageType, 0, 2) == '0:') {
669 if (is_array($specRowConf['pageIcon.'])) {
670 $this->iconFileNameCache[$imageType] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
671 } else {
672 $icon = 'EXT:indexed_search/pi/res/pages.gif';
673 }
674 } elseif ($this->externalParsers[$imageType]) {
675 $icon = $this->externalParsers[$imageType]->getIcon($imageType);
676 }
677
678 if ($icon) {
679 $fullPath = t3lib_div::getFileAbsFileName($icon);
680
681 if ($fullPath) {
682 $info = @getimagesize($fullPath);
683 $iconPath = substr($fullPath, strlen(PATH_site));
684 $this->iconFileNameCache[$imageType] = (is_array($info)) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
685 }
686 }
687 }
688 }
689 return $this->iconFileNameCache[$imageType];
690 }
691
692
693
694 /**
695 * Returns the resume for the search-result.
696 *
697 * @param array $row Search result row
698 * @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.
699 * @param integer $length String length
700 * @return string HTML string
701 * @todo overwork this
702 */
703 protected function makeDescription($row, $noMarkup = FALSE, $lgd = 180) {
704 if ($row['show_resume']) {
705 if (!$noMarkup) {
706 $markedSW = '';
707 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash=' . intval($row['phash']));
708 if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
709 // Cut HTTP references after some length
710 $content = preg_replace('/(http:\/\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
711 $markedSW = $this->markupSWpartsOfString($content);
712 }
713 $GLOBALS['TYPO3_DB']->sql_free_result($res);
714 }
715
716 if (!trim($markedSW)) {
717 $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8', $row['item_description'], $length);
718 $outputStr = htmlspecialchars($outputStr);
719 }
720 $output = $outputStr ? $outputStr : $markedSW;
721 $output = $GLOBALS['TSFE']->csConv($output, 'utf-8');
722 } else {
723 $output = '<span class="noResume">' . Tx_Extbase_Utility_Localization::translate('result.noResume', 'indexed_search') . '</span>';
724 }
725
726 return $output;
727 }
728
729 /**
730 * Marks up the search words from $this->sWarr in the $str with a color.
731 *
732 * @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.
733 * @return string Processed content
734 */
735 protected function markupSWpartsOfString($str) {
736
737 // Init:
738 $str = str_replace('&nbsp;',' ',t3lib_parsehtml::bidir_htmlspecialchars($str,-1));
739 $str = preg_replace('/\s\s+/',' ',$str);
740 $swForReg = array();
741
742 // Prepare search words for regex:
743 foreach ($this->sWArr as $d) {
744 $swForReg[] = preg_quote($d['sword'],'/');
745 }
746 $regExString = '('.implode('|',$swForReg).')';
747
748 // Split and combine:
749 $parts = preg_split('/'.$regExString.'/i', ' '.$str.' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
750 // debug($parts,$regExString);
751 // Constants:
752 $summaryMax = 300;
753 $postPreLgd = 60;
754 $postPreLgd_offset = 5;
755 $divider = ' ... ';
756
757 $occurencies = (count($parts)-1)/2;
758 if ($occurencies) {
759 $postPreLgd = t3lib_div::intInRange($summaryMax / $occurencies, $postPreLgd, $summaryMax / 2);
760 }
761
762 // Variable:
763 $summaryLgd = 0;
764 $output = array();
765
766 // Shorten in-between strings:
767 foreach ($parts as $k => $strP) {
768 if (($k % 2) == 0) {
769
770 // Find length of the summary part:
771 $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
772 $output[$k] = $parts[$k];
773
774 // Possibly shorten string:
775 if (!$k) { // First entry at all (only cropped on the frontside)
776 if ($strLen > $postPreLgd) {
777 $output[$k] = $divider.preg_replace('/^[^[:space:]]+[[:space:]]/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
778 }
779 } elseif ($summaryLgd > $summaryMax || !isset($parts[$k+1])) { // In case summary length is exceed OR if there are no more entries at all:
780 if ($strLen > $postPreLgd) {
781 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], $postPreLgd - $postPreLgd_offset)) . $divider;
782 }
783 } else { // In-between search words:
784 if ($strLen > $postPreLgd * 2) {
785 $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], $postPreLgd - $postPreLgd_offset)).
786 $divider.
787 preg_replace('/^[^[:space:]]+[[:space:]]/','',$GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
788 }
789 }
790 $summaryLgd+= $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);
791
792 // Protect output:
793 $output[$k] = htmlspecialchars($output[$k]);
794
795 // If summary lgd is exceed, break the process:
796 if ($summaryLgd > $summaryMax) {
797 break;
798 }
799 } else {
800 $summaryLgd += $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $strP);
801 $output[$k] = '<strong class="tx-indexedsearch-redMarkup">' . htmlspecialchars($parts[$k]) . '</strong>';
802 }
803 }
804
805 // Return result:
806 return implode('', $output);
807 }
808
809
810
811 /**
812 * Write statistics information to database for the search operation
813 *
814 * @param array search params
815 * @param array Search Word array
816 * @param integer Number of hits
817 * @param integer Milliseconds the search took
818 * @return void
819 */
820 protected function writeSearchStat($searchParams, $searchWords, $count, $pt) {
821 $insertFields = array(
822 'searchstring' => $this->sword,
823 'searchoptions' => serialize(array($searchParams, $searchWords, $pt)),
824 'feuser_id' => intval($GLOBALS['TSFE']->fe_user->user['uid']),
825 // cookie as set or retrieved. If people has cookies disabled this will vary all the time
826 'cookie' => $GLOBALS['TSFE']->fe_user->id,
827 // Remote IP address
828 'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'),
829 // Number of hits on the search
830 'hits' => intval($count),
831 // Time stamp
832 'tstamp' => $GLOBALS['EXEC_TIME']
833 );
834
835 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
836 $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
837
838 if ($newId) {
839 foreach ($searchWords as $val) {
840 $insertFields = array(
841 'word' => $val['sword'], // $GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $val['sword'], 'toLower'),
842 'index_stat_search_id' => $newId,
843 // Time stamp
844 'tstamp' => $GLOBALS['EXEC_TIME'],
845 // search page id for indexed search stats
846 'pageid' => $GLOBALS['TSFE']->id
847 );
848
849 $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
850 }
851 }
852 }
853
854
855
856
857 /**
858 * 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)
859 *
860 * Only words with 2 or more characters are accepted
861 * Max 200 chars total
862 * Space is used to split words, "" can be used search for a whole string
863 * AND, OR and NOT are prefix words, overruling the default operator
864 * +/|/- equals AND, OR and NOT as operators.
865 * All search words are converted to lowercase.
866 *
867 * $defOp is the default operator. 1=OR, 0=AND
868 *
869 * @param boolean $defaultOperator If TRUE, the default operator will be OR, not AND
870 * @return array Search words if any found
871 */
872 protected function getSearchWords($defaultOperator) {
873 // 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!)
874 $searchWords = substr($this->sword, 0, 200);
875
876 // Convert to UTF-8 + conv. entities (was also converted during indexing!)
877 $searchWords = $GLOBALS['TSFE']->csConvObj->utf8_encode($searchWords, $GLOBALS['TSFE']->metaCharset);
878 $searchWords = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($searchWords, TRUE);
879
880 $sWordArray = FALSE;
881 if ($hookObj = $this->hookRequest('getSearchWords')) {
882 $sWordArray = $hookObj->getSearchWords_splitSWords($searchWords, $defaultOperator);
883 } else {
884 // sentence
885 if ($this->searchDat['searchType'] == 20) {
886 $sWordArray = array(
887 array(
888 'sword' => trim($searchWords),
889 'oper' => 'AND'
890 )
891 );
892 } else {
893
894 // case-sensitive. Defines the words, which will be
895 // operators between words
896 $operatorTranslateTable = array(
897 array('+' , 'AND'),
898 array('|' , 'OR'),
899 array('-' , 'AND NOT'),
900 // english
901 // array('AND' , 'AND'),
902 // array('OR' , 'OR'),
903 // array('NOT' , 'AND NOT'),
904 // Add operators for various languages
905 // Converts the operators to UTF-8 and lowercase
906 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'),
907 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'),
908 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'),
909 );
910
911 $search = t3lib_div::makeInstance('tslib_search');
912 $search->default_operator = $defaultOperator == 1 ? 'OR' : 'AND';
913 $search->operator_translate_table = $operatorTranslateTable;
914 $search->register_and_explode_search_string($searchWords);
915
916 if (is_array($search->sword_array)) {
917 $sWordArray = $this->procSearchWordsByLexer($search->sword_array);
918 }
919 }
920 }
921 return $sWordArray;
922 }
923
924 /**
925 * Post-process the search word array so it will match the words that was indexed (including case-folding if any)
926 * If any words are splitted into multiple words (eg. CJK will be!) the operator of the main word will remain.
927 *
928 * @param array $searchWords Search word array
929 * @return array Search word array, processed through lexer
930 */
931 protected function procSearchWordsByLexer($searchWords) {
932 $newSearchWords = array();
933
934 // Init lexer (used to post-processing of search words)
935 $lexerObjRef = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['lexer'] ?
936 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['lexer'] :
937 'EXT:indexed_search/class.lexer.php:&tx_indexedsearch_lexer';
938 $this->lexerObj = t3lib_div::getUserObj($lexerObjRef);
939
940 // Traverse the search word array
941 foreach ($searchWords as $wordDef) {
942 // No space in word (otherwise it might be a sentense in quotes like "there is").
943 if (strpos($wordDef['sword'], ' ') === FALSE) {
944 // Split the search word by lexer:
945 $res = $this->lexerObj->split2Words($wordDef['sword']);
946
947 // Traverse lexer result and add all words again:
948 foreach ($res as $word) {
949 $newSearchWords[] = array(
950 'sword' => $word,
951 'oper' => $wordDef['oper']
952 );
953 }
954 } else {
955 $newSearchWords[] = $wordDef;
956 }
957 }
958 return $newSearchWords;
959 }
960
961
962
963
964 /**
965 * Sort options about the search form
966 *
967 * @param array $search The search data / params
968 * @return void
969 * @dontvalidate $search
970 */
971 public function formAction($search = array()) {
972 $this->initialize($search);
973
974 // Adding search field value
975 $this->view->assign('sword', $this->sword);
976
977 // Additonal keyword => "Add to current search words"
978 $showAdditionalKeywordSearch = ($this->settings['clearSearchBox'] && $this->settings['clearSearchBox.']['enableSubSearchCheckBox']);
979 if ($showAdditionalKeywordSearch) {
980 $this->view->assign('previousSearchWord', ($this->settings['clearSearchBox'] ? '' : $this->sword));
981 }
982 $this->view->assign('showAdditionalKeywordSearch', $showAdditionalKeywordSearch);
983
984 // Extended search
985 if ($search['extendedSearch']) {
986
987 // "Search for"
988 $allSearchTypes = $this->getAllAvailableSearchTypeOptions();
989 $this->view->assign('allSearchTypes', $allSearchTypes);
990
991 $allDefaultOperands = $this->getAllAvailableOperandsOptions();
992 $this->view->assign('allDefaultOperands', $allDefaultOperands);
993
994 $showTypeSearch = (count($allSearchTypes) || count($allDefaultOperands));
995 $this->view->assign('showTypeSearch', $showTypeSearch);
996
997
998 // "Search in"
999 $allMediaTypes = $this->getAllAvailableMediaTypesOptions();
1000 $this->view->assign('allMediaTypes', $allMediaTypes);
1001
1002 $allLanguageUids = $this->getAllAvailableLanguageOptions();
1003 $this->view->assign('allLanguageUids', $allLanguageUids);
1004
1005 $showMediaAndLanguageSearch = (count($allMediaTypes) || count($allLanguageUids));
1006 $this->view->assign('showMediaAndLanguageSearch', $showMediaAndLanguageSearch);
1007
1008
1009 // Sections
1010 $allSections = $this->getAllAvailableSectionsOptions();
1011 $this->view->assign('allSections', $allSections);
1012
1013 // Free Indexing Configurations
1014 $allIndexConfigurations = $this->getAllAvailableIndexConfigurationsOptions();
1015 $this->view->assign('allIndexConfigurations', $allIndexConfigurations);
1016
1017 // Sorting
1018 $allSortOrders = $this->getAllAvailableSortOrderOptions();
1019 $this->view->assign('allSortOrders', $allSortOrders);
1020 $allSortDescendings = $this->getAllAvailableSortDescendingOptions();
1021 $this->view->assign('allSortDescendings', $allSortDescendings);
1022 $showSortOrders = (count($allSortOrders) || count($allSortDescendings));
1023 $this->view->assign('showSortOrders', $showSortOrders);
1024
1025 // Limits
1026 $allNumberOfResults = $this->getAllAvailableNumberOfResultsOptions();
1027 $this->view->assign('allNumberOfResults', $allNumberOfResults);
1028
1029 $allGroups = $this->getAllAvailableGroupOptions();
1030 $this->view->assign('allGroups', $allGroups);
1031 }
1032 }
1033
1034
1035
1036
1037
1038
1039
1040
1041 /****************************************
1042 * building together the available options for every dropdown
1043 ***************************************/
1044
1045 /**
1046 * get the values for the "type" selector
1047 *
1048 * @return array Associative array with options
1049 */
1050 protected function getAllAvailableSearchTypeOptions() {
1051 $allOptions = array();
1052 $types = array(0, 1, 2, 3, 10, 20);
1053
1054 $blindSettings = $this->settings['blind.'];
1055 if (!$blindSettings['searchType']) {
1056 foreach ($types as $typeNum) {
1057 $allOptions[$typeNum] = Tx_Extbase_Utility_Localization::translate('searchTypes.' . $typeNum, 'indexed_search');
1058 }
1059 }
1060
1061 // Remove this option if metaphone search is disabled)
1062 if (!$this->enableMetaphoneSearch) {
1063 unset ($allOptions[10]);
1064 }
1065
1066 // disable single entries by TypoScript
1067 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['searchType.']);
1068 return $allOptions;
1069 }
1070
1071
1072 /**
1073 * get the values for the "defaultOperand" selector
1074 *
1075 * @return array Associative array with options
1076 */
1077 protected function getAllAvailableOperandsOptions() {
1078 $allOptions = array();
1079
1080 $blindSettings = $this->settings['blind.'];
1081 if (!$blindSettings['defaultOperand']) {
1082 $allOptions = array(
1083 0 => Tx_Extbase_Utility_Localization::translate('defaultOperands.0', 'indexed_search'),
1084 1 => Tx_Extbase_Utility_Localization::translate('defaultOperands.1', 'indexed_search')
1085 );
1086 }
1087
1088 // disable single entries by TypoScript
1089 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['defaultOperand.']);
1090 return $allOptions;
1091 }
1092
1093 /**
1094 * get the values for the "media type" selector
1095 *
1096 * @return array Associative array with options
1097 */
1098 protected function getAllAvailableMediaTypesOptions() {
1099 $allOptions = array();
1100 $mediaTypes = array(-1, 0, -2);
1101
1102 $blindSettings = $this->settings['blind.'];
1103 if (!$blindSettings['mediaType']) {
1104 foreach ($mediaTypes as $mediaType) {
1105 $allOptions[$mediaType] = Tx_Extbase_Utility_Localization::translate('mediaTypes.' . $mediaType, 'indexed_search');
1106 }
1107
1108 // Add media to search in:
1109 $additionalMedia = trim($this->settings['mediaList']);
1110 if (strlen($additionalMedia) > 0) {
1111 $additionalMedia = t3lib_div::trimExplode(',', $additionalMedia, TRUE);
1112 }
1113
1114 foreach ($this->externalParsers as $extension => $obj) {
1115 // Skip unwanted extensions
1116 if (count($additionalMedia) && !in_array($extension, $additionalMedia)) {
1117 continue;
1118 }
1119
1120 if ($name = $obj->searchTypeMediaTitle($extension)) {
1121 $allOptions[$extension] = Tx_Extbase_Utility_Localization::translate('mediaTypes.' . $extension, $name);
1122 }
1123 }
1124 }
1125
1126 // disable single entries by TypoScript
1127 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['mediaType.']);
1128 return $allOptions;
1129 }
1130
1131
1132
1133 /**
1134 * get the values for the "language" selector
1135 *
1136 * @return array Associative array with options
1137 */
1138 protected function getAllAvailableLanguageOptions() {
1139 $allOptions = array(
1140 '-1' => Tx_Extbase_Utility_Localization::translate('languageUids.-1', 'indexed_search'),
1141 '0' => Tx_Extbase_Utility_Localization::translate('languageUids.0', 'indexed_search')
1142 );
1143
1144 $blindSettings = $this->settings['blind.'];
1145 if (!$blindSettings['languageUid']) {
1146
1147 // Add search languages
1148 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1149 '*',
1150 'sys_language',
1151 '1=1' . $GLOBALS['TSFE']->cObj->enableFields('sys_language')
1152 );
1153 if ($res) {
1154 while ($lang = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1155 $allOptions[$lang['uid']] = $lang['title'];
1156 }
1157 }
1158
1159 // disable single entries by TypoScript
1160 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['languageUid.']);
1161
1162 } else {
1163 $allOptions = array();
1164 }
1165 return $allOptions;
1166 }
1167
1168
1169 /**
1170 * get the values for the "section" selector
1171 * Here values like "rl1_" and "rl2_" + a rootlevel 1/2 id can be added
1172 * to perform searches in rootlevel 1+2 specifically. The id-values can even
1173 * be commaseparated. Eg. "rl1_1,2" would search for stuff inside pages on
1174 * menu-level 1 which has the uid's 1 and 2.
1175 *
1176 * @return array Associative array with options
1177 */
1178 protected function getAllAvailableSectionsOptions() {
1179 $allOptions = array();
1180 $sections = array(0, -1, -2, -3);
1181
1182 $blindSettings = $this->settings['blind.'];
1183 if (!$blindSettings['sections']) {
1184 foreach ($sections as $section) {
1185 $allOptions[$section] = Tx_Extbase_Utility_Localization::translate('sections.' . $section, 'indexed_search');
1186 }
1187 }
1188
1189 // Creating levels for section menu:
1190 // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
1191 if ($this->settings['displayLevel1Sections']) {
1192 $firstLevelMenu = $this->getMenuOfPages($this->searchRootPageIdList);
1193 $labelLevel1 = Tx_Extbase_Utility_Localization::translate('sections.Rl1', 'indexed_search');
1194 $labelLevel2 = Tx_Extbase_Utility_Localization::translate('sections.Rl2', 'indexed_search');
1195 foreach ($firstLevelMenu as $firstLevelKey => $menuItem) {
1196 if (!$menuItem['nav_hide']) {
1197 $allOptions['rl1_' . $menuItem['uid']] = trim($labelLevel1 . ' ' . $menuItem['title']);
1198
1199 if ($this->settings['displayLevel2Sections']) {
1200 $secondLevelMenu = $this->getMenuOfPages($menuItem['uid']);
1201 foreach ($secondLevelMenu as $secondLevelKey => $menuItemLevel2) {
1202 if (!$menuItemLevel2['nav_hide']) {
1203 $allOptions['rl2_' . $menuItemLevel2['uid']] = trim($labelLevel2 . ' ' . $menuItemLevel2['title']);
1204 } else {
1205 unset($secondLevelMenu[$secondLevelKey]);
1206 }
1207 }
1208 $allOptions['rl2_' . implode(',', array_keys($secondLevelMenu))] = Tx_Extbase_Utility_Localization::translate('sections.Rl2All', 'indexed_search');
1209 }
1210 } else {
1211 unset($firstLevelMenu[$firstLevelKey]);
1212 }
1213 }
1214 $allOptions['rl1_' . implode(',', array_keys($firstLevelMenu))] = Tx_Extbase_Utility_Localization::translate('sections.Rl1All', 'indexed_search');
1215 }
1216
1217 // disable single entries by TypoScript
1218 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['sections.']);
1219 return $allOptions;
1220 }
1221
1222
1223 /**
1224 * get the values for the "freeIndexUid" selector
1225 *
1226 * @return array Associative array with options
1227 */
1228 protected function getAllAvailableIndexConfigurationsOptions() {
1229 $allOptions = array(
1230 '-1' => Tx_Extbase_Utility_Localization::translate('indexingConfigurations.-1', 'indexed_search'),
1231 '-2' => Tx_Extbase_Utility_Localization::translate('indexingConfigurations.-2', 'indexed_search'),
1232 '0' => Tx_Extbase_Utility_Localization::translate('indexingConfigurations.0', 'indexed_search')
1233 );
1234 $blindSettings = $this->settings['blind.'];
1235 if (!$blindSettings['indexingConfigurations']) {
1236
1237 // add an additional index configuration
1238 if ($this->settings['defaultFreeIndexUidList']) {
1239 $uidList = t3lib_div::intExplode(',', $this->settings['defaultFreeIndexUidList']);
1240 $indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
1241 'uid,title',
1242 'index_config',
1243 'uid IN (' . implode(',', $uidList). ')'
1244 . $GLOBALS['TSFE']->cObj->enableFields('index_config'),
1245 '',
1246 '',
1247 '',
1248 'uid'
1249 );
1250
1251 foreach ($uidList as $uidValue) {
1252 if (is_array($indexCfgRecords[$uidValue])) {
1253 $allOptions[$uidValue] = $indexCfgRecords[$uidValue]['title'];
1254 }
1255 }
1256 }
1257
1258 // disable single entries by TypoScript
1259 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['indexingConfigurations.']);
1260 } else {
1261 $allOptions = array();
1262 }
1263 return $allOptions;
1264 }
1265
1266 /**
1267 * get the values for the "section" selector
1268 * Here values like "rl1_" and "rl2_" + a rootlevel 1/2 id can be added
1269 * to perform searches in rootlevel 1+2 specifically. The id-values can even
1270 * be commaseparated. Eg. "rl1_1,2" would search for stuff inside pages on
1271 * menu-level 1 which has the uid's 1 and 2.
1272 *
1273 * @return array Associative array with options
1274 */
1275 protected function getAllAvailableSortOrderOptions() {
1276 $allOptions = array();
1277 $sortOrders = array('rank_flag', 'rank_freq', 'rank_first', 'rank_count', 'mtime', 'title', 'crdate');
1278 $blindSettings = $this->settings['blind.'];
1279 if (!$blindSettings['sortOrder']) {
1280 foreach ($sortOrders as $order) {
1281 $allOptions[$order] = Tx_Extbase_Utility_Localization::translate('sortOrders.' . $order, 'indexed_search');
1282 }
1283 }
1284
1285 // disable single entries by TypoScript
1286 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['sortOrder.']);
1287 return $allOptions;
1288 }
1289
1290 /**
1291 * get the values for the "group" selector
1292 *
1293 * @return array Associative array with options
1294 */
1295 protected function getAllAvailableGroupOptions() {
1296 $allOptions = array();
1297 $blindSettings = $this->settings['blind.'];
1298 if (!$blindSettings['groupBy']) {
1299 $allOptions = array(
1300 'sections' => Tx_Extbase_Utility_Localization::translate('groupBy.sections', 'indexed_search'),
1301 'flat' => Tx_Extbase_Utility_Localization::translate('groupBy.flat', 'indexed_search')
1302 );
1303 }
1304
1305 // disable single entries by TypoScript
1306 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['groupBy.']);
1307 return $allOptions;
1308 }
1309
1310 /**
1311 * get the values for the "sortDescending" selector
1312 *
1313 * @return array Associative array with options
1314 */
1315 protected function getAllAvailableSortDescendingOptions() {
1316 $allOptions = array();
1317 $blindSettings = $this->settings['blind.'];
1318 if (!$blindSettings['descending']) {
1319 $allOptions = array(
1320 0 => Tx_Extbase_Utility_Localization::translate('sortOrders.descending', 'indexed_search'),
1321 1 => Tx_Extbase_Utility_Localization::translate('sortOrders.ascending', 'indexed_search')
1322 );
1323 }
1324
1325 // disable single entries by TypoScript
1326 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['descending.']);
1327 return $allOptions;
1328 }
1329
1330 /**
1331 * get the values for the "results" selector
1332 *
1333 * @return array Associative array with options
1334 */
1335 protected function getAllAvailableNumberOfResultsOptions() {
1336 $allOptions = array();
1337 $blindSettings = $this->settings['blind.'];
1338 if (!$blindSettings['numberOfResults']) {
1339 $allOptions = array(
1340 10 => 10,
1341 25 => 25,
1342 50 => 50,
1343 100 => 100
1344 );
1345 }
1346
1347 // disable single entries by TypoScript
1348 $allOptions = $this->removeOptionsFromOptionList($allOptions, $blindSettings['numberOfResults.']);
1349 return $allOptions;
1350 }
1351
1352
1353 /**
1354 * removes blinding entries from the option list of a selector
1355 *
1356 * @param array $allOptions associative array containing all options
1357 * @param array $blindOptions associative array containing the optionkey as they key and the value = 1 if it should be removed
1358 * @return array Options from $allOptions with some options removed
1359 */
1360 protected function removeOptionsFromOptionList($allOptions, $blindOptions) {
1361 if (is_array($blindOptions)) {
1362 foreach ($blindOptions as $key => $val) {
1363 if ($val == 1) {
1364 unset($allOptions[$key]);
1365 }
1366 }
1367 }
1368 return $allOptions;
1369 }
1370
1371
1372
1373 /**
1374 * Links the $linkText to page $pageUid
1375 *
1376 * @param integer $pageUid Page id
1377 * @param string $linkText Title String to link
1378 * @param array $row Result row
1379 * @param array $markUpSwParams Additional parameters for marking up seach words
1380 * @return string <A> tag wrapped title string.
1381 * @todo make use of the UriBuilder
1382 */
1383 protected function linkPage($pageUid, $linkText, $row=array(), $markUpSwParams = array()) {
1384
1385 // Parameters for link
1386 $urlParameters = (array) unserialize($row['cHashParams']);
1387
1388 // Add &type and &MP variable:
1389 if ($row['data_page_mp']) $urlParameters['MP'] = $row['data_page_mp'];
1390 if ($row['sys_language_uid']) $urlParameters['L'] = $row['sys_language_uid'];
1391
1392 // markup-GET vars:
1393 $urlParameters = array_merge($urlParameters, $markUpSwParams);
1394
1395 // This will make sure that the path is retrieved if it hasn't been
1396 // already. Used only for the sake of the domain_record thing.
1397 if (!is_array($this->domainRecords[$pageUid])) {
1398 $this->getPathFromPageId($pageUid);
1399 }
1400
1401
1402 // If external domain, then link to that:
1403 if (count($this->domainRecords[$pageUid])) {
1404 $scheme = t3lib_div::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
1405 $firstDomain = reset($this->domainRecords[$pageUid]);
1406
1407 $additionalParams = '';
1408 if (is_array($urlParameters)) {
1409 if (count($urlParameters)) {
1410 $additionalParams = t3lib_div::implodeArrayForUrl('', $urlParameters);
1411 }
1412 }
1413
1414 $uri = $scheme . $firstDomain . '/index.php?id=' . $pageUid . $additionalParams;
1415
1416 if ($target = $this->settings['detectDomainRecords.']['target']) {
1417 $target = ' target="' . $target . '"';
1418 }
1419 } else {
1420 $uriBuilder = $this->controllerContext->getUriBuilder();
1421 $uri = $uriBuilder
1422 ->setTargetPageUid($pageUid)
1423 ->setTargetPageType($row['data_page_type'])
1424 ->setUseCacheHash(TRUE)
1425 ->setArguments($urlParameters)
1426 ->build();
1427 }
1428 return '<a href="' . htmlspecialchars($uri) . '"' . $target . '>' . htmlspecialchars($linkText) . '</a>';
1429 }
1430
1431
1432
1433 /**
1434 * Return the menu of pages used for the selector.
1435 *
1436 * @param integer $pageUid Page ID for which to return menu
1437 * @return array Menu items (for making the section selector box)
1438 */
1439 protected function getMenuOfPages($pageUid) {
1440 if ($this->settings['displayLevelxAllTypes']) {
1441 $menu = array();
1442 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1443 'title,uid',
1444 'pages',
1445 'pid=' . intval($pageUid)
1446 . $GLOBALS['TSFE']->cObj->enableFields('pages'),
1447 '',
1448 'sorting'
1449 );
1450 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1451 $menu[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
1452 }
1453 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1454 } else {
1455 $menu = $GLOBALS['TSFE']->sys_page->getMenu($pageUid);
1456 }
1457 return $menu;
1458 }
1459
1460
1461 /**
1462 * Returns the path to the page $id
1463 *
1464 * @param integer $id Page ID
1465 * @param string MP variable content
1466 * @return string Path
1467 */
1468 protected function getPathFromPageId($id, $pathMP = '') {
1469 $identStr = $id . '|' . $pathMP;
1470
1471 if (!isset($this->pathCache[$identStr])) {
1472 $this->requiredFrontendUsergroups[$id] = array();
1473 $this->domainRecords[$id] = array();
1474 $rl = $GLOBALS['TSFE']->sys_page->getRootLine($id, $pathMP);
1475 $path = '';
1476 if (is_array($rl) && count($rl)) {
1477 foreach ($rl as $k => $v) {
1478 // Check fe_user
1479 if ($v['fe_group'] && ($v['uid'] == $id || $v['extendToSubpages'])) {
1480 $this->requiredFrontendUsergroups[$id][] = $v['fe_group'];
1481 }
1482
1483 // Check sys_domain
1484 if ($this->settings['detectDomainRcords']) {
1485 $domainName = $this->getFirstSysDomainRecordForPage($v['uid']);
1486 if ($domainName) {
1487 $this->domainRecords[$id][] = $domainName;
1488 // Set path accordingly
1489 $path = $domainName . $path;
1490 break;
1491 }
1492 }
1493
1494 // Stop, if we find that the current id is the current root page.
1495 if ($v['uid'] == $GLOBALS['TSFE']->config['rootLine'][0]['uid']) {
1496 break;
1497 }
1498 $path = '/' . $v['title'] . $path;
1499 }
1500 }
1501
1502 $this->pathCache[$identStr] = $path;
1503 }
1504
1505 return $this->pathCache[$identStr];
1506 }
1507
1508
1509 /**
1510 * Gets the first sys_domain record for the page, $id
1511 *
1512 * @param integer $id Page id
1513 * @return string Domain name
1514 */
1515 protected function getFirstSysDomainRecordForPage($id) {
1516 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1517 'domainName',
1518 'sys_domain',
1519 'pid=' . intval($id)
1520 . $GLOBALS['TSFE']->cObj->enableFields('sys_domain'),
1521 '',
1522 'sorting'
1523 );
1524 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
1525 return rtrim($row['domainName'], '/');
1526 }
1527
1528
1529 /**
1530 * simple function to initialize possible external parsers
1531 * feeds the $this->externalParsers array
1532 *
1533 * @return void
1534 */
1535 protected function initializeExternalParsers() {
1536 // Initialize external document parsers for icon display and other soft operations
1537 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'])) {
1538 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
1539 $this->externalParsers[$extension] = t3lib_div::getUserObj($_objRef);
1540
1541 // Init parser and if it returns FALSE, unset its entry again
1542 if (!$this->externalParsers[$extension]->softInit($extension)) {
1543 unset($this->externalParsers[$extension]);
1544 }
1545 }
1546 }
1547 }
1548
1549
1550 /**
1551 * Returns an object reference to the hook object if any
1552 *
1553 * @param string $functionName Name of the function you want to call / hook key
1554 * @return object Hook object, if any. Otherwise NULL.
1555 */
1556 protected function hookRequest($functionName) {
1557 // Hook: menuConfig_preProcessModMenu
1558 if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
1559 $hookObj = t3lib_div::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
1560 if (method_exists($hookObj, $functionName)) {
1561 $hookObj->pObj = $this;
1562 return $hookObj;
1563 }
1564 }
1565 return NULL;
1566 }
1567
1568
1569 /**
1570 * Returns if an item type is a multipage item type
1571 *
1572 * @param string $item_type Item type
1573 * @return boolean TRUE if multipage capable
1574 */
1575 protected function multiplePagesType($item_type) {
1576 return is_object($this->externalParsers[$item_type]) && $this->externalParsers[$item_type]->isMultiplePageExtension($item_type);
1577 }
1578
1579 }
1580
1581
1582 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/indexed_search/Classes/Controller/SearchController.php'])) {
1583 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/indexed_search/Classes/Controller/SearchController.php']);
1584 }
1585
1586
1587 ?>