Commit 031a2752 authored by Ryan Wyllie's avatar Ryan Wyllie
Browse files

MDL-61135 question: add tag filter condition

parent 73d16fc6
...@@ -157,6 +157,7 @@ $string['fileformat'] = 'File format'; ...@@ -157,6 +157,7 @@ $string['fileformat'] = 'File format';
$string['filesareacourse'] = 'the course files area'; $string['filesareacourse'] = 'the course files area';
$string['filesareasite'] = 'the site files area'; $string['filesareasite'] = 'the site files area';
$string['filestomove'] = 'Move / copy files to {$a}?'; $string['filestomove'] = 'Move / copy files to {$a}?';
$string['filterbytags'] = 'Filter by tags...';
$string['firsttry'] = 'First try'; $string['firsttry'] = 'First try';
$string['flagged'] = 'Flagged'; $string['flagged'] = 'Flagged';
$string['flagthisquestion'] = 'Flag this question'; $string['flagthisquestion'] = 'Flag this question';
...@@ -228,6 +229,7 @@ $string['nopermissionmove'] = 'You don\'t have permission to move questions from ...@@ -228,6 +229,7 @@ $string['nopermissionmove'] = 'You don\'t have permission to move questions from
$string['noprobs'] = 'No problems found in your question database.'; $string['noprobs'] = 'No problems found in your question database.';
$string['noquestions'] = 'No questions were found that could be exported. Make sure that you have selected a category to export that contains questions.'; $string['noquestions'] = 'No questions were found that could be exported. Make sure that you have selected a category to export that contains questions.';
$string['noquestionsinfile'] = 'There are no questions in the import file'; $string['noquestionsinfile'] = 'There are no questions in the import file';
$string['notagfiltersapplied'] = 'No tag filters applied';
$string['notenoughanswers'] = 'This type of question requires at least {$a} answers'; $string['notenoughanswers'] = 'This type of question requires at least {$a} answers';
$string['notenoughdatatoeditaquestion'] = 'Neither a question id, nor a category id and question type, was specified.'; $string['notenoughdatatoeditaquestion'] = 'Neither a question id, nor a category id and question type, was specified.';
$string['notenoughdatatomovequestions'] = 'You need to provide the question ids of questions you want to move.'; $string['notenoughdatatomovequestions'] = 'You need to provide the question ids of questions you want to move.';
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A condition for adding filtering by tag to the question bank.
*
* @package core_question
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_question\bank\search;
defined('MOODLE_INTERNAL') || die();
/**
* Question bank search class to allow searching/filtering by tags on a question.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_condition extends condition {
/** @var string SQL fragment to add to the where clause. */
protected $where;
/** @var string SQL fragment to add to the where clause. */
protected $contexts;
/** @var array List of IDs for tags that have been selected in the form. */
protected $selectedtagids;
/**
* Constructor.
* @param context[] $contexts List of contexts to show tags from
* @param int[] $selectedtagids List of IDs for tags to filter by.
*/
public function __construct(array $contexts, array $selectedtagids = []) {
global $DB;
$this->contexts = $contexts;
// If some tags have been selected then we need to filter
// the question list by the selected tags.
if ($selectedtagids) {
// We treat each additional tag as an AND condition rather than
// an OR condition.
//
// For example, if the user filters by the tags "foo" and "bar" then
// we reduce the question list to questions that are tagged with both
// "foo" AND "bar". Any question that does not have ALL of the specified
// tags will be omitted.
list($tagsql, $tagparams) = $DB->get_in_or_equal($selectedtagids, SQL_PARAMS_NAMED);
$tagparams['tagcount'] = count($selectedtagids);
$this->selectedtagids = $selectedtagids;
$this->params = $tagparams;
$this->where = "q.id IN (SELECT ti.itemid
FROM {tag_instance} ti
WHERE ti.tagid {$tagsql}
GROUP BY ti.itemid
HAVING COUNT(itemid) = :tagcount)";
} else {
$this->selectedtagids = [];
$this->params = [];
$this->where = '';
}
}
/**
* Get the SQL WHERE snippet to be used in the SQL to retrieve the
* list of questions. This SQL snippet will add the logic for the
* tag condition.
*
* @return string
*/
public function where() {
return $this->where;
}
/**
* Named SQL params to be used with the SQL WHERE snippet.
*
* @return array
*/
public function params() {
return $this->params;
}
/**
* Print HTML to display the list of tags to filter by.
*/
public function display_options() {
global $OUTPUT;
$tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $this->contexts);
$tagoptions = array_map(function($tag) {
return [
'id' => $tag->id,
'name' => $tag->name,
'selected' => in_array($tag->id, $this->selectedtagids)
];
}, array_values($tags));
$context = [
'tagoptions' => $tagoptions
];
echo $OUTPUT->render_from_template('core_question/tag_condition', $context);
}
}
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_question/tag_condition
An auto-complete select box containing a list of available tags to
filter the quesiton bank questions by.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* tagoptions A list of available tags
Example context (json):
{
"tagoptions": [
{
"id": 1,
"name": "foo",
"selected": true
},
{
"id": 2,
"name": "bar",
"selected": false
}
]
}
}}
<div class="tag-condition-container" data-region="tag-condition-container-{{uniqid}}">
<div class="form-group">
<select multiple name="qtagids[]" class="form-control invisible" size="3" data-region="tag-select">
{{#tagoptions}}
<option {{#selected}}selected{{/selected}} value="{{id}}">{{name}}</option>
{{/tagoptions}}
</select>
{{< core/overlay_loading }}
{{$hiddenclass}}{{/hiddenclass}}
{{/ core/overlay_loading }}
</div>
</div>
{{#js}}
require(
[
'jquery',
'core/form-autocomplete'
],
function(
$,
AutoComplete
) {
var root = $('[data-region="tag-condition-container-{{uniqid}}"]');
var selectElement = root.find('[data-region="tag-select"]');
var loadingContainer = root.find('[data-region="overlay-icon-container"]');
var placeholderText = '{{#str}} filterbytags, core_question {{/str}}';
var noSelectionText = '{{#str}} notagfiltersapplied, core_question {{/str}}';
AutoComplete.enhance(
selectElement, // Element to enhance.
false, // Don't allow support for creating new tags.
false, // Don't allow AMD module to handle loading new tags.
placeholderText, // Placeholder text.
false, // Make search case insensitive.
true, // Show suggestions for tags.
noSelectionText // Text when no tags are selected.
).always(function() {
// Hide the loading icon once the autocomplete has initialised.
loadingContainer.addClass('hidden');
});
// We need to trigger a form submission because of how the question bank
// page handles reloading the questions when an option changes.
selectElement.on('change', function() {
selectElement.closest('form').submit();
});
});
{{/js}}
...@@ -622,6 +622,10 @@ body.path-question-type .mform fieldset.hidden { ...@@ -622,6 +622,10 @@ body.path-question-type .mform fieldset.hidden {
box-sizing: content-box; box-sizing: content-box;
} }
.tag-condition-container {
position: relative;
}
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
.que .info { .que .info {
float: none; float: none;
......
...@@ -541,3 +541,7 @@ body.path-question-type .mform fieldset.hidden { ...@@ -541,3 +541,7 @@ body.path-question-type .mform fieldset.hidden {
padding: 0; padding: 0;
margin: 0.7em 0 0; margin: 0.7em 0 0;
} }
.tag-condition-container {
position: relative;
}
...@@ -9685,6 +9685,9 @@ body.path-question-type .mform fieldset.hidden { ...@@ -9685,6 +9685,9 @@ body.path-question-type .mform fieldset.hidden {
padding: 0; padding: 0;
margin: 0.7em 0 0; margin: 0.7em 0 0;
} }
.tag-condition-container {
position: relative;
}
/* user.less */ /* user.less */
.userprofile .fullprofilelink { .userprofile .fullprofilelink {
text-align: center; text-align: center;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment