diff --git a/question/todo/diffstat.txt b/question/todo/diffstat.txt
index 804ebe92daa81..bc91606a9c552 100644
--- a/question/todo/diffstat.txt
+++ b/question/todo/diffstat.txt
@@ -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 +-
diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php
index 5419fb48afad5..82f55da0728bf 100644
--- a/question/type/edit_question_form.php
+++ b/question/type/edit_question_form.php
@@ -425,6 +425,7 @@ public function set_data($question) {
}
}
}
+
// subclass adds data_preprocessing code here
$question = $this->data_preprocessing($question);
@@ -478,6 +479,35 @@ protected function data_preprocessing_answers($question) {
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;
diff --git a/question/type/multichoice/db/install.xml b/question/type/multichoice/db/install.xml
index da93831b65c7f..3516b9ebcc375 100644
--- a/question/type/multichoice/db/install.xml
+++ b/question/type/multichoice/db/install.xml
@@ -18,7 +18,8 @@
-
+
+
diff --git a/question/type/multichoice/db/upgrade.php b/question/type/multichoice/db/upgrade.php
index 3c8eed3a33fc9..b3ce830ef05f0 100644
--- a/question/type/multichoice/db/upgrade.php
+++ b/question/type/multichoice/db/upgrade.php
@@ -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;
+}
diff --git a/question/type/multichoice/edit_multichoice_form.php b/question/type/multichoice/edit_multichoice_form.php
index 44efd4bb1068a..a1cf2e58c3551 100644
--- a/question/type/multichoice/edit_multichoice_form.php
+++ b/question/type/multichoice/edit_multichoice_form.php
@@ -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 © 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 © 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 @@ function definition_inner(&$mform) {
$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 @@ function validation($data, $files) {
$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 @@ function validation($data, $files) {
}
return $errors;
}
+
+ public function qtype() {
+ return 'multichoice';
+ }
}
diff --git a/question/type/multichoice/lang/en/qtype_multichoice.php b/question/type/multichoice/lang/en/qtype_multichoice.php
index bc00a317edf57..e569525bf962e 100644
--- a/question/type/multichoice/lang/en/qtype_multichoice.php
+++ b/question/type/multichoice/lang/en/qtype_multichoice.php
@@ -27,13 +27,18 @@
$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%
Instead, they add up to {$a}%';
@@ -42,8 +47,6 @@
$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%
Instead, they add up to {$a}%
Do you want to go back and fix this question?';
$string['fractionsnomax'] = 'One of the choices should be 100%, so that it is
possible to get a full grade for this question.
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['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.';
diff --git a/question/type/multichoice/question.php b/question/type/multichoice/question.php
new file mode 100644
index 0000000000000..f3fa6afa4a045
--- /dev/null
+++ b/question/type/multichoice/question.php
@@ -0,0 +1,426 @@
+.
+
+
+/**
+ * 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
+ */
+class qtype_multichoice_multi_question extends qtype_multichoice_base {
+ public function get_renderer() {
+ global $PAGE; // TODO get rid of this global.
+ return $PAGE->get_renderer('qtype_multichoice', 'multi');
+ }
+
+ public function get_min_fraction() {
+ return 0;
+ }
+
+ public function clear_wrong_from_response(array $response) {
+ foreach ($this->order as $key => $ans) {
+ if (array_key_exists($this->field($key), $response) &&
+ question_state::graded_state_for_fraction(
+ $this->answers[$ans]->fraction)->is_incorrect()) {
+ $response[$this->field($key)] = 0;
+ }
+ }
+ return $response;
+ }
+
+ public function get_num_parts_right(array $response) {
+ $numright = 0;
+ foreach ($this->order as $key => $ans) {
+ $fieldname = $this->field($key);
+ if (!array_key_exists($fieldname, $response) || !$response[$fieldname]) {
+ continue;
+ }
+
+ if (!question_state::graded_state_for_fraction(
+ $this->answers[$ans]->fraction)->is_incorrect()) {
+ $numright += 1;
+ }
+ }
+ return array($numright, count($this->order));
+ }
+
+ /**
+ * @param integer $key choice number
+ * @return string the question-type variable name.
+ */
+ protected function field($key) {
+ return 'choice' . $key;
+ }
+
+ public function get_expected_data() {
+ $expected = array();
+ foreach ($this->order as $key => $notused) {
+ $expected[$this->field($key)] = PARAM_BOOL;
+ }
+ return $expected;
+ }
+
+ public function summarise_response(array $response) {
+ $selectedchoices = array();
+ foreach ($this->order as $key => $ans) {
+ $fieldname = $this->field($key);
+ if (array_key_exists($fieldname, $response) && $response[$fieldname]) {
+ $selectedchoices[] = $this->html_to_text($this->answers[$ans]->answer);
+ }
+ }
+ if (empty($selectedchoices)) {
+ return null;
+ }
+ return implode('; ', $selectedchoices);
+ }
+
+ public function classify_response(array $response) {
+ $selectedchoices = array();
+ foreach ($this->order as $key => $ansid) {
+ $fieldname = $this->field($key);
+ if (array_key_exists($fieldname, $response) && $response[$fieldname]) {
+ $selectedchoices[$ansid] = 1;
+ }
+ }
+ $choices = array();
+ foreach ($this->answers as $ansid => $ans) {
+ if (isset($selectedchoices[$ansid])) {
+ $choices[$ansid] = new question_classified_response($ansid,
+ $this->html_to_text($ans->answer), $ans->fraction);
+ }
+ }
+ return $choices;
+ }
+
+ public function get_correct_response() {
+ $response = array();
+ foreach ($this->order as $key => $ans) {
+ if (!question_state::graded_state_for_fraction(
+ $this->answers[$ans]->fraction)->is_incorrect()) {
+ $response[$this->field($key)] = 1;
+ }
+ }
+ return $response;
+ }
+
+ public function is_same_response(array $prevresponse, array $newresponse) {
+ foreach ($this->order as $key => $notused) {
+ $fieldname = $this->field($key);
+ if (!question_utils::arrays_same_at_key($prevresponse, $newresponse, $fieldname)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function is_complete_response(array $response) {
+ foreach ($this->order as $key => $notused) {
+ if (!empty($response[$this->field($key)])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function is_gradable_response(array $response) {
+ return $this->is_complete_response($response);
+ }
+
+ /**
+ * @param array $response responses, as returned by {@link question_attempt_step::get_qt_data()}.
+ * @return integer the number of choices that were selected. in this response.
+ */
+ public function get_num_selected_choices(array $response) {
+ $numselected = 0;
+ foreach ($response as $key => $value) {
+ if (!empty($value)) {
+ $numselected += 1;
+ }
+ }
+ return $numselected;
+ }
+
+ /**
+ * @return integer the number of choices that are correct.
+ */
+ public function get_num_correct_choices() {
+ $numcorrect = 0;
+ foreach ($this->answers as $ans) {
+ if (!question_state::graded_state_for_fraction($ans->fraction)->is_incorrect()) {
+ $numcorrect += 1;
+ }
+ }
+ return $numcorrect;
+ }
+
+ public function grade_response(array $response) {
+ $fraction = 0;
+ foreach ($this->order as $key => $ansid) {
+ if (!empty($response[$this->field($key)])) {
+ $fraction += $this->answers[$ansid]->fraction;
+ }
+ }
+ $fraction = min(max(0, $fraction), 1.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('pleaseselectatleastoneanswer', 'qtype_multichoice');
+ }
+
+ /**
+ * Disable those hint settings that we don't want when the student has selected
+ * more choices than the number of right choices. This avoids giving the game away.
+ * @param question_hint_with_parts $hint a hint.
+ */
+ protected function disable_hint_settings_when_too_many_selected(question_hint_with_parts $hint) {
+ $hint->clearwrong = false;
+ }
+
+ public function get_hint($hintnumber, question_attempt $qa) {
+ $hint = parent::get_hint($hintnumber, $qa);
+ if (is_null($hint)) {
+ return $hint;
+ }
+
+ if ($this->get_num_selected_choices($qa->get_last_qt_data()) >
+ $this->get_num_correct_choices()) {
+ $hint = clone($hint);
+ $this->disable_hint_settings_when_too_many_selected($hint);
+ }
+ return $hint;
+ }
+
+ public function get_response(question_attempt $qa) {
+ return $qa->get_last_qt_data();
+ }
+
+ public function is_choice_selected($response, $value) {
+ return !empty($response['choice' . $value]);
+ }
+}
diff --git a/question/type/multichoice/questiontype.php b/question/type/multichoice/questiontype.php
index 02f8087ec8cad..1374e06a46a75 100644
--- a/question/type/multichoice/questiontype.php
+++ b/question/type/multichoice/questiontype.php
@@ -1,41 +1,47 @@
.
+
+
/**
* The questiontype class for the multiple choice question type.
*
- * Note, This class contains some special features in order to make the
- * question type embeddable within a multianswer (cloze) question
- *
- * @package questionbank
- * @subpackage questiontypes
+ * @package qtype
+ * @subpackage multichoice
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class question_multichoice_qtype extends default_questiontype {
- function name() {
- return 'multichoice';
- }
-
- function get_question_options(&$question) {
+/**
+ * The multiple choice question type.
+ *
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice extends question_type {
+ public function get_question_options($question) {
global $DB, $OUTPUT;
- // Get additional information from database
- // and attach it to the question object
- if (!$question->options = $DB->get_record('question_multichoice', array('question' => $question->id))) {
- echo $OUTPUT->notification('Error: Missing question options for multichoice question'.$question->id.'!');
- return false;
- }
-
- list ($usql, $params) = $DB->get_in_or_equal(explode(',', $question->options->answers));
- if (!$question->options->answers = $DB->get_records_select('question_answers', "id $usql", $params, 'id')) {
- echo $OUTPUT->notification('Error: Missing question answers for multichoice question'.$question->id.'!');
- return false;
- }
-
- return true;
+ $question->options = $DB->get_record('question_multichoice', array('question' => $question->id), '*', MUST_EXIST);
+ parent::get_question_options($question);
}
- function save_question_options($question) {
+ public function save_question_options($question) {
global $DB;
$context = $question->context;
- $result = new stdClass;
+ $result = new stdClass();
$oldanswers = $DB->get_records('question_answers',
array('question' => $question->id), 'id ASC');
@@ -106,7 +112,7 @@ function save_question_options($question) {
$options = $DB->get_record('question_multichoice', array('question' => $question->id));
if (!$options) {
- $options = new stdClass;
+ $options = new stdClass();
$options->question = $question->id;
$options->correctfeedback = '';
$options->partiallycorrectfeedback = '';
@@ -121,6 +127,7 @@ function save_question_options($question) {
}
$options->answernumbering = $question->answernumbering;
$options->shuffleanswers = $question->shuffleanswers;
+ $options->shownumcorrect = !empty($question->shownumcorrect);
$options->correctfeedback = $this->import_or_save_files($question->correctfeedback,
$context, 'qtype_multichoice', 'correctfeedback', $question->id);
$options->correctfeedbackformat = $question->correctfeedback['format'];
@@ -146,286 +153,76 @@ function save_question_options($question) {
return $result;
}
}
-
- return true;
- }
-
- function delete_question($questionid, $contextid) {
- global $DB;
- $DB->delete_records('question_multichoice', array('question' => $questionid));
-
- parent::delete_question($questionid, $contextid);
}
- function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
- // create an array of answerids ??? why so complicated ???
- $answerids = array_values(array_map(create_function('$val',
- 'return $val->id;'), $question->options->answers));
- // Shuffle the answers if required
- if (!empty($cmoptions->shuffleanswers) and !empty($question->options->shuffleanswers)) {
- $answerids = swapshuffle($answerids);
- }
- $state->options->order = $answerids;
- // Create empty responses
- if ($question->options->single) {
- $state->responses = array('' => '');
+ protected function make_question_instance($questiondata) {
+ question_bank::load_question_definition_classes($this->name());
+ if ($questiondata->options->single) {
+ $class = 'qtype_multichoice_single_question';
} else {
- $state->responses = array();
+ $class = 'qtype_multichoice_multi_question';
}
- return true;
+ return new $class();
}
-
- function restore_session_and_responses(&$question, &$state) {
- // The serialized format for multiple choice quetsions
- // is an optional comma separated list of answer ids (the order of the
- // answers) followed by a colon, followed by another comma separated
- // list of answer ids, which are the radio/checkboxes that were
- // ticked.
- // E.g. 1,3,2,4:2,4 means that the answers were shown in the order
- // 1, 3, 2 and then 4 and the answers 2 and 4 were checked.
-
- $pos = strpos($state->responses[''], ':');
- if (false === $pos) { // No order of answers is given, so use the default
- $state->options->order = array_keys($question->options->answers);
- } else { // Restore the order of the answers
- $state->options->order = explode(',', substr($state->responses[''], 0, $pos));
- $state->responses[''] = substr($state->responses[''], $pos + 1);
- }
- // Restore the responses
- // This is done in different ways if only a single answer is allowed or
- // if multiple answers are allowed. For single answers the answer id is
- // saved in $state->responses[''], whereas for the multiple answers case
- // the $state->responses array is indexed by the answer ids and the
- // values are also the answer ids (i.e. key = value).
- if (empty($state->responses[''])) { // No previous responses
- $state->responses = array('' => '');
- } else {
- if ($question->options->single) {
- $state->responses = array('' => $state->responses['']);
- } else {
- // Get array of answer ids
- $state->responses = explode(',', $state->responses['']);
- // Create an array indexed by these answer ids
- $state->responses = array_flip($state->responses);
- // Set the value of each element to be equal to the index
- array_walk($state->responses, create_function('&$a, $b',
- '$a = $b;'));
- }
- }
- return true;
- }
-
- function save_session_and_responses(&$question, &$state) {
- global $DB;
- // Bundle the answer order and the responses into the legacy answer
- // field.
- // The serialized format for multiple choice quetsions
- // is (optionally) a comma separated list of answer ids
- // followed by a colon, followed by another comma separated
- // list of answer ids, which are the radio/checkboxes that were
- // ticked.
- // E.g. 1,3,2,4:2,4 means that the answers were shown in the order
- // 1, 3, 2 and then 4 and the answers 2 and 4 were checked.
- $responses = implode(',', $state->options->order) . ':';
- $responses .= implode(',', $state->responses);
-
- // Set the legacy answer field
- $DB->set_field('question_states', 'answer', $responses, array('id' => $state->id));
- return true;
+ protected function make_hint($hint) {
+ return question_hint_with_parts::load_from_record($hint);
}
- function get_correct_responses(&$question, &$state) {
- if ($question->options->single) {
- foreach ($question->options->answers as $answer) {
- if (((int) $answer->fraction) === 1) {
- return array('' => $answer->id);
- }
- }
- return null;
+ protected function initialise_question_instance(question_definition $question, $questiondata) {
+ parent::initialise_question_instance($question, $questiondata);
+ $question->shuffleanswers = $questiondata->options->shuffleanswers;
+ $question->answernumbering = $questiondata->options->answernumbering;
+ if (!empty($questiondata->options->layout)) {
+ $question->layout = $questiondata->options->layout;
} else {
- $responses = array();
- foreach ($question->options->answers as $answer) {
- if (((float) $answer->fraction) > 0.0) {
- $responses[$answer->id] = (string) $answer->id;
- }
- }
- return empty($responses) ? null : $responses;
+ $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
}
- }
-
- function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
- global $CFG;
-
- // required by file api
- $context = $this->get_context_by_category_id($question->category);
- $component = 'qtype_' . $question->qtype;
-
- $answers = &$question->options->answers;
- $correctanswers = $this->get_correct_responses($question, $state);
- $readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
-
- $formatoptions = new stdClass;
- $formatoptions->noclean = true;
- $formatoptions->para = false;
-
- // Print formulation
- $questiontext = format_text($question->questiontext, $question->questiontextformat,
- $formatoptions, $cmoptions->course);
- $answerprompt = ($question->options->single) ? get_string('singleanswer', 'quiz') :
- get_string('multipleanswers', 'quiz');
-
- // Print each answer in a separate row
- foreach ($state->options->order as $key => $aid) {
- $answer = &$answers[$aid];
- $checked = '';
- $chosen = false;
-
- if ($question->options->single) {
- $type = 'type="radio"';
- $name = "name=\"{$question->name_prefix}\"";
- if (isset($state->responses['']) and $aid == $state->responses['']) {
- $checked = 'checked="checked"';
- $chosen = true;
- }
- } else {
- $type = ' type="checkbox" ';
- $name = "name=\"{$question->name_prefix}{$aid}\"";
- if (isset($state->responses[$aid])) {
- $checked = 'checked="checked"';
- $chosen = true;
- }
- }
-
- $a = new stdClass;
- $a->id = $question->name_prefix . $aid;
- $a->class = '';
- $a->feedbackimg = '';
- // Print the control
- $a->control = "id\" $name $checked $type value=\"$aid\" />";
+ $question->correctfeedback = $questiondata->options->correctfeedback;
+ $question->partiallycorrectfeedback = $questiondata->options->partiallycorrectfeedback;
+ $question->incorrectfeedback = $questiondata->options->incorrectfeedback;
+ $question->shownumcorrect = !$questiondata->options->single && $questiondata->options->shownumcorrect;
- if ($options->correct_responses && $answer->fraction > 0) {
- $a->class = question_get_feedback_class(1);
- }
- if (($options->feedback && $chosen) || $options->correct_responses) {
- if ($type == ' type="checkbox" ') {
- $a->feedbackimg = question_get_feedback_image($answer->fraction > 0 ? 1 : 0, $chosen && $options->feedback);
- } else {
- $a->feedbackimg = question_get_feedback_image($answer->fraction, $chosen && $options->feedback);
- }
- }
-
- // Print the answer text
- $a->text = $this->number_in_style($key, $question->options->answernumbering) .
- format_text($answer->answer, $answer->answerformat, $formatoptions, $cmoptions->course);
-
- // Print feedback if feedback is on
- if (($options->feedback || $options->correct_responses) && $checked) {
- // feedback for each answer
- $a->feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id);
- $a->feedback = format_text($a->feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course);
- } else {
- $a->feedback = '';
- }
-
- $anss[] = clone($a);
- }
-
- $feedback = '';
- if ($options->feedback) {
- if ($state->raw_grade >= $question->maxgrade/1.01) {
- $feedback = $question->options->correctfeedback;
- $feedbacktype = 'correctfeedback';
- } else if ($state->raw_grade > 0) {
- $feedback = $question->options->partiallycorrectfeedback;
- $feedbacktype = 'partiallycorrectfeedback';
- } else {
- $feedback = $question->options->incorrectfeedback;
- $feedbacktype = 'incorrectfeedback';
- }
+ $this->initialise_question_answers($question, $questiondata);
+ }
- $feedback = quiz_rewrite_question_urls($feedback, 'pluginfile.php', $context->id, $component, $feedbacktype, array($state->attempt, $state->question), $question->id);
- $feedbackformat = $feedbacktype . 'format';
- $feedback = format_text($feedback, $question->options->$feedbackformat, $formatoptions, $cmoptions->course);
- }
+ public function delete_question($questionid, $contextid) {
+ global $DB;
+ $DB->delete_records('question_multichoice', array('question' => $questionid));
- include("$CFG->dirroot/question/type/multichoice/display.html");
+ parent::delete_question($questionid, $contextid);
}
- function compare_responses($question, $state, $teststate) {
- if ($question->options->single) {
- if (!empty($state->responses[''])) {
- return $state->responses[''] == $teststate->responses[''];
- } else {
- return empty($teststate->response['']);
- }
- } else {
- foreach ($question->options->answers as $ansid => $notused) {
- if (empty($state->responses[$ansid]) != empty($teststate->responses[$ansid])) {
- return false;
- }
- }
- return true;
+ public function get_random_guess_score($questiondata) {
+ $totalfraction = 0;
+ foreach ($questiondata->options->answers as $answer) {
+ $totalfraction += $answer->fraction;
}
+ return $totalfraction / count($questiondata->options->answers);
}
- function grade_responses(&$question, &$state, $cmoptions) {
- $state->raw_grade = 0;
- if($question->options->single) {
- $response = reset($state->responses);
- if ($response) {
- $state->raw_grade = $question->options->answers[$response]->fraction;
- }
- } else {
- foreach ($state->responses as $response) {
- if ($response) {
- $state->raw_grade += $question->options->answers[$response]->fraction;
- }
- }
- }
-
- // Make sure we don't assign negative or too high marks
- $state->raw_grade = min(max((float) $state->raw_grade,
- 0.0), 1.0) * $question->maxgrade;
-
- // Apply the penalty for this attempt
- $state->penalty = $question->penalty * $question->maxgrade;
+ function get_possible_responses($questiondata) {
+ if ($questiondata->options->single) {
+ $responses = array();
- // mark the state as graded
- $state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
+ foreach ($questiondata->options->answers as $aid => $answer) {
+ $responses[$aid] = new question_possible_response($answer->answer,
+ $answer->fraction);
+ }
- return true;
- }
+ $responses[null] = question_possible_response::no_response();
+ return array($questiondata->id => $responses);
+ } else {
+ $parts = array();
- // ULPGC ecastro
- function get_actual_response($question, $state) {
- $answers = $question->options->answers;
- $responses = array();
- if (!empty($state->responses)) {
- foreach ($state->responses as $aid =>$rid){
- if (!empty($answers[$rid])) {
- $responses[] = $answers[$rid]->answer;
- }
+ foreach ($questiondata->options->answers as $aid => $answer) {
+ $parts[$aid] = array($aid =>
+ new question_possible_response($answer->answer, $answer->fraction));
}
- } else {
- $responses[] = '';
- }
- return $responses;
- }
- /**
- * @param object $question
- * @return mixed either a integer score out of 1 that the average random
- * guess by a student might give or an empty string which means will not
- * calculate.
- */
- function get_random_guess_score($question) {
- $totalfraction = 0;
- foreach ($question->options->answers as $answer){
- $totalfraction += $answer->fraction;
+ return $parts;
}
- return $totalfraction / count($question->options->answers);
}
/**
@@ -434,61 +231,12 @@ function get_random_guess_score($question) {
* language file, and a case in the switch statement in number_in_style,
* and it should be listed in the definition of this column in install.xml.
*/
- function get_numbering_styles() {
- return array('abc', 'ABCD', '123', 'none');
- }
-
- function number_html($qnum) {
- return '' . $qnum . '. ';
- }
-
- /**
- * @param int $num The number, starting at 0.
- * @param string $style The style to render the number in. One of the ones returned by $numberingoptions.
- * @return string the number $num in the requested style.
- */
- function number_in_style($num, $style) {
- switch($style) {
- case 'abc':
- return $this->number_html(chr(ord('a') + $num));
- case 'ABCD':
- return $this->number_html(chr(ord('A') + $num));
- case '123':
- return $this->number_html(($num + 1));
- case 'none':
- return '';
- default:
- return 'ERR';
+ public static function get_numbering_styles() {
+ $styles = array();
+ foreach (array('abc', 'ABCD', '123', 'iii', 'IIII', 'none') as $numberingoption) {
+ $styles[$numberingoption] = get_string('answernumbering' . $numberingoption, 'qtype_multichoice');
}
- }
-
- /**
- * Runs all the code required to set up and save an essay question for testing purposes.
- * Alternate DB table prefix may be used to facilitate data deletion.
- */
- function generate_test($name, $courseid = null) {
- global $DB;
- list($form, $question) = parent::generate_test($name, $courseid);
- $question->category = $form->category;
- $form->questiontext = "How old is the sun?";
- $form->generalfeedback = "General feedback";
- $form->penalty = 0.1;
- $form->single = 1;
- $form->shuffleanswers = 1;
- $form->answernumbering = 'abc';
- $form->noanswers = 3;
- $form->answer = array('Ancient', '5 billion years old', '4.5 billion years old');
- $form->fraction = array(0.3, 0.9, 1);
- $form->feedback = array('True, but lacking in accuracy', 'Close, but no cigar!', 'Yep, that is it!');
- $form->correctfeedback = 'Excellent!';
- $form->incorrectfeedback = 'Nope!';
- $form->partiallycorrectfeedback = 'Not bad';
-
- if ($courseid) {
- $course = $DB->get_record('course', array('id' => $courseid));
- }
-
- return $this->save_question($question, $form);
+ return $styles;
}
function move_files($questionid, $oldcontextid, $newcontextid) {
@@ -514,39 +262,4 @@ protected function delete_files($questionid, $contextid) {
$fs->delete_area_files($contextid, 'qtype_multichoice', 'partiallycorrectfeedback', $questionid);
$fs->delete_area_files($contextid, 'qtype_multichoice', 'incorrectfeedback', $questionid);
}
-
- function check_file_access($question, $state, $options, $contextid, $component,
- $filearea, $args) {
- $itemid = reset($args);
-
- if (empty($question->maxgrade)) {
- $question->maxgrade = $question->defaultgrade;
- }
-
- if (in_array($filearea, array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
- $result = $options->feedback && ($itemid == $question->id);
- if (!$result) {
- return false;
- }
- if ($state->raw_grade >= $question->maxgrade/1.01) {
- $feedbacktype = 'correctfeedback';
- } else if ($state->raw_grade > 0) {
- $feedbacktype = 'partiallycorrectfeedback';
- } else {
- $feedbacktype = 'incorrectfeedback';
- }
- if ($feedbacktype != $filearea) {
- return false;
- }
- return true;
- } else if ($component == 'question' && $filearea == 'answerfeedback') {
- return $options->feedback && (array_key_exists($itemid, $question->options->answers));
- } else {
- return parent::check_file_access($question, $state, $options, $contextid, $component,
- $filearea, $args);
- }
- }
}
-
-// Register this question type with the question bank.
-question_register_questiontype(new question_multichoice_qtype());
diff --git a/question/type/multichoice/renderer.php b/question/type/multichoice/renderer.php
new file mode 100644
index 0000000000000..aef9399ec0898
--- /dev/null
+++ b/question/type/multichoice/renderer.php
@@ -0,0 +1,294 @@
+.
+
+
+/**
+ * Multiple choice question renderer 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 generating the bits of output common to multiple choice
+ * single and multiple questions.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedback_renderer {
+ abstract protected function get_input_type();
+
+ abstract protected function get_input_name(question_attempt $qa, $value);
+
+ abstract protected function get_input_value($value);
+
+ abstract protected function get_input_id(question_attempt $qa, $value);
+
+ /**
+ * Whether a choice should be considered right, wrong or partially right.
+ * @param question_answer $ans representing one of the choices.
+ * @return fload 1.0, 0.0 or something in between, respectively.
+ */
+ abstract protected function is_right(question_answer $ans);
+
+ abstract protected function prompt();
+
+ public function formulation_and_controls(question_attempt $qa,
+ question_display_options $options) {
+
+ $question = $qa->get_question();
+ $order = $question->get_order($qa);
+ $response = $question->get_response($qa);
+
+ $inputname = $qa->get_qt_field_name('answer');
+ $inputattributes = array(
+ 'type' => $this->get_input_type(),
+ 'name' => $inputname,
+ );
+
+ if ($options->readonly) {
+ $inputattributes['disabled'] = 'disabled';
+ }
+
+ $radiobuttons = array();
+ $feedbackimg = array();
+ $feedback = array();
+ $classes = array();
+ foreach ($order as $value => $ansid) {
+ $ans = $question->answers[$ansid];
+ $inputattributes['name'] = $this->get_input_name($qa, $value);
+ $inputattributes['value'] = $this->get_input_value($value);
+ $inputattributes['id'] = $this->get_input_id($qa, $value);
+ $isselected = $question->is_choice_selected($response, $value);
+ if ($isselected) {
+ $inputattributes['checked'] = 'checked';
+ } else {
+ unset($inputattributes['checked']);
+ }
+ $hidden = '';
+ if (!$options->readonly && $this->get_input_type() == 'checkbox') {
+ $hidden = html_writer::empty_tag('input', array(
+ 'type' => 'hidden',
+ 'name' => $inputattributes['name'],
+ 'value' => 0,
+ ));
+ }
+ $radiobuttons[] = $hidden . html_writer::empty_tag('input', $inputattributes) .
+ html_writer::tag('label', $this->number_in_style($value, $question->answernumbering) .
+ $question->format_text($ans->answer, $qa,
+ 'question', 'answer', $ansid), array('for' => $inputattributes['id']));
+
+ // $options->suppresschoicefeedback is a hack specific to the
+ // oumultiresponse question type. It would be good to refactor to
+ // avoid refering to it here.
+ if ($options->feedback && empty($options->suppresschoicefeedback) &&
+ $isselected && trim($ans->feedback)) {
+ $feedback[] = html_writer::tag('div',
+ $question->format_text($ans->feedback, $qa, 'question', 'answerfeedback', $ansid),
+ array('class' => 'specificfeedback'));
+ } else {
+ $feedback[] = '';
+ }
+ $class = 'r' . ($value % 2);
+ if ($options->correctness && $isselected) {
+ $feedbackimg[] = $this->feedback_image($this->is_right($ans));
+ $class .= ' ' . $this->feedback_class($this->is_right($ans));
+ } else {
+ $feedbackimg[] = '';
+ }
+ $classes[] = $class;
+ }
+
+ $result = '';
+ $result .= html_writer::tag('div', $question->format_questiontext($qa),
+ array('class' => 'qtext'));
+
+ $result .= html_writer::start_tag('div', array('class' => 'ablock'));
+ $result .= html_writer::tag('div', $this->prompt(), array('class' => 'prompt'));
+
+ $result .= html_writer::start_tag('div', array('class' => 'answer'));
+ foreach ($radiobuttons as $key => $radio) {
+ $result .= html_writer::tag('span', $radio . ' ' . $feedbackimg[$key] . $feedback[$key],
+ array('class' => $classes[$key])) . "\n";
+ }
+ $result .= html_writer::end_tag('div'); // answer
+
+ $result .= html_writer::end_tag('div'); // ablock
+
+ if ($qa->get_state() == question_state::$invalid) {
+ $result .= html_writer::nonempty_tag('div',
+ $question->get_validation_error($qa->get_last_qt_data()),
+ array('class' => 'validationerror'));
+ }
+
+ return $result;
+ }
+
+ protected function number_html($qnum) {
+ return $qnum . '. ';
+ }
+
+ /**
+ * @param int $num The number, starting at 0.
+ * @param string $style The style to render the number in. One of the
+ * options returned by {@link qtype_multichoice:;get_numbering_styles()}.
+ * @return string the number $num in the requested style.
+ */
+ protected function number_in_style($num, $style) {
+ switch($style) {
+ case 'abc':
+ $number = chr(ord('a') + $num);
+ break;
+ case 'ABCD':
+ $number = chr(ord('A') + $num);
+ break;
+ case '123':
+ $number = $num + 1;
+ break;
+ case 'iii':
+ $number = question_utils::int_to_roman($num + 1);
+ break;
+ case 'IIII':
+ $number = strtoupper(question_utils::int_to_roman($num + 1));
+ break;
+ case 'none':
+ return '';
+ default:
+ return 'ERR';
+ }
+ return $this->number_html($number);
+ }
+
+ public function specific_feedback(question_attempt $qa) {
+ return $this->combined_feedback($qa);
+ }
+}
+
+
+/**
+ * Subclass for generating the bits of output specific to multiple choice
+ * single questions.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice_single_renderer extends qtype_multichoice_renderer_base {
+ protected function get_input_type() {
+ return 'radio';
+ }
+
+ protected function get_input_name(question_attempt $qa, $value) {
+ return $qa->get_qt_field_name('answer');
+ }
+
+ protected function get_input_value($value) {
+ return $value;
+ }
+
+ protected function get_input_id(question_attempt $qa, $value) {
+ return $qa->get_qt_field_name('answer' . $value);
+ }
+
+ protected function is_right(question_answer $ans) {
+ return $ans->fraction;
+ }
+
+ protected function prompt() {
+ return get_string('selectone', 'qtype_multichoice');
+ }
+
+ public function correct_response(question_attempt $qa) {
+ $question = $qa->get_question();
+
+ foreach ($question->answers as $ansid => $ans) {
+ if (question_state::graded_state_for_fraction($ans->fraction) ==
+ question_state::$gradedright) {
+ return get_string('correctansweris', 'qtype_multichoice',
+ $question->format_text($ans->answer, $qa, 'question', 'answer', $ansid));
+ }
+ }
+
+ return '';
+ }
+}
+
+/**
+ * Subclass for generating the bits of output specific to multiple choice
+ * multi=select questions.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice_multi_renderer extends qtype_multichoice_renderer_base {
+ protected function get_input_type() {
+ return 'checkbox';
+ }
+
+ protected function get_input_name(question_attempt $qa, $value) {
+ return $qa->get_qt_field_name('choice' . $value);
+ }
+
+ protected function get_input_value($value) {
+ return 1;
+ }
+
+ protected function get_input_id(question_attempt $qa, $value) {
+ return $this->get_input_name($qa, $value);
+ }
+
+ protected function is_right(question_answer $ans) {
+ if ($ans->fraction > 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ protected function prompt() {
+ return get_string('selectmulti', 'qtype_multichoice');
+ }
+
+ public function correct_response(question_attempt $qa) {
+ $question = $qa->get_question();
+
+ $right = array();
+ foreach ($question->answers as $ansid => $ans) {
+ if ($ans->fraction > 0) {
+ $right[] = $question->format_text($ans->answer, $qa, 'question', 'answer', $ansid);
+ }
+ }
+
+ if (!empty($right)) {
+ return get_string('correctansweris', 'qtype_multichoice',
+ implode(', ', $right));
+
+ }
+ return '';
+ }
+
+ protected function num_parts_correct(question_attempt $qa) {
+ if ($qa->get_question()->get_num_selected_choices($qa->get_last_qt_data()) >
+ $qa->get_question()->get_num_correct_choices()) {
+ return get_string('toomanyselected', 'qtype_multichoice');
+ }
+
+ return parent::num_parts_correct($qa);
+ }
+}
diff --git a/question/type/multichoice/simpletest/testquestion.php b/question/type/multichoice/simpletest/testquestion.php
new file mode 100644
index 0000000000000..7dbca7d22d407
--- /dev/null
+++ b/question/type/multichoice/simpletest/testquestion.php
@@ -0,0 +1,238 @@
+.
+
+
+/**
+ * Unit tests for the 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
+ */
+
+require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
+
+
+/**
+ * Unit tests for the multiple choice, multiple response question definition class.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice_single_question_test extends UnitTestCase {
+
+ public function test_get_expected_data() {
+ $question = test_question_maker::make_a_multichoice_single_question();
+ $this->assertEqual(array('answer' => PARAM_INT), $question->get_expected_data());
+ }
+
+ public function test_is_complete_response() {
+ $question = test_question_maker::make_a_multichoice_single_question();
+
+ $this->assertFalse($question->is_complete_response(array()));
+ $this->assertTrue($question->is_complete_response(array('answer' => '0')));
+ $this->assertTrue($question->is_complete_response(array('answer' => '2')));
+ }
+
+ public function test_is_gradable_response() {
+ $question = test_question_maker::make_a_multichoice_single_question();
+
+ $this->assertFalse($question->is_gradable_response(array()));
+ $this->assertTrue($question->is_gradable_response(array('answer' => '0')));
+ $this->assertTrue($question->is_gradable_response(array('answer' => '2')));
+ }
+
+ public function test_grading() {
+ $question = test_question_maker::make_a_multichoice_single_question();
+ $question->shuffleanswers = false;
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(1, question_state::$gradedright),
+ $question->grade_response(array('answer' => 0)));
+ $this->assertEqual(array(-0.3333333, question_state::$gradedwrong),
+ $question->grade_response(array('answer' => 1)));
+ $this->assertEqual(array(-0.3333333, question_state::$gradedwrong),
+ $question->grade_response(array('answer' => 2)));
+ }
+
+ public function test_grading_rounding_three_right() {
+ question_bank::load_question_definition_classes('multichoice');
+ $mc = new qtype_multichoice_multi_question();
+ test_question_maker::initialise_a_question($mc);
+ $mc->name = 'Odd numbers';
+ $mc->questiontext = 'Which are the odd numbers?';
+ $mc->generalfeedback = '1, 3 and 5 are the odd numbers.';
+ $mc->qtype = question_bank::get_qtype('multichoice');
+
+ $mc->shuffleanswers = 0;
+ $mc->answernumbering = 'abc';
+
+ test_question_maker::set_standard_combined_feedback_fields($mc);
+
+ $mc->answers = array(
+ 11 => new question_answer('1', 0.3333333, ''),
+ 12 => new question_answer('2', -1, ''),
+ 13 => new question_answer('3', 0.3333333, ''),
+ 14 => new question_answer('4', -1, ''),
+ 15 => new question_answer('5', 0.3333333, ''),
+ 16 => new question_answer('6', -1, ''),
+ );
+
+ $mc->init_first_step(new question_attempt_step());
+
+ list($grade, $state) = $mc->grade_response(
+ array('choice0' => 1, 'choice2' => 1, 'choice4' => 1));
+ $this->assertWithinMargin(1, $grade, 0.000001);
+ $this->assertEqual(question_state::$gradedright, $state);
+ }
+
+ public function test_get_correct_response() {
+ $question = test_question_maker::make_a_multichoice_single_question();
+ $question->shuffleanswers = false;
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array('answer' => 0),
+ $question->get_correct_response());
+ }
+
+ public function test_summarise_response() {
+ $mc = test_question_maker::make_a_multichoice_single_question();
+ $mc->shuffleanswers = false;
+ $mc->init_first_step(new question_attempt_step());
+
+ $summary = $mc->summarise_response(array('answer' => 0),
+ test_question_maker::get_a_qa($mc));
+
+ $this->assertEqual('A', $summary);
+ }
+
+ public function test_classify_response() {
+ $mc = test_question_maker::make_a_multichoice_single_question();
+ $mc->shuffleanswers = false;
+ $mc->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(
+ $mc->id => new question_classified_response(14, 'B', -0.3333333),
+ ), $mc->classify_response(array('answer' => 1)));
+
+ $this->assertEqual(array(
+ $mc->id => question_classified_response::no_response(),
+ ), $mc->classify_response(array()));
+ }
+}
+
+
+/**
+ * Unit tests for the multiple choice, single response question definition class.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice_multi_question_test extends UnitTestCase {
+
+ public function test_get_expected_data() {
+ $question = test_question_maker::make_a_multichoice_multi_question();
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array('choice0' => PARAM_BOOL, 'choice1' => PARAM_BOOL,
+ 'choice2' => PARAM_BOOL, 'choice3' => PARAM_BOOL), $question->get_expected_data());
+ }
+
+ public function test_is_complete_response() {
+ $question = test_question_maker::make_a_multichoice_multi_question();
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertFalse($question->is_complete_response(array()));
+ $this->assertFalse($question->is_complete_response(
+ array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+ $this->assertTrue($question->is_complete_response(array('choice1' => '1')));
+ $this->assertTrue($question->is_complete_response(
+ array('choice0' => '1', 'choice1' => '1', 'choice2' => '1', 'choice3' => '1')));
+ }
+
+ public function test_is_gradable_response() {
+ $question = test_question_maker::make_a_multichoice_multi_question();
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertFalse($question->is_gradable_response(array()));
+ $this->assertFalse($question->is_gradable_response(
+ array('choice0' => '0', 'choice1' => '0', 'choice2' => '0', 'choice3' => '0')));
+ $this->assertTrue($question->is_gradable_response(array('choice1' => '1')));
+ $this->assertTrue($question->is_gradable_response(
+ array('choice0' => '1', 'choice1' => '1', 'choice2' => '1', 'choice3' => '1')));
+ }
+
+ public function test_grading() {
+ $question = test_question_maker::make_a_multichoice_multi_question();
+ $question->shuffleanswers = false;
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(1, question_state::$gradedright),
+ $question->grade_response(array('choice0' => '1', 'choice2' => '1')));
+ $this->assertEqual(array(0.5, question_state::$gradedpartial),
+ $question->grade_response(array('choice0' => '1')));
+ $this->assertEqual(array(0, question_state::$gradedwrong),
+ $question->grade_response(array('choice0' => '1', 'choice1' => '1', 'choice2' => '1')));
+ $this->assertEqual(array(0, question_state::$gradedwrong),
+ $question->grade_response(array('choice1' => '1')));
+ }
+
+ public function test_get_correct_response() {
+ $question = test_question_maker::make_a_multichoice_multi_question();
+ $question->shuffleanswers = false;
+ $question->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array('choice0' => '1', 'choice2' => '1'),
+ $question->get_correct_response());
+ }
+
+ public function test_get_question_summary() {
+ $mc = test_question_maker::make_a_multichoice_single_question();
+ $mc->init_first_step(new question_attempt_step());
+
+ $qsummary = $mc->get_question_summary();
+
+ $this->assertPattern('/' . preg_quote($mc->questiontext) . '/', $qsummary);
+ foreach ($mc->answers as $answer) {
+ $this->assertPattern('/' . preg_quote($answer->answer) . '/', $qsummary);
+ }
+ }
+
+ public function test_summarise_response() {
+ $mc = test_question_maker::make_a_multichoice_multi_question();
+ $mc->shuffleanswers = false;
+ $mc->init_first_step(new question_attempt_step());
+
+ $summary = $mc->summarise_response(array('choice1' => 1, 'choice2' => 1),
+ test_question_maker::get_a_qa($mc));
+
+ $this->assertEqual('B; C', $summary);
+ }
+
+ public function test_classify_response() {
+ $mc = test_question_maker::make_a_multichoice_multi_question();
+ $mc->shuffleanswers = false;
+ $mc->init_first_step(new question_attempt_step());
+
+ $this->assertEqual(array(
+ 13 => new question_classified_response(13, 'A', 0.5),
+ 14 => new question_classified_response(14, 'B', -1.0),
+ ), $mc->classify_response(array('choice0' => 1, 'choice1' => 1)));
+
+ $this->assertEqual(array(), $mc->classify_response(array()));
+ }
+}
diff --git a/question/type/multichoice/simpletest/testquestiontype.php b/question/type/multichoice/simpletest/testquestiontype.php
new file mode 100644
index 0000000000000..5edb7fad4f2f3
--- /dev/null
+++ b/question/type/multichoice/simpletest/testquestiontype.php
@@ -0,0 +1,90 @@
+.
+
+
+/**
+ * Unit tests for the mulitple choice question definition class.
+ *
+ * @package qtype_multichoice
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/multichoice/questiontype.php');
+
+/**
+ * Unit tests for the multiple choice question definition class.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_multichoice_test extends UnitTestCase {
+ var $qtype;
+
+ public function setUp() {
+ $this->qtype = new qtype_multichoice();
+ }
+
+ public function tearDown() {
+ $this->qtype = null;
+ }
+
+ public function test_name() {
+ $this->assertEqual($this->qtype->name(), 'multichoice');
+ }
+
+ protected function get_test_question_data() {
+ $q = new stdClass;
+ $q->id = 1;
+ $q->options->single = true;
+ $q->options->answers[1] = (object) array('answer' => 'frog', 'fraction' => 1);
+ $q->options->answers[2] = (object) array('answer' => 'toad', 'fraction' => 0);
+
+ return $q;
+ }
+
+ public function test_can_analyse_responses() {
+ $this->assertTrue($this->qtype->can_analyse_responses());
+ }
+
+ public function test_get_random_guess_score() {
+ $q = $this->get_test_question_data();
+ $this->assertEqual(0.5, $this->qtype->get_random_guess_score($q));
+ }
+
+ public function test_get_possible_responses_single() {
+ $q = $this->get_test_question_data();
+ $responses = $this->qtype->get_possible_responses($q);
+
+ $this->assertEqual(array(
+ $q->id => array(
+ 1 => new question_possible_response('frog', 1),
+ 2 => new question_possible_response('toad', 0),
+ null => question_possible_response::no_response(),
+ )), $this->qtype->get_possible_responses($q));
+ }
+
+ public function test_get_possible_responses_multi() {
+ $q = $this->get_test_question_data();
+ $q->options->single = false;
+
+ $this->assertEqual(array(
+ 1 => array(1 => new question_possible_response('frog', 1)),
+ 2 => array(2 => new question_possible_response('toad', 0)),
+ ), $this->qtype->get_possible_responses($q));
+ }
+}
diff --git a/question/type/multichoice/simpletest/testwalkthrough.php b/question/type/multichoice/simpletest/testwalkthrough.php
new file mode 100644
index 0000000000000..7c673918345da
--- /dev/null
+++ b/question/type/multichoice/simpletest/testwalkthrough.php
@@ -0,0 +1,91 @@
+.
+
+
+/**
+ * This file contains tests that walk mutichoice questions through various behaviours.
+ *
+ * Note, there are already lots of tests of the multichoice type in the behaviour
+ * tests. (Search for test_question_maker::make_a_multichoice.) This file only
+ * contains a few additional tests for problems that were found during testing.
+ *
+ * @package qtype_multichoice
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+require_once($CFG->dirroot . '/question/engine/lib.php');
+require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
+
+class qtype_multichoice_walkthrough_test extends qbehaviour_walkthrough_test_base {
+ public function test_deferredfeedback_feedback_multichoice_single() {
+
+ // Create a true-false question with correct answer true.
+ $mc = test_question_maker::make_a_multichoice_single_question();
+ $mc->shuffleanswers = false;
+ $mc->answers[14]->fraction = 0.1; // Make one of the choices partially right.
+ $rightindex = 0;
+
+ $this->start_attempt_at_question($mc, 'deferredfeedback', 3);
+ $this->process_submission(array('answer' => $rightindex));
+
+ // Verify.
+ $this->check_current_state(question_state::$complete);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ $this->get_contains_mc_radio_expectation($rightindex, true, true),
+ $this->get_contains_mc_radio_expectation($rightindex + 1, true, false),
+ $this->get_contains_mc_radio_expectation($rightindex + 2, true, false),
+ $this->get_does_not_contain_correctness_expectation(),
+ $this->get_does_not_contain_feedback_expectation());
+
+ // Finish the attempt.
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(3);
+ $this->check_current_output(
+ $this->get_contains_mc_radio_expectation($rightindex, false, true),
+ $this->get_contains_correct_expectation(),
+ new PatternExpectation('/class="r0 correct"/'),
+ new PatternExpectation('/class="r1"/'));
+ }
+
+ public function test_deferredfeedback_feedback_multichoice_multi() {
+ // Create a true-false question with correct answer true.
+ $mc = test_question_maker::make_a_multichoice_multi_question();
+ $mc->shuffleanswers = false;
+
+ $this->start_attempt_at_question($mc, 'deferredfeedback', 2);
+ $this->process_submission($mc->get_correct_response());
+ $this->quba->finish_all_questions();
+
+ // Verify.
+ $this->check_current_state(question_state::$gradedright);
+ $this->check_current_mark(2);
+ $this->check_current_output(
+ $this->get_contains_mc_checkbox_expectation('choice0', false, true),
+ $this->get_contains_mc_checkbox_expectation('choice1', false, false),
+ $this->get_contains_mc_checkbox_expectation('choice2', false, true),
+ $this->get_contains_mc_checkbox_expectation('choice3', false, false),
+ $this->get_contains_correct_expectation(),
+ new PatternExpectation('/class="r0 correct"/'),
+ new PatternExpectation('/class="r1"/'));
+ }
+}
diff --git a/question/type/multichoice/styles.css b/question/type/multichoice/styles.css
new file mode 100644
index 0000000000000..311d9cb8c7628
--- /dev/null
+++ b/question/type/multichoice/styles.css
@@ -0,0 +1,10 @@
+/* TODO */
+.que.multichoice .answer .specificfeedback {
+ display: inline;
+ padding: 0 0.7em;
+ background: #FFF3BF;
+}
+.que.multichoice .answer .specificfeedback * {
+ display: inline;
+ background: #FFF3BF;
+}
diff --git a/question/type/multichoice/version.php b/question/type/multichoice/version.php
index 9061bb2e5ebf2..f5abde65759f6 100644
--- a/question/type/multichoice/version.php
+++ b/question/type/multichoice/version.php
@@ -1,6 +1,4 @@
version = 2010090501;
+$plugin->version = 2011011200;
$plugin->requires = 2010090501;
-
-