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