TYPO3  7.6
SearchFormController.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\IndexedSearch\Controller;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 use TYPO3\CMS\IndexedSearch\Utility;
21 
29 {
35  public $prefixId = 'tx_indexedsearch';
36 
42  public $extKey = 'indexed_search';
43 
49  public $join_pages = 0;
50 
51 
52  public $defaultResultNumber = 10;
53 
59  public $operator_translate_table = array(array('+', 'AND'), array('|', 'OR'), array('-', 'AND NOT'));
60 
66  public $wholeSiteIdList = 0;
67 
73  public $sWArr = array();
74 
80  public $optValues = array();
81 
87  public $firstRow = array();
88 
94  public $cache_path = array();
95 
101  public $cache_rl = array();
102 
108  public $fe_groups_required = array();
109 
115  public $domain_records = array();
116 
122  public $wSelClauses = array();
123 
129  public $resultSections = array();
130 
135  public $external_parsers = array();
136 
142  public $iconFileNameCache = array();
143 
149  public $templateCode = '';
150 
151  public $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
152 
158  public $indexerConfig = array();
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, 100000, $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 = array(
241  'type' => array(
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' => array(
250  '0' => $this->pi_getLL('opt_defOp_0'),
251  '1' => $this->pi_getLL('opt_defOp_1')
252  ),
253  'sections' => array(
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' => array(
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' => array(
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' => array(
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' => array(
279  'sections' => $this->pi_getLL('opt_group_sections'),
280  'flat' => $this->pi_getLL('opt_group_flat')
281  ),
282  'lang' => array(
283  -1 => $this->pi_getLL('opt_lang_-1'),
284  0 => $this->pi_getLL('opt_lang_0')
285  ),
286  'desc' => array(
287  '0' => $this->pi_getLL('opt_desc_0'),
288  '1' => $this->pi_getLL('opt_desc_1')
289  ),
290  'results' => array(
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[] = array($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[] = array($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[] = array($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 = array(array('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 = array();
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[] = array('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'], array($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 = array();
552  // Used for filtering out duplicates
553  $groupingChashes = array();
554  // Used for filtering out duplicates BASED ON cHash
555  $firstRow = array();
556  // Will hold the first row in result - used to calculate relative hit-ratings.
557  $resultRows = array();
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  if (!$this->checkExistence($row)) {
571  // Check if the record is still available or if it has been deleted meanwhile.
572  // Currently this works for files only, since extending it to content elements would cause a lot of overhead...
573  // Otherwise, skip the row.
574  $totalSearchResultCount--;
575  continue;
576  }
577  // Set first row:
578  if ($positionInSearchResults === 0) {
579  $firstRow = $row;
580  }
581  $row['show_resume'] = $this->checkResume($row);
582  // Tells whether we can link directly to a document or not (depends on possible right problems)
583  $phashGr = !in_array($row['phash_grouping'], $groupingPhashes);
584  $chashGr = !in_array(($row['contentHash'] . '.' . $row['data_page_id']), $groupingChashes);
585  if ($phashGr && $chashGr) {
586  if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords']) {
587  // Only if the resume may be shown are we going to filter out duplicates...
588  if (!$this->multiplePagesType($row['item_type'])) {
589  // Only on documents which are not multiple pages documents
590  $groupingPhashes[] = $row['phash_grouping'];
591  }
592  $groupingChashes[] = $row['contentHash'] . '.' . $row['data_page_id'];
593  $positionInSearchResults++;
594  // Check if we are inside result range for current page
595  if ($positionInSearchResults > $lastResultNumberOnPreviousPage && $positionInSearchResults <= $lastResultNumberToAnalyze) {
596  // Collect results to display
597  $row['result_number'] = $positionInSearchResults;
598  $resultRows[] = $row;
599  // This may lead to a problem: If the result
600  // check is not stopped here, the search will
601  // take longer. However the result counter
602  // will not filter out grouped cHashes/pHashes
603  // that were not processed yet. You can change
604  // this behavior using the "search.exactCount"
605  // property (see above).
606  $nextResultPosition = $positionInSearchResults + 1;
607  if (!$calculateExactCount && $nextResultPosition > $firstResultNumberOnNextPage) {
608  break;
609  }
610  }
611  } else {
612  // Skip this row if the user cannot view it (missing permission)
613  $totalSearchResultCount--;
614  }
615  } else {
616  // For each time a phash_grouping document is found
617  // (which is thus not displayed) the search-result count
618  // is reduced, so that it matches the number of rows displayed.
619  $totalSearchResultCount--;
620  }
621  }
622  $this->databaseConnection->sql_free_result($res);
623  $result = array(
624  'resultRows' => $resultRows,
625  'firstRow' => $firstRow,
626  'count' => $totalSearchResultCount
627  );
628  }
629  return $result;
630  }
631 
639  public function getResultRows_SQLpointer($sWArr, $freeIndexUid = -1)
640  {
641  // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
642  $list = $this->getPhashList($sWArr);
643  // Perform SQL Search / collection of result rows array:
644  if ($list) {
645  // Do the search:
646  $GLOBALS['TT']->push('execFinalQuery');
647  $res = $this->execFinalQuery($list, $freeIndexUid);
648  $GLOBALS['TT']->pull();
649  return $res;
650  } else {
651  return false;
652  }
653  }
654 
663  public function getDisplayResults($sWArr, $resData, $freeIndexUid = -1)
664  {
665  $content = '';
666  // Perform display of result rows array:
667  if ($resData) {
668  $GLOBALS['TT']->push('Display Final result');
669  // Set first selected row (for calculation of ranking later)
670  $this->firstRow = $resData['firstRow'];
671  // Result display here:
672  $rowcontent = '';
673  $rowcontent .= $this->compileResult($resData['resultRows'], $freeIndexUid);
674  // Browsing box:
675  if ($resData['count']) {
676  $this->internal['res_count'] = $resData['count'];
677  $this->internal['results_at_a_time'] = $this->piVars['results'];
678  $this->internal['maxPages'] = MathUtility::forceIntegerInRange($this->conf['search.']['page_links'], 1, 100, 10);
679  $resultSectionsCount = count($this->resultSections);
680  $addString = $resData['count'] && $this->piVars['group'] == 'sections' && $freeIndexUid <= 0 ? ' ' . sprintf($this->pi_getLL(($resultSectionsCount > 1 ? 'inNsections' : 'inNsection')), $resultSectionsCount) : '';
681  $browseBox1 = $this->pi_list_browseresults(1, $addString, $this->printResultSectionLinks(), $freeIndexUid);
682  $browseBox2 = $this->pi_list_browseresults(0, '', '', $freeIndexUid);
683  // Browsing nav, bottom.
684  $content = $browseBox1 . $rowcontent . $browseBox2;
685  } else {
686  $content = '<p' . $this->pi_classParam('noresults') . '>' . $this->pi_getLL('noResults', '', true) . '</p>';
687  }
688  $GLOBALS['TT']->pull();
689  } else {
690  $content .= '<p' . $this->pi_classParam('noresults') . '>' . $this->pi_getLL('noResults', '', true) . '</p>';
691  }
692  // Print a message telling which words we searched for, and in which sections etc.
693  $what = $this->tellUsWhatIsSeachedFor($sWArr) . (substr($this->piVars['sections'], 0, 2) == 'rl' ? ' ' . $this->pi_getLL('inSection', '', true) . ' "' . $this->getPathFromPageId(substr($this->piVars['sections'], 4)) . '"' : '');
694  $what = '<div' . $this->pi_classParam('whatis') . '>' . $this->cObj->stdWrap($what, $this->conf['whatis_stdWrap.']) . '</div>';
695  $content = $what . $content;
696  // Return content:
697  return $content;
698  }
699 
708  public function compileResult($resultRows, $freeIndexUid = -1)
709  {
710  $content = '';
711  // Transfer result rows to new variable, performing some mapping of sub-results etc.
712  $newResultRows = array();
713  foreach ($resultRows as $row) {
714  $id = md5($row['phash_grouping']);
715  if (is_array($newResultRows[$id])) {
716  if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) {
717  // swapping:
718  // Remove old
719  $subrows = $newResultRows[$id]['_sub'];
720  unset($newResultRows[$id]['_sub']);
721  $subrows[] = $newResultRows[$id];
722  // Insert new:
723  $newResultRows[$id] = $row;
724  $newResultRows[$id]['_sub'] = $subrows;
725  } else {
726  $newResultRows[$id]['_sub'][] = $row;
727  }
728  } else {
729  $newResultRows[$id] = $row;
730  }
731  }
732  $resultRows = $newResultRows;
733  $this->resultSections = array();
734  if ($freeIndexUid <= 0) {
735  switch ($this->piVars['group']) {
736  case 'sections':
737  $rl2flag = substr($this->piVars['sections'], 0, 2) == 'rl';
738  $sections = array();
739  foreach ($resultRows as $row) {
740  $id = $row['rl0'] . '-' . $row['rl1'] . ($rl2flag ? '-' . $row['rl2'] : '');
741  $sections[$id][] = $row;
742  }
743  $this->resultSections = array();
744  foreach ($sections as $id => $resultRows) {
745  $rlParts = explode('-', $id);
746  $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1] ? $rlParts[1] : $rlParts[0]);
747  $theRLid = $rlParts[2] ? 'rl2_' . $rlParts[2] : ($rlParts[1] ? 'rl1_' . $rlParts[1] : '0');
748  $sectionName = $this->getPathFromPageId($theId);
749  if ($sectionName[0] == '/') {
750  $sectionName = substr($sectionName, 1);
751  }
752  if (!trim($sectionName)) {
753  $sectionTitleLinked = $this->pi_getLL('unnamedSection', '', true) . ':';
754  } elseif ($this->conf['linkSectionTitles']) {
755  $quotedPrefix = GeneralUtility::quoteJSvalue($this->prefixId);
756  $onclick = 'document.forms[' . $quotedPrefix . '][' . GeneralUtility::quoteJSvalue($this->prefixId . '[_sections]') . '].value=' . GeneralUtility::quoteJSvalue($theRLid) . ';document.forms[' . $quotedPrefix . '].submit();return false;';
757  $sectionTitleLinked = '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . $sectionName . ':</a>';
758  } else {
759  $sectionTitleLinked = $sectionName;
760  }
761  $resultRowsCount = count($resultRows);
762  $this->resultSections[$id] = array($sectionName, $resultRowsCount);
763  // Add content header:
764  $content .= $this->makeSectionHeader($id, $sectionTitleLinked, $resultRowsCount);
765  // Render result rows:
766  $resultlist = '';
767  foreach ($resultRows as $row) {
768  $resultlist .= $this->printResultRow($row);
769  }
770  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
771  }
772  break;
773  default:
774  // flat:
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  } else {
782  $resultlist = '';
783  foreach ($resultRows as $row) {
784  $resultlist .= $this->printResultRow($row);
785  }
786  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
787  }
788  return '<div' . $this->pi_classParam('res') . '>' . $content . '</div>';
789  }
790 
791  /***********************************
792  *
793  * Searching functions (SQL)
794  *
795  ***********************************/
803  public function getPhashList($sWArr)
804  {
805  // Initialize variables:
806  $c = 0;
807  $totalHashList = array();
808  // This array accumulates the phash-values
809  // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
810  foreach ($sWArr as $k => $v) {
811  // Making the query for a single search word based on the search-type
812  $sWord = $v['sword'];
813  $theType = (string)$this->piVars['type'];
814  if (strstr($sWord, ' ')) {
815  // If there are spaces in the search-word, make a full text search instead.
816  $theType = 20;
817  }
818  $GLOBALS['TT']->push('SearchWord "' . $sWord . '" - $theType=' . $theType);
819  // Perform search for word:
820  switch ($theType) {
821  case '1':
822  // Part of word
823  $res = $this->searchWord($sWord, Utility\LikeWildcard::BOTH);
824  break;
825  case '2':
826  // First part of word
827  $res = $this->searchWord($sWord, Utility\LikeWildcard::RIGHT);
828  break;
829  case '3':
830  // Last part of word
831  $res = $this->searchWord($sWord, Utility\LikeWildcard::LEFT);
832  break;
833  case '10':
834  // Sounds like
840  // Initialize the indexer-class
841  $indexerObj = GeneralUtility::makeInstance(\TYPO3\CMS\IndexedSearch\Indexer::class);
842  // Perform metaphone search
843  $res = $this->searchMetaphone($indexerObj->metaphone($sWord, $this->storeMetaphoneInfoAsWords));
844  unset($indexerObj);
845  break;
846  case '20':
847  // Sentence
848  $res = $this->searchSentence($sWord);
849  $this->piVars['order'] = 'mtime';
850  // 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.
851  break;
852  default:
853  // Distinct word
854  $res = $this->searchDistinct($sWord);
855  }
856  // If there was a query to do, then select all phash-integers which resulted from this.
857  if ($res) {
858  // Get phash list by searching for it:
859  $phashList = array();
860  while ($row = $this->databaseConnection->sql_fetch_assoc($res)) {
861  $phashList[] = $row['phash'];
862  }
863  $this->databaseConnection->sql_free_result($res);
864  // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
865  if ($c) {
866  switch ($v['oper']) {
867  case 'OR':
868  $totalHashList = array_unique(array_merge($phashList, $totalHashList));
869  break;
870  case 'AND NOT':
871  $totalHashList = array_diff($totalHashList, $phashList);
872  break;
873  default:
874  // AND...
875  $totalHashList = array_intersect($totalHashList, $phashList);
876  }
877  } else {
878  $totalHashList = $phashList;
879  }
880  }
881  $GLOBALS['TT']->pull();
882  $c++;
883  }
884  return implode(',', $totalHashList);
885  }
886 
894  public function execPHashListQuery($wordSel, $plusQ = '')
895  {
896  return $this->databaseConnection->exec_SELECTquery('IR.phash', 'index_words IW,
897  index_rel IR,
898  index_section ISEC', $wordSel . '
899  AND IW.wid=IR.wid
900  AND ISEC.phash = IR.phash
901  ' . $this->sectionTableWhere() . '
902  ' . $plusQ, 'IR.phash');
903  }
904 
912  public function searchWord($sWord, $wildcard)
913  {
914  $likeWildcard = Utility\LikeWildcard::cast($wildcard);
915  $wSel = $likeWildcard->getLikeQueryPart(
916  'index_words',
917  'IW.baseword',
918  $sWord
919  );
920 
921  $this->wSelClauses[] = $wSel;
922  $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
923  return $res;
924  }
925 
932  public function searchDistinct($sWord)
933  {
934  $wSel = 'IW.wid=' . \TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::md5inthash($sWord);
935  $this->wSelClauses[] = $wSel;
936  $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
937  return $res;
938  }
939 
946  public function searchSentence($sSentence)
947  {
948  $this->wSelClauses[] = '1=1';
949  $likeWildcard = Utility\LikeWildcard::cast(Utility\LikeWildcard::BOTH);
950  $likePart = $likeWildcard->getLikeQueryPart(
951  'index_fulltext',
952  'IFT.fulltextdata',
953  $sSentence
954  );
955 
956  return $this->databaseConnection->exec_SELECTquery('ISEC.phash',
957  'index_section ISEC, index_fulltext IFT',
958  $likePart . ' AND ISEC.phash = IFT.phash' . $this->sectionTableWhere(), 'ISEC.phash'
959  );
960  }
961 
968  public function searchMetaphone($sWord)
969  {
970  $wSel = 'IW.metaphone=' . $sWord;
971  $this->wSelClauses[] = $wSel;
972  return $this->execPHashListQuery($wSel, ' AND is_stopword=0');
973  }
974 
980  public function sectionTableWhere()
981  {
982  $out = $this->wholeSiteIdList < 0 ? '' : ' AND ISEC.rl0 IN (' . $this->wholeSiteIdList . ')';
983  $match = '';
984  if (substr($this->piVars['sections'], 0, 4) == 'rl1_') {
985  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], 4)));
986  $out .= ' AND ISEC.rl1 IN (' . $list . ')';
987  $match = true;
988  } elseif (substr($this->piVars['sections'], 0, 4) == 'rl2_') {
989  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], 4)));
990  $out .= ' AND ISEC.rl2 IN (' . $list . ')';
991  $match = true;
992  } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
993  // Traversing user configured fields to see if any of those are used to limit search to a section:
994  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
995  if (substr($this->piVars['sections'], 0, strlen($fieldName) + 1) == $fieldName . '_') {
996  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], strlen($fieldName) + 1)));
997  $out .= ' AND ISEC.' . $fieldName . ' IN (' . $list . ')';
998  $match = true;
999  break;
1000  }
1001  }
1002  }
1003  // If no match above, test the static types:
1004  if (!$match) {
1005  switch ((string)$this->piVars['sections']) {
1006  case '-1':
1007  // '-1' => 'Only this page',
1008  $out .= ' AND ISEC.page_id=' . $this->frontendController->id;
1009  break;
1010  case '-2':
1011  // '-2' => 'Top + level 1',
1012  $out .= ' AND ISEC.rl2=0';
1013  break;
1014  case '-3':
1015  // '-3' => 'Level 2 and out',
1016  $out .= ' AND ISEC.rl2>0';
1017  break;
1018  }
1019  }
1020  return $out;
1021  }
1022 
1028  public function mediaTypeWhere()
1029  {
1030  switch ((string)$this->piVars['media']) {
1031  case '0':
1032  // '0' => 'Kun TYPO3 sider',
1033  $out = ' AND IP.item_type=' . $this->databaseConnection->fullQuoteStr('0', 'index_phash');
1034  break;
1035  case '-2':
1036  // All external documents
1037  $out = ' AND IP.item_type<>' . $this->databaseConnection->fullQuoteStr('0', 'index_phash');
1038  break;
1039  case '-1':
1040  // All content
1041  $out = '';
1042  break;
1043  default:
1044  $out = ' AND IP.item_type=' . $this->databaseConnection->fullQuoteStr($this->piVars['media'], 'index_phash');
1045  }
1046  return $out;
1047  }
1048 
1054  public function languageWhere()
1055  {
1056  $languageWhere = '';
1057  if ($this->piVars['lang'] >= 0) {
1058  // -1 is the same as ALL language.
1059  $languageWhere = 'AND IP.sys_language_uid=' . (int)$this->piVars['lang'];
1060  }
1061  return $languageWhere;
1062  }
1063 
1070  public function freeIndexUidWhere($freeIndexUid)
1071  {
1072  if ($freeIndexUid < 0) {
1073  return '';
1074  }
1075  // First, look if the freeIndexUid is a meta configuration:
1076  $indexCfgRec = $this->databaseConnection->exec_SELECTgetSingleRow('indexcfgs', 'index_config', 'type=5 AND uid=' . (int)$freeIndexUid . $this->cObj->enableFields('index_config'));
1077  if (is_array($indexCfgRec)) {
1078  $refs = GeneralUtility::trimExplode(',', $indexCfgRec['indexcfgs']);
1079  $list = array(-99);
1080  // Default value to protect against empty array.
1081  foreach ($refs as $ref) {
1082  list($table, $uid) = GeneralUtility::revExplode('_', $ref, 2);
1083  switch ($table) {
1084  case 'index_config':
1085  $idxRec = $this->databaseConnection->exec_SELECTgetSingleRow('uid', 'index_config', 'uid=' . (int)$uid . $this->cObj->enableFields('index_config'));
1086  if ($idxRec) {
1087  $list[] = $uid;
1088  }
1089  break;
1090  case 'pages':
1091  $indexCfgRecordsFromPid = $this->databaseConnection->exec_SELECTgetRows('uid', 'index_config', 'pid=' . (int)$uid . $this->cObj->enableFields('index_config'));
1092  foreach ($indexCfgRecordsFromPid as $idxRec) {
1093  $list[] = $idxRec['uid'];
1094  }
1095  break;
1096  }
1097  }
1098  $list = array_unique($list);
1099  } else {
1100  $list = array((int)$freeIndexUid);
1101  }
1102  return ' AND IP.freeIndexUid IN (' . implode(',', $list) . ')';
1103  }
1104 
1112  public function execFinalQuery($list, $freeIndexUid = -1)
1113  {
1114  // Setting up methods of filtering results based on page types, access, etc.
1115  $page_join = '';
1116  $page_where = '';
1117  // Calling hook for alternative creation of page ID list
1118  if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
1119  $page_where = $hookObj->execFinalQuery_idList($list);
1120  } elseif ($this->join_pages) {
1121  // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
1122  $page_join = ',
1123  pages';
1124  $page_where = 'pages.uid = ISEC.page_id
1125  ' . $this->cObj->enableFields('pages') . '
1126  AND pages.no_search=0
1127  AND pages.doktype<200
1128  ';
1129  } elseif ($this->wholeSiteIdList >= 0) {
1130  // 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!
1131  $siteIdNumbers = GeneralUtility::intExplode(',', $this->wholeSiteIdList);
1132  $id_list = array();
1133  foreach ($siteIdNumbers as $rootId) {
1134  $id_list[] = $this->cObj->getTreeList(-1 * $rootId, 9999);
1135  }
1136  $page_where = 'ISEC.page_id IN (' . implode(',', $id_list) . ')';
1137  } else {
1138  // Disable everything... (select all)
1139  $page_where = '1=1';
1140  }
1141  // Indexing configuration clause:
1142  $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
1143  // 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.
1144  if (substr($this->piVars['order'], 0, 5) == 'rank_') {
1145  switch ($this->piVars['order']) {
1146  case 'rank_flag':
1147  // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1148  // The ordering is refined with the frequency sum as well.
1149  $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1150  $orderBy = 'order_val1' . $this->isDescending() . ',order_val2' . $this->isDescending();
1151  break;
1152  case 'rank_first':
1153  // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1154  $grsel = 'AVG(IR.first) AS order_val';
1155  $orderBy = 'order_val' . $this->isDescending(1);
1156  break;
1157  case 'rank_count':
1158  // Number of words found
1159  $grsel = 'SUM(IR.count) AS order_val';
1160  $orderBy = 'order_val' . $this->isDescending();
1161  break;
1162  default:
1163  // Frequency sum. I'm not sure if this is the best way to do it (make a sum...). Or should it be the average?
1164  $grsel = 'SUM(IR.freq) AS order_val';
1165  $orderBy = 'order_val' . $this->isDescending();
1166  }
1167 
1168  // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1169  $wordSel = '(' . implode(' OR ', $this->wSelClauses) . ') AND ';
1170 
1171  $res = $this->databaseConnection->exec_SELECTquery(
1172  'ISEC.*, IP.*, ' . $grsel,
1173  'index_words IW,
1174  index_rel IR,
1175  index_section ISEC,
1176  index_phash IP' . $page_join,
1177  $wordSel .
1178  'IP.phash IN (' . $list . ') ' .
1179  $this->mediaTypeWhere() . ' ' . $this->languageWhere() . $freeIndexUidClause . '
1180  AND IW.wid=IR.wid
1181  AND ISEC.phash = IR.phash
1182  AND IP.phash = IR.phash
1183  AND ' . $page_where,
1184  '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',
1185  $orderBy
1186  );
1187  } else {
1188  // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1189  $orderBy = '';
1190  switch ((string)$this->piVars['order']) {
1191  case 'title':
1192  $orderBy = 'IP.item_title' . $this->isDescending();
1193  break;
1194  case 'crdate':
1195  $orderBy = 'IP.item_crdate' . $this->isDescending();
1196  break;
1197  case 'mtime':
1198  $orderBy = 'IP.item_mtime' . $this->isDescending();
1199  break;
1200  }
1201  $res = $this->databaseConnection->exec_SELECTquery('ISEC.*, IP.*', 'index_phash IP,index_section ISEC' . $page_join, 'IP.phash IN (' . $list . ') ' . $this->mediaTypeWhere() . ' ' . $this->languageWhere() . $freeIndexUidClause . '
1202  AND IP.phash = ISEC.phash
1203  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);
1204  }
1205  return $res;
1206  }
1207 
1215  public function checkResume($row)
1216  {
1217  // If the record is indexed by an indexing configuration, just show it.
1218  // At least this is needed for external URLs and files.
1219  // For records we might need to extend this - for instance block display if record is access restricted.
1220  if ($row['freeIndexUid']) {
1221  return true;
1222  }
1223  // Evaluate regularly indexed pages based on item_type:
1224  if ($row['item_type']) {
1225  // External media:
1226  // For external media we will check the access of the parent page on which the media was linked from.
1227  // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1228  // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1229  // 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.
1230  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1231  $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'));
1232  } else {
1233  $res = false;
1234  }
1235  if ($res && $this->databaseConnection->sql_num_rows($res)) {
1236  return true;
1237  } else {
1238  return false;
1239  }
1240  } else {
1241  // Ordinary TYPO3 pages:
1242  if ((string)$row['gr_list'] !== (string)$this->frontendController->gr_list) {
1243  // 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...
1244  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1245  $res = $this->databaseConnection->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash'] . ' AND gr_list=' . $this->databaseConnection->fullQuoteStr($this->frontendController->gr_list, 'index_grlist'));
1246  } else {
1247  $res = false;
1248  }
1249  if ($res && $this->databaseConnection->sql_num_rows($res)) {
1250  return true;
1251  } else {
1252  return false;
1253  }
1254  } else {
1255  return true;
1256  }
1257  }
1258  }
1259 
1268  public function checkExistance($row)
1269  {
1271  return $this->checkExistence($row);
1272  }
1273 
1281  protected function checkExistence($row)
1282  {
1283  $recordExists = true;
1284  // Always expect that page content exists
1285  if ($row['item_type']) {
1286  // External media:
1287  if (!is_file($row['data_filename']) || !file_exists($row['data_filename'])) {
1288  $recordExists = false;
1289  }
1290  }
1291  return $recordExists;
1292  }
1293 
1300  public function isDescending($inverse = false)
1301  {
1302  $desc = $this->piVars['desc'];
1303  if ($inverse) {
1304  $desc = !$desc;
1305  }
1306  return !$desc ? ' DESC' : '';
1307  }
1308 
1317  public function writeSearchStat($sWArr, $count, $pt)
1318  {
1319  $insertFields = array(
1320  'searchstring' => $this->piVars['sword'],
1321  'searchoptions' => serialize(array($this->piVars, $sWArr, $pt)),
1322  'feuser_id' => (int)$GLOBALS['TSFE']->user['uid'],
1323  // fe_user id, integer
1324  'cookie' => (string)$GLOBALS['TSFE']->id,
1325  // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1326  'IP' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1327  // Remote IP address
1328  'hits' => (int)$count,
1329  // Number of hits on the search.
1330  'tstamp' => $GLOBALS['EXEC_TIME']
1331  );
1332  $this->databaseConnection->exec_INSERTquery('index_stat_search', $insertFields);
1333  $newId = $this->databaseConnection->sql_insert_id();
1334  if ($newId) {
1335  foreach ($sWArr as $val) {
1336  $insertFields = array(
1337  'word' => $val['sword'],
1338  'index_stat_search_id' => $newId,
1339  'tstamp' => $GLOBALS['EXEC_TIME'],
1340  // Time stamp
1341  'pageid' => $this->frontendController->id
1342  );
1343  $this->databaseConnection->exec_INSERTquery('index_stat_word', $insertFields);
1344  }
1345  }
1346  }
1347 
1348  /***********************************
1349  *
1350  * HTML output functions
1351  *
1352  ***********************************/
1359  public function makeSearchForm($optValues)
1360  {
1361  $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
1362  // Multilangual text
1363  $substituteArray = array('legend', 'searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style', 'freeIndexUid');
1364  foreach ($substituteArray as $marker) {
1365  $markerArray['###FORM_' . GeneralUtility::strtoupper($marker) . '###'] = $this->pi_getLL('form_' . $marker, '', true);
1366  }
1367  $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label', '', true);
1368  // Adding search field value
1369  $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1370  // Additonal keyword => "Add to current search words"
1371  if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1372  $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1373  $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"' : '';
1374  $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch', '', true);
1375  } else {
1376  $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1377  }
1378  $markerArray['###ACTION_URL###'] = htmlspecialchars($this->getSearchFormActionURL());
1379  $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
1380  $hiddenFieldCode = preg_replace('/^\\n\\t(.+)/ms', '$1', $hiddenFieldCode);
1381  // Remove first newline and tab (cosmetical issue)
1382  $hiddenFieldArr = array();
1383  foreach (GeneralUtility::trimExplode(',', $this->hiddenFieldList) as $fieldName) {
1384  $hiddenFieldMarkerArray = array();
1385  $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = $this->prefixId . '[' . $fieldName . ']';
1386  $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string)$this->piVars[$fieldName]);
1387  $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, array(), array());
1388  }
1389  // Extended search
1390  if ($this->piVars['ext']) {
1391  // Search for
1392  if (!is_array($optValues['type']) && !is_array($optValues['defOp']) || $this->conf['blind.']['type'] && $this->conf['blind.']['defOp']) {
1393  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1394  } else {
1395  if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
1396  unset($hiddenFieldArr['type']);
1397  $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'], $optValues['type']);
1398  } else {
1399  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1400  }
1401  if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp']) {
1402  $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'], $optValues['defOp']);
1403  } else {
1404  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1405  }
1406  }
1407  // Search in
1408  if (!is_array($optValues['media']) && !is_array($optValues['lang']) || $this->conf['blind.']['media'] && $this->conf['blind.']['lang']) {
1409  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1410  } else {
1411  if (is_array($optValues['media']) && !$this->conf['blind.']['media']) {
1412  unset($hiddenFieldArr['media']);
1413  $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'], $optValues['media']);
1414  } else {
1415  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1416  }
1417  if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
1418  unset($hiddenFieldArr['lang']);
1419  $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'], $optValues['lang']);
1420  } else {
1421  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1422  }
1423  }
1424  // Sections
1425  if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
1426  $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1427  } else {
1428  $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'], $optValues['sections']);
1429  }
1430  // Free Indexing Configurations:
1431  if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid']) {
1432  $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
1433  } else {
1434  $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'], $optValues['freeIndexUid']);
1435  }
1436  // Sorting
1437  if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order']) {
1438  $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1439  } else {
1440  unset($hiddenFieldArr['order']);
1441  unset($hiddenFieldArr['desc']);
1442  unset($hiddenFieldArr['results']);
1443  $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'], $optValues['order']);
1444  $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'], $optValues['desc']);
1445  $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1446  }
1447  // Limits
1448  if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results']) {
1449  $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1450  } else {
1451  $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1452  }
1453  // Grouping
1454  if (!is_array($optValues['group']) || $this->conf['blind.']['group']) {
1455  $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1456  } else {
1457  unset($hiddenFieldArr['group']);
1458  $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'], $optValues['group']);
1459  }
1460  if ($this->conf['blind.']['extResume']) {
1461  $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1462  } else {
1463  $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
1464  }
1465  } else {
1466  // Extended search
1467  $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1468  }
1469  if ($this->conf['show.']['advancedSearchLink']) {
1470  $linkToOtherMode = $this->piVars['ext'] ? $this->pi_getPageLink($this->frontendController->id, $this->frontendController->sPre) : $this->pi_getPageLink($this->frontendController->id, $this->frontendController->sPre, array($this->prefixId . '[ext]' => 1));
1471  $markerArray['###LINKTOOTHERMODE###'] = '<a href="' . htmlspecialchars($linkToOtherMode) . '">' . $this->pi_getLL(($this->piVars['ext'] ? 'link_regularSearch' : 'link_advancedSearch'), '', true) . '</a>';
1472  } else {
1473  $markerArray['###LINKTOOTHERMODE###'] = '';
1474  }
1475  // Write all hidden fields
1476  $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('', $hiddenFieldArr));
1477  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1478  return $substitutedContent;
1479  }
1480 
1488  public function renderSelectBoxValues($value, $optValues)
1489  {
1490  if (!is_array($optValues)) {
1491  return '';
1492  }
1493  $opt = array();
1494  $isSelFlag = 0;
1495  foreach ($optValues as $k => $v) {
1496  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1497  if ($sel) {
1498  $isSelFlag++;
1499  }
1500  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1501  }
1502  return implode('', $opt);
1503  }
1504 
1510  public function printRules()
1511  {
1512  if (!$this->conf['show.']['rules']) {
1513  return '';
1514  }
1515  $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1516  $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header', '', true);
1517  $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text', '', true)));
1518  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1519  return $this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']);
1520  }
1521 
1527  public function printResultSectionLinks()
1528  {
1529  if (empty($this->resultSections)) {
1530  return '';
1531  }
1532  $links = array();
1533  $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1534  $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1535  $anchorPrefix = $GLOBALS['TSFE']->baseUrl ? substr(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'))) : '';
1536  foreach ($this->resultSections as $id => $dat) {
1537  $markerArray = array();
1538  $aBegin = '<a href="' . htmlspecialchars($anchorPrefix . '#anchor_' . md5($id)) . '">';
1539  $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) . ')';
1540  $aEnd = '</a>';
1541  $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1542  $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
1543  }
1544  $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode('', $links)), array(), array());
1545  return '<div' . $this->pi_classParam('sectionlinks') . '>' . $this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']) . '</div>';
1546  }
1547 
1556  public function makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
1557  {
1558  $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1559  $markerArray['###ANCHOR_URL###'] = 'anchor_' . md5($id);
1560  $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1561  $markerArray['###RESULT_COUNT###'] = $countResultRows;
1562  $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page' . ($countResultRows > 1 ? 's' : ''));
1563  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1564  return $substitutedContent;
1565  }
1566 
1574  public function printResultRow($row, $headerOnly = 0)
1575  {
1576  // Get template content:
1577  $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1578  if ($hookObj = $this->hookRequest('printResultRow')) {
1579  return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1580  } else {
1581  $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1582  if (!is_array($row['_sub'])) {
1583  $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1584  }
1585  if (!$headerOnly) {
1586  $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1587  } elseif ($headerOnly == 1) {
1588  $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1589  } elseif ($headerOnly == 2) {
1590  $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1591  $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1592  }
1593  if (is_array($tmplContent)) {
1594  foreach ($tmplContent as $k => $v) {
1595  $markerArray['###' . GeneralUtility::strtoupper($k) . '###'] = $v;
1596  }
1597  }
1598  // Description text
1599  $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size', '', true);
1600  $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created', '', true);
1601  $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified', '', true);
1602  $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path', '', true);
1603  $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1604  // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1605  if (is_array($row['_sub'])) {
1606  if ($this->multiplePagesType($row['item_type'])) {
1607  $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching', '', true), $html);
1608  foreach ($row['_sub'] as $subRow) {
1609  $html .= $this->printResultRow($subRow, 1);
1610  }
1611  } else {
1612  $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching', '', true);
1613  $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell', '', true), $html);
1614  }
1615  }
1616  return $html;
1617  }
1618  }
1619 
1629  public function pi_list_browseresults($showResultCount = true, $addString = '', $addPart = '', $freeIndexUid = -1)
1630  {
1631  // Initializing variables:
1632  $pointer = (int)$this->piVars['pointer'];
1633  $count = (int)$this->internal['res_count'];
1634  $results_at_a_time = MathUtility::forceIntegerInRange($this->internal['results_at_a_time'], 1, 1000);
1635  $pageCount = (int)ceil($count / $results_at_a_time);
1636 
1637  $links = array();
1638  // only show the result browser if more than one page is needed
1639  if ($pageCount > 1) {
1640  $maxPages = MathUtility::forceIntegerInRange($this->internal['maxPages'], 1, $pageCount);
1641 
1642  // Make browse-table/links:
1643  if ($pointer > 0) {
1644  // all pages after the 1st one
1645  $links[] = '<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev', '< Previous', true), $pointer - 1, $freeIndexUid) . '</li>';
1646  }
1647  $minPage = $pointer - (int)floor($maxPages / 2);
1648  $maxPage = $minPage + $maxPages - 1;
1649  // Check if the indexes are within the page limits
1650  if ($minPage < 0) {
1651  $maxPage -= $minPage;
1652  $minPage = 0;
1653  } elseif ($maxPage >= $pageCount) {
1654  $minPage -= $maxPage - $pageCount + 1;
1655  $maxPage = $pageCount - 1;
1656  }
1657  $pageLabel = $this->pi_getLL('pi_list_browseresults_page', 'Page', true);
1658  for ($a = $minPage; $a <= $maxPage; $a++) {
1659  $label = trim($pageLabel . ' ' . ($a + 1));
1660  $link = $this->makePointerSelector_link($label, $a, $freeIndexUid);
1661  if ($a === $pointer) {
1662  $links[] = '<li' . $this->pi_classParam('browselist-currentPage') . '><strong>' . $link . '</strong></li>';
1663  } else {
1664  $links[] = '<li>' . $link . '</li>';
1665  }
1666  }
1667  if ($pointer + 1 < $pageCount) {
1668  $links[] = '<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next', 'Next >', true), $pointer + 1, $freeIndexUid) . '</li>';
1669  }
1670  }
1671  if (!empty($links)) {
1672  $addPart .= '
1673  <ul class="browsebox">
1674  ' . implode('', $links) . '
1675  </ul>';
1676  }
1677  $label = str_replace(
1678  array('###TAG_BEGIN###', '###TAG_END###'),
1679  array('<strong>', '</strong>'),
1680  $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###')
1681  );
1682  $resultsFrom = $pointer * $results_at_a_time + 1;
1683  $resultsTo = min($resultsFrom + $results_at_a_time - 1, $count);
1684  $resultCountText = '';
1685  if ($showResultCount) {
1686  $resultCountText = '<p>' . sprintf($label, $resultsFrom, $resultsTo, $count) . $addString . '</p>';
1687  }
1688  $sTables = '<div' . $this->pi_classParam('browsebox') . '>'
1689  . $resultCountText
1690  . $addPart . '</div>';
1691  return $sTables;
1692  }
1693 
1694  /***********************************
1695  *
1696  * Support functions for HTML output (with a minimum of fixed markup)
1697  *
1698  ***********************************/
1706  public function prepareResultRowTemplateData($row, $headerOnly)
1707  {
1708  // Initialize:
1709  $specRowConf = $this->getSpecialConfigForRow($row);
1710  $CSSsuffix = $specRowConf['CSSsuffix'] ? '-' . $specRowConf['CSSsuffix'] : '';
1711  // If external media, link to the media-file instead.
1712  if ($row['item_type']) {
1713  // External media
1714  if ($row['show_resume']) {
1715  // Can link directly.
1716  $targetAttribute = '';
1717  if ($this->frontendController->config['config']['fileTarget']) {
1718  $targetAttribute = ' target="' . htmlspecialchars($this->frontendController->config['config']['fileTarget']) . '"';
1719  }
1720  $title = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' . htmlspecialchars($this->makeTitle($row)) . '</a>';
1721  } else {
1722  // Suspicious, so linking to page instead...
1723  $copy_row = $row;
1724  unset($copy_row['cHashParams']);
1725  $title = $this->linkPage($row['page_id'], htmlspecialchars($this->makeTitle($row)), $copy_row);
1726  }
1727  } else {
1728  // Else the page:
1729  // Prepare search words for markup in content:
1730  if ($this->conf['forwardSearchWordsInResultLink']) {
1731  if ($this->conf['forwardSearchWordsInResultLink.']['no_cache']) {
1732  $markUpSwParams = array('no_cache' => 1);
1733  } else {
1734  $markUpSwParams = array();
1735  }
1736  foreach ($this->sWArr as $d) {
1737  $markUpSwParams['sword_list'][] = $d['sword'];
1738  }
1739  } else {
1740  $markUpSwParams = array();
1741  }
1742  $title = $this->linkPage($row['data_page_id'], htmlspecialchars($this->makeTitle($row)), $row, $markUpSwParams);
1743  }
1744  $tmplContent = array();
1745  $tmplContent['title'] = $title;
1746  $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'] . ': ' : '&nbsp;';
1747  $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'], '', $specRowConf);
1748  $tmplContent['rating'] = $this->makeRating($row);
1749  $tmplContent['description'] = $this->makeDescription(
1750  $row,
1751  !($this->piVars['extResume'] && !$headerOnly),
1752  $this->conf['results.']['summaryCropAfter']
1753  );
1754  $tmplContent = $this->makeInfo($row, $tmplContent);
1755  $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1756  $tmplContent['language'] = $this->makeLanguageIndication($row);
1757  $tmplContent['CSSsuffix'] = $CSSsuffix;
1758  // Post processing with hook.
1759  if ($hookObj = $this->hookRequest('prepareResultRowTemplateData_postProc')) {
1760  $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1761  }
1762  return $tmplContent;
1763  }
1764 
1772  {
1773  // Init:
1774  $searchingFor = '';
1775  $c = 0;
1776  // Traverse search words:
1777  foreach ($sWArr as $k => $v) {
1778  if ($c) {
1779  switch ($v['oper']) {
1780  case 'OR':
1781  $searchingFor .= ' ' . $this->pi_getLL('searchFor_or', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1782  break;
1783  case 'AND NOT':
1784  $searchingFor .= ' ' . $this->pi_getLL('searchFor_butNot', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1785  break;
1786  default:
1787  // AND...
1788  $searchingFor .= ' ' . $this->pi_getLL('searchFor_and', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1789  }
1790  } else {
1791  $searchingFor = $this->pi_getLL('searchFor', '', true) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1792  }
1793  $c++;
1794  }
1795  return $searchingFor;
1796  }
1797 
1804  public function wrapSW($str)
1805  {
1806  return '"<span' . $this->pi_classParam('sw') . '>' . htmlspecialchars($str) . '</span>"';
1807  }
1808 
1817  public function renderSelectBox($name, $value, $optValues)
1818  {
1819  if (is_array($optValues)) {
1820  $opt = array();
1821  $isSelFlag = 0;
1822  foreach ($optValues as $k => $v) {
1823  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1824  if ($sel) {
1825  $isSelFlag++;
1826  }
1827  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1828  }
1829  return '<select name="' . $name . '">' . implode('', $opt) . '</select>';
1830  }
1831  }
1832 
1842  public function makePointerSelector_link($str, $p, $freeIndexUid)
1843  {
1844  $onclick = 'document.getElementById(\'' . $this->prefixId . '_pointer\').value=\'' . $p . '\';' . 'document.getElementById(\'' . $this->prefixId . '_freeIndexUid\').value=\'' . rawurlencode($freeIndexUid) . '\';' . 'document.getElementById(\'' . $this->prefixId . '\').submit();return false;';
1845  return '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . $str . '</a>';
1846  }
1847 
1856  public function makeItemTypeIcon($it, $alt = '', $specRowConf)
1857  {
1858  // Build compound key if item type is 0, iconRendering is not used
1859  // and specConfs.[pid].pageIcon was set in TS
1860  if ($it === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->conf['iconRendering.'])) {
1861  $it .= ':' . $specRowConf['_pid'];
1862  }
1863  if (!isset($this->iconFileNameCache[$it])) {
1864  $this->iconFileNameCache[$it] = '';
1865  // If TypoScript is used to render the icon:
1866  if (is_array($this->conf['iconRendering.'])) {
1867  $this->cObj->setCurrentVal($it);
1868  $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'], $this->conf['iconRendering.']);
1869  } else {
1870  // Default creation / finding of icon:
1871  $icon = '';
1872  if ($it === '0' || substr($it, 0, 2) == '0:') {
1873  if (is_array($specRowConf['pageIcon.'])) {
1874  $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle('IMAGE', $specRowConf['pageIcon.']);
1875  } else {
1876  $icon = 'EXT:indexed_search/Resources/Public/Icons/FileTypes/pages.gif';
1877  }
1878  } elseif ($this->external_parsers[$it]) {
1879  $icon = $this->external_parsers[$it]->getIcon($it);
1880  }
1881  if ($icon) {
1882  $fullPath = GeneralUtility::getFileAbsFileName($icon);
1883  if ($fullPath) {
1884  $info = @getimagesize($fullPath);
1885  $iconPath = \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($fullPath);
1886  $this->iconFileNameCache[$it] = is_array($info) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
1887  }
1888  }
1889  }
1890  }
1891  return $this->iconFileNameCache[$it];
1892  }
1893 
1900  public function makeRating($row)
1901  {
1902  switch ((string)$this->piVars['order']) {
1903  case 'rank_count':
1904  // Number of occurencies on page
1905  return $row['order_val'] . ' ' . $this->pi_getLL('maketitle_matches');
1906  break;
1907  case 'rank_first':
1908  // Close to top of page
1909  return ceil(MathUtility::forceIntegerInRange((255 - $row['order_val']), 1, 255) / 255 * 100) . '%';
1910  break;
1911  case 'rank_flag':
1912  // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1913  if ($this->firstRow['order_val2']) {
1914  $base = $row['order_val1'] * 256;
1915  // (3 MSB bit, 224 is highest value of order_val1 currently)
1916  $freqNumber = $row['order_val2'] / $this->firstRow['order_val2'] * pow(2, 12);
1917  // 15-3 MSB = 12
1918  $total = MathUtility::forceIntegerInRange($base + $freqNumber, 0, 32767);
1919  return ceil(log($total) / log(32767) * 100) . '%';
1920  }
1921  break;
1922  case 'rank_freq':
1923  // Based on frequency
1924  $max = 10000;
1925  $total = MathUtility::forceIntegerInRange($row['order_val'], 0, $max);
1926  return ceil(log($total) / log($max) * 100) . '%';
1927  break;
1928  case 'crdate':
1929  // Based on creation date
1930  return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'], 0);
1931  break;
1932  case 'mtime':
1933  // Based on modification time
1934  return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'], 0);
1935  break;
1936  default:
1937  // fx. title
1938  return '&nbsp;';
1939  }
1940  }
1941 
1950  public function makeDescription($row, $noMarkup = false, $lgd = 180)
1951  {
1952  if ($row['show_resume']) {
1953  $markedSW = '';
1954  $outputStr = '';
1955  if (!$noMarkup) {
1956  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_fulltext')) {
1957  $res = $this->databaseConnection->exec_SELECTquery('*', 'index_fulltext', 'phash=' . (int)$row['phash']);
1958  } else {
1959  $res = false;
1960  }
1961  if ($res) {
1962  if ($ftdrow = $this->databaseConnection->sql_fetch_assoc($res)) {
1963  // Cut HTTP references after some length
1964  $content = preg_replace('/(http:\\/\\/[^ ]{' . $this->conf['results.']['hrefInSummaryCropAfter'] . '})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
1965  $markedSW = $this->markupSWpartsOfString($content);
1966  }
1967  $this->databaseConnection->sql_free_result($res);
1968  }
1969  }
1970  if (!trim($markedSW)) {
1971  $outputStr = $this->frontendController->csConvObj->crop('utf-8', $row['item_description'], $lgd, $this->conf['results.']['summaryCropSignifier']);
1972  $outputStr = htmlspecialchars($outputStr);
1973  }
1974  $output = $this->utf8_to_currentCharset($outputStr ?: $markedSW);
1975  } else {
1976  $output = '<span class="noResume">' . $this->pi_getLL('res_noResume', '', true) . '</span>';
1977  }
1978  return $output;
1979  }
1980 
1987  public function markupSWpartsOfString($str)
1988  {
1989  $htmlParser = GeneralUtility::makeInstance(HtmlParser::class);
1990  // Init:
1991  $str = str_replace('&nbsp;', ' ', $htmlParser->bidir_htmlspecialchars($str, -1));
1992  $str = preg_replace('/\\s\\s+/', ' ', $str);
1993  $swForReg = array();
1994  // Prepare search words for regex:
1995  foreach ($this->sWArr as $d) {
1996  $swForReg[] = preg_quote($d['sword'], '/');
1997  }
1998  $regExString = '(' . implode('|', $swForReg) . ')';
1999  // Split and combine:
2000  $parts = preg_split('/' . $regExString . '/ui', ' ' . $str . ' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
2001  // Constants:
2002  $summaryMax = $this->conf['results.']['markupSW_summaryMax'];
2003  $postPreLgd = $this->conf['results.']['markupSW_postPreLgd'];
2004  $postPreLgd_offset = $this->conf['results.']['markupSW_postPreLgd_offset'];
2005  $divider = $this->conf['results.']['markupSW_divider'];
2006  $occurencies = (count($parts) - 1) / 2;
2007  if ($occurencies) {
2008  $postPreLgd = MathUtility::forceIntegerInRange($summaryMax / $occurencies, $postPreLgd, $summaryMax / 2);
2009  }
2010  // Variable:
2011  $summaryLgd = 0;
2012  $output = array();
2013  // Shorten in-between strings:
2014  foreach ($parts as $k => $strP) {
2015  if ($k % 2 == 0) {
2016  // Find length of the summary part:
2017  $strLen = $this->frontendController->csConvObj->strlen('utf-8', $parts[$k]);
2018  $output[$k] = $parts[$k];
2019  // Possibly shorten string:
2020  if (!$k) {
2021  // First entry at all (only cropped on the frontside)
2022  if ($strLen > $postPreLgd) {
2023  $output[$k] = $divider . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $this->frontendController->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
2024  }
2025  } elseif ($summaryLgd > $summaryMax || !isset($parts[($k + 1)])) {
2026  // In case summary length is exceed OR if there are no more entries at all:
2027  if ($strLen > $postPreLgd) {
2028  $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/', '', $this->frontendController->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider;
2029  }
2030  } else {
2031  // In-between search words:
2032  if ($strLen > $postPreLgd * 2) {
2033  $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)));
2034  }
2035  }
2036  $summaryLgd += $this->frontendController->csConvObj->strlen('utf-8', $output[$k]);
2037  // Protect output:
2038  $output[$k] = htmlspecialchars($output[$k]);
2039  // If summary lgd is exceed, break the process:
2040  if ($summaryLgd > $summaryMax) {
2041  break;
2042  }
2043  } else {
2044  $summaryLgd += $this->frontendController->csConvObj->strlen('utf-8', $strP);
2045  $output[$k] = '<strong class="tx-indexedsearch-redMarkup">' . htmlspecialchars($parts[$k]) . '</strong>';
2046  }
2047  }
2048  // Return result:
2049  return implode('', $output);
2050  }
2051 
2058  public function makeTitle($row)
2059  {
2060  $add = '';
2061  if ($this->multiplePagesType($row['item_type'])) {
2062  $dat = unserialize($row['cHashParams']);
2063  $pp = explode('-', $dat['key']);
2064  if ($pp[0] != $pp[1]) {
2065  $add = ', ' . $this->pi_getLL('word_pages') . ' ' . $dat['key'];
2066  } else {
2067  $add = ', ' . $this->pi_getLL('word_page') . ' ' . $pp[0];
2068  }
2069  }
2070  $outputString = $this->frontendController->csConvObj->crop('utf-8', $row['item_title'], $this->conf['results.']['titleCropAfter'], $this->conf['results.']['titleCropSignifier']);
2071  return $this->utf8_to_currentCharset($outputString) . $add;
2072  }
2073 
2081  public function makeInfo($row, $tmplArray)
2082  {
2083  $tmplArray['size'] = GeneralUtility::formatSize($row['item_size']);
2084  $tmplArray['created'] = $this->formatCreatedDate($row['item_crdate']);
2085  $tmplArray['modified'] = $this->formatModifiedDate($row['item_mtime']);
2086  $pathId = $row['data_page_id'] ?: $row['page_id'];
2087  $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2088  $pI = parse_url($row['data_filename']);
2089  if ($pI['scheme']) {
2090  $targetAttribute = '';
2091  if ($this->frontendController->config['config']['fileTarget']) {
2092  $targetAttribute = ' target="' . htmlspecialchars($this->frontendController->config['config']['fileTarget']) . '"';
2093  }
2094  $tmplArray['path'] = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' . htmlspecialchars($row['data_filename']) . '</a>';
2095  } else {
2096  $pathStr = $this->getPathFromPageId($pathId, $pathMP);
2097  $tmplArray['path'] = $this->linkPage($pathId, $pathStr, array(
2098  'cHashParams' => $row['cHashParams'],
2099  'data_page_type' => $row['data_page_type'],
2100  'data_page_mp' => $pathMP,
2101  'sys_language_uid' => $row['sys_language_uid']
2102  ));
2103  }
2104  return $tmplArray;
2105  }
2106 
2113  public function getSpecialConfigForRow($row)
2114  {
2115  $pathId = $row['data_page_id'] ?: $row['page_id'];
2116  $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2117  $rl = $this->getRootLine($pathId, $pathMP);
2118  $specConf = $this->conf['specConfs.']['0.'];
2119  if (is_array($rl)) {
2120  foreach ($rl as $dat) {
2121  if (is_array($this->conf['specConfs.'][$dat['uid'] . '.'])) {
2122  $specConf = $this->conf['specConfs.'][$dat['uid'] . '.'];
2123  $specConf['_pid'] = $dat['uid'];
2124  break;
2125  }
2126  }
2127  }
2128  return $specConf;
2129  }
2130 
2137  public function makeLanguageIndication($row)
2138  {
2139  // If search result is a TYPO3 page:
2140  if ((string)$row['item_type'] === '0') {
2141  // If TypoScript is used to render the flag:
2142  if (is_array($this->conf['flagRendering.'])) {
2143  $this->cObj->setCurrentVal($row['sys_language_uid']);
2144  return $this->cObj->cObjGetSingle($this->conf['flagRendering'], $this->conf['flagRendering.']);
2145  }
2146  }
2147  return '&nbsp;';
2148  }
2149 
2157  public function makeAccessIndication($id)
2158  {
2159  if (is_array($this->fe_groups_required[$id]) && !empty($this->fe_groups_required[$id])) {
2160  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="" />';
2161  }
2162 
2163  return '';
2164  }
2165 
2175  public function linkPage($id, $str, $row = array(), $markUpSwParams = array())
2176  {
2177  // Parameters for link:
2178  $urlParameters = (array)unserialize($row['cHashParams']);
2179  // Add &type and &MP variable:
2180  if ($row['data_page_type']) {
2181  $urlParameters['type'] = $row['data_page_type'];
2182  }
2183  if ($row['data_page_mp']) {
2184  $urlParameters['MP'] = $row['data_page_mp'];
2185  }
2186  if ($row['sys_language_uid']) {
2187  $urlParameters['L'] = $row['sys_language_uid'];
2188  }
2189  // markup-GET vars:
2190  $urlParameters = array_merge($urlParameters, $markUpSwParams);
2191  // 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...
2192  if (!is_array($this->domain_records[$id])) {
2193  $this->getPathFromPageId($id);
2194  }
2195  // If external domain, then link to that:
2196  if (!empty($this->domain_records[$id])) {
2197  reset($this->domain_records[$id]);
2198  $firstDom = current($this->domain_records[$id]);
2199  $scheme = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2200  $addParams = '';
2201  if (is_array($urlParameters) && !empty($urlParameters)) {
2202  $addParams .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
2203  }
2204  if ($target = $this->conf['search.']['detect_sys_domain_records.']['target']) {
2205  $target = ' target="' . $target . '"';
2206  }
2207  return '<a href="' . htmlspecialchars(($scheme . $firstDom . '/index.php?id=' . $id . $addParams)) . '"' . $target . '>' . htmlspecialchars($str) . '</a>';
2208  } else {
2209  return $this->pi_linkToPage($str, $id, $this->conf['result_link_target'], $urlParameters);
2210  }
2211  }
2212 
2220  public function getRootLine($id, $pathMP = '')
2221  {
2222  $identStr = $id . '|' . $pathMP;
2223  if (!isset($this->cache_path[$identStr])) {
2224  $this->cache_rl[$identStr] = $this->frontendController->sys_page->getRootLine($id, $pathMP);
2225  }
2226  return $this->cache_rl[$identStr];
2227  }
2228 
2235  public function getFirstSysDomainRecordForPage($id)
2236  {
2237  $res = $this->databaseConnection->exec_SELECTquery('domainName', 'sys_domain', 'pid=' . (int)$id . $this->cObj->enableFields('sys_domain'), '', 'sorting');
2238  $row = $this->databaseConnection->sql_fetch_assoc($res);
2239  return rtrim($row['domainName'], '/');
2240  }
2241 
2249  public function getPathFromPageId($id, $pathMP = '')
2250  {
2251  $identStr = $id . '|' . $pathMP;
2252  if (!isset($this->cache_path[$identStr])) {
2253  $this->fe_groups_required[$id] = array();
2254  $this->domain_records[$id] = array();
2255  $rl = $this->getRootLine($id, $pathMP);
2256  $path = '';
2257  $pageCount = count($rl);
2258  if (is_array($rl) && !empty($rl)) {
2259  $index = 0;
2260  $breadcrumbWrap = isset($this->conf['breadcrumbWrap']) ? $this->conf['breadcrumbWrap'] : '/';
2261  $breadcrumbWraps = $GLOBALS['TSFE']->tmpl->splitConfArray(array('wrap' => $breadcrumbWrap), $pageCount);
2262  foreach ($rl as $k => $v) {
2263  // Check fe_user
2264  if ($v['fe_group'] && ($v['uid'] == $id || $v['extendToSubpages'])) {
2265  $this->fe_groups_required[$id][] = $v['fe_group'];
2266  }
2267  // Check sys_domain.
2268  if ($this->conf['search.']['detect_sys_domain_records']) {
2269  $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2270  if ($sysDName) {
2271  $this->domain_records[$id][] = $sysDName;
2272  // Set path accordingly:
2273  $path = $sysDName . $path;
2274  break;
2275  }
2276  }
2277  // Stop, if we find that the current id is the current root page.
2278  if ($v['uid'] == $this->frontendController->config['rootLine'][0]['uid']) {
2279  array_pop($breadcrumbWraps);
2280  break;
2281  }
2282  $path = $this->cObj->wrap(htmlspecialchars($v['title']), array_pop($breadcrumbWraps)['wrap']) . $path;
2283  }
2284  }
2285  $this->cache_path[$identStr] = $path;
2286  if (is_array($this->conf['path_stdWrap.'])) {
2287  $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2288  }
2289  }
2290  return $this->cache_path[$identStr];
2291  }
2292 
2299  public function getMenu($id)
2300  {
2301  if ($this->conf['show.']['LxALLtypes']) {
2302  $output = array();
2303  $res = $this->databaseConnection->exec_SELECTquery('title,uid', 'pages', 'pid=' . (int)$id . $this->cObj->enableFields('pages'), '', 'sorting');
2304  while ($row = $this->databaseConnection->sql_fetch_assoc($res)) {
2305  $output[$row['uid']] = $this->frontendController->sys_page->getPageOverlay($row);
2306  }
2307  $this->databaseConnection->sql_free_result($res);
2308  return $output;
2309  } else {
2310  return $this->frontendController->sys_page->getMenu($id);
2311  }
2312  }
2313 
2320  public function multiplePagesType($item_type)
2321  {
2322  return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2323  }
2324 
2331  public function utf8_to_currentCharset($str)
2332  {
2333  return $this->frontendController->csConv($str, 'utf-8');
2334  }
2335 
2342  public function hookRequest($functionName)
2343  {
2344  // Hook: menuConfig_preProcessModMenu
2345  if ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2346  $hookObj = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2347  if (method_exists($hookObj, $functionName)) {
2348  $hookObj->pObj = $this;
2349  return $hookObj;
2350  }
2351  }
2352  }
2353 
2359  protected function getSearchFormActionURL()
2360  {
2361  $targetUrlPid = $this->getSearchFormActionPidFromTS();
2362  if ($targetUrlPid == 0) {
2363  $targetUrlPid = $this->frontendController->id;
2364  }
2365  return $this->pi_getPageLink($targetUrlPid, $this->frontendController->sPre);
2366  }
2367 
2373  protected function getSearchFormActionPidFromTS()
2374  {
2375  $result = 0;
2376  if (isset($this->conf['search.']['targetPid']) || isset($this->conf['search.']['targetPid.'])) {
2377  if (is_array($this->conf['search.']['targetPid.'])) {
2378  $result = $this->cObj->stdWrap($this->conf['search.']['targetPid'], $this->conf['search.']['targetPid.']);
2379  } else {
2380  $result = $this->conf['search.']['targetPid'];
2381  }
2382  $result = (int)$result;
2383  }
2384  return $result;
2385  }
2386 
2393  protected function formatCreatedDate($date)
2394  {
2395  $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2396  return $this->formatDate($date, 'created', $defaultFormat);
2397  }
2398 
2405  protected function formatModifiedDate($date)
2406  {
2407  $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2408  return $this->formatDate($date, 'modified', $defaultFormat);
2409  }
2410 
2420  protected function formatDate($date, $tsKey, $defaultFormat)
2421  {
2422  $strftimeFormat = $this->conf['dateFormat.'][$tsKey];
2423  if ($strftimeFormat) {
2424  $result = strftime($strftimeFormat, $date);
2425  } else {
2426  $result = date($defaultFormat, $date);
2427  }
2428  return $result;
2429  }
2430 
2437  public function getSearchType()
2438  {
2439  return (int)$this->piVars['type'];
2440  }
2441 
2447  public function getSearchRootPageIdList()
2448  {
2449  return \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->wholeSiteIdList);
2450  }
2451 
2458  public function getJoinPagesForQuery()
2459  {
2460  return (bool)$this->join_pages;
2461  }
2462 
2466  protected function loadSettings()
2467  {
2468  if (!is_array($this->conf['results.'])) {
2469  $this->conf['results.'] = array();
2470  }
2471  $this->conf['results.']['summaryCropAfter'] = MathUtility::forceIntegerInRange(
2472  $this->cObj->stdWrap($this->conf['results.']['summaryCropAfter'], $this->conf['results.']['summaryCropAfter.']),
2473  10, 5000, 180
2474  );
2475  $this->conf['results.']['summaryCropSignifier'] = $this->cObj->stdWrap($this->conf['results.']['summaryCropSignifier'], $this->conf['results.']['summaryCropSignifier.']);
2476  $this->conf['results.']['titleCropAfter'] = MathUtility::forceIntegerInRange(
2477  $this->cObj->stdWrap($this->conf['results.']['titleCropAfter'], $this->conf['results.']['titleCropAfter.']),
2478  10, 500, 50
2479  );
2480  $this->conf['results.']['titleCropSignifier'] = $this->cObj->stdWrap($this->conf['results.']['titleCropSignifier'], $this->conf['results.']['titleCropSignifier.']);
2481  $this->conf['results.']['markupSW_summaryMax'] = MathUtility::forceIntegerInRange(
2482  $this->cObj->stdWrap($this->conf['results.']['markupSW_summaryMax'], $this->conf['results.']['markupSW_summaryMax.']),
2483  10, 5000, 300
2484  );
2485  $this->conf['results.']['markupSW_postPreLgd'] = MathUtility::forceIntegerInRange(
2486  $this->cObj->stdWrap($this->conf['results.']['markupSW_postPreLgd'], $this->conf['results.']['markupSW_postPreLgd.']),
2487  1, 500, 60
2488  );
2489  $this->conf['results.']['markupSW_postPreLgd_offset'] = MathUtility::forceIntegerInRange(
2490  $this->cObj->stdWrap($this->conf['results.']['markupSW_postPreLgd_offset'], $this->conf['results.']['markupSW_postPreLgd_offset.']),
2491  1, 50, 5
2492  );
2493  $this->conf['results.']['markupSW_divider'] = $this->cObj->stdWrap($this->conf['results.']['markupSW_divider'], $this->conf['results.']['markupSW_divider.']);
2494  $this->conf['results.']['hrefInSummaryCropAfter'] = MathUtility::forceIntegerInRange(
2495  $this->cObj->stdWrap($this->conf['results.']['hrefInSummaryCropAfter'], $this->conf['results.']['hrefInSummaryCropAfter.']),
2496  10, 400, 60
2497  );
2498  $this->conf['results.']['hrefInSummaryCropSignifier'] = $this->cObj->stdWrap($this->conf['results.']['hrefInSummaryCropSignifier'], $this->conf['results.']['hrefInSummaryCropSignifier.']);
2499  }
2500 }