TYPO3 CMS  TYPO3_7-6
SearchFormController.php
Go to the documentation of this file.
1 <?php
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 
22 
30 {
36  public $prefixId = 'tx_indexedsearch';
37 
43  public $extKey = 'indexed_search';
44 
50  public $join_pages = 0;
51 
52  public $defaultResultNumber = 10;
53 
59  public $operator_translate_table = [['+', 'AND'], ['|', 'OR'], ['-', 'AND NOT']];
60 
66  public $wholeSiteIdList = 0;
67 
73  public $sWArr = [];
74 
80  public $optValues = [];
81 
87  public $firstRow = [];
88 
94  public $cache_path = [];
95 
101  public $cache_rl = [];
102 
108  public $fe_groups_required = [];
109 
115  public $domain_records = [];
116 
122  public $wSelClauses = [];
123 
129  public $resultSections = [];
130 
135  public $external_parsers = [];
136 
142  public $iconFileNameCache = [];
143 
149  public $templateCode = '';
150 
151  public $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
152 
158  public $indexerConfig = [];
159 
163  public $enableMetaphoneSearch = false;
164 
166 
172  public $lexerObj;
173 
181  public function main($content, $conf)
182  {
183  // Initialize:
184  $this->conf = $conf;
185  $this->pi_loadLL('EXT:indexed_search/Resources/Private/Language/locallang_pi.xlf');
186  $this->pi_setPiVarDefaults();
187  // Initialize:
188  $this->initialize();
189  // Do search:
190  // If there were any search words entered...
191  if (is_array($this->sWArr) && !empty($this->sWArr)) {
192  $content = $this->doSearch($this->sWArr);
193  }
194  // Finally compile all the content, form, messages and results:
195  $content = $this->makeSearchForm($this->optValues) . $this->printRules() . $content;
196  return $this->pi_wrapInBaseClass($content);
197  }
198 
204  public function initialize()
205  {
206  // Indexer configuration from Extension Manager interface:
207  $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']);
208  $this->enableMetaphoneSearch = (bool)$this->indexerConfig['enableMetaphoneSearch'];
209  $this->storeMetaphoneInfoAsWords = !\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_words');
210  // Initialize external document parsers for icon display and other soft operations
211  if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'])) {
212  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
213  $this->external_parsers[$extension] = GeneralUtility::getUserObj($_objRef);
214  // Init parser and if it returns FALSE, unset its entry again:
215  if (!$this->external_parsers[$extension]->softInit($extension)) {
216  unset($this->external_parsers[$extension]);
217  }
218  }
219  }
220  // Init lexer (used to post-processing of search words)
221  $lexerObjRef = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['lexer'] ?: \TYPO3\CMS\IndexedSearch\Lexer::class;
222  $this->lexerObj = GeneralUtility::getUserObj($lexerObjRef);
223  // If "_sections" is set, this value overrides any existing value.
224  if ($this->piVars['_sections']) {
225  $this->piVars['sections'] = $this->piVars['_sections'];
226  }
227  // If "_sections" is set, this value overrides any existing value.
228  if ($this->piVars['_freeIndexUid'] !== '_') {
229  $this->piVars['freeIndexUid'] = $this->piVars['_freeIndexUid'];
230  }
231  // Add previous search words to current
232  if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev']) {
233  $this->piVars['sword'] = trim($this->piVars['sword_prev']) . ' ' . $this->piVars['sword'];
234  }
235  $this->piVars['results'] = MathUtility::forceIntegerInRange($this->piVars['results'], 1, 100, $this->defaultResultNumber);
236  // Make sure that some cropping and markup constants used later are defined
237  $this->loadSettings();
238 
239  // Selector-box values defined here:
240  $this->optValues = [
241  'type' => [
242  '0' => $this->pi_getLL('opt_type_0'),
243  '1' => $this->pi_getLL('opt_type_1'),
244  '2' => $this->pi_getLL('opt_type_2'),
245  '3' => $this->pi_getLL('opt_type_3'),
246  '10' => $this->pi_getLL('opt_type_10'),
247  '20' => $this->pi_getLL('opt_type_20')
248  ],
249  'defOp' => [
250  '0' => $this->pi_getLL('opt_defOp_0'),
251  '1' => $this->pi_getLL('opt_defOp_1')
252  ],
253  'sections' => [
254  '0' => $this->pi_getLL('opt_sections_0'),
255  '-1' => $this->pi_getLL('opt_sections_-1'),
256  '-2' => $this->pi_getLL('opt_sections_-2'),
257  '-3' => $this->pi_getLL('opt_sections_-3')
258  ],
259  'freeIndexUid' => [
260  '-1' => $this->pi_getLL('opt_freeIndexUid_-1'),
261  '-2' => $this->pi_getLL('opt_freeIndexUid_-2'),
262  '0' => $this->pi_getLL('opt_freeIndexUid_0')
263  ],
264  'media' => [
265  '-1' => $this->pi_getLL('opt_media_-1'),
266  '0' => $this->pi_getLL('opt_media_0'),
267  '-2' => $this->pi_getLL('opt_media_-2')
268  ],
269  'order' => [
270  'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
271  'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
272  'rank_first' => $this->pi_getLL('opt_order_rank_first'),
273  'rank_count' => $this->pi_getLL('opt_order_rank_count'),
274  'mtime' => $this->pi_getLL('opt_order_mtime'),
275  'title' => $this->pi_getLL('opt_order_title'),
276  'crdate' => $this->pi_getLL('opt_order_crdate')
277  ],
278  'group' => [
279  'sections' => $this->pi_getLL('opt_group_sections'),
280  'flat' => $this->pi_getLL('opt_group_flat')
281  ],
282  'lang' => [
283  -1 => $this->pi_getLL('opt_lang_-1'),
284  0 => $this->pi_getLL('opt_lang_0')
285  ],
286  'desc' => [
287  '0' => $this->pi_getLL('opt_desc_0'),
288  '1' => $this->pi_getLL('opt_desc_1')
289  ],
290  'results' => [
291  '10' => '10',
292  '20' => '20',
293  '50' => '50',
294  '100' => '100'
295  ]
296  ];
297  // Remove this option if metaphone search is disabled)
298  if (!$this->enableMetaphoneSearch) {
299  unset($this->optValues['type']['10']);
300  }
301  // Free Index Uid:
302  if ($this->conf['search.']['defaultFreeIndexUidList']) {
303  $uidList = GeneralUtility::intExplode(',', $this->conf['search.']['defaultFreeIndexUidList']);
304  $indexCfgRecords = $this->databaseConnection->exec_SELECTgetRows('uid,title', 'index_config', 'uid IN (' . implode(',', $uidList) . ')' . $this->cObj->enableFields('index_config'), '', '', '', 'uid');
305  foreach ($uidList as $uidValue) {
306  if (is_array($indexCfgRecords[$uidValue])) {
307  $this->optValues['freeIndexUid'][$uidValue] = $indexCfgRecords[$uidValue]['title'];
308  }
309  }
310  }
311  // Should we use join_pages instead of long lists of uids?
312  if ($this->conf['search.']['skipExtendToSubpagesChecking']) {
313  $this->join_pages = 1;
314  }
315  // Add media to search in:
316  if (trim($this->conf['search.']['mediaList']) !== '') {
317  $mediaList = implode(',', GeneralUtility::trimExplode(',', $this->conf['search.']['mediaList'], true));
318  }
319  foreach ($this->external_parsers as $extension => $obj) {
320  // Skip unwanted extensions
321  if ($mediaList && !GeneralUtility::inList($mediaList, $extension)) {
322  continue;
323  }
324  if ($name = $obj->searchTypeMediaTitle($extension)) {
325  $this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_' . $extension, $name);
326  }
327  }
328  // Add operators for various languages
329  // Converts the operators to UTF-8 and lowercase
330  $this->operator_translate_table[] = [$this->frontendController->csConvObj->conv_case('utf-8', $this->frontendController->csConvObj->utf8_encode($this->pi_getLL('local_operator_AND'), $this->frontendController->renderCharset), 'toLower'), 'AND'];
331  $this->operator_translate_table[] = [$this->frontendController->csConvObj->conv_case('utf-8', $this->frontendController->csConvObj->utf8_encode($this->pi_getLL('local_operator_OR'), $this->frontendController->renderCharset), 'toLower'), 'OR'];
332  $this->operator_translate_table[] = [$this->frontendController->csConvObj->conv_case('utf-8', $this->frontendController->csConvObj->utf8_encode($this->pi_getLL('local_operator_NOT'), $this->frontendController->renderCharset), 'toLower'), 'AND NOT'];
333  // This is the id of the site root. This value may be a commalist of integer (prepared for this)
334  $this->wholeSiteIdList = (int)$this->frontendController->config['rootLine'][0]['uid'];
335  // Creating levels for section menu:
336  // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
337  if ($this->conf['show.']['L1sections']) {
338  $firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
339  foreach ($firstLevelMenu as $optionName => $mR) {
340  if (!$mR['nav_hide']) {
341  $this->optValues['sections']['rl1_' . $mR['uid']] = trim($this->pi_getLL('opt_RL1') . ' ' . $mR['title']);
342  if ($this->conf['show.']['L2sections']) {
343  $secondLevelMenu = $this->getMenu($mR['uid']);
344  foreach ($secondLevelMenu as $kk2 => $mR2) {
345  if (!$mR2['nav_hide']) {
346  $this->optValues['sections']['rl2_' . $mR2['uid']] = trim($this->pi_getLL('opt_RL2') . ' ' . $mR2['title']);
347  } else {
348  unset($secondLevelMenu[$kk2]);
349  }
350  }
351  $this->optValues['sections']['rl2_' . implode(',', array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
352  }
353  } else {
354  unset($firstLevelMenu[$optionName]);
355  }
356  }
357  $this->optValues['sections']['rl1_' . implode(',', array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
358  }
359  // Setting the list of root IDs for the search. Notice, these page IDs MUST have a TypoScript template with root flag on them! Basically this list is used to select on the "rl0" field and page ids are registered as "rl0" only if a TypoScript template record with root flag is there.
360  // This happens AFTER the use of $this->wholeSiteIdList above because the above will then fetch the menu for the CURRENT site - regardless of this kind of searching here. Thus a general search will lookup in the WHOLE database while a specific section search will take the current sections...
361  if ($this->conf['search.']['rootPidList']) {
362  $this->wholeSiteIdList = implode(',', GeneralUtility::intExplode(',', $this->conf['search.']['rootPidList']));
363  }
364  // Load the template
365  $this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
366  // Add search languages:
367  $res = $this->databaseConnection->exec_SELECTquery('*', 'sys_language', '1=1' . $this->cObj->enableFields('sys_language'));
368  while (false !== ($data = $this->databaseConnection->sql_fetch_assoc($res))) {
369  $this->optValues['lang'][$data['uid']] = $data['title'];
370  }
371  $this->databaseConnection->sql_free_result($res);
372  // Calling hook for modification of initialized content
373  if ($hookObj = $this->hookRequest('initialize_postProc')) {
374  $hookObj->initialize_postProc();
375  }
376  // Default values set:
377  // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
378  foreach ($this->optValues as $optionName => $optionValue) {
379  if (!isset($this->piVars[$optionName])) {
380  reset($optionValue);
381  $this->piVars[$optionName] = key($optionValue);
382  }
383  }
384  // Blind selectors:
385  if (is_array($this->conf['blind.'])) {
386  foreach ($this->conf['blind.'] as $optionName => $optionValue) {
387  if (is_array($optionValue)) {
388  foreach ($optionValue as $optionValueSubKey => $optionValueSubValue) {
389  if (!is_array($optionValueSubValue) && $optionValueSubValue && is_array($this->optValues[substr($optionName, 0, -1)])) {
390  unset($this->optValues[substr($optionName, 0, -1)][$optionValueSubKey]);
391  }
392  }
393  } elseif ($optionValue) {
394  // If value is not set, unset the option array
395  unset($this->optValues[$optionName]);
396  }
397  }
398  }
399  // This gets the search-words into the $sWArr:
400  $this->sWArr = $this->getSearchWords($this->piVars['defOp']);
401  }
402 
418  public function getSearchWords($defOp)
419  {
420  // 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!)
421  $inSW = substr($this->piVars['sword'], 0, 200);
422  // Convert to UTF-8 + conv. entities (was also converted during indexing!)
423  $inSW = $this->frontendController->csConvObj->utf8_encode($inSW, $this->frontendController->metaCharset);
424  $inSW = $this->frontendController->csConvObj->entities_to_utf8($inSW, true);
425  $sWordArray = false;
426  if ($hookObj = $this->hookRequest('getSearchWords')) {
427  $sWordArray = $hookObj->getSearchWords_splitSWords($inSW, $defOp);
428  } else {
429  if ($this->piVars['type'] == 20) {
430  // type = Sentence
431  $sWordArray = [['sword' => trim($inSW), 'oper' => 'AND']];
432  } else {
433  $searchWords = \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::getExplodedSearchString($inSW, $defOp == 1 ? 'OR' : 'AND', $this->operator_translate_table);
434  if (is_array($searchWords)) {
435  $sWordArray = $this->procSearchWordsByLexer($searchWords);
436  }
437  }
438  }
439  return $sWordArray;
440  }
441 
449  public function procSearchWordsByLexer($SWArr)
450  {
451  // Init output variable:
452  $newSWArr = [];
453  // Traverse the search word array:
454  foreach ($SWArr as $wordDef) {
455  if (!strstr($wordDef['sword'], ' ')) {
456  // No space in word (otherwise it might be a sentense in quotes like "there is").
457  // Split the search word by lexer:
458  $res = $this->lexerObj->split2Words($wordDef['sword']);
459  // Traverse lexer result and add all words again:
460  foreach ($res as $word) {
461  $newSWArr[] = ['sword' => $word, 'oper' => $wordDef['oper']];
462  }
463  } else {
464  $newSWArr[] = $wordDef;
465  }
466  }
467  // Return result:
468  return $newSWArr;
469  }
470 
471  /*****************************
472  *
473  * Main functions
474  *
475  *****************************/
482  public function doSearch($sWArr)
483  {
484  // Find free index uid:
485  $freeIndexUid = $this->piVars['freeIndexUid'];
486  if ($freeIndexUid == -2) {
487  $freeIndexUid = $this->conf['search.']['defaultFreeIndexUidList'];
488  }
489  $indexCfgs = GeneralUtility::intExplode(',', $freeIndexUid);
490  $accumulatedContent = '';
491  foreach ($indexCfgs as $freeIndexUid) {
492  // Get result rows:
494  if ($hookObj = $this->hookRequest('getResultRows')) {
495  $resData = $hookObj->getResultRows($sWArr, $freeIndexUid);
496  } else {
497  $resData = $this->getResultRows($sWArr, $freeIndexUid);
498  }
499  // Display search results:
501  if ($hookObj = $this->hookRequest('getDisplayResults')) {
502  $content = $hookObj->getDisplayResults($sWArr, $resData, $freeIndexUid);
503  } else {
504  $content = $this->getDisplayResults($sWArr, $resData, $freeIndexUid);
505  }
507  // Create header if we are searching more than one indexing configuration:
508  if (count($indexCfgs) > 1) {
509  if ($freeIndexUid > 0) {
510  $indexCfgRec = $this->databaseConnection->exec_SELECTgetSingleRow('title', 'index_config', 'uid=' . (int)$freeIndexUid . $this->cObj->enableFields('index_config'));
511  $titleString = $indexCfgRec['title'];
512  } else {
513  $titleString = $this->pi_getLL('opt_freeIndexUid_header_' . $freeIndexUid);
514  }
515  $content = '<h1 class="tx-indexedsearch-category">' . htmlspecialchars($titleString) . '</h1>' . $content;
516  }
517  $accumulatedContent .= $content;
518  }
519  // Write search statistics
520  $this->writeSearchStat($sWArr, $resData['count'], [$pt1, $pt2, $pt3]);
521  // Return content:
522  return $accumulatedContent;
523  }
524 
532  public function getResultRows($searchWordArray, $freeIndexUid = -1)
533  {
534  // Getting SQL result pointer. This fetches ALL results (1,000,000 if found)
535  $GLOBALS['TT']->push('Searching result');
536  if ($hookObj = &$this->hookRequest('getResultRows_SQLpointer')) {
537  $res = $hookObj->getResultRows_SQLpointer($searchWordArray, $freeIndexUid);
538  } else {
539  $res = $this->getResultRows_SQLpointer($searchWordArray, $freeIndexUid);
540  }
541  $GLOBALS['TT']->pull();
542  // Organize and process result:
543  $result = false;
544  if ($res) {
545  $totalSearchResultCount = $this->databaseConnection->sql_num_rows($res);
546  // Total search-result count
547  $currentPageNumber = MathUtility::forceIntegerInRange($this->piVars['pointer'], 0, floor($totalSearchResultCount / $this->piVars['results']));
548  // The pointer is set to the result page that is currently being viewed
549  // Initialize result accumulation variables:
550  $positionInSearchResults = 0;
551  $groupingPhashes = [];
552  // Used for filtering out duplicates
553  $groupingChashes = [];
554  // Used for filtering out duplicates BASED ON cHash
555  $firstRow = [];
556  // Will hold the first row in result - used to calculate relative hit-ratings.
557  $resultRows = [];
558  // Will hold the results rows for display.
559  // Should we continue counting and checking of results even if
560  // we are sure they are not displayed in this request?
561  // This will slow down your page rendering, but it allows
562  // precise search result counters.
563  $calculateExactCount = (bool)$this->conf['search.']['exactCount'];
564  $lastResultNumberOnPreviousPage = $currentPageNumber * $this->piVars['results'];
565  $firstResultNumberOnNextPage = ($currentPageNumber + 1) * $this->piVars['results'];
566  $lastResultNumberToAnalyze = ($currentPageNumber + 1) * $this->piVars['results'] + $this->piVars['results'];
567  // Now, traverse result and put the rows to be displayed into an array
568  // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (bool) and "result_number" (counter)
569  while (false !== ($row = $this->databaseConnection->sql_fetch_assoc($res))) {
570  // Set first row:
571  if ($positionInSearchResults === 0) {
572  $firstRow = $row;
573  }
574  $row['show_resume'] = $this->checkResume($row);
575  // Tells whether we can link directly to a document or not (depends on possible right problems)
576  $phashGr = !in_array($row['phash_grouping'], $groupingPhashes);
577  $chashGr = !in_array(($row['contentHash'] . '.' . $row['data_page_id']), $groupingChashes);
578  if ($phashGr && $chashGr) {
579  if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords']) {
580  // Only if the resume may be shown are we going to filter out duplicates...
581  if (!$this->multiplePagesType($row['item_type'])) {
582  // Only on documents which are not multiple pages documents
583  $groupingPhashes[] = $row['phash_grouping'];
584  }
585  $groupingChashes[] = $row['contentHash'] . '.' . $row['data_page_id'];
586  $positionInSearchResults++;
587  // Check if we are inside result range for current page
588  if ($positionInSearchResults > $lastResultNumberOnPreviousPage && $positionInSearchResults <= $lastResultNumberToAnalyze) {
589  // Collect results to display
590  $row['result_number'] = $positionInSearchResults;
591  $resultRows[] = $row;
592  // This may lead to a problem: If the result
593  // check is not stopped here, the search will
594  // take longer. However the result counter
595  // will not filter out grouped cHashes/pHashes
596  // that were not processed yet. You can change
597  // this behavior using the "search.exactCount"
598  // property (see above).
599  $nextResultPosition = $positionInSearchResults + 1;
600  if (!$calculateExactCount && $nextResultPosition > $firstResultNumberOnNextPage) {
601  break;
602  }
603  }
604  } else {
605  // Skip this row if the user cannot view it (missing permission)
606  $totalSearchResultCount--;
607  }
608  } else {
609  // For each time a phash_grouping document is found
610  // (which is thus not displayed) the search-result count
611  // is reduced, so that it matches the number of rows displayed.
612  $totalSearchResultCount--;
613  }
614  }
615  $this->databaseConnection->sql_free_result($res);
616  $result = [
617  'resultRows' => $resultRows,
618  'firstRow' => $firstRow,
619  'count' => $totalSearchResultCount
620  ];
621  }
622  return $result;
623  }
624 
632  public function getResultRows_SQLpointer($sWArr, $freeIndexUid = -1)
633  {
634  // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
635  $list = $this->getPhashList($sWArr);
636  // Perform SQL Search / collection of result rows array:
637  if ($list) {
638  // Do the search:
639  $GLOBALS['TT']->push('execFinalQuery');
640  $res = $this->execFinalQuery($list, $freeIndexUid);
641  $GLOBALS['TT']->pull();
642  return $res;
643  } else {
644  return false;
645  }
646  }
647 
656  public function getDisplayResults($sWArr, $resData, $freeIndexUid = -1)
657  {
658  $content = '';
659  // Perform display of result rows array:
660  if ($resData) {
661  $GLOBALS['TT']->push('Display Final result');
662  // Set first selected row (for calculation of ranking later)
663  $this->firstRow = $resData['firstRow'];
664  // Result display here:
665  $rowcontent = '';
666  $rowcontent .= $this->compileResult($resData['resultRows'], $freeIndexUid);
667  // Browsing box:
668  if ($resData['count']) {
669  $this->internal['res_count'] = $resData['count'];
670  $this->internal['results_at_a_time'] = $this->piVars['results'];
671  $this->internal['maxPages'] = MathUtility::forceIntegerInRange($this->conf['search.']['page_links'], 1, 100, 10);
672  $resultSectionsCount = count($this->resultSections);
673  $addString = $resData['count'] && $this->piVars['group'] == 'sections' && $freeIndexUid <= 0 ? ' ' . sprintf($this->pi_getLL(($resultSectionsCount > 1 ? 'inNsections' : 'inNsection')), $resultSectionsCount) : '';
674  $browseBox1 = $this->renderPagination(1, $addString, $this->printResultSectionLinks(), $freeIndexUid);
675  $browseBox2 = $this->renderPagination(0, '', '', $freeIndexUid);
676  // Browsing nav, bottom.
677  $content = $browseBox1 . $rowcontent . $browseBox2;
678  } else {
679  $content = '<p' . $this->pi_classParam('noresults') . '>' . $this->pi_getLL('noResults', '', true) . '</p>';
680  }
681  $GLOBALS['TT']->pull();
682  } else {
683  $content .= '<p' . $this->pi_classParam('noresults') . '>' . $this->pi_getLL('noResults', '', true) . '</p>';
684  }
685  // Print a message telling which words we searched for, and in which sections etc.
686  $what = $this->tellUsWhatIsSeachedFor($sWArr) . (substr($this->piVars['sections'], 0, 2) == 'rl' ? ' ' . $this->pi_getLL('inSection', '', true) . ' "' . $this->getPathFromPageId(substr($this->piVars['sections'], 4)) . '"' : '');
687  $what = '<div' . $this->pi_classParam('whatis') . '>' . $this->cObj->stdWrap($what, $this->conf['whatis_stdWrap.']) . '</div>';
688  $content = $what . $content;
689  // Return content:
690  return $content;
691  }
692 
701  public function compileResult($resultRows, $freeIndexUid = -1)
702  {
703  $content = '';
704  // Transfer result rows to new variable, performing some mapping of sub-results etc.
705  $newResultRows = [];
706  foreach ($resultRows as $row) {
707  $id = md5($row['phash_grouping']);
708  if (is_array($newResultRows[$id])) {
709  if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) {
710  // swapping:
711  // Remove old
712  $subrows = $newResultRows[$id]['_sub'];
713  unset($newResultRows[$id]['_sub']);
714  $subrows[] = $newResultRows[$id];
715  // Insert new:
716  $newResultRows[$id] = $row;
717  $newResultRows[$id]['_sub'] = $subrows;
718  } else {
719  $newResultRows[$id]['_sub'][] = $row;
720  }
721  } else {
722  $newResultRows[$id] = $row;
723  }
724  }
725  $resultRows = $newResultRows;
726  $this->resultSections = [];
727  if ($freeIndexUid <= 0) {
728  switch ($this->piVars['group']) {
729  case 'sections':
730  $rl2flag = substr($this->piVars['sections'], 0, 2) == 'rl';
731  $sections = [];
732  foreach ($resultRows as $row) {
733  $id = $row['rl0'] . '-' . $row['rl1'] . ($rl2flag ? '-' . $row['rl2'] : '');
734  $sections[$id][] = $row;
735  }
736  $this->resultSections = [];
737  foreach ($sections as $id => $resultRows) {
738  $rlParts = explode('-', $id);
739  $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1] ? $rlParts[1] : $rlParts[0]);
740  $theRLid = $rlParts[2] ? 'rl2_' . $rlParts[2] : ($rlParts[1] ? 'rl1_' . $rlParts[1] : '0');
741  $sectionName = $this->getPathFromPageId($theId);
742  if ($sectionName[0] == '/') {
743  $sectionName = substr($sectionName, 1);
744  }
745  if (!trim($sectionName)) {
746  $sectionTitleLinked = $this->pi_getLL('unnamedSection', '', true) . ':';
747  } elseif ($this->conf['linkSectionTitles']) {
748  $quotedPrefix = GeneralUtility::quoteJSvalue($this->prefixId);
749  $onclick = 'document.forms[' . $quotedPrefix . '][' . GeneralUtility::quoteJSvalue($this->prefixId . '[_sections]') . '].value=' . GeneralUtility::quoteJSvalue($theRLid) . ';document.forms[' . $quotedPrefix . '].submit();return false;';
750  $sectionTitleLinked = '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . $sectionName . ':</a>';
751  } else {
752  $sectionTitleLinked = $sectionName;
753  }
754  $resultRowsCount = count($resultRows);
755  $this->resultSections[$id] = [$sectionName, $resultRowsCount];
756  // Add content header:
757  $content .= $this->makeSectionHeader($id, $sectionTitleLinked, $resultRowsCount);
758  // Render result rows:
759  $resultlist = '';
760  foreach ($resultRows as $row) {
761  $resultlist .= $this->printResultRow($row);
762  }
763  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
764  }
765  break;
766  default:
767  // flat:
768  $resultlist = '';
769  foreach ($resultRows as $row) {
770  $resultlist .= $this->printResultRow($row);
771  }
772  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
773  }
774  } else {
775  $resultlist = '';
776  foreach ($resultRows as $row) {
777  $resultlist .= $this->printResultRow($row);
778  }
779  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
780  }
781  return '<div' . $this->pi_classParam('res') . '>' . $content . '</div>';
782  }
783 
784  /***********************************
785  *
786  * Searching functions (SQL)
787  *
788  ***********************************/
796  public function getPhashList($sWArr)
797  {
798  // Initialize variables:
799  $c = 0;
800  $totalHashList = [];
801  // This array accumulates the phash-values
802  // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
803  foreach ($sWArr as $k => $v) {
804  // Making the query for a single search word based on the search-type
805  $sWord = $v['sword'];
806  $theType = (string)$this->piVars['type'];
807  if (strstr($sWord, ' ')) {
808  // If there are spaces in the search-word, make a full text search instead.
809  $theType = 20;
810  }
811  $GLOBALS['TT']->push('SearchWord "' . $sWord . '" - $theType=' . $theType);
812  // Perform search for word:
813  switch ($theType) {
814  case '1':
815  // Part of word
816  $res = $this->searchWord($sWord, Utility\LikeWildcard::BOTH);
817  break;
818  case '2':
819  // First part of word
820  $res = $this->searchWord($sWord, Utility\LikeWildcard::RIGHT);
821  break;
822  case '3':
823  // Last part of word
824  $res = $this->searchWord($sWord, Utility\LikeWildcard::LEFT);
825  break;
826  case '10':
827  // Sounds like
833  // Initialize the indexer-class
834  $indexerObj = GeneralUtility::makeInstance(\TYPO3\CMS\IndexedSearch\Indexer::class);
835  // Perform metaphone search
836  $res = $this->searchMetaphone($indexerObj->metaphone($sWord, $this->storeMetaphoneInfoAsWords));
837  unset($indexerObj);
838  break;
839  case '20':
840  // Sentence
841  $res = $this->searchSentence($sWord);
842  $this->piVars['order'] = 'mtime';
843  // If there is a fulltext search for a sentence there is a likeliness that sorting cannot be done by the rankings from the rel-table (because no relations will exist for the sentence in the word-table). So therefore mtime is used instead. It is not required, but otherwise some hits may be left out.
844  break;
845  default:
846  // Distinct word
847  $res = $this->searchDistinct($sWord);
848  }
849  // If there was a query to do, then select all phash-integers which resulted from this.
850  if ($res) {
851  // Get phash list by searching for it:
852  $phashList = [];
853  while ($row = $this->databaseConnection->sql_fetch_assoc($res)) {
854  $phashList[] = $row['phash'];
855  }
856  $this->databaseConnection->sql_free_result($res);
857  // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
858  if ($c) {
859  switch ($v['oper']) {
860  case 'OR':
861  $totalHashList = array_unique(array_merge($phashList, $totalHashList));
862  break;
863  case 'AND NOT':
864  $totalHashList = array_diff($totalHashList, $phashList);
865  break;
866  default:
867  // AND...
868  $totalHashList = array_intersect($totalHashList, $phashList);
869  }
870  } else {
871  $totalHashList = $phashList;
872  }
873  }
874  $GLOBALS['TT']->pull();
875  $c++;
876  }
877  return implode(',', $totalHashList);
878  }
879 
887  public function execPHashListQuery($wordSel, $plusQ = '')
888  {
889  return $this->databaseConnection->exec_SELECTquery('IR.phash', 'index_words IW,
890  index_rel IR,
891  index_section ISEC', $wordSel . '
892  AND IW.wid=IR.wid
893  AND ISEC.phash = IR.phash
894  ' . $this->sectionTableWhere() . '
895  ' . $plusQ, 'IR.phash');
896  }
897 
905  public function searchWord($sWord, $wildcard)
906  {
907  $likeWildcard = Utility\LikeWildcard::cast($wildcard);
908  $wSel = $likeWildcard->getLikeQueryPart(
909  'index_words',
910  'IW.baseword',
911  $sWord
912  );
913 
914  $this->wSelClauses[] = $wSel;
915  $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
916  return $res;
917  }
918 
925  public function searchDistinct($sWord)
926  {
928  $this->wSelClauses[] = $wSel;
929  $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
930  return $res;
931  }
932 
939  public function searchSentence($sSentence)
940  {
941  $this->wSelClauses[] = '1=1';
942  $likeWildcard = Utility\LikeWildcard::cast(Utility\LikeWildcard::BOTH);
943  $likePart = $likeWildcard->getLikeQueryPart(
944  'index_fulltext',
945  'IFT.fulltextdata',
946  $sSentence
947  );
948 
949  return $this->databaseConnection->exec_SELECTquery('ISEC.phash',
950  'index_section ISEC, index_fulltext IFT',
951  $likePart . ' AND ISEC.phash = IFT.phash' . $this->sectionTableWhere(), 'ISEC.phash'
952  );
953  }
954 
961  public function searchMetaphone($sWord)
962  {
963  $wSel = 'IW.metaphone=' . $sWord;
964  $this->wSelClauses[] = $wSel;
965  return $this->execPHashListQuery($wSel, ' AND is_stopword=0');
966  }
967 
973  public function sectionTableWhere()
974  {
975  $out = $this->wholeSiteIdList < 0 ? '' : ' AND ISEC.rl0 IN (' . $this->wholeSiteIdList . ')';
976  $match = '';
977  if (substr($this->piVars['sections'], 0, 4) == 'rl1_') {
978  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], 4)));
979  $out .= ' AND ISEC.rl1 IN (' . $list . ')';
980  $match = true;
981  } elseif (substr($this->piVars['sections'], 0, 4) == 'rl2_') {
982  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], 4)));
983  $out .= ' AND ISEC.rl2 IN (' . $list . ')';
984  $match = true;
985  } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
986  // Traversing user configured fields to see if any of those are used to limit search to a section:
987  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
988  if (substr($this->piVars['sections'], 0, strlen($fieldName) + 1) == $fieldName . '_') {
989  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], strlen($fieldName) + 1)));
990  $out .= ' AND ISEC.' . $fieldName . ' IN (' . $list . ')';
991  $match = true;
992  break;
993  }
994  }
995  }
996  // If no match above, test the static types:
997  if (!$match) {
998  switch ((string)$this->piVars['sections']) {
999  case '-1':
1000  // '-1' => 'Only this page',
1001  $out .= ' AND ISEC.page_id=' . $this->frontendController->id;
1002  break;
1003  case '-2':
1004  // '-2' => 'Top + level 1',
1005  $out .= ' AND ISEC.rl2=0';
1006  break;
1007  case '-3':
1008  // '-3' => 'Level 2 and out',
1009  $out .= ' AND ISEC.rl2>0';
1010  break;
1011  }
1012  }
1013  return $out;
1014  }
1015 
1021  public function mediaTypeWhere()
1022  {
1023  switch ((string)$this->piVars['media']) {
1024  case '0':
1025  // '0' => 'Kun TYPO3 sider',
1026  $out = ' AND IP.item_type=' . $this->databaseConnection->fullQuoteStr('0', 'index_phash');
1027  break;
1028  case '-2':
1029  // All external documents
1030  $out = ' AND IP.item_type<>' . $this->databaseConnection->fullQuoteStr('0', 'index_phash');
1031  break;
1032  case '-1':
1033  // All content
1034  $out = '';
1035  break;
1036  default:
1037  $out = ' AND IP.item_type=' . $this->databaseConnection->fullQuoteStr($this->piVars['media'], 'index_phash');
1038  }
1039  return $out;
1040  }
1041 
1047  public function languageWhere()
1048  {
1049  $languageWhere = '';
1050  if ($this->piVars['lang'] >= 0) {
1051  // -1 is the same as ALL language.
1052  $languageWhere = 'AND IP.sys_language_uid=' . (int)$this->piVars['lang'];
1053  }
1054  return $languageWhere;
1055  }
1056 
1063  public function freeIndexUidWhere($freeIndexUid)
1064  {
1065  if ($freeIndexUid < 0) {
1066  return '';
1067  }
1068  // First, look if the freeIndexUid is a meta configuration:
1069  $indexCfgRec = $this->databaseConnection->exec_SELECTgetSingleRow('indexcfgs', 'index_config', 'type=5 AND uid=' . (int)$freeIndexUid . $this->cObj->enableFields('index_config'));
1070  if (is_array($indexCfgRec)) {
1071  $refs = GeneralUtility::trimExplode(',', $indexCfgRec['indexcfgs']);
1072  $list = [-99];
1073  // Default value to protect against empty array.
1074  foreach ($refs as $ref) {
1075  list($table, $uid) = GeneralUtility::revExplode('_', $ref, 2);
1076  switch ($table) {
1077  case 'index_config':
1078  $idxRec = $this->databaseConnection->exec_SELECTgetSingleRow('uid', 'index_config', 'uid=' . (int)$uid . $this->cObj->enableFields('index_config'));
1079  if ($idxRec) {
1080  $list[] = $uid;
1081  }
1082  break;
1083  case 'pages':
1084  $indexCfgRecordsFromPid = $this->databaseConnection->exec_SELECTgetRows('uid', 'index_config', 'pid=' . (int)$uid . $this->cObj->enableFields('index_config'));
1085  foreach ($indexCfgRecordsFromPid as $idxRec) {
1086  $list[] = $idxRec['uid'];
1087  }
1088  break;
1089  }
1090  }
1091  $list = array_unique($list);
1092  } else {
1093  $list = [(int)$freeIndexUid];
1094  }
1095  return ' AND IP.freeIndexUid IN (' . implode(',', $list) . ')';
1096  }
1097 
1105  public function execFinalQuery($list, $freeIndexUid = -1)
1106  {
1107  // Setting up methods of filtering results based on page types, access, etc.
1108  $page_join = '';
1109  $page_where = '';
1110  // Calling hook for alternative creation of page ID list
1111  if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
1112  $page_where = $hookObj->execFinalQuery_idList($list);
1113  } elseif ($this->join_pages) {
1114  // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
1115  $page_join = ',
1116  pages';
1117  $page_where = 'pages.uid = ISEC.page_id
1118  ' . $this->cObj->enableFields('pages') . '
1119  AND pages.no_search=0
1120  AND pages.doktype<200
1121  ';
1122  } elseif ($this->wholeSiteIdList >= 0) {
1123  // Collecting all pages IDs in which to search; filtering out ALL pages that are not accessible due to enableFields. Does NOT look for "no_search" field!
1124  $siteIdNumbers = GeneralUtility::intExplode(',', $this->wholeSiteIdList);
1125  $id_list = [];
1126  foreach ($siteIdNumbers as $rootId) {
1127  $id_list[] = $this->cObj->getTreeList(-1 * $rootId, 9999);
1128  }
1129  $page_where = 'ISEC.page_id IN (' . implode(',', $id_list) . ')';
1130  } else {
1131  // Disable everything... (select all)
1132  $page_where = '1=1';
1133  }
1134  // Indexing configuration clause:
1135  $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
1136  // If any of the ranking sortings are selected, we must make a join with the word/rel-table again, because we need to calculate ranking based on all search-words found.
1137  if (substr($this->piVars['order'], 0, 5) == 'rank_') {
1138  switch ($this->piVars['order']) {
1139  case 'rank_flag':
1140  // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1141  // The ordering is refined with the frequency sum as well.
1142  $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1143  $orderBy = 'order_val1' . $this->isDescending() . ',order_val2' . $this->isDescending();
1144  break;
1145  case 'rank_first':
1146  // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1147  $grsel = 'AVG(IR.first) AS order_val';
1148  $orderBy = 'order_val' . $this->isDescending(1);
1149  break;
1150  case 'rank_count':
1151  // Number of words found
1152  $grsel = 'SUM(IR.count) AS order_val';
1153  $orderBy = 'order_val' . $this->isDescending();
1154  break;
1155  default:
1156  // Frequency sum. I'm not sure if this is the best way to do it (make a sum...). Or should it be the average?
1157  $grsel = 'SUM(IR.freq) AS order_val';
1158  $orderBy = 'order_val' . $this->isDescending();
1159  }
1160 
1161  // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1162  $wordSel = '(' . implode(' OR ', $this->wSelClauses) . ') AND ';
1163 
1164  $res = $this->databaseConnection->exec_SELECTquery(
1165  'ISEC.*, IP.*, ' . $grsel,
1166  'index_words IW,
1167  index_rel IR,
1168  index_section ISEC,
1169  index_phash IP' . $page_join,
1170  $wordSel .
1171  'IP.phash IN (' . $list . ') ' .
1172  $this->mediaTypeWhere() . ' ' . $this->languageWhere() . $freeIndexUidClause . '
1173  AND IW.wid=IR.wid
1174  AND ISEC.phash = IR.phash
1175  AND IP.phash = IR.phash
1176  AND ' . $page_where,
1177  'IP.phash,ISEC.phash,ISEC.phash_t3,ISEC.rl0,ISEC.rl1,ISEC.rl2 ,ISEC.page_id,ISEC.uniqid,IP.phash_grouping,IP.data_filename ,IP.data_page_id ,IP.data_page_reg1,IP.data_page_type,IP.data_page_mp,IP.gr_list,IP.item_type,IP.item_title,IP.item_description,IP.item_mtime,IP.tstamp,IP.item_size,IP.contentHash,IP.crdate,IP.parsetime,IP.sys_language_uid,IP.item_crdate,IP.cHashParams,IP.externalUrl,IP.recordUid,IP.freeIndexUid,IP.freeIndexSetId',
1178  $orderBy
1179  );
1180  } else {
1181  // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1182  $orderBy = '';
1183  switch ((string)$this->piVars['order']) {
1184  case 'title':
1185  $orderBy = 'IP.item_title' . $this->isDescending();
1186  break;
1187  case 'crdate':
1188  $orderBy = 'IP.item_crdate' . $this->isDescending();
1189  break;
1190  case 'mtime':
1191  $orderBy = 'IP.item_mtime' . $this->isDescending();
1192  break;
1193  }
1194  $res = $this->databaseConnection->exec_SELECTquery('ISEC.*, IP.*', 'index_phash IP,index_section ISEC' . $page_join, 'IP.phash IN (' . $list . ') ' . $this->mediaTypeWhere() . ' ' . $this->languageWhere() . $freeIndexUidClause . '
1195  AND IP.phash = ISEC.phash
1196  AND ' . $page_where, 'IP.phash,ISEC.phash,ISEC.phash_t3,ISEC.rl0,ISEC.rl1,ISEC.rl2 ,ISEC.page_id,ISEC.uniqid,IP.phash_grouping,IP.data_filename ,IP.data_page_id ,IP.data_page_reg1,IP.data_page_type,IP.data_page_mp,IP.gr_list,IP.item_type,IP.item_title,IP.item_description,IP.item_mtime,IP.tstamp,IP.item_size,IP.contentHash,IP.crdate,IP.parsetime,IP.sys_language_uid,IP.item_crdate,IP.cHashParams,IP.externalUrl,IP.recordUid,IP.freeIndexUid,IP.freeIndexSetId', $orderBy);
1197  }
1198  return $res;
1199  }
1200 
1208  public function checkResume($row)
1209  {
1210  // If the record is indexed by an indexing configuration, just show it.
1211  // At least this is needed for external URLs and files.
1212  // For records we might need to extend this - for instance block display if record is access restricted.
1213  if ($row['freeIndexUid']) {
1214  return true;
1215  }
1216  // Evaluate regularly indexed pages based on item_type:
1217  if ($row['item_type']) {
1218  // External media:
1219  // For external media we will check the access of the parent page on which the media was linked from.
1220  // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1221  // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1222  // If this is NOT found, there is still a theoretical possibility that another user accessible page would display a link, so maybe the resume of such a document here may be unjustified hidden. But better safe than sorry.
1223  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1224  $res = $this->databaseConnection->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash_t3'] . ' AND gr_list=' . $this->databaseConnection->fullQuoteStr($this->frontendController->gr_list, 'index_grlist'));
1225  } else {
1226  $res = false;
1227  }
1228  if ($res && $this->databaseConnection->sql_num_rows($res)) {
1229  return true;
1230  } else {
1231  return false;
1232  }
1233  } else {
1234  // Ordinary TYPO3 pages:
1235  if ((string)$row['gr_list'] !== (string)$this->frontendController->gr_list) {
1236  // Selecting for the grlist records belonging to the phash-row where the current users gr_list exists. If it is found it is proof that this user has direct access to the phash-rows content although he did not himself initiate the indexing...
1237  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1238  $res = $this->databaseConnection->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash'] . ' AND gr_list=' . $this->databaseConnection->fullQuoteStr($this->frontendController->gr_list, 'index_grlist'));
1239  } else {
1240  $res = false;
1241  }
1242  if ($res && $this->databaseConnection->sql_num_rows($res)) {
1243  return true;
1244  } else {
1245  return false;
1246  }
1247  } else {
1248  return true;
1249  }
1250  }
1251  }
1252 
1261  public function checkExistance($row)
1262  {
1263  return $this->checkExistence($row);
1264  }
1265 
1274  protected function checkExistence($row)
1275  {
1277  $recordExists = true;
1278  // Always expect that page content exists
1279  if ($row['item_type']) {
1280  // External media:
1281  if (!is_file($row['data_filename']) || !file_exists($row['data_filename'])) {
1282  $recordExists = false;
1283  }
1284  }
1285  return $recordExists;
1286  }
1287 
1294  public function isDescending($inverse = false)
1295  {
1296  $desc = $this->piVars['desc'];
1297  if ($inverse) {
1298  $desc = !$desc;
1299  }
1300  return !$desc ? ' DESC' : '';
1301  }
1302 
1311  public function writeSearchStat($sWArr, $count, $pt)
1312  {
1313  $ipAddress = '';
1314  try {
1315  $ipMask = isset($this->indexerConfig['trackIpInStatistic']) ? (int)$this->indexerConfig['trackIpInStatistic'] : 2;
1316  $ipAddress = IpAnonymizationUtility::anonymizeIp(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $ipMask);
1317  } catch (\Exception $e) {
1318  }
1319 
1320  $insertFields = [
1321  'searchstring' => $this->piVars['sword'],
1322  'searchoptions' => serialize([$this->piVars, $sWArr, $pt]),
1323  'feuser_id' => (int)$GLOBALS['TSFE']->user['uid'],
1324  // fe_user id, integer
1325  'cookie' => (string)$GLOBALS['TSFE']->id,
1326  // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1327  'IP' => $ipAddress,
1328  // Remote IP address
1329  'hits' => (int)$count,
1330  // Number of hits on the search.
1331  'tstamp' => $GLOBALS['EXEC_TIME']
1332  ];
1333  $this->databaseConnection->exec_INSERTquery('index_stat_search', $insertFields);
1334  $newId = $this->databaseConnection->sql_insert_id();
1335  if ($newId) {
1336  foreach ($sWArr as $val) {
1337  $insertFields = [
1338  'word' => $val['sword'],
1339  'index_stat_search_id' => $newId,
1340  'tstamp' => $GLOBALS['EXEC_TIME'],
1341  // Time stamp
1342  'pageid' => $this->frontendController->id
1343  ];
1344  $this->databaseConnection->exec_INSERTquery('index_stat_word', $insertFields);
1345  }
1346  }
1347  }
1348 
1349  /***********************************
1350  *
1351  * HTML output functions
1352  *
1353  ***********************************/
1360  public function makeSearchForm($optValues)
1361  {
1362  $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
1363  // Multilangual text
1364  $substituteArray = ['legend', 'searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style', 'freeIndexUid'];
1365  foreach ($substituteArray as $marker) {
1366  $markerArray['###FORM_' . GeneralUtility::strtoupper($marker) . '###'] = $this->pi_getLL('form_' . $marker, '', true);
1367  }
1368  $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label', '', true);
1369  // Adding search field value
1370  $markerArray['###SWORD_VALUE###'] = '';
1371  $markerArray['###PLACEHOLDER###'] = '';
1372  $isHTML5 = $GLOBALS['TSFE']->config['config']['doctype'] === 'html5';
1373  if (!empty($this->piVars['sword'])) {
1374  $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1375  } elseif (!$isHTML5) {
1376  // only use placeholder as default value if we cannot use
1377  // HTML5 placeholder attribute
1378  $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->pi_getLL('default_search_word_entry'));
1379  }
1380  // Add a HTML5 placeholder attribute if the configured doctype allows it
1381  if ($isHTML5) {
1382  $markerArray['###PLACEHOLDER###'] = 'placeholder="' . htmlspecialchars($this->pi_getLL('default_search_word_entry')) . '"';
1383  }
1384  // Additonal keyword => "Add to current search words"
1385  if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1386  $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1387  $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"' : '';
1388  $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch', '', true);
1389  } else {
1390  $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1391  }
1392  $markerArray['###ACTION_URL###'] = htmlspecialchars($this->getSearchFormActionURL());
1393  $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
1394  $hiddenFieldCode = preg_replace('/^\\n\\t(.+)/ms', '$1', $hiddenFieldCode);
1395  // Remove first newline and tab (cosmetical issue)
1396  $hiddenFieldArr = [];
1397  foreach (GeneralUtility::trimExplode(',', $this->hiddenFieldList) as $fieldName) {
1398  $hiddenFieldMarkerArray = [];
1399  $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = $this->prefixId . '[' . $fieldName . ']';
1400  $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string)$this->piVars[$fieldName]);
1401  $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, [], []);
1402  }
1403  // Extended search
1404  if ($this->piVars['ext']) {
1405  // Search for
1406  if (!is_array($optValues['type']) && !is_array($optValues['defOp']) || $this->conf['blind.']['type'] && $this->conf['blind.']['defOp']) {
1407  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1408  } else {
1409  if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
1410  unset($hiddenFieldArr['type']);
1411  $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'], $optValues['type']);
1412  } else {
1413  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1414  }
1415  if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp']) {
1416  $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'], $optValues['defOp']);
1417  } else {
1418  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1419  }
1420  }
1421  // Search in
1422  if (!is_array($optValues['media']) && !is_array($optValues['lang']) || $this->conf['blind.']['media'] && $this->conf['blind.']['lang']) {
1423  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1424  } else {
1425  if (is_array($optValues['media']) && !$this->conf['blind.']['media']) {
1426  unset($hiddenFieldArr['media']);
1427  $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'], $optValues['media']);
1428  } else {
1429  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1430  }
1431  if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
1432  unset($hiddenFieldArr['lang']);
1433  $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'], $optValues['lang']);
1434  } else {
1435  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1436  }
1437  }
1438  // Sections
1439  if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
1440  $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1441  } else {
1442  $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'], $optValues['sections']);
1443  }
1444  // Free Indexing Configurations:
1445  if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid']) {
1446  $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
1447  } else {
1448  $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'], $optValues['freeIndexUid']);
1449  }
1450  // Sorting
1451  if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order']) {
1452  $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1453  } else {
1454  unset($hiddenFieldArr['order']);
1455  unset($hiddenFieldArr['desc']);
1456  unset($hiddenFieldArr['results']);
1457  $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'], $optValues['order']);
1458  $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'], $optValues['desc']);
1459  $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1460  }
1461  // Limits
1462  if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results']) {
1463  $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1464  } else {
1465  $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1466  }
1467  // Grouping
1468  if (!is_array($optValues['group']) || $this->conf['blind.']['group']) {
1469  $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1470  } else {
1471  unset($hiddenFieldArr['group']);
1472  $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'], $optValues['group']);
1473  }
1474  if ($this->conf['blind.']['extResume']) {
1475  $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1476  } else {
1477  $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
1478  }
1479  } else {
1480  // Extended search
1481  $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1482  }
1483  if ($this->conf['show.']['advancedSearchLink']) {
1484  $linkToOtherMode = $this->piVars['ext'] ? $this->pi_getPageLink($this->frontendController->id, $this->frontendController->sPre) : $this->pi_getPageLink($this->frontendController->id, $this->frontendController->sPre, [$this->prefixId . '[ext]' => 1]);
1485  $markerArray['###LINKTOOTHERMODE###'] = '<a href="' . htmlspecialchars($linkToOtherMode) . '">' . $this->pi_getLL(($this->piVars['ext'] ? 'link_regularSearch' : 'link_advancedSearch'), '', true) . '</a>';
1486  } else {
1487  $markerArray['###LINKTOOTHERMODE###'] = '';
1488  }
1489  // Write all hidden fields
1490  $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('', $hiddenFieldArr));
1491  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, [], []);
1492  return $substitutedContent;
1493  }
1494 
1502  public function renderSelectBoxValues($value, $optValues)
1503  {
1504  if (!is_array($optValues)) {
1505  return '';
1506  }
1507  $opt = [];
1508  $isSelFlag = 0;
1509  foreach ($optValues as $k => $v) {
1510  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1511  if ($sel) {
1512  $isSelFlag++;
1513  }
1514  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1515  }
1516  return implode('', $opt);
1517  }
1518 
1524  public function printRules()
1525  {
1526  if (!$this->conf['show.']['rules']) {
1527  return '';
1528  }
1529  $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1530  $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header', '', true);
1531  $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text', '', true)));
1532  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, [], []);
1533  return $this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']);
1534  }
1535 
1541  public function printResultSectionLinks()
1542  {
1543  if (empty($this->resultSections)) {
1544  return '';
1545  }
1546  $links = [];
1547  $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1548  $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1549  $anchorPrefix = $GLOBALS['TSFE']->baseUrl ? substr(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'))) : '';
1550  foreach ($this->resultSections as $id => $dat) {
1551  $markerArray = [];
1552  $aBegin = '<a href="' . htmlspecialchars($anchorPrefix . '#anchor_' . md5($id)) . '">';
1553  $aContent = (trim($dat[0]) ? trim($dat[0]) : htmlspecialchars($this->pi_getLL('unnamedSection'))) . ' (' . $dat[1] . ' ' . $this->pi_getLL(($dat[1] > 1 ? 'word_pages' : 'word_page'), '', true) . ')';
1554  $aEnd = '</a>';
1555  $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1556  $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, [], []);
1557  }
1558  $html = $this->cObj->substituteMarkerArrayCached($html, ['###LINKS###' => implode('', $links)], [], []);
1559  return '<div' . $this->pi_classParam('sectionlinks') . '>' . $this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']) . '</div>';
1560  }
1561 
1570  public function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
1571  {
1572  $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1573  $markerArray['###ANCHOR_URL###'] = 'anchor_' . md5($id);
1574  $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1575  $markerArray['###RESULT_COUNT###'] = $countResultRows;
1576  $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page' . ($countResultRows > 1 ? 's' : ''));
1577  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, [], []);
1578  return $substitutedContent;
1579  }
1580 
1588  public function printResultRow($row, $headerOnly = 0)
1589  {
1590  // Get template content:
1591  $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1592  if ($hookObj = $this->hookRequest('printResultRow')) {
1593  return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1594  } else {
1595  $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1596  if (!is_array($row['_sub'])) {
1597  $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1598  }
1599  if (!$headerOnly) {
1600  $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1601  } elseif ($headerOnly == 1) {
1602  $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1603  } elseif ($headerOnly == 2) {
1604  $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1605  $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1606  }
1607  if (is_array($tmplContent)) {
1608  foreach ($tmplContent as $k => $v) {
1609  $markerArray['###' . GeneralUtility::strtoupper($k) . '###'] = $v;
1610  }
1611  }
1612  // Description text
1613  $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size', '', true);
1614  $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created', '', true);
1615  $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified', '', true);
1616  $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path', '', true);
1617  $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, [], []);
1618  // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1619  if (is_array($row['_sub'])) {
1620  if ($this->multiplePagesType($row['item_type'])) {
1621  $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching', '', true), $html);
1622  foreach ($row['_sub'] as $subRow) {
1623  $html .= $this->printResultRow($subRow, 1);
1624  }
1625  } else {
1626  $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching', '', true);
1627  $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell', '', true), $html);
1628  }
1629  }
1630  return $html;
1631  }
1632  }
1633 
1643  protected function renderPagination($showResultCount = true, $addString = '', $addPart = '', $freeIndexUid = -1)
1644  {
1645  // Initializing variables:
1646  $pointer = (int)$this->piVars['pointer'];
1647  $count = (int)$this->internal['res_count'];
1648  $results_at_a_time = MathUtility::forceIntegerInRange($this->internal['results_at_a_time'], 1, 1000);
1649  $pageCount = (int)ceil($count / $results_at_a_time);
1650 
1651  $links = [];
1652  // only show the result browser if more than one page is needed
1653  if ($pageCount > 1) {
1654  $maxPages = MathUtility::forceIntegerInRange($this->internal['maxPages'], 1, $pageCount);
1655 
1656  // Make browse-table/links:
1657  if ($pointer > 0) {
1658  // all pages after the 1st one
1659  $links[] = '<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev', '< Previous', true), $pointer - 1, $freeIndexUid) . '</li>';
1660  }
1661  $minPage = $pointer - (int)floor($maxPages / 2);
1662  $maxPage = $minPage + $maxPages - 1;
1663  // Check if the indexes are within the page limits
1664  if ($minPage < 0) {
1665  $maxPage -= $minPage;
1666  $minPage = 0;
1667  } elseif ($maxPage >= $pageCount) {
1668  $minPage -= $maxPage - $pageCount + 1;
1669  $maxPage = $pageCount - 1;
1670  }
1671  $pageLabel = $this->pi_getLL('pi_list_browseresults_page', 'Page', true);
1672  for ($a = $minPage; $a <= $maxPage; $a++) {
1673  $label = trim($pageLabel . ' ' . ($a + 1));
1674  $link = $this->makePointerSelector_link($label, $a, $freeIndexUid);
1675  if ($a === $pointer) {
1676  $links[] = '<li' . $this->pi_classParam('browselist-currentPage') . '><strong>' . $link . '</strong></li>';
1677  } else {
1678  $links[] = '<li>' . $link . '</li>';
1679  }
1680  }
1681  if ($pointer + 1 < $pageCount) {
1682  $links[] = '<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next', 'Next >', true), $pointer + 1, $freeIndexUid) . '</li>';
1683  }
1684  }
1685  if (!empty($links)) {
1686  $addPart .= '
1687  <ul class="browsebox">
1688  ' . implode('', $links) . '
1689  </ul>';
1690  }
1691  $label = str_replace(
1692  ['###TAG_BEGIN###', '###TAG_END###'],
1693  ['<strong>', '</strong>'],
1694  $this->pi_getLL('pi_list_browseresults_display', 'Displaying results ###TAG_BEGIN###%1$s to %2$s###TAG_END### out of ###TAG_BEGIN###%3$s###TAG_END###')
1695  );
1696  $resultsFrom = $pointer * $results_at_a_time + 1;
1697  $resultsTo = min($resultsFrom + $results_at_a_time - 1, $count);
1698  $resultCountText = '';
1699  if ($showResultCount) {
1700  $resultCountText = '<p>' . sprintf($label, $resultsFrom, $resultsTo, $count) . $addString . '</p>';
1701  }
1702  $sTables = '<div' . $this->pi_classParam('browsebox') . '>'
1703  . $resultCountText
1704  . $addPart . '</div>';
1705  return $sTables;
1706  }
1707 
1708  /***********************************
1709  *
1710  * Support functions for HTML output (with a minimum of fixed markup)
1711  *
1712  ***********************************/
1720  public function prepareResultRowTemplateData($row, $headerOnly)
1721  {
1722  // Initialize:
1723  $specRowConf = $this->getSpecialConfigForRow($row);
1724  $CSSsuffix = $specRowConf['CSSsuffix'] ? '-' . $specRowConf['CSSsuffix'] : '';
1725  // If external media, link to the media-file instead.
1726  if ($row['item_type']) {
1727  // External media
1728  if ($row['show_resume']) {
1729  // Can link directly.
1730  $targetAttribute = '';
1731  if ($this->frontendController->config['config']['fileTarget']) {
1732  $targetAttribute = ' target="' . htmlspecialchars($this->frontendController->config['config']['fileTarget']) . '"';
1733  }
1734  $title = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' . htmlspecialchars($this->makeTitle($row)) . '</a>';
1735  } else {
1736  // Suspicious, so linking to page instead...
1737  $copy_row = $row;
1738  unset($copy_row['cHashParams']);
1739  $title = $this->linkPage($row['page_id'], htmlspecialchars($this->makeTitle($row)), $copy_row);
1740  }
1741  } else {
1742  // Else the page:
1743  // Prepare search words for markup in content:
1744  if ($this->conf['forwardSearchWordsInResultLink']) {
1745  if ($this->conf['forwardSearchWordsInResultLink.']['no_cache']) {
1746  $markUpSwParams = ['no_cache' => 1];
1747  } else {
1748  $markUpSwParams = [];
1749  }
1750  foreach ($this->sWArr as $d) {
1751  $markUpSwParams['sword_list'][] = $d['sword'];
1752  }
1753  } else {
1754  $markUpSwParams = [];
1755  }
1756  $title = $this->linkPage($row['data_page_id'], htmlspecialchars($this->makeTitle($row)), $row, $markUpSwParams);
1757  }
1758  $tmplContent = [];
1759  $tmplContent['title'] = $title;
1760  $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'] . ': ' : '&nbsp;';
1761  $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'], '', $specRowConf);
1762  $tmplContent['rating'] = $this->makeRating($row);
1763  $tmplContent['description'] = $this->makeDescription(
1764  $row,
1765  !($this->piVars['extResume'] && !$headerOnly),
1766  $this->conf['results.']['summaryCropAfter']
1767  );
1768  $tmplContent = $this->makeInfo($row, $tmplContent);
1769  $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1770  $tmplContent['language'] = $this->makeLanguageIndication($row);
1771  $tmplContent['CSSsuffix'] = $CSSsuffix;
1772  // Post processing with hook.
1773  if ($hookObj = $this->hookRequest('prepareResultRowTemplateData_postProc')) {
1774  $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1775  }
1776  return $tmplContent;
1777  }
1778 
1786  {
1787  // Init:
1788  $searchingFor = '';
1789  $c = 0;
1790  // Traverse search words:
1791  foreach ($sWArr as $k => $v) {
1792  if ($c) {
1793  switch ($v['oper']) {
1794  case 'OR':
1795  $searchingFor .= ' ' . $this->pi_getLL('searchFor_or', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1796  break;
1797  case 'AND NOT':
1798  $searchingFor .= ' ' . $this->pi_getLL('searchFor_butNot', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1799  break;
1800  default:
1801  // AND...
1802  $searchingFor .= ' ' . $this->pi_getLL('searchFor_and', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1803  }
1804  } else {
1805  $searchingFor = $this->pi_getLL('searchFor', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1806  }
1807  $c++;
1808  }
1809  return $searchingFor;
1810  }
1811 
1818  public function wrapSW($str)
1819  {
1820  return '"<span' . $this->pi_classParam('sw') . '>' . htmlspecialchars($str) . '</span>"';
1821  }
1822 
1831  public function renderSelectBox($name, $value, $optValues)
1832  {
1833  if (is_array($optValues)) {
1834  $opt = [];
1835  $isSelFlag = 0;
1836  foreach ($optValues as $k => $v) {
1837  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1838  if ($sel) {
1839  $isSelFlag++;
1840  }
1841  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1842  }
1843  return '<select name="' . $name . '">' . implode('', $opt) . '</select>';
1844  }
1845  }
1846 
1856  public function makePointerSelector_link($str, $p, $freeIndexUid)
1857  {
1858  $onclick = 'document.getElementById(' . GeneralUtility::quoteJSvalue($this->prefixId . '_pointer') . ').value=' . GeneralUtility::quoteJSvalue($p) . ';' . 'document.getElementById(' . GeneralUtility::quoteJSvalue($this->prefixId . '_freeIndexUid') . ').value=' . GeneralUtility::quoteJSvalue($freeIndexUid) . ';' . 'document.getElementById(' . GeneralUtility::quoteJSvalue($this->prefixId) . ').submit();return false;';
1859  return '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . $str . '</a>';
1860  }
1861 
1870  public function makeItemTypeIcon($it, $alt = '', $specRowConf)
1871  {
1872  // Build compound key if item type is 0, iconRendering is not used
1873  // and specConfs.[pid].pageIcon was set in TS
1874  if ($it === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->conf['iconRendering.'])) {
1875  $it .= ':' . $specRowConf['_pid'];
1876  }
1877  if (!isset($this->iconFileNameCache[$it])) {
1878  $this->iconFileNameCache[$it] = '';
1879  // If TypoScript is used to render the icon:
1880  if (is_array($this->conf['iconRendering.'])) {
1881  $this->cObj->setCurrentVal($it);
1882  $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'], $this->conf['iconRendering.']);
1883  } else {
1884  // Default creation / finding of icon:
1885  $icon = '';
1886  if ($it === '0' || substr($it, 0, 2) == '0:') {
1887  if (is_array($specRowConf['pageIcon.'])) {
1888  $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle('IMAGE', $specRowConf['pageIcon.']);
1889  } else {
1890  $icon = 'EXT:indexed_search/Resources/Public/Icons/FileTypes/pages.gif';
1891  }
1892  } elseif ($this->external_parsers[$it]) {
1893  $icon = $this->external_parsers[$it]->getIcon($it);
1894  }
1895  if ($icon) {
1896  $fullPath = GeneralUtility::getFileAbsFileName($icon);
1897  if ($fullPath) {
1898  $info = @getimagesize($fullPath);
1900  $this->iconFileNameCache[$it] = is_array($info) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
1901  }
1902  }
1903  }
1904  }
1905  return $this->iconFileNameCache[$it];
1906  }
1907 
1914  public function makeRating($row)
1915  {
1916  switch ((string)$this->piVars['order']) {
1917  case 'rank_count':
1918  // Number of occurencies on page
1919  return $row['order_val'] . ' ' . $this->pi_getLL('maketitle_matches');
1920  break;
1921  case 'rank_first':
1922  // Close to top of page
1923  return ceil(MathUtility::forceIntegerInRange((255 - $row['order_val']), 1, 255) / 255 * 100) . '%';
1924  break;
1925  case 'rank_flag':
1926  // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1927  if ($this->firstRow['order_val2']) {
1928  $base = $row['order_val1'] * 256;
1929  // (3 MSB bit, 224 is highest value of order_val1 currently)
1930  $freqNumber = $row['order_val2'] / $this->firstRow['order_val2'] * pow(2, 12);
1931  // 15-3 MSB = 12
1932  $total = MathUtility::forceIntegerInRange($base + $freqNumber, 0, 32767);
1933  return ceil(log($total) / log(32767) * 100) . '%';
1934  }
1935  break;
1936  case 'rank_freq':
1937  // Based on frequency
1938  $max = 10000;
1939  $total = MathUtility::forceIntegerInRange($row['order_val'], 0, $max);
1940  return ceil(log($total) / log($max) * 100) . '%';
1941  break;
1942  case 'crdate':
1943  // Based on creation date
1944  return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'], 0);
1945  break;
1946  case 'mtime':
1947  // Based on modification time
1948  return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'], 0);
1949  break;
1950  default:
1951  // fx. title
1952  return '&nbsp;';
1953  }
1954  }
1955 
1964  public function makeDescription($row, $noMarkup = false, $lgd = 180)
1965  {
1966  if ($row['show_resume']) {
1967  $markedSW = '';
1968  $outputStr = '';
1969  if (!$noMarkup) {
1970  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_fulltext')) {
1971  $res = $this->databaseConnection->exec_SELECTquery('*', 'index_fulltext', 'phash=' . (int)$row['phash']);
1972  } else {
1973  $res = false;
1974  }
1975  if ($res) {
1976  if ($ftdrow = $this->databaseConnection->sql_fetch_assoc($res)) {
1977  // Cut HTTP references after some length
1978  $content = preg_replace('/(http:\\/\\/[^ ]{' . $this->conf['results.']['hrefInSummaryCropAfter'] . '})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
1979  $markedSW = $this->markupSWpartsOfString($content);
1980  }
1981  $this->databaseConnection->sql_free_result($res);
1982  }
1983  }
1984  if (!trim($markedSW)) {
1985  $outputStr = $this->frontendController->csConvObj->crop('utf-8', $row['item_description'], $lgd, $this->conf['results.']['summaryCropSignifier']);
1986  $outputStr = htmlspecialchars($outputStr);
1987  }
1988  $output = $this->utf8_to_currentCharset($outputStr ?: $markedSW);
1989  } else {
1990  $output = '<span class="noResume">' . $this->pi_getLL('res_noResume', '', true) . '</span>';
1991  }
1992  return $output;
1993  }
1994 
2001  public function markupSWpartsOfString($str)
2002  {
2003  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
2004  // Init:
2005  $str = str_replace('&nbsp;', ' ', $htmlParser->bidir_htmlspecialchars($str, -1));
2006  $str = preg_replace('/\\s\\s+/', ' ', $str);
2007  $swForReg = [];
2008  // Prepare search words for regex:
2009  foreach ($this->sWArr as $d) {
2010  $swForReg[] = preg_quote($d['sword'], '/');
2011  }
2012  $regExString = '(' . implode('|', $swForReg) . ')';
2013  // Split and combine:
2014  $parts = preg_split('/' . $regExString . '/ui', ' ' . $str . ' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
2015  // Constants:
2016  $summaryMax = $this->conf['results.']['markupSW_summaryMax'];
2017  $postPreLgd = $this->conf['results.']['markupSW_postPreLgd'];
2018  $postPreLgd_offset = $this->conf['results.']['markupSW_postPreLgd_offset'];
2019  $divider = $this->conf['results.']['markupSW_divider'];
2020  $occurencies = (count($parts) - 1) / 2;
2021  if ($occurencies) {
2022  $postPreLgd = MathUtility::forceIntegerInRange($summaryMax / $occurencies, $postPreLgd, $summaryMax / 2);
2023  }
2024  // Variable:
2025  $summaryLgd = 0;
2026  $output = [];
2027  // Shorten in-between strings:
2028  foreach ($parts as $k => $strP) {
2029  if ($k % 2 == 0) {
2030  // Find length of the summary part:
2031  $strLen = $this->frontendController->csConvObj->strlen('utf-8', $parts[$k]);
2032  $output[$k] = $parts[$k];
2033  // Possibly shorten string:
2034  if (!$k) {
2035  // First entry at all (only cropped on the frontside)
2036  if ($strLen > $postPreLgd) {
2037  $output[$k] = $divider . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $this->frontendController->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
2038  }
2039  } elseif ($summaryLgd > $summaryMax || !isset($parts[$k + 1])) {
2040  // In case summary length is exceed OR if there are no more entries at all:
2041  if ($strLen > $postPreLgd) {
2042  $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/', '', $this->frontendController->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider;
2043  }
2044  } else {
2045  // In-between search words:
2046  if ($strLen > $postPreLgd * 2) {
2047  $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/', '', $this->frontendController->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $this->frontendController->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
2048  }
2049  }
2050  $summaryLgd += $this->frontendController->csConvObj->strlen('utf-8', $output[$k]);
2051  // Protect output:
2052  $output[$k] = htmlspecialchars($output[$k]);
2053  // If summary lgd is exceed, break the process:
2054  if ($summaryLgd > $summaryMax) {
2055  break;
2056  }
2057  } else {
2058  $summaryLgd += $this->frontendController->csConvObj->strlen('utf-8', $strP);
2059  $output[$k] = '<strong class="tx-indexedsearch-redMarkup">' . htmlspecialchars($parts[$k]) . '</strong>';
2060  }
2061  }
2062  // Return result:
2063  return implode('', $output);
2064  }
2065 
2072  public function makeTitle($row)
2073  {
2074  $add = '';
2075  if ($this->multiplePagesType($row['item_type'])) {
2076  $dat = unserialize($row['cHashParams']);
2077  $pp = explode('-', $dat['key']);
2078  if ($pp[0] != $pp[1]) {
2079  $add = ', ' . $this->pi_getLL('word_pages') . ' ' . $dat['key'];
2080  } else {
2081  $add = ', ' . $this->pi_getLL('word_page') . ' ' . $pp[0];
2082  }
2083  }
2084  $outputString = $this->frontendController->csConvObj->crop('utf-8', $row['item_title'], $this->conf['results.']['titleCropAfter'], $this->conf['results.']['titleCropSignifier']);
2085  return $this->utf8_to_currentCharset($outputString) . $add;
2086  }
2087 
2095  public function makeInfo($row, $tmplArray)
2096  {
2097  $tmplArray['size'] = GeneralUtility::formatSize($row['item_size']);
2098  $tmplArray['created'] = $this->formatCreatedDate($row['item_crdate']);
2099  $tmplArray['modified'] = $this->formatModifiedDate($row['item_mtime']);
2100  $pathId = $row['data_page_id'] ?: $row['page_id'];
2101  $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2102  $pI = parse_url($row['data_filename']);
2103  if ($pI['scheme']) {
2104  $targetAttribute = '';
2105  if ($this->frontendController->config['config']['fileTarget']) {
2106  $targetAttribute = ' target="' . htmlspecialchars($this->frontendController->config['config']['fileTarget']) . '"';
2107  }
2108  $tmplArray['path'] = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' . htmlspecialchars($row['data_filename']) . '</a>';
2109  } else {
2110  $pathStr = $this->getPathFromPageId($pathId, $pathMP);
2111  $tmplArray['path'] = $this->linkPage($pathId, $pathStr, [
2112  'cHashParams' => $row['cHashParams'],
2113  'data_page_type' => $row['data_page_type'],
2114  'data_page_mp' => $pathMP,
2115  'sys_language_uid' => $row['sys_language_uid']
2116  ]);
2117  }
2118  return $tmplArray;
2119  }
2120 
2127  public function getSpecialConfigForRow($row)
2128  {
2129  $pathId = $row['data_page_id'] ?: $row['page_id'];
2130  $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2131  $rl = $this->getRootLine($pathId, $pathMP);
2132  $specConf = $this->conf['specConfs.']['0.'];
2133  if (is_array($rl)) {
2134  foreach ($rl as $dat) {
2135  if (is_array($this->conf['specConfs.'][$dat['uid'] . '.'])) {
2136  $specConf = $this->conf['specConfs.'][$dat['uid'] . '.'];
2137  $specConf['_pid'] = $dat['uid'];
2138  break;
2139  }
2140  }
2141  }
2142  return $specConf;
2143  }
2144 
2151  public function makeLanguageIndication($row)
2152  {
2153  // If search result is a TYPO3 page:
2154  if ((string)$row['item_type'] === '0') {
2155  // If TypoScript is used to render the flag:
2156  if (is_array($this->conf['flagRendering.'])) {
2157  $this->cObj->setCurrentVal($row['sys_language_uid']);
2158  return $this->cObj->cObjGetSingle($this->conf['flagRendering'], $this->conf['flagRendering.']);
2159  }
2160  }
2161  return '&nbsp;';
2162  }
2163 
2171  public function makeAccessIndication($id)
2172  {
2173  if (is_array($this->fe_groups_required[$id]) && !empty($this->fe_groups_required[$id])) {
2174  return '<img src="' . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath('indexed_search') . 'Resources/Public/Icons/FileTypes/locked.gif" width="12" height="15" vspace="5" title="' . sprintf($this->pi_getLL('res_memberGroups', '', true), implode(',', array_unique($this->fe_groups_required[$id]))) . '" alt="" />';
2175  }
2176 
2177  return '';
2178  }
2179 
2189  public function linkPage($id, $str, $row = [], $markUpSwParams = [])
2190  {
2191  // Parameters for link:
2192  $urlParameters = (array)unserialize($row['cHashParams']);
2193  // Add &type and &MP variable:
2194  if ($row['data_page_type']) {
2195  $urlParameters['type'] = $row['data_page_type'];
2196  }
2197  if ($row['data_page_mp']) {
2198  $urlParameters['MP'] = $row['data_page_mp'];
2199  }
2200  $urlParameters['L'] = intval($row['sys_language_uid']);
2201  // markup-GET vars:
2202  $urlParameters = array_merge($urlParameters, $markUpSwParams);
2203  // This will make sure that the path is retrieved if it hasn't been already. Used only for the sake of the domain_record thing...
2204  if (!is_array($this->domain_records[$id])) {
2205  $this->getPathFromPageId($id);
2206  }
2207  // If external domain, then link to that:
2208  if (!empty($this->domain_records[$id])) {
2209  reset($this->domain_records[$id]);
2210  $firstDom = current($this->domain_records[$id]);
2211  $scheme = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2212  $addParams = '';
2213  if (is_array($urlParameters) && !empty($urlParameters)) {
2214  $addParams .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
2215  }
2216  if ($target = $this->conf['search.']['detect_sys_domain_records.']['target']) {
2217  $target = ' target="' . $target . '"';
2218  }
2219  return '<a href="' . htmlspecialchars(($scheme . $firstDom . '/index.php?id=' . $id . $addParams)) . '"' . $target . '>' . htmlspecialchars($str) . '</a>';
2220  } else {
2221  return $this->pi_linkToPage($str, $id, $this->conf['result_link_target'], $urlParameters);
2222  }
2223  }
2224 
2232  public function getRootLine($id, $pathMP = '')
2233  {
2234  $identStr = $id . '|' . $pathMP;
2235  if (!isset($this->cache_path[$identStr])) {
2236  $this->cache_rl[$identStr] = $this->frontendController->sys_page->getRootLine($id, $pathMP);
2237  }
2238  return $this->cache_rl[$identStr];
2239  }
2240 
2247  public function getFirstSysDomainRecordForPage($id)
2248  {
2249  $res = $this->databaseConnection->exec_SELECTquery('domainName', 'sys_domain', 'pid=' . (int)$id . $this->cObj->enableFields('sys_domain'), '', 'sorting');
2250  $row = $this->databaseConnection->sql_fetch_assoc($res);
2251  return rtrim($row['domainName'], '/');
2252  }
2253 
2261  public function getPathFromPageId($id, $pathMP = '')
2262  {
2263  $identStr = $id . '|' . $pathMP;
2264  if (!isset($this->cache_path[$identStr])) {
2265  $this->fe_groups_required[$id] = [];
2266  $this->domain_records[$id] = [];
2267  $rl = $this->getRootLine($id, $pathMP);
2268  $path = '';
2269  $pageCount = count($rl);
2270  if (is_array($rl) && !empty($rl)) {
2271  $index = 0;
2272  $breadcrumbWrap = isset($this->conf['breadcrumbWrap']) ? $this->conf['breadcrumbWrap'] : '/';
2273  $breadcrumbWraps = $GLOBALS['TSFE']->tmpl->splitConfArray(['wrap' => $breadcrumbWrap], $pageCount);
2274  foreach ($rl as $k => $v) {
2275  // Check fe_user
2276  if ($v['fe_group'] && ($v['uid'] == $id || $v['extendToSubpages'])) {
2277  $this->fe_groups_required[$id][] = $v['fe_group'];
2278  }
2279  // Check sys_domain.
2280  if ($this->conf['search.']['detect_sys_domain_records']) {
2281  $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2282  if ($sysDName) {
2283  $this->domain_records[$id][] = $sysDName;
2284  // Set path accordingly:
2285  $path = $sysDName . $path;
2286  break;
2287  }
2288  }
2289  // Stop, if we find that the current id is the current root page.
2290  if ($v['uid'] == $this->frontendController->config['rootLine'][0]['uid']) {
2291  array_pop($breadcrumbWraps);
2292  break;
2293  }
2294  $path = $this->cObj->wrap(htmlspecialchars($v['title']), array_pop($breadcrumbWraps)['wrap']) . $path;
2295  }
2296  }
2297  $this->cache_path[$identStr] = $path;
2298  if (is_array($this->conf['path_stdWrap.'])) {
2299  $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2300  }
2301  }
2302  return $this->cache_path[$identStr];
2303  }
2304 
2311  public function getMenu($id)
2312  {
2313  if ($this->conf['show.']['LxALLtypes']) {
2314  $output = [];
2315  $res = $this->databaseConnection->exec_SELECTquery('title,uid', 'pages', 'pid=' . (int)$id . $this->cObj->enableFields('pages'), '', 'sorting');
2316  while ($row = $this->databaseConnection->sql_fetch_assoc($res)) {
2317  $output[$row['uid']] = $this->frontendController->sys_page->getPageOverlay($row);
2318  }
2319  $this->databaseConnection->sql_free_result($res);
2320  return $output;
2321  } else {
2322  return $this->frontendController->sys_page->getMenu($id);
2323  }
2324  }
2325 
2332  public function multiplePagesType($item_type)
2333  {
2334  return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2335  }
2336 
2343  public function utf8_to_currentCharset($str)
2344  {
2345  return $this->frontendController->csConv($str, 'utf-8');
2346  }
2347 
2354  public function hookRequest($functionName)
2355  {
2356  // Hook: menuConfig_preProcessModMenu
2357  if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2358  $hookObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2359  if (method_exists($hookObj, $functionName)) {
2360  $hookObj->pObj = $this;
2361  return $hookObj;
2362  }
2363  }
2364  }
2365 
2371  protected function getSearchFormActionURL()
2372  {
2373  $targetUrlPid = $this->getSearchFormActionPidFromTS();
2374  if ($targetUrlPid == 0) {
2375  $targetUrlPid = $this->frontendController->id;
2376  }
2377  return $this->pi_getPageLink($targetUrlPid, $this->frontendController->sPre);
2378  }
2379 
2385  protected function getSearchFormActionPidFromTS()
2386  {
2387  $result = 0;
2388  if (isset($this->conf['search.']['targetPid']) || isset($this->conf['search.']['targetPid.'])) {
2389  if (is_array($this->conf['search.']['targetPid.'])) {
2390  $result = $this->cObj->stdWrap($this->conf['search.']['targetPid'], $this->conf['search.']['targetPid.']);
2391  } else {
2392  $result = $this->conf['search.']['targetPid'];
2393  }
2394  $result = (int)$result;
2395  }
2396  return $result;
2397  }
2398 
2405  protected function formatCreatedDate($date)
2406  {
2407  $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2408  return $this->formatDate($date, 'created', $defaultFormat);
2409  }
2410 
2417  protected function formatModifiedDate($date)
2418  {
2419  $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2420  return $this->formatDate($date, 'modified', $defaultFormat);
2421  }
2422 
2432  protected function formatDate($date, $tsKey, $defaultFormat)
2433  {
2434  $strftimeFormat = $this->conf['dateFormat.'][$tsKey];
2435  if ($strftimeFormat) {
2436  $result = strftime($strftimeFormat, $date);
2437  } else {
2438  $result = date($defaultFormat, $date);
2439  }
2440  return $result;
2441  }
2442 
2449  public function getSearchType()
2450  {
2451  return (int)$this->piVars['type'];
2452  }
2453 
2459  public function getSearchRootPageIdList()
2460  {
2461  return \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->wholeSiteIdList);
2462  }
2463 
2470  public function getJoinPagesForQuery()
2471  {
2472  return (bool)$this->join_pages;
2473  }
2474 
2478  protected function loadSettings()
2479  {
2480  if (!is_array($this->conf['results.'])) {
2481  $this->conf['results.'] = [];
2482  }
2483  $this->conf['results.']['summaryCropAfter'] = MathUtility::forceIntegerInRange(
2484  $this->cObj->stdWrap($this->conf['results.']['summaryCropAfter'], $this->conf['results.']['summaryCropAfter.']),
2485  10, 5000, 180
2486  );
2487  $this->conf['results.']['summaryCropSignifier'] = $this->cObj->stdWrap($this->conf['results.']['summaryCropSignifier'], $this->conf['results.']['summaryCropSignifier.']);
2488  $this->conf['results.']['titleCropAfter'] = MathUtility::forceIntegerInRange(
2489  $this->cObj->stdWrap($this->conf['results.']['titleCropAfter'], $this->conf['results.']['titleCropAfter.']),
2490  10, 500, 50
2491  );
2492  $this->conf['results.']['titleCropSignifier'] = $this->cObj->stdWrap($this->conf['results.']['titleCropSignifier'], $this->conf['results.']['titleCropSignifier.']);
2493  $this->conf['results.']['markupSW_summaryMax'] = MathUtility::forceIntegerInRange(
2494  $this->cObj->stdWrap($this->conf['results.']['markupSW_summaryMax'], $this->conf['results.']['markupSW_summaryMax.']),
2495  10, 5000, 300
2496  );
2497  $this->conf['results.']['markupSW_postPreLgd'] = MathUtility::forceIntegerInRange(
2498  $this->cObj->stdWrap($this->conf['results.']['markupSW_postPreLgd'], $this->conf['results.']['markupSW_postPreLgd.']),
2499  1, 500, 60
2500  );
2501  $this->conf['results.']['markupSW_postPreLgd_offset'] = MathUtility::forceIntegerInRange(
2502  $this->cObj->stdWrap($this->conf['results.']['markupSW_postPreLgd_offset'], $this->conf['results.']['markupSW_postPreLgd_offset.']),
2503  1, 50, 5
2504  );
2505  $this->conf['results.']['markupSW_divider'] = $this->cObj->stdWrap($this->conf['results.']['markupSW_divider'], $this->conf['results.']['markupSW_divider.']);
2506  $this->conf['results.']['hrefInSummaryCropAfter'] = MathUtility::forceIntegerInRange(
2507  $this->cObj->stdWrap($this->conf['results.']['hrefInSummaryCropAfter'], $this->conf['results.']['hrefInSummaryCropAfter.']),
2508  10, 400, 60
2509  );
2510  $this->conf['results.']['hrefInSummaryCropSignifier'] = $this->cObj->stdWrap($this->conf['results.']['hrefInSummaryCropSignifier'], $this->conf['results.']['hrefInSummaryCropSignifier.']);
2511  }
2512 }
makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
if(!defined("DB_ERROR")) define("DB_ERROR"
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:31
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static static static anonymizeIp($address, $mask=null)
renderPagination($showResultCount=true, $addString='', $addPart='', $freeIndexUid=-1)
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
pi_getPageLink($id, $target='', $urlParameters=[])
static getExplodedSearchString($sword, $defaultOperator, $operatorTranslateTable)
$uid
Definition: server.php:38
static getFileAbsFileName($filename, $onlyRelative=true, $relToTYPO3_mainDir=false)
static formatSize($sizeInBytes, $labels='', $base=0)
static revExplode($delimiter, $string, $count=0)
pi_getLL($key, $alternativeLabel='', $hsc=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
pi_linkToPage($str, $id, $target='', $urlParameters=[])