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}.