3 * FusionForge search engine
5 * Copyright 1999-2001, VA Linux Systems, Inc
6 * Copyright 2004, Guillaume Smet/Open Wide
7 * Copyright 2009, Roland Mas
8 * Copyright 2010-2011, Franck Villaume - Capgemini
10 * This file is part of FusionForge. FusionForge is free software;
11 * you can redistribute it and/or modify it under the terms of the
12 * GNU General Public License as published by the Free Software
13 * Foundation; either version 2 of the Licence, or (at your option)
16 * FusionForge is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 class SearchQuery extends Error {
28 * the operator between each part of the query. Can be AND or OR.
30 * @var string $operator
34 * Number of rows per page
36 * @var int $rowsPerPage
40 * Number of rows we will display on the page
46 * Number of rows returned by the query
48 * @var int $rowsTotalCount
50 var $rowsTotalCount = 0;
60 * @var resource $result
64 * When search by id is enabled, the id to search for
68 var $searchId = false;
70 * if we want to search for all the words or if only one is sufficient
72 * @var boolean $isExact
76 * sections to search in
78 * @var array $sections
80 var $sections = SEARCH__ALL_SECTIONS;
84 var $phrases = array();
86 // Something that's hopefully not going to end up in real data
87 var $field_separator = ' ioM0Thu6_fieldseparator_kaeph9Ee ';
92 * @param string $words words we are searching for
93 * @param int $offset offset
94 * @param boolean $isExact if we want to search for all the words or if only one is sufficient
95 * @param int $rowsPerPage number of rows per page
97 function SearchQuery($words, $offset, $isExact, $rowsPerPage = SEARCH__DEFAULT_ROWS_PER_PAGE) {
98 $this->field_separator = ' ioM0Thu6_fieldseparator_kaeph9Ee ';
100 $this->cleanSearchWords($words);
101 //We manual escap because every Query in Search escap parameters
102 $words = addslashes($words);
103 if (is_array ($this->words)){
104 $this->words = array_map ('addslashes',$this->words);
106 $this->words = array();
108 if (is_array ($this->phrases)){
109 $this->phrases = array_map ('addslashes',$this->phrases);
111 $this->phrases = array();
113 $this->rowsPerPage = $rowsPerPage;
114 $this->offset = $offset;
115 $this->isExact = $isExact;
116 $this->operator = $this->getOperator();
120 * cleanSearchWords - clean the words we are searching for
122 * @param string $words words we are searching for
124 function cleanSearchWords($words) {
125 $words = trim($words);
127 $this->setError(_('Error: criteria not specified'));
130 if(is_numeric($words) && $this->implementsSearchById()) {
131 $this->searchId = (int) $words;
133 $words = preg_replace("/[ \t]+/", ' ', $words);
134 if(strlen($words) < 3) {
135 $this->setError(_('Error: search query too short'));
138 $words = htmlspecialchars($words);
139 $words = strtr($words, array('%' => '\%', '_' => '\_'));
142 foreach(explode(' ', quotemeta($words)) as $word) {
144 if(substr($word, -1) == "'") {
145 $word = substr($word, 0, -1);
147 $phrase .= ' '.$word;
148 $this->phrases[] = $phrase;
150 $phrase .= ' '.$word;
153 if(substr($word, 0, 1) == "'") {
154 $word = substr($word, 1);
156 if(substr($word, -1) == "'") {
157 // This is a special case where the phrase is just one word
158 $word = substr($word, 0, -1);
160 $this->words[] = $word;
165 $this->words[] = $word;
173 * executeQuery - execute the SQL query to get the results
175 function executeQuery() {
177 if($this->searchId) {
178 $qpa = $this->getSearchByIdQuery();
180 $qpa = $this->getQuery();
183 $this->result = db_query_qpa (
185 $this->rowsPerPage + 1,
189 $this->rowsTotalCount = db_numrows($this->result);
190 $this->rowsCount = min($this->rowsPerPage, $this->rowsTotalCount);
194 * getQuery - returns the query built to get the search results
195 * This is an abstract method. It _MUST_ be implemented in children classes.
197 * @return array query+params array
199 function getQuery() {
203 function addMatchCondition($qpa, $fieldName) {
205 if(!count($this->phrases)) {
206 $qpa = db_construct_qpa ($qpa, 'TRUE') ;
211 foreach ($this->phrases as $p) {
212 $regexs[] = strtolower (preg_replace ("/\s+/", "\s+", $p));
215 for ($i = 0; $i < count ($regexs); $i++) {
217 $qpa = db_construct_qpa ($qpa,
220 $qpa = db_construct_qpa ($qpa,
222 array ($regexs[$i])) ;
227 function addIlikeCondition($qpa, $fieldName) {
228 $wordArgs = array_map ('strtolower',
229 array_merge($this->words, $this->phrases));
231 for ($i = 0; $i < count ($wordArgs); $i++) {
233 $qpa = db_construct_qpa ($qpa,
236 $qpa = db_construct_qpa ($qpa,
237 'lower ('.$fieldName.') LIKE $1',
238 array ('%'.$wordArgs[$i].'%')) ;
244 * getOperator - get the operator we have to use in ILIKE condition
246 * @return string AND if it is an exact search, OR otherwise
248 function getOperator() {
257 * implementsSearchById - check if the current object implements the search by id feature by having a getSearchByIdQuery method
259 * @return boolean true if our object implements search by id, false otherwise.
261 function implementsSearchById() {
262 return method_exists($this, 'getSearchByIdQuery');
266 * getResult - returns the result set
268 * @return resource result set
270 function & getResult() {
271 return $this->result;
275 * getRowsCount - returns number of rows for the current page
277 * @return int rows count for the current page
279 function getRowsCount() {
280 return $this->rowsCount;
284 * getRowsTotalCount - returns total number of rows
286 * @return int rows count
288 function getRowsTotalCount() {
289 return $this->rowsTotalCount;
293 * getOffset - returns the offset
297 function getOffset() {
298 return $this->offset;
302 * getRowsPerPage - returns number of rows per page
304 * @return int number of rows per page
306 function getRowsPerPage() {
307 return $this->rowsPerPage;
311 * getWords - returns the array containing words we are searching for
313 * @return array words we are searching for
315 function getWords() {
320 * getPhrases - returns the array containing phrases we are searching for
322 * @return array phrases we are searching for
324 function getPhrases() {
325 return $this->phrases;
329 * setSections - set the sections list
331 * @param $sections mixed array of sections or SEARCH__ALL_SECTIONS
333 function setSections($sections) {
334 if(is_array($sections)) {
335 $this->sections = array_values($sections) ;
337 $this->sections = $sections;
342 * getFTIwords - get words formatted in order to be used in the FTI stored procedures
344 * @return string words we are searching for, separated by
346 function getFTIwords() {
347 $bits = $this->words;
348 foreach ($this->phrases as $p) {
349 $bits[] = '('.implode ('&', explode (' ', $p)).')';
351 if ($this->isExact) {
352 $query = implode('&', $bits);
354 $query = implode('|', $bits);
362 // c-file-style: "bsd"