global search review and extension for physical files

This commit is contained in:
diml 2007-07-09 21:12:16 +00:00
parent cf0b12ac83
commit 2f338ab5b0
28 changed files with 3903 additions and 1485 deletions

89
search/LISEZMOI.txt Normal file
View file

@ -0,0 +1,89 @@
Cette distribution partielle contient une refonte du moteur de
recherche globalde Moodle.
Le moteur de recherche est capable d'indexer et de rechercher
des informations dans un grand nombre de contenus stockés
dans la plate-forme à travers la manipulation des activités et
des blocs.
Le moteur de recherche procède à une première indexation des
ressources disponibles par action de l'administrateur. Une fois
cette indexation effectuée, le moteur maintient régulièrement les
indexes, en ajoutant les nouvelles entrées et en nettoyant les
entrées obsolètes.
La recherche permet d'obtenir des références d'accès au contexte
qui diffuse cette information, au nom de l'utilisateur courant.
Le filtrage des résultats enlève de la liste des réponses toute
ressource que la situation de l'utilisateur empêcherait de voir
s'il y accédait dans son contexte habituel.
Mise en oeuvre
##############
Pour déployer le moteur :
* Copie de fichiers
1. Ajouter les deux librairies fournies aux librairies de Moodle
2. Ecraser le répertoire "search" par le répertoire fourni
3. Ecraser le bloc "blocs/search" par le bloc fourni.
* Installation logique
4. Aller dans les notifications administratives et dérouler la procédure d'installation/mise à jour du bloc. L'installation crée la table image
des documents indexés et utilisés dans le module search.
5. Insérer un nouveau bloc de recherche globale dans la plate-forme
6. Effectuer une recherche vide (en administrateur)
7. Aller sur la page des statistiques
8. Activer l'indexation (indexsplash.php). Attention, si la plate-form contient beaucoup de contenus cette indexation peut être TRES LONGUE.
Pour effectuer des recherches, une fois la première indexation terminée, retourner au bloc de recherche et tenter une recherche.
Eléments pris en charge
#######################
Dans l'état actuel, les éléments indexés par le moteur sont :
- les entrées de forum
- les fiches de base de données
- les commentaires sur fiches de données
- les entrées de glossaire
- les commentaires sur entrées de glossaire
- les ressources natives Moodle
- les ressources physiques de type MSWord
- les ressources physiques de type PDF
- les ressources physiques de type fichier texte (.txt)
- les ressources physiques de type HTML (.htm et .html)
- les ressources physiques de type XML (.xml)
- les ressources physiques de type (Microsoft) Powerpoint (.ppt)
- les pages de wiki
- les entités de projet technique
- les sessions de chat
Extensions
##########
L'API du moteur de recherche permet désormais :
- l'indexation de contenus de blocs.
- l'indexation de modules contenant une information complexe ou de plusieurs types distincts
- la sécurisation des informations indexées lors des extractions de résultats
- l'indexation de tout module tiers par ajout d'un fichier php calibré
- l'indexation de toute nouvelle resource physique par ajout d'un fichier php calibré
Extensions futures
##################
- De nouvelles prises en charge de contenus tels que les attachements des forums, les attachement des glossaires, ainsi que d'autres modules non encore
implémentés.
- l'extension mnet de la recherche dans un réseau de moodle interconnectés.

90
search/READMETOO.txt Normal file
View file

@ -0,0 +1,90 @@
This partial distribution contains a complete review of the
Global Search Engine of Moodle.
The Global Search Engine stores indexes about a huge quantity
of information from within modules, block or resources stored
by Moodle either in the database or the file system.
The administrator initialy indexes the existing content. Once this
first initialization performed, the search engine maintains indexes
regularily, adding new entries, deleting obsolete one or updating
some that have changed.
Search will produce links for acceding the information in a similar
context as usually accessed, from the current user point of view.
Results filtering removes from results any link to information the
current user would not be allowed to acces on a straight situation.
Deployement
###########
For setting the engine :
* File copy
1. Add to Moodle's library both additional libraries provided in the distribution
2. Replace the "search" directory with the new one
3. Replace the "blocks/search" with the new one.
* Logical install
4. Browse to the administrative notification screen and let the
install/update process run. The install process creates the Moodle
table needed for backing the indexed documents identities.
5. Go to the block administration panel and setup once the Global Search
block. This will initialize useful parameters for the global search engine.
6. Insert a new Global Search block somewhere in a course or top-level screen.
7. Launch an empty search (you must be administrator).
8. Go to the statistics screen.
9. Activate indexation (indexersplash.php). Beware, if your Moodle has
a large amount of content, indexing process may be VERY LONG.
To search, go back to the search block and try a query.
Handled information for indexing
################################
In the actual state, the engine indexes the following information:
- forum posts
- database records (using textual fields only)
- database comments
- glossary entries
- glossary comments on entries
- Moodle native resources
- physical MSWord files as resources (.doc)
- physical Powerpoint files as resources (.ppt)
- physical PDF files as resources
- physical text files as resources (.txt)
- physical html files as resources (.htm and .html)
- physical xml files as resources (.xml)
- wiki pages
- techproject descriptions
- char sessions
Extensions
##########
The reviewed search engine API allows:
- indexing of blocks contents
- indexation of modules or blocks containing a complex information model
- securing the access to the results
- adding indexing handling adding a php calibrated script
- adding physical filetype handling adding a php calibrated script
Future extensions
#################
- Should be added more information to index such as forum and glossary attachements, so will other standard module contents.
- extending the search capability to a mnet network information space.

View file

@ -1,46 +1,59 @@
<?php
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* Asynchronous adder for new indexable contents
*
* Major chages in this review is passing the xxxx_db_names return to
* multiple arity to handle multiple document types modules
*/
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_login();
require_login();
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
if (!isadmin()) {
error("You need to be an admin user to use this page.", "$CFG->wwwroot/login/index.php");
} //if
if (!isadmin()) {
error(get_string('beadmin', 'search'), "$CFG->wwwroot/login/index.php");
}
//check for php5 (lib.php)
if (!search_check_php5()) {
//check for php5 (lib.php)
if (!search_check_php5()) {
$phpversion = phpversion();
mtrace("Sorry, global search requires PHP 5.0.0 or later (currently using version $phpversion)");
exit(0);
} //if
}
require_once("$CFG->dirroot/search/indexlib.php");
require_once("$CFG->dirroot/search/indexlib.php");
$index = new Zend_Search_Lucene(SEARCH_INDEX_PATH);
$dbcontrol = new IndexDBControl();
$addition_count = 0;
$index = new Zend_Search_Lucene(SEARCH_INDEX_PATH);
$dbcontrol = new IndexDBControl();
$addition_count = 0;
$startindextime = time();
$indexdate = $CFG->search_indexer_run_date;
$indexdate = $CFG->search_indexer_run_date;
mtrace('<pre>Starting index update (additions)...');
mtrace('Index size before: '.$CFG->search_index_size."\n");
mtrace('<pre>Starting index update (additions)...');
mtrace('Index size before: '.$CFG->search_index_size."\n");
//get all modules
if ($mods = get_records_select('modules')) {
//append virtual modules onto array
$mods = array_merge($mods, search_get_additional_modules());
//get all modules
if ($mods = get_records_select('modules')) {
//append virtual modules onto array
$mods = array_merge($mods, search_get_additional_modules());
foreach ($mods as $mod) {
//build include file and function names
$class_file = $CFG->dirroot.'/search/documents/'.$mod->name.'_document.php';
$db_names_function = $mod->name.'_db_names';
$get_document_function = $mod->name.'_single_document';
$get_newrecords_function = $mod->name.'_new_records';
$additions = array();
if (file_exists($class_file)) {
@ -49,55 +62,83 @@
//if both required functions exist
if (function_exists($db_names_function) and function_exists($get_document_function)) {
mtrace("Checking $mod->name module for additions.");
$values = $db_names_function();
$where = (isset($values[4])) ? $values[4] : '';
$valuesArray = $db_names_function();
if ($valuesArray){
foreach($valuesArray as $values){
$where = (isset($values[5])) ? 'AND ('.$values[5].')' : '';
$itemtypes = ($values[4] != '*') ? " AND itemtype = '{$values[4]}' " : '' ;
//select records in MODULE table, but not in SEARCH_DATABASE_TABLE
$sql = "select id, ".$values[0]." as docid from ".$values[1].
" where id not in".
" (select docid from ".SEARCH_DATABASE_TABLE." where doctype like '$mod->name')".
" and ".$values[2]." > $indexdate".
" $where";
$table = SEARCH_DATABASE_TABLE;
$query = "
SELECT
docid,
itemtype
FROM
{$CFG->prefix}{$table}
WHERE
doctype = '{$mod->name}'
$itemtypes
";
$docIds = get_records_sql_menu($query);
$docIdList = ($docIds) ? implode("','", array_keys($docIds)) : '' ;
$records = get_records_sql($sql);
$query = "
SELECT id,
{$values[0]} as docid
FROM
{$CFG->prefix}{$values[1]}
WHERE
id NOT IN ('{$docIdList}') and
{$values[2]} > {$indexdate}
$where
";
$records = get_records_sql($query);
//foreach record, build a module specific search document using the get_document function
// foreach record, build a module specific search document using the get_document function
if (is_array($records)) {
foreach($records as $record) {
$additions[] = $get_document_function($record->id);
} //foreach
} //if
$add = $get_document_function($record->docid, $values[4]);
// some documents may not be indexable
if ($add)
$additions[] = $add;
}
}
}
//foreach document, add it to the index and database table
// foreach document, add it to the index and database table
foreach ($additions as $add) {
++$addition_count;
//object to insert into db
// object to insert into db
$dbid = $dbcontrol->addDocument($add);
//synchronise db with index
// synchronise db with index
$add->addField(Zend_Search_Lucene_Field::Keyword('dbid', $dbid));
mtrace(" Add: $add->title (database id = $add->dbid, moodle instance id = $add->docid)");
$index->addDocument($add);
} //foreach
}
}
else{
mtrace("No types to add.\n");
}
mtrace("Finished $mod->name.\n");
} //if
} //if
} //foreach
} //if
}
}
}
}
//commit changes
$index->commit();
// commit changes
$index->commit();
//update index date and size
set_config("search_indexer_run_date", time());
set_config("search_index_size", (int)$CFG->search_index_size + (int)$addition_count);
// update index date and size
set_config("search_indexer_run_date", $startindextime);
set_config("search_index_size", (int)$CFG->search_index_size + (int)$addition_count);
//print some additional info
mtrace("Added $addition_count documents.");
mtrace('Index size after: '.$index->count().'</pre>');
// print some additional info
mtrace("Added $addition_count documents.");
mtrace('Index size after: '.$index->count().'</pre>');
?>

View file

@ -1,35 +1,47 @@
<?php
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* Asynchronous index cleaner
*
* Major chages in this review is passing the xxxx_db_names return to
* multiple arity to handle multiple document types modules
*/
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_login();
require_login();
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
if (!isadmin()) {
error("You need to be an admin user to use this page.", "$CFG->wwwroot/login/index.php");
} //if
if (!isadmin()) {
error(get_string('beadmin', 'search'), "$CFG->wwwroot/login/index.php");
} //if
//check for php5 (lib.php)
if (!search_check_php5()) {
//check for php5 (lib.php)
if (!search_check_php5()) {
$phpversion = phpversion();
mtrace("Sorry, global search requires PHP 5.0.0 or later (currently using version $phpversion)");
exit(0);
} //if
}
require_once("$CFG->dirroot/search/indexlib.php");
require_once("$CFG->dirroot/search/indexlib.php");
$index = new Zend_Search_Lucene(SEARCH_INDEX_PATH);
$dbcontrol = new IndexDBControl();
$deletion_count = 0;
$index = new Zend_Search_Lucene(SEARCH_INDEX_PATH);
$dbcontrol = new IndexDBControl();
$deletion_count = 0;
$startcleantime = time();
mtrace('<pre>Starting clean-up of removed records...');
mtrace('Index size before: '.$CFG->search_index_size."\n");
mtrace('<pre>Starting clean-up of removed records...');
mtrace('Index size before: '.$CFG->search_index_size."\n");
if ($mods = get_records_select('modules')) {
if ($mods = get_records_select('modules')) {
$mods = array_merge($mods, search_get_additional_modules());
foreach ($mods as $mod) {
@ -42,29 +54,52 @@
if (file_exists($class_file)) {
require_once($class_file);
//if both required functions exist
if (function_exists($delete_function) and function_exists($db_names_function)) {
mtrace("Checking $mod->name module for deletions.");
$values = $db_names_function();
$valuesArray = $db_names_function();
if ($valuesArray){
foreach($valuesArray as $values){
$where = (isset($values[5])) ? 'WHERE '.$values[5] : '';
$itemtypes = ($values[4] != '*') ? " itemtype = '{$values[4]}' AND " : '' ;
$query = "
SELECT
id,
{$values[0]}
FROM
{$CFG->prefix}{$values[1]}
$where
";
$docIds = get_records_sql($query);
$docIdList = ($docIds) ? implode("','", array_keys($docIds)) : '' ;
$sql = "select id, docid from ".SEARCH_DATABASE_TABLE.
" where doctype like '$mod->name'".
" and docid not in".
" (select ".$values[0]." from ".$values[1].")";
$table = SEARCH_DATABASE_TABLE;
$query = "
SELECT
id,
docid
FROM
{$CFG->prefix}{$table}
WHERE
doctype = '{$mod->name}' AND
$itemtypes
docid not in ('{$docIdList}')
";
$records = get_records_sql($query);
$records = get_records_sql($sql);
//build an array of all the deleted records
// build an array of all the deleted records
if (is_array($records)) {
foreach($records as $record) {
$deletions[] = $delete_function($record->docid);
} //foreach
} //if
$deletions[] = $delete_function($record->docid, $values[4]);
}
}
}
foreach ($deletions as $delete) {
//find the specific document in the index, using it's docid and doctype as keys
$doc = $index->find("+docid:$delete +doctype:$mod->name");
// find the specific document in the index, using it's docid and doctype as keys
$doc = $index->find("+docid:{$delete->id} +doctype:$mod->name +itemtype:{$delete->itemtype}");
//get the record, should only be one
// get the record, should only be one
foreach ($doc as $thisdoc) {
++$deletion_count;
mtrace(" Delete: $thisdoc->title (database id = $thisdoc->dbid, index id = $thisdoc->id, moodle instance id = $thisdoc->docid)");
@ -72,23 +107,26 @@
//remove it from index and database table
$dbcontrol->delDocument($thisdoc);
$index->delete($thisdoc->id);
} //foreach
} //foreach
}
}
}
else{
mtrace("No types to delete.\n");
}
mtrace("Finished $mod->name.\n");
} //if
} //if
} //foreach
} //if
}
}
}
}
//commit changes
$index->commit();
//commit changes
$index->commit();
//update index date and index size
set_config("search_indexer_run_date", time());
set_config("search_index_size", (int)$CFG->search_index_size - (int)$deletion_count);
//update index date and index size
set_config("search_indexer_cleanup_date", $startcleantime);
set_config("search_index_size", (int)$CFG->search_index_size - (int)$deletion_count);
mtrace("Finished $deletion_count removals.");
mtrace('Index size after: '.$index->count().'</pre>');
mtrace("Finished $deletion_count removals.");
mtrace('Index size after: '.$index->count().'</pre>');
?>

View file

@ -0,0 +1,271 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for chat activity module
* This file contains the mapping between a chat history and it's indexable counterpart,
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/chat/lib.php
**/
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/chat/lib.php");
/*
* a class for representing searchable information
*
**/
class ChatTrackSearchDocument extends SearchDocument {
/**
* constructor
*
*/
public function __construct(&$chatsession, $chat_module_id, $course_id, $group_id, $context_id) {
// generic information; required
$doc->docid = $chat_module_id.'-'.$chatsession['sessionstart'].'-'.$chatsession['sessionend'];
$doc->documenttype = SEARCH_TYPE_CHAT;
$doc->itemtype = 'session';
$doc->contextid = $context_id;
$duration = $chatsession['sessionend'] - $chatsession['sessionstart'];
// we cannot call userdate with relevant locale at indexing time.
$doc->title = get_string('chatreport', 'chat').' '.get_string('openedon', 'search').' TT_'.$chatsession['sessionstart'].'_TT ('.get_string('duration', 'search').' : '.get_string('numseconds', '', $duration).')';
$doc->date = $chatsession['sessionend'];
//remove '(ip.ip.ip.ip)' from chat author list
$doc->author = preg_replace('/\(.*?\)/', '', $chatsession['authors']);
$doc->contents = $chatsession['content'];
$doc->url = chat_make_link($chat_module_id, $chatsession['sessionstart'], $chatsession['sessionend']);
// module specific information; optional
$data->chat = $chat_module_id;
// construct the parent class
parent::__construct($doc, $data, $course_id, $group_id, 0, PATH_FOR_SEARCH_TYPE_CHAT);
} //constructor
} //ChatTrackSearchDocument
/**
* constructs a valid link to a chat content
* @param cm_id the chat course module
* @param start the start time of the session
* @param end th end time of the session
* @return a well formed link to session display
*/
function chat_make_link($cm_id, $start, $end) {
global $CFG;
return $CFG->wwwroot.'/mod/chat/report.php?id='.$cm_id.'&amp;start='.$start.'&amp;end='.$end;
} //chat_make_link
/**
* fetches all the records for a given session and assemble them as a unique track
* we revamped here the code of report.php for making sessions, but without any output.
* note that we should collect sessions "by groups" if groupmode() is SEPARATEGROUPS.
* @param chat_id the database
* @return an array of objects representing the chat sessions.
*/
function chat_get_session_tracks($chat_id, $fromtime = 0, $totime = 0) {
global $CFG;
$chat = get_record('chat', 'id', $chat_id);
$course = get_record('course', 'id', $chat->course);
$coursemodule = get_field('modules', 'id', 'name', 'data');
$cm = get_record('course_modules', 'course', $course->id, 'module', $coursemodule, 'instance', $chat->id);
$groupmode = groupmode($course, $cm);
$fromtimeclause = ($fromtime) ? "AND timestamp >= {$fromtime}" : '';
$totimeclause = ($totime) ? "AND timestamp <= {$totime}" : '';
$tracks = array();
$messages = get_records_select('chat_messages', "chatid = '{$chat_id}' $fromtimeclause $totimeclause", "timestamp DESC");
if ($messages){
// splits discussions against groups
$groupedMessages = array();
if ($groupmode != SEPARATEGROUPS){
foreach($messages as $aMessage){
$groupedMessages[$aMessage->groupid][] = $aMessage;
}
}
else{
$groupedMessages[-1] = &$messages;
}
$sessiongap = 5 * 60; // 5 minutes silence means a new session
$sessionend = 0;
$sessionstart = 0;
$sessionusers = array();
$lasttime = time();
foreach ($groupedMessages as $groupId => $messages) { // We are walking BACKWARDS through the messages
$messagesleft = count($messages);
foreach ($messages as $message) { // We are walking BACKWARDS through the messages
$messagesleft --; // Countdown
if ($message->system) {
continue;
}
// we are within a session track
if ((($lasttime - $message->timestamp) < $sessiongap) and $messagesleft) { // Same session
if (count($tracks) > 0){
if ($message->userid) { // Remember user and count messages
$tracks[count($tracks) - 1]->sessionusers[$message->userid] = $message->userid;
// update last track (if exists) record appending content (remember : we go backwards)
}
$tracks[count($tracks) - 1]->content .= ' '.$message->message;
$tracks[count($tracks) - 1]->sessionstart = $message->timestamp;
}
}
// we initiate a new session track (backwards)
else {
$track = new Object();
$track->sessionend = $message->timestamp;
$track->sessionstart = $message->timestamp;
$track->content = $message->message;
// reset the accumulator of users
$track->sessionusers = array();
$track->sessionusers[$message->userid] = $message->userid;
$track->groupid = $groupId;
$tracks[] = $track;
}
$lasttime = $message->timestamp;
}
}
}
return $tracks;
} //chat_get_session_tracks
/**
* part of search engine API
*
*/
function chat_iterator() {
$chatrooms = get_records('chat');
return $chatrooms;
} //chat_iterator
/**
* part of search engine API
*
*/
function chat_get_content_for_index(&$chat) {
$documents = array();
$course = get_record('course', 'id', $chat->course);
$coursemodule = get_field('modules', 'id', 'name', 'chat');
$cm = get_record('course_modules', 'course', $course->id, 'module', $coursemodule, 'instance', $chat->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// getting records for indexing
$sessionTracks = chat_get_session_tracks($chat->id);
if ($sessionTracks){
foreach($sessionTracks as $aTrackId => $aTrack) {
foreach($aTrack->sessionusers as $aUserId){
$user = get_record('user', 'id', $aUserId);
$aTrack->authors = ($user) ? $user->firstname.' '.$user->lastname : '' ;
$documents[] = new ChatTrackSearchDocument(get_object_vars($aTrack), $cm->id, $chat->course, $aTrack->groupid, $context->id);
}
}
}
return $documents;
} //chat_get_content_for_index
/**
* returns a single data search document based on a chat_session id
* chat session id is a text composite identifier made of :
* - the chat id
* - the timestamp when the session starts
* - the timestamp when the session ends
* @param id the multipart chat session id
* @param itemtype the type of information (session is the only type)
*/
function chat_single_document($id, $itemtype) {
list($chat_id, $sessionstart, $sessionend) = split('-', $id);
$chat = get_record('chat', 'id', $chat_id);
$course = get_record('course', 'id', $chat->course);
$coursemodule = get_field('modules', 'id', 'name', 'chat');
$cm = get_record('course_modules', 'course', $course->id, 'module', $coursemodule, 'instance', $chat->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// should be only one
$tracks = chat_get_session_tracks($chat->id, $sessionstart, $sessionstart);
if ($tracks){
$aTrack = $tracks[0];
$documents[] = new ChatTrackSearchDocument(get_object_vars($aTrack), $cm->id, $chat->course, $aTrack->groupid, $context->id);
}
} //chat_single_document
/**
* dummy delete function that packs id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function chat_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //chat_delete
/**
* returns the var names needed to build a sql query for addition/deletions
* // TODO chat indexable records are virtual. Should proceed in a special way
*/
function chat_db_names() {
//[primary id], [table name], [time created field name], [time modified field name]
return null;
} //chat_db_names
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* When this point is reached, we already know that :
* - user is legitimate in the surrounding context
* - user may be guest and guest access is allowed to the module
* - the function may perform local checks within the module information logic
* @param path the access path to the module script code
* @param itemtype the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by entry_type. In chats, this id
* points out a session history which is a close sequence of messages.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function chat_check_text_access($path, $itemtype, $this_id, $user, $group_id, $context_id){
global $CFG;
include_once("{$CFG->dirroot}/{$path}/lib.php");
list($chat_id, $sessionstart, $sessionend) = split('-', $id);
// get the chat session and all related stuff
$chat = get_record('chat', 'id', $chat_id);
$course = get_record('course', 'id', $chat->course);
$module_context = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $module_context->instanceid);
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $module_context)) return false;
//group consistency check : checks the following situations about groups
// trap if user is not same group and groups are separated
$current_group = get_current_group($course->id);
if ((groupmode($course) == SEPARATEGROUPS) && !ismember($group_id) && !has_capability('moodle/site:accessallgroups', $module_context)) return false;
//ownership check : checks the following situations about user
// trap if user is not owner and has cannot see other's entries
// TODO : typically may be stored into indexing cache
if (!has_capability('mod/chat:readlog', $module_context)) return false;
return true;
} //chat_check_text_access
/**
* this call back is called when displaying the link for some last post processing
*
*/
function chat_link_post_processing($title){
setLocale(LC_TIME, substr(current_language(), 0, 2));
$title = preg_replace('/TT_(.*)_TT/e', "userdate(\\1)", $title);
return $title;
} //chat_link_post_processing
?>

View file

@ -0,0 +1,370 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for data activity module
* This file contains the mapping between a database object and it's indexable counterpart,
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/data/lib.php
**/
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/data/lib.php");
/*
* a class for representing searchable information (data records)
*
**/
class DataSearchDocument extends SearchDocument {
/**
* constructor
*
*/
public function __construct(&$record, $course_id, $context_id) {
// generic information; required
$doc->docid = $record['id'];
$doc->documenttype = SEARCH_TYPE_DATA;
$doc->itemtype = 'record';
$doc->contextid = $context_id;
$doc->title = $record['title'];
$doc->date = $record['timemodified'];
//remove '(ip.ip.ip.ip)' from data record author field
if ($record['userid']){
$user = get_record('user', 'id', $record['userid']);
}
$doc->author = (isset($user)) ? $user->firstname.' '.$user->lastname : '' ;
$doc->contents = $record['content'];
$doc->url = data_make_link($record['dataid'], $record['id']);
// module specific information; optional
// $data->params = serialize(@$record['params']); may be useful
$data->database = $record['dataid'];
// construct the parent class
parent::__construct($doc, $data, $course_id, $record['groupid'], $record['userid'], PATH_FOR_SEARCH_TYPE_DATA);
} //constructor
} //ChatSearchDocument
/*
* a class for representing searchable information (comments on data records)
*
**/
class DataCommentSearchDocument extends SearchDocument {
/**
* constructor
*
*/
public function __construct(&$comment, $course_id, $context_id) {
// generic information; required
$doc->docid = $comment['id'];
$doc->documenttype = SEARCH_TYPE_DATA;
$doc->itemtype = 'comment';
$doc->contextid = $context_id;
$doc->title = get_string('commenton', 'search').' '.$comment['title'];
$doc->date = $comment['modified'];
//remove '(ip.ip.ip.ip)' from data record author field
$doc->author = preg_replace('/\(.*?\)/', '', $comment['author']);
$doc->contents = $comment['content'];
$doc->url = data_make_link($data_id, $comment['recordid']);
// module specific information; optional
$data->database = $comment['dataid'];
// construct the parent class
parent::__construct($doc, $data, $course_id, $comment['groupid'], $comment['userid'], PATH_FOR_SEARCH_TYPE_DATA);
} //constructor
} //ChatCommentSearchDocument
/**
* constructs a valid link to a data record content
* @param database_id the database reference
* @param record_id the record reference
* @return a valid url top access the information as a string
*/
function data_make_link($database_id, $record_id) {
global $CFG;
return $CFG->wwwroot.'/mod/data/view.php?d='.$database_id.'&amp;rid='.$record_id;
} //data_make_link
/**
* fetches all the records for a given database
* @param database_id the database
* @param typematch a comma separated list of types that should be considered for searching or *
* @return an array of objects representing the data records.
*/
function data_get_records($database_id, $typematch = '*') {
global $CFG;
$fieldset = get_records('data_fields', 'dataid', $database_id);
$query = "
SELECT
c.*
FROM
{$CFG->prefix}data_content as c,
{$CFG->prefix}data_records as r
WHERE
c.recordid = r.id AND
r.dataid = {$database_id}
ORDER BY
c.fieldid
";
$data = get_records_sql($query);
$records = array();
if ($data){
foreach($data as $aDatum){
if($typematch == '*' || preg_match("/\\b{$fieldset[$aDatum->fieldid]->type}\\b/", $typematch)){
if (!isset($records[$aDatum->recordid])){
$records[$aDatum->recordid]['_first'] = $aDatum->content.' '.$aDatum->content1.' '.$aDatum->content2.' '.$aDatum->content3.' '.$aDatum->content4.' ';
}
else{
$records[$aDatum->recordid][$fieldset[$aDatum->fieldid]->name] = $aDatum->content.' '.$aDatum->content1.' '.$aDatum->content2.' '.$aDatum->content3.' '.$aDatum->content4.' ';
}
}
}
}
return $records;
} //data_get_records
/**
* fetches all the comments for a given database
* @param database_id the database
* @return an array of objects representing the data record comments.
*/
function data_get_comments($database_id) {
global $CFG;
$query = "
SELECT
c.id,
r.groupid,
c.userid,
c.recordid,
c.content,
c.created,
c.modified,
r.dataid
FROM
{$CFG->prefix}data_comments as c,
{$CFG->prefix}data_records as r
WHERE
c.recordid = r.id
";
$comments = get_records_sql($query);
return $comments;
} //data_get_comments
/**
* part of search engine API
*
*/
function data_iterator() {
$databases = get_records('data');
return $databases;
} //data_iterator
/**
* part of search engine API
* @param database the database instance
* @return an array of searchable documents
*/
function data_get_content_for_index(&$database) {
$documents = array();
$recordTitles = array();
$coursemodule = get_field('modules', 'id', 'name', 'data');
$cm = get_record('course_modules', 'course', $database->course, 'module', $coursemodule, 'instance', $database->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// getting records for indexing
$records_content = data_get_records($database->id, 'text');
if ($records_content){
foreach(array_keys($records_content) as $aRecordId) {
// extract title as first record in order
$first = $records_content[$aRecordId]['_first'];
unset($records_content[$aRecordId]['_first']);
// concatenates all other texts
foreach($records_content[$aRecordId] as $aField){
$content = @$content.' '.$aField;
}
if (strlen($content) > 0) {
unset($recordMetaData);
$recordMetaData = get_record('data_records', 'id', $aRecordId);
$recordMetaData->title = $first;
$recordTitles[$aRecordId] = $first;
$recordMetaData->content = $content;
$documents[] = new DataSearchDocument(get_object_vars($recordMetaData), $database->course, $context->id);
}
}
}
// getting comments for indexing
$records_comments = data_get_comments($database->id);
if ($records_comments){
foreach($records_comments as $aComment){
$aComment->title = $recordsTitle[$aComment->recordid];
$documents[] = new DataCommentSearchDocument(get_object_vars($aComment), $database->course, $context->id);
}
}
return $documents;
} //data_get_content_for_index
/**
* returns a single data search document based on a data entry id
* @param id the id of the record
* @param the type of the information
* @return a single searchable document
*/
function data_single_document($id, $itemtype) {
if ($itemtype == 'record'){
// get main record
$recordMetaData = get_record('data_records', 'id', $id);
// get context
$record_course = get_field('data', 'course', 'id', $recordMetaData->dataid);
$coursemodule = get_field('modules', 'id', 'name', 'data');
$cm = get_record('course_modules', 'course', $record_course, 'module', $coursemodule, 'instance', $recordMetaData->dataid);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// compute text
$recordData = get_records_select('data_content', "recordid = $id AND type = 'text'", 'recordid');
$accumulator = '';
if ($recordData){
$first = $recordData[0];
if (count($recordData) > 1){
$others = array_splice($recordData, 0, 1);
foreach($others as $aDatum){
$accumulator .= $data->content.' '.$data->content1.' '.$data->content2.' '.$data->content3.' '.$data->content4.' ';
}
}
}
// add extra fields
$recordMetaData->title = $first;
$recordMetaData->content = $accumulator;
// make document
$documents[] = new DataSearchDocument(get_object_vars($recordMetaData), $record_course, $context->id);
}
elseif($itemtype == 'comment'){
// get main records
$comment = get_record('data_comments', 'id', $id);
$record = get_record('data_records', 'id', $comment->recordid);
// get context
$record_course = get_field('data', 'course', 'id', $record->dataid);
$coursemodule = get_field('modules', 'id', 'name', 'data');
$cm = get_record('course_modules', 'course', $record_course, 'module', $coursemodule, 'instance', $recordMetaData->dataid);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
// add extra fields
$comment->title = get_field('search_document', 'title', 'docid', $record->id, 'itemtype', 'record');
$comment->dataid = $record->dataid;
$comment->groupid = $record->groupid;
// make document
$documents[] = new DataCommentSearchDocument(get_object_vars($comment), $record_course, $context->id);
}
else{
mtrace('Error : bad or missing item type');
}
} //data_single_document
/**
* dummy delete function that packs id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function data_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //data_delete
/**
* returns the var names needed to build a sql query for addition/deletions
*
*/
function data_db_names() {
//[primary id], [table name], [time created field name], [time modified field name]
return array(
array('id', 'data_records', 'timecreated', 'timemodified', 'record'),
array('id', 'data_comments', 'created', 'modified', 'comment')
);
} //data_db_names
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* When this point is reached, we already know that :
* - user is legitimate in the surrounding context
* - user may be guest and guest access is allowed to the module
* - the function may perform local checks within the module information logic
* @param path the access path to the module script code
* @param itemtype the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by itemtype. In databases, this id
* points out an indexed data record page.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function data_check_text_access($path, $itemtype, $this_id, $user, $group_id, $context_id){
global $CFG;
// get the database object and all related stuff
if ($itemtype == 'record'){
$record = get_record('data_records', 'id', $this_id);
}
elseif($itemtype == 'comment'){
$comment = get_record('data_comments', 'id', $this_id);
$record = get_record('data_records', 'id', $comment->recordid);
}
else{
// we do not know what type of information is required
return false;
}
$data = get_record('data', 'id', $record->dataid);
$course = get_record('course', 'id', $data->course);
$module_context = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $module_context->instance);
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $module_context)) return false;
//group consistency check : checks the following situations about groups
// trap if user is not same group and groups are separated
$current_group = get_current_group($course->id);
if ((groupmode($course) == SEPARATEGROUPS) && !ismember($group_id) && !has_capability('moodle/site:accessallgroups', $module_context)) return false;
//ownership check : checks the following situations about user
// trap if user is not owner and has cannot see other's entries
if ($itemtype == 'record'){
if ($user->id != $record->userid && !has_capability('mod/data:viewentry', $module_context) && !has_capability('mod/data:manageentries', $module_context)) return false;
}
//approval check
// trap if unapproved and has not approval capabilities
// TODO : report a potential capability lack of : mod/data:approve
$approval = get_field('data_records', 'approved', 'id', $record->id);
if (!$approval && !isteacher($data->course) && !has_capability('mod/data:manageentries', $module_context)) return false;
//minimum records to view check
// trap if too few records
// TODO : report a potential capability lack of : mod/data:viewhiddenentries
$recordsAmount = count_records('data_records', 'dataid', $data->id);
if ($data->requiredentriestoview > $recordsAmount && !isteacher($data->course) && !has_capability('mod/data:manageentries', $module_context)) return false;
//opening periods check
// trap if user has not capability to see hidden records and date is out of opening range
// TODO : report a potential capability lack of : mod/data:viewhiddenentries
$now = usertime(time());
if ($data->timeviewfrom > 0)
if ($now < $data->timeviewfrom && !isteacher($data->course) && !has_capability('mod/data:manageentries', $module_context)) return false;
if ($data->timeviewto > 0)
if ($now > $data->timeviewto && !isteacher($data->course) && !has_capability('mod/data:manageentries', $module_context)) return false;
return true;
} // data_check_text_access
?>

View file

@ -1,11 +1,34 @@
<?php
/* Base search document from which other module/block types can
* extend.
* */
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* Base search document from which other module/block types can
* extend.
**/
abstract class SearchDocument extends Zend_Search_Lucene_Document {
public function __construct(&$doc, &$data, $document_type, $course_id, $group_id) {
abstract class SearchDocument extends Zend_Search_Lucene_Document {
public function __construct(&$doc, &$data, $course_id, $group_id, $user_id, $path) {
//document identification and indexing
$this->addField(Zend_Search_Lucene_Field::Keyword('docid', $doc->docid));
//document type : the name of the Moodle element that manages it
$this->addField(Zend_Search_Lucene_Field::Keyword('doctype', $doc->documenttype));
//allows subclassing information from complex modules.
$this->addField(Zend_Search_Lucene_Field::Keyword('itemtype', $doc->itemtype));
//caches the course context.
$this->addField(Zend_Search_Lucene_Field::Keyword('course_id', $course_id));
//caches the originator's group.
$this->addField(Zend_Search_Lucene_Field::Keyword('group_id', $group_id));
//caches the originator if any
$this->addField(Zend_Search_Lucene_Field::Keyword('user_id', $user_id));
// caches the context of this information. i-e, the context in which this information
// is being produced/attached. Speeds up the "check for access" process as context in
// which the information resides (a course, a module, a block, the site) is stable.
$this->addField(Zend_Search_Lucene_Field::UnIndexed('context_id', $doc->contextid));
//data for document
$this->addField(Zend_Search_Lucene_Field::Text('title', $doc->title));
$this->addField(Zend_Search_Lucene_Field::Text('author', $doc->author));
$this->addField(Zend_Search_Lucene_Field::UnStored('contents', $doc->contents));
@ -15,10 +38,21 @@
//additional data added on a per-module basis
$this->addField(Zend_Search_Lucene_Field::Binary('data', serialize($data)));
$this->addField(Zend_Search_Lucene_Field::Keyword('doctype', $document_type));
$this->addField(Zend_Search_Lucene_Field::Keyword('course_id', $course_id));
$this->addField(Zend_Search_Lucene_Field::Keyword('group_id', $group_id));
// adding a path allows the document to know where to find specific library calls
// for checking access to a module or block content. The Lucene records should only
// be responsible to bring back to that call sufficient and consistent information
// in order to perform the check.
$this->addField(Zend_Search_Lucene_Field::UnIndexed('path', $path));
/*
// adding a capability set required for viewing. -1 if no capability required.
// the capability required for viewing is depending on the local situation
// of the document. each module should provide this information when pushing
// out search document structure. Although capability model should be kept flat
// there is no exclusion some module or block developpers use logical combinations
// of multiple capabilities in their code. This possibility should be left open here.
$this->addField(Zend_Search_Lucene_Field::UnIndexed('capabilities', $caps));
*/
} //constructor
} //SearchDocument
} //SearchDocument
?>

View file

@ -1,105 +1,159 @@
<?php
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for forum activity module
* This file contains the mapping between a forum post and it's indexable counterpart,
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/forum/lib.php
**/
/* see wiki_document.php for descriptions */
/* see wiki_document.php for descriptions */
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/forum/lib.php");
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/forum/lib.php");
/*
* a class for representing searchable information
*
**/
class ForumSearchDocument extends SearchDocument {
class ForumSearchDocument extends SearchDocument {
public function __construct(&$post, $forum_id, $course_id, $group_id) {
/**
* constructor
*
*/
public function __construct(&$post, $forum_id, $course_id, $itemtype, $context_id) {
// generic information
$doc->docid = $post['id'];
$doc->documenttype = SEARCH_TYPE_FORUM;
$doc->itemtype = $itemtype;
$doc->contextid = $context_id;
$doc->title = $post['subject'];
$doc->author = $post['firstname']." ".$post['lastname'];
$doc->contents = $post['message'];
$doc->date = $post['created'];
$doc->url = forum_make_link($post['discussion'], $post['id']);
// module specific information
$data->forum = $forum_id;
$data->discussion = $post['discussion'];
parent::__construct($doc, $data, SEARCH_TYPE_FORUM, $course_id, $group_id);
parent::__construct($doc, $data, $course_id, $post['groupid'], $post['userid'], PATH_FOR_SEARCH_TYPE_FORUM);
} //constructor
} //ForumSearchDocument
} //ForumSearchDocument
function forum_make_link($discussion_id, $post_id) {
/**
* constructs a valid link to a chat content
* @param discussion_id the discussion
* @param post_id the id of a single post
* @return a well formed link to forum message display
*/
function forum_make_link($discussion_id, $post_id) {
global $CFG;
return $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion_id.'#'.$post_id;
} //forum_make_link
} //forum_make_link
function forum_iterator() {
//no @ = Undefined index: 82 in moodle/lib/datalib.php on line 2671
return @get_all_instances_in_courses("forum", get_courses());
} //forum_iterator
/**
* search standard API
*
*/
function forum_iterator() {
$forums = get_records('forum');
return $forums;
} //forum_iterator
/**
* search standard API
* @param forum a forum instance
* @return an array of searchable documents
*/
function forum_get_content_for_index(&$forum) {
function forum_get_content_for_index(&$forum) {
$documents = array();
if (!$forum) return $documents;
$posts = forum_get_discussions_fast($forum->id);
if (!$posts) return $documents;
while (!$posts->EOF) {
$post = $posts->fields;
if (is_array($post)) {
if (strlen($post['message']) > 0 && ($post['deleted'] != 1)) {
$documents[] = new ForumSearchDocument($post, $forum->id, $forum->course, $post['groupid']);
} //if
if ($children = forum_get_child_posts_fast($post['id'], $forum->id)) {
while (!$children->EOF) {
$child = $children->fields;
if (strlen($child['message']) > 0 && ($child['deleted'] != 1)) {
$documents[] = new ForumSearchDocument($child, $forum->id, $forum->course, $post['groupid']);
} //if
$children->MoveNext();
} //foreach
} //if
} //if
$posts->MoveNext();
} //foreach
$coursemodule = get_field('modules', 'id', 'name', 'forum');
$cm = get_record('course_modules', 'course', $forum->course, 'module', $coursemodule, 'instance', $forum->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
foreach($posts as $aPost) {
$aPost->itemtype = 'head';
if ($aPost) {
if (strlen($aPost->message) > 0) {
$documents[] = new ForumSearchDocument(get_object_vars($aPost), $forum->id, $forum->course, 'head', $context->id);
}
if ($children = forum_get_child_posts_fast($aPost->id, $forum->id)) {
foreach($children as $aChild) {
$aChild->itemtype = 'post';
if (strlen($aChild->message) > 0) {
$documents[] = new ForumSearchDocument(get_object_vars($child), $forum->id, $forum->course, 'post', $context->id);
}
}
}
}
}
return $documents;
} //forum_get_content_for_index
} //forum_get_content_for_index
//returns a single forum search document based on a forum_entry id
function forum_single_document($id) {
$posts = get_recordset('forum_posts', 'id', $id);
$post = $posts->fields;
/**
* returns a single forum search document based on a forum entry id
* @param id an id for a single information stub
* @param itemtype the type of information
*/
function forum_single_document($id, $itemtype) {
$discussions = get_recordset('forum_discussions', 'id', $post['discussion']);
$discussion = $discussions->fields;
// both known item types are posts so get them the same way
$post = get_record('forum_posts', 'id', $id);
$discussion = get_record('forum_discussions', 'id', $post->discussion);
$coursemodule = get_field('modules', 'id', 'name', 'forum');
$cm = get_record('course_modules', 'course', $discussion->course, 'module', $coursemodule, 'instance', $discussion->forum);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
return new ForumSearchDocument(get_object_vars($post), $discussion->forum, $discussion->course, $itemtype, $context->id);
} //forum_single_document
$forums = get_recordset('forum', 'id', $discussion['forum']);
$forum = $forums->fields;
/**
* dummy delete function that aggregates id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function forum_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //forum_delete
return new ForumSearchDocument($post, $forum['id'], $forum['course'], $post['groupid']);
} //forum_single_document
function forum_delete($info) {
return $info;
} //forum_delete
//returns the var names needed to build a sql query for addition/deletions
function forum_db_names() {
/**
* returns the var names needed to build a sql query for addition/deletions
*
*/
function forum_db_names() {
//[primary id], [table name], [time created field name], [time modified field name]
return array('id', 'forum_posts', 'created', 'modified');
} //forum_db_names
return array(
array('id', 'forum_posts', 'created', 'modified', 'head', 'parent = 0'),
array('id', 'forum_posts', 'created', 'modified', 'post', 'parent != 0')
);
} //forum_db_names
//reworked faster version from /mod/forum/lib.php
function forum_get_discussions_fast($forum) {
/**
* reworked faster version from /mod/forum/lib.php
* @param forum_id a forum identifier
* @return an array of posts
*/
function forum_get_discussions_fast($forum_id) {
global $CFG, $USER;
$timelimit='';
if (!empty($CFG->forum_enabletimedposts)) {
if (!((isadmin() and !empty($CFG->admineditalways)) || isteacher(get_field('forum', 'course', 'id', $forum)))) {
if (!((isadmin() and !empty($CFG->admineditalways)) || isteacher(get_field('forum', 'course', 'id', $forum_id)))) {
$now = time();
$timelimit = " AND ((d.timestart = 0 OR d.timestart <= '$now') AND (d.timeend = 0 OR d.timeend > '$now')";
if (!empty($USER->id)) {
@ -109,27 +163,107 @@
}
}
return get_recordset_sql("SELECT p.id, p.subject, p.discussion, p.message,
p.deleted, d.groupid, u.firstname, u.lastname
FROM {$CFG->prefix}forum_discussions d
JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
JOIN {$CFG->prefix}user u ON p.userid = u.id
WHERE d.forum = '$forum'
AND p.parent = 0
$query = "
SELECT
p.id,
p.subject,
p.discussion,
p.message,
p.created,
d.groupid,
p.userid,
u.firstname,
u.lastname
FROM
{$CFG->prefix}forum_discussions d
JOIN
{$CFG->prefix}forum_posts p
ON
p.discussion = d.id
JOIN
{$CFG->prefix}user u
ON
p.userid = u.id
WHERE
d.forum = '{$forum_id}' AND
p.parent = 0
$timelimit
ORDER BY d.timemodified DESC");
} //forum_get_discussions_fast
ORDER BY
d.timemodified DESC
";
return get_records_sql($query);
} //forum_get_discussions_fast
//reworked faster version from /mod/forum/lib.php
function forum_get_child_posts_fast($parent, $forumid) {
/**
* reworked faster version from /mod/forum/lib.php
* @param parent the id of the first post within the discussion
* @param forum_id the forum identifier
* @return an array of posts
*/
function forum_get_child_posts_fast($parent, $forum_id) {
global $CFG;
return get_recordset_sql("SELECT p.id, p.subject, p.discussion, p.message, p.deleted,
$forumid AS forum, u.firstname, u.lastname
FROM {$CFG->prefix}forum_posts p
LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
WHERE p.parent = '$parent'
ORDER BY p.created ASC");
} //forum_get_child_posts_fast
$query = "
SELECT
p.id,
p.subject,
p.discussion,
p.message,
p.created,
{$forum_id} AS forum,
p.userid,
u.firstname,
u.lastname
FROM
{$CFG->prefix}forum_posts p
LEFT JOIN
{$CFG->prefix}user u
ON
p.userid = u.id
WHERE
p.parent = '{$parent}'
ORDER BY
p.created ASC
";
return get_records_sql($query);
} //forum_get_child_posts_fast
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* When this point is reached, we already know that :
* - user is legitimate in the surrounding context
* - user may be guest and guest access is allowed to the module
* - the function may perform local checks within the module information logic
* @param path the access path to the module script code
* @param itemtype the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by itemtype. In forums, this id
* points out the individual post.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function forum_check_text_access($path, $itemtype, $this_id, $user, $group_id){
global $CFG;
include_once("{$CFG->dirroot}/{$path}/lib.php");
// get the glossary object and all related stuff
$post = get_record('forum_posts', 'id', $this_id);
$dicussion = get_record('forum_discussion', 'id', $post->discussion);
$course = get_record('course', 'id', $discussion->course);
$context_module = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $context_module->instanceid);
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context_module)) return false;
// approval check : entries should be approved for being viewed, or belongs to the user
if (!$post->mailed && !has_capability('mod/forum:viewhiddentimeposts')) return false;
// group check : entries should be in accessible groups
$current_group = get_current_group($course->id);
if ((groupmode($course, $cm) == SEPARATEGROUPS) && ($group_id != $current_group) && !has_capability('mod/forum:viewdiscussionsfromallgroups')) return false;
return true;
} //forum_check_text_access
?>

View file

@ -1,88 +1,235 @@
<?php
/* This document illustrates how easy it is to add a module to
* the search index - the only modifications made were creating
* this file, and adding the SEARCH_TYPE_GLOSSARY constant to
* search/lib.php - everything else is automatically handled
* by the indexer script.
* */
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for glossary activity module
* This file contains a mapping between a glossary entry and it's indexable counterpart,
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/glossary/lib.php
**/
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/search/documents/document.php");
class GlossarySearchDocument extends SearchDocument {
public function __construct(&$entry, $glossary_id, $course_id, $group_id) {
/*
* a class for representing searchable information
*
**/
class GlossarySearchDocument extends SearchDocument {
/**
* document constructor
*
*/
public function __construct(&$entry, $course_id, $context_id) {
// generic information; required
$doc->docid = $entry['id'];
$doc->documenttype = SEARCH_TYPE_GLOSSARY;
$doc->itemtype = 'standard';
$doc->contextid = $context_id;
$doc->title = $entry['concept'];
$doc->date = $entry['timecreated'];
$user = get_recordset('user', 'id', $entry['userid'])->fields;
$doc->author = $user['firstname'].' '.$user['lastname'];
$doc->contents = $entry['definition'];
if ($entry['userid'])
$user = get_record('user', 'id', $entry['userid']);
$doc->author = ($user ) ? $user->firstname.' '.$user->lastname : '' ;
$doc->contents = strip_tags($entry['definition']);
$doc->url = glossary_make_link($entry['id']);
// module specific information; optional
$data->glossary = $entry['glossaryid'];
// construct the parent class
parent::__construct($doc, $data, $course_id, -1, $entry['userid'], PATH_FOR_SEARCH_TYPE_GLOSSARY);
} //constructor
} //GlossarySearchDocument
/*
* a class for representing searchable information
*
**/
class GlossaryCommentSearchDocument extends SearchDocument {
/**
* document constructor
*
*/
public function __construct(&$entry, $glossary_id, $course_id, $context_id) {
// generic information; required
$doc->docid = $entry['id'];
$doc->documenttype = SEARCH_TYPE_GLOSSARY;
$doc->itemtype = 'comment';
$doc->contextid = $context_id;
$doc->title = get_string('commenton', 'search') . ' ' . $entry['concept'];
$doc->date = $entry['timemodified'];
if ($entry['userid'])
$user = get_record('user', 'id', $entry['userid']);
$doc->author = ($user ) ? $user->firstname.' '.$user->lastname : '' ;
$doc->contents = strip_tags($entry['entrycomment']);
$doc->url = glossary_make_link($entry['entryid']);
// module specific information; optional
$data->glossary = $glossary_id;
// construct the parent class
parent::__construct($doc, $data, SEARCH_TYPE_GLOSSARY, $course_id, $group_id);
parent::__construct($doc, $data, $course_id, -1, $entry['userid'], PATH_FOR_SEARCH_TYPE_GLOSSARY);
} //constructor
} //GlossarySearchDocument
} //GlossaryCommentSearchDocument
function glossary_make_link($entry_id) {
/**
* constructs valid access links to information
* @param entry_id the id of the glossary entry
* @return a full featured link element as a string
*/
function glossary_make_link($entry_id) {
global $CFG;
//links directly to entry
//return $CFG->wwwroot.'/mod/glossary/showentry.php?eid='.$entry_id;
// return $CFG->wwwroot.'/mod/glossary/showentry.php?eid='.$entry_id;
//preserve glossary pop-up, be careful where you place your ' and "s
// TOO LONG URL
// Suggestion : bounce on popup within the glossarie's showentry page
// preserve glossary pop-up, be careful where you place your ' and "s
//this function is meant to return a url that is placed between href='[url here]'
return "$CFG->wwwroot/mod/glossary/showentry.php?eid=$entry_id' onclick='return openpopup(\"/mod/glossary/showentry.php?eid=$entry_id\", \"entry\", \"menubar=0,location=0,scrollbars,resizable,width=600,height=450\", 0);";
} //glossary_make_link
return "$CFG->wwwroot/mod/glossary/showentry.php?eid=$entry_id' onclick='return openpopup(\"/mod/glossary/showentry.php?eid=$entry_id\", \"entry\", DEFAULT_POPUP_SETTINGS, 0);";
} //glossary_make_link
function glossary_iterator() {
return get_all_instances_in_courses("glossary", get_courses());
} //glossary_iterator
/**
* part of search engine API
*
*/
function glossary_iterator() {
$glossaries = get_records('glossary');
return $glossaries;
} //glossary_iterator
/**
* part of search engine API
* @glossary a glossary instance
* @return an array of searchable documents
*/
function glossary_get_content_for_index(&$glossary) {
// get context
$coursemodule = get_field('modules', 'id', 'name', 'glossary');
$cm = get_record('course_modules', 'course', $glossary->course, 'module', $coursemodule, 'instance', $glossary->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
function glossary_get_content_for_index(&$glossary) {
$documents = array();
$entryIds = array();
// index entries
$entries = get_records('glossary_entries', 'glossaryid', $glossary->id);
if ($entries){
foreach($entries as $entry) {
$concepts[$entry->id] = $entry->concept;
if (strlen($entry->definition) > 0) {
$entryIds[] = $entry->id;
$documents[] = new GlossarySearchDocument(get_object_vars($entry), $glossary->course, $context->id);
}
}
}
$entries = get_recordset('glossary_entries', 'glossaryid', $glossary->id);
while (!$entries->EOF) {
$entry = $entries->fields;
if ($entry and strlen($entry['definition']) > 0) {
$documents[] = new GlossarySearchDocument($entry, $glossary->id, $glossary->course, -1);
} //if
$entries->MoveNext();
} //foreach
// index comments
if (count($entryIds)){
$entryIdList = implode(',', $entryIds);
$comments = get_records_list('glossary_comments', 'entryid', $entryIdList);
if ($comments){
foreach($comments as $comment) {
if (strlen($comment->entrycomment) > 0) {
$comment->concept = $concepts[$comment->entryid];
$documents[] = new GlossaryCommentSearchDocument(get_object_vars($comment), $glossary->id, $glossary->course, $context->id);
}
}
}
}
return $documents;
} //glossary_get_content_for_index
} //glossary_get_content_for_index
//returns a single glossary search document based on a glossary_entry id
function glossary_single_document($id) {
$entries = get_recordset('glossary_entries', 'id', $id);
$entry = $entries->fields;
/**
* part of search engine API
* @param id the glossary entry identifier
* @itemtype the type of information
* @return a single search document based on a glossary entry
*/
function glossary_single_document($id, $itemtype) {
if ($itemtype == 'standard'){
$entry = get_record('glossary_entries', 'id', $id);
}
elseif ($itemtype == 'comment'){
$comment = get_record('glossary_comments', 'id', $id);
$entry = get_record('glossary_entries', 'id', $comment->entryid);
}
$glossary_course = get_field('glossary', 'course', 'id', $entry->glossaryid);
$coursemodule = get_field('modules', 'id', 'name', 'glossary');
$cm = get_record('course_modules', 'course', $glossary_course, 'module', $coursemodule, 'instance', $entry->glossaryid);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
if ($itemtype == 'standard'){
return new GlossarySearchDocument(get_object_vars($entry), $glossary_course, $context->id);
}
elseif ($itemtype == 'comment'){
return new GlossaryCommentSearchDocument(get_object_vars($comment), $entry->glossaryid, $glossary_course, $context->id);
}
} //glossary_single_document
$glossaries = get_recordset('glossary', 'id', $entry['glossaryid']);
$glossary = $glossaries->fields;
/**
* dummy delete function that packs id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function glossary_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //glossary_delete
return new GlossarySearchDocument($entry, $entry['glossaryid'], $glossary['course'], -1);
} //glossary_single_document
//dummy delete function that converts docid from the search table to itself..
//this was here for a reason, but I can't remember it at the moment.
function glossary_delete($info) {
return $info;
} //glossary_delete
//returns the var names needed to build a sql query for addition/deletions
function glossary_db_names() {
/**
* returns the var names needed to build a sql query for addition/deletions
*
*/
function glossary_db_names() {
//[primary id], [table name], [time created field name], [time modified field name]
return array('id', 'glossary_entries', 'timecreated', 'timemodified');
} //glossary_db_names
return array(
array('id', 'glossary_entries', 'timecreated', 'timemodified', 'standard'),
array('id', 'glossary_comments', 'timemodified', 'timemodified', 'comment')
);
} //glossary_db_names
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* When this point is reached, we already know that :
* - user is legitimate in the surrounding context
* - user may be guest and guest access is allowed to the module
* - the function may perform local checks within the module information logic
* @param path the access path to the module script code
* @param itemtype the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by itemtype. In glossaries, this id
* points out the indexed glossary item.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function glossary_check_text_access($path, $itemtype, $this_id, $user, $group_id, $context_id){
global $CFG;
// get the glossary object and all related stuff
$entry = get_record('glossary_entries', 'id', $id);
$glossary = get_record('glossary', 'id', $entry->glossaryid);
$course = get_record('course', 'id', $glossary->course);
$module_context = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $module_context->instance);
if (!$cm->visible && !has_capability('moodle/course:viewhiddenactivities', $module_context)) return false;
//approval check : entries should be approved for being viewed, or belongs to the user unless the viewer can approve them or manage them
if (!$entry->approved && $user != $entry->userid && !has_capability('mod/glossary:approve', $module_context) && !has_capability('mod/glossary:manageentries', $module_context)) return false;
return true;
} //glossary_check_text_access
?>

View file

@ -0,0 +1,47 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
/*
* MS Word extractor
*/
function get_text_for_indexing_doc(&$resource){
global $CFG, $USER;
// SECURITY : do not allow non admin execute anything on system !!
if (!isadmin($USER->id)) return;
// just call pdftotext over stdout and capture the output
if (!empty($CFG->block_search_word_to_text_cmd)){
if (!file_exists("{$CFG->dirroot}/{$CFG->block_search_word_to_text_cmd}")){
mtrace('Error with MSWord to text converter command : exectuable not found.');
}
else{
$file = $CFG->dataroot.'/'.$resource->course.'/'.$resource->reference;
$text_converter_cmd = "{$CFG->dirroot}/{$CFG->block_search_word_to_text_cmd} $file";
if ($CFG->block_search_word_to_text_env){
putenv($CFG->block_search_word_to_text_env);
}
$result = shell_exec($text_converter_cmd);
if ($result){
return mb_convert_encoding($result, 'UTF8', 'auto');
}
else{
mtrace('Error with MSWord to text converter command : execution failed.');
return '';
}
}
}
else {
mtrace('Error with MSWord to text converter command : command not set up. Execute once search block configuration.');
return '';
}
}
?>

View file

@ -0,0 +1,39 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
function get_text_for_indexing_htm(&$resource){
global $CFG, $USER;
// SECURITY : do not allow non admin execute anything on system !!
if (!isadmin($USER->id)) return;
// just get text
$text = implode('', file("{$CFG->dataroot}/{$resource->course}/($resource->reference)"));
// extract keywords and other interesting meta information and put it back as real content for indexing
if (preg_match('/(.*)<meta ([^>]*)>(.*)/is',$text, $matches)){
$prefix = $matches[1];
$meta_attributes = $matches[2];
$suffix = $matches{3];
if (preg_match('/name="(keywords|description)"/i', $attributes)){
preg_match('/content="[^"]+"/i', $attributes, $matches);
$text = $prefix.' '.$matches[1].' '.$suffix;
}
}
// filter all html tags
// $text = clean_text($text, FORMAT_PLAIN);
// NOTE : this is done in ResourceSearchDocument __constructor
if (!empty($CFG->block_search_limit_index_body)){
$text = shorten($text, $CFG->block_search_limit_index_body);
}
return $text;
}
?>

View file

@ -0,0 +1,17 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
function get_text_for_indexing_html(&$resource){
// wraps to htm handler
include_once 'physical_htm.php';
return get_text_for_indexing_htm($resource);
}
?>

View file

@ -0,0 +1,41 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
function get_text_for_indexing_pdf(&$resource){
global $CFG, $USER;
// SECURITY : do not allow non admin execute anything on system !!
if (!isadmin($USER->id)) return;
// just call pdftotext over stdout and capture the output
if (!empty($CFG->block_search_pdf_to_text_cmd)){
preg_match("/^\S+/", $CFG->block_search_pdf_to_text_cmd, $matches);
if (!file_exists("{$CFG->dirroot}/{$matches[0]}")){
mtrace('Error with pdf to text converter command : exectuable not found.');
}
else{
$file = $CFG->dataroot.'/'.$resource->course.'/'.$resource->reference;
$text_converter_cmd = "{$CFG->dirroot}/{$CFG->block_search_pdf_to_text_cmd} $file -";
$result = shell_exec($text_converter_cmd);
if ($result){
return $result;
}
else{
mtrace('Error with pdf to text converter command : execution failed.');
return '';
}
}
}
else {
mtrace('Error with pdf to text converter command : command not set up. Execute once search block configuration.');
return '';
}
}
?>

View file

@ -0,0 +1,80 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
/**
* first implementation is a trivial heuristic based on ppt character stream :
* text sequence always starts with a 00 9F 0F 04 sequence followed by a 15 bytes
* sequence
* In this sequence is a A8 0F or A0 0F or AA 0F followed by a little-indian encoding of text buffer size
* A8 0F denotes for ASCII text (local system monobyte encoding)
* A0 0F denotes for UTF-16 encoding
* AA 0F are non textual sequences
* texts are either in ASCII or UTF-16
* text ends on a new sequence start, or on a 00 00 NULL UTF-16 end of stream
*
* based on these following rules, here is a little empiric texte extractor for PPT
*/
function get_text_for_indexing_ppt(&$resource){
global $CFG, $USER;
$indextext = null;
// SECURITY : do not allow non admin execute anything on system !!
if (!isadmin($USER->id)) return;
$text = implode('', file("{$CFG->dataroot}/{$resource->course}/{$resource->reference}"));
$remains = $text;
$fragments = array();
while (preg_match('/\x00\x9F\x0F\x04.{9}(......)(.*)/s', $remains, $matches)){
$unpacked = unpack("ncode/Llength", $matches[1]);
$sequencecode = $unpacked['code'];
$length = $unpacked['length'];
// print "length : ".$length." ; segment type : ".sprintf("%x", $sequencecode)."<br/>";
$followup = $matches[2];
// local system encoding sequence
if ($sequencecode == 0xA80F){
$aFragment = substr($followup, 0, $length);
$remains = substr($followup, $length);
$fragments[] = $aFragment;
}
// denotes unicode encoded sequence
elseif ($sequencecode == 0xA00F){
$aFragment = substr($followup, 0, $length);
// $aFragment = mb_convert_encoding($aFragment, 'UTF-16', 'UTF-8');
$aFragment = preg_replace('/\xA0\x00\x19\x20/s', "'", $aFragment); // some quotes
$aFragment = preg_replace('/\x00/s', "", $aFragment);
$remains = substr($followup, $length);
$fragments[] = $aFragment;
}
else{
$remains = $followup;
}
}
$indextext = implode(' ', $fragments);
$indextext = preg_replace('/\x19\x20/', "'", $indextext); // some quotes
$indextext = preg_replace('/\x09/', '', $indextext); // some extra chars
$indextext = preg_replace('/\x0D/', "\n", $indextext); // some quotes
$indextext = preg_replace('/\x0A/', "\n", $indextext); // some quotes
$indextextprint = implode('<hr/>', $fragments);
$logppt = fopen("C:/php5/logs/pptlog", "w");
fwrite($logppt, $indextext);
fclose($logppt);
if (!empty($CFG->block_search_limit_index_body)){
$indextext = shorten($text, $CFG->block_search_limit_index_body);
}
$indextext = mb_convert_encoding($indextext, 'UTF8', 'auto');
return $indextext;
}
?>

View file

@ -0,0 +1,24 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
function get_text_for_indexing_txt(&$resource){
global $CFG, $USER;
// SECURITY : do not allow non admin execute anything on system !!
if (!isadmin($USER->id)) return;
// just try to get text empirically from ppt binary flow
$text = implode('', file("{$CFG->dataroot}/{$resource->course}/{$resource->reference}"));
if (!empty($CFG->block_search_limit_index_body)){
$text = shorten($text, $CFG->block_search_limit_index_body);
}
return $text;
}
?>

View file

@ -0,0 +1,28 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* this is a format handler for getting text out of a proprietary binary format
* so it can be indexed by Lucene search engine
*/
function get_text_for_indexing_xml(&$resource){
global $CFG, $USER;
// SECURITY : do not allow non admin execute anything on system !!
if (!isadmin($USER->id)) return;
// just get text
$text = implode('', file("{$CFG->dataroot}/{$resource->course}/($resource->reference)"));
// filter out all xml tags
$text = preg_replace("/<[^>]*>/", ' ', $text);
if (!empty($CFG->block_search_limit_index_body)){
$text = shorten($text, $CFG->block_search_limit_index_body);
}
return $text;
}
?>

View file

@ -1,14 +1,34 @@
<?php
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for all resources
* This file contains the mapping between a resource and it's indexable counterpart,
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/resource/lib.php
**/
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/resource/lib.php");
class ResourceSearchDocument extends SearchDocument {
public function __construct(&$resource) {
/*
* a class for representing searchable information
*
**/
class ResourceSearchDocument extends SearchDocument {
public function __construct(&$resource, $context_id) {
// generic information; required
$doc->docid = $resource['id'];
$doc->docid = $resource['trueid'];
$doc->documenttype = SEARCH_TYPE_RESOURCE;
$doc->itemtype = $resource['type'];
$doc->contextid = $context_id;
$doc->title = strip_tags($resource['name']);
$doc->date = $resource['timemodified'];
$doc->author = '';
$doc->contents = strip_tags($resource['summary']).' '.strip_tags($resource['alltext']);
$doc->url = resource_make_link($resource['id']);
@ -17,70 +37,271 @@
$data = array();
// construct the parent class
parent::__construct($doc, $data, SEARCH_TYPE_RESOURCE, $resource['course'], -1);
parent::__construct($doc, $data, $resource['course'], 0, 0, PATH_FOR_SEARCH_TYPE_RESOURCE);
} //constructor
} //ResourceSearchDocument
} //ResourceSearchDocument
function resource_make_link($resource_id) {
/**
* constructs valid access links to information
* @param resourceId the of the resource
* @return a full featured link element as a string
*/
function resource_make_link($resource_id) {
global $CFG;
return $CFG->wwwroot.'/mod/resource/view.php?r='.$resource_id;
} //resource_make_link
function resource_iterator() {
return $CFG->wwwroot.'/mod/resource/view.php?id='.$resource_id;
} //resource_make_link
/**
* part of standard API
*
*/
function resource_iterator() {
//trick to leave search indexer functionality intact, but allow
//this document to only use the below function to return info
//to be searched
return array(true);
} //resource_iterator
//this function does not need a content iterator, returns all the info
//itself; remember to fake the iterator array though
function resource_get_content_for_index(&$notneeded) {
/**
* part of standard API
* this function does not need a content iterator, returns all the info
* itself;
* @param notneeded to comply API, remember to fake the iterator array though
* @return an array of searchable documents
*/
function resource_get_content_for_index(&$notneeded) {
global $CFG;
// starting with Moodle native resources
$documents = array();
$query = "
SELECT
id as trueid,
r.*
FROM
{$CFG->prefix}resource as r
WHERE
alltext != '' AND
alltext != ' ' AND
alltext != '&nbsp;' AND
type != 'file'
";
$resources = get_records_sql($query);
$resources = get_recordset_sql('SELECT *
FROM {$CFG->prefix}resource
WHERE alltext NOT LIKE ""
AND alltext NOT LIKE " "
AND alltext NOT LIKE "&nbsp;"
AND TYPE != "file"');
foreach($resources as $aResource){
$coursemodule = get_field('modules', 'id', 'name', 'resource');
$cm = get_record('course_modules', 'course', $aResource->course, 'module', $coursemodule, 'instance', $aResource->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
$aResource->id = $cm->id;
$documents[] = new ResourceSearchDocument(get_object_vars($aResource), $context->id);
mtrace("finished $aResource->name");
}
while (!$resources->EOF) {
$resource = $resources->fields;
if ($resource) {
$documents[] = new ResourceSearchDocument($resource);
} //if
$resources->MoveNext();
} //foreach
// special physical files handling
/**
* this sequence searches for a compatible physical stream handler for getting a text
* equivalence for the content.
*
*/
if (@$CFG->block_search_enable_file_indexing){
$query = "
SELECT
r.id as trueid,
cm.id as id,
r.course as course,
r.name as name,
r.summary as summary,
r.alltext as alltext,
r.reference as reference,
r.type as type,
r.timemodified as timemodified
FROM
{$CFG->prefix}resource as r,
{$CFG->prefix}course_modules as cm,
{$CFG->prefix}modules as m
WHERE
r.type = 'file' AND
cm.instance = r.id AND
cm.course = r.course AND
cm.module = m.id AND
m.name = 'resource'
";
$resources = get_records_sql($query);
// invokes external content extractor if exists.
foreach($resources as $aResource){
// fetches a physical indexable document and adds it to documents passed by ref
$coursemodule = get_field('modules', 'id', 'name', 'resource');
$cm = get_record('course_modules', 'id', $aResource->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
resource_get_physical_file($aResource, $context->id, false, $documents);
}
}
return $documents;
} //resource_get_content_for_index
} //resource_get_content_for_index
//returns a single resource search document based on a resource_entry id
function resource_single_document($id) {
$resources = get_recordset_sql('SELECT *
FROM {$CFG->prefix}resource
WHERE alltext NOT LIKE ""
AND alltext NOT LIKE " "
AND alltext NOT LIKE "&nbsp;"
AND TYPE != "file",
AND id = '.$id);
/**
* get text from a physical file
* @param resource a resource for which to fetch some representative text
* @param getsingle if true, returns a single search document, elsewhere return the array
* given as documents increased by one
* @param documents the array of documents, by ref, where to add the new document.
* @return a search document when unique or false.
*/
function resource_get_physical_file(&$resource, $context_id, $getsingle, &$documents = null){
global $CFG;
$resource = $resources->fields;
// cannot index empty references
if (empty($resource->reference)) return false;
return new ResourceSearchDocument($resource);
} //resource_single_document
// cannot index remote resources
if (resource_is_url($resource->reference)){
mtrace("Cannot index remote URLs.");
return false;
}
function resource_delete($info) {
return $info;
} //resource_delete
$fileparts = pathinfo($resource->reference);
// cannot index unknown or masked types
if (empty($fileparts['extension'])) {
return false;
}
//returns the var names needed to build a sql query for addition/deletions
function resource_db_names() {
// cannot index non existent file
$file = "{$CFG->dataroot}/{$resource->course}/{$resource->reference}";
if (!file_exists($file)){
mtrace("Missing resource file $file : will not be indexed.");
return false;
}
$ext = strtolower($fileparts['extension']);
// cannot index unallowed or unhandled types
if (!preg_match("/\b$ext\b/i", $CFG->block_search_filetypes)) {
mtrace($fileparts['extension'] . ' is not an allowed extension for indexing');
return false;
}
if (file_exists($CFG->dirroot.'/search/documents/physical_'.$ext.'.php')){
include_once($CFG->dirroot.'/search/documents/physical_'.$ext.'.php');
$function_name = 'get_text_for_indexing_'.$ext;
$resource->alltext = $function_name($resource);
if (!empty($resource->alltext)){
if ($getsingle){
return new ResourceSearchDocument(get_object_vars($resource));
}
else{
$documents[] = new ResourceSearchDocument(get_object_vars($resource), $context_id);
}
mtrace("finished file $resource->name as {$resource->reference}");
}
}
else{
mtrace("fulltext handler not found for $ext type");
}
return false;
}
/**
* part of standard API.
* returns a single resource search document based on a resource_entry id
* @param id the id of the accessible document
* @return a searchable object or null if failure
*/
function resource_single_document($id, $itemtype) {
global $CFG;
// rewriting with legacy moodle databse API
$query = "
SELECT
r.id as trueid,
cm.id as id,
r.course as course,
r.name as name,
r.summary as summary,
r.alltext as alltext,
r.reference as reference,
r.type as type,
r.timemodified as timemodified
FROM
{$CFG->prefix}resource as r,
{$CFG->prefix}course_modules as cm,
{$CFG->prefix}modules as m
WHERE
cm.instance = r.id AND
cm.course = r.course AND
cm.module = m.id AND
m.name = 'resource' AND
((r.type != 'file' AND
r.alltext != '' AND
r.alltext != ' ' AND
r.alltext != '&nbsp;') OR
r.type = 'file') AND
r.id = '{$id}'
";
$resource = get_record_sql($query);
if ($resource){
$coursemodule = get_field('modules', 'id', 'name', 'resource');
$cm = get_record('course_modules', 'id', $resource->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
if ($resource->type == 'file' && @$CFG->block_search_enable_file_indexing){
$document = resource_get_physical_file($resource, true, $context->id);
if (!$document) mtrace("Warning : this document {$resource->name} will not be indexed");
return $document;
}
else{
return new ResourceSearchDocument(get_object_vars($resource), $context->id);
}
}
mtrace("null resource");
return null;
} //resource_single_document
/**
* dummy delete function that aggregates id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function resource_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //resource_delete
/**
* returns the var names needed to build a sql query for addition/deletions
*
*/
function resource_db_names() {
//[primary id], [table name], [time created field name], [time modified field name], [additional where conditions for sql]
return array('id', 'resource', 'timemodified', 'timemodified', "WHERE alltext NOT LIKE '' AND alltext NOT LIKE ' ' AND alltext NOT LIKE '&nbsp;' AND TYPE != 'file'");
} //resource_db_names
return array(array('id', 'resource', 'timemodified', 'timemodified', '*', " (alltext != '' AND alltext != ' ' AND alltext != '&nbsp;' AND TYPE != 'file') OR TYPE = 'file' "));
} //resource_db_names
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* @param path the access path to the module script code
* @param itemtype the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by itemtype. In resources, this id
* points to the resource record and not to the module that shows it.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function resource_check_text_access($path, $itemtype, $this_id, $user, $group_id, $context_id){
global $CFG;
include_once("{$CFG->dirroot}/{$path}/lib.php");
$r = get_record('resource', 'id', $this_id);
$module_context = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $module_context->instance);
//check if found course module is visible
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $module_context)){
return false;
}
return true;
} //resource_check_text_access
?>

View file

@ -0,0 +1,278 @@
<?php
/**
* Global Search Engine for Moodle
* add-on 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for techproject activity module
*/
/* see wiki_document.php for descriptions */
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/techproject/lib.php");
/**
* a class for representing searchable information
*
*/
class TechprojectEntrySearchDocument extends SearchDocument {
/**
* constructor
*
*/
public function __construct(&$entry, $course_id, $context_id) {
// generic information
$doc->docid = $entry['id'];
$doc->documenttype = SEARCH_TYPE_TECHPROJECT;
$doc->itemtype = $entry['entry_type'];
$doc->contextid = $context_id;
$doc->title = $entry['abstract'];
$doc->author = ($entry['userid']) ? $entry['author'] : '';
$doc->contents = strip_tags($entry['description']);
$doc->date = '';
$doc->url = techproject_make_link($entry['projectid'], $entry['id'], $entry['entry_type'], $entry['groupid']);
// module specific information
$data->techproject = $entry['projectid'];
parent::__construct($doc, $data, $course_id, $entry['groupid'], $entry['userid'], PATH_FOR_SEARCH_TYPE_TECHPROJECT);
} //constructor
} //TechprojectEntrySearchDocument
/**
* constructs a valid link to a description detail
*
*/
function techproject_make_link($techproject_id, $entry_id, $entry_type, $group_id) {
global $CFG;
return $CFG->wwwroot.'/mod/techproject/view.php?view=view_detail&amp;id='.$techproject_id.'&amp;objectId='.$entry_id.'&amp;objectClass='.$entry_type.'&amp;group='.$group_id;
} //techproject_make_link
/**
* search standard API
*
*/
function techproject_iterator() {
$techprojects = get_records('techproject');
return $techprojects;
} //techproject_iterator
/**
* search standard API
* @param techproject a techproject instance
* @return an array of collected searchable documents
*/
function techproject_get_content_for_index(&$techproject) {
$documents = array();
if (!$techproject) return $documents;
$requirements = techproject_get_entries($techproject->id, 'requirement');
$specifications = techproject_get_entries($techproject->id, 'specification');
$tasks = techproject_get_tasks($techproject->id);
$milestones = techproject_get_entries($techproject->id, 'milestone');
$deliverables = techproject_get_entries($techproject->id, 'deliverable');
$coursemodule = get_field('modules', 'id', 'name', 'techproject');
$cm = get_record('course_modules', 'course', $techproject->course, 'module', $coursemodule, 'instance', $techproject->id);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
$entries = array_merge($requirements, $specifications, $milestones, $deliverables);
foreach($entries as $anEntry) {
if ($anEntry) {
if (strlen($anEntry->description) > 0) {
$documents[] = new TechprojectEntrySearchDocument(get_object_vars($anEntry), $techproject->course, $context->id);
}
}
}
foreach($tasks as $aTask) {
if ($aTask) {
if (strlen($aTask->description) > 0) {
if ($aTask->assignee){
$user = get_record('user', 'id', $aTask->assignee);
$aTask->author = $user->firstname.' '.$user->lastname;
}
$documents[] = new TechprojectEntrySearchDocument(get_object_vars($aTask), $techproject->course, $context->id);
}
}
}
return $documents;
} //techproject_get_content_for_index
/**
* returns a single techproject search document based on a techproject_entry id and itemtype
*
*/
function techproject_single_document($id, $itemtype) {
switch ($itemtype){
case 'requirement':{
$entry = get_record('techproject_requirement', 'id', $id);
break;
}
case 'specification':{
$entry = get_record('techproject_specification', 'id', $id);
break;
}
case 'milestone':{
$entry = get_record('techproject_milestone', 'id', $id);
break;
}
case 'deliverable':{
$entry = get_record('techproject_deliverable', 'id', $id);
break;
}
case 'task':{
$entry = get_record('techproject_task', 'id', $id);
if ($entry->assignee){
$user = get_record('user', 'id', $entry->assignee);
$entry->author = $user->firstname.' '.$user->lastname;
}
break;
}
}
$techprojet_course = get_field('techproject', 'course', 'id', $entry->projectid);
$coursemodule = get_field('modules', 'id', 'name', 'techproject');
$cm = get_record('course_modules', 'course', $techproject_course, 'module', $coursemodule, 'instance', $entry->projectid);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
$entry->type = $itemtype;
$techproject = get_record('techproject', 'id', $requirement->projectid);
return new TechprojectEntrySearchDocument(get_object_vars($anEntry), $techproject->course, $context->id);
} //techproject_single_document
/**
* dummy delete function that packs id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function techproject_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //techproject_delete
/**
* returns the var names needed to build a sql query for addition/deletions
*
*/
// TODO : what should we do there ?
function techproject_db_names() {
//[primary id], [table name], [time created field name], [time modified field name]
return array(
array('id', 'techproject_requirement', 'created', 'modified', 'requirement'),
array('id', 'techproject_specification', 'created', 'modified', 'specification'),
array('id', 'techproject_task', 'created', 'modified', 'task'),
array('id', 'techproject_milestone', 'created', 'modified', 'milestone'),
array('id', 'techproject_deliverable', 'created', 'modified', 'deliverable')
);
} //techproject_db_names
/**
* get a complete list of entries of one particular type
* @param techprojectId the project instance
* @param type the entity type
* @return an array of records
*/
function techproject_get_entries($techproject_id, $type) {
global $CFG;
$query = "
SELECT
e.id,
e.abstract,
e.description,
e.projectid,
e.groupid,
e.userid,
'$type' AS entry_type
FROM
{$CFG->prefix}techproject_{$type} AS e
WHERE
e.projectid = '{$techproject_id}'
";
return get_records_sql($query);
} //techproject_get_entries
/**
* get the task list for a project instance
* @param techprojectId the project
* @return an array of records that represent tasks
*/
function techproject_get_tasks($techproject_id) {
global $CFG;
$query = "
SELECT
t.id,
t.abstract,
t.description,
t.projectid,
t.groupid,
t.owner as userid,
u.firstname,
u.lastname,
'task' as entry_type
FROM
{$CFG->prefix}techproject_task AS t
LEFT JOIN
{$CFG->prefix}user AS u
ON
t.owner = u.id
WHERE
t.projectid = '{$techproject_id}'
ORDER BY
t.taskstart ASC
";
return get_records_sql($query);
} //techproject_get_tasks
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* When this point is reached, we already know that :
* - user is legitimate in the surrounding context
* - user may be guest and guest access is allowed to the module
* - the function may perform local checks within the module information logic
* @param path the access path to the module script code
* @param entry_type the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by entry_type. In techprojects, this id
* points to the techproject instance in which all resources are indexed.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function techproject_check_text_access($path, $entry_type, $this_id, $user, $group_id, $context_id){
global $CFG;
include_once("{$CFG->dirroot}/{$path}/lib.php");
// get the techproject object and all related stuff
$techproject = get_record('techproject', 'id', $this_id);
$course = get_record('course', 'id', $techproject->course);
$module_context = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $module_context->instance);
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $module_context)) return false;
//group consistency check : checks the following situations about groups
// if user is guest check access capabilities for guests :
// guests can see default project, and other records if groups are liberal
// TODO : change guestsallowed in a capability
if (isguest() && $techproject->guestsallowed){
if ($group_id && groupmode($course, $cm) == SEPARATEGROUPS)
return false;
return true;
}
// trap if user is not same group and groups are separated
$current_group = get_current_group($course->id);
if ((groupmode($course) == SEPARATEGROUPS) && $group_id != $current_group && $group_id) return false;
//trap if ungroupedsees is off in strict access mode and user is not teacher
if ((groupmode($course) == SEPARATEGROUPS) && !$techproject->ungroupedsees && !$group_id && isteacher($user->id)) return false;
return true;
} //techproject_check_text_access
?>

View file

@ -1,61 +1,88 @@
<?php
/* Wiki Search Document class and functions
* This file contains the mapping between a wiki page and it's indexable counterpart,
* e.g. searchdocument->title = wikipage->pagename
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/wiki/lib.php
* */
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* document handling for wiki activity module
* This file contains the mapping between a wiki page and it's indexable counterpart,
* e.g. searchdocument->title = wikipage->pagename
*
* Functions for iterating and retrieving the necessary records are now also included
* in this file, rather than mod/wiki/lib.php
**/
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/wiki/lib.php");
require_once("$CFG->dirroot/search/documents/document.php");
require_once("$CFG->dirroot/mod/wiki/lib.php");
/* All the $doc->___ fields are required by the base document class!
* Each and every module that requires search functionality must correctly
* map their internal fields to the five $doc fields (id, title, author, contents
* and url). Any module specific data can be added to the $data object, which is
* serialised into a binary field in the index.
* */
class WikiSearchDocument extends SearchDocument {
public function __construct(&$page, $wiki_id, $course_id, $group_id) {
/*
* All the $doc->___ fields are required by the base document class!
* Each and every module that requires search functionality must correctly
* map their internal fields to the five $doc fields (id, title, author, contents
* and url). Any module specific data can be added to the $data object, which is
* serialised into a binary field in the index.
**/
class WikiSearchDocument extends SearchDocument {
public function __construct(&$page, $wiki_id, $course_id, $group_id, $user_id, $context_id) {
// generic information; required
$doc->docid = $page->id;
$doc->title = $page->pagename;
$doc->date = $page->timemodified;
$doc->docid = $page['id'];
$doc->documenttype = SEARCH_TYPE_WIKI;
$doc->itemtype = 'standard';
$doc->contextid = $context_id;
$doc->title = $page['pagename'];
$doc->date = $page['timemodified'];
//remove '(ip.ip.ip.ip)' from wiki author field
$doc->author = preg_replace('/\(.*?\)/', '', $page->author);
$doc->contents = $page->content;
$doc->url = wiki_make_link($wiki_id, $page->pagename, $page->version);
$doc->author = preg_replace('/\(.*?\)/', '', $page['author']);
$doc->contents = $page['content'];
$doc->url = wiki_make_link($wiki_id, $page['pagename'], $page['version']);
// module specific information; optional
$data->version = $page->version;
$data->version = $page['version'];
$data->wiki = $wiki_id;
// construct the parent class
parent::__construct($doc, $data, SEARCH_TYPE_WIKI, $course_id, $group_id);
parent::__construct($doc, $data, $course_id, $group_id, $user_id, PATH_FOR_SEARCH_TYPE_WIKI);
} //constructor
} //WikiSearchDocument
} //WikiSearchDocument
function wiki_name_convert($str) {
/**
* converts a page name to cope Wiki constraints. Transforms spaces in plus.
* @param str the name to convert
* @return the converted name
*/
function wiki_name_convert($str) {
return str_replace(' ', '+', $str);
} //wiki_name_convert
} //wiki_name_convert
function wiki_make_link($wiki_id, $title, $version) {
/**
* constructs a valid link to a wiki content
* @param wikiId
* @param title
* @param version
*/
function wiki_make_link($wikiId, $title, $version) {
global $CFG;
return $CFG->wwwroot.'/mod/wiki/view.php?wid='.$wiki_id.'&page='.wiki_name_convert($title).'&version='.$version;
} //wiki_make_link
//rescued and converted from ewikimoodlelib.php
//retrieves latest version of a page
function wiki_get_latest_page(&$entry, $pagename, $version=0) {
return $CFG->wwwroot.'/mod/wiki/view.php?wid='.$wikiId.'&amp;page='.wiki_name_convert($title).'&amp;version='.$version;
} //wiki_make_link
/**
* rescued and converted from ewikimoodlelib.php
* retrieves latest version of a page
* @param entry the wiki object as a reference
* @param pagename the name of the page known by the wiki engine
* @param version
*/
function wiki_get_latest_page(&$entry, $pagename, $version = 0) {
$pagename = "'".addslashes($pagename)."'";
if ($version > 0 and is_int($version)) {
$version = "AND (version=$version)";
} else {
$version = '';
} //else
}
$select = "(pagename=$pagename) AND wiki=".$entry->id." $version ";
$sort = 'version DESC';
@ -64,24 +91,31 @@
if ($result_arr = get_records_select('wiki_pages', $select, $sort, '*', 0, 1)) {
foreach ($result_arr as $obj) {
$result_obj = $obj;
} //foreach
} //if
}
}
if (isset($result_obj)) {
$result_obj->meta = @unserialize($result_obj->meta);
return $result_obj;
} else {
return false;
} //else
} //wiki_get_latest_page
}
} //wiki_get_latest_page
//fetches all pages, including old versions
function wiki_get_pages(&$entry) {
/**
* fetches all pages, including old versions
* @param entry the wiki object as a reference
* @return an array of record objects that represents pages of this wiki object
*/
function wiki_get_pages(&$entry) {
return get_records('wiki_pages', 'wiki', $entry->id);
} //wiki_get_pages
} //wiki_get_pages
//fetches all the latest versions of all the pages
function wiki_get_latest_pages(&$entry) {
/**
* fetches all the latest versions of all the pages
*
*/
function wiki_get_latest_pages(&$entry) {
//== (My)SQL for this
/* select * from wiki_pages
inner join
@ -93,66 +127,120 @@
$pages = array();
//http://moodle.org/bugs/bug.php?op=show&bugid=5877&pos=0
//if ($ids = get_records('wiki_pages', 'wiki', $entry->id, '', 'distinct pagename')) {
if ($rs = get_recordset('wiki_pages', 'wiki', $entry->id, '', 'distinct pagename')) {
$ids = $rs->GetRows();
//--
foreach ($ids as $id) {
$pages[] = wiki_get_latest_page($entry, $id[0]);
} //foreach
if ($ids = get_records('wiki_pages', 'wiki', $entry->id, '', 'distinct pagename')) {
if ($pagesets = get_records('wiki_pages', 'wiki', $entry->id, '', 'distinct pagename')) {
foreach ($pagesets as $aPageset) {
$pages[] = wiki_get_latest_page($entry, $aPageset->id);
}
} else {
return false;
} //else
}
}
return $pages;
} //wiki_get_latest_pages
} //wiki_get_latest_pages
function wiki_iterator() {
return get_all_instances_in_courses("wiki", get_courses());
} //wiki_iterator
/**
* part of search engine API
*
*/
function wiki_iterator() {
$wikis = get_records('wiki');
return $wikis;
} //wiki_iterator
/**
* part of search engine API
* @param wiki a wiki instance
* @return an array of searchable deocuments
*/
function wiki_get_content_for_index(&$wiki) {
function wiki_get_content_for_index(&$wiki) {
$documents = array();
$entries = wiki_get_entries($wiki);
foreach($entries as $entry) {
$coursemodule = get_field('modules', 'id', 'name', 'wiki');
$cm = get_record('course_modules', 'course', $entry->course, 'module', $coursemodule, 'instance', $entry->wikiid);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
//all pages
//$pages = wiki_get_pages($entry);
//latest pages
$pages = wiki_get_latest_pages($entry);
if (is_array($pages)) {
foreach($pages as $page) {
if (strlen($page->content) > 0) {
$documents[] = new WikiSearchDocument($page, $entry->wikiid, $entry->course, $entry->groupid);
} //if
} //foreach
} //if
} //foreach
$documents[] = new WikiSearchDocument(get_object_vars($page), $entry->wikiid, $entry->course, $entry->groupid, $page->userid, $context->id);
}
}
}
}
return $documents;
} //wiki_get_content_for_index
} //wiki_get_content_for_index
//returns a single wiki search document based on a wiki_entry id
function wiki_single_document($id) {
$pages = get_recordset('wiki_pages', 'id', $id);
$page = $pages->fields;
/**
* returns a single wiki search document based on a wiki_entry id
* @param id the id of the wiki
* @param itemtype the type of information (standard)
* @retuen a searchable document
*/
function wiki_single_document($id, $itemtype) {
$page = get_record('wiki_pages', 'id', $id);
$entry = get_record('wiki_entries', 'id', $page->wiki);
$coursemodule = get_field('modules', 'id', 'name', 'wiki');
$cm = get_record('course_modules', 'course', $entry->course, 'module', $coursemodule, 'instance', $entry->wikiid);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
return new WikiSearchDocument(get_object_vars($page), $entry->wikiid, $entry->course, $entry->groupid, $page->userid, $context->id);
} //wiki_single_document
$entries = get_recordset('wiki_entries', 'id', $page['wiki']);
$entry = $entries->fields;
/**
* dummy delete function that packs id with itemtype.
* this was here for a reason, but I can't remember it at the moment.
*
*/
function wiki_delete($info, $itemtype) {
$object->id = $info;
$object->itemtype = $itemtype;
return $object;
} //wiki_delete
return new WikiSearchDocument($page, $entry['wikiid'], $entry['course'], $entry['groupid']);
} //wiki_single_document
function wiki_delete($info) {
return $info;
} //wiki_delete
//returns the var names needed to build a sql query for addition/deletions
function wiki_db_names() {
//returns the var names needed to build a sql query for addition/deletions
function wiki_db_names() {
//[primary id], [table name], [time created field name], [time modified field name]
return array('id', 'wiki_pages', 'created', 'lastmodified');
} //wiki_db_names
return array(array('id', 'wiki_pages', 'created', 'lastmodified', 'standard'));
} //wiki_db_names
/**
* this function handles the access policy to contents indexed as searchable documents. If this
* function does not exist, the search engine assumes access is allowed.
* When this point is reached, we already know that :
* - user is legitimate in the surrounding context
* - user may be guest and guest access is allowed to the module
* - the function may perform local checks within the module information logic
* @param path the access path to the module script code
* @param itemtype the information subclassing (usefull for complex modules, defaults to 'standard')
* @param this_id the item id within the information class denoted by itemtype. In wikies, this id
* points out the indexed wiki page.
* @param user the user record denoting the user who searches
* @param group_id the current group used by the user when searching
* @return true if access is allowed, false elsewhere
*/
function wiki_check_text_access($path, $itemtype, $this_id, $user, $group_id, $context_id){
global $CFG;
// get the wiki object and all related stuff
$page = get_record('wiki_pages', 'id', $id);
$entry = get_record('wiki_entries', 'id', $page->wiki);
$course = get_record('course', 'id', $entry->course);
$module_context = get_record('context', 'id', $context_id);
$cm = get_record('course_modules', 'id', $module_context->instance);
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $module_context)) return false;
//group consistency check : checks the following situations about groups
// trap if user is not same group and groups are separated
$current_group = get_current_group($course->id);
if ((groupmode($course) == SEPARATEGROUPS) && $group_id != $current_group && !has_capability('moodle/site:accessallgroups', $module_context)) return false;
return true;
} //wiki_check_text_access
?>

View file

@ -1,8 +1,9 @@
<?php
/* Entry page for /search
* Redirects to query.php, because that is the most likely place a
* user intended to go to when typing moodle.site/search
* */
/*
* Entry page for /search
* Redirects to query.php, because that is the most likely place a
* user intended to go to when typing moodle.site/search
**/
header("Location: query.php");
header("Location: query.php");
?>

View file

@ -1,106 +1,132 @@
<?php
/* The indexer logic -
* Look through each installed module's search document class file (/search/documents)
* for necessary search functions, and if they're present add the content to the index.
* Repeat this for blocks.
*
* Because the iterator/retrieval functions are now stored in /search/documents/mod_document.php,
* /mod/mod/lib.php doesn't have to be modified - and thus the search module becomes quite
* self-sufficient. URL's are now stored in the index, stopping us from needing to require
* the class files to generate a results page.
*
* Along with the index data, each document's summary gets stored in the database
* and synchronised to the index (flat file) via the primary key ('id') which is mapped
* to the 'db_id' field in the index
* */
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* The indexer logic -
*
* Look through each installed module's or block's search document class file (/search/documents)
* for necessary search functions, and if they're present add the content to the index.
* Repeat this for blocks.
*
* Because the iterator/retrieval functions are now stored in /search/documents/<mod>_document.php,
* /mod/mod/lib.php doesn't have to be modified - and thus the search module becomes quite
* self-sufficient. URL's are now stored in the index, stopping us from needing to require
* the class files to generate a results page.
*
* Along with the index data, each document's summary gets stored in the database
* and synchronised to the index (flat file) via the primary key ('id') which is mapped
* to the 'dbid' field in the index
* */
//this'll take some time, set up the environment
@set_time_limit(0);
@ob_implicit_flush(true);
@ob_end_flush();
//this'll take some time, set up the environment
@set_time_limit(0);
@ob_implicit_flush(true);
@ob_end_flush();
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
//only administrators can index the moodle installation, because access to all pages is required
require_login();
//only administrators can index the moodle installation, because access to all pages is required
require_login();
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
if (!isadmin()) {
error("You need to be an admin user to use this page.", "$CFG->wwwroot/login/index.php");
} //if
if (!isadmin()) {
error(get_string('beadmin', 'search'), "$CFG->wwwroot/login/index.php");
} //if
//confirmation flag to prevent accidental reindexing (indexersplash.php is the correct entry point)
$sure = strtolower(optional_param('areyousure', '', PARAM_ALPHA));
//confirmation flag to prevent accidental reindexing (indexersplash.php is the correct entry point)
$sure = strtolower(optional_param('areyousure', '', PARAM_ALPHA));
if ($sure != 'yes') {
if ($sure != 'yes') {
mtrace("<pre>Sorry, you need to confirm indexing via <a href='indexersplash.php'>indexersplash.php</a>"
.". (<a href='index.php'>Back to query page</a>).</pre>");
exit(0);
} //if
} //if
//check for php5 (lib.php)
if (!search_check_php5()) {
//check for php5 (lib.php)
if (!search_check_php5()) {
$phpversion = phpversion();
mtrace("Sorry, global search requires PHP 5.0.0 or later (currently using version $phpversion)");
exit(0);
} //if
}
//php5 found, continue including php5-only files
//require_once("$CFG->dirroot/search/Zend/Search/Lucene.php");
require_once("$CFG->dirroot/search/indexlib.php");
//php5 found, continue including php5-only files
//require_once("$CFG->dirroot/search/Zend/Search/Lucene.php");
require_once("$CFG->dirroot/search/indexlib.php");
mtrace('<pre>Server Time: '.date('r',time())."\n");
mtrace('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8" /></head><body>');
mtrace('<pre>Server Time: '.date('r',time())."\n");
if ($CFG->search_indexer_busy == '1') {
if ($CFG->search_indexer_busy == '1') {
//means indexing was not finished previously
mtrace("Warning: Indexing was not successfully completed last time, restarting.\n");
} //if
}
//turn on busy flag
set_config('search_indexer_busy', '1');
//turn on busy flag
set_config('search_indexer_busy', '1');
//paths
$index_path = SEARCH_INDEX_PATH;
$index_db_file = "$CFG->dirroot/search/db/$CFG->dbtype.sql";
$dbcontrol = new IndexDBControl();
//paths
$index_path = SEARCH_INDEX_PATH;
$index_db_file = "{$CFG->dirroot}/search/db/$CFG->dbtype.sql";
$dbcontrol = new IndexDBControl();
//setup directory in data root
if (!file_exists($index_path)) {
//setup directory in data root
if (!file_exists($index_path)) {
mtrace("Data directory ($index_path) does not exist, attempting to create.");
if (!mkdir($index_path)) {
search_pexit("Error creating data directory at: $index_path. Please correct.");
} else {
}
else {
mtrace("Directory successfully created.");
} //else
} else {
}
}
else {
mtrace("Using $index_path as data directory.");
} //else
}
$index = new Zend_Search_Lucene($index_path, true);
$index = new Zend_Search_Lucene($index_path, true);
if (!$dbcontrol->checkDB()) {
if (!$dbcontrol->checkDB()) {
search_pexit("Database error. Please check settings/files.");
} //if
}
//begin timer
search_stopwatch();
mtrace("Starting activity modules\n");
//begin timer
search_stopwatch();
mtrace("Starting activity modules\n");
//the presence of the required search functions -
// * mod_iterator
// * mod_get_content_for_index
//are the sole basis for including a module in the index at the moment.
//the presence of the required search functions -
// * mod_iterator
// * mod_get_content_for_index
//are the sole basis for including a module in the index at the moment.
$searchables = array();
if ($mods = get_records_select('modules' /*'index this module?' where statement*/)) {
//add virtual modules onto the back of the array
$mods = array_merge($mods, search_get_additional_modules());
// collects modules
if ($mods = get_records('modules', '', '', '', 'id,name')) {
$searchables = array_merge($searchables, $mods);
}
mtrace(count($searchables).' modules found.');
foreach ($mods as $mod) {
// collects blocks as indexable information may be found in blocks either
if ($blocks = get_records('block', '', '', '', 'id,name')) {
// prepend the "block_" prefix to discriminate document type plugins
foreach(array_keys($blocks) as $aBlockId){
$blocks[$aBlockId]->name = 'block_'.$blocks[$aBlockId]->name;
}
$searchables = array_merge($searchables, $blocks);
mtrace(count($blocks).' blocks found.');
}
//add virtual modules onto the back of the array
$searchables = array_merge($searchables, search_get_additional_modules());
if ($searchables){
foreach ($searchables as $mod) {
$class_file = $CFG->dirroot.'/search/documents/'.$mod->name.'_document.php';
if (file_exists($class_file)) {
@ -109,18 +135,16 @@
//build function names
$iter_function = $mod->name.'_iterator';
$index_function = $mod->name.'_get_content_for_index';
$counter = 0;
$doc = new stdClass;
if (function_exists($index_function) && function_exists($iter_function)) {
mtrace("Processing module function $index_function ...");
foreach ($iter_function() as $i) {
$sources = $iter_function();
if ($sources){
foreach ($sources as $i) {
$documents = $index_function($i);
//begin transaction
if ($documents){
foreach($documents as $document) {
$counter++;
@ -134,43 +158,40 @@
$index->addDocument($document);
//commit every x new documents, and print a status message
if (($counter%2000) == 0) {
if (($counter % 2000) == 0) {
$index->commit();
mtrace(".. $counter");
} //if
} //foreach
}
}
}
//end transaction
} //foreach
}
}
//commit left over documents, and finish up
$index->commit();
mtrace("-- $counter documents indexed");
mtrace("done.\n");
} //if
} //if
} //foreach
} //if
}
}
}
}
//finished modules
mtrace('Finished activity modules');
search_stopwatch();
//finished modules
mtrace('Finished activity modules');
search_stopwatch();
//now blocks...
//
mtrace(".<br/><a href='index.php'>Back to query page</a>.");
mtrace('</pre>');
mtrace(".<br/><a href='index.php'>Back to query page</a>.");
mtrace('</pre>');
//finished, turn busy flag off
set_config("search_indexer_busy", "0");
//finished, turn busy flag off
set_config("search_indexer_busy", "0");
//mark the time we last updated
set_config("search_indexer_run_date", time());
//mark the time we last updated
set_config("search_indexer_run_date", time());
//and the index size
set_config("search_index_size", (int)$index->count());
//and the index size
set_config("search_index_size", (int)$index->count());
?>

View file

@ -1,33 +1,39 @@
<?php
/* This file serves as a splash-screen (entry page) to the indexer script -
* it is in place to prevent accidental reindexing which can lead to a loss
* of time, amongst other things.
* */
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* This file serves as a splash-screen (entry page) to the indexer script -
* it is in place to prevent accidental reindexing which can lead to a loss
* of time, amongst other things.
**/
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_login();
require_login();
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
if (!isadmin()) {
error("You need to be an admin user to use this page.", "$CFG->wwwroot/login/index.php");
} //if
if (!isadmin()) {
error(get_string('beadmin', 'search'), "$CFG->wwwroot/login/index.php");
}
//check for php5 (lib.php)
if (!search_check_php5()) {
//check for php5 (lib.php)
if (!search_check_php5()) {
$phpversion = phpversion();
mtrace("Sorry, global search requires PHP 5.0.0 or later (currently using version $phpversion)");
exit(0);
} //if
}
require_once("$CFG->dirroot/search/indexlib.php");
$indexinfo = new IndexInfo();
require_once("$CFG->dirroot/search/indexlib.php");
$indexinfo = new IndexInfo();
if ($indexinfo->valid()) {
if ($indexinfo->valid()) {
mtrace("<pre>The data directory ($indexinfo->path) contains $indexinfo->filecount files, and\n"
."there are ".$indexinfo->dbcount." records in the <em>search_documents</em> table.\n"
."\n"
@ -42,7 +48,8 @@
."<a href='tests/index.php'>Test indexing</a> or "
."<a href='indexer.php?areyousure=yes'>Continue indexing</a> or <a href='index.php'>Back to query page</a>."
."</pre>");
} else {
}
else {
header('Location: indexer.php?areyousure=yes');
} //else
}
?>

View file

@ -1,15 +1,20 @@
<?php
/* Index info class
*
* Used to retrieve information about an index.
* Has methods to check for valid database and data directory,
* and the index itself.
* */
/*
* Author: Michael Champanis
*
* Reviewed by: Valery Fremaux (2007)
*
* Index info class
*
* Used to retrieve information about an index.
* Has methods to check for valid database and data directory,
* and the index itself.
**/
require_once("$CFG->dirroot/search/lib.php");
require_once("$CFG->dirroot/search/Zend/Search/Lucene.php");
require_once("$CFG->dirroot/search/lib.php");
require_once("$CFG->dirroot/search/Zend/Search/Lucene.php");
class IndexInfo {
class IndexInfo {
private $path, //index data directory
$size, //size of directory (i.e. the whole index)
$filecount, //number of files
@ -19,7 +24,7 @@
$complete, //is index completely formed?
$time; //date index was generated
public function __construct($path=SEARCH_INDEX_PATH) {
public function __construct($path = SEARCH_INDEX_PATH) {
global $CFG, $db;
$this->path = $path;
@ -38,11 +43,12 @@
$index_dir = get_directory_list($this->path, '', false, false);
$this->filecount = count($index_dir);
$this->indexcount = $test_index->count();
} else {
}
else {
$this->size = 0;
$this->filecount = 0;
$this->indexcount = 0;
} //else
}
$db_exists = false; //for now
@ -66,126 +72,155 @@
foreach($types as $type) {
$c = count_records(SEARCH_DATABASE_TABLE, 'doctype', $type);
$this->types[$type] = (int)$c;
} //foreach
} else {
}
}
else {
$this->dbcount = 0;
$this->types = array();
} //else
}
//check if the busy flag is set
if ($CFG->search_indexer_busy == '1') {
$this->complete = false;
} else {
}
else {
$this->complete = true;
} //if
}
//get the last run date for the indexer
if ($this->valid() && $CFG->search_indexer_run_date) {
$this->time = $CFG->search_indexer_run_date;
} else {
}
else {
$this->time = 0;
} //else
}
} //__construct
//returns false on error, and the error message via referenced variable $err
public function valid(&$err=null) {
/**
* returns false on error, and the error message via referenced variable $err
*
*/
public function valid(&$err = null) {
$err = array();
$ret = true;
if (!$this->is_valid_dir()) {
$err['dir'] = 'Index directory either contains an invalid index, or nothing at all.';
$err['dir'] = get_string('invalidindexerror', 'search');
$ret = false;
} //if
}
if (!$this->is_valid_db()) {
$err['db'] = 'Database table is not present, or contains no index records.';
$err['db'] = get_string('emptydatabaseerror', 'search');
$ret = false;
} //if
}
if (!$this->complete) {
$err['index'] = 'Indexing was not successfully completed, please restart it.';
$err['index'] = get_string('uncompleteindexingerror','search');
$ret = false;
} //if
}
return $ret;
} //valid
//is the index dir valid
/**
* is the index dir valid
*
*/
public function is_valid_dir() {
if ($this->filecount > 0) {
return true;
} else {
}
else {
return false;
} //else
}
} //is_valid_dir
//is the db table valid
/**
* is the db table valid
*
*/
public function is_valid_db() {
if ($this->dbcount > 0) {
return true;
} else {
}
else {
return false;
} //else
}
} //is_valid_db
//shorthand get method for the class variables
/**
* shorthand get method for the class variables
*
*/
public function __get($var) {
if (in_array($var, array_keys(get_class_vars(get_class($this))))) {
return $this->$var;
} //if
}
} //__get
} //IndexInfo
} //IndexInfo
/* DB Index control class
/*
* DB Index control class
*
* Used to control the search index database table
**/
class IndexDBControl {
/**
* does the table exist?
*
* Used to control the search index database table
* */
class IndexDBControl {
//does the table exist?
*/
public function checkTableExists() {
global $CFG, $db;
$table = SEARCH_DATABASE_TABLE;
$tables = $db->MetaTables();
if (in_array($CFG->prefix.$table, $tables)) {
return true;
} else {
}
else {
return false;
} //else
}
} //checkTableExists
//is our database setup valid?
/**
* is our database setup valid?
*
*/
public function checkDB() {
global $CFG, $db;
$sqlfile = "$CFG->dirroot/search/db/$CFG->dbtype.sql";
$sqlfile = "$CFG->dirroot/blocks/search/db/$CFG->dbtype.sql";
$ret = false;
if ($this->checkTableExists()) {
execute_sql('drop table '.$CFG->prefix.SEARCH_DATABASE_TABLE, false);
} //if
}
ob_start(); //turn output buffering on - to hide modify_database() output
//turn output buffering on - to hide modify_database() output
ob_start();
$ret = modify_database($sqlfile, '', false);
ob_end_clean(); //chuck the buffer and resume normal operation
//chuck the buffer and resume normal operation
ob_end_clean();
return $ret;
} //checkDB
//add a document record to the table
/**
* add a document record to the table
* @param document must be a Lucene SearchDocument instance
*/
public function addDocument($document=null) {
global $db;
global $db, $CFG;
if ($document == null) {
return false;
} //if
}
//object to insert into db
// object to insert into db
$doc->doctype = $document->doctype;
$doc->docid = $document->docid;
$doc->itemtype = $document->itemtype;
$doc->title = search_escape_string($document->title);
$doc->url = search_escape_string($document->url);
$doc->update = time();
@ -199,12 +234,15 @@
return $id;
} //addDocument
//remove a document record from the index
/**
* remove a document record from the index
* @param document must be a Lucene document instance, or at least a dbid enveloppe
*/
public function delDocument($document) {
global $db;
delete_records(SEARCH_DATABASE_TABLE, 'id', $document->dbid);
} //delDocument
} //IndexControl
} //IndexControl
?>

View file

@ -1,61 +1,95 @@
<?php
/*
* Author: Michael Champanis
*
* This file must not contain any PHP 5, because it is used to test for PHP 5
* itself, and needs to be able to be executed on PHP 4 installations.
*
* Reviewed by: Valery Fremaux (2007)
* - adding techproject search capabilities
* - adding full internationalization
**/
/* Move this stuff to lib/searchlib.php?
* Author: Michael Champanis
*
* This file must not contain any PHP 5, because it is used to test for PHP 5
* itself, and needs to be able to be executed on PHP 4 installations.
* */
/*
// function reference
function search_get_document_types($prefix = 'SEARCH_TYPE_') {
function search_get_additional_modules() {
function search_shorten_url($url, $length=30) {
function search_escape_string($str) {
function search_check_php5($feedback = false) {
function search_stopwatch($cli = false) {
function search_pexit($str = "") {
*/
define('SEARCH_INDEX_PATH', "$CFG->dataroot/search");
define('SEARCH_DATABASE_TABLE', 'search_documents');
define('SEARCH_INDEX_PATH', "$CFG->dataroot/search");
define('SEARCH_DATABASE_TABLE', 'search_documents');
//document types that can be searched
//define('SEARCH_TYPE_NONE', 'none');
define('SEARCH_TYPE_WIKI', 'wiki');
define('SEARCH_TYPE_FORUM', 'forum');
define('SEARCH_TYPE_GLOSSARY', 'glossary');
define('SEARCH_TYPE_RESOURCE', 'resource');
//document types that can be searched
//define('SEARCH_TYPE_NONE', 'none');
define('SEARCH_TYPE_WIKI', 'wiki');
define('PATH_FOR_SEARCH_TYPE_WIKI', 'mod/wiki');
define('SEARCH_TYPE_FORUM', 'forum');
define('PATH_FOR_SEARCH_TYPE_FORUM', 'mod/forum');
define('SEARCH_TYPE_GLOSSARY', 'glossary');
define('PATH_FOR_SEARCH_TYPE_GLOSSARY', 'mod/glossary');
define('SEARCH_TYPE_RESOURCE', 'resource');
define('PATH_FOR_SEARCH_TYPE_RESOURCE', 'mod/resource');
define('SEARCH_TYPE_TECHPROJECT', 'techproject');
define('PATH_FOR_SEARCH_TYPE_TECHPROJECT', 'mod/techproject');
define('SEARCH_TYPE_DATA', 'data');
define('PATH_FOR_SEARCH_TYPE_DATA', 'mod/data');
define('SEARCH_TYPE_CHAT', 'chat');
define('PATH_FOR_SEARCH_TYPE_CHAT', 'mod/chat');
//returns all the document type constants
function search_get_document_types($prefix='SEARCH_TYPE') {
/**
* returns all the document type constants
* @param prefix a pattern for recognizing constants
* @return an array of type labels
*/
function search_get_document_types($prefix = 'SEARCH_TYPE_') {
$ret = array();
foreach (get_defined_constants() as $key=>$value) {
if (substr($key, 0, strlen($prefix)) == $prefix) {
foreach (get_defined_constants() as $key => $value) {
if (preg_match("/^{$prefix}/", $key)){
$ret[$key] = $value;
} //if
} //foreach
}
}
sort($ret);
return $ret;
} //search_get_document_types
} //search_get_document_types
// additional virtual modules to index
//
// By adding 'moo' to the extras array, an additional document type
// documents/moo_document.php will be indexed - this allows for
// virtual modules to be added to the index, i.e. non-module specific
// information.
function search_get_additional_modules() {
/**
* additional virtual modules to index
*
* By adding 'moo' to the extras array, an additional document type
* documents/moo_document.php will be indexed - this allows for
* virtual modules to be added to the index, i.e. non-module specific
* information.
*/
function search_get_additional_modules() {
$extras = array(/* additional keywords go here */);
$ret = array();
foreach($extras as $extra) {
$temp->name = $extra;
$ret[] = clone($temp);
} //foreach
}
return $ret;
} //search_get_additional_modules
} //search_get_additional_modules
//shortens a url so it can fit on the results page
function search_shorten_url($url, $length=30) {
/**
* shortens a url so it can fit on the results page
* @param url the url
* @param length the size limit we want
*/
function search_shorten_url($url, $length=30) {
return substr($url, 0, $length)."...";
} //search_shorten_url
} //search_shorten_url
function search_escape_string($str) {
/**
* a local function for escaping
* @param str the string to escape
* @return the escaped string
*/
function search_escape_string($str) {
global $CFG;
switch ($CFG->dbfamily) {
@ -67,47 +101,56 @@
break;
default:
$s = addslashes($str);
} //switch
}
return $s;
} //search_escape_string
} //search_escape_string
//get a real php 5 version number, using 5.0.0 arbitrarily
function search_check_php5($feedback=false) {
/**
* get a real php 5 version number, using 5.0.0 arbitrarily
* @param feedback if true, prints a feedback message to output.
* @return true if version of PHP is high enough
*/
function search_check_php5($feedback = false) {
if (!check_php_version("5.0.0")) {
if ($feedback) {
$phpversion = phpversion();
print_heading("Sorry, global search requires PHP 5.0.0 or later (currently using version $phpversion)");
} //if
print_heading(get_string('versiontoolow', 'search'));
}
return false;
} else {
}
else {
return true;
} //else
} //search_check_php5
}
} //search_check_php5
//simple timer function, outputs result on 2nd call
function search_stopwatch($cli = false) {
/**
* simple timer function, on first call, records a current microtime stamp, outputs result on 2nd call
* @param cli an output formatting switch
* @return void
*/
function search_stopwatch($cli = false) {
if (!empty($GLOBALS['search_script_start_time'])) {
if (!$cli) print '<em>';
print round(microtime(true) - $GLOBALS['search_script_start_time'], 6).' seconds';
print round(microtime(true) - $GLOBALS['search_script_start_time'], 6).' '.get_string('seconds', 'search');
if (!$cli) print '</em>';
unset($GLOBALS['search_script_start_time']);
} else {
}
else {
$GLOBALS['search_script_start_time'] = microtime(true);
} //else
} //search_stopwatch
}
} //search_stopwatch
//print and exit (for debugging)
function search_pexit($str = "") {
/**
* print and exit (for debugging)
* @param str a variable to explore
* @return void
*/
function search_pexit($str = "") {
if (is_array($str) or is_object($str)) {
print_r($str);
} else if ($str) {
print $str."<br/>";
} //if
}
exit(0);
} //search_pexit
} //search_pexit
?>

View file

@ -1,43 +1,48 @@
<?php
/**
* The query page - accepts a user-entered query string and returns results.
*
* Queries are boolean-aware, e.g.:
*
* '+' term required
* '-' term must not be present
* '' (no modifier) term's presence increases rank, but isn't required
* 'field:' search this field
*
* Examples:
*
* 'earthquake +author:michael'
* Searches for documents written by 'michael' that contain 'earthquake'
*
* 'earthquake +doctype:wiki'
* Search all wiki pages for 'earthquake'
*
* '+author:helen +author:foster'
* All articles written by Helen Foster
*
*/
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* The query page - accepts a user-entered query string and returns results.
*
* Queries are boolean-aware, e.g.:
*
* '+' term required
* '-' term must not be present
* '' (no modifier) term's presence increases rank, but isn't required
* 'field:' search this field
*
* Examples:
*
* 'earthquake +author:michael'
* Searches for documents written by 'michael' that contain 'earthquake'
*
* 'earthquake +doctype:wiki'
* Search all wiki pages for 'earthquake'
*
* '+author:helen +author:foster'
* All articles written by Helen Foster
*
*/
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
if ($CFG->forcelogin) {
if ($CFG->forcelogin) {
require_login();
}
}
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
$adv = new Object();
$adv = new Object();
//check for php5, but don't die yet (see line 52)
if ($check = search_check_php5()) {
require_once("$CFG->dirroot/search/querylib.php");
// check for php5, but don't die yet (see line 52)
if ($check = search_check_php5()) {
require_once("{$CFG->dirroot}/search/querylib.php");
$page_number = optional_param('page', -1, PARAM_INT);
$pages = ($page_number == -1) ? false : true;
@ -45,24 +50,25 @@
$query_string = optional_param('query_string', '', PARAM_CLEAN);
if ($pages && isset($_SESSION['search_advanced_query'])) {
//if both are set, then we are busy browsing through the result pages of an advanced query
// if both are set, then we are busy browsing through the result pages of an advanced query
$adv = unserialize($_SESSION['search_advanced_query']);
} else if ($advanced) {
//otherwise we are dealing with a new advanced query
}
else if ($advanced) {
// otherwise we are dealing with a new advanced query
unset($_SESSION['search_advanced_query']);
session_unregister('search_advanced_query');
//chars to strip from strings (whitespace)
// chars to strip from strings (whitespace)
$chars = " \t\n\r\0\x0B,-+";
//retrieve advanced query variables
// retrieve advanced query variables
$adv->mustappear = trim(optional_param('mustappear', '', PARAM_CLEAN), $chars);
$adv->notappear = trim(optional_param('notappear', '', PARAM_CLEAN), $chars);
$adv->canappear = trim(optional_param('canappear', '', PARAM_CLEAN), $chars);
$adv->module = optional_param('module', '', PARAM_CLEAN);
$adv->title = trim(optional_param('title', '', PARAM_CLEAN), $chars);
$adv->author = trim(optional_param('author', '', PARAM_CLEAN), $chars);
} //else
}
if ($advanced) {
//parse the advanced variables into a query string
@ -70,193 +76,216 @@
$query_string = '';
//get all available module types
$module_types = array_merge(array('All'), array_values(search_get_document_types()));
$adv->module = in_array($adv->module, $module_types) ? $adv->module : 'All';
// get all available module types
$module_types = array_merge(array('all'), array_values(search_get_document_types()));
$adv->module = in_array($adv->module, $module_types) ? $adv->module : 'all';
//convert '1 2' into '+1 +2' for required words field
// convert '1 2' into '+1 +2' for required words field
if (strlen(trim($adv->mustappear)) > 0) {
$query_string = ' +'.implode(' +', preg_split("/[\s,;]+/", $adv->mustappear));
} //if
}
//convert '1 2' into '-1 -2' for not wanted words field
// convert '1 2' into '-1 -2' for not wanted words field
if (strlen(trim($adv->notappear)) > 0) {
$query_string .= ' -'.implode(' -', preg_split("/[\s,;]+/", $adv->notappear));
} //if
}
//this field is left untouched, apart from whitespace being stripped
// this field is left untouched, apart from whitespace being stripped
if (strlen(trim($adv->canappear)) > 0) {
$query_string .= ' '.implode(' ', preg_split("/[\s,;]+/", $adv->canappear));
} //if
}
//add module restriction
if ($adv->module != 'All') {
$query_string .= ' +doctype:'.$adv->module;
} //if
// add module restriction
$doctypestr = get_string('doctype', 'search');
$titlestr = get_string('title', 'search');
$authorstr = get_string('author', 'search');
if ($adv->module != 'all') {
$query_string .= " +{$doctypestr}:".$adv->module;
}
//create title search string
// create title search string
if (strlen(trim($adv->title)) > 0) {
$query_string .= ' +title:'.implode(' +title:', preg_split("/[\s,;]+/", $adv->title));
} //if
$query_string .= " +{$titlestr}:".implode(" +{$titlestr}:", preg_split("/[\s,;]+/", $adv->title));
}
//create author search string
// create author search string
if (strlen(trim($adv->author)) > 0) {
$query_string .= ' +author:'.implode(' +author:', preg_split("/[\s,;]+/", $adv->author));
} //if
$query_string .= " +{$authorstr}:".implode(" +{$authorstr}:", preg_split("/[\s,;]+/", $adv->author));
}
//save our options if the query is valid
// save our options if the query is valid
if (!empty($query_string)) {
$_SESSION['search_advanced_query'] = serialize($adv);
} //if
} //if
}
}
//normalise page number
// normalise page number
if ($page_number < 1) {
$page_number = 1;
} //if
}
//run the query against the index
$sq = new SearchQuery($query_string, $page_number, 10, false);
} //if
}
if (!$site = get_site()) {
if (!$site = get_site()) {
redirect("index.php");
} //if
}
$strsearch = "Search"; //get_string();
$strquery = "Enter your search query"; //get_string();
$strsearch = get_string('search', 'search');
$strquery = get_string('enteryoursearchquery', 'search');
print_header("$site->shortname: $strsearch: $strquery", "$site->fullname",
print_header("$site->shortname: $strsearch: $strquery", "$site->fullname",
"<a href=\"index.php\">$strsearch</a> -> $strquery");
//keep things pretty, even if php5 isn't available
if (!$check) {
//keep things pretty, even if php5 isn't available
if (!$check) {
print_heading(search_check_php5(true));
print_footer();
exit(0);
} //if
}
print_simple_box_start('center', '100%', '', 20);
print_heading($strquery);
print_box_start();
print_heading($strquery);
print_simple_box_start('center', '', '', 20);
print_box_start();
$vars = get_object_vars($adv);
$vars = get_object_vars($adv);
if (isset($vars)) {
if (isset($vars)) {
foreach ($vars as $key => $value) {
$adv->$key = stripslashes(htmlentities($value));
} //foreach
}
}
?>
<form id="query" method="get" action="query.php">
<?php if (!$advanced) { ?>
<input type="text" name="query_string" length="50" value="<?php print stripslashes(htmlentities($query_string)) ?>" />
&nbsp;<input type="submit" value="Search" /> &nbsp;
<a href="query.php?a=1">Advanced search</a> |
<a href="stats.php">Statistics</a>
<?php } else {
print_simple_box_start('center', '', 'white', 10);
<?php
if (!$advanced) {
?>
<input type="text" name="query_string" length="50" value="<?php print stripslashes($query_string) ?>" />
&nbsp;<input type="submit" value="<?php print_string('search', 'search') ?>" /> &nbsp;
<a href="query.php?a=1"><?php print_string('advancedsearch', 'search') ?></a> |
<a href="stats.php"><?php print_string('statistics', 'search') ?></a>
<?php
}
else {
print_box_start();
?>
<input type="hidden" name="a" value="<?php print $advanced; ?>"/>
<table border="0" cellpadding="3" cellspacing="3">
<tr>
<td width="240">These words must appear:</td>
<td width="240"><?php print_string('thesewordsmustappear', 'search') ?>:</td>
<td><input type="text" name="mustappear" length="50" value="<?php print $adv->mustappear; ?>" /></td>
</tr>
<tr>
<td>These words must not appear:</td>
<td><?php print_string('thesewordsmustnotappear', 'search') ?>:</td>
<td><input type="text" name="notappear" length="50" value="<?php print $adv->notappear; ?>" /></td>
</tr>
<tr>
<td>These words help improve rank:</td>
<td><?php print_string('thesewordshelpimproverank', 'search') ?>:</td>
<td><input type="text" name="canappear" length="50" value="<?php print $adv->canappear; ?>" /></td>
</tr>
<tr>
<td>Which modules to search?:</td>
<td><?php print_string('whichmodulestosearch?', 'search') ?>:</td>
<td>
<select name="module">
<?php foreach($module_types as $mod) {
<?php
foreach($module_types as $mod) {
if ($mod == $adv->module) {
print "<option value='$mod' selected>$mod</option>\n";
} else {
print "<option value='$mod'>$mod</option>\n";
} //else
} ?>
if ($mod != 'all'){
print "<option value='$mod' selected=\"selected\">".get_string('modulenameplural', $mod)."</option>\n";
}
else{
print "<option value='$mod' selected=\"selected\">".get_string('all', 'search')."</option>\n";
}
}
else {
if ($mod != 'all'){
print "<option value='$mod'>".get_string('modulenameplural', $mod)."</option>\n";
}
else{
print "<option value='$mod'>".get_string('all', 'search')."</option>\n";
}
}
}
?>
</select>
</td>
</tr>
<tr>
<td>Words in title:</td>
<td><?php print_string('wordsintitle', 'search') ?>:</td>
<td><input type="text" name="title" length="50" value="<?php print $adv->title; ?>" /></td>
</tr>
<tr>
<td>Author name:</td>
<td><?php print_string('authorname', 'search') ?>:</td>
<td><input type="text" name="author" length="50" value="<?php print $adv->author; ?>" /></td>
</tr>
<tr>
<td colspan="3" align="center"><br /><input type="submit" value="Search" /></td>
<td colspan="3" align="center"><br /><input type="submit" value="<?php print_string('search', 'search') ?>" /></td>
</tr>
<tr>
<td colspan="3" align="center">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td><a href="query.php">Normal search</a> |</td>
<td>&nbsp;<a href="stats.php">Statistics</a></td>
<td><a href="query.php"><?php print_string('normalsearch', 'search') ?></a> |</td>
<td>&nbsp;<a href="stats.php"><?php print_string('statistics', 'search') ?></a></td>
</tr>
</table>
</td>
</tr>
</table>
<?php
print_simple_box_end();
} //if
?>
<?php
print_box_end();
}
?>
</form>
<br/>
<div align="center">
<?php
print_string('searching', 'search') . ': ';
print '<div align="center">';
print 'Searching: ';
if ($sq->is_valid_index()) {
if ($sq->is_valid_index()) {
//use cached variable to show up-to-date index size (takes deletions into account)
print $CFG->search_index_size;
} else {
}
else {
print "0";
} //else
}
print ' documents.';
print ' ';
print_string('documents', 'search');
print '.';
if (!$sq->is_valid_index() and isadmin()) {
print "<p>Admin: There appears to be no search index. Please <a href='indexersplash.php'>create an index</a>.</p>\n";
} //if
if (!$sq->is_valid_index() and isadmin()) {
print '<p>' . get_string('noindexmessage', 'search') . '<a href="indexersplash.php">' . get_string('createanindex', 'search')."</a></p>\n";
}
print '</div>';
?>
</div>
<?php
print_box_end();
print_simple_box_end();
if ($sq->is_valid()) {
print_simple_box_start('center', '50%', 'white', 10);
// prints all the results in a box
if ($sq->is_valid()) {
print_box_start();
search_stopwatch();
$hit_count = $sq->count();
print "<br />";
print $hit_count." results returned for '".stripslashes($query_string)."'.";
print $hit_count.' '.get_string('resultsreturnedfor', 'search') . " '".stripslashes($query_string)."'.";
print "<br />";
if ($hit_count > 0) {
@ -264,35 +293,45 @@
$hits = $sq->results();
if ($advanced) {
//if in advanced mode, search options are saved in the session, so
//we can remove the query string var from the page links, and replace
//it with a=1 (Advanced = on) instead
// if in advanced mode, search options are saved in the session, so
// we can remove the query string var from the page links, and replace
// it with a=1 (Advanced = on) instead
$page_links = preg_replace("/query_string=[^&]+/", 'a=1', $page_links);
} //if
}
print "<ol>";
$typestr = get_string('type', 'search');
$scorestr = get_string('score', 'search');
$authorstr = get_string('author', 'search');
foreach ($hits as $listing) {
print "<li value='".($listing->number+1)."'><a href='".$listing->url."'>$listing->title</a><br />\n"
if ($CFG->unicodedb) $listing->title = mb_convert_encoding($listing->title, 'auto', 'UTF8');
$title_post_processing_function = $listing->doctype.'_link_post_processing';
require_once "{$CFG->dirroot}/search/documents/{$listing->doctype}_document.php";
if (function_exists($title_post_processing_function))
$listing->title = $title_post_processing_function($listing->title);
print "<li value='".($listing->number+1)."'><a href='".str_replace('DEFAULT_POPUP_SETTINGS', DEFAULT_POPUP_SETTINGS ,$listing->url)."'>$listing->title</a><br />\n"
."<em>".search_shorten_url($listing->url, 70)."</em><br />\n"
."Type: ".$listing->doctype.", score: ".round($listing->score, 3).", author: ".$listing->author."\n"
."{$typestr}: ".$listing->doctype.", {$scorestr}: ".round($listing->score, 3).", {$authorstr}: ".$listing->author."\n"
."</li>\n";
} //for
}
print "</ol>";
print $page_links;
} //if
}
print_simple_box_end();
print_box_end();
?>
<div align="center">
It took <?php search_stopwatch(); ?> to fetch these results.
<?php
print_string('ittook', 'search');
search_stopwatch();
print_string('tofetchtheseresults', 'search');
?>.
</div>
<?php
} //if (sq is valid)
print_simple_box_end();
print_footer();
}
print_box_end();
print_footer();
?>

View file

@ -1,22 +1,28 @@
<?php
require_once("$CFG->dirroot/search/Zend/Search/Lucene.php");
require_once("{$CFG->dirroot}/search/Zend/Search/Lucene.php");
class SearchResult {
public $url,
define('DEFAULT_POPUP_SETTINGS', "\"menubar=0,location=0,scrollbars,resizable,width=600,height=450\"");
/**
* a class that represents a single result record of the search engine
*/
class SearchResult {
public $url,
$title,
$doctype,
$author,
$score,
$number;
} //SearchResult
} //SearchResult
//split this into Cache class and extend to SearchCache?
class SearchCache {
private $mode,
//split this into Cache class and extend to SearchCache?
class SearchCache {
private $mode,
$valid;
public function __construct($mode='session') {
// foresees other caching locations
public function __construct($mode = 'session') {
$accepted_modes = array('session');
if (in_array($mode, $accepted_modes)) {
@ -28,11 +34,19 @@
$this->valid = true;
} //constructor
/**
* returns the search cache status
* @return boolean
*/
public function can_cache() {
return $this->valid;
} //can_cache
public function cache($id=false, $object=false) {
/**
*
*
*/
public function cache($id = false, $object = false) {
//see if there was a previous query
$last_term = $this->fetch('search_last_term');
@ -47,11 +61,17 @@
$this->store($id, $object);
return true;
//otherwise return the stored results
} else if ($id and $this->exists($id)) {
}
else if ($id and $this->exists($id)) {
return $this->fetch($id);
} //else
} //cache
/**
* do key exist in cache ?
* @param id the object key
* @return boolean
*/
private function exists($id) {
switch ($this->mode) {
case 'session' :
@ -59,6 +79,11 @@
} //switch
} //exists
/**
* clears a cached object in cache
* @param the object key to clear
* @return void
*/
private function clear($id) {
switch ($this->mode) {
case 'session' :
@ -68,6 +93,11 @@
} //switch
} //clear
/**
* fetches a cached object
* @param id the object identifier
* @return the object cached
*/
private function fetch($id) {
switch ($this->mode) {
case 'session' :
@ -75,6 +105,12 @@
} //switch
} //fetch
/**
* put an object in cache
* @param id the key for that object
* @param object the object to cache as a serialized value
* @return void
*/
private function store($id, $object) {
switch ($this->mode) {
case 'session' :
@ -82,10 +118,13 @@
return;
} //switch
} //store
} //SearchCache
} //SearchCache
class SearchQuery {
/**
* Represents a single query with results
*
*/
class SearchQuery {
private $index,
$term,
$pagenumber,
@ -96,7 +135,11 @@
$results_per_page,
$total_results;
public function __construct($term='', $page=1, $results_per_page=10, $cache=false) {
/**
* constructor records query parameters
*
*/
public function __construct($term = '', $page = 1, $results_per_page = 10, $cache = false) {
global $CFG;
$this->term = $term;
@ -122,28 +165,43 @@
} //else
} //constructor
public function set_query($term='') {
/**
* determines state of query object depending on query entry and
* tries to lauch search if all is OK
* @return void (this is only a state changing trigger).
*/
public function set_query($term = '') {
if (!empty($term)) {
$this->term = $term;
} //if
if (empty($this->term)) {
$this->validquery = false;
} else {
}
else {
$this->validquery = true;
} //else
if ($this->validquery and $this->validindex) {
$this->results = $this->get_results();
} else {
}
else {
$this->results = array();
} //else
} //set_query
/**
* accessor to the result table.
* @return an array of result records
*/
public function results() {
return $this->results;
} //results
/**
* do the effective collection of results
*
*/
private function process_results($all=false) {
global $USER;
@ -166,8 +224,9 @@
if (!$all) {
if ($hitcount < $this->results_per_page) {
$this->pagenumber = 1;
} else if ($this->pagenumber > $totalpages) {
$this->pagenumber =$totalpages;
}
else if ($this->pagenumber > $totalpages) {
$this->pagenumber = $totalpages;
} //if
$start = ($this->pagenumber - 1) * $this->results_per_page;
@ -176,7 +235,8 @@
if ($end > $hitcount) {
$end = $hitcount;
} //if
} else {
}
else {
$start = 0;
$end = $hitcount;
} //else
@ -188,7 +248,7 @@
$hit = $hits[$i];
//check permissions on each result
if ($this->can_display($USER, $hit->id, $hit->doctype, $hit->course_id, $hit->group_id)) {
if ($this->can_display($USER, $hit->docid, $hit->doctype, $hit->course_id, $hit->group_id, $hit->path, $hit->itemtype, $hit->context_id )) {
$resultdoc->number = $i;
$resultdoc->url = $hit->url;
$resultdoc->title = $hit->title;
@ -199,11 +259,19 @@
//and store it
$resultdocs[] = clone($resultdoc);
} //if
else{
// lowers total_results one unit
$this->total_results--;
}
} //foreach
return $resultdocs;
} //process_results
/**
* get results of a search query using a caching strategy if available
* @return the result documents as an array of search objects
*/
private function get_results() {
$cache = new SearchCache();
@ -213,11 +281,13 @@
//cache the results so we don't have to compute this on every page-load
$cache->cache($this->term, $resultdocs);
//print "Using new results.";
} else {
}
else {
//There was something in the cache, so we're using that to save time
//print "Using cached results.";
} //else
} else {
}
else {
//no caching :(
//print "Caching disabled!";
$resultdocs = $this->process_results();
@ -226,36 +296,40 @@
return $resultdocs;
} //get_results
/**
* constructs the results paging links on results.
* @return string the results paging links
*/
public function page_numbers() {
$pages = $this->total_pages();
$query = htmlentities($this->term);
$page = $this->pagenumber;
$next = "Next";
$back = "Back";
$next = get_string('next', 'search');
$back = get_string('back', 'search');
$ret = "<div align='center' id='search_page_links'>";
//Back is disabled if we're on page 1
if ($page > 1) {
$ret .= "<a href='query.php?query_string=$query&page=".($page-1)."'>< $back</a>&nbsp;";
$ret .= "<a href='query.php?query_string={$query}&page=".($page-1)."'>&lt; {$back}</a>&nbsp;";
} else {
$ret .= "< $back&nbsp;";
$ret .= "&lt; {$back}&nbsp;";
} //else
//don't <a href> the current page
for ($i = 1; $i <= $pages; $i++) {
if ($page == $i) {
$ret .= "[$i]&nbsp;";
$ret .= "($i)&nbsp;";
} else {
$ret .= "<a href='query.php?query_string=$query&page=$i'>$i</a>&nbsp;";
$ret .= "<a href='query.php?query_string={$query}&page={$i}'>{$i}</a>&nbsp;";
} //else
} //for
//Next disabled if we're on the last page
if ($page < $pages) {
$ret .= "<a href='query.php?query_string=$query&page=".($page+1)."'>$next ></a>&nbsp;";
$ret .= "<a href='query.php?query_string={$query}&page=".($page+1)."'>{$next} &gt;</a>&nbsp;";
} else {
$ret .= "$next >&nbsp;";
$ret .= "{$next} &gt;&nbsp;";
} //else
$ret .= "</div>";
@ -274,13 +348,68 @@
return $ret;
} //page_numbers
//can the user see this result?
private function can_display(&$user, $this_id, $doctype, $course_id, $group_id) {
//this function should return true/false depending on
//whether or not a user can see this resource
//..
//if one of you nice moodlers see this, feel free to
//implement it for me .. :-P
/**
* can the user see this result ?
* @param user a reference upon the user to be checked for access
* @param this_id the item identifier
* @param doctype the search document type. MAtches the module or block or
* extra search source definition
* @param course_id the course reference of the searched result
* @param group_id the group identity attached to the found resource
* @param path the path that routes to the local lib.php of the searched
* surrounding object fot that document
* @param item_type a subclassing information for complex module data models
* // TODO reorder parameters more consistently
*/
private function can_display(&$user, $this_id, $doctype, $course_id, $group_id, $path, $item_type, $context_id) {
global $CFG;
/**
* course related checks
*/
// admins can see everything, anyway.
if (isadmin()){
return true;
}
// first check course compatibility against user : enrolled users to that course can see.
$myCourses = get_my_courses($user->id);
$unenroled = !in_array($course_id, array_keys($myCourses));
// if guests are allowed, logged guest can see
$isallowedguest = (isguest()) ? get_field('course', 'guest', 'id', $course_id) : false ;
if ($unenroled && !$isallowedguest){
return false;
}
// if user is enrolled or is allowed user and course is hidden, can he see it ?
$visibility = get_field('course', 'visible', 'id', $course_id);
if ($visibility <= 0){
if (!has_capability('moodle/course:viewhiddencourses', get_context_instance(CONTEXT_COURSE, $course->id))){
return false;
}
}
/**
* prerecorded capabilities
*/
// get context caching information and tries to discard unwanted records here
/**
* final checks
*/
// then give back indexing data to the module for local check
include_once "{$CFG->dirroot}/search/documents/{$doctype}_document.php";
$access_check_function = "{$doctype}_check_text_access";
if (function_exists($access_check_function)){
$modulecheck = $access_check_function($path, $item_type, $this_id, $user, $group_id, $context_id);
// echo "module said $modulecheck for item $doctype/$item_type/$this_id";
return($modulecheck);
}
return true;
} //can_display

View file

@ -1,51 +1,75 @@
<?php
/* Prints some basic statistics about the current index.
*
* Does some diagnostics if you are logged in as an administrator.
* */
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* Prints some basic statistics about the current index.
* Does some diagnostics if you are logged in as an administrator.
*
*/
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("{$CFG->dirroot}/search/lib.php");
if ($CFG->forcelogin) {
if ($CFG->forcelogin) {
require_login();
}
}
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
//check for php5, but don't die yet
if ($check = search_check_php5()) {
require_once("$CFG->dirroot/search/indexlib.php");
//check for php5, but don't die yet
if ($check = search_check_php5()) {
require_once("{$CFG->dirroot}/search/indexlib.php");
$indexinfo = new IndexInfo();
} //if
}
if (!$site = get_site()) {
if (!$site = get_site()) {
redirect("index.php");
} //if
}
$strsearch = "Search"; //get_string();
$strquery = "Search statistics"; //get_string();
$strsearch = get_string('search', 'search');
$strquery = get_string('statistics', 'search');
print_header("$site->shortname: $strsearch: $strquery", "$site->fullname",
print_header("$site->shortname: $strsearch: $strquery", "$site->fullname",
"<a href=\"index.php\">$strsearch</a> -> $strquery");
//keep things pretty, even if php5 isn't available
if (!$check) {
//keep things pretty, even if php5 isn't available
if (!$check) {
print_heading(search_check_php5(true));
print_footer();
exit(0);
} //if
}
print_simple_box_start('center', '100%', '', 20);
print_heading($strquery);
print_box_start();
print_heading($strquery);
print_simple_box_start('center', '', '', 20);
print_box_start();
$databasestr = get_string('database', 'search');
$documentsinindexstr = get_string('documentsinindex', 'search');
$deletionsinindexstr = get_string('deletionsinindex', 'search');
$documentsindatabasestr = get_string('documentsindatabase', 'search');
$databasestatestr = get_string('databasestate', 'search');
//this table is only for admins, shows index directory size and location
if (isadmin()) {
$datadirectorystr = get_string('datadirectory', 'search');
$inindexdirectorystr = get_string('filesinindexdirectory', 'search');
$totalsizestr = get_string('totalsize', 'search');
$errorsstr = get_string('errors', 'search');
$solutionsstr = get_string('solutions', 'search');
$checkdirstr = get_string('checkdir', 'search');
$checkdbstr = get_string('checkdb', 'search');
$checkdiradvicestr = get_string('checkdiradvice', 'search');
$checkdbadvicestr = get_string('checkdbadvice', 'search');
$runindexerteststr = get_string('runindexertest', 'search');
$runindexerstr = get_string('runindexer', 'search');
//this table is only for admins, shows index directory size and location
if (isadmin()) {
$admin_table->tablealign = "center";
$admin_table->align = array ("right", "left");
$admin_table->wrap = array ("nowrap", "nowrap");
@ -53,72 +77,73 @@
$admin_table->cellspacing = 0;
$admin_table->width = '500';
$admin_table->data[] = array('<strong>Data directory</strong>', '<em><strong>'.$indexinfo->path.'</strong></em>');
$admin_table->data[] = array('Files in index directory', $indexinfo->filecount);
$admin_table->data[] = array('Total size', $indexinfo->size);
$admin_table->data[] = array("<strong>{$datadirectorystr}</strong>", '<em><strong>'.$indexinfo->path.'</strong></em>');
$admin_table->data[] = array($inindexdirectorystr, $indexinfo->filecount);
$admin_table->data[] = array($totalsizestr, $indexinfo->size);
if ($indexinfo->time > 0) {
$admin_table->data[] = array('Created on', date('r', $indexinfo->time));
} else {
$admin_table->data[] = array('Created on', '-');
} //else
$admin_table->data[] = array(get_string('createdon', 'search'), date('r', $indexinfo->time));
}
else {
$admin_table->data[] = array(get_string('createdon', 'search'), '-');
}
if (!$indexinfo->valid($errors)) {
$admin_table->data[] = array('<strong>Errors</strong>', '&nbsp;');
foreach ($errors as $key=>$value) {
$admin_table->data[] = array("<strong>{$errorsstr}</strong>", '&nbsp;');
foreach ($errors as $key => $value) {
$admin_table->data[] = array($key.' ... ', $value);
} //foreach
}
}
$admin_table->data[] = array('<strong>Solutions</strong>', '&nbsp;');
print_table($admin_table);
print_spacer(20);
print_heading($solutionsstr);
unset($admin_table->data);
if (isset($errors['dir'])) {
$admin_table->data[] = array('Check dir', 'Ensure the data directory exists and is writable.');
} //if
$admin_table->data[] = array($checkdirstr, $checkdiradvicestr);
}
if (isset($errors['db'])) {
$admin_table->data[] = array('Check DB', 'Check your database for any problems.');
} //if
$admin_table->data[] = array($checkdbstr, $checkdbadvicestr);
}
$admin_table->data[] = array('Run indexer test', '<a href=\'tests/index.php\'>tests/index.php</a>');
$admin_table->data[] = array('Run indexer', '<a href=\'indexersplash.php\'>indexersplash.php</a>');
} //if
} //if
$admin_table->data[] = array($runindexerteststr, '<a href="tests/index.php" target="_blank">tests/index.php</a>');
$admin_table->data[] = array($runindexerstr, '<a href="indexersplash.php" target="_blank">indexersplash.php</a>');
//this is the standard summary table for normal users, shows document counts
$table->tablealign = "center";
$table->align = array ("right", "left");
$table->wrap = array ("nowrap", "nowrap");
$table->cellpadding = 5;
$table->cellspacing = 0;
$table->width = '500';
print_table($admin_table);
print_spacer(20);
}
$table->data[] = array('<strong>Database</strong>', '<em><strong>search_documents<strong></em>');
//this is the standard summary table for normal users, shows document counts
$table->tablealign = "center";
$table->align = array ("right", "left");
$table->wrap = array ("nowrap", "nowrap");
$table->cellpadding = 5;
$table->cellspacing = 0;
$table->width = '500';
//add extra fields if we're admin
if (isadmin()) {
$table->data[] = array("<strong>{$databasestr}</strong>", "<em><strong>{$CFG->prefix}search_documents</strong></em>");
//add extra fields if we're admin
if (isadmin()) {
//don't want to confuse users if the two totals don't match (hint: they should)
$table->data[] = array('Documents in index', $indexinfo->indexcount);
$table->data[] = array($documentsinindexstr, $indexinfo->indexcount);
//*cough* they should match if deletions were actually removed from the index,
//as it turns out, they're only marked as deleted and not returned in search results
$table->data[] = array('Deletions in index', (int)$indexinfo->indexcount - (int)$indexinfo->dbcount);
} //if
$table->data[] = array($deletionsinindexstr, (int)$indexinfo->indexcount - (int)$indexinfo->dbcount);
}
$table->data[] = array('Documents in database', $indexinfo->dbcount);
$table->data[] = array($documentsindatabasestr, $indexinfo->dbcount);
foreach($indexinfo->types as $key => $value) {
$table->data[] = array("'$key' documents", $value);
} //foreach
foreach($indexinfo->types as $key => $value) {
$table->data[] = array(get_string('documentsfor', 'search') . " '".get_string('modulenameplural', $key)."'", $value);
}
if (isadmin()) {
print_table($admin_table);
print_spacer(20);
} //if
print_heading($databasestatestr);
print_table($table);
print_table($table);
print_simple_box_end();
print_simple_box_end();
print_footer();
print_box_end();
print_box_end();
print_footer();
?>

View file

@ -1,36 +1,47 @@
<?php
/**
* Global Search Engine for Moodle
* Michael Champanis (mchampan) [cynnical@gmail.com]
* review 1.8+ : Valery Fremaux [valery.fremaux@club-internet.fr]
* 2007/08/02
*
* Index asynchronous updator
*
* Major chages in this review is passing the xxxx_db_names return to
* multiple arity to handle multiple document types modules
*/
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_once('../config.php');
require_once("$CFG->dirroot/search/lib.php");
require_login();
require_login();
if (empty($CFG->enableglobalsearch)) {
error('Global searching is not enabled.');
}
if (empty($CFG->enableglobalsearch)) {
error(get_string('globalsearchdisabled', 'search'));
}
if (!isadmin()) {
error("You need to be an admin user to use this page.", "$CFG->wwwroot/login/index.php");
} //if
if (!isadmin()) {
error(get_string('beadmin', 'search'), "$CFG->wwwroot/login/index.php");
}
//check for php5 (lib.php)
if (!search_check_php5()) {
//check for php5 (lib.php)
if (!search_check_php5()) {
$phpversion = phpversion();
mtrace("Sorry, global search requires PHP 5.0.0 or later (currently using version $phpversion)");
exit(0);
} //if
}
require_once("$CFG->dirroot/search/indexlib.php");
require_once("$CFG->dirroot/search/indexlib.php");
$index = new Zend_Search_Lucene(SEARCH_INDEX_PATH);
$dbcontrol = new IndexDBControl();
$update_count = 0;
$index = new Zend_Search_Lucene(SEARCH_INDEX_PATH);
$dbcontrol = new IndexDBControl();
$update_count = 0;
$indexdate = $CFG->search_indexer_update_date;
$startupdatedate = time();
$indexdate = $CFG->search_indexer_run_date;
mtrace("<pre>Starting index update (updates)...\n");
mtrace("<pre>Starting index update (updates)...\n");
if ($mods = get_records_select('modules')) {
if ($mods = get_records_select('modules')) {
$mods = array_merge($mods, search_get_additional_modules());
foreach ($mods as $mod) {
@ -43,63 +54,90 @@
if (file_exists($class_file)) {
require_once($class_file);
//if both required functions exist
if (function_exists($delete_function) and function_exists($db_names_function) and function_exists($get_document_function)) {
mtrace("Checking $mod->name module for updates.");
$values = $db_names_function();
$valuesArray = $db_names_function();
if ($valuesArray){
foreach($valuesArray as $values){
$where = (isset($values[5])) ? 'AND ('.$values[5].')' : '';
$itemtypes = ($values[4] != '*') ? " AND itemtype = '{$values[4]}' " : '' ;
//TODO: check 'in' syntax with other RDBMS' (add and update.php as well)
$sql = "select id, ".$values[0]." as docid from ".$values[1].
" where ".$values[3]." > $indexdate".
" and id in (select docid from ".SEARCH_DATABASE_TABLE.")";
$records = get_records_sql($sql);
$table = SEARCH_DATABASE_TABLE;
$query = "
SELECT
docid,
itemtype
FROM
{$CFG->prefix}{$table}
WHERE
doctype = '{$mod->name}'
$itemtypes
";
$docIds = get_records_sql_menu($query);
$docIdList = ($docIds) ? implode("','", array_keys($docIds)) : '' ;
$query = "
SELECT
id,
{$values[0]} as docid
FROM
{$CFG->prefix}{$values[1]}
WHERE
{$values[3]} > {$indexdate} AND
id IN ('{$docIdList}')
$where
";
$records = get_records_sql($query);
if (is_array($records)) {
foreach($records as $record) {
$updates[] = $delete_function($record->docid);
} //foreach
} //if
$updates[] = $delete_function($record->docid, $docIds[$record->docid]);
}
}
}
foreach ($updates as $update) {
++$update_count;
//delete old document
$doc = $index->find("+docid:$update +doctype:$mod->name");
$doc = $index->find("+docid:{$update->id} +doctype:{$mod->name} +itemtype:{$update->itemtype}");
//get the record, should only be one
foreach ($doc as $thisdoc) {
mtrace(" Delete: $thisdoc->title (database id = $thisdoc->dbid, index id = $thisdoc->id, moodle instance id = $thisdoc->docid)");
$dbcontrol->delDocument($thisdoc);
$index->delete($thisdoc->id);
} //foreach
}
//add new modified document back into index
$add = $get_document_function($update);
$add = $get_document_function($update->id, $update->itemtype);
//object to insert into db
$dbid = $dbcontrol->addDocument($add);
//synchronise db with index
$add->addField(Zend_Search_Lucene_Field::Keyword('dbid', $dbid));
mtrace(" Add: $add->title (database id = $add->dbid, moodle instance id = $add->docid)");
$index->addDocument($add);
} //foreach
}
}
else{
mtrace("No types to update.\n");
}
mtrace("Finished $mod->name.\n");
} //if
} //if
} //foreach
} //if
}
}
}
}
//commit changes
$index->commit();
//commit changes
$index->commit();
//update index date
set_config("search_indexer_run_date", time());
//update index date
set_config("search_indexer_update_date", $startupdatedate);
mtrace("Finished $update_count updates.</pre>");
mtrace("Finished $update_count updates.</pre>");
?>