mirror of
https://github.com/moodle/moodle.git
synced 2025-04-08 09:52:42 +02:00
Merge branch 'MDL-74255' of https://github.com/timhunt/moodle
This commit is contained in:
commit
217fb9ec66
@ -80,10 +80,13 @@ class restore_quiz_decode_testcase extends \core_privacy\tests\provider_testcase
|
||||
|
||||
$newcm = duplicate_module($course, get_fast_modinfo($course)->get_cm($quiz->cmid));
|
||||
|
||||
$quizquestions = \mod_quiz\question\bank\qbank_helper::get_question_structure_data($newcm->instance);
|
||||
$quizquestions = \mod_quiz\question\bank\qbank_helper::get_question_structure(
|
||||
$newcm->instance, context_module::instance($newcm->id));
|
||||
$questionids = [];
|
||||
foreach ($quizquestions as $quizquestion) {
|
||||
$questionids[] = $quizquestion->id;
|
||||
if ($quizquestion->questionid) {
|
||||
$questionids[] = $quizquestion->questionid;
|
||||
}
|
||||
}
|
||||
list($condition, $param) = $DB->get_in_or_equal($questionids, SQL_PARAMS_NAMED, 'questionid');
|
||||
$condition = 'WHERE qa.question ' . $condition;
|
||||
|
@ -271,7 +271,7 @@ class course_modinfo {
|
||||
|
||||
/**
|
||||
* Obtains all instances of a particular module on this course.
|
||||
* @param $modname Name of module (not full frankenstyle) e.g. 'label'
|
||||
* @param string $modname Name of module (not full frankenstyle) e.g. 'label'
|
||||
* @return cm_info[] Array from instance id => cm_info for modules on this course; empty if none
|
||||
*/
|
||||
public function get_instances_of($modname) {
|
||||
|
@ -2102,7 +2102,7 @@ function is_latest(string $version, string $questionbankentryid) : bool {
|
||||
* filter settings, theme, lang, etc.) Defaults to $PAGE->context.
|
||||
* @return moodle_url the URL.
|
||||
* @deprecated since Moodle 4.0
|
||||
* @see qbank_previewquestion\previewquestion_helper::question_preview_url()
|
||||
* @see qbank_previewquestion\helper::question_preview_url()
|
||||
* @todo Final deprecation on Moodle 4.4 MDL-72438
|
||||
*/
|
||||
function question_preview_url($questionid, $preferredbehaviour = null,
|
||||
|
@ -25,9 +25,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
|
||||
/**
|
||||
* Class for quiz exceptions. Just saves a couple of arguments on the
|
||||
@ -79,7 +80,11 @@ class quiz {
|
||||
/** @var context the quiz context. */
|
||||
protected $context;
|
||||
|
||||
/** @var stdClass[] of questions augmented with slot information. */
|
||||
/**
|
||||
* @var stdClass[] of questions augmented with slot information. For non-random
|
||||
* questions, the array key is question id. For random quesions it is 's' . $slotid.
|
||||
* probalby best to use ->questionid field of the object instead.
|
||||
*/
|
||||
protected $questions = null;
|
||||
/** @var stdClass[] of quiz_section rows. */
|
||||
protected $sections = null;
|
||||
@ -145,35 +150,33 @@ class quiz {
|
||||
* Load just basic information about all the questions in this quiz.
|
||||
*/
|
||||
public function preload_questions() {
|
||||
$specificquestionids = \mod_quiz\question\bank\qbank_helper::get_specific_version_question_ids($this->quiz->id);
|
||||
$latestquestionids = \mod_quiz\question\bank\qbank_helper::get_always_latest_version_question_ids($this->quiz->id);
|
||||
$questionids = array_merge($specificquestionids, $latestquestionids);
|
||||
$questiondata = [];
|
||||
if (!empty($questionids)) {
|
||||
$questiondata = \mod_quiz\question\bank\qbank_helper::get_question_structure_data($this->quiz->id, $questionids, true);
|
||||
$slots = qbank_helper::get_question_structure($this->quiz->id, $this->context);
|
||||
$this->questions = [];
|
||||
foreach ($slots as $slot) {
|
||||
$this->questions[$slot->questionid] = $slot;
|
||||
}
|
||||
$allquestiondata = \mod_quiz\question\bank\qbank_helper::question_array_sort(
|
||||
\mod_quiz\question\bank\qbank_helper::question_load_random_questions($this->quiz->id, $questiondata), 'slot');
|
||||
$this->questions = $allquestiondata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully load some or all of the questions for this quiz. You must call
|
||||
* {@link preload_questions()} first.
|
||||
*
|
||||
* @param array|null $questionids question ids of the questions to load. null for all.
|
||||
* @param array|null $deprecated no longer supported (it was not used).
|
||||
*/
|
||||
public function load_questions($questionids = null) {
|
||||
public function load_questions($deprecated = null) {
|
||||
if ($deprecated !== null) {
|
||||
debugging('The argument to quiz::load_questions is no longer supported. ' .
|
||||
'All questions are always loaded.', DEBUG_DEVELOPER);
|
||||
}
|
||||
if ($this->questions === null) {
|
||||
throw new coding_exception('You must call preload_questions before calling load_questions.');
|
||||
}
|
||||
if (is_null($questionids)) {
|
||||
$questionids = array_keys($this->questions);
|
||||
}
|
||||
$questionstoprocess = array();
|
||||
foreach ($questionids as $id) {
|
||||
if (array_key_exists($id, $this->questions)) {
|
||||
$questionstoprocess[$id] = $this->questions[$id];
|
||||
|
||||
$questionstoprocess = [];
|
||||
foreach ($this->questions as $question) {
|
||||
if (is_number($question->questionid)) {
|
||||
$question->id = $question->questionid;
|
||||
$questionstoprocess[$question->questionid] = $question;
|
||||
}
|
||||
}
|
||||
get_question_options($questionstoprocess);
|
||||
@ -540,8 +543,17 @@ class quiz {
|
||||
$qcategories = array();
|
||||
|
||||
foreach ($this->get_questions() as $questiondata) {
|
||||
if (!in_array($questiondata->qtype, $questiontypes)) {
|
||||
$questiontypes[] = $questiondata->qtype;
|
||||
if ($questiondata->qtype == 'random' and $includepotential) {
|
||||
if (!isset($qcategories[$questiondata->category])) {
|
||||
$qcategories[$questiondata->category] = false;
|
||||
}
|
||||
if ($questiondata->includingsubcategories) {
|
||||
$qcategories[$questiondata->category] = true;
|
||||
}
|
||||
} else {
|
||||
if (!in_array($questiondata->qtype, $questiontypes)) {
|
||||
$questiontypes[] = $questiondata->qtype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2099,8 +2111,9 @@ class quiz_attempt {
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
// Add the question to the usage. It is important we do this before we choose a variant.
|
||||
$newquestion = question_bank::load_question(
|
||||
\mod_quiz\question\bank\qbank_helper::choose_question_for_redo($this->slots[$slot]->id, $qubaids));
|
||||
$newquestionid = qbank_helper::choose_question_for_redo($this->get_quizid(),
|
||||
$this->get_quizobj()->get_context(), $this->slots[$slot]->id, $qubaids);
|
||||
$newquestion = question_bank::load_question($newquestionid, $this->get_quiz()->shuffleanswers);
|
||||
$newslot = $this->quba->add_question_in_place_of_other($slot, $newquestion);
|
||||
|
||||
// Choose the variant.
|
||||
|
@ -353,7 +353,7 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
|
||||
$questionreference->questionarea = 'slot';
|
||||
$questionreference->itemid = $data->id;
|
||||
$questionreference->questionbankentryid = $question->questionbankentryid;
|
||||
$questionreference->version = $question->version;
|
||||
$questionreference->version = null; // Default to Always latest.
|
||||
$DB->insert_record('question_references', $questionreference);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class submit_question_version extends external_api {
|
||||
* Set the questions slot parameters to display the question template.
|
||||
*
|
||||
* @param int $slotid Slot id to display.
|
||||
* @param int $newversion
|
||||
* @param int $newversion the version to set. 0 means 'always latest'.
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(int $slotid, int $newversion): array {
|
||||
|
@ -220,4 +220,4 @@ class slot_random {
|
||||
]);
|
||||
$event->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ defined('MOODLE_INTERNAL') || die();
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
use \mod_quiz\structure;
|
||||
use \html_writer;
|
||||
use \qbank_previewquestion\helper;
|
||||
use renderable;
|
||||
|
||||
/**
|
||||
@ -733,7 +732,6 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function question(structure $structure, int $slot, \moodle_url $pageurl) {
|
||||
global $DB;
|
||||
// Get the data required by the question_slot template.
|
||||
$slotid = $structure->get_slot_id_for_slot($slot);
|
||||
|
||||
@ -758,7 +756,7 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
$data['versionoptions'] = [];
|
||||
if ($structure->get_slot_by_number($slot)->qtype !== 'random') {
|
||||
$data['versionselection'] = true;
|
||||
$data['versionoption'] = qbank_helper::get_question_version_info($structure->get_question_in_slot($slot)->id, $slotid);
|
||||
$data['versionoption'] = $structure->get_version_choices_for_slot($slot);
|
||||
$this->page->requires->js_call_amd('mod_quiz/question_slot', 'init', [$slotid]);
|
||||
}
|
||||
|
||||
@ -821,8 +819,9 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
$qtype = $structure->get_question_type_for_slot($slot);
|
||||
$questionicons = '';
|
||||
if ($qtype !== 'random') {
|
||||
$questionicons .= $this->question_preview_icon($structure->get_quiz(), $structure->get_question_in_slot($slot),
|
||||
null, null, $qtype);
|
||||
$questionicons .= $this->question_preview_icon($structure->get_quiz(),
|
||||
$structure->get_question_in_slot($slot),
|
||||
null, null, $qtype);
|
||||
}
|
||||
if ($structure->can_be_edited()) {
|
||||
$questionicons .= $this->question_remove_icon($structure, $slot, $pageurl);
|
||||
@ -862,13 +861,19 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
* Render the preview icon.
|
||||
*
|
||||
* @param \stdClass $quiz the quiz settings from the database.
|
||||
* @param \stdClass $question data from the question and quiz_slots tables.
|
||||
* @param \stdClass $questiondata which question to preview.
|
||||
* If ->questionid is set, that is used instead of ->id.
|
||||
* @param bool $label if true, show the preview question label after the icon
|
||||
* @param int $variant which question variant to preview (optional).
|
||||
* @param string $qtype the type of question
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function question_preview_icon($quiz, $question, $label = null, $variant = null, $qtype = null) {
|
||||
public function question_preview_icon($quiz, $questiondata, $label = null, $variant = null) {
|
||||
$question = clone($questiondata);
|
||||
if (isset($question->questionid)) {
|
||||
|
||||
$question->id = $question->questionid;
|
||||
}
|
||||
|
||||
$url = quiz_question_preview_url($quiz, $question, $variant);
|
||||
|
||||
// Do we want a label?
|
||||
@ -994,7 +999,7 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
$question = $structure->get_question_in_slot($slot);
|
||||
$editurl = new \moodle_url('/question/bank/editquestion/question.php', array(
|
||||
'returnurl' => $pageurl->out_as_local_url(),
|
||||
'cmid' => $structure->get_cmid(), 'id' => $question->id));
|
||||
'cmid' => $structure->get_cmid(), 'id' => $question->questionid));
|
||||
|
||||
$instancename = quiz_question_tostring($question);
|
||||
|
||||
@ -1036,9 +1041,6 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
$temp->questiontext = '';
|
||||
$instancename = quiz_question_tostring($temp);
|
||||
|
||||
$setreference = qbank_helper::get_random_question_data_from_slot($slot->id);
|
||||
$filtercondition = json_decode($setreference->filtercondition);
|
||||
|
||||
$configuretitle = get_string('configurerandomquestion', 'quiz');
|
||||
$qtype = \question_bank::get_qtype($question->qtype, false);
|
||||
$namestr = $qtype->local_name();
|
||||
@ -1046,15 +1048,15 @@ class edit_renderer extends \plugin_renderer_base {
|
||||
'class' => 'icon activityicon', 'alt' => ' ', 'role' => 'presentation'));
|
||||
|
||||
$editicon = $this->pix_icon('t/edit', $configuretitle, 'moodle', array('title' => ''));
|
||||
$qbankurlparams = array(
|
||||
'cmid' => $structure->get_cmid(),
|
||||
'cat' => $filtercondition->questioncategoryid . ',' . $setreference->questionscontextid,
|
||||
'recurse' => !empty($setreference->questionscontextid)
|
||||
);
|
||||
$qbankurlparams = [
|
||||
'cmid' => $structure->get_cmid(),
|
||||
'cat' => $slot->category . ',' . $slot->contextid,
|
||||
'recurse' => $slot->randomrecurse,
|
||||
];
|
||||
|
||||
$slottags = [];
|
||||
if (isset($filtercondition->tags)) {
|
||||
$slottags = $filtercondition->tags;
|
||||
if (isset($slot->randomtags)) {
|
||||
$slottags = $slot->randomtags;
|
||||
}
|
||||
foreach ($slottags as $index => $slottag) {
|
||||
$slottag = explode(',', $slottag);
|
||||
|
@ -16,6 +16,11 @@
|
||||
|
||||
namespace mod_quiz\question\bank;
|
||||
|
||||
use core_question\local\bank\question_version_status;
|
||||
use core_question\local\bank\random_question_loader;
|
||||
use qubaid_condition;
|
||||
use quiz_attempt;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/mod/quiz/accessmanager.php');
|
||||
@ -33,396 +38,212 @@ require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
|
||||
class qbank_helper {
|
||||
|
||||
/**
|
||||
* Check if the slot is a random question or not.
|
||||
* Get the available versions of a question where one of the version has the given question id.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @return bool
|
||||
* @param int $questionid id of a question.
|
||||
* @return \stdClass[] other versions of this question. Each object has fields versionid,
|
||||
* version and questionid. Array is returned most recent version first.
|
||||
*/
|
||||
public static function is_random($slotid): bool {
|
||||
public static function get_version_options(int $questionid): array {
|
||||
global $DB;
|
||||
|
||||
return $DB->get_records_sql("
|
||||
SELECT allversions.id AS versionid,
|
||||
allversions.version,
|
||||
allversions.questionid
|
||||
|
||||
FROM {question_versions} allversions
|
||||
|
||||
WHERE allversions.questionbankentryid = (
|
||||
SELECT givenversion.questionbankentryid
|
||||
FROM {question_versions} givenversion
|
||||
WHERE givenversion.questionid = ?
|
||||
)
|
||||
AND allversions.status <> ?
|
||||
|
||||
ORDER BY allversions.version DESC
|
||||
", [$questionid, question_version_status::QUESTION_STATUS_DRAFT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the information about which questions should be used to create a quiz attempt.
|
||||
*
|
||||
* Each element in the returned array is indexed by slot.slot (slot number) an each object hass:
|
||||
* - All the field of the slot table.
|
||||
* - contextid for where the question(s) come from.
|
||||
* - category id for where the questions come from.
|
||||
* - For non-random questions, All the fields of the question table (but id is in questionid).
|
||||
* Also question version and question bankentryid.
|
||||
* - For random questions, filtercondition, which is also unpacked into category, randomrecurse,
|
||||
* randomtags, and note that these also have a ->name set and ->qtype set to 'random'.
|
||||
*
|
||||
* @param int $quizid the id of the quiz to load the data for.
|
||||
* @param \context_module $quizcontext the context of this quiz.
|
||||
* @param int|null $slotid optional, if passed only load the data for this one slot (if it is in this quiz).
|
||||
* @return array indexed by slot, with information about the content of each slot.
|
||||
*/
|
||||
public static function get_question_structure(int $quizid, \context_module $quizcontext,
|
||||
int $slotid = null): array {
|
||||
global $DB;
|
||||
|
||||
$params = [
|
||||
'itemid' => $slotid,
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot'
|
||||
];
|
||||
return $DB->record_exists('question_set_references', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version options for the question.
|
||||
*
|
||||
* @param int $questionid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_version_options($questionid): array {
|
||||
global $DB;
|
||||
$sql = "SELECT qv.id AS versionid, qv.version, qv.questionid
|
||||
FROM {question_versions} qv
|
||||
WHERE qv.questionbankentryid = (SELECT DISTINCT qbe.id
|
||||
FROM {question_bank_entries} qbe
|
||||
JOIN {question_versions} qv ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question} q ON qv.questionid = q.id
|
||||
WHERE q.id = ?)
|
||||
ORDER BY qv.version DESC";
|
||||
|
||||
return $DB->get_records_sql($sql, [$questionid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the elements of an array according to a key.
|
||||
*
|
||||
* @param array $arrays
|
||||
* @param string $on
|
||||
* @param int $order
|
||||
* @return array
|
||||
*/
|
||||
public static function question_array_sort($arrays, $on, $order = SORT_ASC): array {
|
||||
$element = [];
|
||||
foreach ($arrays as $array) {
|
||||
$element[$array->$on] = $array;
|
||||
}
|
||||
ksort($element, $order);
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question id from slot id.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_question_for_redo($slotid) {
|
||||
global $DB;
|
||||
$params = [
|
||||
'itemid' => $slotid,
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot'
|
||||
'draft' => question_version_status::QUESTION_STATUS_DRAFT,
|
||||
'quizcontextid' => $quizcontext->id,
|
||||
'quizcontextid2' => $quizcontext->id,
|
||||
'quizcontextid3' => $quizcontext->id,
|
||||
'quizid' => $quizid,
|
||||
'quizid2' => $quizid,
|
||||
];
|
||||
$referencerecord = $DB->get_record('question_references', $params);
|
||||
if ($referencerecord->version === null) {
|
||||
$questionsql = 'SELECT q.id
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
WHERE qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qv.questionbankentryid)
|
||||
AND qv.questionbankentryid = ?';
|
||||
$questionid = $DB->get_record_sql($questionsql, [$referencerecord->questionbankentryid])->id;
|
||||
} else {
|
||||
$questionid = $DB->get_field('question_versions', 'questionid',
|
||||
['questionbankentryid' => $referencerecord->questionbankentryid,
|
||||
'version' => $referencerecord->version]);
|
||||
$slotidtest = '';
|
||||
$slotidtest2 = '';
|
||||
if ($slotid !== null) {
|
||||
$params['slotid'] = $slotid;
|
||||
$params['slotid2'] = $slotid;
|
||||
$slotidtest = ' AND slot.id = :slotid';
|
||||
$slotidtest2 = ' AND lslot.id = :slotid2';
|
||||
}
|
||||
return $questionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random question object from the slot id.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @return false|mixed|\stdClass
|
||||
*/
|
||||
public static function get_random_question_data_from_slot($slotid) {
|
||||
global $DB;
|
||||
$params = [
|
||||
'itemid' => $slotid,
|
||||
'component' => 'mod_quiz',
|
||||
'questionarea' => 'slot'
|
||||
];
|
||||
return $DB->get_record('question_set_references', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question ids for specific question version.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_specific_version_question_ids($quizid) {
|
||||
global $DB;
|
||||
$questionids = [];
|
||||
$sql = 'SELECT qv.questionid
|
||||
FROM {quiz_slots} qs
|
||||
JOIN {question_references} qr ON qr.itemid = qs.id
|
||||
JOIN {question_versions} qv ON qv.questionbankentryid = qr.questionbankentryid
|
||||
AND qv.version = qr.version
|
||||
WHERE qr.version IS NOT NULL
|
||||
AND qs.quizid = ?
|
||||
AND qr.component = ?
|
||||
AND qr.questionarea = ?';
|
||||
$questions = $DB->get_records_sql($sql, [$quizid, 'mod_quiz', 'slot']);
|
||||
foreach ($questions as $question) {
|
||||
$questionids [] = $question->questionid;
|
||||
}
|
||||
return $questionids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question ids for always latest options.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_always_latest_version_question_ids($quizid) {
|
||||
global $DB;
|
||||
$questionids = [];
|
||||
$sql = 'SELECT qr.questionbankentryid as entry
|
||||
FROM {quiz_slots} qs
|
||||
JOIN {question_references} qr ON qr.itemid = qs.id
|
||||
WHERE qr.version IS NULL
|
||||
AND qs.quizid = ?
|
||||
AND qr.component = ?
|
||||
AND qr.questionarea = ?';
|
||||
$entryids = $DB->get_records_sql($sql, [$quizid, 'mod_quiz', 'slot']);
|
||||
$questionentries = [];
|
||||
foreach ($entryids as $entryid) {
|
||||
$questionentries [] = $entryid->entry;
|
||||
}
|
||||
if (empty($questionentries)) {
|
||||
return $questionids;
|
||||
}
|
||||
list($questionidcondition, $params) = $DB->get_in_or_equal($questionentries);
|
||||
$extracondition = 'AND qv.questionbankentryid ' . $questionidcondition;
|
||||
$questionsql = "SELECT q.id
|
||||
FROM {question} q
|
||||
JOIN {question_versions} qv ON qv.questionid = q.id
|
||||
WHERE qv.version = (SELECT MAX(v.version)
|
||||
FROM {question_versions} v
|
||||
JOIN {question_bank_entries} be
|
||||
ON be.id = v.questionbankentryid
|
||||
WHERE be.id = qv.questionbankentryid)
|
||||
$extracondition";
|
||||
$questions = $DB->get_records_sql($questionsql, $params);
|
||||
foreach ($questions as $question) {
|
||||
$questionids [] = $question->id;
|
||||
}
|
||||
return $questionids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the question structure data for the given quiz or question ids.
|
||||
*
|
||||
* @param null $quizid
|
||||
* @param array $questionids
|
||||
* @param bool $attempt
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_structure_data($quizid, $questionids = [], $attempt = false) {
|
||||
global $DB;
|
||||
$params = ['quizid' => $quizid];
|
||||
$condition = '';
|
||||
$joinon = 'AND qr.version = qv.version';
|
||||
if (!empty($questionids)) {
|
||||
list($condition, $param) = $DB->get_in_or_equal($questionids, SQL_PARAMS_NAMED, 'questionid');
|
||||
$condition = 'AND q.id ' . $condition;
|
||||
$joinon = '';
|
||||
$params = array_merge($params, $param);
|
||||
}
|
||||
if ($attempt) {
|
||||
$selectstart = 'q.*, slot.id AS slotid, slot.slot,';
|
||||
} else {
|
||||
$selectstart = 'slot.slot, slot.id AS slotid, q.*,';
|
||||
}
|
||||
$sql = "SELECT $selectstart
|
||||
q.id AS questionid,
|
||||
q.name,
|
||||
q.qtype,
|
||||
q.length,
|
||||
// Load all the data about each slot.
|
||||
$slotdata = $DB->get_records_sql("
|
||||
SELECT slot.slot,
|
||||
slot.id AS slotid,
|
||||
slot.page,
|
||||
slot.maxmark,
|
||||
slot.requireprevious,
|
||||
qc.id as category,
|
||||
qc.contextid,qv.status,
|
||||
qv.id as versionid,
|
||||
qsr.filtercondition,
|
||||
qv.status,
|
||||
qv.id AS versionid,
|
||||
qv.version,
|
||||
qv.questionbankentryid
|
||||
qr.version AS requestedversion,
|
||||
qv.questionbankentryid,
|
||||
q.id AS questionid,
|
||||
q.*,
|
||||
qc.id AS category,
|
||||
COALESCE(qc.contextid, qsr.questionscontextid) AS contextid
|
||||
|
||||
FROM {quiz_slots} slot
|
||||
LEFT JOIN {question_references} qr ON qr.itemid = slot.id AND qr.component = 'mod_quiz' AND qr.questionarea = 'slot'
|
||||
|
||||
-- case where a particular question has been added to the quiz.
|
||||
LEFT JOIN {question_references} qr ON qr.usingcontextid = :quizcontextid AND qr.component = 'mod_quiz'
|
||||
AND qr.questionarea = 'slot' AND qr.itemid = slot.id
|
||||
LEFT JOIN {question_bank_entries} qbe ON qbe.id = qr.questionbankentryid
|
||||
LEFT JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id $joinon
|
||||
|
||||
-- This way of getting the latest version for each slot is a bit more complicated
|
||||
-- than we would like, but the simpler SQL did not work in Oracle 11.2.
|
||||
-- (It did work find in Oracle 19.x, so once we have updated our min supported
|
||||
-- version we could consider digging the old code out of git history from
|
||||
-- just before the commit that added this comment.
|
||||
-- For relevant question_bank_entries, this gets the latest non-draft slot number.
|
||||
LEFT JOIN (
|
||||
SELECT lv.questionbankentryid, MAX(lv.version) AS version
|
||||
FROM {quiz_slots} lslot
|
||||
JOIN {question_references} lqr ON lqr.usingcontextid = :quizcontextid2 AND lqr.component = 'mod_quiz'
|
||||
AND lqr.questionarea = 'slot' AND lqr.itemid = lslot.id
|
||||
JOIN {question_versions} lv ON lv.questionbankentryid = lqr.questionbankentryid
|
||||
WHERE lslot.quizid = :quizid2
|
||||
$slotidtest2
|
||||
AND lqr.version IS NULL
|
||||
AND lv.status <> :draft
|
||||
GROUP BY lv.questionbankentryid
|
||||
) latestversions ON latestversions.questionbankentryid = qr.questionbankentryid
|
||||
|
||||
LEFT JOIN {question_versions} qv ON qv.questionbankentryid = qbe.id
|
||||
-- Either specified version, or latest ready version.
|
||||
AND qv.version = COALESCE(qr.version, latestversions.version)
|
||||
LEFT JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
|
||||
LEFT JOIN {question} q ON q.id = qv.questionid
|
||||
|
||||
-- Case where a random question has been added.
|
||||
LEFT JOIN {question_set_references} qsr ON qsr.usingcontextid = :quizcontextid3 AND qsr.component = 'mod_quiz'
|
||||
AND qsr.questionarea = 'slot' AND qsr.itemid = slot.id
|
||||
|
||||
WHERE slot.quizid = :quizid
|
||||
$condition";
|
||||
$questiondatas = $DB->get_records_sql($sql, $params);
|
||||
foreach ($questiondatas as $questiondata) {
|
||||
$questiondata->_partiallyloaded = true;
|
||||
}
|
||||
if (!empty($questiondatas)) {
|
||||
return $questiondatas;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
$slotidtest
|
||||
|
||||
/**
|
||||
* Get question structure.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_structure($quizid) {
|
||||
$firstslotsets = self::get_question_structure_data($quizid);
|
||||
$latestquestionids = self::get_always_latest_version_question_ids($quizid);
|
||||
$secondslotsets = self::get_question_structure_data($quizid, $latestquestionids);
|
||||
foreach ($firstslotsets as $key => $firstslotset) {
|
||||
foreach ($secondslotsets as $secondslotset) {
|
||||
if ($firstslotset->slotid === $secondslotset->slotid) {
|
||||
unset($firstslotsets[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
ORDER BY slot.slot
|
||||
", $params);
|
||||
|
||||
return self::question_array_sort(array_merge($firstslotsets, $secondslotsets), 'slot');
|
||||
}
|
||||
// Unpack the random info from question_set_reference.
|
||||
foreach ($slotdata as $slot) {
|
||||
// Ensure the right id is the id.
|
||||
$slot->id = $slot->slotid;
|
||||
|
||||
/**
|
||||
* Load random questions.
|
||||
*
|
||||
* @param int $quizid
|
||||
* @param array $questiondata
|
||||
* @return array
|
||||
*/
|
||||
public static function question_load_random_questions($quizid, $questiondata) {
|
||||
global $DB, $USER;
|
||||
$sql = 'SELECT slot.id AS slotid,
|
||||
slot.maxmark,
|
||||
slot.slot,
|
||||
slot.page,
|
||||
qsr.filtercondition
|
||||
FROM {question_set_references} qsr
|
||||
JOIN {quiz_slots} slot ON slot.id = qsr.itemid
|
||||
WHERE slot.quizid = ?
|
||||
AND qsr.component = ?
|
||||
AND qsr.questionarea = ?';
|
||||
$randomquestiondatas = $DB->get_records_sql($sql, [$quizid, 'mod_quiz', 'slot']);
|
||||
|
||||
$randomquestions = [];
|
||||
// Questions already added.
|
||||
$usedquestionids = [];
|
||||
foreach ($questiondata as $question) {
|
||||
if (isset($usedquestions[$question->id])) {
|
||||
$usedquestionids[$question->id] += 1;
|
||||
if ($slot->filtercondition) {
|
||||
// Unpack the information about a random question.
|
||||
$filtercondition = json_decode($slot->filtercondition);
|
||||
$slot->questionid = 's' . $slot->id; // Sometimes this is used as an array key, so needs to be unique.
|
||||
$slot->category = $filtercondition->questioncategoryid;
|
||||
$slot->randomrecurse = (bool) $filtercondition->includingsubcategories;
|
||||
$slot->randomtags = isset($filtercondition->tags) ? (array) $filtercondition->tags : [];
|
||||
$slot->qtype = 'random';
|
||||
$slot->name = get_string('random', 'quiz');
|
||||
$slot->length = 1;
|
||||
} else if ($slot->qtype === null) {
|
||||
// This question must have gone missing. Put in a placeholder.
|
||||
$slot->questionid = 's' . $slot->id; // Sometimes this is used as an array key, so needs to be unique.
|
||||
$slot->category = 0;
|
||||
$slot->qtype = 'missingtype';
|
||||
$slot->name = get_string('missingquestion', 'quiz');
|
||||
$slot->maxmark = 0;
|
||||
$slot->questiontext = ' ';
|
||||
$slot->questiontextformat = FORMAT_HTML;
|
||||
$slot->length = 1;
|
||||
} else if (!\question_bank::qtype_exists($slot->qtype)) {
|
||||
// Question of unknown type found in the database. Set to placeholder question types instead.
|
||||
$slot->qtype = 'missingtype';
|
||||
} else {
|
||||
$usedquestionids[$question->id] = 1;
|
||||
$slot->_partiallyloaded = 1;
|
||||
}
|
||||
}
|
||||
// Usages for this user's previous quiz attempts.
|
||||
$qubaids = new \mod_quiz\question\qubaids_for_users_attempts($quizid, $USER->id);
|
||||
$randomloader = new \core_question\local\bank\random_question_loader($qubaids, $usedquestionids);
|
||||
|
||||
foreach ($randomquestiondatas as $randomquestiondata) {
|
||||
$filtercondition = json_decode($randomquestiondata->filtercondition);
|
||||
$tagids = [];
|
||||
if (isset($filtercondition->tags)) {
|
||||
foreach ($filtercondition->tags as $tag) {
|
||||
$tagstring = explode(',', $tag);
|
||||
$tagids [] = $tagstring[0];
|
||||
}
|
||||
}
|
||||
$randomquestiondata->randomfromcategory = $filtercondition->questioncategoryid;
|
||||
$randomquestiondata->randomincludingsubcategories = $filtercondition->includingsubcategories;
|
||||
$randomquestiondata->questionid = $randomloader->get_next_question_id($randomquestiondata->randomfromcategory,
|
||||
$randomquestiondata->randomincludingsubcategories, $tagids);
|
||||
$randomquestions [] = $randomquestiondata;
|
||||
}
|
||||
|
||||
foreach ($randomquestions as $randomquestion) {
|
||||
// Should not add if there is no question found from the ramdom question loader, maybe empty category.
|
||||
if ($randomquestion->questionid === null) {
|
||||
continue;
|
||||
}
|
||||
$question = new \stdClass();
|
||||
$question->slotid = $randomquestion->slotid;
|
||||
$question->maxmark = $randomquestion->maxmark;
|
||||
$question->slot = $randomquestion->slot;
|
||||
$question->page = $randomquestion->page;
|
||||
$qdatas = question_preload_questions($randomquestion->questionid);
|
||||
$qdatas = reset($qdatas);
|
||||
foreach ($qdatas as $key => $qdata) {
|
||||
$question->$key = $qdata;
|
||||
}
|
||||
$questiondata[$question->id] = $question;
|
||||
}
|
||||
|
||||
return $questiondata;
|
||||
return $slotdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose question for redo.
|
||||
* Get this list of random selection tag ids from one of the slots returned by get_question_structure.
|
||||
*
|
||||
* @param int $slotid
|
||||
* @param \qubaid_condition $qubaids
|
||||
* @return int
|
||||
* @param \stdClass $slotdata one of the array elements returend by get_question_structure.
|
||||
* @return array list of tag ids.
|
||||
*/
|
||||
public static function choose_question_for_redo($slotid, $qubaids): int {
|
||||
// Choose the replacement question.
|
||||
if (!self::is_random($slotid)) {
|
||||
$newqusetionid = self::get_question_for_redo($slotid);
|
||||
} else {
|
||||
$tagids = [];
|
||||
$randomquestiondata = self::get_random_question_data_from_slot($slotid);
|
||||
$filtercondition = json_decode($randomquestiondata->filtercondition);
|
||||
if (isset($filtercondition->tags)) {
|
||||
foreach ($filtercondition->tags as $tag) {
|
||||
$tagstring = explode(',', $tag);
|
||||
$tagids [] = $tagstring[0];
|
||||
}
|
||||
}
|
||||
public static function get_tag_ids_for_slot(\stdClass $slotdata): array {
|
||||
if (empty($slot->randomtags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$randomloader = new \core_question\local\bank\random_question_loader($qubaids, []);
|
||||
$newqusetionid = $randomloader->get_next_question_id($filtercondition->questioncategoryid,
|
||||
(bool) $filtercondition->includingsubcategories, $tagids);
|
||||
if ($newqusetionid === null) {
|
||||
throw new \moodle_exception('notenoughrandomquestions', 'quiz');
|
||||
}
|
||||
$tagids = [];
|
||||
foreach ($slotdata->randomtags as $tag) {
|
||||
$tagids[] = $tag->id;
|
||||
}
|
||||
return $tagids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose question for redo in a particular slot.
|
||||
*
|
||||
* @param int $quizid the id of the quiz to load the data for.
|
||||
* @param \context_module $quizcontext the context of this quiz.
|
||||
* @param int $slotid optional, if passed only load the data for this one slot (if it is in this quiz).
|
||||
* @param qubaid_condition $qubaids attempts to consider when avoiding picking repeats of random questions.
|
||||
* @return int the id of the question to use.
|
||||
*/
|
||||
public static function choose_question_for_redo(int $quizid, \context_module $quizcontext,
|
||||
int $slotid, qubaid_condition $qubaids): int {
|
||||
$slotdata = self::get_question_structure($quizid, $quizcontext, $slotid);
|
||||
$slotdata = reset($slotdata);
|
||||
|
||||
// Non-random question.
|
||||
if ($slotdata->qtype != 'random') {
|
||||
return $slotdata->questionid;
|
||||
}
|
||||
|
||||
// Random question.
|
||||
$randomloader = new random_question_loader($qubaids, []);
|
||||
$newqusetionid = $randomloader->get_next_question_id($slotdata->category,
|
||||
$slotdata->randomrecurse, self::get_tag_ids_for_slot($slotdata));
|
||||
|
||||
if ($newqusetionid === null) {
|
||||
throw new \moodle_exception('notenoughrandomquestions', 'quiz');
|
||||
}
|
||||
return $newqusetionid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version information for a question to show in the version selection dropdown.
|
||||
*
|
||||
* @param int $questionid
|
||||
* @param int $slotid
|
||||
* @return array
|
||||
*/
|
||||
public static function get_question_version_info($questionid, $slotid): array {
|
||||
global $DB;
|
||||
$versiondata = [];
|
||||
$versionsoptions = self::get_version_options($questionid);
|
||||
$latestversion = reset($versionsoptions);
|
||||
// Object for using the latest version.
|
||||
$alwaysuselatest = new \stdClass();
|
||||
$alwaysuselatest->versionid = 0;
|
||||
$alwaysuselatest->version = 0;
|
||||
$alwaysuselatest->versionvalue = get_string('alwayslatest', 'quiz');
|
||||
array_unshift($versionsoptions, $alwaysuselatest);
|
||||
$referencedata = $DB->get_record('question_references',
|
||||
['itemid' => $slotid, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
|
||||
if (!isset($referencedata->version) || ($referencedata->version === null)) {
|
||||
$currentversion = 0;
|
||||
} else {
|
||||
$currentversion = $referencedata->version;
|
||||
}
|
||||
|
||||
foreach ($versionsoptions as $versionsoption) {
|
||||
$versionsoption->selected = false;
|
||||
if ($versionsoption->version === $currentversion) {
|
||||
$versionsoption->selected = true;
|
||||
}
|
||||
if (!isset($versionsoption->versionvalue)) {
|
||||
if ($versionsoption->version === $latestversion->version) {
|
||||
$versionsoption->versionvalue = get_string('questionversionlatest', 'quiz', $versionsoption->version);
|
||||
} else {
|
||||
$versionsoption->versionvalue = get_string('questionversion', 'quiz', $versionsoption->version);
|
||||
}
|
||||
}
|
||||
|
||||
$versiondata[] = $versionsoption;
|
||||
}
|
||||
return $versiondata;
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ class structure {
|
||||
public static function create_for_quiz($quizobj) {
|
||||
$structure = self::create();
|
||||
$structure->quizobj = $quizobj;
|
||||
$structure->populate_structure($quizobj->get_quiz());
|
||||
$structure->populate_structure();
|
||||
return $structure;
|
||||
}
|
||||
|
||||
@ -612,83 +612,28 @@ class structure {
|
||||
|
||||
/**
|
||||
* Set up this class with the structure for a given quiz.
|
||||
* @param \stdClass $quiz the quiz settings.
|
||||
*/
|
||||
public function populate_structure($quiz) {
|
||||
protected function populate_structure() {
|
||||
global $DB;
|
||||
|
||||
$slots = qbank_helper::get_question_structure($quiz->id);
|
||||
|
||||
$slots = $this->populate_missing_questions($slots);
|
||||
$slots = qbank_helper::get_question_structure($this->quizobj->get_quizid(), $this->quizobj->get_context());
|
||||
|
||||
$this->questions = [];
|
||||
$this->slotsinorder = [];
|
||||
foreach ($slots as $slotdata) {
|
||||
$this->questions[$slotdata->questionid] = $slotdata;
|
||||
|
||||
$slot = new \stdClass();
|
||||
$slot->id = $slotdata->slotid;
|
||||
$slot->name = $slotdata->name;
|
||||
$slot->slot = $slotdata->slot;
|
||||
$slot->quizid = $quiz->id;
|
||||
$slot->page = $slotdata->page;
|
||||
$slot->questionid = $slotdata->questionid;
|
||||
$slot->maxmark = $slotdata->maxmark;
|
||||
$slot->requireprevious = $slotdata->requireprevious;
|
||||
$slot->qtype = $slotdata->qtype;
|
||||
$slot->length = $slotdata->length;
|
||||
$slot->category = $slotdata->category;
|
||||
$slot->questionbankentryid = $slotdata->questionbankentryid ?? null;
|
||||
$slot->version = $slotdata->version ?? null;
|
||||
|
||||
$slot = clone($slotdata);
|
||||
$slot->quizid = $this->quizobj->get_quizid();
|
||||
$this->slotsinorder[$slot->slot] = $slot;
|
||||
}
|
||||
|
||||
// Get quiz sections in ascending order of the firstslot.
|
||||
$this->sections = $DB->get_records('quiz_sections', array('quizid' => $quiz->id), 'firstslot ASC');
|
||||
$this->sections = $DB->get_records('quiz_sections', ['quizid' => $this->quizobj->get_quizid()], 'firstslot');
|
||||
$this->populate_slots_with_sections();
|
||||
$this->populate_question_numbers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by populate. Make up fake data for any missing questions.
|
||||
* @param \stdClass[] $slots the data about the slots and questions in the quiz.
|
||||
* @return \stdClass[] updated $slots array.
|
||||
*/
|
||||
protected function populate_missing_questions($slots) {
|
||||
global $DB;
|
||||
// Address missing/random question types.
|
||||
foreach ($slots as $slot) {
|
||||
if ($slot->qtype === null) {
|
||||
// Check if the question is random.
|
||||
if ($setreference = $DB->get_record('question_set_references',
|
||||
['itemid' => $slot->slotid, 'component' => 'mod_quiz', 'questionarea' => 'slot'])) {
|
||||
$filtercondition = json_decode($setreference->filtercondition);
|
||||
$slot->id = $slot->slotid;
|
||||
$slot->category = $filtercondition->questioncategoryid;
|
||||
$slot->qtype = 'random';
|
||||
$slot->name = get_string('random', 'quiz');
|
||||
$slot->length = 1;
|
||||
} else {
|
||||
// If the questiontype is missing change the question type.
|
||||
$slot->id = $slot->questionid;
|
||||
$slot->category = 0;
|
||||
$slot->qtype = 'missingtype';
|
||||
$slot->name = get_string('missingquestion', 'quiz');
|
||||
$slot->maxmark = 0;
|
||||
$slot->requireprevious = 0;
|
||||
$slot->questiontext = ' ';
|
||||
$slot->questiontextformat = FORMAT_HTML;
|
||||
$slot->length = 1;
|
||||
}
|
||||
} else if (!\question_bank::qtype_exists($slot->qtype)) {
|
||||
$slot->qtype = 'missingtype';
|
||||
}
|
||||
}
|
||||
|
||||
return $slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in the section ids for each slot.
|
||||
*/
|
||||
@ -721,6 +666,45 @@ class structure {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version options to show on the Questions page for a particular question.
|
||||
*
|
||||
* @param int $slotnumber which slot to get the choices for.
|
||||
* @return \stdClass[] other versions of this question. Each object has fields versionid,
|
||||
* version and selected. Array is returned most recent version first.
|
||||
*/
|
||||
public function get_version_choices_for_slot(int $slotnumber): array {
|
||||
$slot = $this->get_slot_by_number($slotnumber);
|
||||
|
||||
// Get all the versions which exist.
|
||||
$versions = qbank_helper::get_version_options($slot->questionid);
|
||||
$latestversion = reset($versions);
|
||||
|
||||
// Format the choices for display.
|
||||
$versionoptions = [];
|
||||
foreach ($versions as $version) {
|
||||
$version->selected = $version->version === $slot->requestedversion;
|
||||
|
||||
if ($version->version === $latestversion->version) {
|
||||
$version->versionvalue = get_string('questionversionlatest', 'quiz', $version->version);
|
||||
} else {
|
||||
$version->versionvalue = get_string('questionversion', 'quiz', $version->version);
|
||||
}
|
||||
|
||||
$versionoptions[] = $version;
|
||||
}
|
||||
|
||||
// Make a choice for 'Always latest'.
|
||||
$alwaysuselatest = new \stdClass();
|
||||
$alwaysuselatest->versionid = 0;
|
||||
$alwaysuselatest->version = 0;
|
||||
$alwaysuselatest->versionvalue = get_string('alwayslatest', 'quiz');
|
||||
$alwaysuselatest->selected = $slot->requestedversion === null;
|
||||
array_unshift($versionoptions, $alwaysuselatest);
|
||||
|
||||
return $versionoptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a slot from its current location to a new location.
|
||||
*
|
||||
|
@ -28,7 +28,6 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/mod/quiz/lib.php');
|
||||
@ -40,6 +39,7 @@ require_once($CFG->libdir . '/completionlib.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
require_once($CFG->libdir . '/questionlib.php');
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
/**
|
||||
* @var int We show the countdown timer if there is less than this amount of time left before the
|
||||
@ -81,7 +81,7 @@ define('QUIZ_SHOWIMAGE_LARGE', 2);
|
||||
*
|
||||
* @param object $quizobj the quiz object to create an attempt for.
|
||||
* @param int $attemptnumber the sequence number for the attempt.
|
||||
* @param object $lastattempt the previous attempt by this user, if any. Only needed
|
||||
* @param stdClass|null $lastattempt the previous attempt by this user, if any. Only needed
|
||||
* if $attemptnumber > 1 and $quiz->attemptonlast is true.
|
||||
* @param int $timenow the time the attempt was started at.
|
||||
* @param bool $ispreview whether this new attempt is a preview.
|
||||
@ -171,12 +171,10 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
|
||||
|
||||
// First load all the non-random questions.
|
||||
$randomfound = false;
|
||||
$randomtestfound = false;
|
||||
$slot = 0;
|
||||
$questions = array();
|
||||
$maxmark = array();
|
||||
$page = array();
|
||||
$questiondatarandom = [];
|
||||
foreach ($quizobj->get_questions() as $questiondata) {
|
||||
$slot += 1;
|
||||
$maxmark[$slot] = $questiondata->maxmark;
|
||||
@ -185,34 +183,55 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
|
||||
$randomfound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Intended for testing purposes only.
|
||||
foreach ($questionids as $key => $questionid) {
|
||||
if ($questionid !== (int)$questiondata->id && $slot === $key) {
|
||||
$randomtestfound = true;
|
||||
$questiondatarandom[$key] = $questiondata;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$quizobj->get_quiz()->shuffleanswers) {
|
||||
$questiondata->options->shuffleanswers = false;
|
||||
}
|
||||
$questions[$slot] = question_bank::make_question($questiondata);
|
||||
}
|
||||
|
||||
// Then find a question throw an error as something horribly wrong might have happened.
|
||||
// Then find a question to go in place of each random question.
|
||||
if ($randomfound) {
|
||||
throw new coding_exception(
|
||||
'Using "random" questions directly in an attempt is deprecated. Please use question_set_references table instead.'
|
||||
);
|
||||
}
|
||||
$slot = 0;
|
||||
$usedquestionids = array();
|
||||
foreach ($questions as $question) {
|
||||
if ($question->id && isset($usedquestions[$question->id])) {
|
||||
$usedquestionids[$question->id] += 1;
|
||||
} else {
|
||||
$usedquestionids[$question->id] = 1;
|
||||
}
|
||||
}
|
||||
$randomloader = new \core_question\local\bank\random_question_loader($qubaids, $usedquestionids);
|
||||
|
||||
foreach ($quizobj->get_questions() as $questiondata) {
|
||||
$slot += 1;
|
||||
if ($questiondata->qtype != 'random') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tagids = qbank_helper::get_tag_ids_for_slot($questiondata);
|
||||
|
||||
// Then find a question to go in place of each random question. Intended for testing purposes only.
|
||||
if ($randomtestfound) {
|
||||
foreach ($questiondatarandom as $slot => $questiondata) {
|
||||
// Deal with fixed random choices for testing.
|
||||
$questions[$slot] = question_bank::load_question($questionids[$slot], $quizobj->get_quiz()->shuffleanswers);
|
||||
if (isset($questionids[$quba->next_slot_number()])) {
|
||||
if ($randomloader->is_question_available($questiondata->category,
|
||||
(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;
|
||||
} else {
|
||||
throw new coding_exception('Forced question id not available.');
|
||||
}
|
||||
}
|
||||
|
||||
// Normal case, pick one at random.
|
||||
$questionid = $randomloader->get_next_question_id($questiondata->category,
|
||||
$questiondata->randomrecurse, $tagids);
|
||||
if ($questionid === null) {
|
||||
throw new moodle_exception('notenoughrandomquestions', 'quiz',
|
||||
$quizobj->view_url(), $questiondata);
|
||||
}
|
||||
|
||||
$questions[$slot] = question_bank::load_question($questionid,
|
||||
$quizobj->get_quiz()->shuffleanswers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ abstract class quiz_attempts_report extends quiz_default_report {
|
||||
* 3 => \core\dml\sql_join Contains joins, wheres, params for all the students to show in the report.
|
||||
* Will be the same as either element 1 or 2.
|
||||
*/
|
||||
protected function init($mode, $formclass, $quiz, $cm, $course) {
|
||||
public function init($mode, $formclass, $quiz, $cm, $course) {
|
||||
$this->mode = $mode;
|
||||
|
||||
$this->context = context_module::instance($cm->id);
|
||||
|
@ -22,6 +22,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@ -45,11 +46,9 @@ class quiz_overview_report extends quiz_attempts_report {
|
||||
protected $hasgroupstudents;
|
||||
|
||||
/**
|
||||
* @var array|null used during regrades, to cache which new questionid to use for each old on.
|
||||
* for slots which are not random, stores slot => questionid for the question to use.
|
||||
* See get_new_question_for_regrade.
|
||||
* @var array|null cached copy of qbank_helper::get_question_structure for use during regrades.
|
||||
*/
|
||||
protected $slotquestionids = null;
|
||||
protected $structureforregrade = null;
|
||||
|
||||
/**
|
||||
* @var array|null used during regrades, to cache which new questionid to use for each old on.
|
||||
@ -339,7 +338,7 @@ class quiz_overview_report extends quiz_attempts_report {
|
||||
* @param array $slots if null, regrade all questions, otherwise, just regrade
|
||||
* the questions with those slots.
|
||||
*/
|
||||
protected function regrade_attempt($attempt, $dryrun = false, $slots = null) {
|
||||
public function regrade_attempt($attempt, $dryrun = false, $slots = null) {
|
||||
global $DB;
|
||||
// Need more time for a quiz with many questions.
|
||||
core_php_time_limit::raise(300);
|
||||
@ -394,6 +393,14 @@ class quiz_overview_report extends quiz_attempts_report {
|
||||
gc_collect_cycles();
|
||||
}
|
||||
|
||||
/**
|
||||
* For use in tests only. Clear the cached regrade data.
|
||||
*/
|
||||
public function clear_regrade_date_cache(): void {
|
||||
$this->structureforregrade = null;
|
||||
$this->newquestionidsforold = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out of we should be using a new question version for a particular slot in a regrade.
|
||||
*
|
||||
@ -407,40 +414,27 @@ class quiz_overview_report extends quiz_attempts_report {
|
||||
global $DB;
|
||||
|
||||
// If the cache is empty, get information about all the slots.
|
||||
if ($this->slotquestionids === null) {
|
||||
if ($this->structureforregrade === null) {
|
||||
$this->newquestionidsforold = [];
|
||||
// Load the data about all the non-random slots now.
|
||||
$this->slotquestionids = $DB->get_records_sql_menu("
|
||||
SELECT slot.slot, qve.questionid
|
||||
FROM {quiz_slots} slot
|
||||
JOIN {question_references} qref ON qref.itemid = slot.id
|
||||
AND qref.component = 'mod_quiz' AND qref.questionarea = 'slot' AND qref.usingcontextid = ?
|
||||
JOIN {question_versions} qve ON qve.questionbankentryid = qref.questionbankentryid
|
||||
WHERE slot.quizid = ?
|
||||
AND (qve.version = qref.version OR
|
||||
qref.version IS NULL AND qve.version = (
|
||||
SELECT MAX(version)
|
||||
FROM {question_versions} iqve
|
||||
WHERE iqve.questionbankentryid = qref.questionbankentryid
|
||||
)
|
||||
)
|
||||
", [$this->context->id, $attempt->quiz]);
|
||||
$this->structureforregrade = qbank_helper::get_question_structure(
|
||||
$attempt->quiz, $this->context);
|
||||
}
|
||||
|
||||
// If this is a non-random slot, we will have the right info cached.
|
||||
if (array_key_exists($slot, $this->slotquestionids)) {
|
||||
if ($this->structureforregrade[$slot]->qtype != 'random') {
|
||||
// This is a non-random slot.
|
||||
return question_bank::load_question($this->slotquestionids[$slot]);
|
||||
return question_bank::load_question($this->structureforregrade[$slot]->questionid);
|
||||
}
|
||||
|
||||
// We must be dealing with a random question. Check that cache.
|
||||
$currentquestion = $quba->get_question_attempt($slot)->get_question(false);
|
||||
if (array_key_exists($currentquestion->id, $this->newquestionidsforold)) {
|
||||
if (isset($this->newquestionidsforold[$currentquestion->id])) {
|
||||
return question_bank::load_question($this->newquestionidsforold[$currentquestion->id]);
|
||||
}
|
||||
|
||||
// This is a random question we have not seen yet. Find the latest version.
|
||||
$versionsoptions = \mod_quiz\question\bank\qbank_helper::get_version_options($currentquestion->id);
|
||||
$versionsoptions = qbank_helper::get_version_options($currentquestion->id);
|
||||
$latestversion = reset($versionsoptions);
|
||||
$this->newquestionidsforold[$currentquestion->id] = $latestversion->questionid;
|
||||
return question_bank::load_question($latestversion->questionid);
|
||||
|
@ -22,8 +22,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_question\local\bank\question_version_status;
|
||||
use mod_quiz\external\submit_question_version;
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@ -32,6 +32,7 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/default.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/overview/report.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_form.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/report/overview/tests/helpers.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
|
||||
|
||||
@ -41,7 +42,6 @@ require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.ph
|
||||
*
|
||||
* @copyright 2014 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \question_usage_by_activity
|
||||
*/
|
||||
class quiz_overview_report_testcase extends advanced_testcase {
|
||||
use \quiz_question_helper_test_trait;
|
||||
@ -51,19 +51,19 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
*
|
||||
* @return array the data for the test sub-cases.
|
||||
*/
|
||||
public function report_sql_cases() {
|
||||
public function report_sql_cases(): array {
|
||||
return [[null], ['csv']]; // Only need to test on or off, not all download types.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test how the report queries the database.
|
||||
*
|
||||
* @param bool $isdownloading a download type, or null.
|
||||
* @param string|null $isdownloading a download type, or null.
|
||||
* @dataProvider report_sql_cases
|
||||
*/
|
||||
public function test_report_sql($isdownloading) {
|
||||
public function test_report_sql(?string $isdownloading): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest(true);
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a course and a quiz.
|
||||
$generator = $this->getDataGenerator();
|
||||
@ -74,6 +74,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
'attempts' => 10));
|
||||
|
||||
// Add one question.
|
||||
/** @var core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$q = $questiongenerator->create_question('essay', 'plain', ['category' => $cat->id]);
|
||||
@ -244,7 +245,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
* Bands provider.
|
||||
* @return array
|
||||
*/
|
||||
public function get_bands_count_and_width_provider() {
|
||||
public function get_bands_count_and_width_provider(): array {
|
||||
return [
|
||||
[10, [20, .5]],
|
||||
[20, [20, 1]],
|
||||
@ -263,8 +264,8 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
* @param int $grade grade
|
||||
* @param array $expected
|
||||
*/
|
||||
public function test_get_bands_count_and_width($grade, $expected) {
|
||||
$this->resetAfterTest(true);
|
||||
public function test_get_bands_count_and_width(int $grade, array $expected): void {
|
||||
$this->resetAfterTest();
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$quiz = $quizgenerator->create_instance(['course' => SITEID, 'grade' => $grade]);
|
||||
$this->assertEquals($expected, quiz_overview_report::get_bands_count_and_width($quiz));
|
||||
@ -273,8 +274,8 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Test delete_selected_attempts function.
|
||||
*/
|
||||
public function test_delete_selected_attempts() {
|
||||
$this->resetAfterTest(true);
|
||||
public function test_delete_selected_attempts(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$timestamp = 1234567890;
|
||||
$timestart = $timestamp + 3600;
|
||||
@ -292,6 +293,7 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
]);
|
||||
|
||||
// Add one question.
|
||||
/** @var core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$q = $questiongenerator->create_question('essay', 'plain', ['category' => $cat->id]);
|
||||
@ -326,53 +328,80 @@ class quiz_overview_report_testcase extends advanced_testcase {
|
||||
* @covers ::regrade_question
|
||||
*/
|
||||
public function test_regrade_question() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$quiz = $this->create_test_quiz($course);
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $course->id)->id);
|
||||
$cm = get_fast_modinfo($course->id)->get_cm($quiz->cmid);
|
||||
$context = context_module::instance($quiz->cmid);
|
||||
|
||||
/** @var core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
// Create a couple of questions.
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
|
||||
$numq = $questiongenerator->create_question('essay', null,
|
||||
['category' => $cat->id, 'name' => 'This is the first version', 'correctanswer' => false]);
|
||||
// Create two version.
|
||||
$numq2 = $questiongenerator->update_question($numq, null,
|
||||
['name' => 'This is the second version', 'correctanswer' => true]);
|
||||
$numq3 = $questiongenerator->update_question($numq, null,
|
||||
['name' => 'This is the third version', 'correctanswer' => false]);
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
// Create the quiz object.
|
||||
$quizobj = \quiz::create($quiz->id);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$slots = $structure->get_slots();
|
||||
$slot = reset($slots);
|
||||
// Now change the version using the external service.
|
||||
$versions = qbank_helper::get_version_options($slot->questionid);
|
||||
// We dont want the current version.
|
||||
$selectversions = [];
|
||||
foreach ($versions as $version) {
|
||||
if ($version->version === $slot->version) {
|
||||
continue;
|
||||
}
|
||||
$selectversions[$version->version] = $version;
|
||||
}
|
||||
// Change to version 1, with correct response.
|
||||
$this->expectException('moodle_exception');
|
||||
submit_question_version::execute($slot->id, (int)$selectversions[1]->version);
|
||||
list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $student);
|
||||
$q = $questiongenerator->create_question('shortanswer', null,
|
||||
['category' => $cat->id, 'name' => 'Toad scores 0.8']);
|
||||
|
||||
// Create a version, the last one draft.
|
||||
// Sadly, update_question is a bit dodgy, so it can't handle updating the answer score.
|
||||
$q2 = $questiongenerator->update_question($q, null,
|
||||
['name' => 'Toad now scores 1.0']);
|
||||
$toadanswer = $DB->get_record_select('question_answers',
|
||||
'question = ? AND ' . $DB->sql_compare_text('answer') . ' = ?',
|
||||
[$q2->id, 'toad'], '*', MUST_EXIST);
|
||||
$DB->set_field('question_answers', 'fraction', 1, ['id' => $toadanswer->id]);
|
||||
|
||||
// Add the question to the quiz.
|
||||
quiz_add_quiz_question($q2->id, $quiz, 0, 10);
|
||||
|
||||
// Attempt the quiz, submitting response 'toad'.
|
||||
$quizobj = quiz::create($quiz->id);
|
||||
$attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$attemptobj->process_submitted_actions(time(), false, [1 => ['answer' => 'toad']]);
|
||||
$attemptobj->process_finish(time(), false);
|
||||
|
||||
// We should be using 'always latest' version, which is currently v2, so should be right.
|
||||
$this->assertEquals(10, $attemptobj->get_question_usage()->get_total_mark());
|
||||
|
||||
// Change to version 2, with wrong response.
|
||||
submit_question_version::execute($slot->id, (int)$selectversions[2]->version);
|
||||
$quba->regrade_question(1, quiz_attempt::FINISHED, null, $numq2->id);
|
||||
$this->assertEquals(0, $attemptobj->get_question_usage()->get_total_mark());
|
||||
// Now change the quiz to use fixed version 1.
|
||||
$slot = $quizobj->get_question($q2->id);
|
||||
submit_question_version::execute($slot->slotid, 1);
|
||||
|
||||
// Change to version 3, with correct response.
|
||||
submit_question_version::execute($slot->id, (int)$selectversions[3]->version);
|
||||
$quba->regrade_question(1, quiz_attempt::FINISHED, null, $numq3->id);
|
||||
$this->assertEquals(10, $attemptobj->get_question_usage()->get_total_mark());
|
||||
// Regrade.
|
||||
$report = new quiz_overview_report();
|
||||
$report->init('overview', 'quiz_overview_settings_form', $quiz, $cm, $course);
|
||||
$report->regrade_attempt($attempt);
|
||||
|
||||
// The mark should now be 8.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertEquals(8, $attemptobj->get_question_usage()->get_total_mark());
|
||||
|
||||
// Now add two more versions, the second of which is draft.
|
||||
$q3 = $questiongenerator->update_question($q, null,
|
||||
['name' => 'Toad now scores 0.5']);
|
||||
$toadanswer = $DB->get_record_select('question_answers',
|
||||
'question = ? AND ' . $DB->sql_compare_text('answer') . ' = ?',
|
||||
[$q3->id, 'toad'], '*', MUST_EXIST);
|
||||
$DB->set_field('question_answers', 'fraction', 0.5, ['id' => $toadanswer->id]);
|
||||
|
||||
$q4 = $questiongenerator->update_question($q, null,
|
||||
['name' => 'Toad now scores 0.3',
|
||||
'status' => question_version_status::QUESTION_STATUS_DRAFT]);
|
||||
$toadanswer = $DB->get_record_select('question_answers',
|
||||
'question = ? AND ' . $DB->sql_compare_text('answer') . ' = ?',
|
||||
[$q4->id, 'toad'], '*', MUST_EXIST);
|
||||
$DB->set_field('question_answers', 'fraction', 0.3, ['id' => $toadanswer->id]);
|
||||
|
||||
// Now change the quiz back to always latest and regrade again.
|
||||
submit_question_version::execute($slot->slotid, 0);
|
||||
$report->clear_regrade_date_cache();
|
||||
$report->regrade_attempt($attempt);
|
||||
|
||||
// Score should now be 5, because v3 is the latest non-draft version.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertEquals(5, $attemptobj->get_question_usage()->get_total_mark());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -114,11 +114,11 @@ function quiz_report_get_significant_questions($quiz) {
|
||||
}
|
||||
$qsbyslot[$slotreport->slot] = $slotreport;
|
||||
}
|
||||
$qsbyslot = \mod_quiz\question\bank\qbank_helper::question_array_sort($qsbyslot, 'slot');
|
||||
ksort($qsbyslot);
|
||||
$number = 1;
|
||||
foreach ($qsbyslot as $question) {
|
||||
$question->number = $number;
|
||||
$number ++;
|
||||
$number++;
|
||||
}
|
||||
return $qsbyslot;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class quiz_report_responses_from_steps_testcase extends mod_quiz_attempt_walkthrough_from_csv_testcase {
|
||||
protected function get_full_path_of_csv_file($setname, $test) {
|
||||
protected function get_full_path_of_csv_file(string $setname, string $test): string {
|
||||
// Overridden here so that __DIR__ points to the path of this file.
|
||||
return __DIR__."/fixtures/{$setname}{$test}.csv";
|
||||
}
|
||||
|
@ -856,7 +856,8 @@ class quiz_statistics_report extends quiz_default_report {
|
||||
foreach ($randomquestions as $randomquestion) {
|
||||
$questiondata[$randomquestion->slot] = $randomquestion;
|
||||
}
|
||||
return \mod_quiz\question\bank\qbank_helper::question_array_sort($questiondata, 'slot');
|
||||
ksort($questiondata);
|
||||
return $questiondata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,7 +56,7 @@ class quiz_report_statistics_from_steps_testcase extends mod_quiz_attempt_walkth
|
||||
*/
|
||||
protected $report;
|
||||
|
||||
protected function get_full_path_of_csv_file($setname, $test) {
|
||||
protected function get_full_path_of_csv_file(string $setname, string $test): string {
|
||||
// Overridden here so that __DIR__ points to the path of this file.
|
||||
return __DIR__."/fixtures/{$setname}{$test}.csv";
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
global $SITE, $DB;
|
||||
$this->setAdminUser();
|
||||
|
||||
/** @var core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$slots = array();
|
||||
$qidsbycat = array();
|
||||
@ -147,8 +148,8 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
* @param array $quizsettings Quiz overrides for this quiz.
|
||||
* @param array $csvdata Data loaded from csv files for this test.
|
||||
*/
|
||||
protected function create_quiz_simulate_attempts_and_check_results($quizsettings, $csvdata) {
|
||||
$this->resetAfterTest(true);
|
||||
protected function create_quiz_simulate_attempts_and_check_results(array $quizsettings, array $csvdata) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->create_quiz($quizsettings, $csvdata['questions']);
|
||||
|
||||
@ -166,7 +167,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
* @param string $test
|
||||
* @return string full path of file.
|
||||
*/
|
||||
protected function get_full_path_of_csv_file($setname, $test) {
|
||||
protected function get_full_path_of_csv_file(string $setname, string $test): string {
|
||||
return __DIR__."/fixtures/{$setname}{$test}.csv";
|
||||
}
|
||||
|
||||
@ -177,7 +178,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
* @param string $test
|
||||
* @return array
|
||||
*/
|
||||
protected function load_csv_data_file($setname, $test='') {
|
||||
protected function load_csv_data_file(string $setname, string $test = ''): array {
|
||||
$files = array($setname => $this->get_full_path_of_csv_file($setname, $test));
|
||||
return $this->dataset_from_files($files)->get_rows([$setname]);
|
||||
}
|
||||
@ -188,7 +189,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
* @param array $row from csv file with field names with parts separate by '.'.
|
||||
* @return array the row with each part of the field name following a '.' being a separate sub array's index.
|
||||
*/
|
||||
protected function explode_dot_separated_keys_to_make_subindexs(array $row) {
|
||||
protected function explode_dot_separated_keys_to_make_subindexs(array $row): array {
|
||||
$parts = array();
|
||||
foreach ($row as $columnkey => $value) {
|
||||
$newkeys = explode('.', trim($columnkey));
|
||||
@ -214,7 +215,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
* @return array One array element for each run of the test. Each element contains an array with the params for
|
||||
* test_walkthrough_from_csv.
|
||||
*/
|
||||
public function get_data_for_walkthrough() {
|
||||
public function get_data_for_walkthrough(): array {
|
||||
$quizzes = $this->load_csv_data_file('quizzes')['quizzes'];
|
||||
$datasets = array();
|
||||
foreach ($quizzes as $quizsettings) {
|
||||
@ -230,10 +231,10 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $steps array the step data from the csv file.
|
||||
* @param array $steps the step data from the csv file.
|
||||
* @return array attempt no as in csv file => the id of the quiz_attempt as stored in the db.
|
||||
*/
|
||||
protected function walkthrough_attempts($steps) {
|
||||
protected function walkthrough_attempts(array $steps): array {
|
||||
global $DB;
|
||||
$attemptids = array();
|
||||
foreach ($steps as $steprow) {
|
||||
@ -256,7 +257,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
$prevattempts = quiz_get_user_attempts($this->quiz->id, $user->id, 'all', true);
|
||||
$attemptnumber = count($prevattempts) + 1;
|
||||
$timenow = time();
|
||||
$attempt = quiz_create_attempt($quizobj, $attemptnumber, false, $timenow, false, $user->id);
|
||||
$attempt = quiz_create_attempt($quizobj, $attemptnumber, null, $timenow, false, $user->id);
|
||||
// Select variant and / or random sub question.
|
||||
if (!isset($step['variants'])) {
|
||||
$step['variants'] = array();
|
||||
@ -291,10 +292,10 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $results array the results data from the csv file.
|
||||
* @param $attemptids array attempt no as in csv file => the id of the quiz_attempt as stored in the db.
|
||||
* @param array $results the results data from the csv file.
|
||||
* @param array $attemptids attempt no as in csv file => the id of the quiz_attempt as stored in the db.
|
||||
*/
|
||||
protected function check_attempts_results($results, $attemptids) {
|
||||
protected function check_attempts_results(array $results, array $attemptids) {
|
||||
foreach ($results as $resultrow) {
|
||||
$result = $this->explode_dot_separated_keys_to_make_subindexs($resultrow);
|
||||
// Re-load quiz attempt data.
|
||||
@ -308,9 +309,8 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
|
||||
*
|
||||
* @param array $result row of data read from csv file.
|
||||
* @param quiz_attempt $attemptobj the attempt object loaded from db.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
protected function check_attempt_results($result, $attemptobj) {
|
||||
protected function check_attempt_results(array $result, quiz_attempt $attemptobj) {
|
||||
foreach ($result as $fieldname => $value) {
|
||||
if ($value === '!NULL!') {
|
||||
$value = null;
|
||||
|
@ -86,9 +86,6 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question());
|
||||
|
||||
$prefix1 = $quba->get_field_prefix(1);
|
||||
$prefix2 = $quba->get_field_prefix(2);
|
||||
|
||||
$tosubmit = array(1 => array('answer' => 'frog'),
|
||||
2 => array('answer' => '3.14'));
|
||||
|
||||
|
@ -6,16 +6,18 @@ Feature: Allow students to redo questions in a practice quiz, without starting a
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student | Student | One | student@example.com |
|
||||
| teacher | Teacher | One | teacher@example.com |
|
||||
| username | firstname | lastname |
|
||||
| student | Student | One |
|
||||
| teacher | Teacher | One |
|
||||
| editor | Question | Editor |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student | C1 | student |
|
||||
| teacher | C1 | teacher |
|
||||
| user | course | role |
|
||||
| student | C1 | student |
|
||||
| teacher | C1 | teacher |
|
||||
| editor | C1 | editingteacher |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
@ -41,6 +43,39 @@ Feature: Allow students to redo questions in a practice quiz, without starting a
|
||||
Then the state of "First question" question is shown as "Not complete"
|
||||
And I should see "Marked out of 2.00" in the "First question" "question"
|
||||
|
||||
@javascript
|
||||
Scenario: Start attempt, teacher edits question, redo picks up latest non-draft version
|
||||
# Start attempt as student.
|
||||
Given I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
|
||||
And I press "Attempt quiz"
|
||||
And I click on "False" "radio" in the "First question" "question"
|
||||
And I click on "Check" "button" in the "First question" "question"
|
||||
And I log out
|
||||
|
||||
# Now edit the question as teacher to add a real version and a draft version.
|
||||
# Would be nice to do this with a generator, but I don't have time right now.
|
||||
And I am on the "TF1" "core_question > edit" page logged in as "editor"
|
||||
And I set the following fields to these values:
|
||||
| Question name | TF1-v2 |
|
||||
| Question text | The new first question |
|
||||
| Correct answer | False |
|
||||
And I press "id_submitbutton"
|
||||
And I am on the "TF1-v2" "core_question > edit" page
|
||||
And I set the following fields to these values:
|
||||
| Question name | TF1-v3 |
|
||||
| Question text | This is only draft for now |
|
||||
| Correct answer | True |
|
||||
| Question status | Draft |
|
||||
And I press "id_submitbutton"
|
||||
And I log out
|
||||
|
||||
When I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
|
||||
And I press "Continue your attempt"
|
||||
And I press "Try another question like this one"
|
||||
Then the state of "The new first question" question is shown as "Not complete"
|
||||
And I should see "Marked out of 2.00" in the "The new first question" "question"
|
||||
And I should not see "This is only draft for now"
|
||||
|
||||
@javascript
|
||||
Scenario: The redo question button is visible but disabled for teachers
|
||||
Given I am on the "Quiz 1" "mod_quiz > View" page logged in as "student"
|
||||
|
@ -7,13 +7,13 @@ Feature: Quiz question versioning
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category | groupmode |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Teacher | 1 | teacher@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| user | course | role |
|
||||
| teacher | C1 | editingteacher |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
@ -21,82 +21,93 @@ Feature: Quiz question versioning
|
||||
| activity | name | course | idnumber |
|
||||
| quiz | Quiz 1 | C1 | quiz1 |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext | answer 1 |
|
||||
| Test questions | truefalse | First question | Answer the first question | True |
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
| Test questions | truefalse | Other question | Answer the first question |
|
||||
And quiz "Quiz 1" contains the following questions:
|
||||
| question | page |
|
||||
| First question | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
@javascript
|
||||
Scenario: Approriate question version should be displayed when not edited
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
|
||||
And I should see "First question"
|
||||
Scenario: Appropriate question version should be displayed when not edited
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher"
|
||||
Then I should see "First question"
|
||||
And I should see "Answer the first question"
|
||||
And I should see "v1 (latest)"
|
||||
And the field "version" matches value "Always latest"
|
||||
And "v1 (latest)" "option" should exist in the "version" "select"
|
||||
# We check that the corresponding version is the appropriate one in preview
|
||||
And I click on "Preview question" "link"
|
||||
And I switch to "questionpreview" window
|
||||
And I should see "Version 1 (latest)"
|
||||
And I should see "Answer the first question"
|
||||
And I press "Display options"
|
||||
And I set the following fields to these values:
|
||||
| id_feedback | Not shown |
|
||||
| id_generalfeedback | Not shown |
|
||||
| id_rightanswer | Shown |
|
||||
And I press "id_saveupdate"
|
||||
And I click on "finish" "button"
|
||||
And I should see "The correct answer is 'True'."
|
||||
And I click on "Submit and finish" "button"
|
||||
And I should see "You should have selected true."
|
||||
|
||||
@javascript
|
||||
Scenario: Approriate question version should be displayed when edited
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher"
|
||||
And I click on "Edit question First question" "link"
|
||||
# We edit the question with new informations to generate a second version
|
||||
And I set the following fields to these values:
|
||||
| id_name | Second question |
|
||||
| id_questiontext | This is the second question text |
|
||||
| id_correctanswer | False |
|
||||
| Question name | First question (v2) |
|
||||
| Question text | Answer the new first question |
|
||||
| Correct answer | False |
|
||||
And I press "id_submitbutton"
|
||||
And I set the field "version" to "v2"
|
||||
And I should see "Second question"
|
||||
And I should see "This is the second question text"
|
||||
And the field "version" matches value "Always latest"
|
||||
And "v1" "option" should exist in the "version" "select"
|
||||
And I set the field "version" to "v2 (latest)"
|
||||
Then I should see "First question (v2)"
|
||||
And I should see "Answer the new first question"
|
||||
And I click on "Preview question" "link"
|
||||
And I switch to "questionpreview" window
|
||||
# We check that the corresponding version is the appropriate one in preview
|
||||
# We also check that the new informations are properly displayed
|
||||
# We also check that the new information is properly displayed
|
||||
And I should see "Version 2 (latest)"
|
||||
And I should see "This is the second question text"
|
||||
And I press "Display options"
|
||||
And I should see "Answer the new first question"
|
||||
|
||||
@javascript
|
||||
Scenario: Appropriate question version displayed when later draft version exists
|
||||
# Edit the question in the question bank to add a new draft version.
|
||||
Given I am on the "First question" "core_question > edit" page logged in as teacher
|
||||
And I set the following fields to these values:
|
||||
| id_feedback | Not shown |
|
||||
| id_generalfeedback | Not shown |
|
||||
| id_rightanswer | Shown |
|
||||
And I press "id_saveupdate"
|
||||
And I click on "finish" "button"
|
||||
Then I should see "The correct answer is 'False'."
|
||||
| Question name | First question (v2) |
|
||||
| Question text | Answer the new first question |
|
||||
| Correct answer | False |
|
||||
| Question status | Draft |
|
||||
And I press "id_submitbutton"
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page
|
||||
Then I should see "First question"
|
||||
And I should see "Answer the first question"
|
||||
And the field "version" matches value "Always latest"
|
||||
And "v1 (latest)" "option" should exist in the "version" "select"
|
||||
And "v2" "option" should not exist in the "version" "select"
|
||||
And "v2 (latest)" "option" should not exist in the "version" "select"
|
||||
And I am on the "Quiz 1" "mod_quiz > View" page
|
||||
And I press "Preview quiz"
|
||||
And I should see "Answer the first question"
|
||||
|
||||
@javascript
|
||||
Scenario: Creating a new question should have always latest in the version selection
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
|
||||
And I click on "Add" "link"
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher"
|
||||
# Change the version of the existing question, to ensure it does not match later.
|
||||
And I set the field "version" to "v1 (latest)"
|
||||
And I open the "Page 1" add to quiz menu
|
||||
And I follow "a new question"
|
||||
And I set the field "item_qtype_essay" to "1"
|
||||
And I press "submitbutton"
|
||||
Then I should see "Adding an Essay question"
|
||||
And I set the field "Question name" to "Essay 01 new"
|
||||
And I set the field "Question text" to "Please write 200 words about Essay 01"
|
||||
And I set the following fields to these values:
|
||||
| Question name | New essay |
|
||||
| Question text | Write 200 words about quizzes. |
|
||||
And I press "id_submitbutton"
|
||||
And I should see "Essay 01 new" on quiz page "1"
|
||||
And I should see "Always latest" on quiz page "1"
|
||||
And I should see "New essay" on quiz page "1"
|
||||
And the field "version" in the "New essay" "list_item" matches value "Always latest"
|
||||
|
||||
@javascript
|
||||
Scenario: Adding a question from question bank should have always latest in the version selection
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
|
||||
And I click on "Add" "link"
|
||||
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher"
|
||||
And I open the "Page 1" add to quiz menu
|
||||
And I follow "from question bank"
|
||||
And I set the field with xpath "//input[@type='checkbox' and @id='qbheadercheckbox']" to "1"
|
||||
And I click on "Select" "checkbox" in the "Other question" "table_row"
|
||||
And I press "Add selected questions to the quiz"
|
||||
And I should see "First question" on quiz page "1"
|
||||
And I should see "Always latest" on quiz page "1"
|
||||
Then I should see "Other question" on quiz page "1"
|
||||
And the field "version" in the "Other question" "list_item" matches value "Always latest"
|
||||
|
2
mod/quiz/tests/external/external_test.php
vendored
2
mod/quiz/tests/external/external_test.php
vendored
@ -319,7 +319,7 @@ class external_test extends externallib_advanced_testcase {
|
||||
// We only see a limited set of fields.
|
||||
$this->assertCount(4, $result['quizzes'][0]);
|
||||
$this->assertEquals($quiz2->id, $result['quizzes'][0]['id']);
|
||||
$this->assertEquals($quiz2->coursemodule, $result['quizzes'][0]['coursemodule']);
|
||||
$this->assertEquals($quiz2->cmid, $result['quizzes'][0]['coursemodule']);
|
||||
$this->assertEquals($quiz2->course, $result['quizzes'][0]['course']);
|
||||
$this->assertEquals($quiz2->name, $result['quizzes'][0]['name']);
|
||||
$this->assertEquals($quiz2->course, $result['quizzes'][0]['course']);
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
/**
|
||||
* Class mod_quiz_local_structure_slot_random_test
|
||||
* Class for tests related to the {@link \mod_quiz\local\structure\slot_random} class.
|
||||
@ -352,7 +354,7 @@ class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
|
||||
}
|
||||
|
||||
public function test_insert() {
|
||||
global $SITE, $DB;
|
||||
global $SITE;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
@ -360,6 +362,7 @@ class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
|
||||
// Create a quiz.
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
|
||||
$quizcontext = context_module::instance($quiz->cmid);
|
||||
|
||||
// Create a question category in the system context.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
@ -400,7 +403,7 @@ class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
|
||||
$randomslotdata = new stdClass();
|
||||
$randomslotdata->quizid = $quiz->id;
|
||||
$randomslotdata->maxmark = 1;
|
||||
$randomslotdata->usingcontextid = context_module::instance($quiz->cmid)->id;
|
||||
$randomslotdata->usingcontextid = $quizcontext->id;
|
||||
$randomslotdata->questionscontextid = $category->contextid;
|
||||
|
||||
// Insert the random question to the quiz.
|
||||
@ -409,15 +412,13 @@ class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
|
||||
$randomslot->set_filter_condition($filtercondition);
|
||||
$randomslot->insert(1); // Put the question on the first page of the quiz.
|
||||
|
||||
// Get the random question's quiz_slot. It is at the first slot.
|
||||
$quizslot = $DB->get_record('quiz_slots', array('quizid' => $quiz->id, 'slot' => 1));
|
||||
// Get the random question's tags from quiz_slot_tags. It is at the first slot.
|
||||
$setreference = \mod_quiz\question\bank\qbank_helper::get_random_question_data_from_slot($quizslot->id);
|
||||
$slots = qbank_helper::get_question_structure($quiz->id, $quizcontext);
|
||||
$quizslot = reset($slots);
|
||||
|
||||
$this->assertEquals($category->id, json_decode($setreference->filtercondition)->questioncategoryid);
|
||||
$this->assertEquals(1, json_decode($setreference->filtercondition)->includingsubcategories);
|
||||
$this->assertEquals($category->id, $quizslot->category);
|
||||
$this->assertEquals(1, $quizslot->randomrecurse);
|
||||
$this->assertEquals(1, $quizslot->maxmark);
|
||||
$tagspropery = (array)json_decode($setreference->filtercondition)->tags;
|
||||
$tagspropery = $quizslot->randomtags;
|
||||
|
||||
$this->assertCount(2, $tagspropery);
|
||||
$this->assertEqualsCanonicalizing(
|
||||
|
@ -36,6 +36,11 @@ require_once(__DIR__ . '/quiz_question_helper_test_trait.php');
|
||||
class qbank_helper_test extends \advanced_testcase {
|
||||
use \quiz_question_helper_test_trait;
|
||||
|
||||
/**
|
||||
* @var \stdClass test student user.
|
||||
*/
|
||||
protected $student;
|
||||
|
||||
/**
|
||||
* Called before every test.
|
||||
*/
|
||||
@ -48,180 +53,94 @@ class qbank_helper_test extends \advanced_testcase {
|
||||
$this->user = $USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is random.
|
||||
*
|
||||
* @covers ::is_random
|
||||
* @covers ::get_random_question_data_from_slot
|
||||
*/
|
||||
public function test_is_random() {
|
||||
$this->resetAfterTest();
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$this->add_random_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
// Create the quiz object.
|
||||
$quizobj = \quiz::create($quiz->id);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$slots = $structure->get_slots();
|
||||
foreach ($slots as $slot) {
|
||||
$this->assertEquals(true, qbank_helper::is_random($slot->id));
|
||||
// Test random data for slot.
|
||||
$this->assertEquals($slot->id, qbank_helper::get_random_question_data_from_slot($slot->id)->itemid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reference records.
|
||||
*
|
||||
* @covers ::get_version_options
|
||||
* @covers ::get_question_for_redo
|
||||
* @covers ::get_always_latest_version_question_ids
|
||||
* @covers ::question_load_random_questions
|
||||
* @covers ::question_array_sort
|
||||
*/
|
||||
public function test_reference_records() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
$context = \context_module::instance($quiz->cmid);
|
||||
|
||||
// Create a couple of questions.
|
||||
/** @var \core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
|
||||
$numq = $questiongenerator->create_question('essay', null,
|
||||
['category' => $cat->id, 'name' => 'This is the first version']);
|
||||
|
||||
// Create two version.
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the second version']);
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the third version']);
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
|
||||
// Create the quiz object.
|
||||
$quizobj = \quiz::create($quiz->id);
|
||||
$quizobj->preload_questions();
|
||||
$quizobj->load_questions();
|
||||
$questions = $quizobj->get_questions();
|
||||
$question = reset($questions);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$slots = $structure->get_slots();
|
||||
$slot = reset($slots);
|
||||
$this->assertEquals(3, count(qbank_helper::get_version_options($question->id)));
|
||||
$quizobj->preload_questions();
|
||||
$quizobj->load_questions();
|
||||
$questions = $quizobj->get_questions();
|
||||
$question = reset($questions);
|
||||
$this->assertEquals($question->id, qbank_helper::get_question_for_redo($slot->id));
|
||||
$this->assertEquals($question->id, qbank_helper::choose_question_for_redo(
|
||||
$quiz->id, $context, $slot->id, new \qubaid_list([])));
|
||||
|
||||
// Create another version.
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the latest version']);
|
||||
|
||||
// Change to always latest.
|
||||
submit_question_version::execute($slot->id, 0);
|
||||
$quizobj->preload_questions();
|
||||
$quizobj->load_questions();
|
||||
$questions = $quizobj->get_questions();
|
||||
$question = reset($questions);
|
||||
$this->assertEquals($question->id, qbank_helper::get_question_for_redo($slot->id));
|
||||
// Test always latest version question ids.
|
||||
$latestquestionids = qbank_helper::get_always_latest_version_question_ids($quiz->id);
|
||||
$this->assertEquals($question->id, reset($latestquestionids));
|
||||
$this->assertEquals($question->id, qbank_helper::choose_question_for_redo(
|
||||
$quiz->id, $context, $slot->id, new \qubaid_list([])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test question structure data.
|
||||
*
|
||||
* @covers ::get_question_structure
|
||||
* @covers ::get_question_structure_data
|
||||
* @covers ::question_array_sort
|
||||
* @covers ::get_always_latest_version_question_ids
|
||||
* @covers ::question_load_random_questions
|
||||
*/
|
||||
public function test_get_question_structure() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a quiz.
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
// Create a couple of questions.
|
||||
$quizcontext = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
|
||||
// Create a question in the quiz question bank.
|
||||
/** @var \core_question_generator $questiongenerator */
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
|
||||
$numq = $questiongenerator->create_question('essay', null,
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $quizcontext->id]);
|
||||
$q = $questiongenerator->create_question('essay', null,
|
||||
['category' => $cat->id, 'name' => 'This is the first version']);
|
||||
// Create two version.
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the second version']);
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the third version']);
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
// Create the quiz object.
|
||||
|
||||
// Edit it to create a second and third version.
|
||||
$questiongenerator->update_question($q, null, ['name' => 'This is the second version']);
|
||||
$finalq = $questiongenerator->update_question($q, null, ['name' => 'This is the third version']);
|
||||
|
||||
// Add the question to the quiz.
|
||||
quiz_add_quiz_question($q->id, $quiz);
|
||||
|
||||
// Load the quiz object and check.
|
||||
$quizobj = \quiz::create($quiz->id);
|
||||
$quizobj->preload_questions();
|
||||
$quizobj->load_questions();
|
||||
$questions = $quizobj->get_questions();
|
||||
$question = reset($questions);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$this->assertEquals($finalq->id, $question->id);
|
||||
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$slots = $structure->get_slots();
|
||||
$slot = reset($slots);
|
||||
$structuredatas = qbank_helper::get_question_structure($quiz->id);
|
||||
$structuredata = reset($structuredatas);
|
||||
$this->assertEquals($structuredata->slotid, $slot->id);
|
||||
$this->assertEquals($structuredata->id, $question->id);
|
||||
$this->assertEquals($finalq->id, $slot->questionid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to get the version information for a question to show in the version selection dropdown.
|
||||
*
|
||||
* @covers ::get_question_version_info
|
||||
*/
|
||||
public function test_get_question_version_info() {
|
||||
$this->resetAfterTest();
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
// Create a couple of questions.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
|
||||
$numq = $questiongenerator->create_question('essay', null,
|
||||
['category' => $cat->id, 'name' => 'This is the first version']);
|
||||
// Create two version.
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the second version']);
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the third version']);
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
// Create the quiz object.
|
||||
$quizobj = \quiz::create($quiz->id);
|
||||
$quizobj->preload_questions();
|
||||
$quizobj->load_questions();
|
||||
$questions = $quizobj->get_questions();
|
||||
$question = reset($questions);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$slots = $structure->get_slots();
|
||||
$slot = reset($slots);
|
||||
$versiondata = qbank_helper::get_question_version_info($question->id, $slot->id);
|
||||
$this->assertEquals(4, count($versiondata));
|
||||
$this->assertEquals('Always latest', $versiondata[0]->versionvalue);
|
||||
$this->assertEquals('v3 (latest)', $versiondata[1]->versionvalue);
|
||||
$this->assertEquals('v1', $versiondata[3]->versionvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get the question ids for specific question version.
|
||||
*
|
||||
* @covers ::get_specific_version_question_ids
|
||||
*/
|
||||
public function test_get_specific_version_question_ids() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
// Create a couple of questions.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $context->id]);
|
||||
$numq = $questiongenerator->create_question('essay', null,
|
||||
['category' => $cat->id, 'name' => 'This is the first version']);
|
||||
// Create two version.
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the second version']);
|
||||
$questiongenerator->update_question($numq, null, ['name' => 'This is the third version']);
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
submit_question_version::execute($DB->get_field('quiz_slots', 'id', ['quizid' => $quiz->id, 'slot' => 1]), 3);
|
||||
$specificversionquestionid = qbank_helper::get_specific_version_question_ids($quiz->id);
|
||||
$specificversionquestionid = reset($specificversionquestionid);
|
||||
$this->assertEquals($numq->id, $specificversionquestionid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -45,16 +45,15 @@ trait quiz_question_helper_test_trait {
|
||||
*/
|
||||
protected function create_test_quiz(\stdClass $course): \stdClass {
|
||||
|
||||
/** @var mod_quiz_generator $quizgenerator */
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
|
||||
$quiz = $quizgenerator->create_instance([
|
||||
return $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'questionsperpage' => 0,
|
||||
'grade' => 100.0,
|
||||
'sumgrades' => 2,
|
||||
]);
|
||||
$quiz->coursemodule = $quiz->cmid;
|
||||
return $quiz;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +63,7 @@ trait quiz_question_helper_test_trait {
|
||||
* @param \stdClass $quiz
|
||||
* @param array $override
|
||||
*/
|
||||
protected function add_regular_questions($questiongenerator, \stdClass $quiz, $override = null): void {
|
||||
protected function add_two_regular_questions($questiongenerator, \stdClass $quiz, $override = null): void {
|
||||
// Create a couple of questions.
|
||||
$cat = $questiongenerator->create_question_category($override);
|
||||
|
||||
@ -86,7 +85,7 @@ trait quiz_question_helper_test_trait {
|
||||
* @param \stdClass $quiz
|
||||
* @param array $override
|
||||
*/
|
||||
protected function add_random_questions($questiongenerator, \stdClass $quiz, $override = []): void {
|
||||
protected function add_one_random_question($questiongenerator, \stdClass $quiz, $override = []): void {
|
||||
// Create a random question.
|
||||
$cat = $questiongenerator->create_question_category($override);
|
||||
$questiongenerator->create_question('truefalse', null, array('category' => $cat->id));
|
||||
@ -106,18 +105,18 @@ trait quiz_question_helper_test_trait {
|
||||
$this->setUser($user);
|
||||
|
||||
$starttime = time();
|
||||
$quizobj = \quiz::create($quiz->id, $user->id);
|
||||
$quizobj = quiz::create($quiz->id, $user->id);
|
||||
|
||||
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
|
||||
// Start the attempt.
|
||||
$attempt = quiz_create_attempt($quizobj, $attemptnumber, false, $starttime, false, $user->id);
|
||||
$attempt = quiz_create_attempt($quizobj, $attemptnumber, null, $starttime, false, $user->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $starttime);
|
||||
quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
|
||||
// Finish the attempt.
|
||||
$attemptobj = \quiz_attempt::create($attempt->id);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$attemptobj->process_finish($starttime, false);
|
||||
|
||||
$this->setUser();
|
||||
@ -140,7 +139,7 @@ trait quiz_question_helper_test_trait {
|
||||
|
||||
$backupid = 'test-question-backup-restore';
|
||||
|
||||
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $quiz->coursemodule, backup::FORMAT_MOODLE,
|
||||
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $quiz->cmid, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $user->id);
|
||||
$bc->execute_plan();
|
||||
|
||||
|
@ -38,6 +38,11 @@ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
class quiz_question_restore_test extends \advanced_testcase {
|
||||
use \quiz_question_helper_test_trait;
|
||||
|
||||
/**
|
||||
* @var \stdClass test student user.
|
||||
*/
|
||||
protected $student;
|
||||
|
||||
/**
|
||||
* Called before every test.
|
||||
*/
|
||||
@ -56,23 +61,37 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
* @covers ::get_question_structure
|
||||
*/
|
||||
public function test_quiz_restore_in_a_different_course_using_course_question_bank() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test quiz.
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
$oldquizcontext = \context_module::instance($quiz->cmid);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_course::instance($this->course->id);
|
||||
$coursecontext = \context_course::instance($this->course->id);
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$this->add_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_random_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $coursecontext->id]);
|
||||
$this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $coursecontext->id]);
|
||||
|
||||
// Make the backup.
|
||||
$backupid = $this->backup_quiz($quiz, $this->user);
|
||||
|
||||
// Delete the current course to make sure there is no data.
|
||||
delete_course($this->course, false);
|
||||
// Check if the questions and associated datas are deleted properly.
|
||||
$this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure($quiz->id)));
|
||||
|
||||
// Check if the questions and associated data are deleted properly.
|
||||
$this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
|
||||
$quiz->id, $oldquizcontext)));
|
||||
|
||||
// Restore the course.
|
||||
$newcourse = $this->getDataGenerator()->create_course();
|
||||
$this->restore_quiz($backupid, $newcourse, $this->user);
|
||||
$module = $DB->get_record('quiz', ['course' => $newcourse->id]);
|
||||
$this->assertEquals(3, count(\mod_quiz\question\bank\qbank_helper::get_question_structure($module->id)));
|
||||
|
||||
// Verify.
|
||||
$modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz');
|
||||
$module = reset($modules);
|
||||
$questions = \mod_quiz\question\bank\qbank_helper::get_question_structure(
|
||||
$module->instance, $module->context);
|
||||
$this->assertCount(3, $questions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,33 +100,45 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
* @covers ::get_question_structure
|
||||
*/
|
||||
public function test_quiz_restore_in_a_different_course_using_quiz_question_bank() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the test quiz.
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
$quizcontext = \context_module::instance($quiz->cmid);
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$this->add_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_random_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
|
||||
$this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
|
||||
|
||||
// Make the backup.
|
||||
$backupid = $this->backup_quiz($quiz, $this->user);
|
||||
|
||||
// Delete the current course to make sure there is no data.
|
||||
delete_course($this->course, false);
|
||||
|
||||
// Check if the questions and associated datas are deleted properly.
|
||||
$this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure($quiz->id)));
|
||||
$this->assertEquals(0, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
|
||||
$quiz->id, $quizcontext)));
|
||||
|
||||
// Restore the course.
|
||||
$newcourse = $this->getDataGenerator()->create_course();
|
||||
$this->restore_quiz($backupid, $newcourse, $this->user);
|
||||
$module = $DB->get_record('quiz', ['course' => $newcourse->id]);
|
||||
$this->assertEquals(3, count(\mod_quiz\question\bank\qbank_helper::get_question_structure($module->id)));
|
||||
|
||||
// Verify.
|
||||
$modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz');
|
||||
$module = reset($modules);
|
||||
$this->assertEquals(3, count(\mod_quiz\question\bank\qbank_helper::get_question_structure(
|
||||
$module->instance, $module->context)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the questions for the context.
|
||||
*
|
||||
* @param int $context
|
||||
* @param int $contextid
|
||||
* @param string $extracondition
|
||||
* @return int
|
||||
* @return int the number of questions.
|
||||
*/
|
||||
protected function question_count($context, $extracondition = ''): int {
|
||||
protected function question_count(int $contextid, string $extracondition = ''): int {
|
||||
global $DB;
|
||||
return $DB->count_records_sql(
|
||||
"SELECT COUNT(q.id)
|
||||
@ -116,7 +147,7 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
||||
JOIN {question_categories} qc on qc.id = qbe.questioncategoryid
|
||||
WHERE qc.contextid = ?
|
||||
$extracondition", [$context]);
|
||||
$extracondition", [$contextid]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,8 +161,8 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
// Test for questions from a different context.
|
||||
$context = \context_course::instance($this->course->id);
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$this->add_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_random_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
// Count the questions in course context.
|
||||
$this->assertEquals(7, $this->question_count($context->id));
|
||||
$newquiz = $this->duplicate_quiz($this->course, $quiz);
|
||||
@ -150,10 +181,10 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
$this->resetAfterTest();
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
$context = \context_module::instance($quiz->cmid);
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$this->add_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_random_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
// Count the questions in course context.
|
||||
$this->assertEquals(7, $this->question_count($context->id));
|
||||
$newquiz = $this->duplicate_quiz($this->course, $quiz);
|
||||
@ -169,28 +200,37 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
* @covers ::get_question_structure
|
||||
*/
|
||||
public function test_quiz_restore_with_attempts() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a quiz.
|
||||
$quiz = $this->create_test_quiz($this->course);
|
||||
// Test for questions from a different context.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quiz->id, $this->course->id)->id);
|
||||
$quizcontext = \context_module::instance($quiz->cmid);
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$this->add_regular_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
$this->add_random_questions($questiongenerator, $quiz, ['contextid' => $context->id]);
|
||||
list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $this->student);
|
||||
$userattempts = quiz_get_user_attempts($quiz->id, $this->student->id);
|
||||
// Count the attempts for this quiz.
|
||||
$this->add_two_regular_questions($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
|
||||
$this->add_one_random_question($questiongenerator, $quiz, ['contextid' => $quizcontext->id]);
|
||||
|
||||
// Attempt it as a student, and check.
|
||||
/** @var \question_usage_by_activity $quba */
|
||||
[, $quba] = $this->attempt_quiz($quiz, $this->student);
|
||||
$this->assertEquals(3, $quba->question_count());
|
||||
$this->assertEquals(1, count($userattempts));
|
||||
$this->assertCount(1, quiz_get_user_attempts($quiz->id, $this->student->id));
|
||||
|
||||
// Make the backup.
|
||||
$backupid = $this->backup_quiz($quiz, $this->user);
|
||||
|
||||
// Delete the current course to make sure there is no data.
|
||||
delete_course($this->course, false);
|
||||
|
||||
// Restore the backup.
|
||||
$newcourse = $this->getDataGenerator()->create_course();
|
||||
$this->restore_quiz($backupid, $newcourse, $this->user);
|
||||
$module = $DB->get_record('quiz', ['course' => $newcourse->id]);
|
||||
$userattempts = quiz_get_user_attempts($module->id, $this->student->id);
|
||||
$this->assertEquals(1, count($userattempts));
|
||||
$this->assertEquals(3, count(\mod_quiz\question\bank\qbank_helper::get_question_structure($module->id)));
|
||||
|
||||
// Verify.
|
||||
$modules = get_fast_modinfo($newcourse->id)->get_instances_of('quiz');
|
||||
$module = reset($modules);
|
||||
$this->assertCount(1, quiz_get_user_attempts($module->instance, $this->student->id));
|
||||
$this->assertCount(3, \mod_quiz\question\bank\qbank_helper::get_question_structure(
|
||||
$module->instance, $module->context));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -220,7 +260,7 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
$modinfo = get_fast_modinfo($newcourseid);
|
||||
$quiz = array_values($modinfo->get_instances_of('quiz'))[0];
|
||||
$quizobj = \quiz::create($quiz->instance);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
// Are the correct slots returned?
|
||||
$slots = $structure->get_slots();
|
||||
@ -232,8 +272,7 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
$this->assertCount(2, $questions);
|
||||
|
||||
// Count the questions in quiz qbank.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quizobj->get_quizid(), $newcourseid)->id);
|
||||
$this->assertEquals(2, $this->question_count($context->id));
|
||||
$this->assertEquals(2, $this->question_count($quizobj->get_context()->id));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,6 +283,7 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
public function test_pre_4_quiz_restore_for_random_questions() {
|
||||
global $USER, $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$backupid = 'abc';
|
||||
$backuppath = make_backup_temp_directory($backupid);
|
||||
get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
|
||||
@ -263,7 +303,7 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
$modinfo = get_fast_modinfo($newcourseid);
|
||||
$quiz = array_values($modinfo->get_instances_of('quiz'))[0];
|
||||
$quizobj = \quiz::create($quiz->instance);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
// Are the correct slots returned?
|
||||
$slots = $structure->get_slots();
|
||||
@ -280,8 +320,7 @@ class quiz_question_restore_test extends \advanced_testcase {
|
||||
"AND q.qtype <> 'random'"));
|
||||
|
||||
// Count the questions in quiz qbank.
|
||||
$context = \context_module::instance(get_coursemodule_from_instance("quiz", $quizobj->get_quizid(), $newcourseid)->id);
|
||||
$this->assertEquals(0, $this->question_count($context->id));
|
||||
$this->assertEquals(0, $this->question_count($quizobj->get_context()->id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,9 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
use mod_quiz\structure;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
@ -108,7 +111,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
$quizobj = new quiz($quiz, $cm, $course);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
if (isset($headings[1])) {
|
||||
list($heading, $shuffle) = $this->parse_section_name($headings[1]);
|
||||
$sections = $structure->get_sections();
|
||||
@ -130,9 +133,9 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Verify that the given layout matches that expected.
|
||||
* @param array $expectedlayout as for $layout in {@link create_test_quiz()}.
|
||||
* @param \mod_quiz\structure $structure the structure to test.
|
||||
* @param structure $structure the structure to test.
|
||||
*/
|
||||
protected function assert_quiz_layout($expectedlayout, \mod_quiz\structure $structure) {
|
||||
protected function assert_quiz_layout($expectedlayout, structure $structure) {
|
||||
$sections = $structure->get_sections();
|
||||
|
||||
$slot = 1;
|
||||
@ -193,7 +196,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
// Are the correct slots returned?
|
||||
$slots = $structure->get_slots();
|
||||
@ -204,7 +207,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
$quizobj = $this->create_test_quiz(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$sections = $structure->get_sections();
|
||||
$this->assertCount(1, $sections);
|
||||
@ -222,7 +225,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2*',
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$sections = $structure->get_sections();
|
||||
$this->assertCount(2, $sections);
|
||||
@ -245,13 +248,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$sections = $structure->get_sections();
|
||||
$section = end($sections);
|
||||
$structure->remove_section_heading($section->id);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -264,7 +267,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$sections = $structure->get_sections();
|
||||
$section = reset($sections);
|
||||
@ -279,13 +282,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 1, 'truefalse'),
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
@ -299,13 +302,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 1, 'truefalse'),
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(2)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '2');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
@ -318,13 +321,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
@ -338,13 +341,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(2)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '2');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
'Heading',
|
||||
@ -360,13 +363,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 2, 'truefalse'),
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
@ -380,13 +383,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 1, 'truefalse'),
|
||||
array('TF3', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(3)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF3', 1, 'truefalse'),
|
||||
@ -400,13 +403,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 2, 'truefalse'),
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(3)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF3', 1, 'truefalse'),
|
||||
@ -420,13 +423,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 2, 'truefalse'),
|
||||
array('TF3', 3, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(3)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '3');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF3', 2, 'truefalse'),
|
||||
@ -440,7 +443,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 2, 'truefalse'),
|
||||
array('TF3', 3, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(3)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(2)->slotid;
|
||||
@ -454,7 +457,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 2, 'truefalse'),
|
||||
array('TF3', 3, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(1)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(2)->slotid;
|
||||
@ -470,13 +473,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(1)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(2)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF2', 1, 'truefalse'),
|
||||
@ -494,13 +497,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(3)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '2');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -518,12 +521,12 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF2', 2, 'truefalse'),
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(3)->slotid;
|
||||
$structure->move_slot($idtomove, 0, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF3', 1, 'truefalse'),
|
||||
@ -541,13 +544,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(2)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(2)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '2');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -563,12 +566,12 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, 0, '2');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -582,12 +585,12 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, 0, '1');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -601,12 +604,12 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, 0, '');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -624,13 +627,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF3', 3, 'truefalse'),
|
||||
array('TF4', 3, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(3)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, '2');
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -652,13 +655,13 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF3', 3, 'truefalse'),
|
||||
array('TF4', 3, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$idtomove = $structure->get_question_in_slot(4)->slotid;
|
||||
$idmoveafter = $structure->get_question_in_slot(1)->slotid;
|
||||
$structure->move_slot($idtomove, $idmoveafter, 1);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -677,11 +680,11 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$structure->remove_slot(2);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
'Heading 2',
|
||||
@ -702,7 +705,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
quiz_add_random_questions($quizobj->get_quiz(), 1, $cat->id, 1, false);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$sql = 'SELECT qsr.*
|
||||
FROM {question_set_references} qsr
|
||||
JOIN {quiz_slots} qs ON qs.id = qsr.itemid
|
||||
@ -713,7 +716,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
|
||||
$structure->remove_slot(2);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
), $structure);
|
||||
@ -731,7 +734,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$structure->remove_slot(1);
|
||||
$this->expectException(coding_exception::class);
|
||||
@ -745,7 +748,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 2',
|
||||
array('TF3', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$this->expectException(coding_exception::class);
|
||||
$structure->remove_slot(3);
|
||||
@ -756,7 +759,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$structure->remove_slot(1);
|
||||
|
||||
@ -766,7 +769,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('name' => 'TF2', 'category' => $cat->id));
|
||||
|
||||
quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 0);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
@ -788,7 +791,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
|
||||
quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 1);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF3', 1, 'truefalse'),
|
||||
@ -814,7 +817,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
|
||||
quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 1);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
'Heading 1',
|
||||
array('TF1', 1, 'truefalse'),
|
||||
@ -840,7 +843,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
|
||||
quiz_add_quiz_question($q->id, $quizobj->get_quiz(), 0);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
'Heading 2',
|
||||
@ -854,12 +857,12 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$slotid = $structure->get_question_in_slot(2)->slotid;
|
||||
$slots = $structure->update_page_break($slotid, \mod_quiz\repaginate::LINK);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
@ -871,12 +874,12 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
$slotid = $structure->get_question_in_slot(2)->slotid;
|
||||
$slots = $structure->update_page_break($slotid, \mod_quiz\repaginate::UNLINK);
|
||||
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assert_quiz_layout(array(
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 2, 'truefalse'),
|
||||
@ -888,21 +891,21 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
array('TF1', 1, 'truefalse'),
|
||||
array('TF2', 1, 'truefalse'),
|
||||
));
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
|
||||
// Test adding a dependency.
|
||||
$slotid = $structure->get_slot_id_for_slot(2);
|
||||
$structure->update_question_dependency($slotid, true);
|
||||
|
||||
// Having called update page break, we need to reload $structure.
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assertEquals(1, $structure->is_question_dependent_on_previous_slot(2));
|
||||
|
||||
// Test removing a dependency.
|
||||
$structure->update_question_dependency($slotid, false);
|
||||
|
||||
// Having called update page break, we need to reload $structure.
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$this->assertEquals(0, $structure->is_question_dependent_on_previous_slot(2));
|
||||
}
|
||||
|
||||
@ -920,11 +923,44 @@ class mod_quiz_structure_testcase extends advanced_testcase {
|
||||
$noneditingteacher = $generator->create_and_enrol($course, 'teacher');
|
||||
|
||||
$this->setUser($teacher);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quiz);
|
||||
$structure = structure::create_for_quiz($quiz);
|
||||
$this->assertTrue($structure->can_add_random_questions());
|
||||
|
||||
$this->setUser($noneditingteacher);
|
||||
$structure = \mod_quiz\structure::create_for_quiz($quiz);
|
||||
$structure = structure::create_for_quiz($quiz);
|
||||
$this->assertFalse($structure->can_add_random_questions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to get the version information for a question to show in the version selection dropdown.
|
||||
*
|
||||
* @covers ::get_question_version_info
|
||||
*/
|
||||
public function test_get_version_choices_for_slot() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$quizobj = $this->create_test_quiz([]);
|
||||
|
||||
// Create a question with two versions.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category(['contextid' => $quizobj->get_context()->id]);
|
||||
$q = $questiongenerator->create_question('essay', null,
|
||||
['category' => $cat->id, 'name' => 'This is the first version']);
|
||||
$questiongenerator->update_question($q, null, ['name' => 'This is the second version']);
|
||||
$questiongenerator->update_question($q, null, ['name' => 'This is the third version']);
|
||||
quiz_add_quiz_question($q->id, $quizobj->get_quiz());
|
||||
|
||||
// Create the quiz object.
|
||||
$structure = structure::create_for_quiz($quizobj);
|
||||
$versiondata = $structure->get_version_choices_for_slot(1);
|
||||
$this->assertEquals(4, count($versiondata));
|
||||
$this->assertEquals('Always latest', $versiondata[0]->versionvalue);
|
||||
$this->assertEquals('v3 (latest)', $versiondata[1]->versionvalue);
|
||||
$this->assertEquals('v2', $versiondata[2]->versionvalue);
|
||||
$this->assertEquals('v1', $versiondata[3]->versionvalue);
|
||||
$this->assertTrue($versiondata[0]->selected);
|
||||
$this->assertFalse($versiondata[1]->selected);
|
||||
$this->assertFalse($versiondata[2]->selected);
|
||||
$this->assertFalse($versiondata[3]->selected);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use mod_quiz\question\bank\qbank_helper;
|
||||
|
||||
/**
|
||||
* Test the restore of random question tags.
|
||||
*
|
||||
@ -79,7 +81,7 @@ class mod_quiz_tags_testcase extends advanced_testcase {
|
||||
$this->assertEquals("{$tag2->id},{$tag2->name}", "{$slottags[0]},{$slottags[1]}");
|
||||
|
||||
$defaultcategory = question_get_default_category(context_course::instance($newcourseid)->id);
|
||||
$this->assertEquals($defaultcategory->id, $question->categoryobject->id);
|
||||
$this->assertEquals($defaultcategory->id, $question->category);
|
||||
$randomincludingsubcategories = $DB->get_record('question_set_references',
|
||||
['itemid' => reset($slots)->id, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
|
||||
$filtercondition = json_decode($randomincludingsubcategories->filtercondition);
|
||||
@ -93,7 +95,9 @@ class mod_quiz_tags_testcase extends advanced_testcase {
|
||||
* @return array the tags.
|
||||
*/
|
||||
protected function get_tags_for_slot(int $slotid): array {
|
||||
$referencedata = \mod_quiz\question\bank\qbank_helper::get_random_question_data_from_slot($slotid);
|
||||
global $DB;
|
||||
$referencedata = $DB->get_record('question_set_references',
|
||||
['itemid' => $slotid, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
|
||||
if (isset($referencedata->filtercondition)) {
|
||||
$filtercondition = json_decode($referencedata->filtercondition);
|
||||
if (isset($filtercondition->tags)) {
|
||||
|
@ -17,7 +17,7 @@ This files describes API changes in the quiz code.
|
||||
* The quiz_slots_tags database table has been removed entirely, as has the get_slot_tags_for_slot_id() method
|
||||
from mod/quiz/classes/structure.php and the the locallib.php functions quiz_retrieve_slot_tags and
|
||||
quiz_retrieve_slot_tag_ids. This information is now stored in question_set_references
|
||||
and can be accessed using qbank_helper::get_random_question_data_from_slot.
|
||||
and can be accessed in the results of qbank_helper::get_question_structure.
|
||||
|
||||
|
||||
=== 3.11 ===
|
||||
|
@ -129,7 +129,7 @@ class core_question_generator extends component_generator_base {
|
||||
$fromform = test_question_maker::get_question_form_data($qtype, $which);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $question, $fromform);
|
||||
$fromform = (object) $this->datagenerator->combine_defaults_and_record((array) $fromform, $overrides);
|
||||
$fromform->status = $question->status;
|
||||
$fromform->status = $fromform->status ?? $question->status;
|
||||
|
||||
$question = question_bank::get_qtype($qtype)->save_question($question, $fromform);
|
||||
|
||||
|
@ -903,7 +903,7 @@ class question_type {
|
||||
* specific information (it is passed by reference).
|
||||
*/
|
||||
public function get_question_options($question) {
|
||||
global $CFG, $DB, $OUTPUT;
|
||||
global $DB, $OUTPUT;
|
||||
|
||||
if (!isset($question->options)) {
|
||||
$question->options = new stdClass();
|
||||
|
Loading…
x
Reference in New Issue
Block a user