MDL-20636 Start converting the numerical question type.

This commit is contained in:
Tim Hunt
2011-04-28 20:06:24 +01:00
parent f902a54e5a
commit f77f47ec56
5 changed files with 247 additions and 217 deletions
@@ -47,19 +47,23 @@ class question_edit_numerical_form extends question_edit_form {
return $repeated;
}
/**
* Add question-type specific form fields.
*
* @param MoodleQuickForm $mform the form being built.
*/
protected function definition_inner($mform) {
global $QTYPES ;
//------------------------------------------------------------------------------------------
$creategrades = get_grade_options();
$this->add_per_answer_fields($mform, get_string('answerno', 'qtype_numerical', '{no}'),
$creategrades->gradeoptions);
$QTYPES['numerical']->add_units_options($mform,$this);
$QTYPES['numerical']->add_units_elements($mform,$this);
//------------------------------------------------------------------------------------------
question_bank::get_qtype('numerical')->add_units_options($mform, $this);
question_bank::get_qtype('numerical')->add_units_elements($mform, $this);
}
protected function data_preprocessing($question) {
global $QTYPES ;
if (isset($question->options)){
$answers = $question->options->answers;
if (count($answers)) {
@@ -84,15 +88,15 @@ class question_edit_numerical_form extends question_edit_form {
$key++;
}
}
$QTYPES['numerical']->set_numerical_unit_data($this, $question, $default_values);
question_bank::get_qtype('numerical')->set_numerical_unit_data($this, $question, $default_values);
$question = (object)((array)$question + $default_values);
}
return $question;
}
public function validation($data, $files) {
global $QTYPES;
$errors = parent::validation($data, $files);
// Check the answers.
@@ -120,7 +124,7 @@ class question_edit_numerical_form extends question_edit_form {
if ($maxgrade == false) {
$errors['fraction[0]'] = get_string('fractionsnomax', 'question');
}
$QTYPES['numerical']->validate_numerical_options($data, $errors) ;
question_bank::get_qtype('numerical')->validate_numerical_options($data, $errors);
return $errors;
}
@@ -39,7 +39,10 @@ $string['errornomultiplier'] = 'You must specify a multiplier for this unit.';
$string['errorrepeatedunit'] = 'You cannot have two units with the same name.';
$string['geometric'] = 'Geometric';
$string['instructions'] = 'Instructions ';
$string['invalidnumericanswer'] = 'One of the answers you entered was not a valid number.';
$string['invalidnumerictolerance'] = 'One of the tolerances you entered was not a valid number.';
$string['leftexample'] = 'LEFT as $1.00';
$string['multiplier'] = 'Multiplier';
$string['noneditableunittext'] = 'NON editable text of Unit No1';
$string['nonvalidcharactersinnumber'] = 'NON valid characters in number';
$string['notenoughanswers'] = 'You must enter at least one answer.';
@@ -65,12 +68,14 @@ $string['manynumerical'] = 'Only the NUMERICAL ANSWER will be graded using optio
$string['nominal'] = 'Nominal';
$string['onlynumerical'] = 'Only the NUMERICAL ANSWER will be graded, no units allowed';
$string['oneunitshown'] = 'Only the NUMERICAL ANSWER will be graded, Unit1 will be shown';
$string['pleaseenterananswer'] = 'Please enter an answer.';
$string['relative'] = 'Relative';
$string['rightexample'] = 'RIGHT as 1.00cm';
$string['selectunits'] = 'Select units';
$string['selectunit'] = 'Select one unit';
$string['studentunitanswer'] = 'UNIT ANSWER displayed as a ';
$string['tolerancetype'] = 'Tolerance type';
$string['unit'] = 'Unit';
$string['unitchoice'] = 'Multichoice (radio elements)';
$string['unitdisplay'] = '<STRONG>Unit 1 displayed </STRONG>';
$string['unitedit'] = 'Edit unit';
+69 -136
View File
@@ -15,7 +15,10 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Question type class for the numerical question type.
*
* @package qtype
* @subpackage numerical
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
@@ -25,42 +28,43 @@
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->dirroot/question/type/shortanswer/questiontype.php");
require_once($CFG->dirroot . '/question/type/numerical/question.php');
if ( ! defined ("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY")) {
define("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY", 0);
}
if ( ! defined ("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY")) {
define("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY", 1);
}
if ( ! defined ("NUMERICALQUESTIONUNITTEXTDISPLAY")) {
define("NUMERICALQUESTIONUNITTEXTDISPLAY", 2);
}
if ( ! defined ("NUMERICALQUESTIONUNITNODISPLAY")) {
define("NUMERICALQUESTIONUNITNODISPLAY", 3);
}
/**
* NUMERICAL QUESTION TYPE CLASS
* The numerical question type class.
*
* This class contains some special features in order to make the
* question type embeddable within a multianswer (cloze) question
*
* This question type behaves like shortanswer in most cases.
* Therefore, it extends the shortanswer 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 question_numerical_qtype extends qtype_shortanswer {
public $virtualqtype = false;
function name() {
return 'numerical';
}
function has_wildcards_in_responses() {
class qtype_numerical extends question_type {
public function has_wildcards_in_responses() {
return true;
}
function requires_qtypes() {
return array('shortanswer');
}
function get_question_options(&$question) {
public function get_question_options($question) {
global $CFG, $DB, $OUTPUT;
// Get the question answers and their respective tolerances
// Note: question_numerical is an extension of the answer table rather than
// the question table as is usually the case for qtype
// specific tables.
global $CFG, $DB, $OUTPUT;
if (!$question->options->answers = $DB->get_records_sql(
"SELECT a.*, n.tolerance " .
"FROM {question_answers} a, " .
@@ -71,6 +75,9 @@ class question_numerical_qtype extends qtype_shortanswer {
echo $OUTPUT->notification('Error: Missing question answer for numerical question ' . $question->id . '!');
return false;
}
$question->hints = get_records('question_hints', 'questionid', $question->id, 'id ASC');
$this->get_numerical_units($question);
//get_numerical_options() need to know if there are units
// to set correctly default values
@@ -91,10 +98,13 @@ class question_numerical_qtype extends qtype_shortanswer {
return true;
}
function get_numerical_units(&$question) {
public function get_numerical_units(&$question) {
global $DB;
if ($units = $DB->get_records('question_numerical_units', array('question' => $question->id), 'id ASC')) {
$units = array_values($units);
if ($units = $DB->get_records('question_numerical_units',
array('question' => $question->id), 'id ASC')) {
$units = array_values($units);
} else {
$units = array();
}
@@ -105,7 +115,7 @@ class question_numerical_qtype extends qtype_shortanswer {
return true;
}
function get_default_numerical_unit(&$question) {
public function get_default_numerical_unit(&$question) {
if (isset($question->options->units[0])) {
foreach ($question->options->units as $unit) {
if (abs($unit->multiplier - 1.0) < '1.0e-' . ini_get('precision')) {
@@ -116,7 +126,7 @@ class question_numerical_qtype extends qtype_shortanswer {
return false;
}
function get_numerical_options(&$question) {
public function get_numerical_options(&$question) {
global $DB;
if (!$options = $DB->get_record('question_numerical_options', array('question' => $question->id))) {
$question->options->unitgradingtype = 0; // total grade
@@ -144,11 +154,10 @@ class question_numerical_qtype extends qtype_shortanswer {
return true;
}
/**
* Save the units and the answers associated with this question.
*/
function save_question_options($question) {
public function save_question_options($question) {
global $DB;
$context = $question->context;
@@ -309,10 +318,17 @@ class question_numerical_qtype extends qtype_shortanswer {
$DB->update_record('question_numerical_options', $options);
return $result;
$this->save_hints($question);
// Report any problems.
if (!empty($result->notice)) {
return $result;
}
return true;
}
function save_numerical_units($question) {
public function save_numerical_units($question) {
global $DB;
$result = new stdClass();
@@ -345,27 +361,6 @@ class question_numerical_qtype extends qtype_shortanswer {
return $result;
}
function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
$state->responses = array();
$state->responses['answer'] = '';
$state->responses['unit'] = '';
return true;
}
function restore_session_and_responses(&$question, &$state) {
if(false === strpos($state->responses[''], '|||||')){
$state->responses['answer']= $state->responses[''];
$state->responses['unit'] = '';
$this->split_old_answer($state->responses[''], $question->options->units, $state->responses['answer'] ,$state->responses['unit'] );
}else {
$responses = explode('|||||', $state->responses['']);
$state->responses['answer']= $responses[0];
$state->responses['unit'] = $responses[1];
}
return true;
}
function find_unit_index(&$question,$value){
$length = 0;
$goodkey = 0 ;
@@ -377,39 +372,33 @@ class question_numerical_qtype extends qtype_shortanswer {
return 0 ;
}
function split_old_answer($rawresponse, $units, &$answer ,&$unit ) {
$answer = $rawresponse ;
// remove spaces and normalise decimal places.
$search = array(' ', ',');
$replace = array('', '.');
$rawresponse = str_replace($search, $replace, trim($rawresponse));
if (preg_match('~^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$~',
$rawresponse, $responseparts)) {
if(isset($responseparts[5]) ){
$unit = $responseparts[5] ;
}
if(isset($responseparts[1]) ){
$answer = $responseparts[1] ;
}
}
return ;
protected function initialise_question_instance(question_definition $question, $questiondata) {
parent::initialise_question_instance($question, $questiondata);
$this->initialise_numerical_answers($question, $questiondata);
$this->initialise_numerical_units($question, $questiondata);
}
function save_session_and_responses(&$question, &$state) {
global $DB;
$responses = '';
if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
$responses = $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit;
}else if(isset($state->responses['unit'])){
$responses = $state->responses['answer'].'|||||'.$state->responses['unit'] ;
}else {
$responses = $state->responses['answer'].'|||||';
protected function initialise_numerical_answers(question_definition $question, $questiondata) {
$question->answers = array();
if (empty($questiondata->options->answers)) {
return;
}
// Set the legacy answer field
$DB->set_field('question_states', 'answer', $responses, array('id' => $state->id));
return true;
foreach ($questiondata->options->answers as $a) {
$question->answers[$a->id] = new qtype_numerical_answer($a->answer,
$a->fraction, $a->feedback, $a->tolerance);
}
}
protected function initialise_numerical_units(question_definition $question, $questiondata) {
if (empty($questiondata->options->units)) {
$question->ap = new qtype_numerical_answer_processor(array());
return;
}
$units = array();
foreach ($questiondata->options->units as $unit) {
$units[$unit->unit] = $unit->multiplier;
}
$question->ap = new qtype_numerical_answer_processor($units);
}
function delete_question($questionid, $contextid) {
@@ -840,8 +829,7 @@ class question_numerical_qtype extends qtype_shortanswer {
return true;
}
function get_correct_responses(&$question, &$state) {
public function get_correct_responses($question, $state) {
$correct = parent::get_correct_responses($question, $state);
$unit = $this->get_default_numerical_unit($question);
if (isset($correct['']) && $correct[''] != '*' && $unit) {
@@ -850,48 +838,6 @@ class question_numerical_qtype extends qtype_shortanswer {
return $correct;
}
// ULPGC ecastro
function get_all_responses(&$question, &$state) {
$result = new stdClass();
$answers = array();
$unit = $this->get_default_numerical_unit($question);
if (is_array($question->options->answers)) {
foreach ($question->options->answers as $aid=>$answer) {
$r = new stdClass();
$r->answer = $answer->answer;
$r->credit = $answer->fraction;
$this->get_tolerance_interval($answer);
if ($r->answer != '*' && $unit) {
$r->answer .= ' ' . $unit->unit;
}
if ($answer->max != $answer->min) {
$max = "$answer->max"; //format_float($answer->max, 2);
$min = "$answer->min"; //format_float($answer->max, 2);
$r->answer .= ' ('.$min.'..'.$max.')';
}
$answers[$aid] = $r;
}
}
$result->id = $question->id;
$result->responses = $answers;
return $result;
}
function get_actual_response($question, $state) {
if (!empty($state->responses) && !empty($state->responses[''])) {
if(false === strpos($state->responses[''], '|||||')){
$responses[] = $state->responses[''];
}else {
$resp = explode('|||||', $state->responses['']);
$responses[] = $resp[0].$resp[1];
}
} else {
$responses[] = '';
}
return $responses;
}
function get_tolerance_interval(&$answer) {
// No tolerance
if (empty($answer->tolerance)) {
@@ -1379,16 +1325,3 @@ class question_numerical_qtype extends qtype_shortanswer {
}
}
}
if ( ! defined ("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY")) {
define("NUMERICALQUESTIONUNITTEXTINPUTDISPLAY", 0);
}
if ( ! defined ("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY")) {
define("NUMERICALQUESTIONUNITMULTICHOICEDISPLAY", 1);
}
if ( ! defined ("NUMERICALQUESTIONUNITTEXTDISPLAY")) {
define("NUMERICALQUESTIONUNITTEXTDISPLAY", 2);
}
if ( ! defined ("NUMERICALQUESTIONUNITNODISPLAY")) {
define("NUMERICALQUESTIONUNITNODISPLAY", 3);
}
+117
View File
@@ -0,0 +1,117 @@
<?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/>.
/**
* Numerical question renderer class.
*
* @package qtype_numerical
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Generates the output for short answer questions.
*
* @copyright 2009 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_numerical_renderer extends qtype_renderer {
public function formulation_and_controls(question_attempt $qa,
question_display_options $options) {
$question = $qa->get_question();
$currentanswer = $qa->get_last_qt_var('answer');
$inputname = $qa->get_qt_field_name('answer');
$inputattributes = array(
'type' => 'text',
'name' => $inputname,
'value' => $currentanswer,
'id' => $inputname,
'size' => 80,
);
if ($options->readonly) {
$inputattributes['readonly'] = 'readonly';
}
$feedbackimg = '';
if ($options->correctness) {
$answer = $question->get_matching_answer(array('answer' => $currentanswer));
if ($answer) {
$fraction = $answer->fraction;
} else {
$fraction = 0;
}
$inputattributes['class'] = $this->feedback_class($fraction);
$feedbackimg = $this->feedback_image($fraction);
}
$questiontext = $question->format_questiontext();
$placeholder = false;
if (preg_match('/_____+/', $questiontext, $matches)) {
$placeholder = $matches[0];
$inputattributes['size'] = round(strlen($placeholder) * 1.1);
}
$input = html_writer::empty_tag('input', $inputattributes) . $feedbackimg;
if ($placeholder) {
$questiontext = substr_replace($questiontext, $input,
strpos($questiontext, $placeholder), strlen($placeholder));
}
$result = html_writer::tag('div', $questiontext, array('class' => 'qtext'));
if (!$placeholder) {
$result .= html_writer::start_tag('div', array('class' => 'ablock'));
$result .= get_string('answer', 'qtype_shortanswer',
html_writer::tag('div', $input, array('class' => 'answer')));
$result .= html_writer::end_tag('div');
}
if ($qa->get_state() == question_state::$invalid) {
$result .= html_writer::nonempty_tag('div',
$question->get_validation_error(array('answer' => $currentanswer)),
array('class' => 'validationerror'));
}
return $result;
}
public function specific_feedback(question_attempt $qa) {
$question = $qa->get_question();
$answer = $question->get_matching_answer(array('answer' => $qa->get_last_qt_var('answer')));
if (!$answer || !$answer->feedback) {
return '';
}
return $question->format_text($answer->feedback);
}
public function correct_response(question_attempt $qa) {
$answer = $qa->get_question()->get_correct_answer();
if (!$answer) {
return '';
}
return get_string('correctansweris', 'qtype_shortanswer', s($answer->answer));
}
}
@@ -36,93 +36,64 @@ require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
* @copyright 2006 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class question_numerical_qtype_test extends UnitTestCase {
class qtype_numerical_test extends UnitTestCase {
public static $includecoverage = array('question/type/questiontype.php', 'question/type/numerical/questiontype.php');
var $tolerance = 0.00000001;
var $qtype;
protected $tolerance = 0.00000001;
protected $qtype;
public function setUp() {
$this->qtype = new question_numerical_qtype();
$this->qtype = new qtype_numerical();
}
public function tearDown() {
$this->qtype = null;
$this->qtype = null;
}
protected function get_test_question_data() {
$q = new stdClass;
$q->id = 1;
$q->options->answers[1] = (object) array(
'answer' => 42,
'fraction' => 1,
'feedback' => 'yes',
'tolerance' => 0.5
);
$q->options->answers[2] = (object) array(
'answer' => '*',
'fraction' => 0.1,
'feedback' => 'no',
'tolerance' => ''
);
$q->options->units = array(
(object) array('unit' => 'm', 'multiplier' => 1),
(object) array('unit' => 'cm', 'multiplier' => 0.01)
);
return $q;
}
public function test_name() {
$this->assertEqual($this->qtype->name(), 'numerical');
}
public function test_get_tolerance_interval() {
$answer = new stdClass();
$answer->tolerance = 0.01;
$answer->tolerancetype = 'relative';
$answer->answer = 1.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 0.99, $this->tolerance);
$this->assertWithinMargin($answer->max, 1.01, $this->tolerance);
$answer = new stdClass();
$answer->tolerance = 0.01;
$answer->tolerancetype = 'relative';
$answer->answer = 10.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 9.9, $this->tolerance);
$this->assertWithinMargin($answer->max, 10.1, $this->tolerance);
$answer = new stdClass();
$answer->tolerance = 0.01;
$answer->tolerancetype = 'nominal';
$answer->answer = 1.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 0.99, $this->tolerance);
$this->assertWithinMargin($answer->max, 1.01, $this->tolerance);
$answer = new stdClass();
$answer->tolerance = 2.0;
$answer->tolerancetype = 'nominal';
$answer->answer = 10.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 8, $this->tolerance);
$this->assertWithinMargin($answer->max, 12, $this->tolerance);
$answer = new stdClass(); // Test default tolerance 0.
$answer->tolerancetype = 'nominal';
$answer->answer = 0.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 0, $this->tolerance);
$this->assertWithinMargin($answer->max, 0, $this->tolerance);
$answer = new stdClass(); // Test default type nominal.
$answer->tolerance = 1.0;
$answer->answer = 1.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 0, $this->tolerance);
$this->assertWithinMargin($answer->max, 2, $this->tolerance);
$answer = new stdClass();
$answer->tolerance = 1.0;
$answer->tolerancetype = 'geometric';
$answer->answer = 1.0;
$this->qtype->get_tolerance_interval($answer);
$this->assertWithinMargin($answer->min, 0.5, $this->tolerance);
$this->assertWithinMargin($answer->max, 2.0, $this->tolerance);
public function test_can_analyse_responses() {
$this->assertTrue($this->qtype->can_analyse_responses());
}
public function test_apply_unit() {
$units = array(
(object) array('unit' => 'm', 'multiplier' => 1),
(object) array('unit' => 'cm', 'multiplier' => 100),
(object) array('unit' => 'mm', 'multiplier' => 1000),
(object) array('unit' => 'inch', 'multiplier' => 1.0/0.0254)
);
public function test_get_random_guess_score() {
$q = $this->get_test_question_data();
$this->assertEqual(0.1, $this->qtype->get_random_guess_score($q));
}
$this->assertWithinMargin($this->qtype->apply_unit('1', $units), 1, $this->tolerance);
$this->assertWithinMargin($this->qtype->apply_unit('1.0', $units), 1, $this->tolerance);
$this->assertWithinMargin($this->qtype->apply_unit('-1e0', $units), -1, $this->tolerance);
$this->assertWithinMargin($this->qtype->apply_unit('100m', $units), 100, $this->tolerance);
$this->assertWithinMargin($this->qtype->apply_unit('1cm', $units), 0.01, $this->tolerance);
$this->assertWithinMargin($this->qtype->apply_unit('12inch', $units), .3048, $this->tolerance);
$this->assertWithinMargin($this->qtype->apply_unit('-100', array()), -100, $this->tolerance);
public function test_get_possible_responses() {
$q = $this->get_test_question_data();
$this->assertEqual(array(
$q->id => array(
1 => new question_possible_response('42 m (41.5..42.5)', 1),
2 => new question_possible_response('*', 0.1),
null => question_possible_response::no_response()),
), $this->qtype->get_possible_responses($q));
}
}