MDL-21782 guest filed is not in course table any more
[moodle.git] / search / querylib.php
CommitLineData
d9e1bf24 1<?php
e08a6ee4 2/**
3319ef85 3* Global Search Engine for Moodle
4*
5* @package search
6* @category core
7* @subpackage search_engine
8* @author Michael Champanis (mchampan) [cynnical@gmail.com], Valery Fremaux [valery.fremaux@club-internet.fr] > 1.8
9* @date 2008/03/31
10* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
11*/
12
13/**
14* includes and requires
15*/
63c13a82 16require_once($CFG->dirroot.'/search/Zend/Search/Lucene.php');
2f338ab5 17
18define('DEFAULT_POPUP_SETTINGS', "\"menubar=0,location=0,scrollbars,resizable,width=600,height=450\"");
19
20/**
21* a class that represents a single result record of the search engine
e08a6ee4 22*/
2f338ab5 23class SearchResult {
24public $url,
25 $title,
26 $doctype,
27 $author,
28 $score,
3efa38a4 29 $number,
30 $courseid;
e08a6ee4 31}
2f338ab5 32
33
3319ef85 34/**
35* split this into Cache class and extend to SearchCache?
36*/
2f338ab5 37class SearchCache {
38private $mode,
39 $valid;
40
41 // foresees other caching locations
42 public function __construct($mode = 'session') {
43 $accepted_modes = array('session');
44
45 if (in_array($mode, $accepted_modes)) {
46 $this->mode = $mode;
47 } else {
48 $this->mode = 'session';
49 } //else
eef868d1 50
2f338ab5 51 $this->valid = true;
e08a6ee4 52 }
eef868d1 53
2f338ab5 54 /**
55 * returns the search cache status
56 * @return boolean
57 */
d9e1bf24 58 public function can_cache() {
2f338ab5 59 return $this->valid;
e08a6ee4 60 }
eef868d1 61
2f338ab5 62 /**
63 *
64 *
65 */
66 public function cache($id = false, $object = false) {
67 //see if there was a previous query
68 $last_term = $this->fetch('search_last_term');
69
70 //if this query is different from the last, clear out the last one
63c13a82 71 if ($id != false && $last_term != $id) {
2f338ab5 72 $this->clear($last_term);
e08a6ee4 73 }
eef868d1 74
2f338ab5 75 //store the new query if id and object are passed in
63c13a82 76 if ($object && $id) {
2f338ab5 77 $this->store('search_last_term', $id);
78 $this->store($id, $object);
79 return true;
80 //otherwise return the stored results
63c13a82 81 } else if ($id && $this->exists($id)) {
2f338ab5 82 return $this->fetch($id);
e08a6ee4
PS
83 }
84 }
eef868d1 85
2f338ab5 86 /**
87 * do key exist in cache ?
88 * @param id the object key
89 * @return boolean
90 */
d9e1bf24 91 private function exists($id) {
2f338ab5 92 switch ($this->mode) {
93 case 'session' :
94 return isset($_SESSION[$id]);
e08a6ee4
PS
95 }
96 }
eef868d1 97
2f338ab5 98 /**
99 * clears a cached object in cache
100 * @param the object key to clear
101 * @return void
102 */
d9e1bf24 103 private function clear($id) {
2f338ab5 104 switch ($this->mode) {
105 case 'session' :
106 unset($_SESSION[$id]);
107 session_unregister($id);
108 return;
e08a6ee4
PS
109 }
110 }
eef868d1 111
2f338ab5 112 /**
113 * fetches a cached object
114 * @param id the object identifier
115 * @return the object cached
116 */
d9e1bf24 117 private function fetch($id) {
2f338ab5 118 switch ($this->mode) {
119 case 'session' :
120 return ($this->exists($id)) ? unserialize($_SESSION[$id]) : false;
e08a6ee4
PS
121 }
122 }
eef868d1 123
2f338ab5 124 /**
125 * put an object in cache
126 * @param id the key for that object
127 * @param object the object to cache as a serialized value
128 * @return void
129 */
d9e1bf24 130 private function store($id, $object) {
2f338ab5 131 switch ($this->mode) {
132 case 'session' :
133 $_SESSION[$id] = serialize($object);
134 return;
3319ef85 135 }
e08a6ee4
PS
136 }
137}
eef868d1 138
2f338ab5 139/**
140* Represents a single query with results
141*
142*/
143class SearchQuery {
d9e1bf24 144 private $index,
145 $term,
eef868d1 146 $pagenumber,
d9e1bf24 147 $cache,
148 $validquery,
eef868d1 149 $validindex,
d9e1bf24 150 $results,
0d46c846 151 $results_per_page,
152 $total_results;
eef868d1 153
2f338ab5 154 /**
155 * constructor records query parameters
156 *
157 */
158 public function __construct($term = '', $page = 1, $results_per_page = 10, $cache = false) {
159 global $CFG;
eef868d1 160
2f338ab5 161 $this->term = $term;
162 $this->pagenumber = $page;
163 $this->cache = $cache;
164 $this->validquery = true;
165 $this->validindex = true;
166 $this->results_per_page = $results_per_page;
eef868d1 167
2f338ab5 168 $index_path = SEARCH_INDEX_PATH;
eef868d1 169
2f338ab5 170 try {
171 $this->index = new Zend_Search_Lucene($index_path, false);
172 } catch(Exception $e) {
173 $this->validindex = false;
174 return;
e08a6ee4 175 }
eef868d1 176
2f338ab5 177 if (empty($this->term)) {
178 $this->validquery = false;
179 } else {
180 $this->set_query($this->term);
e08a6ee4
PS
181 }
182 }
183
2f338ab5 184 /**
e08a6ee4 185 * determines state of query object depending on query entry and
2f338ab5 186 * tries to lauch search if all is OK
187 * @return void (this is only a state changing trigger).
188 */
189 public function set_query($term = '') {
190 if (!empty($term)) {
191 $this->term = $term;
3319ef85 192 }
eef868d1 193
2f338ab5 194 if (empty($this->term)) {
195 $this->validquery = false;
3319ef85 196 } else {
2f338ab5 197 $this->validquery = true;
3319ef85 198 }
eef868d1 199
2f338ab5 200 if ($this->validquery and $this->validindex) {
201 $this->results = $this->get_results();
3319ef85 202 } else {
2f338ab5 203 $this->results = array();
3319ef85 204 }
e08a6ee4 205 }
eef868d1 206
2f338ab5 207 /**
208 * accessor to the result table.
209 * @return an array of result records
210 */
d9e1bf24 211 public function results() {
2f338ab5 212 return $this->results;
3319ef85 213 }
eef868d1 214
2f338ab5 215 /**
216 * do the effective collection of results
3319ef85 217 * @param boolean $all
218 * @uses USER
2f338ab5 219 */
0d46c846 220 private function process_results($all=false) {
2f338ab5 221 global $USER;
eef868d1 222
63c13a82 223 // unneeded since changing the default Zend Lexer
224 // $term = mb_convert_case($this->term, MB_CASE_LOWER, 'UTF-8');
225 $term = $this->term;
3efa38a4 226 $page = optional_param('page', 1, PARAM_INT);
e08a6ee4 227
2f338ab5 228 //experimental - return more results
63c13a82 229 // $strip_arr = array('author:', 'title:', '+', '-', 'doctype:');
230 // $stripped_term = str_replace($strip_arr, '', $term);
eef868d1 231
63c13a82 232 // $search_string = $term." title:".$stripped_term." author:".$stripped_term;
233 $search_string = $term;
234 $hits = $this->index->find($search_string);
2f338ab5 235 //--
eef868d1 236
2f338ab5 237 $hitcount = count($hits);
238 $this->total_results = $hitcount;
eef868d1 239
2f338ab5 240 if ($hitcount == 0) return array();
eef868d1 241
3efa38a4 242 $resultdoc = new SearchResult();
243 $resultdocs = array();
244 $searchables = search_collect_searchables(false, false);
eef868d1 245
3efa38a4 246 $realindex = 0;
247
248 /**
2f338ab5 249 if (!$all) {
3efa38a4 250 if ($finalresults < $this->results_per_page) {
2f338ab5 251 $this->pagenumber = 1;
3efa38a4 252 } elseif ($this->pagenumber > $totalpages) {
2f338ab5 253 $this->pagenumber = $totalpages;
3319ef85 254 }
eef868d1 255
2f338ab5 256 $start = ($this->pagenumber - 1) * $this->results_per_page;
257 $end = $start + $this->results_per_page;
eef868d1 258
3efa38a4 259 if ($end > $finalresults) {
260 $end = $finalresults;
e08a6ee4 261 }
3319ef85 262 } else {
2f338ab5 263 $start = 0;
3efa38a4 264 $end = $finalresults;
265 } */
eef868d1 266
3efa38a4 267 for ($i = 0; $i < min($hitcount, ($page) * $this->results_per_page); $i++) {
2f338ab5 268 $hit = $hits[$i];
269
270 //check permissions on each result
3efa38a4 271 if ($this->can_display($USER, $hit->docid, $hit->doctype, $hit->course_id, $hit->group_id, $hit->path, $hit->itemtype, $hit->context_id, $searchables )) {
272 if ($i >= ($page - 1) * $this->results_per_page){
273 $resultdoc->number = $realindex;
274 $resultdoc->url = $hit->url;
275 $resultdoc->title = $hit->title;
276 $resultdoc->score = $hit->score;
277 $resultdoc->doctype = $hit->doctype;
278 $resultdoc->author = $hit->author;
279 $resultdoc->courseid = $hit->course_id;
4021d539 280 $resultdoc->userid = $hit->user_id;
e08a6ee4 281
3efa38a4 282 //and store it
283 $resultdocs[] = clone($resultdoc);
284 }
285 $realindex++;
3319ef85 286 } else {
2f338ab5 287 // lowers total_results one unit
288 $this->total_results--;
289 }
3319ef85 290 }
2f338ab5 291
3efa38a4 292 $totalpages = ceil($this->total_results/$this->results_per_page);
293
294
2f338ab5 295 return $resultdocs;
3319ef85 296 }
eef868d1 297
2f338ab5 298 /**
299 * get results of a search query using a caching strategy if available
300 * @return the result documents as an array of search objects
301 */
d9e1bf24 302 private function get_results() {
2f338ab5 303 $cache = new SearchCache();
304
63c13a82 305 if ($this->cache && $cache->can_cache()) {
2f338ab5 306 if (!($resultdocs = $cache->cache($this->term))) {
307 $resultdocs = $this->process_results();
308 //cache the results so we don't have to compute this on every page-load
309 $cache->cache($this->term, $resultdocs);
310 //print "Using new results.";
3319ef85 311 } else {
2f338ab5 312 //There was something in the cache, so we're using that to save time
313 //print "Using cached results.";
e08a6ee4 314 }
3319ef85 315 } else {
2f338ab5 316 //no caching :(
3efa38a4 317 // print "Caching disabled!";
2f338ab5 318 $resultdocs = $this->process_results();
e08a6ee4 319 }
2f338ab5 320 return $resultdocs;
3319ef85 321 }
eef868d1 322
2f338ab5 323 /**
324 * constructs the results paging links on results.
325 * @return string the results paging links
326 */
d9e1bf24 327 public function page_numbers() {
328 $pages = $this->total_pages();
4021d539 329 $query = htmlentities($this->term,ENT_NOQUOTES,'utf-8');
d9e1bf24 330 $page = $this->pagenumber;
2f338ab5 331 $next = get_string('next', 'search');
332 $back = get_string('back', 'search');
eef868d1 333
4021d539 334 $ret = "<div align='center' id='search_page_links'>";
eef868d1 335
d9e1bf24 336 //Back is disabled if we're on page 1
337 if ($page > 1) {
2f338ab5 338 $ret .= "<a href='query.php?query_string={$query}&page=".($page-1)."'>&lt; {$back}</a>&nbsp;";
d9e1bf24 339 } else {
2f338ab5 340 $ret .= "&lt; {$back}&nbsp;";
e08a6ee4 341 }
eef868d1 342
d9e1bf24 343 //don't <a href> the current page
344 for ($i = 1; $i <= $pages; $i++) {
345 if ($page == $i) {
2f338ab5 346 $ret .= "($i)&nbsp;";
d9e1bf24 347 } else {
2f338ab5 348 $ret .= "<a href='query.php?query_string={$query}&page={$i}'>{$i}</a>&nbsp;";
e08a6ee4
PS
349 }
350 }
eef868d1 351
d9e1bf24 352 //Next disabled if we're on the last page
eef868d1 353 if ($page < $pages) {
2f338ab5 354 $ret .= "<a href='query.php?query_string={$query}&page=".($page+1)."'>{$next} &gt;</a>&nbsp;";
d9e1bf24 355 } else {
2f338ab5 356 $ret .= "{$next} &gt;&nbsp;";
e08a6ee4 357 }
eef868d1 358
359 $ret .= "</div>";
360
d9e1bf24 361 //shorten really long page lists, to stop table distorting width-ways
362 if (strlen($ret) > 70) {
363 $start = 4;
eef868d1 364 $end = $page - 5;
d9e1bf24 365 $ret = preg_replace("/<a\D+\d+\D+>$start<\/a>.*?<a\D+\d+\D+>$end<\/a>/", '...', $ret);
eef868d1 366
d9e1bf24 367 $start = $page + 5;
eef868d1 368 $end = $pages - 3;
d9e1bf24 369 $ret = preg_replace("/<a\D+\d+\D+>$start<\/a>.*?<a\D+\d+\D+>$end<\/a>/", '...', $ret);
3319ef85 370 }
eef868d1 371
d9e1bf24 372 return $ret;
3319ef85 373 }
d9e1bf24 374
2f338ab5 375 /**
376 * can the user see this result ?
377 * @param user a reference upon the user to be checked for access
378 * @param this_id the item identifier
e08a6ee4 379 * @param doctype the search document type. MAtches the module or block or
2f338ab5 380 * extra search source definition
381 * @param course_id the course reference of the searched result
382 * @param group_id the group identity attached to the found resource
e08a6ee4 383 * @param path the path that routes to the local lib.php of the searched
2f338ab5 384 * surrounding object fot that document
385 * @param item_type a subclassing information for complex module data models
3319ef85 386 * @uses CFG
2f338ab5 387 * // TODO reorder parameters more consistently
388 */
3efa38a4 389 private function can_display(&$user, $this_id, $doctype, $course_id, $group_id, $path, $item_type, $context_id, &$searchables) {
63c13a82 390 global $CFG, $DB;
e08a6ee4 391
2f338ab5 392 /**
393 * course related checks
394 */
395 // admins can see everything, anyway.
4f0c2d00 396 if (has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))){
2f338ab5 397 return true;
398 }
e08a6ee4
PS
399
400 // first check course compatibility against user : enrolled users to that course can see.
df997f84 401 $myCourses = enrol_get_users_courses($user->id, true);
3efa38a4 402 $unenroled = !in_array($course_id, array_keys($myCourses));
e08a6ee4 403
3efa38a4 404 // if guests are allowed, logged guest can see
e08a6ee4
PS
405 $isallowedguest = false; //TODO: this will be harder to do now because we do not have guest field in course table any more
406
3efa38a4 407 if ($unenroled && !$isallowedguest){
408 return false;
409 }
e08a6ee4 410
3efa38a4 411 // if user is enrolled or is allowed user and course is hidden, can he see it ?
63c13a82 412 $visibility = $DB->get_field('course', 'visible', array('id' => $course_id));
3efa38a4 413 if ($visibility <= 0){
414 if (!has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course_id))){
415 return false;
416 }
417 }
e08a6ee4 418
3efa38a4 419 /**
420 * prerecorded capabilities
421 */
422 // get context caching information and tries to discard unwanted records here
e08a6ee4
PS
423
424
3efa38a4 425 /**
426 * final checks
427 */
428 // then give back indexing data to the module for local check
429 $searchable_instance = $searchables[$doctype];
430 if ($searchable_instance->location == 'internal'){
431 include_once "{$CFG->dirroot}/search/documents/{$doctype}_document.php";
432 } else {
63c13a82 433 include_once "{$CFG->dirroot}/{$searchable_instance->location}/{$doctype}/search_document.php";
3efa38a4 434 }
435 $access_check_function = "{$doctype}_check_text_access";
e08a6ee4 436
3efa38a4 437 if (function_exists($access_check_function)){
438 $modulecheck = $access_check_function($path, $item_type, $this_id, $user, $group_id, $context_id);
439 // echo "module said $modulecheck for item $doctype/$item_type/$this_id";
440 return($modulecheck);
441 }
e08a6ee4 442
3efa38a4 443 return true;
3319ef85 444 }
eef868d1 445
3319ef85 446 /**
447 *
448 */
d9e1bf24 449 public function count() {
0d46c846 450 return $this->total_results;
d9e1bf24 451 } //count
eef868d1 452
3319ef85 453 /**
454 *
455 */
d9e1bf24 456 public function is_valid() {
457 return ($this->validquery and $this->validindex);
3319ef85 458 }
eef868d1 459
3319ef85 460 /**
461 *
462 */
d9e1bf24 463 public function is_valid_query() {
464 return $this->validquery;
3319ef85 465 }
d9e1bf24 466
3319ef85 467 /**
468 *
469 */
d9e1bf24 470 public function is_valid_index() {
471 return $this->validindex;
3319ef85 472 }
eef868d1 473
3319ef85 474 /**
475 *
476 */
d9e1bf24 477 public function total_pages() {
478 return ceil($this->count()/$this->results_per_page);
3319ef85 479 }
eef868d1 480
3319ef85 481 /**
482 *
483 */
d9e1bf24 484 public function get_pagenumber() {
485 return $this->pagenumber;
3319ef85 486 }
eef868d1 487
3319ef85 488 /**
489 *
490 */
d9e1bf24 491 public function get_results_per_page() {
492 return $this->results_per_page;
3319ef85 493 }
494}
4021d539 495?>