TYPO3 CMS  TYPO3_6-2
SearchResultContentObject.php
Go to the documentation of this file.
1 <?php
3 
26  public $tables = array();
27 
28  // Alternatively 'PRIMARY_KEY'; sorting by primary key
32  public $group_by = 'PRIMARY_KEY';
33 
34  // Standard SQL-operator between words
38  public $default_operator = 'AND';
39 
44 
45  // case-sensitive. Defines the words, which will be operators between words
49  public $operator_translate_table = array(
50  array('+', 'AND'),
51  array('|', 'AND'),
52  array('-', 'AND NOT'),
53  // english
54  array('and', 'AND'),
55  array('or', 'OR'),
56  array('not', 'AND NOT')
57  );
58 
59  // Internal
60  // Contains the search-words and operators
64  public $sword_array;
65 
66  // Contains the query parts after processing.
70  public $queryParts;
71 
72  // Addition to the whereclause. This could be used to limit search to a certain page or alike in the system.
77 
78  // This is set with the foreign table that 'pages' are connected to.
82  public $fTable;
83 
84  // How many rows to offset from the beginning
88  public $res_offset = 0;
89 
90  // How many results to show (0 = no limit)
94  public $res_shows = 20;
95 
96  // Intern: How many results, there was last time (with the exact same searchstring.
100  public $res_count;
101 
102  // List of pageIds.
106  public $pageIdList = '';
107 
111  public $listOfSearchFields = '';
112 
119  public function __construct(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj = NULL) {
120  if (!is_null($cObj)) {
121  $this->cObj = $cObj;
123  }
124  }
125 
132  public function render($conf = array()) {
133  if (\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('sword') && \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('scols')) {
134  $this->register_and_explode_search_string(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('sword'));
135  $this->register_tables_and_columns(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('scols'), $conf['allowedCols']);
136  // Depth
137  $depth = 100;
138  // The startId is found
139  $theStartId = 0;
140  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('stype'))) {
141  $temp_theStartId = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('stype');
142  $rootLine = $GLOBALS['TSFE']->sys_page->getRootLine($temp_theStartId);
143  // The page MUST have a rootline with the Level0-page of the current site inside!!
144  foreach ($rootLine as $val) {
145  if ($val['uid'] == $GLOBALS['TSFE']->tmpl->rootLine[0]['uid']) {
146  $theStartId = $temp_theStartId;
147  }
148  }
149  } elseif (\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('stype')) {
150  if (substr(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('stype'), 0, 1) == 'L') {
151  $pointer = (int)substr(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('stype'), 1);
152  $theRootLine = $GLOBALS['TSFE']->tmpl->rootLine;
153  // location Data:
154  $locDat_arr = explode(':', \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('locationData'));
155  $pId = (int)$locDat_arr[0];
156  if ($pId) {
157  $altRootLine = $GLOBALS['TSFE']->sys_page->getRootLine($pId);
158  ksort($altRootLine);
159  if (count($altRootLine)) {
160  // Check if the rootline has the real Level0 in it!!
161  $hitRoot = 0;
162  $theNewRoot = array();
163  foreach ($altRootLine as $val) {
164  if ($hitRoot || $val['uid'] == $GLOBALS['TSFE']->tmpl->rootLine[0]['uid']) {
165  $hitRoot = 1;
166  $theNewRoot[] = $val;
167  }
168  }
169  if ($hitRoot) {
170  // Override the real rootline if any thing
171  $theRootLine = $theNewRoot;
172  }
173  }
174  }
175  $key = $this->cObj->getKey($pointer, $theRootLine);
176  $theStartId = $theRootLine[$key]['uid'];
177  }
178  }
179  if (!$theStartId) {
180  // If not set, we use current page
181  $theStartId = $GLOBALS['TSFE']->id;
182  }
183  // Generate page-tree
184  $this->pageIdList .= $this->cObj->getTreeList(-1 * $theStartId, $depth);
185  $endClause = 'pages.uid IN (' . $this->pageIdList . ')
186  AND pages.doktype in (' . $GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'] . ($conf['addExtUrlsAndShortCuts'] ? ',3,4' : '') . ')
187  AND pages.no_search=0' . $this->cObj->enableFields($this->fTable) . $this->cObj->enableFields('pages');
188  if ($conf['languageField.'][$this->fTable]) {
189  // (using sys_language_uid which is the ACTUAL language of the page.
190  // sys_language_content is only for selecting DISPLAY content!)
191  $endClause .= ' AND ' . $this->fTable . '.' . $conf['languageField.'][$this->fTable] . ' = ' . (int)$GLOBALS['TSFE']->sys_language_uid;
192  }
193  // Build query
194  $this->build_search_query($endClause);
195  // Count...
196  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('scount'))) {
197  $this->res_count = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('scount');
198  } else {
199  $this->count_query();
200  }
201  // Range
202  $spointer = (int)\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('spointer');
203  $range = isset($conf['range.']) ? $this->cObj->stdWrap($conf['range'], $conf['range.']) : $conf['range'];
204  if ($range) {
205  $theRange = (int)$range;
206  } else {
207  $theRange = 20;
208  }
209  // Order By:
210  $noOrderBy = isset($conf['noOrderBy.']) ? $this->cObj->stdWrap($conf['noOrderBy'], $conf['noOrderBy.']) : $conf['noOrderBy'];
211  if (!$noOrderBy) {
212  $this->queryParts['ORDERBY'] = 'pages.lastUpdated, pages.tstamp';
213  }
214  $this->queryParts['LIMIT'] = $spointer . ',' . $theRange;
215  // Search...
216  $this->execute_query();
217  if ($GLOBALS['TYPO3_DB']->sql_num_rows($this->result)) {
218  $GLOBALS['TSFE']->register['SWORD_PARAMS'] = $this->get_searchwords();
219  $total = $this->res_count;
220  $rangeLow = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($spointer + 1, 1, $total);
221  $rangeHigh = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($spointer + $theRange, 1, $total);
222  // prev/next url:
223  $target = isset($conf['target.']) ? $this->cObj->stdWrap($conf['target'], $conf['target.']) : $conf['target'];
224  $LD = $GLOBALS['TSFE']->tmpl->linkData($GLOBALS['TSFE']->page, $target, 1, '', '', $this->cObj->getClosestMPvalueForPage($GLOBALS['TSFE']->page['uid']));
225  $targetPart = $LD['target'] ? ' target="' . htmlspecialchars($LD['target']) . '"' : '';
226  $urlParams = $this->cObj->URLqMark($LD['totalURL'], '&sword=' . rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('sword')) . '&scols=' . rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('scols')) . '&stype=' . rawurlencode(\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('stype')) . '&scount=' . $total);
227  // substitution:
228  $result = str_replace(
229  array(
230  '###RANGELOW###',
231  '###RANGEHIGH###',
232  '###TOTAL###'
233  ),
234  array(
235  $rangeLow,
236  $rangeHigh,
237  $total
238  ),
239  $this->cObj->cObjGetSingle($conf['layout'], $conf['layout.'], 'layout')
240  );
241  if ($rangeHigh < $total) {
242  $next = $this->cObj->cObjGetSingle($conf['next'], $conf['next.'], 'next');
243  $next = '<a href="' . htmlspecialchars(($urlParams . '&spointer=' . ($spointer + $theRange))) . '"' . $targetPart . $GLOBALS['TSFE']->ATagParams . '>' . $next . '</a>';
244  } else {
245  $next = '';
246  }
247  $result = str_replace('###NEXT###', $next, $result);
248  if ($rangeLow > 1) {
249  $prev = $this->cObj->cObjGetSingle($conf['prev'], $conf['prev.'], 'prev');
250  $prev = '<a href="' . htmlspecialchars(($urlParams . '&spointer=' . ($spointer - $theRange))) . '"' . $targetPart . $GLOBALS['TSFE']->ATagParams . '>' . $prev . '</a>';
251  } else {
252  $prev = '';
253  }
254  $result = str_replace('###PREV###', $prev, $result);
255  // Searching result
256  $theValue = $this->cObj->cObjGetSingle($conf['resultObj'], $conf['resultObj.'], 'resultObj');
258  $cObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer');
259  $cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
260  $renderCode = '';
261  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($this->result)) {
262  // versionOL() here? This is search result displays, is that possible to preview anyway?
263  // Or are records selected here already future versions?
264  $cObj->start($row);
265  $renderCode .= $cObj->cObjGetSingle($conf['renderObj'], $conf['renderObj.'], 'renderObj');
266  }
267  $renderWrap = isset($conf['renderWrap.']) ? $this->cObj->stdWrap($conf['renderWrap'], $conf['renderWrap.']) : $conf['renderWrap'];
268  $theValue .= $this->cObj->wrap($renderCode, $renderWrap);
269  $theValue = str_replace('###RESULT###', $theValue, $result);
270  } else {
271  $theValue = $this->cObj->cObjGetSingle($conf['noResultObj'], $conf['noResultObj.'], 'noResultObj');
272  }
273  $GLOBALS['TT']->setTSlogMessage('Search in fields: ' . $this->listOfSearchFields);
274  // Wrapping
275  $content = $theValue;
276  $wrap = isset($conf['wrap.']) ? $this->cObj->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
277  if ($wrap) {
278  $content = $this->cObj->wrap($content, $wrap);
279  }
280  if (isset($conf['stdWrap.'])) {
281  $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
282  }
283  // Returning, do not cache the result of the search
284  $GLOBALS['TSFE']->set_no_cache('Search result page');
285  return $content;
286  }
287  return '';
288  }
289 
299  public function register_tables_and_columns($requestedCols, $allowedCols) {
300  $rCols = $this->explodeCols($requestedCols);
301  $aCols = $this->explodeCols($allowedCols);
302  foreach ($rCols as $k => $v) {
303  $rCols[$k] = trim($v);
304  if (in_array($rCols[$k], $aCols)) {
305  $parts = explode('.', $rCols[$k]);
306  $this->tables[$parts[0]]['searchfields'][] = $parts[1];
307  }
308  }
309  $this->tables['pages']['primary_key'] = 'uid';
310  $this->tables['pages']['resultfields'][] = 'uid';
311  unset($this->tables['pages']['fkey']);
312  foreach ($aCols as $k => $v) {
313  $aCols[$k] = trim($v);
314  $parts = explode('.', $aCols[$k]);
315  $this->tables[$parts[0]]['resultfields'][] = $parts[1] . ' AS ' . str_replace('.', '_', $aCols[$k]);
316  $this->tables[$parts[0]]['fkey'] = 'pid';
317  }
318  $this->fTable = '';
319  foreach ($this->tables as $t => $v) {
320  if ($t != 'pages') {
321  if (!$this->fTable) {
322  $this->fTable = $t;
323  } else {
324  unset($this->tables[$t]);
325  }
326  }
327  }
328  }
329 
338  public function explodeCols($in) {
339  $theArray = explode(':', $in);
340  $out = array();
341  foreach ($theArray as $val) {
342  $val = trim($val);
343  $parts = explode('.', $val);
344  if ($parts[0] && $parts[1]) {
345  $subparts = explode('-', $parts[1]);
346  foreach ($subparts as $piece) {
347  $piece = trim($piece);
348  if ($piece) {
349  $out[] = $parts[0] . '.' . $piece;
350  }
351  }
352  }
353  }
354  return $out;
355  }
356 
366  public function register_and_explode_search_string($sword) {
367  $sword = trim($sword);
368  if ($sword) {
369  $components = $this->split($sword);
370  // the searchword is stored here during the loop
371  $s_sword = '';
372  if (is_array($components)) {
373  $i = 0;
374  $lastoper = '';
375  foreach ($components as $key => $val) {
376  $operator = $this->get_operator($val);
377  if ($operator) {
378  $lastoper = $operator;
379  } elseif (strlen($val) > 1) {
380  // A searchword MUST be at least two characters long!
381  $this->sword_array[$i]['sword'] = $val;
382  $this->sword_array[$i]['oper'] = $lastoper ?: $this->default_operator;
383  $lastoper = '';
384  $i++;
385  }
386  }
387  }
388  }
389  }
390 
401  public function split($origSword, $specchars = '+-', $delchars = '+.,-') {
402  $sword = $origSword;
403  $specs = '[' . preg_quote($specchars, '/') . ']';
404  // As long as $sword is TRUE (that means $sword MUST be reduced little by little until its empty inside the loop!)
405  while ($sword) {
406  // There was a double-quote and we will then look for the ending quote.
407  if (preg_match('/^"/', $sword)) {
408  // Removes first double-quote
409  $sword = preg_replace('/^"/', '', $sword);
410  // Removes everything till next double-quote
411  preg_match('/^[^"]*/', $sword, $reg);
412  // reg[0] is the value, should not be trimmed
413  $value[] = $reg[0];
414  $sword = preg_replace('/^' . preg_quote($reg[0], '/') . '/', '', $sword);
415  // Removes last double-quote
416  $sword = trim(preg_replace('/^"/', '', $sword));
417  } elseif (preg_match('/^' . $specs . '/', $sword, $reg)) {
418  $value[] = $reg[0];
419  // Removes = sign
420  $sword = trim(preg_replace('/^' . $specs . '/', '', $sword));
421  } elseif (preg_match('/[\\+\\-]/', $sword)) {
422  // Check if $sword contains + or -
423  // + and - shall only be interpreted as $specchars when there's whitespace before it
424  // otherwise it's included in the searchword (e.g. "know-how")
425  // explode $sword to single words
426  $a_sword = explode(' ', $sword);
427  // get first word
428  $word = array_shift($a_sword);
429  // Delete $delchars at end of string
430  $word = rtrim($word, $delchars);
431  // add searchword to values
432  $value[] = $word;
433  // re-build $sword
434  $sword = implode(' ', $a_sword);
435  } else {
436  // There are no double-quotes around the value. Looking for next (space) or special char.
437  preg_match('/^[^ ' . preg_quote($specchars, '/') . ']*/', $sword, $reg);
438  // Delete $delchars at end of string
439  $word = rtrim(trim($reg[0]), $delchars);
440  $value[] = $word;
441  $sword = trim(preg_replace('/^' . preg_quote($reg[0], '/') . '/', '', $sword));
442  }
443  }
444  return $value;
445  }
446 
458  public function build_search_query($endClause) {
459  if (is_array($this->tables)) {
461  $primary_table = '';
462  // Primary key table is found.
463  foreach ($tables as $key => $val) {
464  if ($tables[$key]['primary_key']) {
465  $primary_table = $key;
466  }
467  }
468  if ($primary_table) {
469  // Initialize query parts:
470  $this->queryParts = array(
471  'SELECT' => '',
472  'FROM' => '',
473  'WHERE' => '',
474  'GROUPBY' => '',
475  'ORDERBY' => '',
476  'LIMIT' => ''
477  );
478  // Find tables / field names to select:
479  $fieldArray = array();
480  $tableArray = array();
481  foreach ($tables as $key => $val) {
482  $tableArray[] = $key;
483  $resultfields = $tables[$key]['resultfields'];
484  if (is_array($resultfields)) {
485  foreach ($resultfields as $key2 => $val2) {
486  $fieldArray[] = $key . '.' . $val2;
487  }
488  }
489  }
490  $this->queryParts['SELECT'] = implode(',', $fieldArray);
491  $this->queryParts['FROM'] = implode(',', $tableArray);
492  // Set join WHERE parts:
493  $whereArray = array();
494  $primary_table_and_key = $primary_table . '.' . $tables[$primary_table]['primary_key'];
495  $primKeys = array();
496  foreach ($tables as $key => $val) {
497  $fkey = $tables[$key]['fkey'];
498  if ($fkey) {
499  $primKeys[] = $key . '.' . $fkey . '=' . $primary_table_and_key;
500  }
501  }
502  if (count($primKeys)) {
503  $whereArray[] = '(' . implode(' OR ', $primKeys) . ')';
504  }
505  // Additional where clause:
506  if (trim($endClause)) {
507  $whereArray[] = trim($endClause);
508  }
509  // Add search word where clause:
510  $query_part = $this->build_search_query_for_searchwords();
511  if (!$query_part) {
512  $query_part = '(0!=0)';
513  }
514  $whereArray[] = '(' . $query_part . ')';
515  // Implode where clauses:
516  $this->queryParts['WHERE'] = implode(' AND ', $whereArray);
517  // Group by settings:
518  if ($this->group_by) {
519  if ($this->group_by == 'PRIMARY_KEY') {
520  $this->queryParts['GROUPBY'] = $primary_table_and_key;
521  } else {
522  $this->queryParts['GROUPBY'] = $this->group_by;
523  }
524  }
525  }
526  }
527  }
528 
537  if (is_array($this->sword_array)) {
538  $main_query_part = array();
539  foreach ($this->sword_array as $key => $val) {
540  $s_sword = $this->sword_array[$key]['sword'];
541  // Get subQueryPart
542  $sub_query_part = array();
543  $this->listOfSearchFields = '';
544  foreach ($this->tables as $key3 => $val3) {
545  $searchfields = $this->tables[$key3]['searchfields'];
546  if (is_array($searchfields)) {
547  foreach ($searchfields as $key2 => $val2) {
548  $this->listOfSearchFields .= $key3 . '.' . $val2 . ',';
549  $sub_query_part[] = $key3 . '.' . $val2 . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($s_sword, $key3) . '%\'';
550  }
551  }
552  }
553  if (count($sub_query_part)) {
554  $main_query_part[] = $this->sword_array[$key]['oper'];
555  $main_query_part[] = '(' . implode(' OR ', $sub_query_part) . ')';
556  }
557  }
558  if (count($main_query_part)) {
559  // Remove first part anyways.
560  unset($main_query_part[0]);
561  return implode(' ', $main_query_part);
562  }
563  }
564  }
565 
574  public function get_operator($operator) {
575  $operator = trim($operator);
577  if ($this->operator_translate_table_caseinsensitive) {
578  // case-conversion is charset insensitive, but it doesn't spoil
579  // anything if input string AND operator table is already converted
580  $operator = strtolower($operator);
581  }
582  foreach ($op_array as $key => $val) {
583  $item = $op_array[$key][0];
584  if ($this->operator_translate_table_caseinsensitive) {
585  // See note above.
586  $item = strtolower($item);
587  }
588  if ($operator == $item) {
589  return $op_array[$key][1];
590  }
591  }
592  }
593 
600  public function count_query() {
601  if (is_array($this->queryParts)) {
602  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($this->queryParts['SELECT'], $this->queryParts['FROM'], $this->queryParts['WHERE'], $this->queryParts['GROUPBY']);
603  $this->res_count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
604  return TRUE;
605  }
606  }
607 
614  public function execute_query() {
615  if (is_array($this->queryParts)) {
616  $this->result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($this->queryParts);
617  return TRUE;
618  }
619  }
620 
628  public function get_searchwords() {
629  $SWORD_PARAMS = '';
630  if (is_array($this->sword_array)) {
631  foreach ($this->sword_array as $key => $val) {
632  $SWORD_PARAMS .= '&sword_list[]=' . rawurlencode($val['sword']);
633  }
634  }
635  return $SWORD_PARAMS;
636  }
637 
644  public function get_searchwordsArray() {
645  if (is_array($this->sword_array)) {
646  foreach ($this->sword_array as $key => $val) {
647  $swords[] = $val['sword'];
648  }
649  }
650  return $swords;
651  }
652 
653 }
__construct(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj=NULL)
static forceIntegerInRange($theInt, $min, $max=2000000000, $defaultValue=0)
Definition: MathUtility.php:32
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.
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]