Skip to content

Commit

Permalink
Merge branch 'MDL-59434-master' of https://github.com/sammarshallou/m…
Browse files Browse the repository at this point in the history
  • Loading branch information
snake committed Mar 21, 2018
2 parents 618b223 + fc44079 commit 9a2bf09
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lang/en/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@
$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';
Expand Down
13 changes: 13 additions & 0 deletions search/classes/engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,17 @@ protected function update_schema($oldversion, $newversion) {
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')];
}
}
2 changes: 2 additions & 0 deletions search/classes/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,8 @@ public function paged_search(\stdClass $formdata, $pagenum) {
* - 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
Expand Down
9 changes: 9 additions & 0 deletions search/classes/output/form/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ function definition() {
$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);

Expand Down
36 changes: 36 additions & 0 deletions search/engine/solr/classes/engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -370,6 +376,16 @@ protected function create_user_query($filters, $accessinfo) {
$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;
}

Expand Down Expand Up @@ -1357,4 +1373,24 @@ protected function update_schema($oldversion, $newversion) {

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;
}
}
126 changes: 126 additions & 0 deletions search/engine/solr/tests/engine_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -950,4 +950,130 @@ protected function assert_result_titles(array $expected, array $results) {
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);
}
}
8 changes: 8 additions & 0 deletions search/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
}
$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);

Expand Down Expand Up @@ -112,6 +115,11 @@
}
}

// Inform search engine about source context.
if (!empty($context) && $data) {
$data->context = $context;
}

// Set the page URL.
$urlparams = array('page' => $page);
if ($data) {
Expand Down
10 changes: 10 additions & 0 deletions search/tests/engine_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,14 @@ public function test_engine_schema_modification() {
$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);
}
}
4 changes: 4 additions & 0 deletions search/upgrade.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9a2bf09

Please sign in to comment.