27af904b85
engine give nicer results with adequate pagination. Adding icon and reference to course origine. Generalizing replacement of isadmin by doanithing capability check should contribute to close many global search related issues from http://tracker.moodle.org/browse/MDL-14646 startpoint
329 lines
11 KiB
PHP
329 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Global Search Engine for Moodle
|
|
*
|
|
* @package search
|
|
* @category core
|
|
* @subpackage document_wrappers
|
|
* @author Michael Campanis (mchampan) [cynnical@gmail.com], Valery Fremaux [valery.fremaux@club-internet.fr] > 1.8
|
|
* @date 2008/03/31
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/**
|
|
* requires and includes
|
|
*/
|
|
require_once("$CFG->dirroot/search/documents/document.php");
|
|
require_once("$CFG->dirroot/mod/resource/lib.php");
|
|
|
|
/* *
|
|
* a class for representing searchable information
|
|
*
|
|
*/
|
|
class ResourceSearchDocument extends SearchDocument {
|
|
public function __construct(&$resource, $context_id) {
|
|
// generic information; required
|
|
$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']);
|
|
|
|
// module specific information; optional
|
|
$data = array();
|
|
|
|
// construct the parent class
|
|
parent::__construct($doc, $data, $resource['course'], 0, 0, 'mod/'.SEARCH_TYPE_RESOURCE);
|
|
} //constructor
|
|
} //ResourceSearchDocument
|
|
|
|
/**
|
|
* 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?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
|
|
|
|
/**
|
|
* 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
|
|
* @uses CFG
|
|
* @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 != ' ' AND
|
|
type != 'file'
|
|
";
|
|
$resources = get_records_sql($query);
|
|
|
|
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");
|
|
}
|
|
|
|
// 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.
|
|
if ($resources){
|
|
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
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// cannot index empty references
|
|
if (empty($resource->reference)){
|
|
mtrace("Cannot index, empty reference.");
|
|
return false;
|
|
}
|
|
|
|
// cannot index remote resources
|
|
if (resource_is_url($resource->reference)){
|
|
mtrace("Cannot index remote URLs.");
|
|
return false;
|
|
}
|
|
|
|
$fileparts = pathinfo($resource->reference);
|
|
// cannot index unknown or masked types
|
|
if (empty($fileparts['extension'])) {
|
|
mtrace("Cannot index without explicit extension.");
|
|
return false;
|
|
}
|
|
|
|
// 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){
|
|
$single = new ResourceSearchDocument(get_object_vars($resource), $context_id);
|
|
mtrace("finished file $resource->name as {$resource->reference}");
|
|
return $single;
|
|
} 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 != ' ') 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(array('id', 'resource', 'timemodified', 'timemodified', 'any', " (alltext != '' AND alltext != ' ' AND alltext != ' ' 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->instanceid);
|
|
|
|
//check if found course module is visible
|
|
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $module_context)){
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* post processes the url for cleaner output.
|
|
* @param string $title
|
|
*/
|
|
function resource_link_post_processing($title){
|
|
// return mb_convert_encoding($title, 'UTF-8', 'auto');
|
|
return mb_convert_encoding($title, 'auto', 'UTF-8');
|
|
}
|
|
?>
|