mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 12:32:08 +02:00
Merge branch 'MDL-59434-master' of https://github.com/sammarshallou/moodle
This commit is contained in:
commit
9a2bf09d19
@ -81,6 +81,9 @@ $string['notitle'] = 'No title';
|
||||
$string['normalsearch'] = 'Normal search';
|
||||
$string['openedon'] = 'opened on';
|
||||
$string['optimize'] = 'Optimize';
|
||||
$string['order'] = 'Results order';
|
||||
$string['order_location'] = 'Prioritise results related to {$a}';
|
||||
$string['order_relevance'] = 'Most relevant results first';
|
||||
$string['priority'] = 'Priority';
|
||||
$string['priority_reindexing'] = 'Reindexing';
|
||||
$string['priority_normal'] = 'Normal';
|
||||
|
@ -543,4 +543,17 @@ abstract class engine {
|
||||
public function supports_group_filtering() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a list of results orders (and names for them) that are supported by this
|
||||
* search engine in the given context.
|
||||
*
|
||||
* By default, engines sort by relevance only.
|
||||
*
|
||||
* @param \context $context Context that the user requested search from
|
||||
* @return array Array from order name => display text
|
||||
*/
|
||||
public function get_supported_orders(\context $context) {
|
||||
return ['relevance' => get_string('order_relevance', 'search')];
|
||||
}
|
||||
}
|
||||
|
@ -684,6 +684,8 @@ class manager {
|
||||
* - q (query text)
|
||||
* - courseids (optional list of course ids to restrict)
|
||||
* - contextids (optional list of context ids to restrict)
|
||||
* - context (Moodle context object for location user searched from)
|
||||
* - order (optional ordering, one of the types supported by the search engine e.g. 'relevance')
|
||||
*
|
||||
* @param \stdClass $formdata Query input data (usually from search form)
|
||||
* @param int $limit The maximum number of documents to return
|
||||
|
@ -55,6 +55,15 @@ class search extends \moodleform {
|
||||
$mform->setDefault('searchwithin', '');
|
||||
}
|
||||
|
||||
// If the search engine provides multiple ways to order results, show options.
|
||||
if (!empty($this->_customdata['orderoptions']) &&
|
||||
count($this->_customdata['orderoptions']) > 1) {
|
||||
|
||||
$mform->addElement('select', 'order', get_string('order', 'search'),
|
||||
$this->_customdata['orderoptions']);
|
||||
$mform->setDefault('order', 'relevance');
|
||||
}
|
||||
|
||||
$mform->addElement('header', 'filtersection', get_string('filterheader', 'search'));
|
||||
$mform->setExpanded('filtersection', false);
|
||||
|
||||
|
@ -65,6 +65,12 @@ class engine extends \core_search\engine {
|
||||
*/
|
||||
const HIGHLIGHT_END = '@@HI_E@@';
|
||||
|
||||
/** @var float Boost value for matching course in location-ordered searches */
|
||||
const COURSE_BOOST = 1;
|
||||
|
||||
/** @var float Boost value for matching context (in addition to course boost) */
|
||||
const CONTEXT_BOOST = 0.5;
|
||||
|
||||
/**
|
||||
* @var \SolrClient
|
||||
*/
|
||||
@ -370,6 +376,16 @@ class engine extends \core_search\engine {
|
||||
$query->addFilterQuery('type:'.\core_search\manager::TYPE_TEXT);
|
||||
}
|
||||
|
||||
// If ordering by location, add in boost for the relevant course or context ids.
|
||||
if (!empty($filters->order) && $filters->order === 'location') {
|
||||
$coursecontext = $filters->context->get_course_context();
|
||||
$query->addBoostQuery('courseid', $coursecontext->instanceid, self::COURSE_BOOST);
|
||||
if ($filters->context->contextlevel !== CONTEXT_COURSE) {
|
||||
// If it's a block or activity, also add a boost for the specific context id.
|
||||
$query->addBoostQuery('contextid', $filters->context->id, self::CONTEXT_BOOST);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@ -1357,4 +1373,24 @@ class engine extends \core_search\engine {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solr supports sort by location within course contexts or below.
|
||||
*
|
||||
* @param \context $context Context that the user requested search from
|
||||
* @return array Array from order name => display text
|
||||
*/
|
||||
public function get_supported_orders(\context $context) {
|
||||
$orders = parent::get_supported_orders($context);
|
||||
|
||||
// If not within a course, no other kind of sorting supported.
|
||||
$coursecontext = $context->get_course_context(false);
|
||||
if ($coursecontext) {
|
||||
// Within a course or activity/block, support sort by location.
|
||||
$orders['location'] = get_string('order_location', 'search',
|
||||
$context->get_context_name());
|
||||
}
|
||||
|
||||
return $orders;
|
||||
}
|
||||
}
|
||||
|
@ -950,4 +950,130 @@ class search_solr_engine_testcase extends advanced_testcase {
|
||||
sort($expected);
|
||||
$this->assertEquals($expected, $titles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the get_supported_orders function for contexts where we can only use relevance
|
||||
* (system, category).
|
||||
*/
|
||||
public function test_get_supported_orders_relevance_only() {
|
||||
global $DB;
|
||||
|
||||
// System or category context: relevance only.
|
||||
$orders = $this->engine->get_supported_orders(\context_system::instance());
|
||||
$this->assertCount(1, $orders);
|
||||
$this->assertArrayHasKey('relevance', $orders);
|
||||
|
||||
$categoryid = $DB->get_field_sql('SELECT MIN(id) FROM {course_categories}');
|
||||
$orders = $this->engine->get_supported_orders(\context_coursecat::instance($categoryid));
|
||||
$this->assertCount(1, $orders);
|
||||
$this->assertArrayHasKey('relevance', $orders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the get_supported_orders function for contexts where we support location as well
|
||||
* (course, activity, block).
|
||||
*/
|
||||
public function test_get_supported_orders_relevance_and_location() {
|
||||
global $DB;
|
||||
|
||||
// Test with course context.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course(['fullname' => 'Frogs']);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
|
||||
$orders = $this->engine->get_supported_orders($coursecontext);
|
||||
$this->assertCount(2, $orders);
|
||||
$this->assertArrayHasKey('relevance', $orders);
|
||||
$this->assertArrayHasKey('location', $orders);
|
||||
$this->assertContains('Course: Frogs', $orders['location']);
|
||||
|
||||
// Test with activity context.
|
||||
$page = $generator->create_module('page', ['course' => $course->id, 'name' => 'Toads']);
|
||||
|
||||
$orders = $this->engine->get_supported_orders(\context_module::instance($page->cmid));
|
||||
$this->assertCount(2, $orders);
|
||||
$this->assertArrayHasKey('relevance', $orders);
|
||||
$this->assertArrayHasKey('location', $orders);
|
||||
$this->assertContains('Page: Toads', $orders['location']);
|
||||
|
||||
// Test with block context.
|
||||
$instance = (object)['blockname' => 'html', 'parentcontextid' => $coursecontext->id,
|
||||
'showinsubcontexts' => 0, 'pagetypepattern' => 'course-view-*',
|
||||
'defaultweight' => 0, 'timecreated' => 1, 'timemodified' => 1,
|
||||
'configdata' => ''];
|
||||
$blockid = $DB->insert_record('block_instances', $instance);
|
||||
$blockcontext = \context_block::instance($blockid);
|
||||
|
||||
$orders = $this->engine->get_supported_orders($blockcontext);
|
||||
$this->assertCount(2, $orders);
|
||||
$this->assertArrayHasKey('relevance', $orders);
|
||||
$this->assertArrayHasKey('location', $orders);
|
||||
$this->assertContains('Block: HTML', $orders['location']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ordering by relevance vs location.
|
||||
*/
|
||||
public function test_ordering() {
|
||||
// Create 2 courses and 2 activities.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course1 = $generator->create_course(['fullname' => 'Course 1']);
|
||||
$course1context = \context_course::instance($course1->id);
|
||||
$course1page = $generator->create_module('page', ['course' => $course1]);
|
||||
$course1pagecontext = \context_module::instance($course1page->cmid);
|
||||
$course2 = $generator->create_course(['fullname' => 'Course 2']);
|
||||
$course2context = \context_course::instance($course2->id);
|
||||
$course2page = $generator->create_module('page', ['course' => $course2]);
|
||||
$course2pagecontext = \context_module::instance($course2page->cmid);
|
||||
|
||||
// Create one search record in each activity and course.
|
||||
$this->create_search_record($course1->id, $course1context->id, 'C1', 'Xyzzy');
|
||||
$this->create_search_record($course1->id, $course1pagecontext->id, 'C1P', 'Xyzzy');
|
||||
$this->create_search_record($course2->id, $course2context->id, 'C2', 'Xyzzy');
|
||||
$this->create_search_record($course2->id, $course2pagecontext->id, 'C2P', 'Xyzzy plugh');
|
||||
$this->search->index();
|
||||
|
||||
// Default search works by relevance so the one with both words should be top.
|
||||
$querydata = new stdClass();
|
||||
$querydata->q = 'xyzzy plugh';
|
||||
$results = $this->search->search($querydata);
|
||||
$this->assertCount(4, $results);
|
||||
$this->assertEquals('C2P', $results[0]->get('title'));
|
||||
|
||||
// Same if you explicitly specify relevance.
|
||||
$querydata->order = 'relevance';
|
||||
$results = $this->search->search($querydata);
|
||||
$this->assertEquals('C2P', $results[0]->get('title'));
|
||||
|
||||
// If you specify order by location and you are in C2 or C2P then results are the same.
|
||||
$querydata->order = 'location';
|
||||
$querydata->context = $course2context;
|
||||
$results = $this->search->search($querydata);
|
||||
$this->assertEquals('C2P', $results[0]->get('title'));
|
||||
$querydata->context = $course2pagecontext;
|
||||
$results = $this->search->search($querydata);
|
||||
$this->assertEquals('C2P', $results[0]->get('title'));
|
||||
|
||||
// But if you are in C1P then you get different results (C1P first).
|
||||
$querydata->context = $course1pagecontext;
|
||||
$results = $this->search->search($querydata);
|
||||
$this->assertEquals('C1P', $results[0]->get('title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a record to the mock search area, so that the search engine can find it later.
|
||||
*
|
||||
* @param int $courseid Course id
|
||||
* @param int $contextid Context id
|
||||
* @param string $title Title for search index
|
||||
* @param string $content Content for search index
|
||||
*/
|
||||
protected function create_search_record($courseid, $contextid, $title, $content) {
|
||||
$record = new \stdClass();
|
||||
$record->content = $content;
|
||||
$record->title = $title;
|
||||
$record->courseid = $courseid;
|
||||
$record->contextid = $contextid;
|
||||
$this->generator->create_record($record);
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,9 @@ if ($contextid) {
|
||||
}
|
||||
$customdata['searchwithin'] = $searchwithin;
|
||||
}
|
||||
|
||||
// Get available ordering options from search engine.
|
||||
$customdata['orderoptions'] = $search->get_engine()->get_supported_orders($context);
|
||||
}
|
||||
$mform = new \core_search\output\form\search(null, $customdata);
|
||||
|
||||
@ -112,6 +115,11 @@ if ($data && !empty($data->searchwithin)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Inform search engine about source context.
|
||||
if (!empty($context) && $data) {
|
||||
$data->context = $context;
|
||||
}
|
||||
|
||||
// Set the page URL.
|
||||
$urlparams = array('page' => $page);
|
||||
if ($data) {
|
||||
|
@ -119,4 +119,14 @@ class search_engine_testcase extends advanced_testcase {
|
||||
$updates = $engine->get_and_clear_schema_updates();
|
||||
$this->assertCount(0, $updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the get_supported_orders stub function.
|
||||
*/
|
||||
public function test_get_supported_orders() {
|
||||
$engine = new \mock_search\engine();
|
||||
$orders = $engine->get_supported_orders(\context_system::instance());
|
||||
$this->assertCount(1, $orders);
|
||||
$this->assertArrayHasKey('relevance', $orders);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,10 @@ information provided here is intended especially for developers.
|
||||
contexts first. If not implemented, the default behaviour for modules and blocks is to reindex
|
||||
the newest items first; for other types of search area it will just index the whole system
|
||||
context, oldest data first.
|
||||
* Search engines may now implement get_supported_orders function to provide multiple ordering
|
||||
options (other than 'relevance' which is default). If there is more than one order then a choice
|
||||
will be shown to users. (This is an optional feature, existing search engine plugins do not need
|
||||
to be modified in order to continue working.)
|
||||
|
||||
* Module search areas that wish to support group filtering should set the new optional search
|
||||
document field groupid (note: to remain compatible with earlier versions, do this inside an if
|
||||
|
Loading…
x
Reference in New Issue
Block a user