diff --git a/blocks/globalsearch/block_globalsearch.php b/blocks/globalsearch/block_globalsearch.php index 7dd3057b63c..d3d96b20662 100644 --- a/blocks/globalsearch/block_globalsearch.php +++ b/blocks/globalsearch/block_globalsearch.php @@ -78,6 +78,12 @@ class block_globalsearch extends block_base { 'type' => 'text', 'size' => '15'); $this->content->text .= html_writer::empty_tag('input', $inputoptions); + // Context id. + if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) { + $this->content->text .= html_writer::empty_tag('input', ['type' => 'hidden', + 'name' => 'context', 'value' => $this->page->context->id]); + } + // Search button. $this->content->text .= html_writer::tag('button', get_string('search', 'search'), array('id' => 'searchform_button', 'type' => 'submit', 'title' => 'globalsearch', 'class' => 'btn btn-secondary')); diff --git a/lang/en/search.php b/lang/en/search.php index 096722d5a91..552d9b6064f 100644 --- a/lang/en/search.php +++ b/lang/en/search.php @@ -53,6 +53,7 @@ $string['engineserverstatus'] = 'The search engine is not available. Please cont $string['enteryoursearchquery'] = 'Enter your search query'; $string['errors'] = 'Errors'; $string['errorareanotavailable'] = '{$a} search area is not available.'; +$string['everywhere'] = 'Everywhere you can access'; $string['filesinindexdirectory'] = 'Files in index directory'; $string['filterheader'] = 'Filter'; $string['fromtime'] = 'Modified after'; @@ -89,6 +90,7 @@ $string['searcharea'] = 'Search area'; $string['searching'] = 'Searching in ...'; $string['searchnotpermitted'] = 'You are not allowed to do a search'; $string['searchsetupdescription'] = 'The following steps help you to set up Moodle global search.'; +$string['searchwithin'] = 'Search within'; $string['seconds'] = 'seconds'; $string['solutions'] = 'Solutions'; $string['statistics'] = 'Statistics'; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 83c74a3d749..4fbdedce39f 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -3259,6 +3259,10 @@ EOD; $contents = html_writer::tag('label', get_string('enteryoursearchquery', 'search'), array('for' => 'id_q_' . $id, 'class' => 'accesshide')) . html_writer::tag('input', '', $inputattrs); + if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) { + $contents .= html_writer::empty_tag('input', ['type' => 'hidden', + 'name' => 'context', 'value' => $this->page->context->id]); + } $searchinput = html_writer::tag('form', $contents, $formattrs); return html_writer::tag('div', $searchicon . $searchinput, array('class' => 'search-input-wrapper nav-link', 'id' => $id)); diff --git a/search/classes/output/form/search.php b/search/classes/output/form/search.php index 8364e98eaba..3652de78bef 100644 --- a/search/classes/output/form/search.php +++ b/search/classes/output/form/search.php @@ -48,6 +48,13 @@ class search extends \moodleform { $mform->setType('q', PARAM_TEXT); $mform->addRule('q', get_string('required'), 'required', null, 'client'); + // Show the 'search within' option if the user came from a particular context. + if (!empty($this->_customdata['searchwithin'])) { + $mform->addElement('select', 'searchwithin', get_string('searchwithin', 'search'), + $this->_customdata['searchwithin']); + $mform->setDefault('searchwithin', ''); + } + $mform->addElement('header', 'filtersection', get_string('filterheader', 'search')); $mform->setExpanded('filtersection', false); @@ -79,12 +86,21 @@ class search extends \moodleform { $mform->addElement('course', 'courseids', get_string('courses', 'core'), $options); $mform->setType('courseids', PARAM_INT); + // Course options should be hidden if we choose to search within a specific location. + if (!empty($this->_customdata['searchwithin'])) { + $mform->hideIf('courseids', 'searchwithin', 'ne', ''); + } + $mform->addElement('date_time_selector', 'timestart', get_string('fromtime', 'search'), array('optional' => true)); $mform->setDefault('timestart', 0); $mform->addElement('date_time_selector', 'timeend', get_string('totime', 'search'), array('optional' => true)); $mform->setDefault('timeend', 0); + // Source context i.e. the page they came from when they clicked search. + $mform->addElement('hidden', 'context'); + $mform->setType('context', PARAM_INT); + $this->add_action_buttons(false, get_string('search', 'search')); } } diff --git a/search/index.php b/search/index.php index 9c67e3dfde4..ece070ca488 100644 --- a/search/index.php +++ b/search/index.php @@ -27,6 +27,7 @@ require_once(__DIR__ . '/../config.php'); $page = optional_param('page', 0, PARAM_INT); $q = optional_param('q', '', PARAM_NOTAGS); $title = optional_param('title', '', PARAM_NOTAGS); +$contextid = optional_param('context', 0, PARAM_INT); // Moving areaids, courseids, timestart, and timeend further down as they might come as an array if they come from the form. $context = context_system::instance(); @@ -55,8 +56,23 @@ if (\core_search\manager::is_global_search_enabled() === false) { $search = \core_search\manager::instance(); -// We first get the submitted data as we want to set it all in the page URL. -$mform = new \core_search\output\form\search(null, array('searchengine' => $search->get_engine()->get_plugin_name())); +// Set up custom data for form. +$customdata = ['searchengine' => $search->get_engine()->get_plugin_name()]; +if ($contextid) { + // When a context is supplied, check if it's within course level. If so, show dropdown. + $context = context::instance_by_id($contextid); + $coursecontext = $context->get_course_context(false); + if ($coursecontext) { + $searchwithin = []; + $searchwithin[''] = get_string('everywhere', 'search'); + $searchwithin['course'] = $coursecontext->get_context_name(); + if ($context->contextlevel !== CONTEXT_COURSE) { + $searchwithin['context'] = $context->get_context_name(); + } + $customdata['searchwithin'] = $searchwithin; + } +} +$mform = new \core_search\output\form\search(null, $customdata); $data = $mform->get_data(); if (!$data && $q) { @@ -77,9 +93,25 @@ if (!$data && $q) { } $data->timestart = optional_param('timestart', 0, PARAM_INT); $data->timeend = optional_param('timeend', 0, PARAM_INT); + + $data->context = $contextid; + $mform->set_data($data); } +// Convert the 'search within' option, if used, to course or context restrictions. +if ($data && !empty($data->searchwithin)) { + switch ($data->searchwithin) { + case 'course': + $data->courseids = [$coursecontext->instanceid]; + break; + case 'context': + $data->courseids = [$coursecontext->instanceid]; + $data->contextids = [$context->id]; + break; + } +} + // Set the page URL. $urlparams = array('page' => $page); if ($data) { diff --git a/search/tests/behat/search_query.feature b/search/tests/behat/search_query.feature index 129bbb7204b..e3d9b3502d3 100644 --- a/search/tests/behat/search_query.feature +++ b/search/tests/behat/search_query.feature @@ -7,10 +7,13 @@ Feature: Use global search interface Background: Given the following config values are set as admin: | enableglobalsearch | 1 | + And the following "courses" exist: + | shortname | fullname | + | F1 | Amphibians | And the following "activities" exist: - | activity | name | intro | course | idnumber | - | page | PageName1 | PageDesc1 | Acceptance test site | PAGE1 | - | forum | ForumName1 | ForumDesc1 | Acceptance test site | FORUM1 | + | activity | name | intro | course | idnumber | + | page | PageName1 | PageDesc1 | F1 | PAGE1 | + | forum | ForumName1 | ForumDesc1 | F1 | FORUM1 | And I log in as "admin" @javascript @@ -47,3 +50,44 @@ Feature: Use global search interface # Check the link works. And I follow "ForumName1" And I should see "ForumName1" in the ".breadcrumb" "css_element" + + @javascript + Scenario: Search starting from site context (no within option) + Given global search expects the query "frogs" and will return: + | type | idnumber | + | activity | PAGE1 | + When I search for "frogs" using the header global search box + And I expand all fieldsets + Then I should not see "Search within" + And I should see "Courses" + + @javascript + Scenario: Search starting from course context (within option lists course) + Given global search expects the query "frogs" and will return: + | type | idnumber | + | activity | PAGE1 | + When I am on "Amphibians" course homepage + And I search for "frogs" using the header global search box + And I expand all fieldsets + Then I should see "Search within" + And I select "Everywhere you can access" from the "Search within" singleselect + And I should see "Courses" + And I select "Course: Amphibians" from the "Search within" singleselect + And I should not see "Courses" + + @javascript + Scenario: Search starting from forum context (within option lists course and forum) + Given global search expects the query "frogs" and will return: + | type | idnumber | + | activity | PAGE1 | + When I am on "Amphibians" course homepage + And I follow "ForumName1" + And I search for "frogs" using the header global search box + And I expand all fieldsets + And I should see "Search within" + And I select "Everywhere you can access" from the "Search within" singleselect + And I should see "Courses" + And I select "Course: Amphibians" from the "Search within" singleselect + And I should not see "Courses" + And I select "Forum: ForumName1" from the "Search within" singleselect + And I should not see "Courses"