mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 14:27:22 +01:00
MDL-20636 Mostly working conversion of the multichoice question type.
Currently, you get an error on saving the editing form.
This commit is contained in:
parent
9481434019
commit
c9c989a09a
@ -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 +
|
||||
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 +
|
||||
|
||||
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/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');
|
||||
}
|
||||
|
||||
// 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 © 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 @@ 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;
|
||||
$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);
|
||||
|
||||
// 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);
|
||||
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';
|
||||
}
|
||||
|
||||
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.';
|
||||
|
426
question/type/multichoice/question.php
Normal file
426
question/type/multichoice/question.php
Normal file
@ -0,0 +1,426 @@
|
||||
<?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
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
}
|
@ -1,41 +1,47 @@
|
||||
<?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/>.
|
||||
|
||||
|
||||
/**
|
||||
* 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 @@ class question_multichoice_qtype extends default_questiontype {
|
||||
|
||||
$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 @@ class question_multichoice_qtype extends default_questiontype {
|
||||
}
|
||||
$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 @@ class question_multichoice_qtype extends default_questiontype {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function delete_question($questionid, $contextid) {
|
||||
protected function make_question_instance($questiondata) {
|
||||
question_bank::load_question_definition_classes($this->name());
|
||||
if ($questiondata->options->single) {
|
||||
$class = 'qtype_multichoice_single_question';
|
||||
} else {
|
||||
$class = 'qtype_multichoice_multi_question';
|
||||
}
|
||||
return new $class();
|
||||
}
|
||||
|
||||
protected function make_hint($hint) {
|
||||
return question_hint_with_parts::load_from_record($hint);
|
||||
}
|
||||
|
||||
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 {
|
||||
$question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
|
||||
}
|
||||
|
||||
$question->correctfeedback = $questiondata->options->correctfeedback;
|
||||
$question->partiallycorrectfeedback = $questiondata->options->partiallycorrectfeedback;
|
||||
$question->incorrectfeedback = $questiondata->options->incorrectfeedback;
|
||||
$question->shownumcorrect = !$questiondata->options->single && $questiondata->options->shownumcorrect;
|
||||
|
||||
$this->initialise_question_answers($question, $questiondata);
|
||||
}
|
||||
|
||||
public 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('' => '');
|
||||
} else {
|
||||
$state->responses = array();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 = "<input $readonly id=\"$a->id\" $name $checked $type value=\"$aid\" />";
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
include("$CFG->dirroot/question/type/multichoice/display.html");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// mark the state as graded
|
||||
$state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
public function get_random_guess_score($questiondata) {
|
||||
$totalfraction = 0;
|
||||
foreach ($question->options->answers as $answer){
|
||||
foreach ($questiondata->options->answers as $answer) {
|
||||
$totalfraction += $answer->fraction;
|
||||
}
|
||||
return $totalfraction / count($question->options->answers);
|
||||
return $totalfraction / count($questiondata->options->answers);
|
||||
}
|
||||
|
||||
function get_possible_responses($questiondata) {
|
||||
if ($questiondata->options->single) {
|
||||
$responses = array();
|
||||
|
||||
foreach ($questiondata->options->answers as $aid => $answer) {
|
||||
$responses[$aid] = new question_possible_response($answer->answer,
|
||||
$answer->fraction);
|
||||
}
|
||||
|
||||
$responses[null] = question_possible_response::no_response();
|
||||
return array($questiondata->id => $responses);
|
||||
} else {
|
||||
$parts = array();
|
||||
|
||||
foreach ($questiondata->options->answers as $aid => $answer) {
|
||||
$parts[$aid] = array($aid =>
|
||||
new question_possible_response($answer->answer, $answer->fraction));
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -434,61 +231,12 @@ class question_multichoice_qtype extends default_questiontype {
|
||||
* 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 '<span class="anun">' . $qnum . '<span class="anumsep">.</span></span> ';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 @@ class question_multichoice_qtype extends default_questiontype {
|
||||
$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());
|
||||
|
294
question/type/multichoice/renderer.php
Normal file
294
question/type/multichoice/renderer.php
Normal file
@ -0,0 +1,294 @@
|
||||
<?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 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);
|
||||
}
|
||||
}
|
238
question/type/multichoice/simpletest/testquestion.php
Normal file
238
question/type/multichoice/simpletest/testquestion.php
Normal file
@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for 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()));
|
||||
}
|
||||
}
|
90
question/type/multichoice/simpletest/testquestiontype.php
Normal file
90
question/type/multichoice/simpletest/testquestiontype.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for 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));
|
||||
}
|
||||
}
|
91
question/type/multichoice/simpletest/testwalkthrough.php
Normal file
91
question/type/multichoice/simpletest/testwalkthrough.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?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/>.
|
||||
|
||||
|
||||
/**
|
||||
* 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"/'));
|
||||
}
|
||||
}
|
10
question/type/multichoice/styles.css
Normal file
10
question/type/multichoice/styles.css
Normal file
@ -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;
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
<?php
|
||||
|
||||
$plugin->version = 2010090501;
|
||||
$plugin->version = 2011011200;
|
||||
$plugin->requires = 2010090501;
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user