Merge branch 'MDL-61514-master' of git://github.com/rezaies/moodle

This commit is contained in:
Andrew Nicols 2018-03-19 09:01:19 +08:00
commit d7a6e531ee
8 changed files with 370 additions and 15 deletions

View File

@ -58,7 +58,7 @@ 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', 'maxmark'));
'slot', 'page', 'requireprevious', 'questionid', 'questioncategoryid', 'includingsubcategories', 'tags', 'maxmark'));
$sections = new backup_nested_element('sections');

View File

@ -290,7 +290,21 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
}
$data->quizid = $this->get_new_parentid('quiz');
$data->questionid = $this->get_mappingid('question', $data->questionid);
$questionmapping = $this->get_mapping('question', $data->questionid);
$data->questionid = $questionmapping ? $questionmapping->newitemid : false;
if (isset($data->questioncategoryid)) {
$data->questioncategoryid = $this->get_mappingid('question_category', $data->questioncategoryid);
} else if ($questionmapping && $questionmapping->info->qtype == 'random') {
// Backward compatibility for backups created using Moodle 3.4 or earlier.
$data->questioncategoryid = $this->get_mappingid('question_category', $questionmapping->parentitemid);
$data->includingsubcategories = $questionmapping->info->questiontext ? 1 : 0;
}
if (isset($data->tags)) {
$tags = quiz_extract_random_question_tags($data->tags, $this->task->is_samesite());
$data->tags = quiz_build_random_question_tag_json($tags);
}
$DB->insert_record('quiz_slots', $data);
}

View File

@ -81,4 +81,20 @@ class randomquestion_form extends \moodleform {
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
public function set_data($defaultvalues) {
$mform = $this->_form;
if ($defaultvalues->fromtags) {
$fromtagselement = $mform->getElement('fromtags');
foreach ($defaultvalues->fromtags as $fromtag) {
if (!$fromtagselement->optionExists($fromtag)) {
$optionname = get_string('randomfromunavailabletag', 'mod_quiz', explode(',', $fromtag)[1]);
$fromtagselement->addOption($optionname, $fromtag);
}
}
}
parent::set_data($defaultvalues);
}
}

View File

@ -700,6 +700,7 @@ $string['randomcreate'] = 'Create random questions';
$string['randomediting'] = 'Editing a random question';
$string['randomfromcategory'] = 'Random question from category:';
$string['randomfromexistingcategory'] = 'Random question from an existing category';
$string['randomfromunavailabletag'] = '{$a} (unavailable)';
$string['randomnumber'] = 'Number of random questions';
$string['randomnosubcat'] = 'Questions from this category only, not its subcategories.';
$string['randomquestion'] = 'Random question';

View File

@ -2449,14 +2449,14 @@ function quiz_is_overriden_calendar_event(\calendar_event $event) {
function quiz_build_random_question_tag_json($tagrecords) {
$tags = [];
foreach ($tagrecords as $tagrecord) {
if ($tag = core_tag_tag::get($tagrecord->id, 'id, name')) {
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' => $tag->id,
'id' => (int)$tag->id,
'name' => $tagrecord->name
];
} else {
@ -2475,20 +2475,25 @@ function quiz_build_random_question_tag_json($tagrecords) {
* @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) {
function quiz_extract_random_question_tags($tagsjson, $matchbyid = true) {
$tagrecords = [];
if (!empty($tagsjson)) {
$tags = json_decode($tagsjson);
// Only work with tags that exist.
foreach ($tags as $tagdata) {
if (!array_key_exists($tagdata->id, $tagrecords)) {
if ($tag = core_tag_tag::get($tagdata->id, 'id, name')) {
$tagrecords[$tag->id] = $tag->to_object();
} else if ($tag = core_tag_tag::get_by_name(0, $tagdata->name, 'id, name')) {
$tagrecords[$tag->id] = $tag->to_object();
}
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();
} else {
$tagrecords[] = (object)[
'id' => null,
'name' => $tagdata->name
];
}
}
}
@ -2502,9 +2507,13 @@ function quiz_extract_random_question_tags($tagsjson) {
* @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.
*/
function quiz_extract_random_question_tag_ids($tagsjson) {
$tags = quiz_extract_random_question_tags($tagsjson);
return array_keys($tags);
function quiz_extract_random_question_tag_ids($tagsjson, $matchbyid = true) {
$tags = quiz_extract_random_question_tags($tagsjson, $matchbyid);
// Only work with tags that exist.
return array_filter(array_column($tags, 'id'));
}

Binary file not shown.

View File

@ -420,4 +420,224 @@ 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();
// 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);
$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.
],
);
$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));
}
public function test_quiz_extract_random_question_tags() {
$this->resetAfterTest();
// 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);
$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.
],
));
$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));
}
public function test_quiz_extract_random_question_tag_ids() {
$this->resetAfterTest();
// 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);
$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.
],
));
$expectedrecords = array(
$footag->id,
$bartag->id,
$baztag->id,
$quxtag->id,
);
$this->assertEquals($expectedrecords, quiz_extract_random_question_tag_ids($tagjson));
}
}

View File

@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for usage of tags in quizzes.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Class mod_quiz_tags_testcase
* Class for tests related to usage of question tags in quizzes.
*
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_tags_testcase extends advanced_testcase {
public function test_restore_random_question_by_tag() {
global $CFG, $USER, $DB;
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
$this->resetAfterTest();
$this->setAdminUser();
$backupid = 'abc';
$backuppath = $CFG->tempdir . '/backup/' . $backupid;
check_dir_exists($backuppath);
get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
__DIR__ . "/fixtures/random_by_tag_quiz.mbz", $backuppath);
// Do the restore to new course with default settings.
$categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
$newcourseid = restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
$rc = new restore_controller($backupid, $newcourseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
backup::TARGET_NEW_COURSE);
$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();
// Get the information about the resulting course and check that it is set up correctly.
$modinfo = get_fast_modinfo($newcourseid);
$quiz = array_values($modinfo->get_instances_of('quiz'))[0];
$quizobj = quiz::create($quiz->instance);
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
// Are the correct slots returned?
$slots = $structure->get_slots();
$this->assertCount(1, $slots);
$quizobj->preload_questions();
$quizobj->load_questions();
$questions = $quizobj->get_questions();
$this->assertCount(1, $questions);
$question = array_values($questions)[0];
$tag1 = core_tag_tag::get_by_name(0, 't1', 'id, name');
$this->assertNotFalse($tag1);
$tag2 = core_tag_tag::get_by_name(0, 't2', 'id, name');
$this->assertNotFalse($tag2);
$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);
$defaultcategory = question_get_default_category(context_course::instance($newcourseid)->id);
$this->assertEquals($defaultcategory->id, $question->randomfromcategory);
$this->assertEquals(0, $question->randomincludingsubcategories);
}
}