Fixed couple bugs in query, and improved logic of querylib.
[moodle.git] / search / querylib.php
1 <?php
2   require_once("$CFG->dirroot/search/Zend/Search/Lucene.php");
4   class SearchResult {
5     public  $url,
6             $title,            
7             $doctype,
8             $author,
9             $score,
10             $number;
11   } //SearchResult
14   //split this into Cache class and extend to SearchCache?
15   class SearchCache {
16     private $mode,
17             $valid;
18                 
19     public function __construct($mode='session') {
20       $accepted_modes = array('session');
21       
22       if (in_array($mode, $accepted_modes)) {
23         $this->mode = $mode;
24       } else {
25         $this->mode = 'session';
26       } //else
27       
28       $this->valid = true;
29     } //constructor
30     
31     public function can_cache() {
32       return $this->valid;
33     } //can_cache
34     
35     public function cache($id=false, $object=false) {
36       //see if there was a previous query
37       $last_term = $this->fetch('search_last_term');
38       
39       //if this query is different from the last, clear out the last one
40       if ($id != false and $last_term != $id) {
41         $this->clear($last_term);
42       } //if
43       
44       //store the new query if id and object are passed in
45       if ($object and $id) {
46         $this->store('search_last_term', $id);
47         $this->store($id, $object);
48         return true;
49       //otherwise return the stored results
50       } else if ($id and $this->exists($id)) {        
51         return $this->fetch($id);
52       } //else
53     } //cache
54     
55     private function exists($id) {
56       switch ($this->mode) {
57         case 'session' :
58           return isset($_SESSION[$id]);
59       } //switch
60     } //exists
61         
62     private function clear($id) {
63       switch ($this->mode) {
64         case 'session' :
65           unset($_SESSION[$id]);
66           session_unregister($id);
67           return;
68       } //switch
69     } //clear    
70         
71     private function fetch($id) {
72       switch ($this->mode) {
73         case 'session' :
74           return ($this->exists($id)) ? unserialize($_SESSION[$id]) : false;
75       } //switch
76     } //fetch
77     
78     private function store($id, $object) {
79       switch ($this->mode) {
80         case 'session' :
81           $_SESSION[$id] = serialize($object);
82           return;
83       } //switch
84     } //store        
85   } //SearchCache
86   
87   
88   class SearchQuery {
89     private $index,
90             $term,
91             $pagenumber,            
92             $cache,
93             $validquery,
94             $validindex,            
95             $results,
96             $results_per_page,
97             $total_results;
98     
99     public function __construct($term='', $page=1, $results_per_page=10, $cache=false) {
100       global $CFG;
101       
102       $this->term       = $term;
103       $this->pagenumber = $page;
104       $this->cache      = $cache;
105       $this->validquery = true;
106       $this->validindex = true;      
107       $this->results_per_page = $results_per_page;      
108       
109       $index_path = SEARCH_INDEX_PATH;
110       
111       try {
112         $this->index = new Zend_Search_Lucene($index_path, false);
113       } catch(Exception $e) {    
114         $this->validindex = false;        
115         return;
116       } //catch
117             
118       if (empty($this->term)) {
119         $this->validquery = false;
120       } else {
121         $this->set_query($this->term);
122       } //else      
123     } //constructor
124     
125     public function set_query($term='') {            
126       if (!empty($term)) {
127         $this->term = $term;
128       } //if
129       
130       if (empty($this->term)) {
131         $this->validquery = false;
132       } else {
133         $this->validquery = true;
134       } //else
135       
136       if ($this->validquery and $this->validindex) {
137         $this->results = $this->get_results();
138       } else {
139         $this->results = array();
140       } //else      
141     } //set_query
142     
143     public function results() {
144       return $this->results;
145     } //results
146         
147     private function process_results($all=false) {
148       global $USER;
150       $term = strtolower($this->term);         
151       
152       //experimental - return more results
153       $strip_arr = array('author:', 'title:', '+', '-', 'doctype:');   
154       $stripped_term = str_replace($strip_arr, '', $term);
155       
156       $hits = $this->index->find($term." title:".$stripped_term." author:".$stripped_term);
157       //--
158       
159       $hitcount = count($hits);
160       $this->total_results = $hitcount; 
161             
162       if ($hitcount == 0) return array();
163       
164       $totalpages = ceil($hitcount/$this->results_per_page);
165       
166       if (!$all) {
167         if ($hitcount < $this->results_per_page) {
168           $this->pagenumber = 1;
169         } else if ($this->pagenumber > $totalpages) {
170           $this->pagenumber  =$totalpages;
171         } //if      
172         
173         $start = ($this->pagenumber - 1) * $this->results_per_page;
174         $end = $start + $this->results_per_page;
175         
176         if ($end > $hitcount) {
177           $end = $hitcount;
178         } //if        
179       } else {
180         $start = 0;
181         $end = $hitcount;
182       } //else
183       
184       $resultdoc  = new SearchResult();
185       $resultdocs = array();      
186                   
187       for ($i = $start; $i < $end; $i++) {
188         $hit = $hits[$i];
189                            
190         //check permissions on each result
191         if ($this->can_display($USER, $hit->id, $hit->doctype, $hit->course_id, $hit->group_id)) {
192           $resultdoc->number  = $i;
193           $resultdoc->url     = $hit->url;
194           $resultdoc->title   = $hit->title;
195           $resultdoc->score   = $hit->score;
196           $resultdoc->doctype = $hit->doctype;
197           $resultdoc->author  = $hit->author;
198         
199           //and store it
200           $resultdocs[] = clone($resultdoc);          
201         } //if
202       } //foreach
203             
204       return $resultdocs;
205     } //process_results
206               
207     private function get_results() {
208       $cache = new SearchCache();
209       
210       if ($this->cache and $cache->can_cache()) {        
211         if (!($resultdocs = $cache->cache($this->term))) {
212           $resultdocs = $this->process_results();
213           //cache the results so we don't have to compute this on every page-load
214           $cache->cache($this->term, $resultdocs);          
215           //print "Using new results.";
216         } else {            
217           //There was something in the cache, so we're using that to save time
218           //print "Using cached results.";
219         } //else                
220       } else {
221         //no caching :(
222         //print "Caching disabled!";
223         $resultdocs = $this->process_results();
224       } //else
226       return $resultdocs;
227     } //get_results  
228     
229     public function page_numbers() {
230       $pages  = $this->total_pages();
231       $query  = htmlentities($this->term);     
232       $page   = $this->pagenumber;
233       $next   = "Next";
234       $back   = "Back";
235       
236       $ret = "<div align='center' id='search_page_links'>";    
237       
238       //Back is disabled if we're on page 1
239       if ($page > 1) {
240         $ret .= "<a href='query.php?query_string=$query&page=".($page-1)."'>< $back</a>&nbsp;";
241       } else {
242         $ret .= "< $back&nbsp;";
243       } //else
244       
245       //don't <a href> the current page
246       for ($i = 1; $i <= $pages; $i++) {
247         if ($page == $i) {
248           $ret .= "[$i]&nbsp;";
249         } else {
250           $ret .= "<a href='query.php?query_string=$query&page=$i'>$i</a>&nbsp;";
251         } //else
252       } //for
253       
254       //Next disabled if we're on the last page
255       if ($page < $pages) {      
256         $ret .= "<a href='query.php?query_string=$query&page=".($page+1)."'>$next ></a>&nbsp;";
257       } else {
258         $ret .= "$next >&nbsp;";
259       } //else
260       
261       $ret .= "</div>";    
262       
263       //shorten really long page lists, to stop table distorting width-ways
264       if (strlen($ret) > 70) {
265         $start = 4;
266         $end = $page - 5;     
267         $ret = preg_replace("/<a\D+\d+\D+>$start<\/a>.*?<a\D+\d+\D+>$end<\/a>/", '...', $ret);
268   
269         $start = $page + 5;
270         $end = $pages - 3;      
271         $ret = preg_replace("/<a\D+\d+\D+>$start<\/a>.*?<a\D+\d+\D+>$end<\/a>/", '...', $ret);
272       } //if
273      
274       return $ret;
275     } //page_numbers    
277     //can the user see this result?          
278     private function can_display(&$user, $this_id, $doctype, $course_id, $group_id) {
279       //this function should return true/false depending on
280       //whether or not a user can see this resource
281       //..
282       //if one of you nice moodlers see this, feel free to
283       //implement it for me .. :-P      
284       return true;
285     } //can_display
286     
287     public function count() {
288       return $this->total_results;
289     } //count
290     
291     public function is_valid() {
292       return ($this->validquery and $this->validindex);
293     } //is_valid
294     
295     public function is_valid_query() {
296       return $this->validquery;
297     } //is_valid_query
299     public function is_valid_index() {
300       return $this->validindex;
301     } //is_valid_index
302     
303     public function total_pages() {
304       return ceil($this->count()/$this->results_per_page);
305     } //pages    
306     
307     public function get_pagenumber() {
308       return $this->pagenumber;
309     } //get_pagenumber
310     
311     public function get_results_per_page() {
312       return $this->results_per_page;
313     } //get_results_per_page        
314   } //SearchQuery
316 ?>