3480cfc5c597ea6416a2995d07c9a12d874cf3df
[moodle.git] / search / query.php
1 <?php
2     /**
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     * The query page - accepts a user-entered query string and returns results.
13     *
14     * Queries are boolean-aware, e.g.:
15     *
16     * '+'      term required
17     * '-'      term must not be present
18     * ''       (no modifier) term's presence increases rank, but isn't required
19     * 'field:' search this field
20     *
21     * Examples:
22     *
23     * 'earthquake +author:michael'
24     *   Searches for documents written by 'michael' that contain 'earthquake'
25     *
26     * 'earthquake +doctype:wiki'
27     *   Search all wiki pages for 'earthquake'
28     *
29     * '+author:helen +author:foster'
30     *   All articles written by Helen Foster
31     *
32     */
34     /**
35     * includes and requires
36     */
37     require_once('../config.php');
38     require_once($CFG->dirroot.'/search/lib.php');
40     if ($CFG->forcelogin) {
41         require_login();
42     }
44     if (empty($CFG->enableglobalsearch)) {
45         print_error('globalsearchdisabled', 'search');
46     }
48     $adv = new stdClass();
50 /// check for php5, but don't die yet (see line 52)
52     require_once($CFG->dirroot.'/search/querylib.php');
54     $page_number  = optional_param('page', -1, PARAM_INT);
55     $pages        = ($page_number == -1) ? false : true;
56     $advanced     = (optional_param('a', '0', PARAM_INT) == '1') ? true : false;
57     $query_string = optional_param('query_string', '', PARAM_CLEAN);
59     $url = new moodle_url('/search/query.php');
60     if ($page_number !== -1) {
61         $url->param('page', $page_number);
62     }
63     if ($advanced) {
64         $url->param('a', '1');
65     }
66     $PAGE->set_url($url);
68 /// discard harmfull searches
70     if (!isset($CFG->block_search_utf8dir)){
71         set_config('block_search_utf8dir', 1);
72     }
74 /// discard harmfull searches
76     if (preg_match("/^[\*\?]+$/", $query_string)){
77         $query_string = '';
78         $error = get_string('fullwildcardquery','search');
79     }
82     if ($pages && isset($_SESSION['search_advanced_query'])) {
83         // if both are set, then we are busy browsing through the result pages of an advanced query
84         $adv = unserialize($_SESSION['search_advanced_query']);
85     } elseif ($advanced) {
86         // otherwise we are dealing with a new advanced query
87         unset($_SESSION['search_advanced_query']);
88         session_unregister('search_advanced_query');
90         // chars to strip from strings (whitespace)
91         $chars = " \t\n\r\0\x0B,-+";
93         // retrieve advanced query variables
94         $adv->mustappear  = trim(optional_param('mustappear', '', PARAM_CLEAN), $chars);
95         $adv->notappear   = trim(optional_param('notappear', '', PARAM_CLEAN), $chars);
96         $adv->canappear   = trim(optional_param('canappear', '', PARAM_CLEAN), $chars);
97         $adv->module      = optional_param('module', '', PARAM_CLEAN);
98         $adv->title       = trim(optional_param('title', '', PARAM_CLEAN), $chars);
99         $adv->author      = trim(optional_param('author', '', PARAM_CLEAN), $chars);
100     }
102     if ($advanced) {
103         //parse the advanced variables into a query string
104         //TODO: move out to external query class (QueryParse?)
106         $query_string = '';
108         // get all available module types adding third party modules
109         $module_types = array_merge(array('all'), array_values(search_get_document_types()));
110         $module_types = array_merge($module_types, array_values(search_get_document_types('X_SEARCH_TYPE')));
111         $adv->module = in_array($adv->module, $module_types) ? $adv->module : 'all';
113         // convert '1 2' into '+1 +2' for required words field
114         if (strlen(trim($adv->mustappear)) > 0) {
115             $query_string  = ' +'.implode(' +', preg_split("/[\s,;]+/", $adv->mustappear));
116         }
118         // convert '1 2' into '-1 -2' for not wanted words field
119         if (strlen(trim($adv->notappear)) > 0) {
120             $query_string .= ' -'.implode(' -', preg_split("/[\s,;]+/", $adv->notappear));
121         }
123         // this field is left untouched, apart from whitespace being stripped
124         if (strlen(trim($adv->canappear)) > 0) {
125             $query_string .= ' '.implode(' ', preg_split("/[\s,;]+/", $adv->canappear));
126         }
128         // add module restriction
129         $doctypestr = 'doctype';
130         $titlestr = 'title';
131         $authorstr = 'author';
132         if ($adv->module != 'all') {
133             $query_string .= " +{$doctypestr}:".$adv->module;
134         }
136         // create title search string
137         if (strlen(trim($adv->title)) > 0) {
138             $query_string .= " +{$titlestr}:".implode(" +{$titlestr}:", preg_split("/[\s,;]+/", $adv->title));
139         }
141         // create author search string
142         if (strlen(trim($adv->author)) > 0) {
143             $query_string .= " +{$authorstr}:".implode(" +{$authorstr}:", preg_split("/[\s,;]+/", $adv->author));
144         }
146         // save our options if the query is valid
147         if (!empty($query_string)) {
148             $_SESSION['search_advanced_query'] = serialize($adv);
149         }
150     }
152     // normalise page number
153     if ($page_number < 1) {
154         $page_number = 1;
155     }
157     //run the query against the index ensuring internal coding works in UTF-8
158     Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8_CaseInsensitive());
159     $sq = new SearchQuery($query_string, $page_number, 10, false);
161     $site = get_site();
163     $strsearch = get_string('search', 'search');
164     $strquery  = get_string('enteryoursearchquery', 'search');
166     // print the header
167     $site = get_site();
168     $PAGE->set_context(get_context_instance(CONTEXT_SYSTEM));
169     $PAGE->navbar->add($strsearch, new moodle_url('/search/index.php'));
170     $PAGE->navbar->add($strquery, new moodle_url('/search/stats.php'));
171     $PAGE->set_title($strsearch);
172     $PAGE->set_heading($site->fullname);
173     echo $OUTPUT->header();
175     if (!empty($error)){
176         notice ($error);
177     }
179     echo $OUTPUT->box_start();
180     echo $OUTPUT->heading($strquery);
182     echo $OUTPUT->box_start();
184     $vars = get_object_vars($adv);
186     if (isset($vars)) {
187         foreach ($vars as $key => $value) {
188             // htmlentities breaks non-ascii chars ??
189             $adv->key = $value;
190             //$adv->$key = htmlentities($value);
191         }
192     }
193     ?>
194     <form id="query" method="get" action="query.php">
195     <?php
196     if (!$advanced) {
197     ?>
198         <input type="text" name="query_string" length="50" value="<?php p($query_string) ?>" />&nbsp;
199         <input type="submit" value="<?php print_string('search', 'search') ?>" /> &nbsp;
200         <a href="query.php?a=1"><?php print_string('advancedsearch', 'search') ?></a> |
201         <a href="stats.php"><?php print_string('statistics', 'search') ?></a>
202     <?php
203     }
204     else {
205         echo $OUTPUT->box_start();
206       ?>
207         <input type="hidden" name="a" value="<?php p($advanced); ?>"/>
209         <table border="0" cellpadding="3" cellspacing="3">
211         <tr>
212           <td width="240"><?php print_string('thesewordsmustappear', 'search') ?>:</td>
213           <td><input type="text" name="mustappear" length="50" value="<?php p($adv->mustappear); ?>" /></td>
214         </tr>
216         <tr>
217           <td><?php print_string('thesewordsmustnotappear', 'search') ?>:</td>
218           <td><input type="text" name="notappear" length="50" value="<?php p($adv->notappear); ?>" /></td>
219         </tr>
221         <tr>
222           <td><?php print_string('thesewordshelpimproverank', 'search') ?>:</td>
223           <td><input type="text" name="canappear" length="50" value="<?php p($adv->canappear); ?>" /></td>
224         </tr>
226         <tr>
227           <td><?php print_string('whichmodulestosearch?', 'search') ?>:</td>
228           <td>
229             <select name="module">
230     <?php
231         foreach($module_types as $mod) {
232             if ($mod == $adv->module) {
233                 if ($mod != 'all'){
234                     print "<option value='$mod' selected=\"selected\">".get_string('modulenameplural', $mod)."</option>\n";
235                 }
236                 else{
237                     print "<option value='$mod' selected=\"selected\">".get_string('all', 'search')."</option>\n";
238                 }
239             }
240             else {
241                 if ($mod != 'all'){
242                     print "<option value='$mod'>".get_string('modulenameplural', $mod)."</option>\n";
243                 }
244                 else{
245                     print "<option value='$mod'>".get_string('all', 'search')."</option>\n";
246                 }
247             }
248         }
249     ?>
250             </select>
251           </td>
252         </tr>
254         <tr>
255           <td><?php print_string('wordsintitle', 'search') ?>:</td>
256           <td><input type="text" name="title" length="50" value="<?php p($adv->title); ?>" /></td>
257         </tr>
259         <tr>
260           <td><?php print_string('authorname', 'search') ?>:</td>
261           <td><input type="text" name="author" length="50" value="<?php p($adv->author); ?>" /></td>
262         </tr>
264         <tr>
265           <td colspan="3" align="center"><br /><input type="submit" value="<?php p(get_string('search', 'search')) ?>" /></td>
266         </tr>
268         <tr>
269           <td colspan="3" align="center">
270             <table border="0" cellpadding="0" cellspacing="0">
271               <tr>
272                 <td><a href="query.php"><?php print_string('normalsearch', 'search') ?></a> |</td>
273                 <td>&nbsp;<a href="stats.php"><?php print_string('statistics', 'search') ?></a></td>
274               </tr>
275             </table>
276           </td>
277         </tr>
278         </table>
279     <?php
280         echo $OUTPUT->box_end();
281         }
282     ?>
283     </form>
284     <br/>
286     <div align="center">
287     <?php
288     print_string('searching', 'search') . ': ';
290     if ($sq->is_valid_index()) {
291         //use cached variable to show up-to-date index size (takes deletions into account)
292         print $CFG->search_index_size;
293     }
294     else {
295         print "0";
296     }
298     print ' ';
299     print_string('documents', 'search');
300     print '.';
302     if (!$sq->is_valid_index() and has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
303         print '<p>' . get_string('noindexmessage', 'search') . '<a href="indexersplash.php">' . get_string('createanindex', 'search')."</a></p>\n";
304     }
306     ?>
307     </div>
308     <?php
309     echo $OUTPUT->box_end();
311 /// prints all the results in a box
313     if ($sq->is_valid()) {
314         echo $OUTPUT->box_start();
316         search_stopwatch();
317         $hit_count = $sq->count();
319         print "<br />";
321         print $hit_count.' '.get_string('resultsreturnedfor', 'search') . " '".s($query_string)."'.";
322         print "<br />";
324         if ($hit_count > 0) {
325             $page_links = $sq->page_numbers();
326             $hits = $sq->results();
328             if ($advanced) {
329                 // if in advanced mode, search options are saved in the session, so
330                 // we can remove the query string var from the page links, and replace
331                 // it with a=1 (Advanced = on) instead
332                 $page_links = preg_replace("/query_string=[^&]+/", 'a=1', $page_links);
333             }
335             print "<ol>";
337             $typestr = get_string('type', 'search');
338             $scorestr = get_string('score', 'search');
339             $authorstr = get_string('author', 'search');
341             $searchables = search_collect_searchables(false, false);
343             foreach ($hits as $listing) {
345                 if ($listing->doctype == 'user'){ // A special handle for users
346                     //TODO: this is a performance problem, fetch data elsewhere
347                     $user = (object)array('id'=>$listing->userid);
348                     $icon = $OUTPUT->user_picture($user);
349                 } else {
350                     $iconpath = $OUTPUT->pix_url('icon', $listing->doctype);
351                     $icon = "<img align=\"top\" src=\"".$iconpath."\" class=\"activityicon\" alt=\"\"/>";
352                 }
353                 $coursename = $DB->get_field('course', 'fullname', array('id' => $listing->courseid));
354                 $courseword = mb_convert_case(get_string('course', 'moodle'), MB_CASE_LOWER, 'UTF-8');
355                 $course = ($listing->doctype != 'user') ? '<strong> ('.$courseword.': \''.$coursename.'\')</strong>' : '' ;
357                 $title_post_processing_function = $listing->doctype.'_link_post_processing';
358                 $searchable_instance = $searchables[$listing->doctype];
359                 if ($searchable_instance->location == 'internal'){
360                     require_once "{$CFG->dirroot}/search/documents/{$listing->doctype}_document.php";
361                 } else {
362                     require_once "{$CFG->dirroot}/{$searchable_instance->location}/{$listing->doctype}/search_document.php";
363                 }
364                 if (function_exists($title_post_processing_function)) {
365                     $listing->title = $title_post_processing_function($listing->title);
366                 }
368                 echo "<li value='".($listing->number + 1)."'><a href='"
369                     .str_replace('DEFAULT_POPUP_SETTINGS', DEFAULT_POPUP_SETTINGS ,$listing->url)
370                     ."'>$icon $listing->title</a> $course<br />\n";
371                 echo "{$typestr}: " . $listing->doctype . ", {$scorestr}: " . round($listing->score, 3);
372                 if (!empty($listing->author) && !is_numeric($listing->author)){
373                     echo ", {$authorstr}: ".$listing->author."\n"
374                         ."</li>\n";
375                 }
376             }
377             echo "</ol>";
378             echo $page_links;
379         }
380         echo $OUTPUT->box_end();
381     ?>
382     <div align="center">
383     <?php
384         print_string('ittook', 'search');
385         search_stopwatch();
386         print_string('tofetchtheseresults', 'search');
387     ?>.
388     </div>
390     <?php
391     }
392     echo $OUTPUT->box_end();
393     echo $OUTPUT->footer();
394 ?>