diff --git a/mod/quiz/addrandom.php b/mod/quiz/addrandom.php index 144fe2e4909..155945626f5 100644 --- a/mod/quiz/addrandom.php +++ b/mod/quiz/addrandom.php @@ -102,7 +102,15 @@ if ($data = $mform->get_data()) { 'It seems a form was submitted without any button being pressed???'); } - quiz_add_random_questions($quiz, $addonpage, $categoryid, $data->numbertoadd, $includesubcategories); + if (empty($data->fromtags)) { + $data->fromtags = []; + } + + $tagids = array_map(function($tagstrings) { + return (int)explode(',', $tagstrings)[0]; + }, $data->fromtags); + + quiz_add_random_questions($quiz, $addonpage, $categoryid, $data->numbertoadd, $includesubcategories, $tagids); quiz_delete_previews($quiz); quiz_update_sumgrades($quiz); redirect($returnurl); diff --git a/mod/quiz/addrandomform.php b/mod/quiz/addrandomform.php index 7405ddae666..60ebf7bcf6a 100644 --- a/mod/quiz/addrandomform.php +++ b/mod/quiz/addrandomform.php @@ -56,6 +56,18 @@ class quiz_add_random_form extends moodleform { $tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id')); $mform->hideIf('includesubcategories', 'category', 'in', $tops); + $tags = core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts); + $tagstrings = array(); + foreach ($tags as $tag) { + $tagstrings["{$tag->id},{$tag->name}"] = $tag->name; + } + $options = array( + 'multiple' => true, + 'noselectionstring' => get_string('anytags', 'quiz'), + ); + $mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options); + $mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz'); + $mform->addElement('select', 'numbertoadd', get_string('randomnumber', 'quiz'), $this->get_number_of_questions_to_add_choices()); diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index 452ee008479..aa1194e69bd 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -80,6 +80,7 @@ $string['answers'] = 'Answers'; $string['answersingleno'] = 'Multiple answers allowed'; $string['answersingleyes'] = 'One answer only'; $string['answertoolong'] = 'Answer too long after line {$a} (255 char. max)'; +$string['anytags'] = 'Any tags'; $string['aon'] = 'AON format'; $string['areyousureremoveselected'] = 'Are you sure you want to remove all the selected questions?'; $string['asshownoneditscreen'] = 'As shown on the edit screen'; @@ -699,6 +700,10 @@ $string['randomfromcategory'] = 'Random question from category:'; $string['randomfromexistingcategory'] = 'Random question from an existing category'; $string['randomnumber'] = 'Number of random questions'; $string['randomnosubcat'] = 'Questions from this category only, not its subcategories.'; +$string['randomquestiontags'] = 'Tags'; +$string['randomquestiontags_help'] = 'You can restrict the selection criteria further by specifying some question tags here. + +The "random" questions will be selected from the questions that have all these tags.'; $string['randomquestionusinganewcategory'] = 'Random question using a new category'; $string['randomwithsubcat'] = 'Questions from this category and its subcategories.'; $string['readytosend'] = 'You are about to send your whole quiz to be graded. Are you sure you want to continue?'; diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 0561694388b..f0ee68d540b 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -2173,9 +2173,10 @@ function quiz_update_section_firstslots($quizid, $direction, $afterslot, $before * @param int $categoryid the question category to add the question from. * @param int $number the number of random questions to add. * @param bool $includesubcategories whether to include questoins from subcategories. + * @param int[] $tagids Array of tagids. The question that will be picked randomly should be tagged with all these tags. */ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, - $includesubcategories) { + $includesubcategories, $tagids = []) { global $DB; $category = $DB->get_record('question_categories', array('id' => $categoryid)); @@ -2187,6 +2188,18 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, require_capability('moodle/question:useall', $catcontext); $tags = []; + $tagstrings = []; + foreach ($tagids as $tagid) { + if ($tag = core_tag_tag::get($tagid, 'id,name')) { + $tags[] = [ + 'id' => $tagid, + 'name' => $tag->name + ]; + $tagstrings[] = "{$tagid},{$tag->name}"; + } else if (!empty($tagid)) { + print_error('invalidtagid', 'mod_quiz'); + } + } // Find existing random questions in this category that are // not used by any quiz. @@ -2199,14 +2212,15 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, SELECT * FROM {quiz_slots} WHERE questionid = q.id) - ORDER BY id", array($category->id, ($includesubcategories ? '1' : '0'))); + ORDER BY id", array($category->id, $includesubcategories ? '1' : '0')); for ($i = 0; $i < $number; $i++) { // Take as many of orphaned "random" questions as needed. if (!$question = array_shift($existingquestions)) { $form = new stdClass(); - $form->questiontext = array('text' => ($includesubcategories ? '1' : '0'), 'format' => 0); $form->category = $category->id . ',' . $category->contextid; + $form->includesubcategories = $includesubcategories; + $form->fromtags = $tagstrings; $form->defaultmark = 1; $form->hidden = 1; $form->stamp = make_unique_id_code(); // Set the unique code (not to be changed). diff --git a/question/type/random/lang/en/qtype_random.php b/question/type/random/lang/en/qtype_random.php index 4c16d912cc3..c2d95829797 100644 --- a/question/type/random/lang/en/qtype_random.php +++ b/question/type/random/lang/en/qtype_random.php @@ -30,10 +30,17 @@ $string['pluginname_help'] = 'A random question is not a question type as such, $string['pluginnameediting'] = 'Editing a random question'; $string['randomqname'] = 'Random ({$a})'; $string['randomqnamefromtop'] = 'Faulty random question! Please delete this question.'; +$string['randomqnamefromtoptags'] = 'Faulty random question! Please delete this question.'; +$string['randomqnametags'] = 'Random ({$a->category}, tags: {$a->tags})'; $string['randomqplusname'] = 'Random ({$a} and subcategories)'; $string['randomqplusnamecourse'] = 'Random (Any category in this course)'; $string['randomqplusnamecoursecat'] = 'Random (Any category inside course category {$a})'; +$string['randomqplusnamecoursecattags'] = 'Random (Any category inside course category {$a->category}, tags: {$a->tags})'; +$string['randomqplusnamecoursetags'] = 'Random (Any category in this course, tags: {$a->tags})'; $string['randomqplusnamemodule'] = 'Random (Any category of this quiz)'; +$string['randomqplusnamemoduletags'] = 'Random (Any category of this quiz, tags: {$a->tags})'; $string['randomqplusnamesystem'] = 'Random (Any system-level category)'; +$string['randomqplusnamesystemtags'] = 'Random (Any system-level category, tags: {$a->tags})'; +$string['randomqplusnametags'] = 'Random ({$a->category} and subcategories, tags: {$a->tags})'; $string['selectedby'] = '{$a->questionname} selected by {$a->randomname}'; $string['selectmanualquestions'] = 'Random questions can use manually graded questions'; diff --git a/question/type/random/questiontype.php b/question/type/random/questiontype.php index aecdaf6f6da..e318641c12e 100644 --- a/question/type/random/questiontype.php +++ b/question/type/random/questiontype.php @@ -122,41 +122,59 @@ class qtype_random extends question_type { /** * Random questions always get a question name that is Random (cateogryname). * This function is a centralised place to calculate that, given the category. - * @param object $category the category this question picks from. (Only ->name is used.) + * @param stdClass $category the category this question picks from. (Only ->name is used.) * @param bool $includesubcategories whether this question also picks from subcategories. + * @param string[] $tagnames Name of tags this question picks from. * @return string the name this question should have. */ - public function question_name($category, $includesubcategories) { + public function question_name($category, $includesubcategories, $tagnames = []) { + $categoryname = ''; if ($category->parent && $includesubcategories) { - $name = get_string('randomqplusname', 'qtype_random', shorten_text($category->name, 100)); + $stringid = 'randomqplusname'; + $categoryname = shorten_text($category->name, 100); } else if ($category->parent) { - $name = get_string('randomqname', 'qtype_random', shorten_text($category->name, 100)); + $stringid = 'randomqname'; + $categoryname = shorten_text($category->name, 100); } else if ($includesubcategories) { $context = context::instance_by_id($category->contextid); switch ($context->contextlevel) { case CONTEXT_MODULE: - $name = get_string('randomqplusnamemodule', 'qtype_random'); + $stringid = 'randomqplusnamemodule'; break; case CONTEXT_COURSE: - $name = get_string('randomqplusnamecourse', 'qtype_random'); + $stringid = 'randomqplusnamecourse'; break; case CONTEXT_COURSECAT: - $name = get_string('randomqplusnamecoursecat', 'qtype_random', - shorten_text($context->get_context_name(false), 100)); + $stringid = 'randomqplusnamecoursecat'; + $categoryname = shorten_text($context->get_context_name(false), 100); break; case CONTEXT_SYSTEM: - $name = get_string('randomqplusnamesystem', 'qtype_random'); + $stringid = 'randomqplusnamesystem'; break; default: // Impossible. - $name = ''; } } else { // No question will ever be selected. So, let's warn the teacher. - $name = get_string('randomqnamefromtop', 'qtype_random'); + $stringid = 'randomqnamefromtop'; } - return $name; + if ($tagnames) { + $stringid .= 'tags'; + $a = new stdClass(); + if ($categoryname) { + $a->category = $categoryname; + } + $a->tags = implode(',', array_map(function($tagname) { + return explode(',', $tagname)[1]; + }, $tagnames)); + } else { + $a = $categoryname ? : null; + } + + $name = get_string($stringid, 'qtype_random', $a); + + return shorten_text($name, 255); } protected function set_selected_question_name($question, $randomname) { @@ -172,17 +190,24 @@ class qtype_random extends question_type { $form->name = ''; list($category) = explode(',', $form->category); - // In case someone set the question text to true/false in the old style, set it properly. - if ($form->questiontext['text']) { - $form->questiontext['text'] = '1'; - } else if ($DB->record_exists('question_categories', ['id' => $category, 'parent' => 0])) { - // The chosen category is a top category. - $form->questiontext['text'] = '1'; - } else { - $form->questiontext['text'] = '0'; + if (!$form->includesubcategories) { + if ($DB->record_exists('question_categories', ['id' => $category, 'parent' => 0])) { + // The chosen category is a top category. + $form->includesubcategories = true; + } } + $form->tags = array(); + if (empty($form->fromtags)) { + $form->fromtags = array(); + } + + $form->questiontext = array( + 'text' => $form->includesubcategories ? '1' : '0', + 'format' => 0 + ); + // Name is not a required field for random questions, but // parent::save_question Assumes that it is. return parent::save_question($question, $form); @@ -201,7 +226,7 @@ class qtype_random extends question_type { // We also force the question name to be 'Random (categoryname)'. $category = $DB->get_record('question_categories', array('id' => $question->category), '*', MUST_EXIST); - $updateobject->name = $this->question_name($category, !empty($question->questiontext)); + $updateobject->name = $this->question_name($category, $question->includesubcategories, $question->fromtags); return $DB->update_record('question', $updateobject); }