[TASK] Remove superfluous parenthesis in sysext frontend
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / SearchResultContentObject.php
1 <?php
2 namespace TYPO3\CMS\Frontend\ContentObject;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Searching in database tables, typ. "pages" and "tt_content"
31 * Used to generate search queries for TypoScript.
32 * The class is included from "class.tslib_pagegen.php" based on whether there has been detected content in the GPvar "sword"
33 *
34 * Revised for TYPO3 3.6 June/2003 by Kasper Skårhøj
35 *
36 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
37 * @author René Fritz <r.fritz@colorcube.de>
38 */
39 /**
40 * Search class used for the content object SEARCHRESULT
41 *
42 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
43 * @package TYPO3
44 * @subpackage tslib
45 * @see tslib_cObj::SEARCHRESULT()
46 */
47 class SearchResultContentObject {
48
49 /**
50 * @todo Define visibility
51 */
52 public $tables = array();
53
54 // Alternatively 'PRIMARY_KEY'; sorting by primary key
55 /**
56 * @todo Define visibility
57 */
58 public $group_by = 'PRIMARY_KEY';
59
60 // Standard SQL-operator between words
61 /**
62 * @todo Define visibility
63 */
64 public $default_operator = 'AND';
65
66 /**
67 * @todo Define visibility
68 */
69 public $operator_translate_table_caseinsensitive = TRUE;
70
71 // case-sensitiv. Defineres the words, which will be operators between words
72 /**
73 * @todo Define visibility
74 */
75 public $operator_translate_table = array(
76 array('+', 'AND'),
77 array('|', 'AND'),
78 array('-', 'AND NOT'),
79 // english
80 array('and', 'AND'),
81 array('or', 'OR'),
82 array('not', 'AND NOT')
83 );
84
85 // Internal
86 // Contains the search-words and operators
87 /**
88 * @todo Define visibility
89 */
90 public $sword_array;
91
92 // Contains the query parts after processing.
93 /**
94 * @todo Define visibility
95 */
96 public $queryParts;
97
98 // Addition to the whereclause. This could be used to limit search to a certain page or alike in the system.
99 /**
100 * @todo Define visibility
101 */
102 public $other_where_clauses;
103
104 // This is set with the foreign table that 'pages' are connected to.
105 /**
106 * @todo Define visibility
107 */
108 public $fTable;
109
110 // How many rows to offset from the beginning
111 /**
112 * @todo Define visibility
113 */
114 public $res_offset = 0;
115
116 // How many results to show (0 = no limit)
117 /**
118 * @todo Define visibility
119 */
120 public $res_shows = 20;
121
122 // Intern: How many results, there was last time (with the exact same searchstring.
123 /**
124 * @todo Define visibility
125 */
126 public $res_count;
127
128 // List of pageIds.
129 /**
130 * @todo Define visibility
131 */
132 public $pageIdList = '';
133
134 /**
135 * @todo Define visibility
136 */
137 public $listOfSearchFields = '';
138
139 /**
140 * Creates the $this->tables-array.
141 * The 'pages'-table is ALWAYS included as the search is page-based. Apart from this there may be one and only one table, joined with the pages-table. This table is the first table mentioned in the requested-list. If any more tables are set here, they are ignored.
142 *
143 * @param string $requestedCols is a list (-) of columns that we want to search. This could be input from the search-form (see TypoScript documentation)
144 * @param string $allowedCols $allowedCols: is the list of columns, that MAY be searched. All allowed cols are set as result-fields. All requested cols MUST be in the allowed-fields list.
145 * @return void
146 * @todo Define visibility
147 */
148 public function register_tables_and_columns($requestedCols, $allowedCols) {
149 $rCols = $this->explodeCols($requestedCols);
150 $aCols = $this->explodeCols($allowedCols);
151 foreach ($rCols as $k => $v) {
152 $rCols[$k] = trim($v);
153 if (in_array($rCols[$k], $aCols)) {
154 $parts = explode('.', $rCols[$k]);
155 $this->tables[$parts[0]]['searchfields'][] = $parts[1];
156 }
157 }
158 $this->tables['pages']['primary_key'] = 'uid';
159 $this->tables['pages']['resultfields'][] = 'uid';
160 unset($this->tables['pages']['fkey']);
161 foreach ($aCols as $k => $v) {
162 $aCols[$k] = trim($v);
163 $parts = explode('.', $aCols[$k]);
164 $this->tables[$parts[0]]['resultfields'][] = $parts[1] . ' AS ' . str_replace('.', '_', $aCols[$k]);
165 $this->tables[$parts[0]]['fkey'] = 'pid';
166 }
167 $this->fTable = '';
168 foreach ($this->tables as $t => $v) {
169 if ($t != 'pages') {
170 if (!$this->fTable) {
171 $this->fTable = $t;
172 } else {
173 unset($this->tables[$t]);
174 }
175 }
176 }
177 }
178
179 /**
180 * Function that can convert the syntax for entering which tables/fields the search should be conducted in.
181 *
182 * @param string $in This is the code-line defining the tables/fields to search. Syntax: '[table1].[field1]-[field2]-[field3] : [table2].[field1]-[field2]'
183 * @return array An array where the values is "[table].[field]" strings to search
184 * @see register_tables_and_columns()
185 * @todo Define visibility
186 */
187 public function explodeCols($in) {
188 $theArray = explode(':', $in);
189 $out = array();
190 foreach ($theArray as $val) {
191 $val = trim($val);
192 $parts = explode('.', $val);
193 if ($parts[0] && $parts[1]) {
194 $subparts = explode('-', $parts[1]);
195 foreach ($subparts as $piece) {
196 $piece = trim($piece);
197 if ($piece) {
198 $out[] = $parts[0] . '.' . $piece;
199 }
200 }
201 }
202 }
203 return $out;
204 }
205
206 /**
207 * Takes a search-string (WITHOUT SLASHES or else it'll be a little sppooky , NOW REMEMBER to unslash!!)
208 * Sets up $this->sword_array op with operators.
209 * This function uses $this->operator_translate_table as well as $this->default_operator
210 *
211 * @param string $sword The input search-word string.
212 * @return void
213 * @todo Define visibility
214 */
215 public function register_and_explode_search_string($sword) {
216 $sword = trim($sword);
217 if ($sword) {
218 $components = $this->split($sword);
219 // the searchword is stored here during the loop
220 $s_sword = '';
221 if (is_array($components)) {
222 $i = 0;
223 $lastoper = '';
224 foreach ($components as $key => $val) {
225 $operator = $this->get_operator($val);
226 if ($operator) {
227 $lastoper = $operator;
228 } elseif (strlen($val) > 1) {
229 // A searchword MUST be at least two characters long!
230 $this->sword_array[$i]['sword'] = $val;
231 $this->sword_array[$i]['oper'] = $lastoper ? $lastoper : $this->default_operator;
232 $lastoper = '';
233 $i++;
234 }
235 }
236 }
237 }
238 }
239
240 /**
241 * Used to split a search-word line up into elements to search for. This function will detect boolean words like AND and OR, + and -, and even find sentences encapsulated in ""
242 * This function could be re-written to be more clean and effective - yet it's not that important.
243 *
244 * @param string $origSword The raw sword string from outside
245 * @param string $specchars Special chars which are used as operators (+- is default)
246 * @param string $delchars Special chars which are deleted if the append the searchword (+-., is default)
247 * @return mixed Returns an ARRAY if there were search words, othervise the return value may be unset.
248 * @todo Define visibility
249 */
250 public function split($origSword, $specchars = '+-', $delchars = '+.,-') {
251 $sword = $origSword;
252 $specs = '[' . preg_quote($specchars, '/') . ']';
253 // As long as $sword is TRUE (that means $sword MUST be reduced little by little until its empty inside the loop!)
254 while ($sword) {
255 // There was a double-quote and we will then look for the ending quote.
256 if (preg_match('/^"/', $sword)) {
257 // Removes first double-quote
258 $sword = preg_replace('/^"/', '', $sword);
259 // Removes everything till next double-quote
260 preg_match('/^[^"]*/', $sword, $reg);
261 // reg[0] is the value, should not be trimmed
262 $value[] = $reg[0];
263 $sword = preg_replace('/^' . preg_quote($reg[0], '/') . '/', '', $sword);
264 // Removes last double-quote
265 $sword = trim(preg_replace('/^"/', '', $sword));
266 } elseif (preg_match('/^' . $specs . '/', $sword, $reg)) {
267 $value[] = $reg[0];
268 // Removes = sign
269 $sword = trim(preg_replace('/^' . $specs . '/', '', $sword));
270 } elseif (preg_match('/[\\+\\-]/', $sword)) {
271 // Check if $sword contains + or -
272 // + and - shall only be interpreted as $specchars when there's whitespace before it
273 // otherwise it's included in the searchword (e.g. "know-how")
274 // explode $sword to single words
275 $a_sword = explode(' ', $sword);
276 // get first word
277 $word = array_shift($a_sword);
278 // Delete $delchars at end of string
279 $word = rtrim($word, $delchars);
280 // add searchword to values
281 $value[] = $word;
282 // re-build $sword
283 $sword = implode(' ', $a_sword);
284 } else {
285 // There are no double-quotes around the value. Looking for next (space) or special char.
286 preg_match('/^[^ ' . preg_quote($specchars, '/') . ']*/', $sword, $reg);
287 // Delete $delchars at end of string
288 $word = rtrim(trim($reg[0]), $delchars);
289 $value[] = $word;
290 $sword = trim(preg_replace('/^' . preg_quote($reg[0], '/') . '/', '', $sword));
291 }
292 }
293 return $value;
294 }
295
296 /**
297 * This creates the search-query.
298 * In TypoScript this is used for searching only records not hidden, start/endtimed and fe_grouped! (enable-fields, see tt_content)
299 * Sets $this->queryParts
300 *
301 * @param string $endClause Some extra conditions that the search must match.
302 * @return boolean Returns TRUE no matter what - sweet isn't it!
303 * @access private
304 * @see tslib_cObj::SEARCHRESULT()
305 * @todo Define visibility
306 */
307 public function build_search_query($endClause) {
308 if (is_array($this->tables)) {
309 $tables = $this->tables;
310 $primary_table = '';
311 // Primary key table is found.
312 foreach ($tables as $key => $val) {
313 if ($tables[$key]['primary_key']) {
314 $primary_table = $key;
315 }
316 }
317 if ($primary_table) {
318 // Initialize query parts:
319 $this->queryParts = array(
320 'SELECT' => '',
321 'FROM' => '',
322 'WHERE' => '',
323 'GROUPBY' => '',
324 'ORDERBY' => '',
325 'LIMIT' => ''
326 );
327 // Find tables / field names to select:
328 $fieldArray = array();
329 $tableArray = array();
330 foreach ($tables as $key => $val) {
331 $tableArray[] = $key;
332 $resultfields = $tables[$key]['resultfields'];
333 if (is_array($resultfields)) {
334 foreach ($resultfields as $key2 => $val2) {
335 $fieldArray[] = $key . '.' . $val2;
336 }
337 }
338 }
339 $this->queryParts['SELECT'] = implode(',', $fieldArray);
340 $this->queryParts['FROM'] = implode(',', $tableArray);
341 // Set join WHERE parts:
342 $whereArray = array();
343 $primary_table_and_key = $primary_table . '.' . $tables[$primary_table]['primary_key'];
344 $primKeys = array();
345 foreach ($tables as $key => $val) {
346 $fkey = $tables[$key]['fkey'];
347 if ($fkey) {
348 $primKeys[] = $key . '.' . $fkey . '=' . $primary_table_and_key;
349 }
350 }
351 if (count($primKeys)) {
352 $whereArray[] = '(' . implode(' OR ', $primKeys) . ')';
353 }
354 // Additional where clause:
355 if (trim($endClause)) {
356 $whereArray[] = trim($endClause);
357 }
358 // Add search word where clause:
359 $query_part = $this->build_search_query_for_searchwords();
360 if (!$query_part) {
361 $query_part = '(0!=0)';
362 }
363 $whereArray[] = '(' . $query_part . ')';
364 // Implode where clauses:
365 $this->queryParts['WHERE'] = implode(' AND ', $whereArray);
366 // Group by settings:
367 if ($this->group_by) {
368 if ($this->group_by == 'PRIMARY_KEY') {
369 $this->queryParts['GROUPBY'] = $primary_table_and_key;
370 } else {
371 $this->queryParts['GROUPBY'] = $this->group_by;
372 }
373 }
374 }
375 }
376 }
377
378 /**
379 * Creates the part of the SQL-sentence, that searches for the search-words ($this->sword_array)
380 *
381 * @return string Part of where class limiting result to the those having the search word.
382 * @access private
383 * @todo Define visibility
384 */
385 public function build_search_query_for_searchwords() {
386 if (is_array($this->sword_array)) {
387 $main_query_part = array();
388 foreach ($this->sword_array as $key => $val) {
389 $s_sword = $this->sword_array[$key]['sword'];
390 // Get subQueryPart
391 $sub_query_part = array();
392 $this->listOfSearchFields = '';
393 foreach ($this->tables as $key3 => $val3) {
394 $searchfields = $this->tables[$key3]['searchfields'];
395 if (is_array($searchfields)) {
396 foreach ($searchfields as $key2 => $val2) {
397 $this->listOfSearchFields .= $key3 . '.' . $val2 . ',';
398 $sub_query_part[] = $key3 . '.' . $val2 . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($s_sword, $key3) . '%\'';
399 }
400 }
401 }
402 if (count($sub_query_part)) {
403 $main_query_part[] = $this->sword_array[$key]['oper'];
404 $main_query_part[] = '(' . implode(' OR ', $sub_query_part) . ')';
405 }
406 }
407 if (count($main_query_part)) {
408 // Remove first part anyways.
409 unset($main_query_part[0]);
410 return implode(' ', $main_query_part);
411 }
412 }
413 }
414
415 /**
416 * This returns an SQL search-operator (eg. AND, OR, NOT) translated from the current localized set of operators (eg. in danish OG, ELLER, IKKE).
417 *
418 * @param string $operator The possible operator to find in the internal operator array.
419 * @return string If found, the SQL operator for the localized input operator.
420 * @access private
421 * @todo Define visibility
422 */
423 public function get_operator($operator) {
424 $operator = trim($operator);
425 $op_array = $this->operator_translate_table;
426 if ($this->operator_translate_table_caseinsensitive) {
427 // case-conversion is charset insensitive, but it doesn't spoil
428 // anything if input string AND operator table is already converted
429 $operator = strtolower($operator);
430 }
431 foreach ($op_array as $key => $val) {
432 $item = $op_array[$key][0];
433 if ($this->operator_translate_table_caseinsensitive) {
434 // See note above.
435 $item = strtolower($item);
436 }
437 if ($operator == $item) {
438 return $op_array[$key][1];
439 }
440 }
441 }
442
443 /**
444 * Counts the results and sets the result in $this->res_count
445 *
446 * @return boolean TRUE, if $this->query was found
447 * @todo Define visibility
448 */
449 public function count_query() {
450 if (is_array($this->queryParts)) {
451 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($this->queryParts['SELECT'], $this->queryParts['FROM'], $this->queryParts['WHERE'], $this->queryParts['GROUPBY']);
452 $this->res_count = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
453 return TRUE;
454 }
455 }
456
457 /**
458 * Executes the search, sets result pointer in $this->result
459 *
460 * @return boolean TRUE, if $this->query was set and query performed
461 * @todo Define visibility
462 */
463 public function execute_query() {
464 if (is_array($this->queryParts)) {
465 $this->result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($this->queryParts);
466 return TRUE;
467 }
468 }
469
470 /**
471 * Returns URL-parameters with the current search words.
472 * Used when linking to result pages so that search words can be highlighted.
473 *
474 * @return string URL-parameters with the searchwords
475 * @todo Define visibility
476 */
477 public function get_searchwords() {
478 $SWORD_PARAMS = '';
479 if (is_array($this->sword_array)) {
480 foreach ($this->sword_array as $key => $val) {
481 $SWORD_PARAMS .= '&sword_list[]=' . rawurlencode($val['sword']);
482 }
483 }
484 return $SWORD_PARAMS;
485 }
486
487 /**
488 * Returns an array with the search words in
489 *
490 * @return array IF the internal sword_array contained search words it will return these, otherwise "void
491 * @todo Define visibility
492 */
493 public function get_searchwordsArray() {
494 if (is_array($this->sword_array)) {
495 foreach ($this->sword_array as $key => $val) {
496 $swords[] = $val['sword'];
497 }
498 }
499 return $swords;
500 }
501
502 }
503
504
505 ?>