MDL-10680:
[moodle-linuxchix.git] / search / querylib.php
blob2db68c1fcf349b92964374df9b03df755ca36285
1 <?php
2 require_once("{$CFG->dirroot}/search/Zend/Search/Lucene.php");
4 define('DEFAULT_POPUP_SETTINGS', "\"menubar=0,location=0,scrollbars,resizable,width=600,height=450\"");
6 /**
7 * a class that represents a single result record of the search engine
8 */
9 class SearchResult {
10 public $url,
11 $title,
12 $doctype,
13 $author,
14 $score,
15 $number;
16 } //SearchResult
19 //split this into Cache class and extend to SearchCache?
20 class SearchCache {
21 private $mode,
22 $valid;
24 // foresees other caching locations
25 public function __construct($mode = 'session') {
26 $accepted_modes = array('session');
28 if (in_array($mode, $accepted_modes)) {
29 $this->mode = $mode;
30 } else {
31 $this->mode = 'session';
32 } //else
34 $this->valid = true;
35 } //constructor
37 /**
38 * returns the search cache status
39 * @return boolean
41 public function can_cache() {
42 return $this->valid;
43 } //can_cache
45 /**
49 public function cache($id = false, $object = false) {
50 //see if there was a previous query
51 $last_term = $this->fetch('search_last_term');
53 //if this query is different from the last, clear out the last one
54 if ($id != false and $last_term != $id) {
55 $this->clear($last_term);
56 } //if
58 //store the new query if id and object are passed in
59 if ($object and $id) {
60 $this->store('search_last_term', $id);
61 $this->store($id, $object);
62 return true;
63 //otherwise return the stored results
65 else if ($id and $this->exists($id)) {
66 return $this->fetch($id);
67 } //else
68 } //cache
70 /**
71 * do key exist in cache ?
72 * @param id the object key
73 * @return boolean
75 private function exists($id) {
76 switch ($this->mode) {
77 case 'session' :
78 return isset($_SESSION[$id]);
79 } //switch
80 } //exists
82 /**
83 * clears a cached object in cache
84 * @param the object key to clear
85 * @return void
87 private function clear($id) {
88 switch ($this->mode) {
89 case 'session' :
90 unset($_SESSION[$id]);
91 session_unregister($id);
92 return;
93 } //switch
94 } //clear
96 /**
97 * fetches a cached object
98 * @param id the object identifier
99 * @return the object cached
101 private function fetch($id) {
102 switch ($this->mode) {
103 case 'session' :
104 return ($this->exists($id)) ? unserialize($_SESSION[$id]) : false;
105 } //switch
106 } //fetch
109 * put an object in cache
110 * @param id the key for that object
111 * @param object the object to cache as a serialized value
112 * @return void
114 private function store($id, $object) {
115 switch ($this->mode) {
116 case 'session' :
117 $_SESSION[$id] = serialize($object);
118 return;
119 } //switch
120 } //store
121 } //SearchCache
124 * Represents a single query with results
127 class SearchQuery {
128 private $index,
129 $term,
130 $pagenumber,
131 $cache,
132 $validquery,
133 $validindex,
134 $results,
135 $results_per_page,
136 $total_results;
139 * constructor records query parameters
142 public function __construct($term = '', $page = 1, $results_per_page = 10, $cache = false) {
143 global $CFG;
145 $this->term = $term;
146 $this->pagenumber = $page;
147 $this->cache = $cache;
148 $this->validquery = true;
149 $this->validindex = true;
150 $this->results_per_page = $results_per_page;
152 $index_path = SEARCH_INDEX_PATH;
154 try {
155 $this->index = new Zend_Search_Lucene($index_path, false);
156 } catch(Exception $e) {
157 $this->validindex = false;
158 return;
159 } //catch
161 if (empty($this->term)) {
162 $this->validquery = false;
163 } else {
164 $this->set_query($this->term);
165 } //else
166 } //constructor
169 * determines state of query object depending on query entry and
170 * tries to lauch search if all is OK
171 * @return void (this is only a state changing trigger).
173 public function set_query($term = '') {
174 if (!empty($term)) {
175 $this->term = $term;
176 } //if
178 if (empty($this->term)) {
179 $this->validquery = false;
181 else {
182 $this->validquery = true;
183 } //else
185 if ($this->validquery and $this->validindex) {
186 $this->results = $this->get_results();
188 else {
189 $this->results = array();
190 } //else
191 } //set_query
194 * accessor to the result table.
195 * @return an array of result records
197 public function results() {
198 return $this->results;
199 } //results
202 * do the effective collection of results
205 private function process_results($all=false) {
206 global $USER;
208 $term = strtolower($this->term);
210 //experimental - return more results
211 $strip_arr = array('author:', 'title:', '+', '-', 'doctype:');
212 $stripped_term = str_replace($strip_arr, '', $term);
214 $hits = $this->index->find($term." title:".$stripped_term." author:".$stripped_term);
215 //--
217 $hitcount = count($hits);
218 $this->total_results = $hitcount;
220 if ($hitcount == 0) return array();
222 $totalpages = ceil($hitcount/$this->results_per_page);
224 if (!$all) {
225 if ($hitcount < $this->results_per_page) {
226 $this->pagenumber = 1;
228 else if ($this->pagenumber > $totalpages) {
229 $this->pagenumber = $totalpages;
230 } //if
232 $start = ($this->pagenumber - 1) * $this->results_per_page;
233 $end = $start + $this->results_per_page;
235 if ($end > $hitcount) {
236 $end = $hitcount;
237 } //if
239 else {
240 $start = 0;
241 $end = $hitcount;
242 } //else
244 $resultdoc = new SearchResult();
245 $resultdocs = array();
247 for ($i = $start; $i < $end; $i++) {
248 $hit = $hits[$i];
250 //check permissions on each result
251 if ($this->can_display($USER, $hit->docid, $hit->doctype, $hit->course_id, $hit->group_id, $hit->path, $hit->itemtype, $hit->context_id )) {
252 $resultdoc->number = $i;
253 $resultdoc->url = $hit->url;
254 $resultdoc->title = $hit->title;
255 $resultdoc->score = $hit->score;
256 $resultdoc->doctype = $hit->doctype;
257 $resultdoc->author = $hit->author;
259 //and store it
260 $resultdocs[] = clone($resultdoc);
261 } //if
262 else{
263 // lowers total_results one unit
264 $this->total_results--;
266 } //foreach
268 return $resultdocs;
269 } //process_results
272 * get results of a search query using a caching strategy if available
273 * @return the result documents as an array of search objects
275 private function get_results() {
276 $cache = new SearchCache();
278 if ($this->cache and $cache->can_cache()) {
279 if (!($resultdocs = $cache->cache($this->term))) {
280 $resultdocs = $this->process_results();
281 //cache the results so we don't have to compute this on every page-load
282 $cache->cache($this->term, $resultdocs);
283 //print "Using new results.";
285 else {
286 //There was something in the cache, so we're using that to save time
287 //print "Using cached results.";
288 } //else
290 else {
291 //no caching :(
292 //print "Caching disabled!";
293 $resultdocs = $this->process_results();
294 } //else
296 return $resultdocs;
297 } //get_results
300 * constructs the results paging links on results.
301 * @return string the results paging links
303 public function page_numbers() {
304 $pages = $this->total_pages();
305 $query = htmlentities($this->term);
306 $page = $this->pagenumber;
307 $next = get_string('next', 'search');
308 $back = get_string('back', 'search');
310 $ret = "<div align='center' id='search_page_links'>";
312 //Back is disabled if we're on page 1
313 if ($page > 1) {
314 $ret .= "<a href='query.php?query_string={$query}&page=".($page-1)."'>&lt; {$back}</a>&nbsp;";
315 } else {
316 $ret .= "&lt; {$back}&nbsp;";
317 } //else
319 //don't <a href> the current page
320 for ($i = 1; $i <= $pages; $i++) {
321 if ($page == $i) {
322 $ret .= "($i)&nbsp;";
323 } else {
324 $ret .= "<a href='query.php?query_string={$query}&page={$i}'>{$i}</a>&nbsp;";
325 } //else
326 } //for
328 //Next disabled if we're on the last page
329 if ($page < $pages) {
330 $ret .= "<a href='query.php?query_string={$query}&page=".($page+1)."'>{$next} &gt;</a>&nbsp;";
331 } else {
332 $ret .= "{$next} &gt;&nbsp;";
333 } //else
335 $ret .= "</div>";
337 //shorten really long page lists, to stop table distorting width-ways
338 if (strlen($ret) > 70) {
339 $start = 4;
340 $end = $page - 5;
341 $ret = preg_replace("/<a\D+\d+\D+>$start<\/a>.*?<a\D+\d+\D+>$end<\/a>/", '...', $ret);
343 $start = $page + 5;
344 $end = $pages - 3;
345 $ret = preg_replace("/<a\D+\d+\D+>$start<\/a>.*?<a\D+\d+\D+>$end<\/a>/", '...', $ret);
346 } //if
348 return $ret;
349 } //page_numbers
352 * can the user see this result ?
353 * @param user a reference upon the user to be checked for access
354 * @param this_id the item identifier
355 * @param doctype the search document type. MAtches the module or block or
356 * extra search source definition
357 * @param course_id the course reference of the searched result
358 * @param group_id the group identity attached to the found resource
359 * @param path the path that routes to the local lib.php of the searched
360 * surrounding object fot that document
361 * @param item_type a subclassing information for complex module data models
362 * // TODO reorder parameters more consistently
364 private function can_display(&$user, $this_id, $doctype, $course_id, $group_id, $path, $item_type, $context_id) {
365 global $CFG;
368 * course related checks
370 // admins can see everything, anyway.
371 if (isadmin()){
372 return true;
375 // first check course compatibility against user : enrolled users to that course can see.
376 $myCourses = get_my_courses($user->id);
377 $unenroled = !in_array($course_id, array_keys($myCourses));
379 // if guests are allowed, logged guest can see
380 $isallowedguest = (isguest()) ? get_field('course', 'guest', 'id', $course_id) : false ;
382 if ($unenroled && !$isallowedguest){
383 return false;
386 // if user is enrolled or is allowed user and course is hidden, can he see it ?
387 $visibility = get_field('course', 'visible', 'id', $course_id);
388 if ($visibility <= 0){
389 if (!has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))){
390 return false;
395 * prerecorded capabilities
397 // get context caching information and tries to discard unwanted records here
401 * final checks
403 // then give back indexing data to the module for local check
404 include_once "{$CFG->dirroot}/search/documents/{$doctype}_document.php";
405 $access_check_function = "{$doctype}_check_text_access";
407 if (function_exists($access_check_function)){
408 $modulecheck = $access_check_function($path, $item_type, $this_id, $user, $group_id, $context_id);
409 // echo "module said $modulecheck for item $doctype/$item_type/$this_id";
410 return($modulecheck);
413 return true;
414 } //can_display
416 public function count() {
417 return $this->total_results;
418 } //count
420 public function is_valid() {
421 return ($this->validquery and $this->validindex);
422 } //is_valid
424 public function is_valid_query() {
425 return $this->validquery;
426 } //is_valid_query
428 public function is_valid_index() {
429 return $this->validindex;
430 } //is_valid_index
432 public function total_pages() {
433 return ceil($this->count()/$this->results_per_page);
434 } //pages
436 public function get_pagenumber() {
437 return $this->pagenumber;
438 } //get_pagenumber
440 public function get_results_per_page() {
441 return $this->results_per_page;
442 } //get_results_per_page
443 } //SearchQuery