diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php
index 4ed0d760ef3..c65a65930da 100644
--- a/mod/quiz/attemptlib.php
+++ b/mod/quiz/attemptlib.php
@@ -136,7 +136,7 @@ class quiz {
public function preload_questions() {
$this->questions = question_preload_questions(null,
'slot.maxmark, slot.id AS slotid, slot.slot, slot.page,
- slot.questioncategoryid AS randomfromcategory, slot.tags AS randomfromtags,
+ slot.questioncategoryid AS randomfromcategory,
slot.includingsubcategories AS randomincludingsubcategories',
'{quiz_slots} slot ON slot.quizid = :quizid AND q.id = slot.questionid',
array('quizid' => $this->quiz->id), 'slot.slot');
@@ -569,7 +569,7 @@ class quiz_attempt {
$this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
$this->slots = $DB->get_records('quiz_slots',
array('quizid' => $this->get_quizid()), 'slot',
- 'slot, requireprevious, questionid, includingsubcategories, tags');
+ 'slot, requireprevious, questionid, includingsubcategories');
$this->sections = array_values($DB->get_records('quiz_sections',
array('quizid' => $this->get_quizid()), 'firstslot'));
@@ -1874,7 +1874,7 @@ class quiz_attempt {
if ($questiondata->qtype != 'random') {
$newqusetionid = $questiondata->id;
} else {
- $tagids = quiz_extract_random_question_tag_ids($this->slots[$slot]->tags);
+ $tagids = quiz_retrieve_slot_tag_ids($this->slots[$slot]->id);
$randomloader = new \core_question\bank\random_question_loader($qubaids, array());
$newqusetionid = $randomloader->get_next_question_id($questiondata->category,
diff --git a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
index 97d867c1f20..1e72487c7b1 100644
--- a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
+++ b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
@@ -58,7 +58,10 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
$qinstances = new backup_nested_element('question_instances');
$qinstance = new backup_nested_element('question_instance', array('id'), array(
- 'slot', 'page', 'requireprevious', 'questionid', 'questioncategoryid', 'includingsubcategories', 'tags', 'maxmark'));
+ 'slot', 'page', 'requireprevious', 'questionid', 'questioncategoryid', 'includingsubcategories', 'maxmark'));
+
+ $qinstancetags = new backup_nested_element('tags');
+ $qinstancetag = new backup_nested_element('tag', array('id'), array('tagid', 'tagname'));
$sections = new backup_nested_element('sections');
@@ -98,6 +101,9 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
$quiz->add_child($qinstances);
$qinstances->add_child($qinstance);
+ $qinstance->add_child($qinstancetags);
+ $qinstancetags->add_child($qinstancetag);
+
$quiz->add_child($sections);
$sections->add_child($section);
@@ -119,6 +125,9 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
$qinstance->set_source_table('quiz_slots',
array('quizid' => backup::VAR_PARENTID));
+ $qinstancetag->set_source_table('quiz_slot_tags',
+ array('slotid' => backup::VAR_PARENTID));
+
$section->set_source_table('quiz_sections',
array('quizid' => backup::VAR_PARENTID));
diff --git a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php
index 13da05f41fa..549e6047b10 100644
--- a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php
+++ b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php
@@ -59,6 +59,8 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$paths[] = new restore_path_element('quiz_question_instance',
'/activity/quiz/question_instances/question_instance');
+ $paths[] = new restore_path_element('quiz_slot_tags',
+ '/activity/quiz/question_instances/question_instance/tags/tag');
$paths[] = new restore_path_element('quiz_section', '/activity/quiz/sections/section');
$paths[] = new restore_path_element('quiz_feedback', '/activity/quiz/feedbacks/feedback');
$paths[] = new restore_path_element('quiz_override', '/activity/quiz/overrides/override');
@@ -254,6 +256,7 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
global $CFG, $DB;
$data = (object)$data;
+ $oldid = $data->id;
// Backwards compatibility for old field names (MDL-43670).
if (!isset($data->questionid) && isset($data->question)) {
@@ -301,14 +304,32 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$data->includingsubcategories = $questionmapping->info->questiontext ? 1 : 0;
}
- if (isset($data->tags)) {
- require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+ $newitemid = $DB->insert_record('quiz_slots', $data);
+ // Add mapping, restore of slot tags (for random questions) need it.
+ $this->set_mapping('quiz_question_instance', $oldid, $newitemid);
+ }
- $tags = quiz_extract_random_question_tags($data->tags, $this->task->is_samesite());
- $data->tags = quiz_build_random_question_tag_json($tags);
+ /**
+ * Process a quiz_slot_tags restore
+ *
+ * @param stdClass|array $data The quiz_slot_tags data
+ */
+ protected function process_quiz_slot_tags($data) {
+ global $DB;
+
+ $data = (object)$data;
+
+ $data->slotid = $this->get_new_parentid('quiz_question_instance');
+ if ($this->task->is_samesite() && $tag = core_tag_tag::get($data->tagid, 'id, name')) {
+ $data->tagname = $tag->name;
+ } else if ($tag = core_tag_tag::get_by_name(0, $data->tagname, 'id, name')) {
+ $data->tagid = $tag->id;
+ } else {
+ $data->tagid = null;
+ $data->tagname = $tag->name;
}
- $DB->insert_record('quiz_slots', $data);
+ $DB->insert_record('quiz_slot_tags', $data);
}
protected function process_quiz_section($data) {
diff --git a/mod/quiz/classes/local/structure/slot_random.php b/mod/quiz/classes/local/structure/slot_random.php
index ebd89f14e3a..50ef17fed98 100644
--- a/mod/quiz/classes/local/structure/slot_random.php
+++ b/mod/quiz/classes/local/structure/slot_random.php
@@ -43,6 +43,11 @@ class slot_random {
*/
protected $quiz = null;
+ /**
+ * @var \core_tag_tag[] List of tags for this slot.
+ */
+ protected $tags = [];
+
/**
* slot_random constructor.
*
@@ -52,9 +57,8 @@ class slot_random {
$this->record = new \stdClass();
$properties = array(
- 'id', 'slot', 'quizid', 'page', 'requireprevious',
- 'questionid', 'questioncategoryid', 'includingsubcategories',
- 'tags', 'maxmark');
+ 'id', 'slot', 'quizid', 'page', 'requireprevious', 'questionid',
+ 'questioncategoryid', 'includingsubcategories', 'maxmark');
foreach ($properties as $property) {
if (isset($slotrecord->$property)) {
@@ -95,6 +99,29 @@ class slot_random {
$this->record->quizid = $quiz->id;
}
+ /**
+ * Set some tags for this quiz slot.
+ *
+ * @param \core_tag_tag[] $tags
+ */
+ public function set_tags($tags) {
+ $this->tags = [];
+ foreach ($tags as $tag) {
+ // We use $tag->id as the key for the array so not only it handles duplicates of the same tag being given,
+ // but also it is consistent with the behaviour of set_tags_by_id() below.
+ $this->tags[$tag->id] = $tag;
+ }
+ }
+
+ /**
+ * Set some tags for this quiz slot. This function uses tag ids to find tags.
+ *
+ * @param int[] $tagids
+ */
+ public function set_tags_by_id($tagids) {
+ $this->tags = \core_tag_tag::get_bulk($tagids, 'id, name');
+ }
+
/**
* Inserts the quiz slot at the $page page.
* It is required to call this function if you are building a quiz slot object from scratch.
@@ -151,6 +178,19 @@ class slot_random {
}
$this->record->id = $DB->insert_record('quiz_slots', $this->record);
+
+ if (!empty($this->tags)) {
+ $recordstoinsert = [];
+ foreach ($this->tags as $tag) {
+ $recordstoinsert[] = (object)[
+ 'slotid' => $this->record->id,
+ 'tagid' => $tag->id,
+ 'tagname' => $tag->name
+ ];
+ }
+ $DB->insert_records('quiz_slot_tags', $recordstoinsert);
+ }
+
$trans->allow_commit();
}
}
\ No newline at end of file
diff --git a/mod/quiz/classes/structure.php b/mod/quiz/classes/structure.php
index 342af00a7d2..5f56a8adf1a 100644
--- a/mod/quiz/classes/structure.php
+++ b/mod/quiz/classes/structure.php
@@ -914,6 +914,7 @@ class structure {
$maxslot = $DB->get_field_sql('SELECT MAX(slot) FROM {quiz_slots} WHERE quizid = ?', array($this->get_quizid()));
$trans = $DB->start_delegated_transaction();
+ $DB->delete_records('quiz_slot_tags', array('slotid' => $slot->id));
$DB->delete_records('quiz_slots', array('id' => $slot->id));
for ($i = $slot->slot + 1; $i <= $maxslot; $i++) {
$DB->set_field('quiz_slots', 'slot', $i - 1,
diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml
old mode 100644
new mode 100755
index 46db06732a0..8ff87471019
--- a/mod/quiz/db/install.xml
+++ b/mod/quiz/db/install.xml
@@ -1,5 +1,5 @@
-
@@ -65,7 +65,6 @@
-
@@ -186,5 +185,18 @@
+
\ No newline at end of file
diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php
index 4aa0fabd742..89757eccc9f 100644
--- a/mod/quiz/db/upgrade.php
+++ b/mod/quiz/db/upgrade.php
@@ -117,13 +117,6 @@ function xmldb_quiz_upgrade($oldversion) {
$dbman->add_field($table, $field);
}
- // Define field tags to be added to quiz_slots.
- $field = new xmldb_field('tags', XMLDB_TYPE_TEXT, null, null, null, null, null, 'includingsubcategories');
- // Conditionally launch add field tags.
- if (!$dbman->field_exists($table, $field)) {
- $dbman->add_field($table, $field);
- }
-
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2018020700, 'quiz');
}
@@ -163,5 +156,45 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2018020701, 'quiz');
}
+ if ($oldversion < 2018040700) {
+
+ // Define field tags to be dropped from quiz_slots. This field was added earlier to master only.
+ $table = new xmldb_table('quiz_slots');
+ $field = new xmldb_field('tags');
+
+ // Conditionally launch drop field quizid.
+ if ($dbman->field_exists($table, $field)) {
+ $dbman->drop_field($table, $field);
+ }
+
+ // Quiz savepoint reached.
+ upgrade_mod_savepoint(true, 2018040700, 'quiz');
+ }
+
+ if ($oldversion < 2018040800) {
+
+ // Define table quiz_slot_tags to be created.
+ $table = new xmldb_table('quiz_slot_tags');
+
+ // Adding fields to table quiz_slot_tags.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('slotid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
+ $table->add_field('tagid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
+ $table->add_field('tagname', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+
+ // Adding keys to table quiz_slot_tags.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('slotid', XMLDB_KEY_FOREIGN, array('slotid'), 'quiz_slots', array('id'));
+ $table->add_key('tagid', XMLDB_KEY_FOREIGN, array('tagid'), 'tag', array('id'));
+
+ // Conditionally launch create table for quiz_slot_tags.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Quiz savepoint reached.
+ upgrade_mod_savepoint(true, 2018040800, 'quiz');
+ }
+
return true;
}
diff --git a/mod/quiz/editrandom.php b/mod/quiz/editrandom.php
index 135f433351a..9e32148330b 100644
--- a/mod/quiz/editrandom.php
+++ b/mod/quiz/editrandom.php
@@ -78,11 +78,9 @@ $toform = fullclone($question);
$toform->category = "{$category->id},{$category->contextid}";
$toform->includesubcategories = $slot->includingsubcategories;
$toform->fromtags = array();
-if ($slot->tags) {
- $tags = quiz_extract_random_question_tags($slot->tags);
- foreach ($tags as $tag) {
- $toform->fromtags[] = "{$tag->id},{$tag->name}";
- }
+$currentslottags = quiz_retrieve_slot_tags($slot->id);
+foreach ($currentslottags as $slottag) {
+ $toform->fromtags[] = "{$slottag->tagid},{$slottag->tagname}";
}
$toform->returnurl = $returnurl;
@@ -117,6 +115,8 @@ if ($mform->is_cancelled()) {
$slot->questioncategoryid = $fromform->category;
$slot->includingsubcategories = $fromform->includesubcategories;
+ $DB->update_record('quiz_slots', $slot);
+
$tags = [];
foreach ($fromform->fromtags as $tagstring) {
list($tagid, $tagname) = explode(',', $tagstring);
@@ -125,9 +125,39 @@ if ($mform->is_cancelled()) {
'name' => $tagname
];
}
- $slot->tags = quiz_build_random_question_tag_json($tags);
- $DB->update_record('quiz_slots', $slot);
+ $recordstokeep = [];
+ $recordstoinsert = [];
+ $searchableslottags = array_map(function($slottag) {
+ return ['tagid' => $slottag->tagid, 'tagname' => $slottag->tagname];
+ }, $currentslottags);
+
+ foreach ($tags as $tag) {
+ if ($key = array_search(['tagid' => $tag->id, 'tagname' => $tag->name], $searchableslottags)) {
+ // If found, $key would be the id field in the quiz_slot_tags table.
+ // Therefore, there was no need to check !== false here.
+ $recordstokeep[] = $key;
+ } else {
+ $recordstoinsert[] = (object)[
+ 'slotid' => $slot->id,
+ 'tagid' => $tag->id,
+ 'tagname' => $tag->name
+ ];
+ }
+ }
+
+ // Now, delete the remaining records.
+ if (!empty($recordstokeep)) {
+ list($select, $params) = $DB->get_in_or_equal($recordstokeep, SQL_PARAMS_QM, 'param', false);
+ $DB->delete_records_select('quiz_slot_tags', "id $select", $params);
+ } else {
+ $DB->delete_records('quiz_slot_tags', array('slotid' => $slot->id));
+ }
+
+ // And now, insert the extra records if there is any.
+ if (!empty($recordstoinsert)) {
+ $DB->insert_records('quiz_slot_tags', $recordstoinsert);
+ }
// Purge this question from the cache.
question_bank::notify_question_edited($question->id);
diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php
index 629edc02f1d..8c8fc91291c 100644
--- a/mod/quiz/lib.php
+++ b/mod/quiz/lib.php
@@ -183,7 +183,9 @@ function quiz_delete_instance($id) {
WHERE slot.quizid = ? AND q.qtype = ?";
$questionids = $DB->get_fieldset_sql($sql, array($quiz->id, 'random'));
- // We need to do this before we try and delete randoms, otherwise they would still be 'in use'.
+ // We need to do the following deletes before we try and delete randoms, otherwise they would still be 'in use'.
+ $quizslots = $DB->get_fieldset_select('quiz_slots', 'id', 'quizid = ?', array($quiz->id));
+ $DB->delete_records_list('quiz_slot_tags', 'slotid', $quizslots);
$DB->delete_records('quiz_slots', array('quizid' => $quiz->id));
$DB->delete_records('quiz_sections', array('quizid' => $quiz->id));
diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php
index 7b529562ecc..3761a46379b 100644
--- a/mod/quiz/locallib.php
+++ b/mod/quiz/locallib.php
@@ -208,7 +208,7 @@ function quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $time
continue;
}
- $tagids = quiz_extract_random_question_tag_ids($questiondata->randomfromtags);
+ $tagids = quiz_retrieve_slot_tag_ids($questiondata->slotid);
// Deal with fixed random choices for testing.
if (isset($questionids[$quba->next_slot_number()])) {
@@ -2189,18 +2189,10 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
$catcontext = context::instance_by_id($category->contextid);
require_capability('moodle/question:useall', $catcontext);
- $tags = [];
+ $tags = \core_tag_tag::get_bulk($tagids, 'id, name');
$tagstrings = [];
- foreach ($tagids as $tagid) {
- if ($tag = core_tag_tag::get($tagid, 'id,name')) {
- $tags[] = [
- 'id' => $tagid,
- 'name' => $tag->name
- ];
- $tagstrings[] = "{$tagid},{$tag->name}";
- } else if (!empty($tagid)) {
- print_error('invalidtagid', 'mod_quiz');
- }
+ foreach ($tags as $tag) {
+ $tagstrings[] = "{$tag->id},{$tag->name}";
}
// Find existing random questions in this category that are
@@ -2239,11 +2231,11 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number,
$randomslotdata->questionid = $question->id;
$randomslotdata->questioncategoryid = $categoryid;
$randomslotdata->includingsubcategories = $includesubcategories ? 1 : 0;
- $randomslotdata->tags = json_encode($tags);
$randomslotdata->maxmark = 1;
$randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
$randomslot->set_quiz($quiz);
+ $randomslot->set_tags($tags);
$randomslot->insert($addonpage);
}
}
@@ -2440,82 +2432,58 @@ function quiz_is_overriden_calendar_event(\calendar_event $event) {
}
/**
- * Providing a list of tag records, this function validates each pair and builds a json string
- * that can be stored in the quiz_slots.tags field.
+ * Retrieves tag information for the given quiz slot.
+ * A quiz slot have some tags if and only if it is representing a random question by tags.
*
- * @param stdClass[] $tagrecords List of tag objects with id and name properties.
- * @return string
+ * @param int $slotid The id of the quiz slot.
+ * @return stdClass[] List of quiz_slot_tags records.
*/
-function quiz_build_random_question_tag_json($tagrecords) {
- $tags = [];
- foreach ($tagrecords as $tagrecord) {
- if ($tagrecord->id && $tag = core_tag_tag::get($tagrecord->id, 'id, name')) {
- $tags[] = [
- 'id' => (int)$tagrecord->id,
- 'name' => $tag->name
- ];
- } else if ($tag = core_tag_tag::get_by_name(0, $tagrecord->name, 'id, name')) {
- $tags[] = [
- 'id' => (int)$tag->id,
- 'name' => $tagrecord->name
- ];
+function quiz_retrieve_slot_tags($slotid) {
+ global $DB;
+
+ $slottags = $DB->get_records('quiz_slot_tags', ['slotid' => $slotid]);
+
+ $tagsbyid = core_tag_tag::get_bulk(array_filter(array_column($slottags, 'tagid')), 'id, name');
+
+ $tagcollid = core_tag_area::get_collection('core', 'question');
+ $tagsbyname = false; // It will be loaded later if required.
+
+ foreach ($slottags as $slottag) {
+ if (isset($tagsbyid[$slottag->tagid])) {
+ $slottag->tagname = $tagsbyid[$slottag->tagid]->name; // Make sure that we're returning the most updated tag name.
} else {
- $tags[] = [
- 'id' => null,
- 'name' => $tagrecord->name
- ];
- }
- }
- return json_encode($tags);
-}
-
-/**
- * Providing tags data in the JSON format, this function returns tag records containing the id and name properties.
- *
- * @param string $tagsjson The JSON string representing an array of tags in the [{"id":tagid,"name":"tagname"}] format.
- * E.g. [{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}]
- * Usually equal to the value of the tags field retrieved from the quiz_slots table.
- * @param bool $matchbyid If set to true, then the function tries to find tags by their id.
- * If no tag is found by the tag id or if $matchbyid is set to false, then the function tries to find the tag by its name.
- * @return array An array of tags containing the id and name properties, indexed by tag ids.
- */
-function quiz_extract_random_question_tags($tagsjson, $matchbyid = true) {
- $tagrecords = [];
- if (!empty($tagsjson)) {
- $tags = json_decode($tagsjson);
-
- foreach ($tags as $tagdata) {
- if ($matchbyid && $tag = core_tag_tag::get($tagdata->id, 'id, name')) {
- $tagrecords[] = $tag->to_object();
- } else if ($tag = core_tag_tag::get_by_name(0, $tagdata->name, 'id, name')) {
- $tagrecords[] = $tag->to_object();
+ if ($tagsbyname === false) {
+ // We were hoping that this query could be avoided, but life showed its other side to us!
+ $tagsbyname = core_tag_tag::get_by_name_bulk($tagcollid, array_column($slottags, 'tagname'), 'id, name');
+ }
+ if (isset($tagsbyname[$slottag->tagname])) {
+ $slottag->tagid = $tagsbyname[$slottag->tagname]->id; // Make sure that we're returning the current tag id
+ // that matches the given tag name.
} else {
- $tagrecords[] = (object)[
- 'id' => null,
- 'name' => $tagdata->name
- ];
+ $slottag->tagid = null; // The tag does not exist anymore (neither the tag id nor the tag name
+ // matches an existing tag).
+ // We still need to include this row in the result as some callers might
+ // be interested in these rows. An example is the editing forms that still
+ // need to display tag names even if they don't exist anymore.
}
}
}
- return $tagrecords;
+ return $slottags;
}
/**
- * Providing tags data in the JSON format, this function returns tagids.
+ * Retrieves tag ids for the given quiz slot.
+ * A quiz slot have some tags if and only if it is representing a random question by tags.
*
- * @param string $tagsjson The JSON string representing an array of tags in the [{"id":tagid,"name":"tagname"}] format.
- * E.g. [{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}]
- * Usually equal to the value of the tags field retrieved from the {quiz_slots} table.
- * @param bool $matchbyid If set to true, then this function relies on the tag ids that are stored in $tagsjson to find tags.
- * If no tag is found by the tag id or if $matchbyid is set to false, then this function tries to find the tag by its name.
- * @return int[] List of tag ids.
+ * @param int $slotid The id of the quiz slot.
+ * @return int[]
*/
-function quiz_extract_random_question_tag_ids($tagsjson, $matchbyid = true) {
- $tags = quiz_extract_random_question_tags($tagsjson, $matchbyid);
+function quiz_retrieve_slot_tag_ids($slotid) {
+ $tags = quiz_retrieve_slot_tags($slotid);
// Only work with tags that exist.
- return array_filter(array_column($tags, 'id'));
+ return array_filter(array_column($tags, 'tagid'));
}
/**
diff --git a/mod/quiz/tests/fixtures/random_by_tag_quiz.mbz b/mod/quiz/tests/fixtures/random_by_tag_quiz.mbz
index 643b0c17319..2592e475b63 100644
Binary files a/mod/quiz/tests/fixtures/random_by_tag_quiz.mbz and b/mod/quiz/tests/fixtures/random_by_tag_quiz.mbz differ
diff --git a/mod/quiz/tests/local_structure_slot_random_test.php b/mod/quiz/tests/local_structure_slot_random_test.php
new file mode 100755
index 00000000000..a9c1cd5fbfd
--- /dev/null
+++ b/mod/quiz/tests/local_structure_slot_random_test.php
@@ -0,0 +1,413 @@
+.
+
+/**
+ * Unit tests for the {@link \mod_quiz\local\structure\slot_random} class.
+ *
+ * @package mod_quiz
+ * @category test
+ * @copyright 2018 Shamim Rezaie
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class mod_quiz_local_structure_slot_random_test
+ * Class for tests related to the {@link \mod_quiz\local\structure\slot_random} class.
+ *
+ * @copyright 2018 Shamim Rezaie
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_quiz_local_structure_slot_random_test extends advanced_testcase {
+ /**
+ * Constructor test.
+ */
+ public function test_constructor() {
+ global $SITE;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ // Create a question category in the system context.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $category = $questiongenerator->create_question_category();
+
+ // Create a random question without adding it to a quiz.
+ // We don't want to use quiz_add_random_questions because that itself, instantiates an object from the slot_random class.
+ $form = new stdClass();
+ $form->category = $category->id . ',' . $category->contextid;
+ $form->includesubcategories = true;
+ $form->fromtags = [];
+ $form->defaultmark = 1;
+ $form->hidden = 1;
+ $form->stamp = make_unique_id_code();
+ $question = new stdClass();
+ $question->qtype = 'random';
+ $question = question_bank::get_qtype('random')->save_question($question, $form);
+
+ $randomslotdata = new stdClass();
+ $randomslotdata->quizid = $quiz->id;
+ $randomslotdata->questionid = $question->id;
+ $randomslotdata->questioncategoryid = $category->id;
+ $randomslotdata->includingsubcategories = 1;
+ $randomslotdata->maxmark = 1;
+
+ $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('record');
+ $rcp->setAccessible(true);
+ $record = $rcp->getValue($randomslot);
+
+ $this->assertEquals($quiz->id, $record->quizid);
+ $this->assertEquals($question->id, $record->questionid);
+ $this->assertEquals($category->id, $record->questioncategoryid);
+ $this->assertEquals(1, $record->includingsubcategories);
+ $this->assertEquals(1, $record->maxmark);
+ }
+
+ public function test_get_quiz_quiz() {
+ global $SITE, $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ // Create a question category in the system context.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $category = $questiongenerator->create_question_category();
+
+ quiz_add_random_questions($quiz, 0, $category->id, 1, false);
+
+ // Get the random question's id. It is at the first slot.
+ $questionid = $DB->get_field('quiz_slots', 'questionid', array('quizid' => $quiz->id, 'slot' => 1));
+
+ $randomslotdata = new stdClass();
+ $randomslotdata->quizid = $quiz->id;
+ $randomslotdata->questionid = $questionid;
+ $randomslotdata->questioncategoryid = $category->id;
+ $randomslotdata->includingsubcategories = 1;
+ $randomslotdata->maxmark = 1;
+
+ $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
+
+ // The create_instance had injected an additional cmid propery to the quiz. Let's remove that.
+ unset($quiz->cmid);
+
+ $this->assertEquals($quiz, $randomslot->get_quiz());
+ }
+
+ public function test_set_quiz() {
+ global $SITE, $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ // Create a question category in the system context.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $category = $questiongenerator->create_question_category();
+
+ quiz_add_random_questions($quiz, 0, $category->id, 1, false);
+
+ // Get the random question's id. It is at the first slot.
+ $questionid = $DB->get_field('quiz_slots', 'questionid', array('quizid' => $quiz->id, 'slot' => 1));
+
+ $randomslotdata = new stdClass();
+ $randomslotdata->quizid = $quiz->id;
+ $randomslotdata->questionid = $questionid;
+ $randomslotdata->questioncategoryid = $category->id;
+ $randomslotdata->includingsubcategories = 1;
+ $randomslotdata->maxmark = 1;
+
+ $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
+
+ // The create_instance had injected an additional cmid propery to the quiz. Let's remove that.
+ unset($quiz->cmid);
+
+ $randomslot->set_quiz($quiz);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('quiz');
+ $rcp->setAccessible(true);
+ $quizpropery = $rcp->getValue($randomslot);
+
+ $this->assertEquals($quiz, $quizpropery);
+ }
+
+ private function setup_for_test_tags($tagnames) {
+ global $SITE, $DB;
+
+ // Create a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ // Create a question category in the system context.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $category = $questiongenerator->create_question_category();
+
+ quiz_add_random_questions($quiz, 0, $category->id, 1, false);
+
+ // Get the random question's id. It is at the first slot.
+ $questionid = $DB->get_field('quiz_slots', 'questionid', array('quizid' => $quiz->id, 'slot' => 1));
+
+ $randomslotdata = new stdClass();
+ $randomslotdata->quizid = $quiz->id;
+ $randomslotdata->questionid = $questionid;
+ $randomslotdata->questioncategoryid = $category->id;
+ $randomslotdata->includingsubcategories = 1;
+ $randomslotdata->maxmark = 1;
+
+ $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
+
+ // Create tags.
+ foreach ($tagnames as $tagname) {
+ $tagrecord = array(
+ 'isstandard' => 1,
+ 'flag' => 0,
+ 'rawname' => $tagname,
+ 'description' => $tagname . ' desc'
+ );
+ $tags[$tagname] = $this->getDataGenerator()->create_tag($tagrecord);
+ }
+
+ return array($randomslot, $tags);
+ }
+
+ public function test_set_tags() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar']);
+ $randomslot->set_tags([$tags['foo'], $tags['bar']]);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('tags');
+ $rcp->setAccessible(true);
+ $tagspropery = $rcp->getValue($randomslot);
+
+ $this->assertEquals([
+ $tags['foo']->id => $tags['foo'],
+ $tags['bar']->id => $tags['bar'],
+ ], $tagspropery);
+ }
+
+ public function test_set_tags_twice() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']);
+
+ // Set tags for the first time.
+ $randomslot->set_tags([$tags['foo'], $tags['bar']]);
+ // Now set the tags again.
+ $randomslot->set_tags([$tags['baz']]);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('tags');
+ $rcp->setAccessible(true);
+ $tagspropery = $rcp->getValue($randomslot);
+
+ $this->assertEquals([
+ $tags['baz']->id => $tags['baz'],
+ ], $tagspropery);
+ }
+
+ public function test_set_tags_duplicates() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']);
+
+ $randomslot->set_tags([$tags['foo'], $tags['bar'], $tags['foo']]);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('tags');
+ $rcp->setAccessible(true);
+ $tagspropery = $rcp->getValue($randomslot);
+
+ $this->assertEquals([
+ $tags['foo']->id => $tags['foo'],
+ $tags['bar']->id => $tags['bar'],
+ ], $tagspropery);
+ }
+
+ public function test_set_tags_by_id() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']);
+
+ $randomslot->set_tags_by_id([$tags['foo']->id, $tags['bar']->id]);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('tags');
+ $rcp->setAccessible(true);
+ $tagspropery = $rcp->getValue($randomslot);
+
+ // The set_tags_by_id function only retrieves id and name fields of the tag object.
+ $this->assertCount(2, $tagspropery);
+ $this->assertArrayHasKey($tags['foo']->id, $tagspropery);
+ $this->assertArrayHasKey($tags['bar']->id, $tagspropery);
+ $this->assertEquals(
+ (object)['id' => $tags['foo']->id, 'name' => $tags['foo']->name],
+ $tagspropery[$tags['foo']->id]->to_object()
+ );
+ $this->assertEquals(
+ (object)['id' => $tags['bar']->id, 'name' => $tags['bar']->name],
+ $tagspropery[$tags['bar']->id]->to_object()
+ );
+ }
+
+ public function test_set_tags_by_id_twice() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']);
+
+ // Set tags for the first time.
+ $randomslot->set_tags_by_id([$tags['foo']->id, $tags['bar']->id]);
+ // Now set the tags again.
+ $randomslot->set_tags_by_id([$tags['baz']->id]);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('tags');
+ $rcp->setAccessible(true);
+ $tagspropery = $rcp->getValue($randomslot);
+
+ // The set_tags_by_id function only retrieves id and name fields of the tag object.
+ $this->assertCount(1, $tagspropery);
+ $this->assertArrayHasKey($tags['baz']->id, $tagspropery);
+ $this->assertEquals(
+ (object)['id' => $tags['baz']->id, 'name' => $tags['baz']->name],
+ $tagspropery[$tags['baz']->id]->to_object()
+ );
+ }
+
+ public function test_set_tags_by_id_duplicates() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($randomslot, $tags) = $this->setup_for_test_tags(['foo', 'bar', 'baz']);
+
+ $randomslot->set_tags_by_id([$tags['foo']->id, $tags['bar']->id], $tags['foo']->id);
+
+ $rc = new ReflectionClass('\mod_quiz\local\structure\slot_random');
+ $rcp = $rc->getProperty('tags');
+ $rcp->setAccessible(true);
+ $tagspropery = $rcp->getValue($randomslot);
+
+ // The set_tags_by_id function only retrieves id and name fields of the tag object.
+ $this->assertCount(2, $tagspropery);
+ $this->assertArrayHasKey($tags['foo']->id, $tagspropery);
+ $this->assertArrayHasKey($tags['bar']->id, $tagspropery);
+ $this->assertEquals(
+ (object)['id' => $tags['foo']->id, 'name' => $tags['foo']->name],
+ $tagspropery[$tags['foo']->id]->to_object()
+ );
+ $this->assertEquals(
+ (object)['id' => $tags['bar']->id, 'name' => $tags['bar']->name],
+ $tagspropery[$tags['bar']->id]->to_object()
+ );
+ }
+
+ public function test_insert() {
+ global $SITE, $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ // Create a question category in the system context.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $category = $questiongenerator->create_question_category();
+
+ // Create a random question without adding it to a quiz.
+ $form = new stdClass();
+ $form->category = $category->id . ',' . $category->contextid;
+ $form->includesubcategories = true;
+ $form->fromtags = [];
+ $form->defaultmark = 1;
+ $form->hidden = 1;
+ $form->stamp = make_unique_id_code();
+ $question = new stdClass();
+ $question->qtype = 'random';
+ $question = question_bank::get_qtype('random')->save_question($question, $form);
+
+ // Prepare 2 tags.
+ $tagrecord = array(
+ 'isstandard' => 1,
+ 'flag' => 0,
+ 'rawname' => 'foo',
+ 'description' => 'foo desc'
+ );
+ $footag = $this->getDataGenerator()->create_tag($tagrecord);
+ $tagrecord = array(
+ 'isstandard' => 1,
+ 'flag' => 0,
+ 'rawname' => 'bar',
+ 'description' => 'bar desc'
+ );
+ $bartag = $this->getDataGenerator()->create_tag($tagrecord);
+
+ $randomslotdata = new stdClass();
+ $randomslotdata->quizid = $quiz->id;
+ $randomslotdata->questionid = $question->id;
+ $randomslotdata->questioncategoryid = $category->id;
+ $randomslotdata->includingsubcategories = 1;
+ $randomslotdata->maxmark = 1;
+
+ // Insert the random question to the quiz.
+ $randomslot = new \mod_quiz\local\structure\slot_random($randomslotdata);
+ $randomslot->set_tags([$footag, $bartag]);
+ $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.
+ $quizslottags = $DB->get_records('quiz_slot_tags', array('slotid' => $quizslot->id));
+
+ $this->assertEquals($question->id, $quizslot->questionid);
+ $this->assertEquals($category->id, $quizslot->questioncategoryid);
+ $this->assertEquals(1, $quizslot->includingsubcategories);
+ $this->assertEquals(1, $quizslot->maxmark);
+
+ $this->assertCount(2, $quizslottags);
+ $this->assertEquals(
+ [
+ ['tagid' => $footag->id, 'tagname' => $footag->name],
+ ['tagid' => $bartag->id, 'tagname' => $bartag->name]
+ ],
+ array_map(function($slottag) {
+ return ['tagid' => $slottag->tagid, 'tagname' => $slottag->tagname];
+ }, $quizslottags),
+ '', 0.0, 10, true);
+ }
+}
\ No newline at end of file
diff --git a/mod/quiz/tests/locallib_test.php b/mod/quiz/tests/locallib_test.php
index 3ef0ad13d21..e5c396aa7c1 100644
--- a/mod/quiz/tests/locallib_test.php
+++ b/mod/quiz/tests/locallib_test.php
@@ -421,223 +421,168 @@ class mod_quiz_locallib_testcase extends advanced_testcase {
$this->assertEquals($comparearray, quiz_get_user_timeclose($course->id));
}
- public function test_quiz_build_random_question_tag_json() {
- $this->resetAfterTest();
+ /**
+ * This function creates a quiz with some standard (non-random) and some random questions.
+ * The standard questions are created first and then random questions follow them.
+ * So in a quiz with 3 standard question and 2 random question, the first random question is at slot 4.
+ *
+ * @param int $qnum Number of standard questions that should be created in the quiz.
+ * @param int $randomqnum Number of random questions that should be created in the quiz.
+ * @param array $questiontags Tags to be used for random questions.
+ * This is an array in the following format:
+ * [
+ * 0 => ['foo', 'bar'],
+ * 1 => ['baz', 'qux']
+ * ]
+ * @param string[] $unusedtags Some additional tags to be created.
+ * @return array An array of 2 elements: $quiz and $tagobjects.
+ * $tagobjects is an associative array of all created tag objects with its key being tag names.
+ */
+ private function setup_quiz_and_tags($qnum, $randomqnum, $questiontags = [], $unusedtags = []) {
+ global $SITE;
- // Setup test data.
- $footagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'foo',
- 'description' => 'foo desc'
- );
- $footag = $this->getDataGenerator()->create_tag($footagrecord);
- $bartagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'bar',
- 'description' => 'bar desc'
- );
- $bartag = $this->getDataGenerator()->create_tag($bartagrecord);
- $baztagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'baz',
- 'description' => 'baz desc'
- );
- $baztag = $this->getDataGenerator()->create_tag($baztagrecord);
- $quxtagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'qux',
- 'description' => 'qux desc'
- );
- $quxtag = $this->getDataGenerator()->create_tag($quxtagrecord);
- $quuxtagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'quux',
- 'description' => 'quux desc'
- );
- $quuxtag = $this->getDataGenerator()->create_tag($quuxtagrecord);
+ $tagobjects = [];
- $tagrecords = array(
- (object)[
- 'id' => $footag->id,
- 'name' => 'foo'
- ],
- (object)[
- 'id' => 999, // An invalid tag id.
- 'name' => 'bar'
- ],
- (object)[
- 'id' => null,
- 'name' => 'baz'
- ],
- (object)[
- 'id' => $quxtag->id,
- 'name' => 'invalidqux' // An invalid tag name.
- ],
- (object)[
- 'id' => 999, // An invalid tag id.
- 'name' => 'invalidquux' // An invalid tag name.
- ],
- );
+ // Get all the tags that need to be created.
+ $alltags = [];
+ foreach ($questiontags as $questiontag) {
+ $alltags = array_merge($alltags, $questiontag);
+ }
+ $alltags = array_merge($alltags, $unusedtags);
+ $alltags = array_unique($alltags);
- $expectedjson = json_encode(array(
- ['id' => (int)$footag->id, 'name' => $footag->name],
- ['id' => (int)$bartag->id, 'name' => $bartag->name],
- ['id' => (int)$baztag->id, 'name' => $baztag->name],
- ['id' => (int)$quxtag->id, 'name' => $quxtag->name],
- ['id' => null, 'name' => 'invalidquux'],
- ));
- $this->assertEquals($expectedjson, quiz_build_random_question_tag_json($tagrecords));
+ // Create tags.
+ foreach ($alltags as $tagname) {
+ $tagrecord = array(
+ 'isstandard' => 1,
+ 'flag' => 0,
+ 'rawname' => $tagname,
+ 'description' => $tagname . ' desc'
+ );
+ $tagobjects[$tagname] = $this->getDataGenerator()->create_tag($tagrecord);
+ }
+
+ // Create a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ // Create a question category in the system context.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $cat = $questiongenerator->create_question_category();
+
+ // Setup standard questions.
+ for ($i = 0; $i < $qnum; $i++) {
+ $question = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
+ quiz_add_quiz_question($question->id, $quiz);
+ }
+ // Setup random questions.
+ for ($i = 0; $i < $randomqnum; $i++) {
+ // Just create a standard question first, so there would be enough questions to pick a random question from.
+ $question = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
+ $tagids = [];
+ if (!empty($questiontags[$i])) {
+ foreach ($questiontags[$i] as $tagname) {
+ $tagids[] = $tagobjects[$tagname]->id;
+ }
+ }
+ quiz_add_random_questions($quiz, 0, $cat->id, 1, false, $tagids);
+ }
+
+ return array($quiz, $tagobjects);
}
- public function test_quiz_extract_random_question_tags() {
+ public function test_quiz_retrieve_slot_tags() {
+ global $DB;
+
$this->resetAfterTest();
+ $this->setAdminUser();
- // Setup test data.
- $footagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'foo',
- 'description' => 'foo desc'
- );
- $footag = $this->getDataGenerator()->create_tag($footagrecord);
- $bartagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'bar',
- 'description' => 'bar desc'
- );
- $bartag = $this->getDataGenerator()->create_tag($bartagrecord);
- $baztagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'baz',
- 'description' => 'baz desc'
- );
- $baztag = $this->getDataGenerator()->create_tag($baztagrecord);
- $quxtagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'qux',
- 'description' => 'qux desc'
- );
- $quxtag = $this->getDataGenerator()->create_tag($quxtagrecord);
- $quuxtagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'quux',
- 'description' => 'quux desc'
- );
- $quuxtag = $this->getDataGenerator()->create_tag($quuxtagrecord);
+ list($quiz, $tags) = $this->setup_quiz_and_tags(1, 1, [['foo', 'bar']], ['baz']);
- $tagjson = json_encode(array(
- [
- 'id' => $footag->id,
- 'name' => 'foo'
- ],
- [
- 'id' => 999, // An invalid tag id.
- 'name' => 'bar'
- ],
- [
- 'id' => null,
- 'name' => 'baz'
- ],
- [
- 'id' => $quxtag->id,
- 'name' => 'invalidqux' // An invalid tag name.
- ],
- [
- 'id' => 999, // An invalid tag id.
- 'name' => 'invalidquux' // An invalid tag name.
- ],
- ));
+ // Get the random question's slotid. It is at the second slot.
+ $slotid = $DB->get_field('quiz_slots', 'id', array('quizid' => $quiz->id, 'slot' => 2));
+ $slottags = quiz_retrieve_slot_tags($slotid);
- $expectedrecords = array(
- (object)['id' => $footag->id, 'name' => $footag->name],
- (object)['id' => $bartag->id, 'name' => $bartag->name],
- (object)['id' => $baztag->id, 'name' => $baztag->name],
- (object)['id' => $quxtag->id, 'name' => $quxtag->name],
- (object)['id' => null, 'name' => 'invalidquux'],
- );
-
- $this->assertEquals($expectedrecords, quiz_extract_random_question_tags($tagjson));
+ $this->assertEquals(
+ [
+ ['tagid' => $tags['foo']->id, 'tagname' => $tags['foo']->name],
+ ['tagid' => $tags['bar']->id, 'tagname' => $tags['bar']->name]
+ ],
+ array_map(function($slottag) {
+ return ['tagid' => $slottag->tagid, 'tagname' => $slottag->tagname];
+ }, $slottags),
+ '', 0.0, 10, true);
}
- public function test_quiz_extract_random_question_tag_ids() {
+ public function test_quiz_retrieve_slot_tags_with_removed_tag() {
+ global $DB;
+
$this->resetAfterTest();
+ $this->setAdminUser();
- // Setup test data.
- $footagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'foo',
- 'description' => 'foo desc'
- );
- $footag = $this->getDataGenerator()->create_tag($footagrecord);
- $bartagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'bar',
- 'description' => 'bar desc'
- );
- $bartag = $this->getDataGenerator()->create_tag($bartagrecord);
- $baztagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'baz',
- 'description' => 'baz desc'
- );
- $baztag = $this->getDataGenerator()->create_tag($baztagrecord);
- $quxtagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'qux',
- 'description' => 'qux desc'
- );
- $quxtag = $this->getDataGenerator()->create_tag($quxtagrecord);
- $quuxtagrecord = array(
- 'isstandard' => 1,
- 'flag' => 0,
- 'rawname' => 'quux',
- 'description' => 'quux desc'
- );
- $quuxtag = $this->getDataGenerator()->create_tag($quuxtagrecord);
+ list($quiz, $tags) = $this->setup_quiz_and_tags(1, 1, [['foo', 'bar']], ['baz']);
- $tagjson = json_encode(array(
- [
- 'id' => $footag->id,
- 'name' => 'foo'
- ],
- [
- 'id' => 999, // An invalid tag id.
- 'name' => 'bar'
- ],
- [
- 'id' => null,
- 'name' => 'baz'
- ],
- [
- 'id' => $quxtag->id,
- 'name' => 'invalidqux' // An invalid tag name.
- ],
- [
- 'id' => 999, // An invalid tag id.
- 'name' => 'invalidquux' // An invalid tag name.
- ],
- ));
+ // Get the random question's slotid. It is at the second slot.
+ $slotid = $DB->get_field('quiz_slots', 'id', array('quizid' => $quiz->id, 'slot' => 2));
+ $slottags = quiz_retrieve_slot_tags($slotid);
- $expectedrecords = array(
- $footag->id,
- $bartag->id,
- $baztag->id,
- $quxtag->id,
- );
+ // Now remove the foo tag and check again.
+ core_tag_tag::delete_tags([$tags['foo']->id]);
+ $slottags = quiz_retrieve_slot_tags($slotid);
- $this->assertEquals($expectedrecords, quiz_extract_random_question_tag_ids($tagjson));
+ $this->assertEquals(
+ [
+ ['tagid' => null, 'tagname' => $tags['foo']->name],
+ ['tagid' => $tags['bar']->id, 'tagname' => $tags['bar']->name]
+ ],
+ array_map(function($slottag) {
+ return ['tagid' => $slottag->tagid, 'tagname' => $slottag->tagname];
+ }, $slottags),
+ '', 0.0, 10, true);
+ }
+
+ public function test_quiz_retrieve_slot_tags_for_standard_question() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($quiz, $tags) = $this->setup_quiz_and_tags(1, 1, [['foo', 'bar']]);
+
+ // Get the standard question's slotid. It is at the first slot.
+ $slotid = $DB->get_field('quiz_slots', 'id', array('quizid' => $quiz->id, 'slot' => 1));
+
+ // There should be no slot tags for a non-random question.
+ $this->assertCount(0, quiz_retrieve_slot_tags($slotid));
+ }
+
+ public function test_quiz_retrieve_slot_tag_ids() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($quiz, $tags) = $this->setup_quiz_and_tags(1, 1, [['foo', 'bar']], ['baz']);
+
+ // Get the random question's slotid. It is at the second slot.
+ $slotid = $DB->get_field('quiz_slots', 'id', array('quizid' => $quiz->id, 'slot' => 2));
+ $tagids = quiz_retrieve_slot_tag_ids($slotid);
+
+ $this->assertEquals([$tags['foo']->id, $tags['bar']->id], $tagids, '', 0.0, 10, true);
+ }
+
+ public function test_quiz_retrieve_slot_tag_ids_for_standard_question() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ list($quiz, $tags) = $this->setup_quiz_and_tags(1, 1, [['foo', 'bar']], ['baz']);
+
+ // Get the standard question's slotid. It is at the first slot.
+ $slotid = $DB->get_field('quiz_slots', 'id', array('quizid' => $quiz->id, 'slot' => 1));
+ $tagids = quiz_retrieve_slot_tag_ids($slotid);
+
+ $this->assertEquals([], $tagids, '', 0.0, 10, true);
}
}
diff --git a/mod/quiz/tests/tags_test.php b/mod/quiz/tests/tags_test.php
index 38c70682c8d..094abb29aed 100644
--- a/mod/quiz/tests/tags_test.php
+++ b/mod/quiz/tests/tags_test.php
@@ -36,6 +36,7 @@ class mod_quiz_tags_testcase extends advanced_testcase {
global $CFG, $USER, $DB;
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$this->resetAfterTest();
$this->setAdminUser();
@@ -82,8 +83,16 @@ class mod_quiz_tags_testcase extends advanced_testcase {
$tag3 = core_tag_tag::get_by_name(0, 't3', 'id, name');
$this->assertNotFalse($tag3);
- $tagrecords = array($tag2->to_object());
- $this->assertEquals(quiz_build_random_question_tag_json($tagrecords), $question->randomfromtags);
+ $slottags = quiz_retrieve_slot_tags($question->slotid);
+ $this->assertEquals(
+ [
+ ['tagid' => $tag2->id, 'tagname' => $tag2->name]
+ ],
+ array_map(function($tag) {
+ return ['tagid' => $tag->tagid, 'tagname' => $tag->tagname];
+ }, $slottags),
+ '', 0.0, 10, true
+ );
$defaultcategory = question_get_default_category(context_course::instance($newcourseid)->id);
$this->assertEquals($defaultcategory->id, $question->randomfromcategory);
diff --git a/mod/quiz/version.php b/mod/quiz/version.php
index f13117669ed..efeb4613169 100644
--- a/mod/quiz/version.php
+++ b/mod/quiz/version.php
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2018020701;
+$plugin->version = 2018040800;
$plugin->requires = 2017110800;
$plugin->component = 'mod_quiz';
$plugin->cron = 60;
diff --git a/tag/classes/tag.php b/tag/classes/tag.php
index c41f80f9260..52dfd34b1ec 100644
--- a/tag/classes/tag.php
+++ b/tag/classes/tag.php
@@ -209,7 +209,7 @@ class core_tag_tag {
}
/**
- * Simple function to just return a single tag object by its id
+ * Simple function to just return an array of tag objects by their ids
*
* @param int[] $ids
* @param string $returnfields which fields do we want returned from table {tag}.