diff --git a/question/format/gift/format.php b/question/format/gift/format.php index 0eb0b17a42f2f..c3a873d18ac7d 100755 --- a/question/format/gift/format.php +++ b/question/format/gift/format.php @@ -393,6 +393,14 @@ function readquestion($lines) { // Note similarities to ShortAnswer $answertext = substr($answertext, 1); // remove leading "#" + // If there is feedback for a wrong answer, store it for now. + if (($pos = strpos($answertext, '~')) !== false) { + $wrongfeedback = substr($answertext, $pos); + $answertext = substr($answertext, 0, $pos); + } else { + $wrongfeedback = ''; + } + $answers = explode("=", $answertext); if (isset($answers[0])) { $answers[0] = trim($answers[0]); @@ -454,6 +462,14 @@ function readquestion($lines) { $question->tolerance[$key] = $tol; } // end foreach + if ($wrongfeedback) { + $key += 1; + $question->fraction[$key] = 0; + $question->feedback[$key] = $this->commentparser($wrongfeedback); + $question->answer[$key] = ''; + $question->tolerance[$key] = ''; + } + //$question->defaultgrade = 1; //$question->image = ""; // No images with this format //$question->multiplier = array(); // no numeric multipliers with GIFT @@ -571,12 +587,19 @@ function writequestion( $question ) { $expout .= "}\n"; break; case NUMERICAL: - $answer = array_pop( $question->options->answers ); - $tolerance = $answer->tolerance; - $min = $answer->answer - $tolerance; - $max = $answer->answer + $tolerance; - $expout .= "::".$question->name."::".$tfname.$this->repchar( $question->questiontext, $textformat )."{\n"; - $expout .= "\t#".$min."..".$max."#".$this->repchar( $answer->feedback )."\n"; + $expout .= "::".$question->name."::".$tfname.$this->repchar( $question->questiontext, $textformat )."{#\n"; + foreach ($question->options->answers as $answer) { + // DONOTCOMMIT + echo '
';
+            var_export($answer);
+            echo '
'; + + if ($answer->answer != '') { + $expout .= "\t=".$answer->answer.":".(float)$answer->tolerance."#".$this->repchar( $answer->feedback )."\n"; + } else { + $expout .= "\t~#".$this->repchar( $answer->feedback )."\n"; + } + } $expout .= "}\n"; break; case MATCH: diff --git a/question/type/numerical/editquestion.html b/question/type/numerical/editquestion.html index 01ce543a1a87f..e48dda35f993f 100644 --- a/question/type/numerical/editquestion.html +++ b/question/type/numerical/editquestion.html @@ -2,20 +2,20 @@
- + - + - - + - + - + + + - + - + - - + + + - + - +id) && isset($question->qtype) && - $QTYPES[$question->qtype]->get_question_options($question)) { - $answer = array_values($question->options->answers); - usort($answer, create_function('$a, $b', - 'if ($a->fraction == $a->fraction) { return 0; }' . - 'else { return $a->fraction < $b->fraction ? -1 : 1; }')); - $answer = $answer[0]; // Get the answer with the highest fraction (i.e. 1) + $QTYPES[$question->qtype]->get_question_options($question)) { + + $answers = array_values($question->options->answers); $units = array_values($question->options->units); usort($units, create_function('$a, $b', // make sure the default unit is at index 0 - 'if (1.0 === (float)$a->multiplier) { return -1; } else '. - 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); - $tolerance = $answer->tolerance; + 'if (1.0 === (float)$a->multiplier) { return -1; } else '. + 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); } else { - $answer = new stdClass; - $answer->answer = ''; - $answer->feedback = ''; + $answers = array(); $units = array(); - $tolerance = ''; + } + + // Add blank answers to make the number up to QUESTION_NUMANS + // or one more than current, if there are already lots. + $emptyanswer = new stdClass; + $emptyanswer->answer = ''; + $i = count($answers); + $limit = QUESTION_NUMANS; + $limit = $limit <= $i ? $i+1 : $limit; + for (; $i < $limit; $i++) { + $answers[] = $emptyanswer; } print_heading_with_help(get_string("editingnumerical", "quiz"), "numerical", "quiz"); require("$CFG->dirroot/question/type/numerical/editquestion.html"); - ?> diff --git a/question/type/numerical/questiontype.php b/question/type/numerical/questiontype.php index 0e321cc8bbc92..a8e8427a36103 100644 --- a/question/type/numerical/questiontype.php +++ b/question/type/numerical/questiontype.php @@ -1,21 +1,22 @@ -dirroot/question/type/shortanswer/questiontype.php"); +/** + * 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... + */ class question_numerical_qtype extends question_shortanswer_qtype { function name() { @@ -45,9 +46,9 @@ function get_question_options(&$question) { foreach($question->options->answers as $key => $val) { $answer = trim($val->answer); $length = strlen($defaultunit->unit); - if (substr($answer, -$length) == $defaultunit->unit) { + if ($length && substr($answer, -$length) == $defaultunit->unit) { $question->options->answers[$key]->answer = - substr($answer, 0, strlen($answer)-$length); + substr($answer, 0, strlen($answer)-$length); } } } @@ -88,11 +89,11 @@ function get_default_numerical_unit(&$question) { return $unit; } + /** + * Save the units and the answers associated with this question. + */ function save_question_options($question) { - // save_question_options supports the definition of multiple answers - // for numerical questions. This is not currently used by the editing - // interface, but the GIFT format supports it. The multianswer qtype, - // for example can make use of this feature. + // Get old versions of the objects if (!$oldanswers = get_records("question_answers", "question", $question->id)) { $oldanswers = array(); @@ -102,6 +103,7 @@ function save_question_options($question) { $oldoptions = array(); } + // Save the units. $result = $this->save_numerical_units($question); if (isset($result->error)) { return $result; @@ -111,10 +113,17 @@ function save_question_options($question) { // Insert all the new answers foreach ($question->answer as $key => $dataanswer) { - if ($dataanswer != "") { + if ($dataanswer != '' || trim($question->feedback[$key])) { $answer = new stdClass; $answer->question = $question->id; - $answer->answer = trim($dataanswer); + if (trim($dataanswer) == '') { + $answer->answer = ''; + } else { + $answer->answer = $this->apply_unit($dataanswer, $units); + if ($answer->answer === false) { + $result->notice = get_string('invalidnumericanswer', 'quiz'); + } + } $answer->fraction = $question->fraction[$key]; $answer->feedback = trim($question->feedback[$key]); @@ -124,7 +133,7 @@ function save_question_options($question) { $result->error = "Could not update quiz answer! (id=$answer->id)"; return $result; } - } else { // This is a completely new answer + } else { // This is a completely new answer if (! $answer->id = insert_record("question_answers", $answer)) { $result->error = "Could not insert quiz answer!"; return $result; @@ -137,8 +146,15 @@ function save_question_options($question) { } $options->question = $question->id; $options->answer = $answer->id; - $options->tolerance = $this->apply_unit($question->tolerance[$key], $units); - + if (trim($question->tolerance[$key]) == '') { + $options->tolerance = ''; + } else { + $options->tolerance = $this->apply_unit($question->tolerance[$key], $units); + if ($options->tolerance === false) { + $result->notice = get_string('invalidnumerictolerance', 'quiz'); + } + } + // Save options if (isset($options->id)) { // reusing existing record if (! update_record('question_numerical', $options)) { @@ -151,23 +167,28 @@ function save_question_options($question) { return $result; } } + } + } + // delete old answer records + if (!empty($oldanswers)) { + foreach($oldanswers as $oa) { + delete_records('question_answers', 'id', $oa->id); + } + } - // delete old answer records - if (!empty($oldanswers)) { - foreach($oldanswers as $oa) { - delete_records('question_answers', 'id', $oa->id); - } - } - - // delete old answer records - if (!empty($oldoptions)) { - foreach($oldoptions as $oo) { - delete_records('question_numerical', 'id', $oo->id); - } - } - + // delete old answer records + if (!empty($oldoptions)) { + foreach($oldoptions as $oo) { + delete_records('question_numerical', 'id', $oo->id); } } + + // Report any problems. + if (!empty($result->notice)) { + return $result; + } + + return true; } function save_numerical_units($question) { @@ -180,8 +201,8 @@ function save_numerical_units($question) { $keys = array(); $oldunits = array_values($oldunits); usort($oldunits, create_function('$a, $b', // make sure the default unit is at index 0 - 'if (1.0 === (float)$a->multiplier) { return -1; } else '. - 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); + 'if (1.0 === (float)$a->multiplier) { return -1; } else '. + 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }')); foreach ($oldunits as $unit) { $units[] = clone($unit); } @@ -190,8 +211,7 @@ function save_numerical_units($question) { // Discard any unit which doesn't specify the unit or the multiplier if (!empty($question->multiplier[$i]) && !empty($question->unit[$i])) { $units[$i]->question = $question->id; - $units[$i]->multiplier = - $this->apply_unit($question->multiplier[$i], array()); + $units[$i]->multiplier = $this->apply_unit($question->multiplier[$i], array()); $units[$i]->unit = $question->unit[$i]; } else { unset($units[$i]); @@ -200,6 +220,7 @@ function save_numerical_units($question) { unset($question->multiplier, $question->unit); /// Save units + $result = new stdClass; for ($i = 0; $i < $n; $i++) { if (!isset($units[$i]) && isset($oldunits[$i])) { // Delete if it hasn't been resubmitted delete_records('question_numerical_units', 'id', $oldunits[$i]->id); @@ -223,11 +244,11 @@ function save_numerical_units($question) { } /** - * Deletes question from the question-type specific tables - * - * @return boolean Success/Failure - * @param object $question The question being deleted - */ + * Deletes question from the question-type specific tables + * + * @return boolean Success/Failure + * @param object $question The question being deleted + */ function delete_question($questionid) { delete_records("question_numerical", "question", $questionid); delete_records("question_numerical_units", "question", $questionid); @@ -241,25 +262,25 @@ function compare_responses(&$question, &$state, &$teststate) { return ($response == $testresponse); } - - - // Checks whether a response matches a given answer, taking the tolerance - // into account. Returns a true for if a response matches the answer, false - // if it doesn't. + /** + * Checks whether a response matches a given answer, taking the tolerance + * and units into account. Returns a true for if a response matches the + * answer, false if it doesn't. + */ function test_response(&$question, &$state, $answer) { - if (isset($state->responses[''])) { - $response = $this->apply_unit(stripslashes($state->responses['']), - $question->options->units); - } else { - $response = ''; + if ($answer->answer == '') { + return true; // Blank answer matches anything. } - if (is_numeric($response) && is_numeric($answer->answer)) { - $this->get_tolerance_interval($answer); - return ($answer->min <= $response && $answer->max >= $response); - } else { - return ($response == $answer->answer); + $response = $this->apply_unit(stripslashes($state->responses['']), $question->options->units); + + if ($response === false) { + return false; // The student did not type a number. } + + // The student did type a number, so check it with tolerances. + $this->get_tolerance_interval($answer); + return ($answer->min <= $response && $response <= $answer->max); } // ULPGC ecastro @@ -274,18 +295,14 @@ function check_response(&$question, &$state){ } function grade_responses(&$question, &$state, $cmoptions) { - $answers = &$question->options->answers; + $answers = &$question->options->answers; $state->raw_grade = 0; foreach($answers as $answer) { if($this->test_response($question, $state, $answer)) { - if ($state->raw_grade < $answer->fraction) { - $state->raw_grade = $answer->fraction; - } + $state->raw_grade = $answer->fraction; + break; } } - if (empty($state->raw_grade)) { - $state->raw_grade = 0; - } // Make sure we don't assign negative or too high marks $state->raw_grade = min(max((float) $state->raw_grade, @@ -308,11 +325,12 @@ function get_correct_responses(&$question, &$state) { // ULPGC ecastro function get_all_responses(&$question, &$state) { - unset($answers); + $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) { - unset ($r); + $r = new stdClass; $r->answer = $answer->answer; $r->credit = $answer->fraction; $this->get_tolerance_interval($answer); @@ -326,8 +344,6 @@ function get_all_responses(&$question, &$state) { } $answers[$aid] = $r; } - } else { - $answers[]="error"; // just for debugging, eliminate } $result->id = $question->id; $result->responses = $answers; @@ -337,8 +353,7 @@ function get_all_responses(&$question, &$state) { function get_tolerance_interval(&$answer) { // No tolerance if (empty($answer->tolerance)) { - $answer->min = $answer->max = $answer->answer; - return true; + $answer->tolerance = 0; } // Calculate the interval of correct responses (min/max) @@ -376,40 +391,51 @@ function get_tolerance_interval(&$answer) { } /** - * Checks if the $rawresponse has a unit and applys it if appropriate. - * - * @param string $rawresponse The response string to be converted to a float. - * @param array $units An array with the defined units, where the - * unit is the key and the multiplier the value. - * @return float The rawresponse with the unit taken into - * account as a float. - */ + * Checks if the $rawresponse has a unit and applys it if appropriate. + * + * @param string $rawresponse The response string to be converted to a float. + * @param array $units An array with the defined units, where the + * unit is the key and the multiplier the value. + * @return float The rawresponse with the unit taken into + * account as a float. + */ function apply_unit($rawresponse, $units) { // Make units more useful $tmpunits = array(); foreach ($units as $unit) { $tmpunits[$unit->unit] = $unit->multiplier; } - + // remove spaces and normalise decimal places. $search = array(' ', ','); $replace = array('', '.'); - $rawresponse = str_replace($search, $replace, $rawresponse); // remove spaces - if (ereg( - '^([+-]?([0-9]+(\\.[0-9]*)?|[.][0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$', - $rawresponse, $responseparts)) { - $responsenum = (float)$responseparts[1]; - if (isset($tmpunits[$responseparts[5]])) { - return (float)$responseparts[1] / $tmpunits[$responseparts[5]]; + $rawresponse = str_replace($search, $replace, trim($rawresponse)); + + // Apply any unit that is present. + if (ereg('^([+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][-+]?[0-9]+)?)([^0-9].*)?$', + $rawresponse, $responseparts)) { + + if (!empty($responseparts[5])) { + + if (isset($tmpunits[$responseparts[5]])) { + // Valid number with unit. + return (float)$responseparts[1] / $tmpunits[$responseparts[5]]; + } else { + // Valid number with invalid unit. Must be wrong. + return false; + } + } else { + // Valid number without unit. return (float)$responseparts[1]; } } - return $rawresponse; + // Invalid number. Must be wrong. + return false; } -/// BACKUP FUNCTIONS //////////////////////////// + /// BACKUP FUNCTIONS //////////////////////////// - /* + /** * Backup the data in the question * * This is used in question/backuplib.php @@ -437,9 +463,9 @@ function backup($bf,$preferences,$question,$level=6) { return $status; } -/// RESTORE FUNCTIONS ///////////////// + /// RESTORE FUNCTIONS ///////////////// - /* + /** * Restores the data in the question * * This is used in question/restorelib.php @@ -456,6 +482,7 @@ function restore($old_question_id,$new_question_id,$info,$restore) { $num_info = $numericals[$i]; //Now, build the question_numerical record structure + $numerical = new stdClass; $numerical->question = $new_question_id; $numerical->answer = backup_todb($num_info['#']['ANSWER']['0']['#']); $numerical->tolerance = backup_todb($num_info['#']['TOLERANCE']['0']['#']); @@ -467,7 +494,7 @@ function restore($old_question_id,$new_question_id,$info,$restore) { } //The structure is equal to the db, so insert the question_numerical - $newid = insert_record ("question_numerical",$numerical); + $newid = insert_record ("question_numerical", $numerical); //Do some output if (($i+1) % 50 == 0) { @@ -492,11 +519,8 @@ function restore($old_question_id,$new_question_id,$info,$restore) { } } -//// END OF CLASS //// -////////////////////////////////////////////////////////////////////////// -//// INITIATION - Without this line the question type is not in use... /// -////////////////////////////////////////////////////////////////////////// +// INITIATION - Without this line the question type is not in use. $QTYPES['numerical']= new question_numerical_qtype(); // The following adds the questiontype to the menu of types shown to teachers $QTYPE_MENU['numerical'] = get_string("numerical", "quiz");
:: - id, true, true, $question->category); ?> + id, true, true, $question->category); ?>
:: - " /> - + " /> +
: + :


@@ -30,7 +30,7 @@
- "; } @@ -54,9 +54,10 @@
:: - image", get_string("none"),"",""); @@ -65,42 +66,65 @@
:: - " /> + " /> - +
:: - " /> + " /> - +
:: - " /> + answer === '' && !isset($answer->tolerance) && !isset($answer->fraction)) { + $answervalue = ''; + $answertolerance = ''; + $fractionval = 0; + $feedbacktext = ''; + } else { + $answervalue = $answer->answer; + $answertolerance = $answer->tolerance; + $fractionval = $answer->fraction; + $feedbacktext = $answer->feedback; + } + ?> +    +  ±   + +
:: - " />± - +
: - -  
:: multiplier) { $unit = $units[0]->unit; @@ -111,12 +135,12 @@ ?> " /> - () + ()
::
- : - : + " />    : - +