placeholder = $attributes['placeholder'];
unset($attributes['placeholder']);
}
+ $this->noselectionstring = get_string('noselection', 'form');
+ if (isset($attributes['noselectionstring'])) {
+ $this->noselectionstring = $attributes['noselectionstring'];
+ unset($attributes['noselectionstring']);
+ }
+
if (isset($attributes['ajax'])) {
$this->ajax = $attributes['ajax'];
unset($attributes['ajax']);
@@ -114,7 +122,7 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
$this->_generateId();
$id = $this->getAttribute('id');
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax,
- $this->placeholder, $this->casesensitive, $this->showsuggestions));
+ $this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring));
return parent::toHTML();
}
diff --git a/lib/form/course.php b/lib/form/course.php
index 9121447209e..72e76db1d2b 100644
--- a/lib/form/course.php
+++ b/lib/form/course.php
@@ -54,6 +54,11 @@ class MoodleQuickForm_course extends MoodleQuickForm_autocomplete {
*/
protected $requiredcapabilities = array();
+ /**
+ * @var bool $limittoenrolled Only allow enrolled courses.
+ */
+ protected $limittoenrolled = false;
+
/**
* Constructor
*
@@ -78,15 +83,25 @@ class MoodleQuickForm_course extends MoodleQuickForm_autocomplete {
if (isset($options['requiredcapabilities'])) {
$this->requiredcapabilities = $options['requiredcapabilities'];
}
+ if (isset($options['limittoenrolled'])) {
+ $this->limittoenrolled = $options['limittoenrolled'];
+ }
$validattributes = array(
'ajax' => 'core/form-course-selector',
'data-requiredcapabilities' => implode(',', $this->requiredcapabilities),
- 'data-exclude' => implode(',', $this->exclude)
+ 'data-exclude' => implode(',', $this->exclude),
+ 'data-limittoenrolled' => (int)$this->limittoenrolled
);
if ($this->multiple) {
$validattributes['multiple'] = 'multiple';
}
+ if (isset($options['noselectionstring'])) {
+ $validattributes['noselectionstring'] = $options['noselectionstring'];
+ }
+ if (isset($options['placeholder'])) {
+ $validattributes['placeholder'] = $options['placeholder'];
+ }
parent::__construct($elementname, $elementlabel, array(), $validattributes);
}
diff --git a/lib/templates/form_autocomplete_selection.mustache b/lib/templates/form_autocomplete_selection.mustache
index 8eb5ec400fa..9f238d27c9b 100644
--- a/lib/templates/form_autocomplete_selection.mustache
+++ b/lib/templates/form_autocomplete_selection.mustache
@@ -29,12 +29,13 @@
* multiple True if this field allows multiple selections
* selectionId The dom id of the current selection list.
* items List of items with label and value fields.
+ * noSelectionString String to use when no items are selected
Example context (json):
{ "multiple": true, "selectionId": 1, "items": [
{ "label": "Item label with tags", "value": "5" },
{ "label": "Another item label with tags", "value": "4" }
- ]}
+ ], "noSelectionString": "No selection" }
}}
{{#str}}selecteditems, form{{/str}}
@@ -44,7 +45,7 @@
{{/items}}
{{^items}}
- {{#str}}noselection,form{{/str}}
+ {{noSelectionString}}
{{/items}}
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index cb4b8aa0f14..1e1dfc3e011 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -3,6 +3,8 @@ information provided here is intended especially for developers.
=== 3.1 ===
+* Webservice function core_course_search_courses accepts a new parameter 'limittoenrolled' to filter the results
+ only to courses the user is enrolled in, and are visible to them.
* The moodle/blog:associatecourse and moodle/blog:associatemodule capabilities has been removed.
* The following functions has been finally deprecated and can not be used any more:
- profile_display_badges()
diff --git a/search/classes/manager.php b/search/classes/manager.php
index 1a84344b942..47d8b0518dd 100644
--- a/search/classes/manager.php
+++ b/search/classes/manager.php
@@ -311,9 +311,10 @@ class manager {
* information and there will be a performance benefit on passing only some contexts
* instead of the whole context array set.
*
+ * @param array|false $limitcourseids An array of course ids to limit the search to. False for no limiting.
* @return bool|array Indexed by area identifier (component + area name). Returns true if the user can see everything.
*/
- protected function get_areas_user_accesses() {
+ protected function get_areas_user_accesses($limitcourseids = false) {
global $CFG, $USER;
// All results for admins. Eventually we could add a new capability for managers.
@@ -336,7 +337,7 @@ class manager {
// This will store area - allowed contexts relations.
$areascontexts = array();
- if (!empty($areasbylevel[CONTEXT_SYSTEM])) {
+ if (empty($limitcourseids) && !empty($areasbylevel[CONTEXT_SYSTEM])) {
// We add system context to all search areas working at this level. Here each area is fully responsible of
// the access control as we can not automate much, we can not even check guest access as some areas might
// want to allow guests to retrieve data from them.
@@ -349,9 +350,16 @@ class manager {
// Get the courses where the current user has access.
$courses = enrol_get_my_courses(array('id', 'cacherev'));
- $courses[SITEID] = get_course(SITEID);
- $site = \course_modinfo::instance(SITEID);
+
+ if (empty($limitcourseids) || in_array(SITEID, $limitcourseids)) {
+ $courses[SITEID] = get_course(SITEID);
+ }
+
foreach ($courses as $course) {
+ if (!empty($limitcourseids) && !in_array($course->id, $limitcourseids)) {
+ // Skip non-included courses.
+ continue;
+ }
// Info about the course modules.
$modinfo = get_fast_modinfo($course);
@@ -402,10 +410,15 @@ class manager {
public function search(\stdClass $formdata) {
global $USER;
+ $limitcourseids = false;
+ if (!empty($formdata->courseids)) {
+ $limitcourseids = $formdata->courseids;
+ }
+
// Clears previous query errors.
$this->engine->clear_query_error();
- $areascontexts = $this->get_areas_user_accesses();
+ $areascontexts = $this->get_areas_user_accesses($limitcourseids);
if (!$areascontexts) {
// User can not access any context.
$docs = array();
diff --git a/search/classes/output/form/search.php b/search/classes/output/form/search.php
index fed9dbf6e12..ffebc96d7a8 100644
--- a/search/classes/output/form/search.php
+++ b/search/classes/output/form/search.php
@@ -62,6 +62,14 @@ class search extends \moodleform {
}
$mform->addElement('select', 'areaid', get_string('searcharea', 'search'), $areanames);
+ $options = array(
+ 'multiple' => true,
+ 'limittoenrolled' => !is_siteadmin(),
+ 'noselectionstring' => get_string('allcourses', 'search'),
+ );
+ $mform->addElement('course', 'courseids', get_string('courses', 'core'), $options);
+ $mform->setType('courseids', PARAM_INT);
+
$mform->addElement('date_time_selector', 'timestart', get_string('fromtime', 'search'), array('optional' => true));
$mform->setDefault('timestart', 0);
diff --git a/search/engine/solr/classes/engine.php b/search/engine/solr/classes/engine.php
index 702fa17236e..80b68ad1509 100644
--- a/search/engine/solr/classes/engine.php
+++ b/search/engine/solr/classes/engine.php
@@ -135,6 +135,9 @@ class engine extends \core_search\engine {
// Even if it is only supposed to contain PARAM_ALPHANUMEXT, better to prevent.
$query->addFilterQuery('{!field cache=false f=areaid}' . $data->areaid);
}
+ if (!empty($data->courseids)) {
+ $query->addFilterQuery('{!cache=false}courseid:(' . implode(' OR ', $data->courseids) . ')');
+ }
if (!empty($data->timestart) or !empty($data->timeend)) {
if (empty($data->timestart)) {
@@ -159,19 +162,23 @@ class engine extends \core_search\engine {
// If the user can access all contexts $usercontexts value is just true, we don't need to filter
// in that case.
if ($usercontexts && is_array($usercontexts)) {
- if (!empty($data->areaid)) {
- $query->addFilterQuery('contextid:(' . implode(' OR ', $usercontexts[$data->areaid]) . ')');
- } else {
- // Join all area contexts into a single array and implode.
- $allcontexts = array();
- foreach ($usercontexts as $areacontexts) {
- foreach ($areacontexts as $contextid) {
- // Ensure they are unique.
- $allcontexts[$contextid] = $contextid;
- }
+ // Join all area contexts into a single array and implode.
+ $allcontexts = array();
+ foreach ($usercontexts as $areaid => $areacontexts) {
+ if (!empty($data->areaid) && ($areaid !== $data->areaid)) {
+ // Skip unused areas.
+ continue;
+ }
+ foreach ($areacontexts as $contextid) {
+ // Ensure they are unique.
+ $allcontexts[$contextid] = $contextid;
}
- $query->addFilterQuery('contextid:(' . implode(' OR ', $allcontexts) . ')');
}
+ if (empty($allcontexts)) {
+ // This means there are no valid contexts for them, so they get no results.
+ return array();
+ }
+ $query->addFilterQuery('contextid:(' . implode(' OR ', $allcontexts) . ')');
}
try {
diff --git a/search/engine/solr/tests/engine_test.php b/search/engine/solr/tests/engine_test.php
index 319cb0a2ce6..2ecd3fd0baf 100644
--- a/search/engine/solr/tests/engine_test.php
+++ b/search/engine/solr/tests/engine_test.php
@@ -191,6 +191,14 @@ class search_solr_engine_testcase extends advanced_testcase {
$querydata->title = 'moodle/course:renameroles roleid 1';
$this->assertCount(1, $this->search->search($querydata));
+ // Course IDs.
+ unset($querydata->title);
+ $querydata->courseids = array(SITEID + 1);
+ $this->assertCount(0, $this->search->search($querydata));
+
+ $querydata->courseids = array(SITEID);
+ $this->assertCount(3, $this->search->search($querydata));
+
// Check that index contents get updated.
$DB->delete_records('role_capabilities', array('capability' => 'moodle/course:renameroles'));
$this->search->index(true);
diff --git a/search/index.php b/search/index.php
index 233ddda9271..30ae2d7fa91 100644
--- a/search/index.php
+++ b/search/index.php
@@ -28,7 +28,7 @@ $page = optional_param('page', 0, PARAM_INT);
$q = optional_param('q', '', PARAM_NOTAGS);
$title = optional_param('title', '', PARAM_NOTAGS);
$areaid = optional_param('areaid', false, PARAM_ALPHANUMEXT);
-// Moving timestart and timeend further down as they might come as an array if they come from the form.
+// Moving courseids, timestart, and timeend further down as they might come as an array if they come from the form.
$context = context_system::instance();
$pagetitle = get_string('globalsearch', 'search');
@@ -67,6 +67,11 @@ if (!$data && $q) {
$data->q = $q;
$data->title = $title;
$data->areaid = $areaid;
+ $courseids = optional_param('courseids', '', PARAM_RAW);
+ if (!empty($courseids)) {
+ $courseids = explode(',', $courseids);
+ $data->courseids = clean_param_array($courseids, PARAM_INT);
+ }
$data->timestart = optional_param('timestart', 0, PARAM_INT);
$data->timeend = optional_param('timeend', 0, PARAM_INT);
$mform->set_data($data);
@@ -78,6 +83,9 @@ if ($data) {
$urlparams['q'] = $data->q;
$urlparams['title'] = $data->title;
$urlparams['areaid'] = $data->areaid;
+ if (!empty($data->courseids)) {
+ $urlparams['courseids'] = implode(',', $data->courseids);
+ }
$urlparams['timestart'] = $data->timestart;
$urlparams['timeend'] = $data->timeend;
}
diff --git a/search/tests/fixtures/testable_core_search.php b/search/tests/fixtures/testable_core_search.php
index 41e783d4a36..256f0b83e33 100644
--- a/search/tests/fixtures/testable_core_search.php
+++ b/search/tests/fixtures/testable_core_search.php
@@ -67,8 +67,8 @@ class testable_core_search extends \core_search\manager {
*
* @return array
*/
- public function get_areas_user_accesses() {
- return parent::get_areas_user_accesses();
+ public function get_areas_user_accesses($limitcourseids = false) {
+ return parent::get_areas_user_accesses($limitcourseids);
}
/**
diff --git a/search/tests/manager_test.php b/search/tests/manager_test.php
index 36fbde449c0..c6b0cf3b1f9 100644
--- a/search/tests/manager_test.php
+++ b/search/tests/manager_test.php
@@ -200,5 +200,25 @@ class search_manager_testcase extends advanced_testcase {
$contexts = $search->get_areas_user_accesses();
$this->assertEquals(array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id),
$contexts[$this->forumpostareaid]);
+
+ // Now test course limited searches.
+ set_coursemodule_visible($forum2->cmid, 1);
+ $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student');
+ $contexts = $search->get_areas_user_accesses();
+ $allcontexts = array($frontpageforumcontext->id => $frontpageforumcontext->id, $context1->id => $context1->id,
+ $context2->id => $context2->id, $context3->id => $context3->id);
+ $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
+
+ $contexts = $search->get_areas_user_accesses(array($course1->id, $course2->id));
+ $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id, $context3->id => $context3->id);
+ $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
+
+ $contexts = $search->get_areas_user_accesses(array($course2->id));
+ $allcontexts = array($context3->id => $context3->id);
+ $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
+
+ $contexts = $search->get_areas_user_accesses(array($course1->id));
+ $allcontexts = array($context1->id => $context1->id, $context2->id => $context2->id);
+ $this->assertEquals($allcontexts, $contexts[$this->forumpostareaid]);
}
}