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