Merge branch 'MDL-61380-master' of git://github.com/rezaies/moodle
This commit is contained in:
@@ -110,6 +110,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
|
||||
'xpath_element' => 'xpath_element',
|
||||
'form_row' => 'form_row',
|
||||
'autocomplete_selection' => 'autocomplete_selection',
|
||||
'autocomplete_suggestions' => 'autocomplete_suggestions',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -194,6 +195,9 @@ XPATH
|
||||
XPATH
|
||||
, 'autocomplete_selection' => <<<XPATH
|
||||
.//div[contains(concat(' ', normalize-space(@class), ' '), concat(' ', 'form-autocomplete-selection', ' '))]/span[@role='listitem'][contains(normalize-space(.), %locator%)]
|
||||
XPATH
|
||||
, 'autocomplete_suggestions' => <<<XPATH
|
||||
.//ul[contains(concat(' ', normalize-space(@class), ' '), concat(' ', 'form-autocomplete-suggestions', ' '))]/li[@role='option'][contains(normalize-space(.), %locator%)]
|
||||
XPATH
|
||||
);
|
||||
|
||||
|
||||
@@ -398,6 +398,8 @@ class core_questionlib_testcase extends advanced_testcase {
|
||||
public function test_question_remove_stale_questions_from_category() {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$course = $dg->create_course();
|
||||
$quiz = $dg->create_module('quiz', ['course' => $course->id]);
|
||||
@@ -407,18 +409,28 @@ class core_questionlib_testcase extends advanced_testcase {
|
||||
|
||||
$qcat1 = $qgen->create_question_category(['contextid' => $context->id]);
|
||||
$q1a = $qgen->create_question('shortanswer', null, ['category' => $qcat1->id]); // Will be hidden.
|
||||
$q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q1a->id]);
|
||||
|
||||
$qcat2 = $qgen->create_question_category(['contextid' => $context->id]);
|
||||
$q2a = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden.
|
||||
$q2b = $qgen->create_question('shortanswer', null, ['category' => $qcat2->id]); // Will be hidden but used.
|
||||
$q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
|
||||
$q2d = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will be used.
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2a->id]);
|
||||
$DB->set_field('question', 'hidden', 1, ['id' => $q2b->id]);
|
||||
quiz_add_quiz_question($q2b->id, $quiz);
|
||||
quiz_add_quiz_question($q2d->id, $quiz);
|
||||
quiz_add_random_questions($quiz, 0, $qcat2->id, 1, false);
|
||||
|
||||
// We added one random question to the quiz and we expect the quiz to have only one random question.
|
||||
$q2d = $DB->get_record_sql("SELECT q.*
|
||||
FROM {question} q
|
||||
JOIN {quiz_slots} s ON s.questionid = q.id
|
||||
WHERE q.qtype = :qtype
|
||||
AND s.quizid = :quizid",
|
||||
array('qtype' => 'random', 'quizid' => $quiz->id), MUST_EXIST);
|
||||
|
||||
// The following 2 lines have to be after the quiz_add_random_questions() call above.
|
||||
// Otherwise, quiz_add_random_questions() will to be "smart" and use them instead of creating a new "random" question.
|
||||
$q1b = $qgen->create_question('random', null, ['category' => $qcat1->id]); // Will not be used.
|
||||
$q2c = $qgen->create_question('random', null, ['category' => $qcat2->id]); // Will not be used.
|
||||
|
||||
$this->assertEquals(2, $DB->count_records('question', ['category' => $qcat1->id]));
|
||||
$this->assertEquals(4, $DB->count_records('question', ['category' => $qcat2->id]));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ class quiz {
|
||||
*/
|
||||
public function preload_questions() {
|
||||
$this->questions = question_preload_questions(null,
|
||||
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page',
|
||||
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page,
|
||||
slot.questioncategoryid AS randomfromcategory, slot.tags AS randomfromtags,
|
||||
slot.includingsubcategories AS randomincludingsubcategories',
|
||||
'{quiz_slots} slot ON slot.quizid = :quizid AND q.id = slot.questionid',
|
||||
array('quizid' => $this->quiz->id), 'slot.slot');
|
||||
}
|
||||
@@ -566,7 +568,7 @@ class quiz_attempt {
|
||||
$this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
|
||||
$this->slots = $DB->get_records('quiz_slots',
|
||||
array('quizid' => $this->get_quizid()), 'slot',
|
||||
'slot, requireprevious, questionid');
|
||||
'slot, requireprevious, questionid, includingsubcategories, tags');
|
||||
$this->sections = array_values($DB->get_records('quiz_sections',
|
||||
array('quizid' => $this->get_quizid()), 'firstslot'));
|
||||
|
||||
@@ -1871,12 +1873,14 @@ class quiz_attempt {
|
||||
if ($questiondata->qtype != 'random') {
|
||||
$newqusetionid = $questiondata->id;
|
||||
} else {
|
||||
$tagids = quiz_extract_random_question_tag_ids($this->slots[$slot]->tags);
|
||||
|
||||
$randomloader = new \core_question\bank\random_question_loader($qubaids, array());
|
||||
$newqusetionid = $randomloader->get_next_question_id($questiondata->category,
|
||||
(bool) $questiondata->questiontext);
|
||||
(bool) $questiondata->questiontext, $tagids);
|
||||
if ($newqusetionid === null) {
|
||||
throw new moodle_exception('notenoughrandomquestions', 'quiz',
|
||||
$quizobj->view_url(), $questiondata);
|
||||
$this->quizobj->view_url(), $questiondata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Defines the editing form for random questions.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2018 Shamim Rezaie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_quiz\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/lib/formslib.php');
|
||||
|
||||
/**
|
||||
* Class randomquestion_form
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2018 Shamim Rezaie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class randomquestion_form extends \moodleform {
|
||||
|
||||
/**
|
||||
* Form definiton.
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$contexts = $this->_customdata['contexts'];
|
||||
$usablecontexts = $contexts->having_cap('moodle/question:useall');
|
||||
|
||||
// Standard fields at the start of the form.
|
||||
$mform->addElement('header', 'generalheader', get_string("general", 'form'));
|
||||
|
||||
$mform->addElement('questioncategory', 'category', get_string('category', 'question'),
|
||||
array('contexts' => $usablecontexts, 'top' => true));
|
||||
|
||||
$mform->addElement('advcheckbox', 'includesubcategories', get_string('recurse', 'quiz'), null, null, array(0, 1));
|
||||
|
||||
$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('hidden', 'slotid');
|
||||
$mform->setType('slotid', PARAM_INT);
|
||||
|
||||
$mform->addElement('hidden', 'returnurl');
|
||||
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||
|
||||
$buttonarray = array();
|
||||
$buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('savechanges'));
|
||||
$buttonarray[] = $mform->createElement('cancel');
|
||||
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
|
||||
$mform->closeHeaderBefore('buttonar');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Defines the \mod_quiz\local\structure\slot_random class.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2018 Shamim Rezaie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_quiz\local\structure;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class slot_random, represents a random question slot type.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2018 Shamim Rezaie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class slot_random {
|
||||
|
||||
/** @var \stdClass Slot's properties. A record retrieved from the quiz_slots table. */
|
||||
protected $record;
|
||||
|
||||
/**
|
||||
* @var \stdClass The quiz this question slot belongs to.
|
||||
*/
|
||||
protected $quiz = null;
|
||||
|
||||
/**
|
||||
* slot_random constructor.
|
||||
*
|
||||
* @param \stdClass $slotrecord Represents a record in the quiz_slots table.
|
||||
*/
|
||||
public function __construct($slotrecord = null) {
|
||||
$this->record = new \stdClass();
|
||||
|
||||
$properties = array(
|
||||
'id', 'slot', 'quizid', 'page', 'requireprevious',
|
||||
'questionid', 'questioncategoryid', 'includingsubcategories',
|
||||
'tags', 'maxmark');
|
||||
|
||||
foreach ($properties as $property) {
|
||||
if (isset($slotrecord->$property)) {
|
||||
$this->record->$property = $slotrecord->$property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quiz for this question slot.
|
||||
* The quiz is fetched the first time it is requested and then stored in a member variable to be returned each subsequent time.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_quiz() {
|
||||
global $DB;
|
||||
|
||||
if (empty($this->quiz)) {
|
||||
if (empty($this->record->quizid)) {
|
||||
throw new \coding_exception('quizid is not set.');
|
||||
}
|
||||
$this->quiz = $DB->get_record('quiz', array('id' => $this->record->quizid));
|
||||
}
|
||||
|
||||
return $this->quiz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quiz object for the quiz slot.
|
||||
* It is not mandatory to set the quiz as the quiz slot can fetch it the first time it is accessed,
|
||||
* however it helps with the performance to set the quiz if you already have it.
|
||||
*
|
||||
* @param \stdClass $quiz The qui object.
|
||||
*/
|
||||
public function set_quiz($quiz) {
|
||||
$this->quiz = $quiz;
|
||||
$this->record->quizid = $quiz->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the quiz slot at the $page page.
|
||||
* It is required to call this function if you are building a quiz slot object from scratch.
|
||||
*
|
||||
* @param int $page The page that this slot will be inserted at.
|
||||
*/
|
||||
public function insert($page) {
|
||||
global $DB;
|
||||
|
||||
$slots = $DB->get_records('quiz_slots', array('quizid' => $this->record->quizid),
|
||||
'slot', 'id, slot, page');
|
||||
|
||||
$trans = $DB->start_delegated_transaction();
|
||||
|
||||
$maxpage = 1;
|
||||
$numonlastpage = 0;
|
||||
foreach ($slots as $slot) {
|
||||
if ($slot->page > $maxpage) {
|
||||
$maxpage = $slot->page;
|
||||
$numonlastpage = 1;
|
||||
} else {
|
||||
$numonlastpage += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_int($page) && $page >= 1) {
|
||||
// Adding on a given page.
|
||||
$lastslotbefore = 0;
|
||||
foreach (array_reverse($slots) as $otherslot) {
|
||||
if ($otherslot->page > $page) {
|
||||
$DB->set_field('quiz_slots', 'slot', $otherslot->slot + 1, array('id' => $otherslot->id));
|
||||
} else {
|
||||
$lastslotbefore = $otherslot->slot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->record->slot = $lastslotbefore + 1;
|
||||
$this->record->page = min($page, $maxpage + 1);
|
||||
|
||||
quiz_update_section_firstslots($this->record->quizid, 1, max($lastslotbefore, 1));
|
||||
} else {
|
||||
$lastslot = end($slots);
|
||||
$quiz = $this->get_quiz();
|
||||
if ($lastslot) {
|
||||
$this->record->slot = $lastslot->slot + 1;
|
||||
} else {
|
||||
$this->record->slot = 1;
|
||||
}
|
||||
if ($quiz->questionsperpage && $numonlastpage >= $quiz->questionsperpage) {
|
||||
$this->record->page = $maxpage + 1;
|
||||
} else {
|
||||
$this->record->page = $maxpage;
|
||||
}
|
||||
}
|
||||
|
||||
$this->record->id = $DB->insert_record('quiz_slots', $this->record);
|
||||
$trans->allow_commit();
|
||||
}
|
||||
}
|
||||
@@ -958,16 +958,16 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
* and also to see that category in the question bank.
|
||||
*
|
||||
* @param structure $structure object containing the structure of the quiz.
|
||||
* @param int $slot which slot we are outputting.
|
||||
* @param int $slotnumber which slot we are outputting.
|
||||
* @param \moodle_url $pageurl the canonical URL of this page.
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function random_question(structure $structure, $slot, $pageurl) {
|
||||
public function random_question(structure $structure, $slotnumber, $pageurl) {
|
||||
|
||||
$question = $structure->get_question_in_slot($slot);
|
||||
$editurl = new \moodle_url('/question/question.php', array(
|
||||
'returnurl' => $pageurl->out_as_local_url(),
|
||||
'cmid' => $structure->get_cmid(), 'id' => $question->id));
|
||||
$question = $structure->get_question_in_slot($slotnumber);
|
||||
$slot = $structure->get_slot_by_number($slotnumber);
|
||||
$editurl = new \moodle_url('/mod/quiz/editrandom.php',
|
||||
array('returnurl' => $pageurl->out_as_local_url(), 'slotid' => $slot->id));
|
||||
|
||||
$temp = clone($question);
|
||||
$temp->questiontext = '';
|
||||
|
||||
@@ -409,6 +409,23 @@ class structure {
|
||||
return $this->slots[$slotid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a slot by it's slot number. Throws an exception if it is missing.
|
||||
*
|
||||
* @param int $slotnumber The slot number
|
||||
* @return \stdClass
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function get_slot_by_number($slotnumber) {
|
||||
foreach ($this->slots as $slot) {
|
||||
if ($slot->slot == $slotnumber) {
|
||||
return $slot;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \coding_exception('The \'slotnumber\' could not be found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether adding a section heading is possible
|
||||
* @param int $pagenumber the number of the page.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="mod/quiz/db" VERSION="20150323" COMMENT="XMLDB file for Moodle mod/quiz"
|
||||
<XMLDB PATH="mod/quiz/db" VERSION="20180207" COMMENT="XMLDB file for Moodle mod/quiz"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -63,12 +63,16 @@
|
||||
<FIELD NAME="page" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The page number that this questions appears on. If the question in slot n appears on page p, then the question in slot n+1 must appear on page p or p+1. Well, except that when a quiz is being created, there may be empty pages, which would cause the page number to jump here."/>
|
||||
<FIELD NAME="requireprevious" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Set to 1 when current question requires previous one to be answered first."/>
|
||||
<FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key references question.id."/>
|
||||
<FIELD NAME="questioncategoryid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The question category that the random question will be picked from. Will be null if and only if the question is not a random question."/>
|
||||
<FIELD NAME="includingsubcategories" TYPE="int" LENGTH="4" NOTNULL="false" SEQUENCE="false" COMMENT="Whether the random question can be picked from sub categories or not. Will be null if questioncategoryid is null."/>
|
||||
<FIELD NAME="tags" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Contains data about the tags that a question must have so that it can be selected for this slot. This field is an array in the form of [tagid,tagname] which is stored as JSON. Will be null if questioncategoryid is null."/>
|
||||
<FIELD NAME="maxmark" TYPE="number" LENGTH="12" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="7" COMMENT="How many marks this question contributes to quiz.sumgrades."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="quizid" TYPE="foreign" FIELDS="quizid" REFTABLE="quiz" REFFIELDS="id"/>
|
||||
<KEY NAME="questionid" TYPE="foreign" FIELDS="questionid" REFTABLE="question" REFFIELDS="id"/>
|
||||
<KEY NAME="questioncategoryid" TYPE="foreign" FIELDS="questioncategoryid" REFTABLE="questioncategory" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="quizid-slot" UNIQUE="true" FIELDS="quizid, slot"/>
|
||||
@@ -183,4 +187,4 @@
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
@@ -94,5 +94,74 @@ function xmldb_quiz_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.4.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2018020700) {
|
||||
|
||||
$table = new xmldb_table('quiz_slots');
|
||||
|
||||
// Define field questioncategoryid to be added to quiz_slots.
|
||||
$field = new xmldb_field('questioncategoryid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'questionid');
|
||||
// Conditionally launch add field questioncategoryid.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define key questioncategoryid (foreign) to be added to quiz_slots.
|
||||
$key = new xmldb_key('questioncategoryid', XMLDB_KEY_FOREIGN, array('questioncategoryid'), 'questioncategory', array('id'));
|
||||
// Launch add key questioncategoryid.
|
||||
$dbman->add_key($table, $key);
|
||||
|
||||
// Define field includingsubcategories to be added to quiz_slots.
|
||||
$field = new xmldb_field('includingsubcategories', XMLDB_TYPE_INTEGER, '4', null, null, null, null, 'questioncategoryid');
|
||||
// Conditionally launch add field includingsubcategories.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field tags to be added to quiz_slots.
|
||||
$field = new xmldb_field('tags', XMLDB_TYPE_TEXT, null, null, null, null, null, 'includingsubcategories');
|
||||
// Conditionally launch add field tags.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Quiz savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2018020700, 'quiz');
|
||||
}
|
||||
|
||||
if ($oldversion < 2018020701) {
|
||||
// This SQL fetches all "random" questions from the question bank.
|
||||
$fromclause = "FROM {quiz_slots} qs
|
||||
JOIN {question} q ON q.id = qs.questionid
|
||||
WHERE q.qtype = ?";
|
||||
|
||||
// Get the total record count - used for the progress bar.
|
||||
$total = $DB->count_records_sql("SELECT count(qs.id) $fromclause", array('random'));
|
||||
|
||||
// Get the records themselves.
|
||||
$rs = $DB->get_recordset_sql("SELECT qs.id, q.category, q.questiontext $fromclause", array('random'));
|
||||
|
||||
$a = new stdClass();
|
||||
$a->total = $total;
|
||||
$a->done = 0;
|
||||
|
||||
// For each question, move the configuration data to the quiz_slots table.
|
||||
$pbar = new progress_bar('updatequizslotswithrandom', 500, true);
|
||||
foreach ($rs as $record) {
|
||||
$data = new stdClass();
|
||||
$data->id = $record->id;
|
||||
$data->questioncategoryid = $record->category;
|
||||
$data->includingsubcategories = empty($record->questiontext) ? 0 : 1;
|
||||
$DB->update_record('quiz_slots', $data);
|
||||
|
||||
// Update progress.
|
||||
$a->done++;
|
||||
$pbar->update($a->done, $a->total, get_string('updatequizslotswithrandomxofy', 'quiz', $a));
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
// Quiz savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2018020701, 'quiz');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Page for editing random questions.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2018 Shamim Rezaie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
|
||||
$slotid = required_param('slotid', PARAM_INT);
|
||||
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
|
||||
|
||||
// Get the quiz slot.
|
||||
$slot = $DB->get_record('quiz_slots', array('id' => $slotid));
|
||||
if (!$slot || empty($slot->questioncategoryid)) {
|
||||
print_error('invalidrandomslot', 'mod_quiz');
|
||||
}
|
||||
|
||||
if (!$quiz = $DB->get_record('quiz', array('id' => $slot->quizid))) {
|
||||
print_error('invalidquizid', 'quiz');
|
||||
}
|
||||
|
||||
$cm = get_coursemodule_from_instance('quiz', $slot->quizid, $quiz->course);
|
||||
|
||||
require_login($cm->course, false, $cm);
|
||||
|
||||
if ($returnurl) {
|
||||
$returnurl = new moodle_url($returnurl);
|
||||
} else {
|
||||
$returnurl = new moodle_url('/mod/quiz/edit.php', array('cmid' => $cm->id));
|
||||
}
|
||||
|
||||
$url = new moodle_url('/mod/quiz/editrandom.php', array('slotid' => $slotid));
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_pagelayout('admin');
|
||||
|
||||
if (!$question = $DB->get_record('question', array('id' => $slot->questionid))) {
|
||||
print_error('questiondoesnotexist', 'question', $returnurl);
|
||||
}
|
||||
|
||||
$qtypeobj = question_bank::get_qtype('random');
|
||||
|
||||
// Validate the question category.
|
||||
if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
|
||||
print_error('categorydoesnotexist', 'question', $returnurl);
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
question_require_capability_on($question, 'edit');
|
||||
|
||||
$thiscontext = context_module::instance($cm->id);
|
||||
$contexts = new question_edit_contexts($thiscontext);
|
||||
|
||||
// Create the question editing form.
|
||||
$mform = new mod_quiz\form\randomquestion_form(new moodle_url('/mod/quiz/editrandom.php'),
|
||||
array('contexts' => $contexts));
|
||||
|
||||
// Send the question object and a few more parameters to the form.
|
||||
$toform = fullclone($question);
|
||||
$toform->category = "{$category->id},{$category->contextid}";
|
||||
$toform->includesubcategories = $slot->includingsubcategories;
|
||||
$toform->fromtags = array();
|
||||
if ($slot->tags) {
|
||||
$tags = quiz_extract_random_question_tags($slot->tags);
|
||||
foreach ($tags as $tag) {
|
||||
$toform->fromtags[] = "{$tag->id},{$tag->name}";
|
||||
}
|
||||
}
|
||||
$toform->returnurl = $returnurl;
|
||||
|
||||
if ($cm !== null) {
|
||||
$toform->cmid = $cm->id;
|
||||
$toform->courseid = $cm->course;
|
||||
} else {
|
||||
$toform->courseid = $COURSE->id;
|
||||
}
|
||||
|
||||
$toform->slotid = $slotid;
|
||||
|
||||
$mform->set_data($toform);
|
||||
|
||||
if ($mform->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
} else if ($fromform = $mform->get_data()) {
|
||||
|
||||
// If we are moving a question, check we have permission to move it from
|
||||
// whence it came. Where we are moving to is validated by the form.
|
||||
list($newcatid, $newcontextid) = explode(',', $fromform->category);
|
||||
if (!empty($question->id) && $newcatid != $question->category) {
|
||||
$contextid = $newcontextid;
|
||||
question_require_capability_on($question, 'move');
|
||||
} else {
|
||||
$contextid = $category->contextid;
|
||||
}
|
||||
|
||||
$question = $qtypeobj->save_question($question, $fromform);
|
||||
|
||||
// We need to save some data into the quiz_slots table.
|
||||
$slot->questioncategoryid = $fromform->category;
|
||||
$slot->includingsubcategories = $fromform->includesubcategories;
|
||||
|
||||
$tags = [];
|
||||
foreach ($fromform->fromtags as $tagstring) {
|
||||
list($tagid, $tagname) = explode(',', $tagstring);
|
||||
$tags[] = (object) [
|
||||
'id' => $tagid,
|
||||
'name' => $tagname
|
||||
];
|
||||
}
|
||||
$slot->tags = quiz_build_random_question_tag_json($tags);
|
||||
|
||||
$DB->update_record('quiz_slots', $slot);
|
||||
|
||||
// Purge this question from the cache.
|
||||
question_bank::notify_question_edited($question->id);
|
||||
|
||||
$returnurl->param('lastchanged', $question->id);
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
$streditingquestion = $qtypeobj->get_heading();
|
||||
$PAGE->set_title($streditingquestion);
|
||||
$PAGE->set_heading($COURSE->fullname);
|
||||
$PAGE->navbar->add($streditingquestion);
|
||||
|
||||
// Display a heading, question editing form and possibly some extra content needed for
|
||||
// for this question type.
|
||||
echo $OUTPUT->header();
|
||||
$heading = get_string('randomediting', 'mod_quiz');
|
||||
echo $OUTPUT->heading_with_help($heading, 'randomquestion', 'mod_quiz');
|
||||
|
||||
$mform->display();
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -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';
|
||||
@@ -438,6 +439,7 @@ $string['invalidcategory'] = 'Category ID is invalid';
|
||||
$string['invalidoverrideid'] = 'Invalid override id';
|
||||
$string['invalidquestionid'] = 'Invalid question id';
|
||||
$string['invalidquizid'] = 'Invalid quiz ID';
|
||||
$string['invalidrandomslot'] = 'Invalid random question slot id.';
|
||||
$string['invalidsource'] = 'The source is not accepted as valid.';
|
||||
$string['invalidsourcetype'] = 'Invalid source type.';
|
||||
$string['invalidstateid'] = 'Invalid state id';
|
||||
@@ -695,10 +697,17 @@ $string['quiztimer'] = 'Quiz Timer';
|
||||
$string['quizwillopen'] = 'This quiz will open {$a}';
|
||||
$string['random'] = 'Random question';
|
||||
$string['randomcreate'] = 'Create random questions';
|
||||
$string['randomediting'] = 'Editing a random question';
|
||||
$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['randomquestion'] = 'Random question';
|
||||
$string['randomquestion_help'] = 'A random question is a way of inserting a randomly-chosen question from a specified category or by a specified tag into an activity.';
|
||||
$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?';
|
||||
@@ -917,6 +926,7 @@ $string['ungraded'] = 'Ungraded';
|
||||
$string['unit'] = 'Unit';
|
||||
$string['unknowntype'] = 'Question type not supported at line {$a}. The question will be ignored';
|
||||
$string['updatesettings'] = 'Update quiz settings';
|
||||
$string['updatequizslotswithrandomxofy'] = 'Updating quiz slots with "random" question data ({$a->done}/{$a->total})';
|
||||
$string['updatingatttemptgrades'] = 'Updating attempt grades.';
|
||||
$string['updatingfinalgrades'] = 'Updating final grades.';
|
||||
$string['updatingthegradebook'] = 'Updating the gradebook.';
|
||||
|
||||
+139
-39
@@ -208,10 +208,12 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
|
||||
continue;
|
||||
}
|
||||
|
||||
$tagids = quiz_extract_random_question_tag_ids($questiondata->randomfromtags);
|
||||
|
||||
// Deal with fixed random choices for testing.
|
||||
if (isset($questionids[$quba->next_slot_number()])) {
|
||||
if ($randomloader->is_question_available($questiondata->category,
|
||||
(bool) $questiondata->questiontext, $questionids[$quba->next_slot_number()])) {
|
||||
(bool) $questiondata->questiontext, $questionids[$quba->next_slot_number()], $tagids)) {
|
||||
$questions[$slot] = question_bank::load_question(
|
||||
$questionids[$quba->next_slot_number()], $quizobj->get_quiz()->shuffleanswers);
|
||||
continue;
|
||||
@@ -221,8 +223,8 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
|
||||
}
|
||||
|
||||
// Normal case, pick one at random.
|
||||
$questionid = $randomloader->get_next_question_id($questiondata->category,
|
||||
(bool) $questiondata->questiontext);
|
||||
$questionid = $randomloader->get_next_question_id($questiondata->randomfromcategory,
|
||||
$questiondata->randomincludingsubcategories, $tagids);
|
||||
if ($questionid === null) {
|
||||
throw new moodle_exception('notenoughrandomquestions', 'quiz',
|
||||
$quizobj->view_url(), $questiondata);
|
||||
@@ -2072,6 +2074,15 @@ function quiz_has_question_use($quiz, $slot) {
|
||||
*/
|
||||
function quiz_add_quiz_question($questionid, $quiz, $page = 0, $maxmark = null) {
|
||||
global $DB;
|
||||
|
||||
// Make sue the question is not of the "random" type.
|
||||
$questiontype = $DB->get_field('question', 'qtype', array('id' => $questionid));
|
||||
if ($questiontype == 'random') {
|
||||
throw new coding_exception(
|
||||
'Adding "random" questions via quiz_add_quiz_question() is deprecated. Please use quiz_add_random_questions().'
|
||||
);
|
||||
}
|
||||
|
||||
$slots = $DB->get_records('quiz_slots', array('quizid' => $quiz->id),
|
||||
'slot', 'questionid, slot, page, id');
|
||||
if (array_key_exists($questionid, $slots)) {
|
||||
@@ -2159,14 +2170,15 @@ function quiz_update_section_firstslots($quizid, $direction, $afterslot, $before
|
||||
|
||||
/**
|
||||
* Add a random question to the quiz at a given point.
|
||||
* @param object $quiz the quiz settings.
|
||||
* @param stdClass $quiz the quiz settings.
|
||||
* @param int $addonpage the page on which to add the question.
|
||||
* @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));
|
||||
@@ -2177,44 +2189,62 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
|
||||
$catcontext = context::instance_by_id($category->contextid);
|
||||
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.
|
||||
if ($existingquestions = $DB->get_records_sql(
|
||||
"SELECT q.id, q.qtype FROM {question} q
|
||||
WHERE qtype = 'random'
|
||||
AND category = ?
|
||||
AND " . $DB->sql_compare_text('questiontext') . " = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT *
|
||||
FROM {quiz_slots}
|
||||
WHERE questionid = q.id)
|
||||
ORDER BY id", array($category->id, ($includesubcategories ? '1' : '0')))) {
|
||||
// Take as many of these as needed.
|
||||
while (($existingquestion = array_shift($existingquestions)) && $number > 0) {
|
||||
quiz_add_quiz_question($existingquestion->id, $quiz, $addonpage);
|
||||
$number -= 1;
|
||||
}
|
||||
}
|
||||
$existingquestions = $DB->get_records_sql(
|
||||
"SELECT q.id, q.qtype FROM {question} q
|
||||
WHERE qtype = 'random'
|
||||
AND category = ?
|
||||
AND " . $DB->sql_compare_text('questiontext') . " = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT *
|
||||
FROM {quiz_slots}
|
||||
WHERE questionid = q.id)
|
||||
ORDER BY id", array($category->id, $includesubcategories ? '1' : '0'));
|
||||
|
||||
if ($number <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// More random questions are needed, create them.
|
||||
for ($i = 0; $i < $number; $i += 1) {
|
||||
$form = new stdClass();
|
||||
$form->questiontext = array('text' => ($includesubcategories ? '1' : '0'), 'format' => 0);
|
||||
$form->category = $category->id . ',' . $category->contextid;
|
||||
$form->defaultmark = 1;
|
||||
$form->hidden = 1;
|
||||
$form->stamp = make_unique_id_code(); // Set the unique code (not to be changed).
|
||||
$question = new stdClass();
|
||||
$question->qtype = 'random';
|
||||
$question = question_bank::get_qtype('random')->save_question($question, $form);
|
||||
if (!isset($question->id)) {
|
||||
print_error('cannotinsertrandomquestion', 'quiz');
|
||||
for ($i = 0; $i < $number; $i++) {
|
||||
// Take as many of orphaned "random" questions as needed.
|
||||
if (!$question = array_shift($existingquestions)) {
|
||||
$form = new stdClass();
|
||||
$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).
|
||||
$question = new stdClass();
|
||||
$question->qtype = 'random';
|
||||
$question = question_bank::get_qtype('random')->save_question($question, $form);
|
||||
if (!isset($question->id)) {
|
||||
print_error('cannotinsertrandomquestion', 'quiz');
|
||||
}
|
||||
}
|
||||
quiz_add_quiz_question($question->id, $quiz, $addonpage);
|
||||
|
||||
$randomslotdata = new stdClass();
|
||||
$randomslotdata->quizid = $quiz->id;
|
||||
$randomslotdata->questionid = $question->id;
|
||||
$randomslotdata->questioncategoryid = $categoryid;
|
||||
$randomslotdata->includingsubcategories = $includesubcategories ? 1 : 0;
|
||||
$randomslotdata->tags = json_encode($tags);
|
||||
$randomslotdata->maxmark = 1;
|
||||
|
||||
$randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
|
||||
$randomslot->set_quiz($quiz);
|
||||
$randomslot->insert($addonpage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2408,3 +2438,73 @@ function quiz_is_overriden_calendar_event(\calendar_event $event) {
|
||||
|
||||
return $DB->record_exists('quiz_overrides', $overrideparams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Providing a list of tag records, this function validates each pair and builds a json string
|
||||
* that can be stored in the quiz_slots.tags field.
|
||||
*
|
||||
* @param stdClass[] $tagrecords List of tag objects with id and name properties.
|
||||
* @return string
|
||||
*/
|
||||
function quiz_build_random_question_tag_json($tagrecords) {
|
||||
$tags = [];
|
||||
foreach ($tagrecords as $tagrecord) {
|
||||
if ($tag = core_tag_tag::get($tagrecord->id, 'id, name')) {
|
||||
$tags[] = [
|
||||
'id' => (int)$tagrecord->id,
|
||||
'name' => $tag->name
|
||||
];
|
||||
} else if ($tag = core_tag_tag::get_by_name(0, $tagrecord->name, 'id, name')) {
|
||||
$tags[] = [
|
||||
'id' => $tag->id,
|
||||
'name' => $tagrecord->name
|
||||
];
|
||||
} else {
|
||||
$tags[] = [
|
||||
'id' => null,
|
||||
'name' => $tagrecord->name
|
||||
];
|
||||
}
|
||||
}
|
||||
return json_encode($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Providing tags data in the JSON format, this function returns tag records containing the id and name properties.
|
||||
*
|
||||
* @param string $tagsjson The JSON string representing an array of tags in the [{"id":tagid,"name":"tagname"}] format.
|
||||
* E.g. [{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}]
|
||||
* Usually equal to the value of the tags field retrieved from the quiz_slots table.
|
||||
* @return array An array of tags containing the id and name properties, indexed by tag ids.
|
||||
*/
|
||||
function quiz_extract_random_question_tags($tagsjson) {
|
||||
$tagrecords = [];
|
||||
if (!empty($tagsjson)) {
|
||||
$tags = json_decode($tagsjson);
|
||||
// Only work with tags that exist.
|
||||
foreach ($tags as $tagdata) {
|
||||
if (!array_key_exists($tagdata->id, $tagrecords)) {
|
||||
if ($tag = core_tag_tag::get($tagdata->id, 'id, name')) {
|
||||
$tagrecords[$tag->id] = $tag->to_object();
|
||||
} else if ($tag = core_tag_tag::get_by_name(0, $tagdata->name, 'id, name')) {
|
||||
$tagrecords[$tag->id] = $tag->to_object();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tagrecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Providing tags data in the JSON format, this function returns tagids.
|
||||
*
|
||||
* @param string $tagsjson The JSON string representing an array of tags in the [{"id":tagid,"name":"tagname"}] format.
|
||||
* E.g. [{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}]
|
||||
* Usually equal to the value of the tags field retrieved from the {quiz_slots} table.
|
||||
* @return int[] List of tag ids.
|
||||
*/
|
||||
function quiz_extract_random_question_tag_ids($tagsjson) {
|
||||
$tags = quiz_extract_random_question_tags($tagsjson);
|
||||
return array_keys($tags);
|
||||
}
|
||||
@@ -78,7 +78,7 @@ class behat_mod_quiz extends behat_question_base {
|
||||
$headings = array('question', 'page', 'maxmark');
|
||||
} else {
|
||||
throw new ExpectationException('When adding questions to a quiz, you should give 2 or three 3 things: ' .
|
||||
' the question name, the page number, and optionally the maxiumum mark. ' .
|
||||
' the question name, the page number, and optionally the maximum mark. ' .
|
||||
count($firstrow) . ' values passed.', $this->getSession());
|
||||
}
|
||||
$rows = $data->getRows();
|
||||
@@ -98,9 +98,8 @@ class behat_mod_quiz extends behat_question_base {
|
||||
'the page number column is required.', $this->getSession());
|
||||
}
|
||||
|
||||
// Question id.
|
||||
$questionid = $DB->get_field('question', 'id',
|
||||
array('name' => $questiondata['question']), MUST_EXIST);
|
||||
// Question id, category and type.
|
||||
$question = $DB->get_record('question', array('name' => $questiondata['question']), 'id, category, qtype', MUST_EXIST);
|
||||
|
||||
// Page number.
|
||||
$page = clean_param($questiondata['page'], PARAM_INT);
|
||||
@@ -129,8 +128,17 @@ class behat_mod_quiz extends behat_question_base {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the question.
|
||||
quiz_add_quiz_question($questionid, $quiz, $page, $maxmark);
|
||||
if ($question->qtype == 'random') {
|
||||
if (!array_key_exists('includingsubcategories', $questiondata) || $questiondata['includingsubcategories'] === '') {
|
||||
$includingsubcategories = false;
|
||||
} else {
|
||||
$includingsubcategories = clean_param($questiondata['includingsubcategories'], PARAM_BOOL);
|
||||
}
|
||||
quiz_add_random_questions($quiz, $page, $question->category, 1, $includingsubcategories);
|
||||
} else {
|
||||
// Add the question.
|
||||
quiz_add_quiz_question($question->id, $quiz, $page, $maxmark);
|
||||
}
|
||||
|
||||
// Require previous.
|
||||
if (array_key_exists('requireprevious', $questiondata)) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
@mod @mod_quiz @javascript
|
||||
Feature: Adding random questions to a quiz based on category and tags
|
||||
In order to have better assessment
|
||||
As a teacher
|
||||
I want to display questions that are randomly picked from the question bank
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | t1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| quiz | Quiz 1 | Quiz 1 for testing the Add random question form | C1 | quiz1 |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Questions Category 1|
|
||||
| Course | C1 | Questions Category 2|
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | user | questiontext |
|
||||
| Questions Category 1 | essay | question 1 name | admin | Question 1 text |
|
||||
| Questions Category 1 | essay | question 2 name | teacher1 | Question 2 text |
|
||||
|
||||
Scenario: Available tags are shown in the autocomplete tag field
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I navigate to "Questions" node in "Course administration > Question bank"
|
||||
And I click on "Edit" "link" in the "question 1 name" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Tags | foo |
|
||||
And I press "id_submitbutton"
|
||||
And I click on "Manage tags" "link" in the "question 2 name" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Tags | bar |
|
||||
And I press "Save changes"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Quiz 1"
|
||||
And I navigate to "Edit quiz" in current page administration
|
||||
And I open the "last" add to quiz menu
|
||||
And I follow "a random question"
|
||||
And I open the autocomplete suggestions list
|
||||
Then "foo" "autocomplete_suggestions" should exist
|
||||
And "bar" "autocomplete_suggestions" should exist
|
||||
@@ -1587,6 +1587,8 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
public function test_get_attempt_access_information() {
|
||||
global $DB;
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create a new quiz with attempts.
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$data = array('course' => $this->course->id,
|
||||
@@ -1607,8 +1609,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$question = $questiongenerator->create_question('truefalse', null, array('category' => $cat->id));
|
||||
$question = $questiongenerator->create_question('essay', null, array('category' => $cat->id));
|
||||
|
||||
$question = $questiongenerator->create_question('random', null, array('category' => $cat->id));
|
||||
quiz_add_quiz_question($question->id, $quiz);
|
||||
quiz_add_random_questions($quiz, 0, $cat->id, 1, false);
|
||||
|
||||
$quizobj = quiz::create($quiz->id, $this->student->id);
|
||||
|
||||
@@ -1670,7 +1671,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
* Test get_quiz_required_qtypes
|
||||
*/
|
||||
public function test_get_quiz_required_qtypes() {
|
||||
global $DB;
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create a new quiz.
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
@@ -1691,8 +1692,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$question = $questiongenerator->create_question('truefalse', null, array('category' => $cat->id));
|
||||
$question = $questiongenerator->create_question('essay', null, array('category' => $cat->id));
|
||||
|
||||
$question = $questiongenerator->create_question('random', null, array('category' => $cat->id));
|
||||
quiz_add_quiz_question($question->id, $quiz);
|
||||
quiz_add_random_questions($quiz, 0, $cat->id, 1, false);
|
||||
|
||||
$this->setUser($this->student);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ This files describes API changes in the quiz code.
|
||||
|
||||
=== 3.5 ===
|
||||
* Removed questionbank.ajax.php. Please use the quiz_question_bank fragment instead.
|
||||
* Adding "random" questions to a quiz via quiz_add_quiz_question() has been deprecated. Please use quiz_add_random_questions().
|
||||
|
||||
=== 3.3.2 ===
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->version = 2018020701;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->component = 'mod_quiz';
|
||||
$plugin->cron = 60;
|
||||
|
||||
@@ -77,6 +77,7 @@ class random_question_loader {
|
||||
|
||||
/**
|
||||
* Pick a question at random from the given category, from among those with the fewest uses.
|
||||
* If an array of tag ids are specified, then only the questions that are tagged with ALL those tags will be selected.
|
||||
*
|
||||
* It is up the the caller to verify that the cateogry exists. An unknown category
|
||||
* behaves like an empty one.
|
||||
@@ -84,12 +85,14 @@ class random_question_loader {
|
||||
* @param int $categoryid the id of a category in the question bank.
|
||||
* @param bool $includesubcategories wether to pick a question from exactly
|
||||
* that category, or that category and subcategories.
|
||||
* @param array $tagids An array of tag ids. A question has to be tagged with all the provided tagids (if any)
|
||||
* in order to be eligible for being picked.
|
||||
* @return int|null the id of the question picked, or null if there aren't any.
|
||||
*/
|
||||
public function get_next_question_id($categoryid, $includesubcategories) {
|
||||
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories);
|
||||
public function get_next_question_id($categoryid, $includesubcategories, $tagids = []) {
|
||||
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids);
|
||||
|
||||
$categorykey = $this->get_category_key($categoryid, $includesubcategories);
|
||||
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
|
||||
if (empty($this->availablequestionscache[$categorykey])) {
|
||||
return null;
|
||||
}
|
||||
@@ -107,26 +110,35 @@ class random_question_loader {
|
||||
* @param int $categoryid the id of a category in the question bank.
|
||||
* @param bool $includesubcategories wether to pick a question from exactly
|
||||
* that category, or that category and subcategories.
|
||||
* @param array $tagids an array of tag ids.
|
||||
* @return string the cache key.
|
||||
*/
|
||||
protected function get_category_key($categoryid, $includesubcategories) {
|
||||
protected function get_category_key($categoryid, $includesubcategories, $tagids = []) {
|
||||
if ($includesubcategories) {
|
||||
return $categoryid . '|1';
|
||||
$key = $categoryid . '|1';
|
||||
} else {
|
||||
return $categoryid . '|0';
|
||||
$key = $categoryid . '|0';
|
||||
}
|
||||
|
||||
if (!empty($tagids)) {
|
||||
$key .= '|' . implode('|', $tagids);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate {@link $availablequestionscache} for this combination of options.
|
||||
* @param int $categoryid the id of a category in the question bank.
|
||||
* @param bool $includesubcategories wether to pick a question from exactly
|
||||
* @param int $categoryid The id of a category in the question bank.
|
||||
* @param bool $includesubcategories Whether to pick a question from exactly
|
||||
* that category, or that category and subcategories.
|
||||
* @param array $tagids An array of tag ids. If an array is provided, then
|
||||
* only the questions that are tagged with ALL the provided tagids will be loaded.
|
||||
*/
|
||||
protected function ensure_questions_for_category_loaded($categoryid, $includesubcategories) {
|
||||
protected function ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids = []) {
|
||||
global $DB;
|
||||
|
||||
$categorykey = $this->get_category_key($categoryid, $includesubcategories);
|
||||
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
|
||||
|
||||
if (isset($this->availablequestionscache[$categorykey])) {
|
||||
// Data is already in the cache, nothing to do.
|
||||
@@ -143,8 +155,8 @@ class random_question_loader {
|
||||
list($extraconditions, $extraparams) = $DB->get_in_or_equal($this->excludedqtypes,
|
||||
SQL_PARAMS_NAMED, 'excludedqtype', false);
|
||||
|
||||
$questionidsandcounts = \question_bank::get_finder()->get_questions_from_categories_with_usage_counts(
|
||||
$categoryids, $this->qubaids, 'q.qtype ' . $extraconditions, $extraparams);
|
||||
$questionidsandcounts = \question_bank::get_finder()->get_questions_from_categories_and_tags_with_usage_counts(
|
||||
$categoryids, $this->qubaids, 'q.qtype ' . $extraconditions, $extraparams, $tagids);
|
||||
if (!$questionidsandcounts) {
|
||||
// No questions in this category.
|
||||
$this->availablequestionscache[$categorykey] = array();
|
||||
@@ -200,16 +212,19 @@ class random_question_loader {
|
||||
|
||||
/**
|
||||
* Check whether a given question is available in a given category. If so, mark it used.
|
||||
* If an optional list of tag ids are provided, then the question must be tagged with
|
||||
* ALL of the provided tags to be considered as available.
|
||||
*
|
||||
* @param int $categoryid the id of a category in the question bank.
|
||||
* @param bool $includesubcategories wether to pick a question from exactly
|
||||
* that category, or that category and subcategories.
|
||||
* @param int $questionid the question that is being used.
|
||||
* @param array $tagids An array of tag ids. Only the questions that are tagged with all the provided tagids can be available.
|
||||
* @return bool whether the question is available in the requested category.
|
||||
*/
|
||||
public function is_question_available($categoryid, $includesubcategories, $questionid) {
|
||||
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories);
|
||||
$categorykey = $this->get_category_key($categoryid, $includesubcategories);
|
||||
public function is_question_available($categoryid, $includesubcategories, $questionid, $tagids = []) {
|
||||
$this->ensure_questions_for_category_loaded($categoryid, $includesubcategories, $tagids);
|
||||
$categorykey = $this->get_category_key($categoryid, $includesubcategories, $tagids);
|
||||
|
||||
foreach ($this->availablequestionscache[$categorykey] as $questionids) {
|
||||
if (isset($questionids[$questionid])) {
|
||||
|
||||
+57
-19
@@ -294,10 +294,6 @@ abstract class question_bank {
|
||||
*/
|
||||
public static function get_finder() {
|
||||
return question_finder::get_instance();
|
||||
if (is_null(self::$questionfinder)) {
|
||||
self::$questionfinder = new question_finder();
|
||||
}
|
||||
return self::$questionfinder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,29 +535,71 @@ class question_finder implements cache_data_source {
|
||||
*/
|
||||
public function get_questions_from_categories_with_usage_counts($categoryids,
|
||||
qubaid_condition $qubaids, $extraconditions = '', $extraparams = array()) {
|
||||
return $this->get_questions_from_categories_and_tags_with_usage_counts(
|
||||
$categoryids, $qubaids, $extraconditions, $extraparams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of all the questions in a list of categories that have ALL the provided tags,
|
||||
* with the number of times they have already been used in a given set of usages.
|
||||
*
|
||||
* The result array is returned in order of increasing (count previous uses).
|
||||
*
|
||||
* @param array $categoryids an array of question_category ids.
|
||||
* @param qubaid_condition $qubaids which question_usages to count previous uses from.
|
||||
* @param string $extraconditions extra conditions to AND with the rest of
|
||||
* the where clause. Must use named parameters.
|
||||
* @param array $extraparams any parameters used by $extraconditions.
|
||||
* @param array $tagids an array of tag ids
|
||||
* @return array questionid => count of number of previous uses.
|
||||
*/
|
||||
public function get_questions_from_categories_and_tags_with_usage_counts($categoryids,
|
||||
qubaid_condition $qubaids, $extraconditions = '', $extraparams = array(), $tagids = array()) {
|
||||
global $DB;
|
||||
|
||||
list($qcsql, $qcparams) = $DB->get_in_or_equal($categoryids, SQL_PARAMS_NAMED, 'qc');
|
||||
|
||||
$select = "q.id, (SELECT COUNT(1)
|
||||
FROM " . $qubaids->from_question_attempts('qa') . "
|
||||
WHERE qa.questionid = q.id AND " . $qubaids->where() . "
|
||||
) AS previous_attempts";
|
||||
$from = "{question} q";
|
||||
$where = "q.category {$qcsql}
|
||||
AND q.parent = 0
|
||||
AND q.hidden = 0";
|
||||
$params = $qcparams;
|
||||
|
||||
if (!empty($tagids)) {
|
||||
// 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($tagids, SQL_PARAMS_NAMED, 'ti');
|
||||
$tagparams['tagcount'] = count($tagids);
|
||||
$tagparams['questionitemtype'] = 'question';
|
||||
$tagparams['questioncomponent'] = 'core_question';
|
||||
$where .= " AND q.id IN (SELECT ti.itemid
|
||||
FROM {tag_instance} ti
|
||||
WHERE ti.itemtype = :questionitemtype
|
||||
AND ti.component = :questioncomponent
|
||||
AND ti.tagid {$tagsql}
|
||||
GROUP BY ti.itemid
|
||||
HAVING COUNT(itemid) = :tagcount)";
|
||||
$params += $tagparams;
|
||||
}
|
||||
|
||||
if ($extraconditions) {
|
||||
$extraconditions = ' AND (' . $extraconditions . ')';
|
||||
}
|
||||
|
||||
return $DB->get_records_sql_menu("
|
||||
SELECT q.id, (SELECT COUNT(1)
|
||||
FROM " . $qubaids->from_question_attempts('qa') . "
|
||||
WHERE qa.questionid = q.id AND " . $qubaids->where() . "
|
||||
) AS previous_attempts
|
||||
|
||||
FROM {question} q
|
||||
|
||||
WHERE q.category {$qcsql}
|
||||
AND q.parent = 0
|
||||
AND q.hidden = 0
|
||||
{$extraconditions}
|
||||
|
||||
ORDER BY previous_attempts
|
||||
", $qubaids->from_where_params() + $qcparams + $extraparams);
|
||||
return $DB->get_records_sql_menu("SELECT $select
|
||||
FROM $from
|
||||
WHERE $where $extraconditions
|
||||
ORDER BY previous_attempts",
|
||||
$qubaids->from_where_params() + $params + $extraparams);
|
||||
}
|
||||
|
||||
/* See cache_data_source::load_for_cache. */
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class qtype_random_test_helper extends question_test_helper {
|
||||
public function get_random_question_form_data_basic() {
|
||||
$form = new stdClass();
|
||||
$form->questiontext = array('text' => '');
|
||||
$form->includingsubcategories = '0';
|
||||
$form->includesubcategories = '0';
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user