Search Object

Item 8 of 9

» Download this file

1   <?
2   
3   /*====================================================================================================================
4   
5     Title:        class_SearchObject.inc.php
6     Description:  A class for handling database searches from a free text query.  Can search multiple fields on multiple
7                   tables (if desired).  Queries are simple and structured like natural language, i.e.:
8                   one AND two OR three AND for IN general forum
9   
10    Publisher:    act e-learning (www.actelearning.com)
11    Creator:      Dan Pupius (www.pupius.co.uk)
12    Date:         2003-09-11
13    Rights:       Copyright (c)2003 Act E-learning
14  
15  
16    Example use: —  —  —  —  —  — $query = "\"flying sheep\" OR \"magic books\" frogs IN main forum BY dan ORDERBY date";
17      $search_sql = new SearchQuery($query);
18      $db->query($search_sql->sql);
19      .... rest of your sql code ....
20  
21  
22      the returned sql is:
23  	SELECT f.title,m.author,m.posttype,m.id,m.title,m.body,m.locked,m.status,m.created
24  	FROM messages as m, forum as f
25  	WHERE message.forum = forum.id AND
26  		f.title LIKE 'main forum' AND
27  		m.author LIKE 'dan' AND (
28  			(
29  				(
30  					m.title LIKE '%flying sheep%' OR m.body LIKE '%flying sheep%'
31  				)
32  			) OR (
33  				(
34  					m.title LIKE '%magic books%' OR m.body LIKE '%magic books%'
35  				) AND (
36  					m.title LIKE '%frogs%' OR m.body LIKE '%frogs%'
37  				)
38  			)
39  		)
40  	ORDER BY date
41  
42  
43      As you can see the AND operator is default while the OR operator takes prescidence.  This means that queries are
44      more akin to natural language.  Brackets are not supported so "(a OR b) AND c" would need to be written as
45      "a AND c OR b AND c", for programmers this is less intuative, but it's easier for normal people... I promise ;)
46  
47  
48  
49      NOTE: you must configure the $config array in the SearchQuery object to work with the fields and tables in your
50            database.
51  
52            The main search occurs in the so called "normalfields" while the "specialfields" lock down the search, with
53            the above example "IN main forum" means the search will only return rows where the field corresponding to IN
54            equals "main forum", likewise "BY dan" means the rows must have dan in the field corresponding to "BY" in the
55            specialfields array.
56  
57            Obviously if you want to use the SearchObject to perform a number of different type of searches you can rewrite
58            the config array on the fly instead of hard coding in this file.
59  
60            Special mention about the ORDERBY specialfield which has it's behaviour hard coded and sets the "ORDER BY" field
61            in the SQL.
62  
63  
64  ======================================================================================================================*/
65  
66  
67  class SearchQuery {
68  
69  	//SearchQuery config
70  	var $config = array(	"normalfields" => array("name","url","description","keyword"),		//fields to do the normal text search in
71  				"specialfields"=> array("IN" => "project",				//optional fields that must be satisfied (i.e. 'IN xxx' would mean add an AND project='xxx')
72  							"BY" => "who",
73  							"IS" => "type",
74  							"HASID" => "id"),				//ORDERBY is not a specialfield as such as its behaviour is hard coded
75  				"extrafields"=>   array("size","length","screen","uploaded","parent"),	//extra fields that are returned by the query but not searched in
76  
77  				"fromdatabase"=> "asset",						//the FROM clause in the SQL, can refer to multiple tables (but if you do, then fix the field names above)
78  				"extrawhere"=>	 ""							//prepended to WHERE clause
79  		);
80  
81  
82  	var $rawquery;
83  	var $fixedquery;
84  	var $processedquery;
85  
86  	//all the specialterms MUST be satisified AND at least one of the normalterms (if there are any)
87  	//for a normal term to be satisified, all their ->values must be satisified.
88  	var $normalterms = array();
89  	var $specialterms = array();
90  
91  	var $sql;
92  
93  
94  	function searchQuery($query) {
95  
96  		$this->rawquery = $query;
97  
98  		//strip out illegal characters from the search
99  		$this->fixedquery = preg_replace("/([^\w\s^\"\*]+)/","",$query);
100 
101 
102 		//remove any "AND OR", "AND AND", "OR AND" or "OR OR"s and leave the first occurance instead
103 		$this->fixedquery = preg_replace("/(OR|AND)\s+(OR|AND)/","\\1",$this->fixedquery);
104 
105 		//process the query into this object, ready for processing
106 		//===================================================================
107 
108 		//remove special search terms from the end of our query and store them in a seperate array
109 		$this->processedquery = preg_replace("/(".implode_keys("|",$this->config["specialfields"])."|ORDERBY)\s+(.*)/e","\$this->add_special_term(\"\\1\",\"\\2\")",$this->fixedquery);
110 		$this->processedquery = trim($this->processedquery);
111 
112 		$this->processedquery = preg_replace("/\"([\w\*]*)(\s*)([\w\*]*)(\s*)([\w\*]*)(\s*)([\w\*]*)(\s*)([\w\*]*)\"/","\\1___\\3___\\5___\\6___\\7___\\9",$this->processedquery);
113 
114 
115 		//convert AND and OR into & and | (also remove spaces)
116 		$this->processedquery = preg_replace("/\s+AND\s+/","&",$this->processedquery);
117 		$this->processedquery = preg_replace("/\s+OR\s+/","|",$this->processedquery);
118 
119 		//make & the default operator
120 		$this->processedquery = preg_replace("/([\w\*]*)\s+([\w\*]*)/","\\1&\\2",$this->processedquery);
121 
122 		$this->processedquery = trim($this->processedquery);
123 
124 		if($this->processedquery!="") {
125 			//explode the search string and parse into a collection of objects.  All terms in an object must
126 			//be satisified, and one object must be satisified for the query to match against a string
127 			$normalterms = explode("|",$this->processedquery);
128 			for($i=0;$i<count($normalterms);$i++) {
129 				$obj = new __SearchQuery_NormalTerm;
130 				$parts = explode("&",$normalterms[$i]);
131 				foreach($parts as $part) $obj->add_term(trim(str_replace("___"," ",$part)));
132 				$this->add_normal_term($obj);
133 			}
134 		}
135 
136 		$this->sql = $this->generate_sql();
137 	}
138 
139 
140 	//recursivly add special search terms to the array
141 	function add_special_term($name,$value) {
142 		$fixedvalue = preg_replace("/(".implode_keys("|",$this->config["specialfields"])."|ORDERBY)\s+(.*)/e","\$this->add_special_term(\"\\1\",\"\\2\")",$value);
143 		array_push($this->specialterms,new __SearchQuery_SpecialTerm($name,$fixedvalue));
144 	}
145 
146 	function add_normal_term($term) {
147 		array_push($this->normalterms,$term);
148 	}
149 
150 
151 	function get_special_term_value($name) {
152 		foreach($this->specialterms as $term) if($name==$term->name) return $term->value;
153 		return false;
154 	}
155 
156 
157 	function generate_sql() {
158 
159 		$sql = "\nSELECT " . implode(",",array_merge($this->config["specialfields"],$this->config["normalfields"],$this->config["extrafields"])) . " \n" .
160 		       "FROM " . $this->config["fromdatabase"] . " \n".
161 		       "WHERE " . $this->config["extrawhere"];
162 
163 		$whereparts = array();
164 
165 		//create parts from special fields that are required!
166 		foreach($this->config["specialfields"] as $term => $dbfield) {
167 			if($this->get_special_term_value($term)) {
168 				array_push($whereparts, "\n\t$dbfield LIKE '".trim($this->get_special_term_value($term))."'");
169 			}
170 		}
171 
172 
173 		//now set up the normal terms OR and ANDs
174 		$normalparts = array();
175 		foreach($this->normalterms as $term) {
176 			$thispart = array();
177 			foreach($term->values as $val) {
178 				$micropart = array();
179 				foreach($this->config["normalfields"] as $field) {
180 					array_push($micropart, "$field LIKE '%$val%'");
181 				}
182 
183 				array_push($thispart,"(\n\t\t\t\t".implode(" OR ",$micropart)."\n\t\t\t)");
184 			}
185 
186 			array_push($normalparts,"(\n\t\t\t".implode(" AND ",$thispart)."\n\t\t)");
187 		}
188 
189 		if(count($normalparts)>0) array_push($whereparts,"(\n\t\t".implode(" OR ",$normalparts)."\n\t)");
190 
191 		if(count($whereparts)>0) $sql .= implode(" AND ",$whereparts);
192 
193 
194 		if($this->get_special_term_value("ORDERBY")) $sql .= "\nORDER BY " . $this->get_special_term_value("ORDERBY");
195 
196 		$sql = str_replace("*","%",$sql);
197 
198 		return $sql;
199 	}
200 
201 }
202 
203 
204 
205 
206 
207 class __SearchQuery_SpecialTerm {
208 	var $name;
209 	var $value;
210 
211 	function __SearchQuery_SpecialTerm($name,$value) {
212 		$this->name = $name;
213 		$this->value = $value;
214 	}
215 }
216 
217 class __SearchQuery_NormalTerm {
218 	var $values = array();
219 
220 	function __SearchQuery_NormalTerm() {
221 	}
222 
223 	function add_term($value) {
224 		array_push($this->values,$value);
225 	}
226 }
227 
228 
229 
230 function implode_keys($glue, $array) {
231         $output = array();
232         foreach($array as $key => $item) $output[] = $key;
233 
234         return implode($glue, $output);
235 }
236 
237 ?>
238 

Back to top