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