MDL-61380 Questions: Editing configuration of existing random questions

Editing "random" questions follows a different procedure than adding them.
This commit takes care of editing a "random" question's configuration.
This commit also introduces mod_quiz\form\randomquestion_form, so mod_quiz
can edit random questions without having to use the form that is inside qtype_random.
This commit is contained in:
Shamim Rezaie 2018-03-01 00:48:12 +11:00
parent 4f964a1ca0
commit 6650c66b59
6 changed files with 320 additions and 6 deletions

View File

@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the editing form for random questions.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_quiz\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Class randomquestion_form
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class randomquestion_form extends \moodleform {
/**
* Form definiton.
*/
public function definition() {
$mform = $this->_form;
$contexts = $this->_customdata['contexts'];
$usablecontexts = $contexts->having_cap('moodle/question:useall');
// Standard fields at the start of the form.
$mform->addElement('header', 'generalheader', get_string("general", 'form'));
$mform->addElement('questioncategory', 'category', get_string('category', 'question'),
array('contexts' => $usablecontexts, 'top' => true));
$mform->addElement('advcheckbox', 'includesubcategories', get_string('recurse', 'quiz'), null, null, array(0, 1));
$tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id'));
$mform->hideIf('includesubcategories', 'category', 'in', $tops);
$tags = \core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts);
$tagstrings = array();
foreach ($tags as $tag) {
$tagstrings["{$tag->id},{$tag->name}"] = $tag->name;
}
$options = array(
'multiple' => true,
'noselectionstring' => get_string('anytags', 'quiz'),
);
$mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options);
$mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz');
$mform->addElement('hidden', 'slotid');
$mform->setType('slotid', PARAM_INT);
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
$buttonarray = array();
$buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('savechanges'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
}

View File

@ -958,16 +958,16 @@ class edit_renderer extends \plugin_renderer_base {
* and also to see that category in the question bank.
*
* @param structure $structure object containing the structure of the quiz.
* @param int $slot which slot we are outputting.
* @param int $slotnumber which slot we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function random_question(structure $structure, $slot, $pageurl) {
public function random_question(structure $structure, $slotnumber, $pageurl) {
$question = $structure->get_question_in_slot($slot);
$editurl = new \moodle_url('/question/question.php', array(
'returnurl' => $pageurl->out_as_local_url(),
'cmid' => $structure->get_cmid(), 'id' => $question->id));
$question = $structure->get_question_in_slot($slotnumber);
$slot = $structure->get_slot_by_number($slotnumber);
$editurl = new \moodle_url('/mod/quiz/editrandom.php',
array('returnurl' => $pageurl->out_as_local_url(), 'slotid' => $slot->id));
$temp = clone($question);
$temp->questiontext = '';

View File

@ -409,6 +409,23 @@ class structure {
return $this->slots[$slotid];
}
/**
* Get a slot by it's slot number. Throws an exception if it is missing.
*
* @param int $slotnumber The slot number
* @return \stdClass
* @throws \coding_exception
*/
public function get_slot_by_number($slotnumber) {
foreach ($this->slots as $slot) {
if ($slot->slot == $slotnumber) {
return $slot;
}
}
throw new \coding_exception('The \'slotnumber\' could not be found.');
}
/**
* Check whether adding a section heading is possible
* @param int $pagenumber the number of the page.

152
mod/quiz/editrandom.php Normal file
View File

@ -0,0 +1,152 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Page for editing random questions.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$slotid = required_param('slotid', PARAM_INT);
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
// Get the quiz slot.
$slot = $DB->get_record('quiz_slots', array('id' => $slotid));
if (!$slot || empty($slot->questioncategoryid)) {
print_error('invalidrandomslot', 'mod_quiz');
}
if (!$quiz = $DB->get_record('quiz', array('id' => $slot->quizid))) {
print_error('invalidquizid', 'quiz');
}
$cm = get_coursemodule_from_instance('quiz', $slot->quizid, $quiz->course);
require_login($cm->course, false, $cm);
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else {
$returnurl = new moodle_url('/mod/quiz/edit.php', array('cmid' => $cm->id));
}
$url = new moodle_url('/mod/quiz/editrandom.php', array('slotid' => $slotid));
$PAGE->set_url($url);
$PAGE->set_pagelayout('admin');
if (!$question = $DB->get_record('question', array('id' => $slot->questionid))) {
print_error('questiondoesnotexist', 'question', $returnurl);
}
$qtypeobj = question_bank::get_qtype('random');
// Validate the question category.
if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
print_error('categorydoesnotexist', 'question', $returnurl);
}
// Check permissions.
question_require_capability_on($question, 'edit');
$thiscontext = context_module::instance($cm->id);
$contexts = new question_edit_contexts($thiscontext);
// Create the question editing form.
$mform = new mod_quiz\form\randomquestion_form(new moodle_url('/mod/quiz/editrandom.php'),
array('contexts' => $contexts));
// Send the question object and a few more parameters to the form.
$toform = fullclone($question);
$toform->category = "{$category->id},{$category->contextid}";
$toform->includesubcategories = $slot->includingsubcategories;
$toform->fromtags = array();
if ($slot->tags) {
$tags = quiz_extract_random_question_tags($slot->tags);
foreach ($tags as $tag) {
$toform->fromtags[] = "{$tag->id},{$tag->name}";
}
}
$toform->returnurl = $returnurl;
if ($cm !== null) {
$toform->cmid = $cm->id;
$toform->courseid = $cm->course;
} else {
$toform->courseid = $COURSE->id;
}
$toform->slotid = $slotid;
$mform->set_data($toform);
if ($mform->is_cancelled()) {
redirect($returnurl);
} else if ($fromform = $mform->get_data()) {
// If we are moving a question, check we have permission to move it from
// whence it came. Where we are moving to is validated by the form.
list($newcatid, $newcontextid) = explode(',', $fromform->category);
if (!empty($question->id) && $newcatid != $question->category) {
$contextid = $newcontextid;
question_require_capability_on($question, 'move');
} else {
$contextid = $category->contextid;
}
$question = $qtypeobj->save_question($question, $fromform);
// We need to save some data into the quiz_slots table.
$slot->questioncategoryid = $fromform->category;
$slot->includingsubcategories = $fromform->includesubcategories;
$tags = [];
foreach ($fromform->fromtags as $tagstring) {
list($tagid, $tagname) = explode(',', $tagstring);
$tags[] = (object) [
'id' => $tagid,
'name' => $tagname
];
}
$slot->tags = quiz_build_random_question_tag_json($tags);
$DB->update_record('quiz_slots', $slot);
// Purge this question from the cache.
question_bank::notify_question_edited($question->id);
$returnurl->param('lastchanged', $question->id);
redirect($returnurl);
}
$streditingquestion = $qtypeobj->get_heading();
$PAGE->set_title($streditingquestion);
$PAGE->set_heading($COURSE->fullname);
$PAGE->navbar->add($streditingquestion);
// Display a heading, question editing form and possibly some extra content needed for
// for this question type.
echo $OUTPUT->header();
$heading = get_string('randomediting', 'mod_quiz');
echo $OUTPUT->heading_with_help($heading, 'randomquestion', 'mod_quiz');
$mform->display();
echo $OUTPUT->footer();

View File

@ -439,6 +439,7 @@ $string['invalidcategory'] = 'Category ID is invalid';
$string['invalidoverrideid'] = 'Invalid override id';
$string['invalidquestionid'] = 'Invalid question id';
$string['invalidquizid'] = 'Invalid quiz ID';
$string['invalidrandomslot'] = 'Invalid random question slot id.';
$string['invalidsource'] = 'The source is not accepted as valid.';
$string['invalidsourcetype'] = 'Invalid source type.';
$string['invalidstateid'] = 'Invalid state id';
@ -696,10 +697,13 @@ $string['quiztimer'] = 'Quiz Timer';
$string['quizwillopen'] = 'This quiz will open {$a}';
$string['random'] = 'Random question';
$string['randomcreate'] = 'Create random questions';
$string['randomediting'] = 'Editing a random question';
$string['randomfromcategory'] = 'Random question from category:';
$string['randomfromexistingcategory'] = 'Random question from an existing category';
$string['randomnumber'] = 'Number of random questions';
$string['randomnosubcat'] = 'Questions from this category only, not its subcategories.';
$string['randomquestion'] = 'Random question';
$string['randomquestion_help'] = 'A random question is a way of inserting a randomly-chosen question from a specified category or by a specified tag into an activity.';
$string['randomquestiontags'] = 'Tags';
$string['randomquestiontags_help'] = 'You can restrict the selection criteria further by specifying some question tags here.

View File

@ -2436,3 +2436,60 @@ function quiz_is_overriden_calendar_event(\calendar_event $event) {
return $DB->record_exists('quiz_overrides', $overrideparams);
}
/**
* Providing a list of tag records, this function validates each pair and builds a json string
* that can be stored in the quiz_slots.tags field.
*
* @param stdClass[] $tagrecords List of tag objects with id and name properties.
* @return string
*/
function quiz_build_random_question_tag_json($tagrecords) {
$tags = [];
foreach ($tagrecords as $tagrecord) {
if ($tag = core_tag_tag::get($tagrecord->id, 'id, name')) {
$tags[] = [
'id' => (int)$tagrecord->id,
'name' => $tag->name
];
} else if ($tag = core_tag_tag::get_by_name(0, $tagrecord->name, 'id, name')) {
$tags[] = [
'id' => $tag->id,
'name' => $tagrecord->name
];
} else {
$tags[] = [
'id' => null,
'name' => $tagrecord->name
];
}
}
return json_encode($tags);
}
/**
* Providing tags data in the JSON format, this function returns tag records containing the id and name properties.
*
* @param string $tagsjson The JSON string representing an array of tags in the [{"id":tagid,"name":"tagname"}] format.
* E.g. [{"id":1,"name":"tag1"},{"id":2,"name":"tag2"}]
* Usually equal to the value of the tags field retrieved from the quiz_slots table.
* @return array An array of tags containing the id and name properties, indexed by tag ids.
*/
function quiz_extract_random_question_tags($tagsjson) {
$tagrecords = [];
if (!empty($tagsjson)) {
$tags = json_decode($tagsjson);
// Only work with tags that exist.
foreach ($tags as $tagdata) {
if (!array_key_exists($tagdata->id, $tagrecords)) {
if ($tag = core_tag_tag::get($tagdata->id, 'id, name')) {
$tagrecords[$tag->id] = $tag->to_object();
} else if ($tag = core_tag_tag::get_by_name(0, $tagdata->name, 'id, name')) {
$tagrecords[$tag->id] = $tag->to_object();
}
}
}
}
return $tagrecords;
}