Commit c9c989a0 authored by Tim Hunt's avatar Tim Hunt
Browse files

MDL-20636 Mostly working conversion of the multichoice question type.

Currently, you get an error on saving the editing form.
parent 94814340
......@@ -335,30 +335,30 @@ DONE question/type/essay/version.php | 2 -
question/type/match/simpletest/testwalkthrough.php | 474 ++++
question/type/match/version.php | 4 +-
question/type/missingtype/display.html | 24 -
question/type/missingtype/edit_missingtype_form.php | 102 +-
question/type/missingtype/lang/en_utf8/qtype_missingtype.php | 10 +
question/type/missingtype/question.php | 86 +
question/type/missingtype/questiontype.php | 125 +-
question/type/missingtype/renderer.php | 36 +
question/type/missingtype/simpletest/testmissingtype.php | 125 +
question/type/multianswer/db/upgrade.php | 1 -
question/type/multianswer/edit_multianswer_form.php | 11 +-
question/type/multianswer/questiontype.php | 61 +-
question/type/multianswer/version.php | 2 -
question/type/multichoice/db/install.xml | 3 +-
question/type/multichoice/db/upgrade.php | 16 +-
question/type/multichoice/display.html | 38 -
question/type/multichoice/edit_multichoice_form.php | 88 +-
question/type/multichoice/lang/en_utf8/qtype_multichoice.php | 42 +
question/type/multichoice/question.php | 367 +++
question/type/multichoice/questiontype.php | 399 +--
question/type/multichoice/renderer.php | 313 +++
question/type/multichoice/simpletest/testquestion.php | 236 ++
question/type/multichoice/simpletest/testquestiontype.php | 90 +
question/type/multichoice/simpletest/testwalkthrough.php | 91 +
question/type/multichoice/version.php | 6 +-
DONE question/type/missingtype/display.html | 24 -
DONE question/type/missingtype/edit_missingtype_form.php | 102 +-
DONE question/type/missingtype/lang/en_utf8/qtype_missingtype.php | 10 +
DONE question/type/missingtype/question.php | 86 +
DONE question/type/missingtype/questiontype.php | 125 +-
DONE question/type/missingtype/renderer.php | 36 +
DONE question/type/missingtype/simpletest/testmissingtype.php | 125 +
DONE question/type/multianswer/db/upgrade.php | 1 -
DONE question/type/multianswer/edit_multianswer_form.php | 11 +-
DONE question/type/multianswer/questiontype.php | 61 +-
DONE question/type/multianswer/version.php | 2 -
DONE question/type/multichoice/db/install.xml | 3 +-
DONE question/type/multichoice/db/upgrade.php | 16 +-
DONE question/type/multichoice/display.html | 38 -
DONE question/type/multichoice/edit_multichoice_form.php | 88 +-
DONE question/type/multichoice/lang/en_utf8/qtype_multichoice.php | 42 +
DONE question/type/multichoice/question.php | 367 +++
DONE question/type/multichoice/questiontype.php | 399 +--
DONE question/type/multichoice/renderer.php | 313 +++
DONE question/type/multichoice/simpletest/testquestion.php | 236 ++
DONE question/type/multichoice/simpletest/testquestiontype.php | 90 +
DONE question/type/multichoice/simpletest/testwalkthrough.php | 91 +
DONE question/type/multichoice/version.php | 6 +-
question/type/numerical/db/upgrade.php | 2 -
question/type/numerical/edit_numerical_form.php | 48 +-
......
......@@ -425,6 +425,7 @@ abstract class question_edit_form extends moodleform {
}
}
}
// subclass adds data_preprocessing code here
$question = $this->data_preprocessing($question);
......@@ -478,6 +479,35 @@ abstract class question_edit_form extends moodleform {
return $question;
}
protected function data_preprocessing_combined_feedback($question, $withshownumcorrect = false) {
if (empty($question->options)) {
return $question;
}
foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
$draftid = file_get_submitted_draft_itemid($feedbackname);
$question->$feedbackname = array();
$question->$feedbackname['text'] = file_prepare_draft_area(
$draftid, // draftid
$this->context->id, // context
'qtype_multichoice', // component
$feedbackname, // filarea
!empty($question->id) ? (int) $question->id : null, // itemid
$this->fileoptions, // options
$question->options->$feedbackname // text
);
$feedbackformat = $feedbackname . 'format';
$question->$feedbackname['format'] = $question->options->$feedbackformat;
$question->$feedbackname['itemid'] = $draftid;
}
if ($withshownumcorrect) {
$question->shownumcorrect = $question->options->shownumcorrect;
}
return $question;
}
protected function data_preprocessing_hints($question, $withclearwrong = false, $withshownumpartscorrect = false) {
if (empty($question->hints)) {
return $question;
......
......@@ -18,7 +18,8 @@
<FIELD NAME="partiallycorrectfeedbackformat" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="partiallycorrectfeedback" NEXT="incorrectfeedback"/>
<FIELD NAME="incorrectfeedback" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" COMMENT="Feedback shown for any incorrect response." PREVIOUS="partiallycorrectfeedbackformat" NEXT="incorrectfeedbackformat"/>
<FIELD NAME="incorrectfeedbackformat" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="incorrectfeedback" NEXT="answernumbering"/>
<FIELD NAME="answernumbering" TYPE="char" LENGTH="10" NOTNULL="true" DEFAULT="abc" SEQUENCE="false" COMMENT="Indicates how and whether the choices should be numbered." PREVIOUS="incorrectfeedbackformat"/>
<FIELD NAME="answernumbering" TYPE="char" LENGTH="10" NOTNULL="true" DEFAULT="abc" SEQUENCE="false" COMMENT="Indicates how and whether the choices should be numbered." PREVIOUS="incorrectfeedbackformat" NEXT="shownumcorrect"/>
<FIELD NAME="shownumcorrect" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="false" DEFAULT="0" SEQUENCE="false" COMMENT="If true, then when the user gets a multiple-response question partially correct, tell them how many choices they got correct alongside the feedback." PREVIOUS="answernumbering"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="question"/>
......
......@@ -109,7 +109,23 @@ function xmldb_qtype_multichoice_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2009021801, 'qtype', 'multichoice');
}
return true;
}
// Add new shownumcorrect field. If this is true, then when the user gets a
// multiple-response question partially correct, tell them how many choices
// they got correct alongside the feedback.
if ($oldversion < 2011011200) {
// Define field shownumcorrect to be added to question_multichoice
$table = new xmldb_table('question_multichoice');
$field = new xmldb_field('shownumcorrect', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'answernumbering');
// Launch add field shownumcorrect
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// multichoice savepoint reached
upgrade_plugin_savepoint(true, 2011011200, 'qtype', 'multichoice');
}
return true;
}
......@@ -18,17 +18,19 @@
defined('MOODLE_INTERNAL') || die();
/**
* Defines the editing form for the multichoice question type.
* Defines the editing form for the multiple choice question type.
*
* @package qtype
* @subpackage multichoice
* @copyright &copy; 2007 Jamie Pratt
* @author Jamie Pratt me@jamiep.org
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package questionbank
* @subpackage questiontypes
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* multiple choice editing form definition.
* Multiple choice editing form definition.
*
* @copyright &copy; 2007 Jamie Pratt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_edit_multichoice_form extends question_edit_form {
/**
......@@ -36,7 +38,7 @@ class question_edit_multichoice_form extends question_edit_form {
*
* @param object $mform the form being built.
*/
function definition_inner(&$mform) {
protected function definition_inner($mform) {
global $QTYPES;
$menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice'));
......@@ -47,84 +49,36 @@ class question_edit_multichoice_form extends question_edit_form {
$mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
$mform->setDefault('shuffleanswers', 1);
$numberingoptions = $QTYPES[$this->qtype()]->get_numbering_styles();
$menu = array();
foreach ($numberingoptions as $numberingoption) {
$menu[$numberingoption] = get_string('answernumbering' . $numberingoption, 'qtype_multichoice');
}
$mform->addElement('select', 'answernumbering', get_string('answernumbering', 'qtype_multichoice'), $menu);
$mform->addElement('select', 'answernumbering', get_string('answernumbering', 'qtype_multichoice'),
qtype_multichoice::get_numbering_styles());
$mform->setDefault('answernumbering', 'abc');
/* $mform->addElement('static', 'answersinstruct', get_string('choices', 'qtype_multichoice'), get_string('fillouttwochoices', 'qtype_multichoice'));
$mform->closeHeaderBefore('answersinstruct');
*/
$creategrades = get_grade_options();
$this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
$creategrades->gradeoptionsfull, max(5, QUESTION_NUMANS_START));
$mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice'));
foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
$mform->addElement('editor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'),
array('rows' => 10), $this->editoroptions);
$mform->setType($feedbackname, PARAM_RAW);
}
$this->add_combined_feedback_fields(true);
$mform->disabledIf('shownumcorrect', 'single', 'eq', 1);
$this->add_interactive_settings(true, true);
}
function data_preprocessing($question) {
if (isset($question->options)){
$answers = $question->options->answers;
if (count($answers)) {
$key = 0;
foreach ($answers as $answer){
$default_values['answer['.$key.']'] = $answer->answer;
$default_values['fraction['.$key.']'] = $answer->fraction;
// prepare question text
$draftid = file_get_submitted_draft_itemid('feedback['.$key.']');
$default_values['feedback['.$key.']'] = array();
$default_values['feedback['.$key.']']['text'] = file_prepare_draft_area($draftid, $this->context->id, 'question', 'answerfeedback', empty($answer->id)?null:(int)$answer->id, $this->fileoptions, $answer->feedback);
$default_values['feedback['.$key.']']['format'] = $answer->feedbackformat;
$default_values['feedback['.$key.']']['itemid'] = $draftid;
$key++;
}
}
$default_values['single'] = $question->options->single;
$default_values['answernumbering'] = $question->options->answernumbering;
$default_values['shuffleanswers'] = $question->options->shuffleanswers;
// prepare feedback editor to display files in draft area
foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
$draftid = file_get_submitted_draft_itemid($feedbackname);
$text = $question->options->$feedbackname;
$feedbackformat = $feedbackname . 'format';
$format = $question->options->$feedbackformat;
$default_values[$feedbackname] = array();
$default_values[$feedbackname]['text'] = file_prepare_draft_area(
$draftid, // draftid
$this->context->id, // context
'qtype_multichoice', // component
$feedbackname, // filarea
!empty($question->id)?(int)$question->id:null, // itemid
$this->fileoptions, // options
$text // text
);
$default_values[$feedbackname]['format'] = $format;
$default_values[$feedbackname]['itemid'] = $draftid;
}
// prepare files code block ends
$question = (object)((array)$question + $default_values);
$question = parent::data_preprocessing($question);
$question = $this->data_preprocessing_answers($question, true);
$question = $this->data_preprocessing_combined_feedback($question, true);
$question = $this->data_preprocessing_hints($question);
if (!empty($question->options)) {
$question->single = $question->options->single;
$question->shuffleanswers = $question->options->shuffleanswers;
$question->answernumbering = $question->options->answernumbering;
}
return $question;
}
function qtype() {
return 'multichoice';
return $question;
}
function validation($data, $files) {
public function validation($data, $files) {
$errors = parent::validation($data, $files);
$answers = $data['answer'];
$answercount = 0;
......@@ -132,27 +86,28 @@ class question_edit_multichoice_form extends question_edit_form {
$totalfraction = 0;
$maxfraction = -1;
foreach ($answers as $key => $answer){
foreach ($answers as $key => $answer) {
//check no of choices
$trimmedanswer = trim($answer);
if (!empty($trimmedanswer)){
$answercount++;
if (empty($trimmedanswer)) {
continue;
}
$answercount++;
//check grades
if ($answer != '') {
if ($data['fraction'][$key] > 0) {
$totalfraction += $data['fraction'][$key];
}
if ($data['fraction'][$key] > $maxfraction) {
$maxfraction = $data['fraction'][$key];
}
if ($data['fraction'][$key] > 0) {
$totalfraction += $data['fraction'][$key];
}
if ($data['fraction'][$key] > $maxfraction) {
$maxfraction = $data['fraction'][$key];
}
}
if ($answercount==0){
if ($answercount == 0) {
$errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
$errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
} elseif ($answercount==1){
} elseif ($answercount == 1) {
$errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
}
......@@ -172,4 +127,8 @@ class question_edit_multichoice_form extends question_edit_form {
}
return $errors;
}
public function qtype() {
return 'multichoice';
}
}
......@@ -27,13 +27,18 @@ $string['addingmultichoice'] = 'Adding a Multiple choice question';
$string['addmorechoiceblanks'] = 'Blanks for {no} more choices';
$string['answerhowmany'] = 'One or multiple answers?';
$string['answernumbering'] = 'Number the choices?';
$string['answernumbering123'] = '1., 2., 3., ...';
$string['answernumberingabc'] = 'a., b., c., ...';
$string['answernumberingABCD'] = 'A., B., C., ...';
$string['answernumberingiii'] = 'i., ii., iii., ...';
$string['answernumberingIIII'] = 'I., II., III., ...';
$string['answernumberingnone'] = 'No numbering';
$string['answernumbering123'] = '1., 2., 3., ...';
$string['answersingleno'] = 'Multiple answers allowed';
$string['answersingleyes'] = 'One answer only';
$string['choiceno'] = 'Choice {$a}';
$string['choices'] = 'Available choices';
$string['clozeaid'] = 'Enter missing word';
$string['correctansweris'] = 'The correct answer is: {$a}.';
$string['correctfeedback'] = 'For any correct response';
$string['editingmultichoice'] = 'Editing a Multiple choice question';
$string['errfractionsaddwrong'] = 'The positive grades you have chosen do not add up to 100%<br />Instead, they add up to {$a}%';
......@@ -42,8 +47,6 @@ $string['feedback'] = 'Feedback';
$string['fillouttwochoices'] = 'You must fill out at least two choices. Choices left blank will not be used.';
$string['fractionsaddwrong'] = 'The positive grades you have chosen do not add up to 100%<br />Instead, they add up to {$a}%<br />Do you want to go back and fix this question?';
$string['fractionsnomax'] = 'One of the choices should be 100%, so that it is<br />possible to get a full grade for this question.<br />Do you want to go back and fix this question?';
$string['choiceno'] = 'Choice {$a}';
$string['choices'] = 'Available choices';
$string['incorrectfeedback'] = 'For any incorrect response';
$string['multichoice'] = 'Multiple choice';
$string['multichoice_help'] = 'In response to a question (that may include a image) the respondent chooses from multiple answers. There are two types of multiple choice questions - one answer and multiple answer.';
......@@ -51,10 +54,15 @@ $string['multichoice_link'] = 'question/type/multichoice';
$string['multichoicesummary'] = 'Allows the selection of a single or multiple responses from a pre-defined list.';
$string['notenoughanswers'] = 'This type of question requires at least {$a} choices';
$string['overallcorrectfeedback'] = 'Feedback for any correct response';
$string['overallfeedback'] = 'Overall Feedback';
$string['overallfeedback'] = 'Overall feedback';
$string['overallincorrectfeedback'] = 'Feedback for any incorrect response';
$string['overallpartiallycorrectfeedback'] = 'Feedback for any partially correct response';
$string['partiallycorrectfeedback'] = 'For any partially correct response';
$string['pleaseselectananswer'] = 'Please select an answer.';
$string['pleaseselectatleastoneanswer'] = 'Please select at least one answer.';
$string['selectmulti'] = 'Select one or more:';
$string['selectone'] = 'Select one:';
$string['shuffleanswers'] = 'Shuffle the choices?';
$string['shuffleanswers_help'] = 'If enabled, the order of the answers is randomly shuffled for each attempt, provided that "Shuffle within questions" in the quiz settings is also enabled.';
$string['singleanswer'] = 'Choose one answer.';
$string['toomanyselected'] = 'You have selected too many options.';
<?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/>.
/**
* Multiple choice question definition classes.
*
* @package qtype_multichoice
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Base class for multiple choice questions. The parts that are common to
* single select and multiple select.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class qtype_multichoice_base extends question_graded_automatically {
const LAYOUT_DROPDOWN = 0;
const LAYOUT_VERTICAL = 1;
const LAYOUT_HORIZONTAL = 2;
public $answers;
public $shuffleanswers;
public $answernumbering;
public $layout = self::LAYOUT_VERTICAL;
public $correctfeedback;
public $partiallycorrectfeedback;
public $incorrectfeedback;
protected $order = null;
public function init_first_step(question_attempt_step $step) {
if ($step->has_qt_var('_order')) {
$this->order = explode(',', $step->get_qt_var('_order'));
} else {
$this->order = array_keys($this->answers);
if ($this->shuffleanswers) {
shuffle($this->order);
}
$step->set_qt_var('_order', implode(',', $this->order));
}
}
public function get_question_summary() {
$question = $this->html_to_text($this->questiontext);
$choices = array();
foreach ($this->order as $ansid) {
$choices[] = $this->html_to_text($this->answers[$ansid]->answer);
}
return $question . ': ' . implode('; ', $choices);
}
public function get_order(question_attempt $qa) {
$this->init_order($qa);
return $this->order;
}
protected function init_order(question_attempt $qa) {
if (is_null($this->order)) {
$this->order = explode(',', $qa->get_step(0)->get_qt_var('_order'));
}
}
abstract public function get_response(question_attempt $qa);
abstract public function is_choice_selected($response, $value);
function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
$itemid = reset($args);
if ($component == 'qtype_multichoice' && in_array($filearea,
array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
$state = $qa->get_state();
if (!$state->is_finished()) {
$response = $qa->get_last_qt_data();
if (!$this->is_gradable_response($response)) {
return false;
}
list($notused, $state) = $qa->get_question()->grade_response($response);
}
return $options->feedback && $state == $filearea;
} else if ($component == 'question' && $filearea == 'answerfeedback') {
$answerid = reset($args); // itemid is answer id.
$response = $this->get_response($qa);
$isselected = false;
foreach ($this->order as $value => $ansid) {
if ($ansid == $answerid) {
$isselected = $this->is_choice_selected($response, $value);
break;
}
}
// $options->suppresschoicefeedback is a hack specific to the
// oumultiresponse question type. It would be good to refactor to
// avoid refering to it here.
return $options->feedback && empty($options->suppresschoicefeedback) &&
$isselected;
} else {
return parent::check_file_access($qa, $options, $component, $filearea, $args, $forcedownload);
}
}
}
/**
* Represents a multiple choice question where only one choice should be selected.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multichoice_single_question extends qtype_multichoice_base {
public function get_renderer() {
global $PAGE; // TODO get rid of this global.
return $PAGE->get_renderer('qtype_multichoice', 'single');
}
public function get_min_fraction() {
$minfraction = 0;
foreach ($this->answers as $ans) {
$minfraction = min($minfraction, $ans->fraction);
}
return $minfraction;
}
/**
* Return an array of the question type variables that could be submitted
* as part of a question of this type, with their types, so they can be
* properly cleaned.
* @return array variable name => PARAM_... constant.
*/
public function get_expected_data() {
return array('answer' => PARAM_INT);
}
public function summarise_response(array $response) {
if (!array_key_exists('answer', $response) ||
!array_key_exists($response['answer'], $this->order)) {
return null;
}
$ansid = $this->order[$response['answer']];
return $this->html_to_text($this->answers[$ansid]->answer);
}
public function classify_response(array $response) {
if (!array_key_exists('answer', $response) ||
!array_key_exists($response['answer'], $this->order)) {
return array($this->id => question_classified_response::no_response());
}
$choiceid = $this->order[$response['answer']];
$ans = $this->answers[$choiceid];
return array($this->id => new question_classified_response($choiceid,
$this->html_to_text($ans->answer), $ans->fraction));
}
public function get_correct_response() {
foreach ($this->order as $key => $answerid) {
if (question_state::graded_state_for_fraction(
$this->answers[$answerid]->fraction)->is_correct()) {
return array('answer' => $key);
}
}
return array();
}
public function is_same_response(array $prevresponse, array $newresponse) {
return question_utils::arrays_same_at_key($prevresponse, $newresponse, 'answer');
}
public function is_complete_response(array $response) {
return array_key_exists('answer', $response);
}
public function is_gradable_response(array $response) {
return $this->is_complete_response($response);
}
public function grade_response(array $response) {
if (array_key_exists('answer', $response) &&
array_key_exists($response['answer'], $this->order)) {
$fraction = $this->answers[$this->order[$response['answer']]]->fraction;
} else {
$fraction = 0;
}
return array($fraction, question_state::graded_state_for_fraction($fraction));
}
public function get_validation_error(array $response) {
if ($this->is_gradable_response($response)) {
return '';
}
return get_string('pleaseselectananswer', 'qtype_multichoice');
}
public function get_response(question_attempt $qa) {
return $qa->get_last_qt_var('answer', -1);
}
public function is_choice_selected($response, $value) {
return $response == $value;
}
}
/**
* Represents a multiple choice question where multiple choices can be selected.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later