TYPO3 CMS  TYPO3_6-2
SearchFormController.php
Go to the documentation of this file.
1 <?php
3 
18 
28 
32  public $prefixId = 'tx_indexedsearch';
33 
34  // Same as class name
38  public $scriptRelPath = 'pi/class.tx_indexedsearch.php';
39 
40  // Path to this script relative to the extension dir.
44  public $extKey = 'indexed_search';
45 
46  // The extension key.
50  public $join_pages = 0;
51 
52  // See document for info about this flag...
56  public $defaultResultNumber = 10;
57 
61  public $operator_translate_table = array(array('+', 'AND'), array('|', 'OR'), array('-', 'AND NOT'));
62 
63  // Internal variable
67  public $wholeSiteIdList = 0;
68 
69  // Root-page PIDs to search in (rl0 field where clause, see initialize() function)
70  // Internals:
74  public $sWArr = array();
75 
76  // Search Words and operators
80  public $optValues = array();
81 
82  // Selector box values for search configuration form
86  public $firstRow = array();
87 
88  // Will hold the first row in result - used to calculate relative hit-ratings.
92  public $cache_path = array();
93 
94  // Caching of page path
98  public $cache_rl = array();
99 
100  // Caching of root line data
104  public $fe_groups_required = array();
105 
106  // Required fe_groups memberships for display of a result.
110  public $domain_records = array();
111 
112  // Select clauses for individual words
116  public $wSelClauses = array();
117 
118  // Domain records (?)
122  public $resultSections = array();
123 
124  // Page tree sections for search result.
128  public $external_parsers = array();
129 
130  // External parser objects
134  public $iconFileNameCache = array();
135 
136  // Storage of icons....
141 
142  // Will hold the content of $conf['templateFile']
146  public $hiddenFieldList = 'ext, type, defOp, media, order, group, lang, desc, results';
147 
151  public $indexerConfig = array();
152 
153  // Indexer configuration, coming from $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']
157  public $enableMetaphoneSearch = FALSE;
158 
163 
170  public $lexerObj;
171 
172  const WILDCARD_LEFT = 1;
173  const WILDCARD_RIGHT = 2;
182  public function main($content, $conf) {
183  // Initialize:
184  $this->conf = $conf;
185  $this->pi_loadLL();
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)) {
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 
205  public function initialize() {
206  global $TYPO3_CONF_VARS;
207  // Indexer configuration from Extension Manager interface:
208  $this->indexerConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['indexed_search']);
209  $this->enableMetaphoneSearch = $this->indexerConfig['enableMetaphoneSearch'] ? TRUE : FALSE;
210  $this->storeMetaphoneInfoAsWords = !\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_words');
211  // Initialize external document parsers for icon display and other soft operations
212  if (is_array($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'])) {
213  foreach ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['external_parsers'] as $extension => $_objRef) {
214  $this->external_parsers[$extension] = GeneralUtility::getUserObj($_objRef);
215  // Init parser and if it returns FALSE, unset its entry again:
216  if (!$this->external_parsers[$extension]->softInit($extension)) {
217  unset($this->external_parsers[$extension]);
218  }
219  }
220  }
221  // Init lexer (used to post-processing of search words)
222  $lexerObjRef = $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] ? $TYPO3_CONF_VARS['EXTCONF']['indexed_search']['lexer'] : 'EXT:indexed_search/Classes/Lexer.php:&TYPO3\\CMS\\IndexedSearch\\Lexer';
223  $this->lexerObj = GeneralUtility::getUserObj($lexerObjRef);
224  // If "_sections" is set, this value overrides any existing value.
225  if ($this->piVars['_sections']) {
226  $this->piVars['sections'] = $this->piVars['_sections'];
227  }
228  // If "_sections" is set, this value overrides any existing value.
229  if ($this->piVars['_freeIndexUid'] !== '_') {
230  $this->piVars['freeIndexUid'] = $this->piVars['_freeIndexUid'];
231  }
232  // Add previous search words to current
233  if ($this->piVars['sword_prev_include'] && $this->piVars['sword_prev']) {
234  $this->piVars['sword'] = trim($this->piVars['sword_prev']) . ' ' . $this->piVars['sword'];
235  }
236  $this->piVars['results'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->piVars['results'], 1, 100, $this->defaultResultNumber);
237  // Selector-box values defined here:
238  $this->optValues = array(
239  'type' => array(
240  '0' => $this->pi_getLL('opt_type_0'),
241  '1' => $this->pi_getLL('opt_type_1'),
242  '2' => $this->pi_getLL('opt_type_2'),
243  '3' => $this->pi_getLL('opt_type_3'),
244  '10' => $this->pi_getLL('opt_type_10'),
245  '20' => $this->pi_getLL('opt_type_20')
246  ),
247  'defOp' => array(
248  '0' => $this->pi_getLL('opt_defOp_0'),
249  '1' => $this->pi_getLL('opt_defOp_1')
250  ),
251  'sections' => array(
252  '0' => $this->pi_getLL('opt_sections_0'),
253  '-1' => $this->pi_getLL('opt_sections_-1'),
254  '-2' => $this->pi_getLL('opt_sections_-2'),
255  '-3' => $this->pi_getLL('opt_sections_-3')
256  ),
257  'freeIndexUid' => array(
258  '-1' => $this->pi_getLL('opt_freeIndexUid_-1'),
259  '-2' => $this->pi_getLL('opt_freeIndexUid_-2'),
260  '0' => $this->pi_getLL('opt_freeIndexUid_0')
261  ),
262  'media' => array(
263  '-1' => $this->pi_getLL('opt_media_-1'),
264  '0' => $this->pi_getLL('opt_media_0'),
265  '-2' => $this->pi_getLL('opt_media_-2')
266  ),
267  'order' => array(
268  'rank_flag' => $this->pi_getLL('opt_order_rank_flag'),
269  'rank_freq' => $this->pi_getLL('opt_order_rank_freq'),
270  'rank_first' => $this->pi_getLL('opt_order_rank_first'),
271  'rank_count' => $this->pi_getLL('opt_order_rank_count'),
272  'mtime' => $this->pi_getLL('opt_order_mtime'),
273  'title' => $this->pi_getLL('opt_order_title'),
274  'crdate' => $this->pi_getLL('opt_order_crdate')
275  ),
276  'group' => array(
277  'sections' => $this->pi_getLL('opt_group_sections'),
278  'flat' => $this->pi_getLL('opt_group_flat')
279  ),
280  'lang' => array(
281  -1 => $this->pi_getLL('opt_lang_-1'),
282  0 => $this->pi_getLL('opt_lang_0')
283  ),
284  'desc' => array(
285  '0' => $this->pi_getLL('opt_desc_0'),
286  '1' => $this->pi_getLL('opt_desc_1')
287  ),
288  'results' => array(
289  '10' => '10',
290  '20' => '20',
291  '50' => '50',
292  '100' => '100'
293  )
294  );
295  // Remove this option if metaphone search is disabled)
296  if (!$this->enableMetaphoneSearch) {
297  unset($this->optValues['type']['10']);
298  }
299  // Free Index Uid:
300  if ($this->conf['search.']['defaultFreeIndexUidList']) {
301  $uidList = GeneralUtility::intExplode(',', $this->conf['search.']['defaultFreeIndexUidList']);
302  $indexCfgRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title', 'index_config', 'uid IN (' . implode(',', $uidList) . ')' . $this->cObj->enableFields('index_config'), '', '', '', 'uid');
303  foreach ($uidList as $uidValue) {
304  if (is_array($indexCfgRecords[$uidValue])) {
305  $this->optValues['freeIndexUid'][$uidValue] = $indexCfgRecords[$uidValue]['title'];
306  }
307  }
308  }
309  // Should we use join_pages instead of long lists of uids?
310  if ($this->conf['search.']['skipExtendToSubpagesChecking']) {
311  $this->join_pages = 1;
312  }
313  // Add media to search in:
314  if (strlen(trim($this->conf['search.']['mediaList']))) {
315  $mediaList = implode(',', GeneralUtility::trimExplode(',', $this->conf['search.']['mediaList'], TRUE));
316  }
317  foreach ($this->external_parsers as $extension => $obj) {
318  // Skip unwanted extensions
319  if ($mediaList && !GeneralUtility::inList($mediaList, $extension)) {
320  continue;
321  }
322  if ($name = $obj->searchTypeMediaTitle($extension)) {
323  $this->optValues['media'][$extension] = $this->pi_getLL('opt_sections_' . $extension, $name);
324  }
325  }
326  // Add operators for various languages
327  // Converts the operators to UTF-8 and lowercase
328  $this->operator_translate_table[] = array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $GLOBALS['TSFE']->csConvObj->utf8_encode($this->pi_getLL('local_operator_AND'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'AND');
329  $this->operator_translate_table[] = array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $GLOBALS['TSFE']->csConvObj->utf8_encode($this->pi_getLL('local_operator_OR'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'OR');
330  $this->operator_translate_table[] = array($GLOBALS['TSFE']->csConvObj->conv_case('utf-8', $GLOBALS['TSFE']->csConvObj->utf8_encode($this->pi_getLL('local_operator_NOT'), $GLOBALS['TSFE']->renderCharset), 'toLower'), 'AND NOT');
331  // This is the id of the site root. This value may be a commalist of integer (prepared for this)
332  $this->wholeSiteIdList = (int)$GLOBALS['TSFE']->config['rootLine'][0]['uid'];
333  // Creating levels for section menu:
334  // This selects the first and secondary menus for the "sections" selector - so we can search in sections and sub sections.
335  if ($this->conf['show.']['L1sections']) {
336  $firstLevelMenu = $this->getMenu($this->wholeSiteIdList);
337  foreach ($firstLevelMenu as $optionName => $mR) {
338  if (!$mR['nav_hide']) {
339  $this->optValues['sections']['rl1_' . $mR['uid']] = trim($this->pi_getLL('opt_RL1') . ' ' . $mR['title']);
340  if ($this->conf['show.']['L2sections']) {
341  $secondLevelMenu = $this->getMenu($mR['uid']);
342  foreach ($secondLevelMenu as $kk2 => $mR2) {
343  if (!$mR2['nav_hide']) {
344  $this->optValues['sections']['rl2_' . $mR2['uid']] = trim($this->pi_getLL('opt_RL2') . ' ' . $mR2['title']);
345  } else {
346  unset($secondLevelMenu[$kk2]);
347  }
348  }
349  $this->optValues['sections']['rl2_' . implode(',', array_keys($secondLevelMenu))] = $this->pi_getLL('opt_RL2ALL');
350  }
351  } else {
352  unset($firstLevelMenu[$optionName]);
353  }
354  }
355  $this->optValues['sections']['rl1_' . implode(',', array_keys($firstLevelMenu))] = $this->pi_getLL('opt_RL1ALL');
356  }
357  // Setting the list of root PIDs 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.
358  // 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...
359  if ($this->conf['search.']['rootPidList']) {
360  $this->wholeSiteIdList = implode(',', GeneralUtility::intExplode(',', $this->conf['search.']['rootPidList']));
361  }
362  // Load the template
363  $this->templateCode = $this->cObj->fileResource($this->conf['templateFile']);
364  // Add search languages:
365  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_language', '1=1' . $this->cObj->enableFields('sys_language'));
366  while (FALSE !== ($data = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
367  $this->optValues['lang'][$data['uid']] = $data['title'];
368  }
369  $GLOBALS['TYPO3_DB']->sql_free_result($res);
370  // Calling hook for modification of initialized content
371  if ($hookObj = $this->hookRequest('initialize_postProc')) {
372  $hookObj->initialize_postProc();
373  }
374  // Default values set:
375  // Setting first values in optValues as default values IF there is not corresponding piVar value set already.
376  foreach ($this->optValues as $optionName => $optionValue) {
377  if (!isset($this->piVars[$optionName])) {
378  reset($optionValue);
379  $this->piVars[$optionName] = key($optionValue);
380  }
381  }
382  // Blind selectors:
383  if (is_array($this->conf['blind.'])) {
384  foreach ($this->conf['blind.'] as $optionName => $optionValue) {
385  if (is_array($optionValue)) {
386  foreach ($optionValue as $optionValueSubKey => $optionValueSubValue) {
387  if (!is_array($optionValueSubValue) && $optionValueSubValue && is_array($this->optValues[substr($optionName, 0, -1)])) {
388  unset($this->optValues[substr($optionName, 0, -1)][$optionValueSubKey]);
389  }
390  }
391  } elseif ($optionValue) {
392  // If value is not set, unset the option array
393  unset($this->optValues[$optionName]);
394  }
395  }
396  }
397  // This gets the search-words into the $sWArr:
398  $this->sWArr = $this->getSearchWords($this->piVars['defOp']);
399  }
400 
417  public function getSearchWords($defOp) {
418  // 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!)
419  $inSW = substr($this->piVars['sword'], 0, 200);
420  // Convert to UTF-8 + conv. entities (was also converted during indexing!)
421  $inSW = $GLOBALS['TSFE']->csConvObj->utf8_encode($inSW, $GLOBALS['TSFE']->metaCharset);
422  $inSW = $GLOBALS['TSFE']->csConvObj->entities_to_utf8($inSW, TRUE);
423  $sWordArray = FALSE;
424  if ($hookObj = $this->hookRequest('getSearchWords')) {
425  $sWordArray = $hookObj->getSearchWords_splitSWords($inSW, $defOp);
426  } else {
427  if ($this->piVars['type'] == 20) {
428  // type = Sentence
429  $sWordArray = array(array('sword' => trim($inSW), 'oper' => 'AND'));
430  } else {
431  $search = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\SearchResultContentObject');
432  $search->default_operator = $defOp == 1 ? 'OR' : 'AND';
433  $search->operator_translate_table = $this->operator_translate_table;
434  $search->register_and_explode_search_string($inSW);
435  if (is_array($search->sword_array)) {
436  $sWordArray = $this->procSearchWordsByLexer($search->sword_array);
437  }
438  }
439  }
440  return $sWordArray;
441  }
442 
451  public function procSearchWordsByLexer($SWArr) {
452  // Init output variable:
453  $newSWArr = array();
454  // Traverse the search word array:
455  foreach ($SWArr as $wordDef) {
456  if (!strstr($wordDef['sword'], ' ')) {
457  // No space in word (otherwise it might be a sentense in quotes like "there is").
458  // Split the search word by lexer:
459  $res = $this->lexerObj->split2Words($wordDef['sword']);
460  // Traverse lexer result and add all words again:
461  foreach ($res as $word) {
462  $newSWArr[] = array('sword' => $word, 'oper' => $wordDef['oper']);
463  }
464  } else {
465  $newSWArr[] = $wordDef;
466  }
467  }
468  // Return result:
469  return $newSWArr;
470  }
471 
472  /*****************************
473  *
474  * Main functions
475  *
476  *****************************/
484  public function doSearch($sWArr) {
485  // Find free index uid:
486  $freeIndexUid = $this->piVars['freeIndexUid'];
487  if ($freeIndexUid == -2) {
488  $freeIndexUid = $this->conf['search.']['defaultFreeIndexUidList'];
489  }
490  $indexCfgs = GeneralUtility::intExplode(',', $freeIndexUid);
491  $accumulatedContent = '';
492  foreach ($indexCfgs as $freeIndexUid) {
493  // Get result rows:
495  if ($hookObj = $this->hookRequest('getResultRows')) {
496  $resData = $hookObj->getResultRows($sWArr, $freeIndexUid);
497  } else {
498  $resData = $this->getResultRows($sWArr, $freeIndexUid);
499  }
500  // Display search results:
502  if ($hookObj = $this->hookRequest('getDisplayResults')) {
503  $content = $hookObj->getDisplayResults($sWArr, $resData, $freeIndexUid);
504  } else {
505  $content = $this->getDisplayResults($sWArr, $resData, $freeIndexUid);
506  }
508  // Create header if we are searching more than one indexing configuration:
509  if (count($indexCfgs) > 1) {
510  if ($freeIndexUid > 0) {
511  $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('title', 'index_config', 'uid=' . (int)$freeIndexUid . $this->cObj->enableFields('index_config'));
512  $titleString = $indexCfgRec['title'];
513  } else {
514  $titleString = $this->pi_getLL('opt_freeIndexUid_header_' . $freeIndexUid);
515  }
516  $content = '<h1 class="tx-indexedsearch-category">' . htmlspecialchars($titleString) . '</h1>' . $content;
517  }
518  $accumulatedContent .= $content;
519  }
520  // Write search statistics
521  $this->writeSearchStat($sWArr, $resData['count'], array($pt1, $pt2, $pt3));
522  // Return content:
523  return $accumulatedContent;
524  }
525 
534  public function getResultRows($searchWordArray, $freeIndexUid = -1) {
535  // Getting SQL result pointer. This fetches ALL results (1,000,000 if found)
536  $GLOBALS['TT']->push('Searching result');
537  if ($hookObj = &$this->hookRequest('getResultRows_SQLpointer')) {
538  $res = $hookObj->getResultRows_SQLpointer($searchWordArray, $freeIndexUid);
539  } else {
540  $res = $this->getResultRows_SQLpointer($searchWordArray, $freeIndexUid);
541  }
542  $GLOBALS['TT']->pull();
543  // Organize and process result:
544  $result = FALSE;
545  if ($res) {
546  $totalSearchResultCount = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
547  // Total search-result count
548  $currentPageNumber = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->piVars['pointer'], 0, floor($totalSearchResultCount / $this->piVars['results']));
549  // The pointer is set to the result page that is currently being viewed
550  // Initialize result accumulation variables:
551  $positionInSearchResults = 0;
552  $groupingPhashes = array();
553  // Used for filtering out duplicates
554  $groupingChashes = array();
555  // Used for filtering out duplicates BASED ON cHash
556  $firstRow = array();
557  // Will hold the first row in result - used to calculate relative hit-ratings.
558  $resultRows = array();
559  // Will hold the results rows for display.
560  // Should we continue counting and checking of results even if
561  // we are sure they are not displayed in this request?
562  // This will slow down your page rendering, but it allows
563  // precise search result counters.
564  $calculateExactCount = (bool) $this->conf['search.']['exactCount'];
565  $lastResultNumberOnPreviousPage = $currentPageNumber * $this->piVars['results'];
566  $firstResultNumberOnNextPage = ($currentPageNumber + 1) * $this->piVars['results'];
567  $lastResultNumberToAnalyze = ($currentPageNumber + 1) * $this->piVars['results'] + $this->piVars['results'];
568  // Now, traverse result and put the rows to be displayed into an array
569  // Each row should contain the fields from 'ISEC.*, IP.*' combined + artificial fields "show_resume" (boolean) and "result_number" (counter)
570  while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
571  if (!$this->checkExistance($row)) {
572  // Check if the record is still available or if it has been deleted meanwhile.
573  // Currently this works for files only, since extending it to content elements would cause a lot of overhead...
574  // Otherwise, skip the row.
575  $totalSearchResultCount--;
576  continue;
577  }
578  // Set first row:
579  if ($positionInSearchResults === 0) {
580  $firstRow = $row;
581  }
582  $row['show_resume'] = $this->checkResume($row);
583  // Tells whether we can link directly to a document or not (depends on possible right problems)
584  $phashGr = !in_array($row['phash_grouping'], $groupingPhashes);
585  $chashGr = !in_array(($row['contentHash'] . '.' . $row['data_page_id']), $groupingChashes);
586  if ($phashGr && $chashGr) {
587  if ($row['show_resume'] || $this->conf['show.']['forbiddenRecords']) {
588  // Only if the resume may be shown are we going to filter out duplicates...
589  if (!$this->multiplePagesType($row['item_type'])) {
590  // Only on documents which are not multiple pages documents
591  $groupingPhashes[] = $row['phash_grouping'];
592  }
593  $groupingChashes[] = $row['contentHash'] . '.' . $row['data_page_id'];
594  $positionInSearchResults++;
595  // Check if we are inside result range for current page
596  if ($positionInSearchResults > $lastResultNumberOnPreviousPage && $positionInSearchResults <= $lastResultNumberToAnalyze) {
597  // Collect results to display
598  $row['result_number'] = $positionInSearchResults;
599  $resultRows[] = $row;
600  // This may lead to a problem: If the result
601  // check is not stopped here, the search will
602  // take longer. However the result counter
603  // will not filter out grouped cHashes/pHashes
604  // that were not processed yet. You can change
605  // this behavior using the "search.exactCount"
606  // property (see above).
607  $nextResultPosition = $positionInSearchResults + 1;
608  if (!$calculateExactCount && $nextResultPosition > $firstResultNumberOnNextPage) {
609  break;
610  }
611  }
612  } else {
613  // Skip this row if the user cannot view it (missing permission)
614  $totalSearchResultCount--;
615  }
616  } else {
617  // For each time a phash_grouping document is found
618  // (which is thus not displayed) the search-result count
619  // is reduced, so that it matches the number of rows displayed.
620  $totalSearchResultCount--;
621  }
622  }
623  $GLOBALS['TYPO3_DB']->sql_free_result($res);
624  $result = array(
625  'resultRows' => $resultRows,
626  'firstRow' => $firstRow,
627  'count' => $totalSearchResultCount
628  );
629  }
630  return $result;
631  }
632 
641  public function getResultRows_SQLpointer($sWArr, $freeIndexUid = -1) {
642  // This SEARCHES for the searchwords in $sWArr AND returns a COMPLETE list of phash-integers of the matches.
643  $list = $this->getPhashList($sWArr);
644  // Perform SQL Search / collection of result rows array:
645  if ($list) {
646  // Do the search:
647  $GLOBALS['TT']->push('execFinalQuery');
648  $res = $this->execFinalQuery($list, $freeIndexUid);
649  $GLOBALS['TT']->pull();
650  return $res;
651  } else {
652  return FALSE;
653  }
654  }
655 
665  public function getDisplayResults($sWArr, $resData, $freeIndexUid = -1) {
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'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->conf['search.']['page_links'], 1, 100, 10);
679  $addString = $resData['count'] && $this->piVars['group'] == 'sections' && $freeIndexUid <= 0 ? ' ' . sprintf($this->pi_getLL((count($this->resultSections) > 1 ? 'inNsections' : 'inNsection')), count($this->resultSections)) : '';
680  $browseBox1 = $this->pi_list_browseresults(1, $addString, $this->printResultSectionLinks(), $freeIndexUid);
681  $browseBox2 = $this->pi_list_browseresults(0, '', '', $freeIndexUid);
682  }
683  // Browsing nav, bottom.
684  if ($resData['count']) {
685  $content = $browseBox1 . $rowcontent . $browseBox2;
686  } else {
687  $content = '<p' . $this->pi_classParam('noresults') . '>' . $this->pi_getLL('noResults', '', TRUE) . '</p>';
688  }
689  $GLOBALS['TT']->pull();
690  } else {
691  $content .= '<p' . $this->pi_classParam('noresults') . '>' . $this->pi_getLL('noResults', '', TRUE) . '</p>';
692  }
693  // Print a message telling which words we searched for, and in which sections etc.
694  $what = $this->tellUsWhatIsSeachedFor($sWArr) . (substr($this->piVars['sections'], 0, 2) == 'rl' ? ' ' . $this->pi_getLL('inSection', '', TRUE) . ' "' . preg_replace('#^/#', '', htmlspecialchars($this->getPathFromPageId(substr($this->piVars['sections'], 4)))) . '"' : '');
695  $what = '<div' . $this->pi_classParam('whatis') . '>' . $this->cObj->stdWrap($what, $this->conf['whatis_stdWrap.']) . '</div>';
696  $content = $what . $content;
697  // Return content:
698  return $content;
699  }
700 
710  public function compileResult($resultRows, $freeIndexUid = -1) {
711  $content = '';
712  // Transfer result rows to new variable, performing some mapping of sub-results etc.
713  $newResultRows = array();
714  foreach ($resultRows as $row) {
715  $id = md5($row['phash_grouping']);
716  if (is_array($newResultRows[$id])) {
717  if (!$newResultRows[$id]['show_resume'] && $row['show_resume']) {
718  // swapping:
719  // Remove old
720  $subrows = $newResultRows[$id]['_sub'];
721  unset($newResultRows[$id]['_sub']);
722  $subrows[] = $newResultRows[$id];
723  // Insert new:
724  $newResultRows[$id] = $row;
725  $newResultRows[$id]['_sub'] = $subrows;
726  } else {
727  $newResultRows[$id]['_sub'][] = $row;
728  }
729  } else {
730  $newResultRows[$id] = $row;
731  }
732  }
733  $resultRows = $newResultRows;
734  $this->resultSections = array();
735  if ($freeIndexUid <= 0) {
736  switch ($this->piVars['group']) {
737  case 'sections':
738  $rl2flag = substr($this->piVars['sections'], 0, 2) == 'rl';
739  $sections = array();
740  foreach ($resultRows as $row) {
741  $id = $row['rl0'] . '-' . $row['rl1'] . ($rl2flag ? '-' . $row['rl2'] : '');
742  $sections[$id][] = $row;
743  }
744  $this->resultSections = array();
745  foreach ($sections as $id => $resultRows) {
746  $rlParts = explode('-', $id);
747  $theId = $rlParts[2] ? $rlParts[2] : ($rlParts[1] ? $rlParts[1] : $rlParts[0]);
748  $theRLid = $rlParts[2] ? 'rl2_' . $rlParts[2] : ($rlParts[1] ? 'rl1_' . $rlParts[1] : '0');
749  $sectionName = $this->getPathFromPageId($theId);
750  if ($sectionName[0] == '/') {
751  $sectionName = substr($sectionName, 1);
752  }
753  if (!trim($sectionName)) {
754  $sectionTitleLinked = $this->pi_getLL('unnamedSection', '', TRUE) . ':';
755  } else {
756  $quotedPrefix = GeneralUtility::quoteJSvalue($this->prefixId);
757  $onclick = 'document.forms[' . $quotedPrefix . '][' . GeneralUtility::quoteJSvalue($this->prefixId . '[_sections]') . '].value=' . GeneralUtility::quoteJSvalue($theRLid) . ';document.forms[' . $quotedPrefix . '].submit();return false;';
758  $sectionTitleLinked = '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . htmlspecialchars($sectionName) . ':</a>';
759  }
760  $this->resultSections[$id] = array($sectionName, count($resultRows));
761  // Add content header:
762  $content .= $this->makeSectionHeader($id, $sectionTitleLinked, count($resultRows));
763  // Render result rows:
764  $resultlist = '';
765  foreach ($resultRows as $row) {
766  $resultlist .= $this->printResultRow($row);
767  }
768  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
769  }
770  break;
771  default:
772  // flat:
773  $resultlist = '';
774  foreach ($resultRows as $row) {
775  $resultlist .= $this->printResultRow($row);
776  }
777  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
778  }
779  } else {
780  $resultlist = '';
781  foreach ($resultRows as $row) {
782  $resultlist .= $this->printResultRow($row);
783  }
784  $content .= $this->cObj->stdWrap($resultlist, $this->conf['resultlist_stdWrap.']);
785  }
786  return '<div' . $this->pi_classParam('res') . '>' . $content . '</div>';
787  }
788 
789  /***********************************
790  *
791  * Searching functions (SQL)
792  *
793  ***********************************/
802  public function getPhashList($sWArr) {
803  // Initialize variables:
804  $c = 0;
805  $totalHashList = array();
806  // This array accumulates the phash-values
807  // Traverse searchwords; for each, select all phash integers and merge/diff/intersect them with previous word (based on operator)
808  foreach ($sWArr as $k => $v) {
809  // Making the query for a single search word based on the search-type
810  $sWord = $v['sword'];
811  $theType = (string) $this->piVars['type'];
812  if (strstr($sWord, ' ')) {
813  // If there are spaces in the search-word, make a full text search instead.
814  $theType = 20;
815  }
816  $GLOBALS['TT']->push('SearchWord "' . $sWord . '" - $theType=' . $theType);
817  // Perform search for word:
818  switch ($theType) {
819  case '1':
820  // Part of word
821  $res = $this->searchWord($sWord, self::WILDCARD_LEFT | self::WILDCARD_RIGHT);
822  break;
823  case '2':
824  // First part of word
825  $res = $this->searchWord($sWord, self::WILDCARD_RIGHT);
826  break;
827  case '3':
828  // Last part of word
829  $res = $this->searchWord($sWord, self::WILDCARD_LEFT);
830  break;
831  case '10':
832  // Sounds like
838  // Initialize the indexer-class
839  $indexerObj = GeneralUtility::makeInstance('TYPO3\\CMS\\IndexedSearch\\Indexer');
840  // Perform metaphone search
841  $res = $this->searchMetaphone($indexerObj->metaphone($sWord, $this->storeMetaphoneInfoAsWords));
842  unset($indexerObj);
843  break;
844  case '20':
845  // Sentence
846  $res = $this->searchSentence($sWord);
847  $this->piVars['order'] = 'mtime';
848  // 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.
849  break;
850  default:
851  // Distinct word
852  $res = $this->searchDistinct($sWord);
853  }
854  // If there was a query to do, then select all phash-integers which resulted from this.
855  if ($res) {
856  // Get phash list by searching for it:
857  $phashList = array();
858  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
859  $phashList[] = $row['phash'];
860  }
861  $GLOBALS['TYPO3_DB']->sql_free_result($res);
862  // Here the phash list are merged with the existing result based on whether we are dealing with OR, NOT or AND operations.
863  if ($c) {
864  switch ($v['oper']) {
865  case 'OR':
866  $totalHashList = array_unique(array_merge($phashList, $totalHashList));
867  break;
868  case 'AND NOT':
869  $totalHashList = array_diff($totalHashList, $phashList);
870  break;
871  default:
872  // AND...
873  $totalHashList = array_intersect($totalHashList, $phashList);
874  }
875  } else {
876  $totalHashList = $phashList;
877  }
878  }
879  $GLOBALS['TT']->pull();
880  $c++;
881  }
882  return implode(',', $totalHashList);
883  }
884 
893  public function execPHashListQuery($wordSel, $plusQ = '') {
894  return $GLOBALS['TYPO3_DB']->exec_SELECTquery('IR.phash', 'index_words IW,
895  index_rel IR,
896  index_section ISEC', $wordSel . '
897  AND IW.wid=IR.wid
898  AND ISEC.phash = IR.phash
899  ' . $this->sectionTableWhere() . '
900  ' . $plusQ, 'IR.phash');
901  }
902 
911  public function searchWord($sWord, $mode) {
912  $wildcard_left = $mode & self::WILDCARD_LEFT ? '%' : '';
913  $wildcard_right = $mode & self::WILDCARD_RIGHT ? '%' : '';
914  $wSel = 'IW.baseword LIKE \'' . $wildcard_left . $GLOBALS['TYPO3_DB']->quoteStr($sWord, 'index_words') . $wildcard_right . '\'';
915  $this->wSelClauses[] = $wSel;
916  $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
917  return $res;
918  }
919 
927  public function searchDistinct($sWord) {
929  $this->wSelClauses[] = $wSel;
930  $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0');
931  return $res;
932  }
933 
941  public function searchSentence($sSentence) {
942  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.phash', 'index_section ISEC, index_fulltext IFT', 'IFT.fulltextdata LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($sSentence, 'index_fulltext') . '%\' AND
943  ISEC.phash = IFT.phash
944  ' . $this->sectionTableWhere(), 'ISEC.phash');
945  $this->wSelClauses[] = '1=1';
946  return $res;
947  }
948 
956  public function searchMetaphone($sWord) {
957  $wSel = 'IW.metaphone=' . $sWord;
958  $this->wSelClauses[] = $wSel;
959  return $this->execPHashListQuery($wSel, ' AND is_stopword=0');
960  }
961 
968  public function sectionTableWhere() {
969  $out = $this->wholeSiteIdList < 0 ? '' : ' AND ISEC.rl0 IN (' . $this->wholeSiteIdList . ')';
970  $match = '';
971  if (substr($this->piVars['sections'], 0, 4) == 'rl1_') {
972  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], 4)));
973  $out .= ' AND ISEC.rl1 IN (' . $list . ')';
974  $match = TRUE;
975  } elseif (substr($this->piVars['sections'], 0, 4) == 'rl2_') {
976  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], 4)));
977  $out .= ' AND ISEC.rl2 IN (' . $list . ')';
978  $match = TRUE;
979  } elseif (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'])) {
980  // Traversing user configured fields to see if any of those are used to limit search to a section:
981  foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['indexed_search']['addRootLineFields'] as $fieldName => $rootLineLevel) {
982  if (substr($this->piVars['sections'], 0, strlen($fieldName) + 1) == $fieldName . '_') {
983  $list = implode(',', GeneralUtility::intExplode(',', substr($this->piVars['sections'], strlen($fieldName) + 1)));
984  $out .= ' AND ISEC.' . $fieldName . ' IN (' . $list . ')';
985  $match = TRUE;
986  break;
987  }
988  }
989  }
990  // If no match above, test the static types:
991  if (!$match) {
992  switch ((string) $this->piVars['sections']) {
993  case '-1':
994  // '-1' => 'Only this page',
995  $out .= ' AND ISEC.page_id=' . $GLOBALS['TSFE']->id;
996  break;
997  case '-2':
998  // '-2' => 'Top + level 1',
999  $out .= ' AND ISEC.rl2=0';
1000  break;
1001  case '-3':
1002  // '-3' => 'Level 2 and out',
1003  $out .= ' AND ISEC.rl2>0';
1004  break;
1005  }
1006  }
1007  return $out;
1008  }
1009 
1016  public function mediaTypeWhere() {
1017  switch ((string) $this->piVars['media']) {
1018  case '0':
1019  // '0' => 'Kun TYPO3 sider',
1020  $out = ' AND IP.item_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');
1021  break;
1022  case '-2':
1023  // All external documents
1024  $out = ' AND IP.item_type<>' . $GLOBALS['TYPO3_DB']->fullQuoteStr('0', 'index_phash');
1025  break;
1026  case '-1':
1027  // All content
1028  $out = '';
1029  break;
1030  default:
1031  $out = ' AND IP.item_type=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->piVars['media'], 'index_phash');
1032  }
1033  return $out;
1034  }
1035 
1042  public function languageWhere() {
1043  if ($this->piVars['lang'] >= 0) {
1044  // -1 is the same as ALL language.
1045  return 'AND IP.sys_language_uid=' . (int)$this->piVars['lang'];
1046  }
1047  }
1048 
1056  public function freeIndexUidWhere($freeIndexUid) {
1057  if ($freeIndexUid >= 0) {
1058  // First, look if the freeIndexUid is a meta configuration:
1059  $indexCfgRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('indexcfgs', 'index_config', 'type=5 AND uid=' . (int)$freeIndexUid . $this->cObj->enableFields('index_config'));
1060  if (is_array($indexCfgRec)) {
1061  $refs = GeneralUtility::trimExplode(',', $indexCfgRec['indexcfgs']);
1062  $list = array(-99);
1063  // Default value to protect against empty array.
1064  foreach ($refs as $ref) {
1065  list($table, $uid) = GeneralUtility::revExplode('_', $ref, 2);
1066  switch ($table) {
1067  case 'index_config':
1068  $idxRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('uid', 'index_config', 'uid=' . (int)$uid . $this->cObj->enableFields('index_config'));
1069  if ($idxRec) {
1070  $list[] = $uid;
1071  }
1072  break;
1073  case 'pages':
1074  $indexCfgRecordsFromPid = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', 'index_config', 'pid=' . (int)$uid . $this->cObj->enableFields('index_config'));
1075  foreach ($indexCfgRecordsFromPid as $idxRec) {
1076  $list[] = $idxRec['uid'];
1077  }
1078  break;
1079  }
1080  }
1081  $list = array_unique($list);
1082  } else {
1083  $list = array((int)$freeIndexUid);
1084  }
1085  return ' AND IP.freeIndexUid IN (' . implode(',', $list) . ')';
1086  }
1087  }
1088 
1097  public function execFinalQuery($list, $freeIndexUid = -1) {
1098  // Setting up methods of filtering results based on page types, access, etc.
1099  $page_join = '';
1100  $page_where = '';
1101  // Calling hook for alternative creation of page ID list
1102  if ($hookObj = $this->hookRequest('execFinalQuery_idList')) {
1103  $page_where = $hookObj->execFinalQuery_idList($list);
1104  } elseif ($this->join_pages) {
1105  // Alternative to getting all page ids by ->getTreeList() where "excludeSubpages" is NOT respected.
1106  $page_join = ',
1107  pages';
1108  $page_where = 'pages.uid = ISEC.page_id
1109  ' . $this->cObj->enableFields('pages') . '
1110  AND pages.no_search=0
1111  AND pages.doktype<200
1112  ';
1113  } elseif ($this->wholeSiteIdList >= 0) {
1114  // 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!
1115  $siteIdNumbers = GeneralUtility::intExplode(',', $this->wholeSiteIdList);
1116  $id_list = array();
1117  foreach ($siteIdNumbers as $rootId) {
1118  $id_list[] = $this->cObj->getTreeList(-1 * $rootId, 9999);
1119  }
1120  $page_where = 'ISEC.page_id IN (' . implode(',', $id_list) . ')';
1121  } else {
1122  // Disable everything... (select all)
1123  $page_where = '1=1';
1124  }
1125  // Indexing configuration clause:
1126  $freeIndexUidClause = $this->freeIndexUidWhere($freeIndexUid);
1127  // 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.
1128  if (substr($this->piVars['order'], 0, 5) == 'rank_') {
1129  switch ($this->piVars['order']) {
1130  case 'rank_flag':
1131  // This gives priority to word-position (max-value) so that words in title, keywords, description counts more than in content.
1132  // The ordering is refined with the frequency sum as well.
1133  $grsel = 'MAX(IR.flags) AS order_val1, SUM(IR.freq) AS order_val2';
1134  $orderBy = 'order_val1' . $this->isDescending() . ',order_val2' . $this->isDescending();
1135  break;
1136  case 'rank_first':
1137  // Results in average position of search words on page. Must be inversely sorted (low numbers are closer to top)
1138  $grsel = 'AVG(IR.first) AS order_val';
1139  $orderBy = 'order_val' . $this->isDescending(1);
1140  break;
1141  case 'rank_count':
1142  // Number of words found
1143  $grsel = 'SUM(IR.count) AS order_val';
1144  $orderBy = 'order_val' . $this->isDescending();
1145  break;
1146  default:
1147  // Frequency sum. I'm not sure if this is the best way to do it (make a sum...). Or should it be the average?
1148  $grsel = 'SUM(IR.freq) AS order_val';
1149  $orderBy = 'order_val' . $this->isDescending();
1150  }
1151 
1152  // So, words are imploded into an OR statement (no "sentence search" should be done here - may deselect results)
1153  $wordSel = '(' . implode(' OR ', $this->wSelClauses) . ') AND ';
1154 
1155  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
1156  'ISEC.*, IP.*, ' . $grsel,
1157  'index_words IW,
1158  index_rel IR,
1159  index_section ISEC,
1160  index_phash IP' . $page_join,
1161  $wordSel .
1162  'IP.phash IN (' . $list . ') ' .
1163  $this->mediaTypeWhere() . ' ' . $this->languageWhere() . $freeIndexUidClause . '
1164  AND IW.wid=IR.wid
1165  AND ISEC.phash = IR.phash
1166  AND IP.phash = IR.phash
1167  AND ' . $page_where,
1168  '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',
1169  $orderBy
1170  );
1171  } else {
1172  // Otherwise, if sorting are done with the pages table or other fields, there is no need for joining with the rel/word tables:
1173  $orderBy = '';
1174  switch ((string) $this->piVars['order']) {
1175  case 'title':
1176  $orderBy = 'IP.item_title' . $this->isDescending();
1177  break;
1178  case 'crdate':
1179  $orderBy = 'IP.item_crdate' . $this->isDescending();
1180  break;
1181  case 'mtime':
1182  $orderBy = 'IP.item_mtime' . $this->isDescending();
1183  break;
1184  }
1185  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('ISEC.*, IP.*', 'index_phash IP,index_section ISEC' . $page_join, 'IP.phash IN (' . $list . ') ' . $this->mediaTypeWhere() . ' ' . $this->languageWhere() . $freeIndexUidClause . '
1186  AND IP.phash = ISEC.phash
1187  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);
1188  }
1189  return $res;
1190  }
1191 
1200  public function checkResume($row) {
1201  // If the record is indexed by an indexing configuration, just show it.
1202  // At least this is needed for external URLs and files.
1203  // For records we might need to extend this - for instance block display if record is access restricted.
1204  if ($row['freeIndexUid']) {
1205  return TRUE;
1206  }
1207  // Evaluate regularly indexed pages based on item_type:
1208  if ($row['item_type']) {
1209  // External media:
1210  // For external media we will check the access of the parent page on which the media was linked from.
1211  // "phash_t3" is the phash of the parent TYPO3 page row which initiated the indexing of the documents in this section.
1212  // So, selecting for the grlist records belonging to the parent phash-row where the current users gr_list exists will help us to know.
1213  // 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.
1214  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1215  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash_t3'] . ' AND gr_list=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($GLOBALS['TSFE']->gr_list, 'index_grlist'));
1216  } else {
1217  $res = FALSE;
1218  }
1219  if ($res && $GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1220  return TRUE;
1221  } else {
1222  return FALSE;
1223  }
1224  } else {
1225  // Ordinary TYPO3 pages:
1226  if ((string)$row['gr_list'] !== (string)$GLOBALS['TSFE']->gr_list) {
1227  // 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...
1228  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_grlist')) {
1229  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('phash', 'index_grlist', 'phash=' . (int)$row['phash'] . ' AND gr_list=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($GLOBALS['TSFE']->gr_list, 'index_grlist'));
1230  } else {
1231  $res = FALSE;
1232  }
1233  if ($res && $GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1234  return TRUE;
1235  } else {
1236  return FALSE;
1237  }
1238  } else {
1239  return TRUE;
1240  }
1241  }
1242  }
1243 
1252  public function checkExistance($row) {
1253  $recordExists = TRUE;
1254  // Always expect that page content exists
1255  if ($row['item_type']) {
1256  // External media:
1257  if (!is_file($row['data_filename']) || !file_exists($row['data_filename'])) {
1258  $recordExists = FALSE;
1259  }
1260  }
1261  return $recordExists;
1262  }
1263 
1271  public function isDescending($inverse = FALSE) {
1272  $desc = $this->piVars['desc'];
1273  if ($inverse) {
1274  $desc = !$desc;
1275  }
1276  return !$desc ? ' DESC' : '';
1277  }
1278 
1288  public function writeSearchStat($sWArr, $count, $pt) {
1289  $insertFields = array(
1290  'searchstring' => $this->piVars['sword'],
1291  'searchoptions' => serialize(array($this->piVars, $sWArr, $pt)),
1292  'feuser_id' => (int)$this->fe_user->user['uid'],
1293  // fe_user id, integer
1294  'cookie' => (string)$this->fe_user->id,
1295  // cookie as set or retrieve. If people has cookies disabled this will vary all the time...
1296  'IP' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1297  // Remote IP address
1298  'hits' => (int)$count,
1299  // Number of hits on the search.
1300  'tstamp' => $GLOBALS['EXEC_TIME']
1301  );
1302  $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_search', $insertFields);
1303  $newId = $GLOBALS['TYPO3_DB']->sql_insert_id();
1304  if ($newId) {
1305  foreach ($sWArr as $val) {
1306  $insertFields = array(
1307  'word' => $val['sword'],
1308  'index_stat_search_id' => $newId,
1309  'tstamp' => $GLOBALS['EXEC_TIME'],
1310  // Time stamp
1311  'pageid' => $GLOBALS['TSFE']->id
1312  );
1313  $GLOBALS['TYPO3_DB']->exec_INSERTquery('index_stat_word', $insertFields);
1314  }
1315  }
1316  }
1317 
1318  /***********************************
1319  *
1320  * HTML output functions
1321  *
1322  ***********************************/
1330  public function makeSearchForm($optValues) {
1331  $html = $this->cObj->getSubpart($this->templateCode, '###SEARCH_FORM###');
1332  // Multilangual text
1333  $substituteArray = array('legend', 'searchFor', 'extResume', 'atATime', 'orderBy', 'fromSection', 'searchIn', 'match', 'style', 'freeIndexUid');
1334  foreach ($substituteArray as $marker) {
1335  $markerArray['###FORM_' . GeneralUtility::strtoupper($marker) . '###'] = $this->pi_getLL('form_' . $marker, '', TRUE);
1336  }
1337  $markerArray['###FORM_SUBMIT###'] = $this->pi_getLL('submit_button_label', '', TRUE);
1338  // Adding search field value
1339  $markerArray['###SWORD_VALUE###'] = htmlspecialchars($this->piVars['sword']);
1340  // Additonal keyword => "Add to current search words"
1341  if ($this->conf['show.']['clearSearchBox'] && $this->conf['show.']['clearSearchBox.']['enableSubSearchCheckBox']) {
1342  $markerArray['###SWORD_PREV_VALUE###'] = htmlspecialchars($this->conf['show.']['clearSearchBox'] ? '' : $this->piVars['sword']);
1343  $markerArray['###SWORD_PREV_INCLUDE_CHECKED###'] = $this->piVars['sword_prev_include'] ? ' checked="checked"' : '';
1344  $markerArray['###ADD_TO_CURRENT_SEARCH###'] = $this->pi_getLL('makerating_addToCurrentSearch', '', TRUE);
1345  } else {
1346  $html = $this->cObj->substituteSubpart($html, '###ADDITONAL_KEYWORD###', '');
1347  }
1348  $markerArray['###ACTION_URL###'] = htmlspecialchars($this->getSearchFormActionURL());
1349  $hiddenFieldCode = $this->cObj->getSubpart($this->templateCode, '###HIDDEN_FIELDS###');
1350  $hiddenFieldCode = preg_replace('/^\\n\\t(.+)/ms', '$1', $hiddenFieldCode);
1351  // Remove first newline and tab (cosmetical issue)
1352  $hiddenFieldArr = array();
1353  foreach (GeneralUtility::trimExplode(',', $this->hiddenFieldList) as $fieldName) {
1354  $hiddenFieldMarkerArray = array();
1355  $hiddenFieldMarkerArray['###HIDDEN_FIELDNAME###'] = $this->prefixId . '[' . $fieldName . ']';
1356  $hiddenFieldMarkerArray['###HIDDEN_VALUE###'] = htmlspecialchars((string) $this->piVars[$fieldName]);
1357  $hiddenFieldArr[$fieldName] = $this->cObj->substituteMarkerArrayCached($hiddenFieldCode, $hiddenFieldMarkerArray, array(), array());
1358  }
1359  // Extended search
1360  if ($this->piVars['ext']) {
1361  // Search for
1362  if (!is_array($optValues['type']) && !is_array($optValues['defOp']) || $this->conf['blind.']['type'] && $this->conf['blind.']['defOp']) {
1363  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_FOR###', '');
1364  } else {
1365  if (is_array($optValues['type']) && !$this->conf['blind.']['type']) {
1366  unset($hiddenFieldArr['type']);
1367  $markerArray['###SELECTBOX_TYPE_VALUES###'] = $this->renderSelectBoxValues($this->piVars['type'], $optValues['type']);
1368  } else {
1369  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_TYPE###', '');
1370  }
1371  if (is_array($optValues['defOp']) || !$this->conf['blind.']['defOp']) {
1372  $markerArray['###SELECTBOX_DEFOP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['defOp'], $optValues['defOp']);
1373  } else {
1374  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_DEFOP###', '');
1375  }
1376  }
1377  // Search in
1378  if (!is_array($optValues['media']) && !is_array($optValues['lang']) || $this->conf['blind.']['media'] && $this->conf['blind.']['lang']) {
1379  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_IN###', '');
1380  } else {
1381  if (is_array($optValues['media']) && !$this->conf['blind.']['media']) {
1382  unset($hiddenFieldArr['media']);
1383  $markerArray['###SELECTBOX_MEDIA_VALUES###'] = $this->renderSelectBoxValues($this->piVars['media'], $optValues['media']);
1384  } else {
1385  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_MEDIA###', '');
1386  }
1387  if (is_array($optValues['lang']) || !$this->conf['blind.']['lang']) {
1388  unset($hiddenFieldArr['lang']);
1389  $markerArray['###SELECTBOX_LANG_VALUES###'] = $this->renderSelectBoxValues($this->piVars['lang'], $optValues['lang']);
1390  } else {
1391  $html = $this->cObj->substituteSubpart($html, '###SELECT_SEARCH_LANG###', '');
1392  }
1393  }
1394  // Sections
1395  if (!is_array($optValues['sections']) || $this->conf['blind.']['sections']) {
1396  $html = $this->cObj->substituteSubpart($html, '###SELECT_SECTION###', '');
1397  } else {
1398  $markerArray['###SELECTBOX_SECTIONS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['sections'], $optValues['sections']);
1399  }
1400  // Free Indexing Configurations:
1401  if (!is_array($optValues['freeIndexUid']) || $this->conf['blind.']['freeIndexUid']) {
1402  $html = $this->cObj->substituteSubpart($html, '###SELECT_FREEINDEXUID###', '');
1403  } else {
1404  $markerArray['###SELECTBOX_FREEINDEXUIDS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['freeIndexUid'], $optValues['freeIndexUid']);
1405  }
1406  // Sorting
1407  if (!is_array($optValues['order']) || !is_array($optValues['desc']) || $this->conf['blind.']['order']) {
1408  $html = $this->cObj->substituteSubpart($html, '###SELECT_ORDER###', '');
1409  } else {
1410  unset($hiddenFieldArr['order']);
1411  unset($hiddenFieldArr['desc']);
1412  unset($hiddenFieldArr['results']);
1413  $markerArray['###SELECTBOX_ORDER_VALUES###'] = $this->renderSelectBoxValues($this->piVars['order'], $optValues['order']);
1414  $markerArray['###SELECTBOX_DESC_VALUES###'] = $this->renderSelectBoxValues($this->piVars['desc'], $optValues['desc']);
1415  $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1416  }
1417  // Limits
1418  if (!is_array($optValues['results']) || !is_array($optValues['results']) || $this->conf['blind.']['results']) {
1419  $html = $this->cObj->substituteSubpart($html, '###SELECT_RESULTS###', '');
1420  } else {
1421  $markerArray['###SELECTBOX_RESULTS_VALUES###'] = $this->renderSelectBoxValues($this->piVars['results'], $optValues['results']);
1422  }
1423  // Grouping
1424  if (!is_array($optValues['group']) || $this->conf['blind.']['group']) {
1425  $html = $this->cObj->substituteSubpart($html, '###SELECT_GROUP###', '');
1426  } else {
1427  unset($hiddenFieldArr['group']);
1428  $markerArray['###SELECTBOX_GROUP_VALUES###'] = $this->renderSelectBoxValues($this->piVars['group'], $optValues['group']);
1429  }
1430  if ($this->conf['blind.']['extResume']) {
1431  $html = $this->cObj->substituteSubpart($html, '###SELECT_EXTRESUME###', '');
1432  } else {
1433  $markerArray['###EXT_RESUME_CHECKED###'] = $this->piVars['extResume'] ? ' checked="checked"' : '';
1434  }
1435  } else {
1436  // Extended search
1437  $html = $this->cObj->substituteSubpart($html, '###SEARCH_FORM_EXTENDED###', '');
1438  }
1439  if ($this->conf['show.']['advancedSearchLink']) {
1440  $linkToOtherMode = $this->piVars['ext'] ? $this->pi_getPageLink($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sPre) : $this->pi_getPageLink($GLOBALS['TSFE']->id, $GLOBALS['TSFE']->sPre, array($this->prefixId . '[ext]' => 1));
1441  $markerArray['###LINKTOOTHERMODE###'] = '<a href="' . htmlspecialchars($linkToOtherMode) . '">' . $this->pi_getLL(($this->piVars['ext'] ? 'link_regularSearch' : 'link_advancedSearch'), '', TRUE) . '</a>';
1442  } else {
1443  $markerArray['###LINKTOOTHERMODE###'] = '';
1444  }
1445  // Write all hidden fields
1446  $html = $this->cObj->substituteSubpart($html, '###HIDDEN_FIELDS###', implode('', $hiddenFieldArr));
1447  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1448  return $substitutedContent;
1449  }
1450 
1459  public function renderSelectBoxValues($value, $optValues) {
1460  if (is_array($optValues)) {
1461  $opt = array();
1462  $isSelFlag = 0;
1463  foreach ($optValues as $k => $v) {
1464  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1465  if ($sel) {
1466  $isSelFlag++;
1467  }
1468  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1469  }
1470  return implode('', $opt);
1471  }
1472  }
1473 
1480  public function printRules() {
1481  if ($this->conf['show.']['rules']) {
1482  $html = $this->cObj->getSubpart($this->templateCode, '###RULES###');
1483  $markerArray['###RULES_HEADER###'] = $this->pi_getLL('rules_header', '', TRUE);
1484  $markerArray['###RULES_TEXT###'] = nl2br(trim($this->pi_getLL('rules_text', '', TRUE)));
1485  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1486  return $this->cObj->stdWrap($substitutedContent, $this->conf['rules_stdWrap.']);
1487  }
1488  }
1489 
1496  public function printResultSectionLinks() {
1497  if (count($this->resultSections)) {
1498  $lines = array();
1499  $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS###');
1500  $item = $this->cObj->getSubpart($this->templateCode, '###RESULT_SECTION_LINKS_LINK###');
1501  foreach ($this->resultSections as $id => $dat) {
1502  $markerArray = array();
1503  $aBegin = '<a href="' . htmlspecialchars(($GLOBALS['TSFE']->anchorPrefix . '#anchor_' . md5($id))) . '">';
1504  $aContent = htmlspecialchars((trim($dat[0]) ? trim($dat[0]) : $this->pi_getLL('unnamedSection'))) . ' (' . $dat[1] . ' ' . $this->pi_getLL(($dat[1] > 1 ? 'word_pages' : 'word_page'), '', TRUE) . ')';
1505  $aEnd = '</a>';
1506  $markerArray['###LINK###'] = $aBegin . $aContent . $aEnd;
1507  $links[] = $this->cObj->substituteMarkerArrayCached($item, $markerArray, array(), array());
1508  }
1509  $html = $this->cObj->substituteMarkerArrayCached($html, array('###LINKS###' => implode('', $links)), array(), array());
1510  return '<div' . $this->pi_classParam('sectionlinks') . '>' . $this->cObj->stdWrap($html, $this->conf['sectionlinks_stdWrap.']) . '</div>';
1511  }
1512  }
1513 
1523  public function makeSectionHeader($id, $sectionTitleLinked, $countResultRows) {
1524  $html = $this->cObj->getSubpart($this->templateCode, '###SECTION_HEADER###');
1525  $markerArray['###ANCHOR_URL###'] = 'anchor_' . md5($id);
1526  $markerArray['###SECTION_TITLE###'] = $sectionTitleLinked;
1527  $markerArray['###RESULT_COUNT###'] = $countResultRows;
1528  $markerArray['###RESULT_NAME###'] = $this->pi_getLL('word_page' . ($countResultRows > 1 ? 's' : ''));
1529  $substitutedContent = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1530  return $substitutedContent;
1531  }
1532 
1541  public function printResultRow($row, $headerOnly = 0) {
1542  // Get template content:
1543  $tmplContent = $this->prepareResultRowTemplateData($row, $headerOnly);
1544  if ($hookObj = $this->hookRequest('printResultRow')) {
1545  return $hookObj->printResultRow($row, $headerOnly, $tmplContent);
1546  } else {
1547  $html = $this->cObj->getSubpart($this->templateCode, '###RESULT_OUTPUT###');
1548  if (!is_array($row['_sub'])) {
1549  $html = $this->cObj->substituteSubpart($html, '###ROW_SUB###', '');
1550  }
1551  if (!$headerOnly) {
1552  $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1553  } elseif ($headerOnly == 1) {
1554  $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1555  } elseif ($headerOnly == 2) {
1556  $html = $this->cObj->substituteSubpart($html, '###ROW_SHORT###', '');
1557  $html = $this->cObj->substituteSubpart($html, '###ROW_LONG###', '');
1558  }
1559  if (is_array($tmplContent)) {
1560  foreach ($tmplContent as $k => $v) {
1561  $markerArray['###' . GeneralUtility::strtoupper($k) . '###'] = $v;
1562  }
1563  }
1564  // Description text
1565  $markerArray['###TEXT_ITEM_SIZE###'] = $this->pi_getLL('res_size', '', TRUE);
1566  $markerArray['###TEXT_ITEM_CRDATE###'] = $this->pi_getLL('res_created', '', TRUE);
1567  $markerArray['###TEXT_ITEM_MTIME###'] = $this->pi_getLL('res_modified', '', TRUE);
1568  $markerArray['###TEXT_ITEM_PATH###'] = $this->pi_getLL('res_path', '', TRUE);
1569  $html = $this->cObj->substituteMarkerArrayCached($html, $markerArray, array(), array());
1570  // If there are subrows (eg. subpages in a PDF-file or if a duplicate page is selected due to user-login (phash_grouping))
1571  if (is_array($row['_sub'])) {
1572  if ($this->multiplePagesType($row['item_type'])) {
1573  $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherMatching', '', TRUE), $html);
1574  foreach ($row['_sub'] as $subRow) {
1575  $html .= $this->printResultRow($subRow, 1);
1576  }
1577  } else {
1578  $markerArray['###TEXT_ROW_SUB###'] = $this->pi_getLL('res_otherMatching', '', TRUE);
1579  $html = str_replace('###TEXT_ROW_SUB###', $this->pi_getLL('res_otherPageAsWell', '', TRUE), $html);
1580  }
1581  }
1582  return $html;
1583  }
1584  }
1585 
1596  public function pi_list_browseresults($showResultCount = TRUE, $addString = '', $addPart = '', $freeIndexUid = -1) {
1597  // Initializing variables:
1598  $pointer = (int)$this->piVars['pointer'];
1599  $count = (int)$this->internal['res_count'];
1600  $results_at_a_time = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->internal['results_at_a_time'], 1, 1000);
1601  $pageCount = (int)ceil($count / $results_at_a_time);
1602 
1603  $links = array();
1604  // only show the result browser if more than one page is needed
1605  if ($pageCount > 1) {
1606  $maxPages = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($this->internal['maxPages'], 1, $pageCount);
1607 
1608  // Make browse-table/links:
1609  if ($pointer > 0) {
1610  // all pages after the 1st one
1611  $links[] = '<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_prev', '< Previous', TRUE), $pointer - 1, $freeIndexUid) . '</li>';
1612  }
1613  $minPage = $pointer - (int)floor($maxPages / 2);
1614  $maxPage = $minPage + $maxPages - 1;
1615  // Check if the indexes are within the page limits
1616  if ($minPage < 0) {
1617  $maxPage -= $minPage;
1618  $minPage = 0;
1619  } elseif ($maxPage >= $pageCount) {
1620  $minPage -= $maxPage - $pageCount + 1;
1621  $maxPage = $pageCount - 1;
1622  }
1623  $pageLabel = $this->pi_getLL('pi_list_browseresults_page', 'Page', TRUE);
1624  for ($a = $minPage; $a <= $maxPage; $a++) {
1625  $label = trim($pageLabel . ' ' . ($a + 1));
1626  $link = $this->makePointerSelector_link($label, $a, $freeIndexUid);
1627  if ($a === $pointer) {
1628  $links[] = '<li' . $this->pi_classParam('browselist-currentPage') . '><strong>' . $link . '</strong></li>';
1629  } else {
1630  $links[] = '<li>' . $link . '</li>';
1631  }
1632  }
1633  if ($pointer + 1 < $pageCount) {
1634  $links[] = '<li>' . $this->makePointerSelector_link($this->pi_getLL('pi_list_browseresults_next', 'Next >', TRUE), $pointer + 1, $freeIndexUid) . '</li>';
1635  }
1636  }
1637  if (!empty($links)) {
1638  $addPart .= '
1639  <ul class="browsebox">
1640  ' . implode('', $links) . '
1641  </ul>';
1642  }
1643  $label = str_replace(
1644  array('###TAG_BEGIN###', '###TAG_END###'),
1645  array('<strong>', '</strong>'),
1646  $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###')
1647  );
1648  $resultsFrom = $pointer * $results_at_a_time + 1;
1649  $resultsTo = min($resultsFrom + $results_at_a_time - 1, $count);
1650  $resultCountText = '';
1651  if ($showResultCount) {
1652  $resultCountText = '<p>' . sprintf($label, $resultsFrom, $resultsTo, $count) . $addString . '</p>';
1653  }
1654  $sTables = '<div' . $this->pi_classParam('browsebox') . '>'
1655  . $resultCountText
1656  . $addPart . '</div>';
1657  return $sTables;
1658  }
1659 
1660  /***********************************
1661  *
1662  * Support functions for HTML output (with a minimum of fixed markup)
1663  *
1664  ***********************************/
1673  public function prepareResultRowTemplateData($row, $headerOnly) {
1674  // Initialize:
1675  $specRowConf = $this->getSpecialConfigForRow($row);
1676  $CSSsuffix = $specRowConf['CSSsuffix'] ? '-' . $specRowConf['CSSsuffix'] : '';
1677  // If external media, link to the media-file instead.
1678  if ($row['item_type']) {
1679  // External media
1680  if ($row['show_resume']) {
1681  // Can link directly.
1682  $targetAttribute = '';
1683  if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
1684  $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
1685  }
1686  $title = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' . htmlspecialchars($this->makeTitle($row)) . '</a>';
1687  } else {
1688  // Suspicious, so linking to page instead...
1689  $copy_row = $row;
1690  unset($copy_row['cHashParams']);
1691  $title = $this->linkPage($row['page_id'], htmlspecialchars($this->makeTitle($row)), $copy_row);
1692  }
1693  } else {
1694  // Else the page:
1695  // Prepare search words for markup in content:
1696  if ($this->conf['forwardSearchWordsInResultLink']) {
1697  $markUpSwParams = array('no_cache' => 1);
1698  foreach ($this->sWArr as $d) {
1699  $markUpSwParams['sword_list'][] = $d['sword'];
1700  }
1701  } else {
1702  $markUpSwParams = array();
1703  }
1704  $title = $this->linkPage($row['data_page_id'], htmlspecialchars($this->makeTitle($row)), $row, $markUpSwParams);
1705  }
1706  $tmplContent = array();
1707  $tmplContent['title'] = $title;
1708  $tmplContent['result_number'] = $this->conf['show.']['resultNumber'] ? $row['result_number'] . ': ' : '&nbsp;';
1709  $tmplContent['icon'] = $this->makeItemTypeIcon($row['item_type'], '', $specRowConf);
1710  $tmplContent['rating'] = $this->makeRating($row);
1711  $tmplContent['description'] = $this->makeDescription($row, $this->piVars['extResume'] && !$headerOnly ? 0 : 1);
1712  $tmplContent = $this->makeInfo($row, $tmplContent);
1713  $tmplContent['access'] = $this->makeAccessIndication($row['page_id']);
1714  $tmplContent['language'] = $this->makeLanguageIndication($row);
1715  $tmplContent['CSSsuffix'] = $CSSsuffix;
1716  // Post processing with hook.
1717  if ($hookObj = $this->hookRequest('prepareResultRowTemplateData_postProc')) {
1718  $tmplContent = $hookObj->prepareResultRowTemplateData_postProc($tmplContent, $row, $headerOnly);
1719  }
1720  return $tmplContent;
1721  }
1722 
1730  public function tellUsWhatIsSeachedFor($sWArr) {
1731  // Init:
1732  $searchingFor = '';
1733  $c = 0;
1734  // Traverse search words:
1735  foreach ($sWArr as $k => $v) {
1736  if ($c) {
1737  switch ($v['oper']) {
1738  case 'OR':
1739  $searchingFor .= ' ' . $this->pi_getLL('searchFor_or', '', TRUE) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1740  break;
1741  case 'AND NOT':
1742  $searchingFor .= ' ' . $this->pi_getLL('searchFor_butNot', '', TRUE) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1743  break;
1744  default:
1745  // AND...
1746  $searchingFor .= ' ' . $this->pi_getLL('searchFor_and', '', TRUE) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1747  }
1748  } else {
1749  $searchingFor = $this->pi_getLL('searchFor', '', TRUE) . ' ' . $this->wrapSW($this->utf8_to_currentCharset($v['sword']));
1750  }
1751  $c++;
1752  }
1753  return $searchingFor;
1754  }
1755 
1763  public function wrapSW($str) {
1764  return '"<span' . $this->pi_classParam('sw') . '>' . htmlspecialchars($str) . '</span>"';
1765  }
1766 
1776  public function renderSelectBox($name, $value, $optValues) {
1777  if (is_array($optValues)) {
1778  $opt = array();
1779  $isSelFlag = 0;
1780  foreach ($optValues as $k => $v) {
1781  $sel = (string)$k === (string)$value ? ' selected="selected"' : '';
1782  if ($sel) {
1783  $isSelFlag++;
1784  }
1785  $opt[] = '<option value="' . htmlspecialchars($k) . '"' . $sel . '>' . htmlspecialchars($v) . '</option>';
1786  }
1787  return '<select name="' . $name . '">' . implode('', $opt) . '</select>';
1788  }
1789  }
1790 
1801  public function makePointerSelector_link($str, $p, $freeIndexUid) {
1802  $onclick = 'document.getElementById(\'' . $this->prefixId . '_pointer\').value=\'' . $p . '\';' . 'document.getElementById(\'' . $this->prefixId . '_freeIndexUid\').value=\'' . rawurlencode($freeIndexUid) . '\';' . 'document.getElementById(\'' . $this->prefixId . '\').submit();return false;';
1803  return '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . $str . '</a>';
1804  }
1805 
1815  public function makeItemTypeIcon($it, $alt = '', $specRowConf) {
1816  // Build compound key if item type is 0, iconRendering is not used
1817  // and specConfs.[pid].pageIcon was set in TS
1818  if ($it === '0' && $specRowConf['_pid'] && is_array($specRowConf['pageIcon.']) && !is_array($this->conf['iconRendering.'])) {
1819  $it .= ':' . $specRowConf['_pid'];
1820  }
1821  if (!isset($this->iconFileNameCache[$it])) {
1822  $this->iconFileNameCache[$it] = '';
1823  // If TypoScript is used to render the icon:
1824  if (is_array($this->conf['iconRendering.'])) {
1825  $this->cObj->setCurrentVal($it);
1826  $this->iconFileNameCache[$it] = $this->cObj->cObjGetSingle($this->conf['iconRendering'], $this->conf['iconRendering.']);
1827  } else {
1828  // Default creation / finding of icon:
1829  $icon = '';
1830  if ($it === '0' || substr($it, 0, 2) == '0:') {
1831  if (is_array($specRowConf['pageIcon.'])) {
1832  $this->iconFileNameCache[$it] = $this->cObj->IMAGE($specRowConf['pageIcon.']);
1833  } else {
1834  $icon = 'EXT:indexed_search/pi/res/pages.gif';
1835  }
1836  } elseif ($this->external_parsers[$it]) {
1837  $icon = $this->external_parsers[$it]->getIcon($it);
1838  }
1839  if ($icon) {
1840  $fullPath = GeneralUtility::getFileAbsFileName($icon);
1841  if ($fullPath) {
1842  $info = @getimagesize($fullPath);
1843  $iconPath = \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix($fullPath);
1844  $this->iconFileNameCache[$it] = is_array($info) ? '<img src="' . $iconPath . '" ' . $info[3] . ' title="' . htmlspecialchars($alt) . '" alt="" />' : '';
1845  }
1846  }
1847  }
1848  }
1849  return $this->iconFileNameCache[$it];
1850  }
1851 
1859  public function makeRating($row) {
1860  switch ((string) $this->piVars['order']) {
1861  case 'rank_count':
1862  // Number of occurencies on page
1863  return $row['order_val'] . ' ' . $this->pi_getLL('maketitle_matches');
1864  break;
1865  case 'rank_first':
1866  // Close to top of page
1867  return ceil(\TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange((255 - $row['order_val']), 1, 255) / 255 * 100) . '%';
1868  break;
1869  case 'rank_flag':
1870  // Based on priority assigned to <title> / <meta-keywords> / <meta-description> / <body>
1871  if ($this->firstRow['order_val2']) {
1872  $base = $row['order_val1'] * 256;
1873  // (3 MSB bit, 224 is highest value of order_val1 currently)
1874  $freqNumber = $row['order_val2'] / $this->firstRow['order_val2'] * pow(2, 12);
1875  // 15-3 MSB = 12
1876  $total = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($base + $freqNumber, 0, 32767);
1877  return ceil(log($total) / log(32767) * 100) . '%';
1878  }
1879  break;
1880  case 'rank_freq':
1881  // Based on frequency
1882  $max = 10000;
1883  $total = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($row['order_val'], 0, $max);
1884  return ceil(log($total) / log($max) * 100) . '%';
1885  break;
1886  case 'crdate':
1887  // Based on creation date
1888  return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_crdate'], 0);
1889  break;
1890  case 'mtime':
1891  // Based on modification time
1892  return $this->cObj->calcAge($GLOBALS['EXEC_TIME'] - $row['item_mtime'], 0);
1893  break;
1894  default:
1895  // fx. title
1896  return '&nbsp;';
1897  }
1898  }
1899 
1909  public function makeDescription($row, $noMarkup = 0, $lgd = 180) {
1910  if ($row['show_resume']) {
1911  if (!$noMarkup) {
1912  $markedSW = '';
1913  if (\TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility::isTableUsed('index_fulltext')) {
1914  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash=' . (int)$row['phash']);
1915  } else {
1916  $res = FALSE;
1917  }
1918  if ($res) {
1919  if ($ftdrow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1920  // Cut HTTP references after some length
1921  $content = preg_replace('/(http:\\/\\/[^ ]{60})([^ ]+)/i', '$1...', $ftdrow['fulltextdata']);
1922  $markedSW = $this->markupSWpartsOfString($content);
1923  }
1924  $GLOBALS['TYPO3_DB']->sql_free_result($res);
1925  }
1926  }
1927  if (!trim($markedSW)) {
1928  $outputStr = $GLOBALS['TSFE']->csConvObj->crop('utf-8', $row['item_description'], $lgd);
1929  $outputStr = htmlspecialchars($outputStr);
1930  }
1931  $output = $this->utf8_to_currentCharset($outputStr ?: $markedSW);
1932  } else {
1933  $output = '<span class="noResume">' . $this->pi_getLL('res_noResume', '', TRUE) . '</span>';
1934  }
1935  return $output;
1936  }
1937 
1945  public function markupSWpartsOfString($str) {
1946  $htmlParser = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\HtmlParser');
1947  // Init:
1948  $str = str_replace('&nbsp;', ' ', $htmlParser->bidir_htmlspecialchars($str, -1));
1949  $str = preg_replace('/\\s\\s+/', ' ', $str);
1950  $swForReg = array();
1951  // Prepare search words for regex:
1952  foreach ($this->sWArr as $d) {
1953  $swForReg[] = preg_quote($d['sword'], '/');
1954  }
1955  $regExString = '(' . implode('|', $swForReg) . ')';
1956  // Split and combine:
1957  $parts = preg_split('/' . $regExString . '/ui', ' ' . $str . ' ', 20000, PREG_SPLIT_DELIM_CAPTURE);
1958  // Constants:
1959  $summaryMax = 300;
1960  $postPreLgd = 60;
1961  $postPreLgd_offset = 5;
1962  $divider = ' ... ';
1963  $occurencies = (count($parts) - 1) / 2;
1964  if ($occurencies) {
1965  $postPreLgd = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($summaryMax / $occurencies, $postPreLgd, $summaryMax / 2);
1966  }
1967  // Variable:
1968  $summaryLgd = 0;
1969  $output = array();
1970  // Shorten in-between strings:
1971  foreach ($parts as $k => $strP) {
1972  if ($k % 2 == 0) {
1973  // Find length of the summary part:
1974  $strLen = $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $parts[$k]);
1975  $output[$k] = $parts[$k];
1976  // Possibly shorten string:
1977  if (!$k) {
1978  // First entry at all (only cropped on the frontside)
1979  if ($strLen > $postPreLgd) {
1980  $output[$k] = $divider . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
1981  }
1982  } elseif ($summaryLgd > $summaryMax || !isset($parts[($k + 1)])) {
1983  // In case summary length is exceed OR if there are no more entries at all:
1984  if ($strLen > $postPreLgd) {
1985  $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider;
1986  }
1987  } else {
1988  // In-between search words:
1989  if ($strLen > $postPreLgd * 2) {
1990  $output[$k] = preg_replace('/[[:space:]][^[:space:]]+$/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], ($postPreLgd - $postPreLgd_offset))) . $divider . preg_replace('/^[^[:space:]]+[[:space:]]/', '', $GLOBALS['TSFE']->csConvObj->crop('utf-8', $parts[$k], -($postPreLgd - $postPreLgd_offset)));
1991  }
1992  }
1993  $summaryLgd += $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $output[$k]);
1994  // Protect output:
1995  $output[$k] = htmlspecialchars($output[$k]);
1996  // If summary lgd is exceed, break the process:
1997  if ($summaryLgd > $summaryMax) {
1998  break;
1999  }
2000  } else {
2001  $summaryLgd += $GLOBALS['TSFE']->csConvObj->strlen('utf-8', $strP);
2002  $output[$k] = '<strong class="tx-indexedsearch-redMarkup">' . htmlspecialchars($parts[$k]) . '</strong>';
2003  }
2004  }
2005  // Return result:
2006  return implode('', $output);
2007  }
2008 
2016  public function makeTitle($row) {
2017  $add = '';
2018  if ($this->multiplePagesType($row['item_type'])) {
2019  $dat = unserialize($row['cHashParams']);
2020  $pp = explode('-', $dat['key']);
2021  if ($pp[0] != $pp[1]) {
2022  $add = ', ' . $this->pi_getLL('word_pages') . ' ' . $dat['key'];
2023  } else {
2024  $add = ', ' . $this->pi_getLL('word_page') . ' ' . $pp[0];
2025  }
2026  }
2027  $outputString = $GLOBALS['TSFE']->csConvObj->crop('utf-8', $row['item_title'], 50, '...');
2028  return $this->utf8_to_currentCharset($outputString) . $add;
2029  }
2030 
2039  public function makeInfo($row, $tmplArray) {
2040  $tmplArray['size'] = GeneralUtility::formatSize($row['item_size']);
2041  $tmplArray['created'] = $this->formatCreatedDate($row['item_crdate']);
2042  $tmplArray['modified'] = $this->formatModifiedDate($row['item_mtime']);
2043  $pathId = $row['data_page_id'] ?: $row['page_id'];
2044  $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2045  $pI = parse_url($row['data_filename']);
2046  if ($pI['scheme']) {
2047  $targetAttribute = '';
2048  if ($GLOBALS['TSFE']->config['config']['fileTarget']) {
2049  $targetAttribute = ' target="' . htmlspecialchars($GLOBALS['TSFE']->config['config']['fileTarget']) . '"';
2050  }
2051  $tmplArray['path'] = '<a href="' . htmlspecialchars($row['data_filename']) . '"' . $targetAttribute . '>' . htmlspecialchars($row['data_filename']) . '</a>';
2052  } else {
2053  $pathStr = htmlspecialchars($this->getPathFromPageId($pathId, $pathMP));
2054  $tmplArray['path'] = $this->linkPage($pathId, $pathStr, array(
2055  'cHashParams' => $row['cHashParams'],
2056  'data_page_type' => $row['data_page_type'],
2057  'data_page_mp' => $pathMP,
2058  'sys_language_uid' => $row['sys_language_uid']
2059  ));
2060  }
2061  return $tmplArray;
2062  }
2063 
2071  public function getSpecialConfigForRow($row) {
2072  $pathId = $row['data_page_id'] ?: $row['page_id'];
2073  $pathMP = $row['data_page_id'] ? $row['data_page_mp'] : '';
2074  $rl = $this->getRootLine($pathId, $pathMP);
2075  $specConf = $this->conf['specConfs.']['0.'];
2076  if (is_array($rl)) {
2077  foreach ($rl as $dat) {
2078  if (is_array($this->conf['specConfs.'][$dat['uid'] . '.'])) {
2079  $specConf = $this->conf['specConfs.'][$dat['uid'] . '.'];
2080  $specConf['_pid'] = $dat['uid'];
2081  break;
2082  }
2083  }
2084  }
2085  return $specConf;
2086  }
2087 
2095  public function makeLanguageIndication($row) {
2096  // If search result is a TYPO3 page:
2097  if ((string) $row['item_type'] === '0') {
2098  // If TypoScript is used to render the flag:
2099  if (is_array($this->conf['flagRendering.'])) {
2100  $this->cObj->setCurrentVal($row['sys_language_uid']);
2101  return $this->cObj->cObjGetSingle($this->conf['flagRendering'], $this->conf['flagRendering.']);
2102  } else {
2103  // ... otherwise, get flag from sys_language record:
2104  // Get sys_language record
2105  $rowDat = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_language', 'uid=' . (int)$row['sys_language_uid'] . ' ' . $this->cObj->enableFields('sys_language'));
2106  // Flag code:
2107  $flag = $rowDat['flag'];
2108  if ($flag) {
2109  // FIXME not all flags from typo3/gfx/flags are available in media/flags/
2110  $file = \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix(PATH_tslib) . 'media/flags/flag_' . $flag;
2111  $imgInfo = @getimagesize((PATH_site . $file));
2112  if (is_array($imgInfo)) {
2113  $output = '<img src="' . $file . '" ' . $imgInfo[3] . ' title="' . htmlspecialchars($rowDat['title']) . '" alt="' . htmlspecialchars($rowDat['title']) . '" />';
2114  return $output;
2115  }
2116  }
2117  }
2118  }
2119  return '&nbsp;';
2120  }
2121 
2130  public function makeAccessIndication($id) {
2131  if (is_array($this->fe_groups_required[$id]) && count($this->fe_groups_required[$id])) {
2132  return '<img src="' . \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath('indexed_search') . 'pi/res/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="" />';
2133  }
2134  }
2135 
2146  public function linkPage($id, $str, $row = array(), $markUpSwParams = array()) {
2147  // Parameters for link:
2148  $urlParameters = (array) unserialize($row['cHashParams']);
2149  // Add &type and &MP variable:
2150  if ($row['data_page_type']) {
2151  $urlParameters['type'] = $row['data_page_type'];
2152  }
2153  if ($row['data_page_mp']) {
2154  $urlParameters['MP'] = $row['data_page_mp'];
2155  }
2156  if ($row['sys_language_uid']) {
2157  $urlParameters['L'] = $row['sys_language_uid'];
2158  }
2159  // markup-GET vars:
2160  $urlParameters = array_merge($urlParameters, $markUpSwParams);
2161  // 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...
2162  if (!is_array($this->domain_records[$id])) {
2163  $this->getPathFromPageId($id);
2164  }
2165  // If external domain, then link to that:
2166  if (count($this->domain_records[$id])) {
2167  reset($this->domain_records[$id]);
2168  $firstDom = current($this->domain_records[$id]);
2169  $scheme = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
2170  $addParams = '';
2171  if (is_array($urlParameters)) {
2172  if (count($urlParameters)) {
2173  $addParams .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
2174  }
2175  }
2176  if ($target = $this->conf['search.']['detect_sys_domain_records.']['target']) {
2177  $target = ' target="' . $target . '"';
2178  }
2179  return '<a href="' . htmlspecialchars(($scheme . $firstDom . '/index.php?id=' . $id . $addParams)) . '"' . $target . '>' . htmlspecialchars($str) . '</a>';
2180  } else {
2181  return $this->pi_linkToPage($str, $id, $this->conf['result_link_target'], $urlParameters);
2182  }
2183  }
2184 
2193  public function getRootLine($id, $pathMP = '') {
2194  $identStr = $id . '|' . $pathMP;
2195  if (!isset($this->cache_path[$identStr])) {
2196  $this->cache_rl[$identStr] = $GLOBALS['TSFE']->sys_page->getRootLine($id, $pathMP);
2197  }
2198  return $this->cache_rl[$identStr];
2199  }
2200 
2208  public function getFirstSysDomainRecordForPage($id) {
2209  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('domainName', 'sys_domain', 'pid=' . (int)$id . $this->cObj->enableFields('sys_domain'), '', 'sorting');
2210  $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
2211  return rtrim($row['domainName'], '/');
2212  }
2213 
2222  public function getPathFromPageId($id, $pathMP = '') {
2223  $identStr = $id . '|' . $pathMP;
2224  if (!isset($this->cache_path[$identStr])) {
2225  $this->fe_groups_required[$id] = array();
2226  $this->domain_records[$id] = array();
2227  $rl = $this->getRootLine($id, $pathMP);
2228  $hitRoot = 0;
2229  $path = '';
2230  if (is_array($rl) && count($rl)) {
2231  foreach ($rl as $k => $v) {
2232  // Check fe_user
2233  if ($v['fe_group'] && ($v['uid'] == $id || $v['extendToSubpages'])) {
2234  $this->fe_groups_required[$id][] = $v['fe_group'];
2235  }
2236  // Check sys_domain.
2237  if ($this->conf['search.']['detect_sys_domain_records']) {
2238  $sysDName = $this->getFirstSysDomainRecordForPage($v['uid']);
2239  if ($sysDName) {
2240  $this->domain_records[$id][] = $sysDName;
2241  // Set path accordingly:
2242  $path = $sysDName . $path;
2243  break;
2244  }
2245  }
2246  // Stop, if we find that the current id is the current root page.
2247  if ($v['uid'] == $GLOBALS['TSFE']->config['rootLine'][0]['uid']) {
2248  break;
2249  }
2250  $path = '/' . $v['title'] . $path;
2251  }
2252  }
2253  $this->cache_path[$identStr] = $path;
2254  if (is_array($this->conf['path_stdWrap.'])) {
2255  $this->cache_path[$identStr] = $this->cObj->stdWrap($this->cache_path[$identStr], $this->conf['path_stdWrap.']);
2256  }
2257  }
2258  return $this->cache_path[$identStr];
2259  }
2260 
2268  public function getMenu($id) {
2269  if ($this->conf['show.']['LxALLtypes']) {
2270  $output = array();
2271  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('title,uid', 'pages', 'pid=' . (int)$id . $this->cObj->enableFields('pages'), '', 'sorting');
2272  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2273  $output[$row['uid']] = $GLOBALS['TSFE']->sys_page->getPageOverlay($row);
2274  }
2275  $GLOBALS['TYPO3_DB']->sql_free_result($res);
2276  return $output;
2277  } else {
2278  return $GLOBALS['TSFE']->sys_page->getMenu($id);
2279  }
2280  }
2281 
2289  public function multiplePagesType($item_type) {
2290  return is_object($this->external_parsers[$item_type]) && $this->external_parsers[$item_type]->isMultiplePageExtension($item_type);
2291  }
2292 
2300  public function utf8_to_currentCharset($str) {
2301  return $GLOBALS['TSFE']->csConv($str, 'utf-8');
2302  }
2303 
2311  public function hookRequest($functionName) {
2312  global $TYPO3_CONF_VARS;
2313  // Hook: menuConfig_preProcessModMenu
2314  if ($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]) {
2315  $hookObj = GeneralUtility::getUserObj($TYPO3_CONF_VARS['EXTCONF']['indexed_search']['pi1_hooks'][$functionName]);
2316  if (method_exists($hookObj, $functionName)) {
2317  $hookObj->pObj = $this;
2318  return $hookObj;
2319  }
2320  }
2321  }
2322 
2328  protected function getSearchFormActionURL() {
2329  $targetUrlPid = $this->getSearchFormActionPidFromTS();
2330  if ($targetUrlPid == 0) {
2331  $targetUrlPid = $GLOBALS['TSFE']->id;
2332  }
2333  return $this->pi_getPageLink($targetUrlPid, $GLOBALS['TSFE']->sPre);
2334  }
2335 
2341  protected function getSearchFormActionPidFromTS() {
2342  $result = 0;
2343  if (isset($this->conf['search.']['targetPid']) || isset($this->conf['search.']['targetPid.'])) {
2344  if (is_array($this->conf['search.']['targetPid.'])) {
2345  $result = $this->cObj->stdWrap($this->conf['search.']['targetPid'], $this->conf['search.']['targetPid.']);
2346  } else {
2347  $result = $this->conf['search.']['targetPid'];
2348  }
2349  $result = (int)$result;
2350  }
2351  return $result;
2352  }
2353 
2360  protected function formatCreatedDate($date) {
2361  $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'];
2362  return $this->formatDate($date, 'created', $defaultFormat);
2363  }
2364 
2371  protected function formatModifiedDate($date) {
2372  $defaultFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
2373  return $this->formatDate($date, 'modified', $defaultFormat);
2374  }
2375 
2385  protected function formatDate($date, $tsKey, $defaultFormat) {
2386  $strftimeFormat = $this->conf['dateFormat.'][$tsKey];
2387  if ($strftimeFormat) {
2388  $result = strftime($strftimeFormat, $date);
2389  } else {
2390  $result = date($defaultFormat, $date);
2391  }
2392  return $result;
2393  }
2394 
2401  public function getSearchType() {
2402  return (int)$this->piVars['type'];
2403  }
2404 
2410  public function getSearchRootPageIdList() {
2411  return \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $this->wholeSiteIdList);
2412  }
2413 
2420  public function getJoinPagesForQuery() {
2421  return (bool)$this->join_pages;
2422  }
2423 }
linkPage($id, $str, $row=array(), $markUpSwParams=array())
makeSectionHeader($id, $sectionTitleLinked, $countResultRows)
$TYPO3_CONF_VARS['SYS']['contentTable']
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
static intExplode($delimiter, $string, $removeEmptyValues=FALSE, $limit=0)
$uid
Definition: server.php:36
static getUserObj($classRef, $checkPrefix='', $silent=FALSE)
static trimExplode($delim, $string, $removeEmptyValues=FALSE, $limit=0)
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
pi_getLL($key, $alternativeLabel='', $hsc=FALSE)
pi_getPageLink($id, $target='', $urlParameters=array())
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=FALSE, $rawurlencodeParamName=FALSE)
pi_linkToPage($str, $id, $target='', $urlParameters=array())
if(isset($ajaxID)) if(in_array( $ajaxID, $noUserAjaxIDs))
Re-apply pairs of single-quotes to the text.
Definition: ajax.php:40
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
static revExplode($delimiter, $string, $count=0)
pi_list_browseresults($showResultCount=TRUE, $addString='', $addPart='', $freeIndexUid=-1)