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