Quiz refactoring is now merged into the main development code!

I've done some basic testing using existing quizzes trying as
much as I could think of, and it all worked well!

Well done, Henrik!  Great job!

Still, this should be regarded as unstable at least until more
people have had a try.  Please test this thing solidly on new
and old quizzes.
This commit is contained in:
moodler 2004-07-21 13:01:08 +00:00
parent 03fe48e753
commit 8966a11131
44 changed files with 4301 additions and 2037 deletions

View File

@ -117,8 +117,8 @@
$timelimit = $quiz->timelimit * 60;
$unattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id);
if($timelimit > 0) {
$unattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id);
$timestart = $unattempt->timestart;
if($timestart) {
$timesincestart = time() - $timestart;
@ -153,28 +153,16 @@
error("No questions found!");
}
foreach ($rawanswers as $key => $value) { // Parse input for question -> answers
foreach ($rawanswers as $key => $value) { // Parse input for question->response
if (ereg('^q([0-9]+)$', $key, $keyregs)) { // It's a real question number, not a coded one
$questions[$keyregs[1]]->answer[] = trim($value);
} else if (ereg('^q([0-9]+)rq([0-9]+)$', $key, $keyregs)) { // Random Question information
$questions[$keyregs[1]]->random = $keyregs[2];
} else if (ereg('^q([0-9]+)a([0-9]+)$', $key, $keyregs)) { // Checkbox style multiple answers
$questions[$keyregs[1]]->answer[] = $keyregs[2];
} else if (ereg('^q([0-9]+)r([0-9]+)$', $key, $keyregs)) { // Random-style answers
$questions[$keyregs[1]]->answer[] = "$keyregs[2]-$value";
} else if (ereg('^q([0-9]+)ma([0-9]+)$', $key, $keyregs)) { // Multi-answer questions
$questions[$keyregs[1]]->answer[] = "$keyregs[2]-$value";
if ($postedquestionid = quiz_extract_posted_id($key)) {
$questions[$postedquestionid]->response[$key] = trim($value);
} else if ('shuffleorder' == $key) {
$shuffleorder = explode(",", $value); // Actual order questions were given in
} else { // Useful for debugging new question types. Must be last.
error("Answer received for non-existent question ($key -> $value)");
error("Unrecognizable input has been posted ($key -> $value)");
}
}
@ -184,7 +172,15 @@
}
}
if (!$result = quiz_grade_attempt_results($quiz, $questions)) {
/// Retrieve ->maxgrade for all questions
If (!($grades = quiz_get_question_grades($quiz->id, $quiz->questions))) {
$grades = array();
}
foreach ($grades as $qid => $grade) {
$questions[$qid]->maxgrade = $grade->grade;
}
if (!$result = quiz_grade_responses($quiz, $questions)) {
error("Could not grade your quiz attempt!");
}
@ -213,7 +209,7 @@
if ($quiz->feedback) {
$quiz->shuffleanswers = false; // Never shuffle answers in feedback
quiz_print_quiz_questions($quiz, $result, $questions, $shuffleorder);
quiz_print_quiz_questions($quiz, $questions, $result, $shuffleorder);
print_continue("view.php?id=$cm->id");
}
@ -233,13 +229,14 @@
/// Actually seeing the questions marks the start of an attempt
if (!$unfinished = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
if ($newattemptid = quiz_start_attempt($quiz->id, $USER->id, $attemptnumber)) {
add_to_log($course->id, "quiz", "attempt",
"review.php?id=$cm->id&attempt=$newattemptid", "$quiz->id", $cm->id);
} else {
error("Sorry! Could not start the quiz (could not save starting time)");
}
if (isset($unattempt) && $unattempt) {
$attempt = $unattempt;
} else if ($attempt = quiz_start_attempt($quiz->id, $USER->id, $attemptnumber)) {
add_to_log($course->id, "quiz", "attempt",
"review.php?id=$cm->id&attempt=$attempt->id", "$quiz->id", $cm->id);
} else {
error("Sorry! Could not start the quiz (could not save starting time)");
}
/// First print the headings and so on
@ -266,58 +263,32 @@
echo "<br />";
$result = NULL; // Default
$questions = NULL; // Default
if ($quiz->attemptonlast && !empty($attempts)) {
$latestfinishedattempt->attempt = 0;
foreach ($attempts as $attempt) {
if ($attempt->timefinish
&& $attempt->attempt > $latestfinishedattempt->attempt)
{
$latestfinishedattempt = $attempt;
}
}
if ($latestfinishedattempt->attempt > 0
and $questions =
quiz_get_attempt_responses($latestfinishedattempt))
{
// An previous attempt to continue on is found:
quiz_remove_unwanted_questions($questions, $quiz); // In case the quiz has been changed
if (!($result = quiz_grade_attempt_results($quiz, $questions))) {
// No results, reset to defaults:
$questions = NULL;
$result = NULL;
} else {
// We're on, latest attempt responses are to be included.
// In order to have this accomplished by
// the method quiz_print_quiz_questions we need to
// temporarilly change some of the $quiz attributes
// and remove some of the information from result.
$quiz->correctanswers = false; // Not a good idea to show them, huh?
$result->feedback = array(); // Not to be printed
$result->attemptbuildsonthelast = true;
}
} else {
// No latest attempt, or latest attempt was empty - Reset to defaults
$questions = NULL;
}
$questions = quiz_get_attempt_questions($quiz, $attempt, true);
if ($quiz->attemptonlast && $attemptnumber >= 2 and
$quiz->attempts == 0 || !unattempt) {
// There are unlimited attempts or it is a new attempt.
// As the attempt also builds on the last, we can here
// have the student see the scores of the pre-entered
// responses that we here will have graded:
$result = quiz_grade_responses($quiz, $questions);
$result->attemptbuildsonthelast = true;
} else {
$result = NULL;
}
if (! quiz_print_quiz_questions($quiz, $result, $questions)) {
// We do not show feedback or correct answers during an attempt:
$quiz->feedback = $quiz->correctanswers = false;
if (!quiz_print_quiz_questions($quiz, $questions, $result)) {
print_continue("view.php?id=$cm->id");
}
/// BEGIN EDIT if quiz is available and time limit is set
/// include floating timer.
/// If quiz is available and time limit is set include floating timer.
if($available and $timelimit > 0) {
if ($available and $timelimit > 0) {
require('jstimer.php');
}
/// END EDIT
/// Finish the page
print_footer($course);
?>

View File

@ -59,7 +59,9 @@ class quiz_default_format {
// Now to save all the answers and type-specific options
$result = quiz_save_question_options($question);
global $QUIZ_QTYPES;
$result = $QUIZ_QTYPES[$question->qtype]
->save_question_options($question);
if (!empty($result->error)) {
notify($result->error);

View File

@ -9,135 +9,6 @@
// Based on format.php, included by ../../import.php
// REGULAR EXPRESSION CONSTANTS
// I do not know any way to make this easier
// Regexes are always awkard when defined but more comprehensible
// when used as constants in the executive code
// ANSWER_ALTERNATIVE regexes
define("ANSWER_ALTERNATIVE_FRACTION_REGEX",
'=|%(-?[0-9]+)%');
define("ANSWER_ALTERNATIVE_ANSWER_REGEX",
'[^~#}]+');
define("ANSWER_ALTERNATIVE_FEEDBACK_REGEX",
'[^~}]*');
define("ANSWER_ALTERNATIVE_REGEX",
'(' . ANSWER_ALTERNATIVE_FRACTION_REGEX .')?'
. '(' . ANSWER_ALTERNATIVE_ANSWER_REGEX . ')'
. '(#(' . ANSWER_ALTERNATIVE_FEEDBACK_REGEX .'))?');
// Parenthesis positions for ANSWER_ALTERNATIVE_REGEX
define("ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION", 2);
define("ANSWER_ALTERNATIVE_REGEX_FRACTION", 1);
define("ANSWER_ALTERNATIVE_REGEX_ANSWER", 3);
define("ANSWER_ALTERNATIVE_REGEX_FEEDBACK", 5);
// NUMBER_FORMATED_ALTERNATIVE_ANSWER_REGEX is used
// for identifying numerical answers in ANSWER_ALTERNATIVE_REGEX_ANSWER
define("NUMBER_REGEX",
'-?(([0-9]+[.,]?[0-9]*|[.,][0-9]+)([eE][-+]?[0-9]+)?)');
define("NUMERICAL_ALTERNATIVE_REGEX",
'^(' . NUMBER_REGEX . ')(:' . NUMBER_REGEX . ')?$');
// Parenthesis positions for NUMERICAL_FORMATED_ALTERNATIVE_ANSWER_REGEX
define("NUMERICAL_CORRECT_ANSWER", 1);
define("NUMERICAL_ABS_ERROR_MARGIN", 6);
// Remaining ANSWER regexes
define("ANSWER_TYPE_DEF_REGEX",
'(NUMERICAL|NM)|(MULTICHOICE|MC)|(SHORTANSWER|SA|MW)');
define("ANSWER_START_REGEX",
'\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX . '):');
define("ANSWER_REGEX",
ANSWER_START_REGEX
. '(' . ANSWER_ALTERNATIVE_REGEX
. '(~'
. ANSWER_ALTERNATIVE_REGEX
. ')*)}' );
// Parenthesis positions for singulars in ANSWER_REGEX
define("ANSWER_REGEX_NORM", 1);
define("ANSWER_REGEX_ANSWER_TYPE_NUMERICAL", 3);
define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE", 4);
define("ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER", 5);
define("ANSWER_REGEX_ALTERNATIVES", 6);
function extractMultiAnswerQuestion($text) {
$question = NULL;
$question->qtype= MULTIANSWER;
$question->questiontext= $text;
$question->answers= array();
$question->defaultgrade = 0; // Will be increased for each answer norm
for ($positionkey=1
; ereg(ANSWER_REGEX, $question->questiontext, $answerregs)
; ++$positionkey )
{
unset($multianswer);
$multianswer->positionkey = $positionkey;
$multianswer->norm = $answerregs[ANSWER_REGEX_NORM]
or $multianswer->norm = '1';
if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]) {
$multianswer->answertype = NUMERICAL;
} else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER]) {
$multianswer->answertype = SHORTANSWER;
} else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]){
$multianswer->answertype = MULTICHOICE;
} else {
error("Cannot identify answertype $answerregs[2]");
return false;
}
$multianswer->alternatives= array();
$remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES];
while (ereg(ANSWER_ALTERNATIVE_REGEX, $remainingalts, $altregs)) {
unset($alternative);
if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION]) {
$alternative->fraction = '1';
} else {
$alternative->fraction = .01 *
$altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION]
or $alternative->fraction = '0';
}
$alternative->feedback = $altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK];
if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]
&& ereg(NUMERICAL_ALTERNATIVE_REGEX,
$altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER],
$numregs) )
{
$alternative->answer = $numregs[NUMERICAL_CORRECT_ANSWER];
if ($numregs[NUMERICAL_ABS_ERROR_MARGIN]) {
$alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER]
- $numregs[NUMERICAL_ABS_ERROR_MARGIN];
$alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER]
+ $numregs[NUMERICAL_ABS_ERROR_MARGIN];
} else {
$alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER];
$alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER];
}
} else { // Min and max must stay undefined...
$alternative->answer =
$altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER];
}
$multianswer->alternatives[] = $alternative;
$tmp = explode($altregs[0], $remainingalts, 2);
$remainingalts = $tmp[1];
}
$question->defaultgrade += $multianswer->norm;
$question->answers[] = $multianswer;
$question->questiontext = implode("{#$positionkey}",
explode($answerregs[0], $question->questiontext, 2));
}
return $question;
}
class quiz_file_format extends quiz_default_format {
function readquestions($lines) {
@ -147,7 +18,9 @@ class quiz_file_format extends quiz_default_format {
/// multianswer import
$questions= array();
$thequestion= extractMultiAnswerQuestion(addslashes(implode('',$lines)));
$thequestion= quiz_qtype_multianswer_extract_question
(addslashes(implode('',$lines)));
$thequestion->qtype = MULTIANSWER;
if (!empty($thequestion)) {
$thequestion->name = $lines[0];

File diff suppressed because it is too large Load Diff

View File

@ -98,66 +98,9 @@
}
}
if ($form = data_submitted()) {
// First, save the basic question itself
$question->name = $form->name;
$question->questiontext = $form->questiontext;
$question->questiontextformat = $form->questiontextformat;
if (empty($form->image)) {
$question->image = "";
} else {
$question->image = $form->image;
}
if (isset($form->defaultgrade)) {
$question->defaultgrade = $form->defaultgrade;
}
if ($err = formcheck($question)) {
notify(get_string("someerrorswerefound"));
} else {
if (!empty($question->id)) { // Question already exists
$question->version ++; // Update version number of question
if (!update_record("quiz_questions", $question)) {
error("Could not update question!");
}
} else { // Question is a new one
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
$question->version = 1;
if (!$question->id = insert_record("quiz_questions", $question)) {
error("Could not insert new question!");
}
}
// Now to save all the answers and type-specific options
$form->id = $question->id;
$form->qtype = $question->qtype;
$form->category = $question->category;
$result = quiz_save_question_options($form);
if (!empty($result->error)) {
error($result->error);
}
if (!empty($result->notice)) {
notice($result->notice, "question.php?id=$question->id");
}
if (!empty($result->noticeyesno)) {
notice_yesno($result->noticeyesno, "question.php?id=$question->id", "edit.php");
print_footer($course);
exit;
}
redirect("edit.php");
}
if ($form = data_submitted()) {
$question = $QUIZ_QTYPES[$qtype]->save_question($question,
$form, $course);
}
$grades = array(1,0.9,0.8,0.75,0.70,0.66666,0.60,0.50,0.40,0.33333,0.30,0.25,0.20,0.16666,0.10,0.05,0);
@ -210,160 +153,7 @@
$onsubmit = "";
}
switch ($qtype) {
case SHORTANSWER:
if (!empty($question->id)) {
$options = get_record("quiz_shortanswer", "question", $question->id);
} else {
$options->usecase = 0;
}
if (!empty($options->answers)) {
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
}
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
$answers[] = ""; // Make answer slots, default as blank
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer; // insert answers into slots
$i++;
}
}
print_heading_with_help(get_string("editingshortanswer", "quiz"), "shortanswer", "quiz");
require("shortanswer.html");
break;
case TRUEFALSE:
if (!empty($question->id)) {
$options = get_record("quiz_truefalse", "question", "$question->id");
}
if (!empty($options->trueanswer)) {
$true = get_record("quiz_answers", "id", $options->trueanswer);
} else {
$true->fraction = 1;
$true->feedback = "";
}
if (!empty($options->falseanswer)) {
$false = get_record("quiz_answers", "id", "$options->falseanswer");
} else {
$false->fraction = 0;
$false->feedback = "";
}
if ($true->fraction > $false->fraction) {
$question->answer = 1;
} else {
$question->answer = 0;
}
print_heading_with_help(get_string("editingtruefalse", "quiz"), "truefalse", "quiz");
require("truefalse.html");
break;
case MULTICHOICE:
if (!empty($question->id)) {
$options = get_record("quiz_multichoice", "question", $question->id);
} else {
$options->single = 1;
}
if (!empty($options->answers)) {
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
}
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
$answers[] = ""; // Make answer slots, default as blank
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer; // insert answers into slots
$i++;
}
}
print_heading_with_help(get_string("editingmultichoice", "quiz"), "multichoice", "quiz");
require("multichoice.html");
break;
case MATCH:
if (!empty($question->id)) {
$options = get_record("quiz_match", "question", $question->id);
if (!empty($options->subquestions)) {
$oldsubquestions = get_records_list("quiz_match_sub", "id", $options->subquestions);
}
}
if (empty($subquestions) and empty($subanswers)) {
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
$subquestions[] = ""; // Make question slots, default as blank
$subanswers[] = ""; // Make answer slots, default as blank
}
if (!empty($oldsubquestions)) {
$i=0;
foreach ($oldsubquestions as $oldsubquestion) {
$subquestions[$i] = $oldsubquestion->questiontext; // insert questions into slots
$subanswers[$i] = $oldsubquestion->answertext; // insert answers into slots
$i++;
}
}
}
print_heading_with_help(get_string("editingmatch", "quiz"), "match", "quiz");
require("match.html");
break;
case RANDOMSAMATCH:
if (!empty($question->id)) {
$options = get_record("quiz_randomsamatch", "question", $question->id);
} else {
$options->choose = "";
}
$numberavailable = count_records("quiz_questions", "category", $category->id, "qtype", SHORTANSWER);
print_heading_with_help(get_string("editingrandomsamatch", "quiz"), "randomsamatch", "quiz");
require("randomsamatch.html");
break;
case RANDOM:
print_heading_with_help(get_string("editingrandom", "quiz"), "random", "quiz");
require("random.html");
break;
case DESCRIPTION:
print_heading_with_help(get_string("editingdescription", "quiz"), "description", "quiz");
require("description.html");
break;
case MULTIANSWER:
print_heading_with_help(get_string("editingmultianswer", "quiz"), "multianswer", "quiz");
require("editmultianswer.php");
break;
case NUMERICAL:
// This will only support one answer of the type NUMERICAL
// However, lib.php has support for multiple answers
if (!empty($question->id)) {
$answersraw= quiz_get_answers($question);
}
$answers= array();
for ($i=0; $i<6; $i++) {
$answers[$i]->answer = ""; // Make answer slots, default as blank...
$answers[$i]->min = "";
$answers[$i]->max = "";
$answers[$i]->feedback = "";
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer;
$i++;
}
}
print_heading_with_help(get_string("editingnumerical", "quiz"), "numerical", "quiz");
require("numerical.html");
break;
default:
error("Invalid question type");
break;
}
require('questiontypes/'.$QUIZ_QTYPES[$qtype]->name().'/editquestion.php');
print_footer($course);

View File

@ -0,0 +1,80 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php quiz_category_select_menu($course->id, true, true); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!isset($question->questiontextformat)) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="hidden" name=defaultgrade value="0">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,6 @@
<?PHP // $Id$
print_heading_with_help(get_string("editingdescription", "quiz"), "description", "quiz");
require("description.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

View File

@ -0,0 +1,57 @@
<?PHP // $Id$
///////////////////
/// DESCRIPTION ///
///////////////////
/// QUESTION TYPE CLASS //////////////////
//
// The question type DESCRIPTION is not really a question type
// and it therefore often sticks to some kind of odd behaviour
//
class quiz_description_qtype extends quiz_default_questiontype {
function name() {
return 'description';
}
function save_question_options($question) {
/// No options to be saved for this question type:
return true;
}
function create_response($question, $nameprefix, $questionsinuse) {
/// This question type does never have any responses,
/// so do not return any...
return array();
}
function print_question($currentnumber, $quiz, $question,
$readonly, $resultdetails) {
echo '<p align="center">';
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
echo '</p>';
return $currentnumber;
}
function grade_response($question, $nameprefix) {
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[DESCRIPTION]= new quiz_description_qtype();
?>

View File

@ -0,0 +1,25 @@
<?PHP // $Id$
if (!empty($question->id)) {
$options = get_record("quiz_match", "question", $question->id);
if (!empty($options->subquestions)) {
$oldsubquestions = get_records_list("quiz_match_sub", "id", $options->subquestions);
}
}
if (empty($subquestions) and empty($subanswers)) {
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
$subquestions[] = ""; // Make question slots, default as blank
$subanswers[] = ""; // Make answer slots, default as blank
}
if (!empty($oldsubquestions)) {
$i=0;
foreach ($oldsubquestions as $oldsubquestion) {
$subquestions[$i] = $oldsubquestion->questiontext; // insert questions into slots
$subanswers[$i] = $oldsubquestion->answertext; // insert answers into slots
$i++;
}
}
}
print_heading_with_help(get_string("editingmatch", "quiz"), "match", "quiz");
require("match.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

View File

@ -0,0 +1,104 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php quiz_category_select_menu($course->id, true, true); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!isset($question->questiontextformat)) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("choices", "quiz") ?></B>:</P></TD>
<TD><P><?php print_string("filloutthreequestions", "quiz") ?></P>
</TD>
</TR>
<?PHP
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
?>
<TR valign=top>
<TD align=right><P><B><?php echo get_string("question", "quiz")." $i"; ?> :</B></P></TD>
<TD>
<textarea name="subquestions[]" rows=5 cols=50><?php p($subquestions[$i-1]) ?></textarea>
<br />
<?php echo get_string("matchanswer", "quiz")." $i"; ?>&nbsp;&nbsp;
<INPUT type="text" name="subanswers[]" size=50 value="<?php p($subanswers[$i-1]) ?>">
</TD>
</TR>
<?PHP
}
?>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,247 @@
<?PHP // $Id$
/////////////
/// MATCH ///
/////////////
/// QUESTION TYPE CLASS //////////////////
class quiz_match_qtype extends quiz_default_questiontype {
function name() {
return 'match';
}
function save_question_options($question) {
if (!$oldsubquestions = get_records("quiz_match_sub", "question", $question->id, "id ASC")) {
$oldsubquestions = array();
}
// following hack to check at least three answers exist
$answercount = 0;
foreach ($question->subquestions as $key=>$questiontext) {
$answertext = $question->subanswers[$key];
if (!empty($questiontext) and !empty($answertext)) {
$answercount++;
}
}
$answercount += count($oldsubquestions);
if ($answercount < 3) { // check there are at lest 3 answers for matching type questions
$result->notice = get_string("notenoughanswers", "quiz", "3");
return $result;
}
$subquestions = array();
// Insert all the new question+answer pairs
foreach ($question->subquestions as $key => $questiontext) {
$answertext = $question->subanswers[$key];
if (!empty($questiontext) and !empty($answertext)) {
if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it
$subquestion->questiontext = $questiontext;
$subquestion->answertext = $answertext;
if (!update_record("quiz_match_sub", $subquestion)) {
$result->error = "Could not insert quiz match subquestion! (id=$subquestion->id)";
return $result;
}
} else {
unset($subquestion);
$subquestion->question = $question->id;
$subquestion->questiontext = $questiontext;
$subquestion->answertext = $answertext;
if (!$subquestion->id = insert_record("quiz_match_sub", $subquestion)) {
$result->error = "Could not insert quiz match subquestion!";
return $result;
}
}
$subquestions[] = $subquestion->id;
}
}
if (count($subquestions) < 3) {
$result->noticeyesno = get_string("notenoughsubquestions", "quiz");
return $result;
}
if ($options = get_record("quiz_match", "question", $question->id)) {
$options->subquestions = implode(",",$subquestions);
if (!update_record("quiz_match", $options)) {
$result->error = "Could not update quiz match options! (id=$options->id)";
return $result;
}
} else {
unset($options);
$options->question = $question->id;
$options->subquestions = implode(",",$subquestions);
if (!insert_record("quiz_match", $options)) {
$result->error = "Could not insert quiz match options!";
return $result;
}
}
return true;
}
function convert_to_response_answer_field($questionresponse) {
/// This method, together with extract_response, should be
/// obsolete as soon as we get a better response storage
$delimiter = '';
$responseanswerfield = '';
foreach ($questionresponse as $key => $value) {
if ($matchid = $this->extract_response_id($key)) {
$responseanswerfield .= "$delimiter$matchid-$value";
$delimiter = ',';
} else {
notify("Error: Illegal match key $key detected");
}
}
return $responseanswerfield;
}
function extract_response($rawresponse, $nameprefix) {
if (!($options = get_record("quiz_match",
"question", $rawresponse->question))) {
notify("Error: Missing question options!");
return array();
}
$subids = explode(',', $options->subquestions);
foreach ($subids as $subid) {
$response[$nameprefix.$subid] =
ereg("(^|,)$subid-([^,]+)", $rawresponse->answer, $regs)
? $regs[2]
: '';
}
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
// Print question text and possible image
if (!empty($question->questiontext)) {
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
}
quiz_print_possible_question_image($quiz->id, $question);
// It so happens to be that $correctanswers for this question type also
// contains the subqustions, which we need to make sure we have:
if (empty($correctanswers)) {
$options = get_record('quiz_match', 'question', $question->id)
and $subquestions = get_records_list('quiz_match_sub', 'id',
$options->subquestions);
} else {
$subquestions = $correctanswers;
}
/// Check whether everything turned out alright:
if (empty($subquestions)) {
notify("Error: Missing subquestions for this question!");
} else {
/// Everything is fine -
/// Set up $subquestions and $answers and do the shuffling:
if ($quiz->shuffleanswers) {
$subquestions = draw_rand_array($subquestions,
count($subquestions));
}
foreach ($subquestions as $key => $subquestion) {
unset($answers[$key]);
$answers[$subquestion->id] = $subquestion->answertext;
}
$answers = draw_rand_array($answers, count($answers));
}
///// Ptint the input controls //////
echo '<table border="0" cellpadding="10" align="right">';
foreach ($subquestions as $subquestion) {
/// Subquestion text:
echo '<tr><td align="left" valign="top">';
echo $subquestion->questiontext;
echo '</td>';
/// Drop-down list:
$menuname = $nameprefix.$subquestion->id;
$response = isset($question->response[$menuname])
? $question->response[$menuname] : '0';
if (isset($correctanswers[$menuname])
&& $correctanswers[$menuname]->id
== $response) {
$class = ' class="highlight" ';
} else {
$class = '';
}
echo "<td align=\"right\" valign=\"top\" $class>";
choose_from_menu($answers, $menuname, $response);
if ($quiz->feedback && isset($answers[$menuname])
&& $answers[$menuname]->feedback) {
quiz_print_comment($answers[$menuname]->feedback);
}
echo '</td></tr>';
}
echo '</table>';
}
function grade_response($question, $nameprefix) {
/// This question type does not use the table quiz_answers
/// but we will take some measures to emulate that record anyway.
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
if (!($options = get_record('quiz_match', 'question', $question->id)
and $subquestions = get_records_list('quiz_match_sub',
'id', $options->subquestions))) {
notify("Error: Cannot find match options and subquestions
for question $question->id");
return $result;
}
$fraction = 1.0 / count($subquestions);
/// Populate correctanswers arrays:
foreach ($subquestions as $subquestion) {
$subquestion->fraction = $fraction;
$subquestion->answer = $subquestion->answertext;
$subquestion->feedback = '';
$result->correctanswers[$nameprefix.$subquestion->id] =
$subquestion;
}
foreach ($question->response as $responsekey => $answerid) {
if ($answerid and $answer =
$result->correctanswers[$nameprefix.$answerid]) {
if ($result->correctanswers[$responsekey]->answer
== $answer->answer) {
/// The response was correct!
$result->answers[$responsekey] =
$result->correctanswers[$responsekey];
$result->grade += $fraction;
} else {
/// The response was incorrect:
$answer->fraction = 0.0;
$result->answers[$responsekey] = $answer;
}
}
}
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[MATCH]= new quiz_match_qtype();
?>

View File

@ -0,0 +1,46 @@
<?PHP // $Id$
if ($question->questiontext and $question->id) {
$answers = quiz_get_answers($question);
foreach ($answers as $multianswer) {
$parsableanswerdef = '{' . $multianswer->norm . ':';
switch ($multianswer->answertype) {
case MULTICHOICE:
$parsableanswerdef .= 'MULTICHOICE:';
break;
case SHORTANSWER:
$parsableanswerdef .= 'SHORTANSWER:';
break;
case NUMERICAL:
$parsableanswerdef .= 'NUMERICAL:';
break;
default:
error("answertype $multianswer->answertype not recognized");
}
$separator= '';
foreach ($multianswer->subanswers as $subanswer) {
$parsableanswerdef .= $separator
. '%' . round(100*$subanswer->fraction) . '%';
$parsableanswerdef .= $subanswer->answer;
if (isset($subanswer->min) && isset($subanswer->max)
and $subanswer->min || $subanswer->max) {
// Special for numerical answers:
$errormargin = $subanswer->answer - $subanswer->min;
$parsableanswerdef .= ":$errormargin";
}
if ($subanswer->feedback) {
$parsableanswerdef .= "#$subanswer->feedback";
}
$separator = '~';
}
$parsableanswerdef .= '}';
$question->questiontext = str_replace
("{#$multianswer->positionkey}", $parsableanswerdef,
$question->questiontext);
}
}
print_heading_with_help(get_string('editingmultianswer', 'quiz'),
'multianswer', 'quiz');
require('multianswer.html');
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

View File

@ -0,0 +1,114 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php quiz_category_select_menu($course->id, true, true ); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("question", "quiz") ?>:</B></P></TD>
<TD>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<BR />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle");
} else {
helpbutton("text", get_string("helptext"), "moodle");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="hidden" name=defaultgrade value="<?php p($question->defaultgrade) ?>">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,623 @@
<?PHP // $Id$
///////////////////
/// MULTIANSWER /// (Embedded - cloze)
///////////////////
///
/// The multianswer question type is special in that it
/// depends on a few other question types, i.e.
/// MULTICHOICE, SHORTANSWER and NUMERICAL.
/// These question types have got a few special features that
/// makes them useable by the MULTIANSWER question type
///
/// QUESTION TYPE CLASS //////////////////
class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
function get_answers($question) {
/// The returned answers includes subanswers...
// As this question type embedds some other question types,
// it is necessary to have access to those:
global $QUIZ_QTYPES;
$answers = array();
$virtualquestion->id = $question->id;
if ($multianswers = get_records('quiz_multianswers', 'question', $question->id)) {
foreach ($multianswers as $multianswer) {
$virtualquestion->qtype = $multianswer->answertype;
// Call to other question type for subanswers
$addedcondition = " AND a.id IN ($multianswer->answers) ";
$multianswer->subanswers =
$QUIZ_QTYPES[$multianswer->answertype]
->get_answers($virtualquestion, $addedcondition);
$answers[] = $multianswer;
}
}
return $answers;
}
function name() {
return 'multianswer';
}
function save_question_options($question) {
if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
$oldmultianswers = array();
}
// Insert all the new multi answers
foreach ($question->answers as $dataanswer) {
if ($oldmultianswer = array_shift($oldmultianswers)) { // Existing answer, so reuse it
$multianswer = $oldmultianswer;
$multianswer->positionkey = $dataanswer->positionkey;
$multianswer->norm = $dataanswer->norm;
$multianswer->answertype = $dataanswer->answertype;
if (! $multianswer->answers =
quiz_qtype_multianswer_save_alternatives
($question->id, $dataanswer->answertype,
$dataanswer->alternatives, $oldmultianswer->answers))
{
$result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
return $result;
}
if (!update_record("quiz_multianswers", $multianswer)) {
$result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
return $result;
}
} else { // This is a completely new answer
unset($multianswer);
$multianswer->question = $question->id;
$multianswer->positionkey = $dataanswer->positionkey;
$multianswer->norm = $dataanswer->norm;
$multianswer->answertype = $dataanswer->answertype;
if (! $multianswer->answers =
quiz_qtype_multianswer_save_alternatives
($question->id, $dataanswer->answertype,
$dataanswer->alternatives))
{
$result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
return $result;
}
if (!insert_record("quiz_multianswers", $multianswer)) {
$result->error = "Could not insert quiz multianswer!";
return $result;
}
}
}
}
function save_question($authorizedquestion, $form, $course) {
$question = quiz_qtype_multianswer_extract_question
($form->questiontext);
$question->id = $authorizedquestion->id;
$question->qtype = $authorizedquestion->qtype;
$question->category = $authorizedquestion->category;
$question->name = $form->name;
if (empty($form->image)) {
$question->image = "";
} else {
$question->image = $form->image;
}
// Formcheck
$err = array();
if (empty($question->name)) {
$err["name"] = get_string("missingname", "quiz");
}
if (empty($question->questiontext)) {
$err["questiontext"] = get_string("missingquestiontext", "quiz");
}
if ($err) { // Formcheck failed
notify(get_string("someerrorswerefound"));
} else {
if (!empty($question->id)) { // Question already exists
if (!update_record("quiz_questions", $question)) {
error("Could not update question!");
}
} else { // Question is a new one
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
if (!$question->id = insert_record("quiz_questions", $question)) {
error("Could not insert new question!");
}
}
// Now to save all the answers and type-specific options
$result = $this->save_question_options($question);
if (!empty($result->error)) {
error($result->error);
}
if (!empty($result->notice)) {
notice_yesno($result->notice, "question.php?id=$question->id", "edit.php");
print_footer($course);
exit;
}
redirect("edit.php");
}
}
function convert_to_response_answer_field($questionresponse) {
/// This method, together with extract_response, should be
/// obsolete as soon as we get a better response storage
$delimiter = '';
$responseanswerfield = '';
foreach ($questionresponse as $key => $value) {
if ($multianswerid = $this->extract_response_id($key)) {
$responseanswerfield .= "$delimiter$multianswerid-$value";
$delimiter = ',';
} else {
notify("Error: Illegal match key $key detected");
}
}
return $responseanswerfield;
}
function extract_response($rawresponse, $nameprefix) {
/// A temporary fix for bug #647 has accidently been enforced here
/// because of the odd circumstances during the refactoring
$multianswers = get_records('quiz_multianswers',
'question', $rawresponse->question);
$response = array();
foreach ($multianswers as $maid => $multianswer) {
if (ereg("(^|,)$maid-(.*)", $rawresponse->answer, $regs)) {
$splits = split(',[0-9]+-', $regs[2], 2);
$response[$nameprefix.$maid] = $splits[0];
} else {
$response[$nameprefix.$maid] = '';
}
}
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
global $THEME;
// For this question type, we better print the image on top:
quiz_print_possible_question_image($quiz->id, $question);
$qtextremaining = format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
$strfeedback = get_string('feedback', 'quiz');
// The regex will recognize text snippets of type {#X}
// where the X can be any text not containg } or white-space characters.
while (ereg('\{#([^[:space:]}]*)}', $qtextremaining, $regs)) {
$qtextsplits = explode($regs[0], $qtextremaining, 2);
echo $qtextsplits[0];
$qtextremaining = $qtextsplits[1];
$multianswer = get_record('quiz_multianswers', 'question',
$question->id, 'positionkey', $regs[1]);
$inputname = $nameprefix.$multianswer->id;
$response = isset($question->response[$inputname])
? $question->response[$inputname] : '';
/// Determine style
if (!empty($correctanswers) && '' !== $response) {
if (!isset($answers[$inputname])
|| $answers[$inputname]->fraction <= 0.0) {
// The response must have been totally wrong:
$style = ' style="background-color:red" ';
} else if ($answers[$inputname]->fraction >= 1.0) {
// The response must was correct!!
$style = 'style="background-color:lime"';
} else {
// This response did at least give some credit:
$style = 'style="background-color:yellow"';
}
} else {
// No colorish feedback is to be used
$style = '';
}
// Determine feedback popup if any
if ($quiz->feedback && isset($answers[$inputname])
&& '' !== $answers[$inputname]->feedback) {
$title = str_replace("'", "\\'", $answers[$inputname]->feedback);
$popup = " onmouseover=\"return overlib('$title', CAPTION, '$strfeedback', FGCOLOR, '$THEME->cellcontent');\" ".
" onmouseout=\"return nd();\" ";
} else {
$popup = '';
}
// Print the input control
switch ($multianswer->answertype) {
case SHORTANSWER:
case NUMERICAL:
echo " <input $style $readonly $popup name=\"$inputname\"
type=\"text\" value=\"$response\" size=\"12\" /> ";
break;
case MULTICHOICE:
$outputoptions = '<option></option>'; // Default empty option
$mcanswers = get_records_list("quiz_answers", "id", $multianswer->answers);
foreach ($mcanswers as $mcanswer) {
$selected = $response == $mcanswer->id
? ' selected="selected" ' : '';
$outputoptions .= "<option value=\"$mcanswer->id\" $selected>$mcanswer->answer</option>";
}
echo "<select $popup $style name=\"$inputname\" $readonly>";
echo $outputoptions;
echo '</select>';
break;
default:
error("Unable to recognized answertype $answer->answertype");
break;
}
}
// Print the final piece of question text:
echo $qtextremaining;
}
function grade_response($question, $nameprefix) {
global $QUIZ_QTYPES;
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
$multianswers = get_records('quiz_multianswers',
'question', $question->id);
// Default settings:
$subquestion->id = $question->id;
$normsum = 0;
// Grade each multianswer
foreach ($multianswers as $multianswer) {
$name = $nameprefix.$multianswer->id;
$subquestion->response[$nameprefix] =
isset($question->response[$name])
? $question->response[$name] : '';
$subresult = $QUIZ_QTYPES[$multianswer->answertype]
->grade_response($subquestion, $nameprefix,
" AND a.id IN ($multianswer->answers) ");
// Summarize subquestion results:
if (isset($subresult->answers[$nameprefix])) {
/// Answer was found:
$result->answers[$name] = $subresult->answers[$nameprefix];
if ($result->answers[$name]->fraction >= 1.0) {
// This is also the correct answer:
$result->correctanswers[$name] = $result->answers[$name];
}
}
if (!isset($result->correctanswers[$name])) {
// Pick the first correctanswer:
foreach ($subresult->correctanswers as $correctanswer) {
$result->correctanswers[$name] = $correctanswer;
break;
}
}
$result->grade += $multianswer->norm * $subresult->grade;
$normsum += $multianswer->norm;
}
$result->grade /= $normsum;
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[MULTIANSWER]= new quiz_embedded_cloze_qtype();
/////////////////////////////////////////////////////////////
//// ADDITIONAL FUNCTIONS
//// The functions below deal exclusivly with editing
//// of questions with question type MULTIANSWER.
//// Therefore they are kept in this file.
//// They are not in the class as they are not
//// likely to be subject for overriding.
/////////////////////////////////////////////////////////////
function quiz_qtype_multianswer_extract_question($text) {
////////////////////////////////////////////////
//// Define some constants first. It is not the
//// pattern commonly used in quiz/questiontypes.
//// The reason is that it has been moved here from
//// quiz/format/multianswer/format.php
////////////////////////////////////////////////
// REGULAR EXPRESSION CONSTANTS
// I do not know any way to make this easier
// Regexes are always awkard when defined but more comprehensible
// when used as constants in the executive code
// ANSWER_ALTERNATIVE regexes
define("ANSWER_ALTERNATIVE_FRACTION_REGEX",
'=|%(-?[0-9]+)%');
define("ANSWER_ALTERNATIVE_ANSWER_REGEX",
'[^~#}]+');
define("ANSWER_ALTERNATIVE_FEEDBACK_REGEX",
'[^~}]*');
define("ANSWER_ALTERNATIVE_REGEX",
'(' . ANSWER_ALTERNATIVE_FRACTION_REGEX .')?'
. '(' . ANSWER_ALTERNATIVE_ANSWER_REGEX . ')'
. '(#(' . ANSWER_ALTERNATIVE_FEEDBACK_REGEX .'))?');
// Parenthesis positions for ANSWER_ALTERNATIVE_REGEX
define("ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION", 2);
define("ANSWER_ALTERNATIVE_REGEX_FRACTION", 1);
define("ANSWER_ALTERNATIVE_REGEX_ANSWER", 3);
define("ANSWER_ALTERNATIVE_REGEX_FEEDBACK", 5);
// NUMBER_FORMATED_ALTERNATIVE_ANSWER_REGEX is used
// for identifying numerical answers in ANSWER_ALTERNATIVE_REGEX_ANSWER
define("NUMBER_REGEX",
'-?(([0-9]+[.,]?[0-9]*|[.,][0-9]+)([eE][-+]?[0-9]+)?)');
define("NUMERICAL_ALTERNATIVE_REGEX",
'^(' . NUMBER_REGEX . ')(:' . NUMBER_REGEX . ')?$');
// Parenthesis positions for NUMERICAL_FORMATED_ALTERNATIVE_ANSWER_REGEX
define("NUMERICAL_CORRECT_ANSWER", 1);
define("NUMERICAL_ABS_ERROR_MARGIN", 6);
// Remaining ANSWER regexes
define("ANSWER_TYPE_DEF_REGEX",
'(NUMERICAL|NM)|(MULTICHOICE|MC)|(SHORTANSWER|SA|MW)');
define("ANSWER_START_REGEX",
'\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX . '):');
define("ANSWER_REGEX",
ANSWER_START_REGEX
. '(' . ANSWER_ALTERNATIVE_REGEX
. '(~'
. ANSWER_ALTERNATIVE_REGEX
. ')*)}' );
// Parenthesis positions for singulars in ANSWER_REGEX
define("ANSWER_REGEX_NORM", 1);
define("ANSWER_REGEX_ANSWER_TYPE_NUMERICAL", 3);
define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE", 4);
define("ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER", 5);
define("ANSWER_REGEX_ALTERNATIVES", 6);
////////////////////////////////////////
//// Start of the actual function
////////////////////////////////////////
$question = NULL;
$question->qtype= MULTIANSWER;
$question->questiontext= $text;
$question->answers= array();
$question->defaultgrade = 0; // Will be increased for each answer norm
for ($positionkey=1
; ereg(ANSWER_REGEX, $question->questiontext, $answerregs)
; ++$positionkey )
{
unset($multianswer);
$multianswer->positionkey = $positionkey;
$multianswer->norm = $answerregs[ANSWER_REGEX_NORM]
or $multianswer->norm = '1';
if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]) {
$multianswer->answertype = NUMERICAL;
} else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER]) {
$multianswer->answertype = SHORTANSWER;
} else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]){
$multianswer->answertype = MULTICHOICE;
} else {
error("Cannot identify answertype $answerregs[2]");
return false;
}
$multianswer->alternatives= array();
$remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES];
while (ereg(ANSWER_ALTERNATIVE_REGEX, $remainingalts, $altregs)) {
unset($alternative);
if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION]) {
$alternative->fraction = '1';
} else {
$alternative->fraction = .01 *
$altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION]
or $alternative->fraction = '0';
}
$alternative->feedback = $altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK];
if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]
&& ereg(NUMERICAL_ALTERNATIVE_REGEX,
$altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER],
$numregs) )
{
$alternative->answer = $numregs[NUMERICAL_CORRECT_ANSWER];
if ($numregs[NUMERICAL_ABS_ERROR_MARGIN]) {
$alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER]
- $numregs[NUMERICAL_ABS_ERROR_MARGIN];
$alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER]
+ $numregs[NUMERICAL_ABS_ERROR_MARGIN];
} else {
$alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER];
$alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER];
}
} else { // Min and max must stay undefined...
$alternative->answer =
$altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER];
}
$multianswer->alternatives[] = $alternative;
$tmp = explode($altregs[0], $remainingalts, 2);
$remainingalts = $tmp[1];
}
$question->defaultgrade += $multianswer->norm;
$question->answers[] = $multianswer;
$question->questiontext = implode("{#$positionkey}",
explode($answerregs[0], $question->questiontext, 2));
}
return $question;
}
function quiz_qtype_multianswer_save_alternatives($questionid,
$answertype, $alternatives, $oldalternativeids= NULL) {
// Returns false if something goes wrong,
// otherwise the ids of the answers.
if (empty($oldalternativeids)
or !($oldalternatives =
get_records_list('quiz_answers', 'id', $oldalternativeids)))
{
$oldalternatives = array();
}
$alternativeids = array();
foreach ($alternatives as $altdata) {
if ($altold = array_shift($oldalternatives)) { // Use existing one...
$alt = $altold;
$alt->answer = $altdata->answer;
$alt->fraction = $altdata->fraction;
$alt->feedback = $altdata->feedback;
if (!update_record("quiz_answers", $alt)) {
return false;
}
} else { // Completely new one
unset($alt);
$alt->question= $questionid;
$alt->answer = $altdata->answer;
$alt->fraction = $altdata->fraction;
$alt->feedback = $altdata->feedback;
if (!($alt->id = insert_record("quiz_answers", $alt))) {
return false;
}
}
// For the answer type numerical, each alternative has individual options:
if ($answertype == NUMERICAL) {
if ($numericaloptions =
get_record('quiz_numerical', 'answer', $alt->id))
{
// Reuse existing numerical options
$numericaloptions->min = $altdata->min;
$numericaloptions->max = $altdata->max;
if (!update_record('quiz_numerical', $numericaloptions)) {
return false;
}
} else {
// New numerical options
$numericaloptions->answer = $alt->id;
$numericaloptions->question = $questionid;
$numericaloptions->min = $altdata->min;
$numericaloptions->max = $altdata->max;
if (!insert_record("quiz_numerical", $numericaloptions)) {
return false;
}
}
} else { // Delete obsolete numerical options
delete_records('quiz_numerical', 'answer', $alt->id);
} // end if NUMERICAL
$alternativeids[] = $alt->id;
} // end foreach $alternatives
$answers = implode(',', $alternativeids);
// Removal of obsolete alternatives from answers and quiz_numerical:
while ($altobsolete = array_shift($oldalternatives)) {
delete_records("quiz_answers", "id", $altobsolete->id);
// Possibly obsolute numerical options are also to be deleted:
delete_records("quiz_numerical", 'answer', $altobsolete->id);
}
// Common alternative options and removal of obsolete options
switch ($answertype) {
case NUMERICAL:
if (!empty($oldalternativeids)) {
delete_records('quiz_shortanswer', 'answers',
$oldalternativeids);
delete_records('quiz_multichoice', 'answers',
$oldalternativeids);
}
break;
case SHORTANSWER:
if (!empty($oldalternativeids)) {
delete_records('quiz_multichoice', 'answers',
$oldalternativeids);
$options = get_record('quiz_shortanswer',
'answers', $oldalternativeids);
} else {
unset($options);
}
if (empty($options)) {
// Create new shortanswer options
$options->question = $questionid;
$options->usecase = 0;
$options->answers = $answers;
if (!insert_record('quiz_shortanswer', $options)) {
return false;
}
} else if ($answers != $oldalternativeids) {
// Shortanswer options needs update:
$options->answers = $answers;
if (!update_record('quiz_shortanswer', $options)) {
return false;
}
}
break;
case MULTICHOICE:
if (!empty($oldalternativeids)) {
delete_records('quiz_shortanswer', 'answers',
$oldalternativeids);
$options = get_record('quiz_multichoice',
'answers', $oldalternativeids);
} else {
unset($options);
}
if (empty($options)) {
// Create new multichoice options
$options->question = $questionid;
$options->layout = 0;
$options->single = 1;
$options->answers = $answers;
if (!insert_record('quiz_multichoice', $options)) {
return false;
}
} else if ($answers != $oldalternativeids) {
// Multichoice options needs update:
$options->answers = $answers;
if (!update_record('quiz_multichoice', $options)) {
return false;
}
}
break;
default:
return false;
}
return $answers;
}
?>

View File

@ -0,0 +1,23 @@
<?PHP // $Id$
if (!empty($question->id)) {
$options = get_record("quiz_multichoice", "question", $question->id);
} else {
$options->single = 1;
}
if (!empty($options->answers)) {
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
}
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
$answers[] = ""; // Make answer slots, default as blank
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer; // insert answers into slots
$i++;
}
}
print_heading_with_help(get_string("editingmultichoice", "quiz"), "multichoice", "quiz");
require("multichoice.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

View File

@ -0,0 +1,124 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php quiz_category_select_menu($course->id, true, true); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!isset($question->questiontextformat)) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("answerhowmany", "quiz") ?>:</B></P></TD>
<TD>
<?php
$menu[0] = get_string("answersingleno", "quiz");
$menu[1] = get_string("answersingleyes", "quiz");
choose_from_menu($menu, "single", "$options->single", "");
unset($menu);
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("choices", "quiz") ?></B>:</P></TD>
<TD><P><?php print_string("fillouttwochoices", "quiz") ?></P>
</TD>
</TR>
<?PHP
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
?>
<TR valign=top>
<TD align=right><P><B><?php echo get_string("choice", "quiz")." $i"; ?>:</B></P></TD>
<TD>
<INPUT type="text" name="answer[]" size=50 maxlength=255 value="<?php p($answers[$i-1]->answer) ?>">&nbsp;&nbsp;
<?php print_string("grade");
echo ":&nbsp;";
choose_from_menu($gradeoptionsfull, "fraction[]", $answers[$i-1]->fraction, ""); ?>
<BR>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("feedback", "quiz") ?>:</B></P></TD>
<TD>
<textarea name="feedback[]" rows=2 cols=50 wrap="virtual"><?php p($answers[$i-1]->feedback) ?></textarea>
</TD>
</TR>
<TR valign=top>
<TD colspan=2>&nbsp;</TD>
</TR>
<?php
} /// End of loop, printing answers
?>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,274 @@
<?PHP // $Id$
///////////////////
/// MULTICHOICE ///
///////////////////
/// QUESTION TYPE CLASS //////////////////
///
/// This class contains some special features in order to make the
/// question type embeddable within a multianswer (cloze) question
///
class quiz_multichoice_qtype extends quiz_default_questiontype {
function get_answers($question, $addedcondition= '') {
// The added condition is one addition that has been added
// to the behaviour of this question type in order to make
// it embeddable within a multianswer (embedded cloze) question
global $CFG;
// There should be multiple answers
return get_records_sql("SELECT a.*, mc.single
FROM {$CFG->prefix}quiz_multichoice mc,
{$CFG->prefix}quiz_answers a
WHERE mc.question = '$question->id'
AND mc.question = a.question "
. $addedcondition);
}
function name() {
return 'multichoice';
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question",
$question->id, "id ASC")) {
$oldanswers = array();
}
// following hack to check at least two answers exist
$answercount = 0;
foreach ($question->answer as $key=>$dataanswer) {
if ($dataanswer != "") {
$answercount++;
}
}
$answercount += count($oldanswers);
if ($answercount < 2) { // check there are at lest 2 answers for multiple choice
$result->notice = get_string("notenoughanswers", "quiz", "2");
return $result;
}
// Insert all the new answers
$totalfraction = 0;
$maxfraction = -1;
$answers = array();
foreach ($question->answer as $key => $dataanswer) {
if ($dataanswer != "") {
if ($answer = array_shift($oldanswers)) { // Existing answer, so reuse it
$answer->answer = $dataanswer;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!update_record("quiz_answers", $answer)) {
$result->error = "Could not update quiz answer! (id=$answer->id)";
return $result;
}
} else {
unset($answer);
$answer->answer = $dataanswer;
$answer->question = $question->id;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!$answer->id = insert_record("quiz_answers", $answer)) {
$result->error = "Could not insert quiz answer! ";
return $result;
}
}
$answers[] = $answer->id;
if ($question->fraction[$key] > 0) { // Sanity checks
$totalfraction += $question->fraction[$key];
}
if ($question->fraction[$key] > $maxfraction) {
$maxfraction = $question->fraction[$key];
}
}
}
if ($options = get_record("quiz_multichoice", "question", $question->id)) {
$options->answers = implode(",",$answers);
$options->single = $question->single;
if (!update_record("quiz_multichoice", $options)) {
$result->error = "Could not update quiz multichoice options! (id=$options->id)";
return $result;
}
} else {
unset($options);
$options->question = $question->id;
$options->answers = implode(",",$answers);
$options->single = $question->single;
if (!insert_record("quiz_multichoice", $options)) {
$result->error = "Could not insert quiz multichoice options!";
return $result;
}
}
/// Perform sanity checks on fractional grades
if ($options->single) {
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
return $result;
}
} else {
$totalfraction = round($totalfraction,2);
if ($totalfraction != 1) {
$totalfraction = $totalfraction * 100;
$result->noticeyesno = get_string("fractionsaddwrong", "quiz", $totalfraction);
return $result;
}
}
return true;
}
function extract_response($rawresponse, $nameprefix) {
// Fetch additional details from the database...
if (!$options = get_record("quiz_multichoice",
"question", $rawresponse->question)) {
notify("Error: Missing question options!");
}
if ($options->single) {
return array($nameprefix => $rawresponse->answer);
} else {
$response = array();
$answerids = explode(',', $options->answers);
foreach ($answerids as $answerid) {
$response[$nameprefix.$answerid] =
ereg("(,|^)$answerid(,|$)", $rawresponse->answer)
? $answerid
: '';
}
return $response;
}
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
// Fetch additional details from the database...
if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
notify("Error: Missing question options!");
}
if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
notify("Error: Missing question answers!");
}
// Print formulation
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
// Print input controls and alternatives
echo "<table align=\"right\">";
$stranswer = get_string("answer", "quiz");
echo "<tr><td valign=\"top\">$stranswer:&nbsp;&nbsp;</td><td>";
echo "<table>";
$answerids = explode(",", $options->answers);
if ($quiz->shuffleanswers) {
$answerids = swapshuffle($answerids);
}
// Handle the case of unanswered single-choice questions:
if ($options->single) {
$singleresponse = isset($question->response[$nameprefix])
? $question->response[$nameprefix] : '0';
}
foreach ($answerids as $key => $aid) {
$answer = $answers[$aid];
$qnumchar = chr(ord('a') + $key);
echo '<tr><td valign="top">';
if ($options->single) {
$type = ' type="radio" ';
$name = " name=\"$nameprefix\" ";
$checked = $singleresponse == $aid
? ' checked="checked" ' : '';
} else {
$type = ' type="checkbox" ';
$name = " name=\"$nameprefix$aid\" ";
$checked = !empty($question->response[$nameprefix.$aid])
? ' checked="checked" ' : '';
}
if ($readonly) {
$readonly = ' readonly="readonly" disabled="disabled" ';
}
echo "<input $readonly $name $checked $type value=\"$answer->id\" />";
echo "</td>";
if ($readonly and $quiz->correctanswers || $quiz->feedback
and !empty($correctanswers[$nameprefix.$aid])) {
echo '<td valign="top" class="highlight">'.format_text("$qnumchar. $answer->answer").'</td>';
} else {
echo '<td valign="top">'.format_text("$qnumchar. $answer->answer").'</td>';
}
if ($quiz->feedback) {
echo "<td valign=\"top\">&nbsp;";
if ($checked) { // Simpliest condition to use here
quiz_print_comment($answer->feedback);
}
echo "</td>";
}
echo "</tr>";
}
echo "</table>";
echo "</td></tr></table>";
}
function grade_response($question, $nameprefix, $addedanswercondition='') {
$result->correctanswers = array();
$result->answers = array();
$result->grade = 0.0;
$answers = $this->get_answers($question, $addedanswercondition);
/// Set ->answers[] and ->grade
if (!empty($question->response)) {
foreach ($question->response as $name => $response) {
if (isset($answers[$response])) {
$result->answers[$name] = $answers[$response];
$result->grade += $answers[$response]->fraction;
}
}
}
/// Set ->correctanswers[]
foreach ($answers as $answer) {
if ($answer->single) {
$result->correctanswers =
quiz_extract_correctanswers($answers, $nameprefix);
break;
} else {
if ($answer->fraction > 0.0) {
$result->correctanswers[$nameprefix.$answer->id] = $answer;
}
}
}
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[MULTICHOICE]= new quiz_multichoice_qtype();
?>

View File

@ -0,0 +1,25 @@
<?PHP // $Id$
// This will only support one answer of the type NUMERICAL
// However, lib.php has support for multiple answers
if (!empty($question->id)) {
$answersraw= quiz_get_answers($question);
}
$answers= array();
for ($i=0; $i<6; $i++) {
$answers[$i]->answer = ""; // Make answer slots, default as blank...
$answers[$i]->min = "";
$answers[$i]->max = "";
$answers[$i]->feedback = "";
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer;
$i++;
}
}
print_heading_with_help(get_string("editingnumerical", "quiz"), "numerical", "quiz");
require("numerical.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

View File

@ -0,0 +1,141 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php quiz_category_select_menu($course->id, true, true); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=50 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!isset($question->questiontextformat)) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("correctanswer", "quiz") ?>:</B></P></TD>
<?php
// Even thou the rest of the module can handle up to six numerical answers,
// this form will limit the number of numerical answers to one only.
if (is_numeric($answers[0]->min) && is_numeric($answers[0]->answer)) {
$acceptederror = (float)($answers[0]->answer)
- (float)($answers[0]->min);
} else {
$acceptederror = "";
}
?>
<TD>
<INPUT align="LEFT" type="text" id="correct0" name="answer[]" size="20" value="<?php p($answers[0]->answer) ?>"/>&nbsp;&nbsp;
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("acceptederror", "quiz"); ?>:</B></P></TD>
<TD>
<INPUT align="LEFT" type="text" id="acceptederror0" name="acceptederror[]" size="15" value="<?php p($acceptederror) ?>" />&plusmn;
<!-- Values max and min will be determined when the form is submitted -->
<INPUT type="HIDDEN" id="min0" name="min[]" value=""/>
<INPUT type="HIDDEN" id="max0" name="max[]" value=""/>
<INPUT type="HIDDEN" name="fraction[]" value="1"/>
<BR/>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("feedback", "quiz") ?>:</B></P></TD>
<TD>
<textarea name="feedback[]" rows=2 cols=50 wrap="virtual"><?php p($answers[0]->feedback) ?></textarea>
</TD>
</TR>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="submit" onClick="return determineMinAndMax();" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<SCRIPT language="JAVASCRIPT">
function determineMinAndMax() {
// This client-side script will determine the values for min and max
// based on the input for answer and acceptederror.
with(document.theform) {
if (correct0.value=='') {
alert('<?php print_string("missingcorrectanswer","quiz") ?>');
return false;
} else if (acceptederror0.value=='') {
var correct= parseFloat(correct0.value);
if (!isNaN(correct)) {
min0.value= correct;
max0.value= correct;
}
return true;
} else if (isNaN(acceptederror0.value) || isNaN(correct0.value)) {
alert('<?php print_string("answerswithacceptederrormarginmustbenumeric", "quiz") ?>');
return false;
} else {
var correct= parseFloat(correct0.value);
var error= Math.abs(acceptederror0.value);
min0.value= correct-error;
max0.value= correct+error;
return true;
}
}
}
</SCRIPT>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,178 @@
<?PHP // $Id$
/////////////////
/// 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...
require_once("$CFG->dirroot/mod/quiz/questiontypes/shortanswer/questiontype.php");
class quiz_numerical_qtype extends quiz_shortanswer_qtype {
function get_answers($question, $addedcondition='') {
// The added condition is one addition that has been added
// to the behaviour of this question type in order to make
// it embeddable within a multianswer (embedded cloze) question
global $CFG;
// There can be multiple answers
return get_records_sql("SELECT a.*, n.min, n.max
FROM {$CFG->prefix}quiz_numerical n,
{$CFG->prefix}quiz_answers a
WHERE a.question = '$question->id'
AND n.answer = a.id "
. $addedcondition);
}
function name() {
return 'numerical';
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
$oldanswers = array();
}
$answers = array();
$maxfraction = -1;
// Insert all the new answers
foreach ($question->answer as $key => $dataanswer) {
if ($dataanswer != "") {
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
$answer = $oldanswer;
$answer->answer = $dataanswer;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!update_record("quiz_answers", $answer)) {
$result->error = "Could not update quiz answer! (id=$answer->id)";
return $result;
}
} else { // This is a completely new answer
unset($answer);
$answer->answer = $dataanswer;
$answer->question = $question->id;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!$answer->id = insert_record("quiz_answers", $answer)) {
$result->error = "Could not insert quiz answer!";
return $result;
}
}
$answers[] = $answer->id;
if ($question->fraction[$key] > $maxfraction) {
$maxfraction = $question->fraction[$key];
}
if ($options = get_record("quiz_numerical", "answer", $answer->id)) {
$options->min= $question->min[$key];
$options->max= $question->max[$key];
if (!update_record("quiz_numerical", $options)) {
$result->error = "Could not update quiz numerical options! (id=$options->id)";
return $result;
}
} else { // completely new answer
unset($options);
$options->question = $question->id;
$options->answer = $answer->id;
$options->min = $question->min[$key];
$options->max = $question->max[$key];
if (!insert_record("quiz_numerical", $options)) {
$result->error = "Could not insert quiz numerical options!";
return $result;
}
}
}
}
/// Perform sanity checks on fractional grades
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
return $result;
} else {
return true;
}
}
function grade_response($question, $nameprefix, $addedanswercondition='') {
$result->answers = array();
if (isset($question->response[$nameprefix])) {
$response = trim(stripslashes($question->response[$nameprefix]));
if (!is_numeric($response) and is_numeric(
$tmp = str_replace(',', '.', $response))) {
/// I haven't ever needed to make a workaround like this
/// before, I have no idea why I need to do it now...
$response = $tmp;
}
} else {
$response = NULL;
}
$answers = $this->get_answers($question, $addedanswercondition);
foreach ($answers as $answer) {
/// Check if response matches answer...
if ('' != $response and empty($result->answers)
|| $answer->fraction
> $result->answers[$nameprefix]->fraction
and strtolower($response) == strtolower($answer->answer)
|| '' != trim($answer->min)
&& ((float)$response >= (float)$answer->min)
&& ((float)$response <= (float)$answer->max)) {
$result->answers[$nameprefix] = $answer;
}
}
$result->grade = isset($result->answers[$nameprefix])
? $result->answers[$nameprefix]->fraction
: 0.0;
$result->correctanswers = quiz_extract_correctanswers($answers,
$nameprefix);
/////////////////////////////////////////////////
// For numerical answer we have the policy to
// set feedback for any response, even it the
// response does not entitles the student to it.
/////////////////////////////////////////////////
if ('' !== $response and empty($result->answers)
|| empty($result->answers[$nameprefix]->feedback)) {
// Look for just any feedback:
foreach ($result->correctanswers as $correctanswer) {
if ($correctanswer->feedback) {
$result->answers[$nameprefix]->feedback =
$correctanswer->feedback;
if (empty($result->answers[$nameprefix]->id)) {
// Better fake an answer as well:
$result->answers[$nameprefix]->id = 0;
$result->answers[$nameprefix]->answer = $response;
$result->answers[$nameprefix]->fraction = 0.0;
$result->answers[$nameprefix]->question = $question->id;
}
break;
}
}
}
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[NUMERICAL]= new quiz_numerical_qtype();
?>

View File

@ -0,0 +1,6 @@
<?PHP // $Id$
print_heading_with_help(get_string("editingrandom", "quiz"), "random", "quiz");
require("random.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

View File

@ -0,0 +1,206 @@
<?PHP // $Id$
//////////////
/// RANDOM ///
//////////////
/// QUESTION TYPE CLASS //////////////////
class quiz_random_qtype extends quiz_default_questiontype {
var $possiblerandomqtypes = array(SHORTANSWER,
NUMERICAL,
MULTICHOICE,
MATCH,
// RANDOMSAMATCH,// Can cause unexpected outcomes
TRUEFALSE,
MULTIANSWER);
// Carries questions available as randoms sorted by category
// This array is used when needed only
var $catrandoms = array();
function name() {
return 'random';
}
function save_question_options($question) {
/// No options to be saved for this question type:
return true;
}
function wrapped_questions($question) {
global $QUIZ_QTYPES;
foreach ($question->response as $key => $response) {
if (ereg('[^0-9][0-9]+random$', $key)) {
$randomquestion = get_record('quiz_questions',
'id', $response);
$randomquestion->response = $question->response;
unset($randomquestion->response[$key]);
if ($subwrapped = $QUIZ_QTYPES[$randomquestion->qtype]
->wrapped_questions($randomquestion)) {
return "$response,$subwrapped";
} else {
return $response;
}
}
}
return false;
}
function convert_to_response_answer_field($questionresponse) {
/// THIS IS PART OF A WORKAROUND AS THIS IS THE ONLY
/// CASE WHERE IT IS NEEDED TO STORE TWO RESPONSE RECORDS...
global $QUIZ_QTYPES;
foreach ($questionresponse as $key => $response) {
if (ereg('[^0-9][0-9]+random$', $key)) {
unset($questionresponse[$key]);
$randomquestion = get_record('quiz_questions',
'id', $response);
return "random$response-"
.$QUIZ_QTYPES[$randomquestion->qtype]
->convert_to_response_answer_field($questionresponse);
}
}
return '';
}
function create_response($question, $nameprefix, $questionsinuse) {
// It's for question types like RANDOMSAMATCH and RANDOM that
// the true power of the pattern with this function comes to the surface.
// This implementation will stand even after a possible exclusion of
// the funtions extract_response and convert_to_response_answer_field
global $CFG;
if (!isset($this->catrandoms[$question->category])) {
//Need to fetch random questions from category $question->category"
$possiblerandomqtypes = "'"
. implode("','", $this->possiblerandomqtypes) . "'";
$this->catrandoms[$question->category] = get_records_sql
("SELECT * FROM {$CFG->prefix}quiz_questions
WHERE category = '$question->category'
AND id NOT IN ($questionsinuse)
AND qtype IN ($possiblerandomqtypes)");
shuffle($this->catrandoms[$question->category]);
}
while ($randomquestion =
array_pop($this->catrandoms[$question->category])) {
if (!ereg("(^|,)$randomquestion->id(,|$)", $questionsinuse)) {
/// $randomquestion is not in use and will therefore be used
/// as the randomquestion here...
global $QUIZ_QTYPES;
$response = $QUIZ_QTYPES[$randomquestion->qtype]
->create_response($randomquestion,
quiz_qtype_nameprefix($randomquestion, $nameprefix),
"$questionsinuse,$randomquestion->id");
$response[$nameprefix] = $randomquestion->id;
return $response;
}
}
notify(get_string('toomanyrandom', 'quiz', $question->category));
return array();
}
function extract_response($rawresponse, $nameprefix) {
global $QUIZ_QTYPES;
if ($randomquestion = get_record('quiz_questions',
'id', $rawresponse->answer)) {
if ($randomresponse = get_record
('quiz_responses', 'question', $rawresponse->answer,
'attempt', $rawresponse->attempt)) {
/// The prefered case:
// The response field for the random question was found
// the response array can be extracted:
$response = $QUIZ_QTYPES[$randomquestion->qtype]
->extract_response($randomresponse,
quiz_qtype_nameprefix($randomquestion, $nameprefix));
} else {
notify("Error: Cannot find response to random question $randomquestion->id");
/// Instead: workaround by creating a new response:
$response = $QUIZ_QTYPES[$randomquestion->qtype]
->create_response($randomquestion,
quiz_qtype_nameprefix($randomquestion, $nameprefix),
"$rawresponse->question,$rawresponse->answer");
// (That last argument is instead of $questionsinuse.
// It is not correct but it would be very messy to
// determine the correct value, while very few
// question types actually use it and they who do have
// good chances to execute properly anyway.)
}
$response[$nameprefix] = $randomquestion->id;
return $response;
} else {
notify("Error: Unable to find random question $rawresponse->question");
/// No new random question is picked as this is probably
/// not what the moodle user has in mind anyway
return array();
}
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
global $QUIZ_QTYPES;
// Get the wrapped question...
if ($actualquestion = $this->get_wrapped_question($question,
$nameprefix)) {
echo '<input type="hidden" name="' . $nameprefix
. '" value="' . $actualquestion->id . '"/>';
return $QUIZ_QTYPES[$actualquestion->qtype]
->print_question_formulation_and_controls($actualquestion,
$quiz, $readonly, $answers, $correctanswers,
quiz_qtype_nameprefix($actualquestion, $nameprefix));
} else {
echo '<p>' . get_string('random', 'quiz') . '</p>';
}
}
function get_wrapped_question($question, $nameprefix) {
if (!empty($question->response[$nameprefix])
and $actualquestion = get_record('quiz_questions',
'id', $question->response[$nameprefix],
// The category check is a security check
'category', $question->category)) {
$actualquestion->response = $question->response;
unset($actualquestion->response[$nameprefix]);
$actualquestion->maxgrade = $question->maxgrade;
return $actualquestion;
} else {
return false;
}
}
function grade_response($question, $nameprefix) {
global $QUIZ_QTYPES;
// Get the wrapped question...
if ($actualquestion = $this->get_wrapped_question($question,
$nameprefix)) {
return $QUIZ_QTYPES[$actualquestion->qtype]->grade_response(
$actualquestion,
quiz_qtype_nameprefix($actualquestion, $nameprefix));
} else {
$result->grade = 0.0;
$result->answers = array();
$result->correctanswers = array();
return $result;
}
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[RANDOM]= new quiz_random_qtype();
?>

View File

@ -0,0 +1,30 @@
<center>
<form name="theform" method="post" action="question.php">
<table cellpadding=5>
<tr valign=top>
<td align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<td>
<?php quiz_category_select_menu($course->id, true, true); ?>
</td>
</tr>
<tr valign=top>
<td align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<td>
<?php if (empty($question->name)) {
$question->name = get_string("random", "quiz");
} ?>
<input type="text" name="name" size=40 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</td>
</tr>
</table>
<input type="hidden" name=questiontext value="---">
<input type="hidden" name=id value="<?php p($question->id) ?>">
<input type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<input type="submit" value="<?php print_string("savechanges") ?>">
</form>
</center>

View File

@ -0,0 +1,11 @@
<?PHP // $Id$
if (!empty($question->id)) {
$options = get_record("quiz_randomsamatch", "question", $question->id);
} else {
$options->choose = "";
}
$numberavailable = count_records("quiz_questions", "category", $category->id, "qtype", SHORTANSWER);
print_heading_with_help(get_string("editingrandomsamatch", "quiz"), "randomsamatch", "quiz");
require("randomsamatch.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

View File

@ -0,0 +1,231 @@
<?PHP // $Id$
/////////////////////
/// RANDOMSAMATCH ///
/////////////////////
/// The use of this question type together with the
/// question type RANDOM within the same quiz can cause
/// a shortanswer question to appear in a RANDOM question
/// as well as one of the matcher questions in a question of this type
/// QUESTION TYPE CLASS //////////////////
class quiz_randomsamatch_qtype extends quiz_match_qtype {
/// Extends MATCH as there are quite a few simularities...
// $catrandoms carries question ids for shortanswer questions
// available as random questios.
// They are sorted by category.
var $catrandoms = array();
function name() {
return 'randomsamatch';
}
function save_question_options($question) {
$options->question = $question->id;
$options->choose = $question->choose;
if ($existing = get_record("quiz_randomsamatch",
"question", $options->question)) {
$options->id = $existing->id;
if (!update_record("quiz_randomsamatch", $options)) {
$result->error = "Could not update quiz randomsamatch options!";
return $result;
}
} else {
if (!insert_record("quiz_randomsamatch", $options)) {
$result->error = "Could not insert quiz randomsamatch options!";
return $result;
}
}
return true;
}
function wrapped_questions($question) {
if (empty($question->response)) {
return false;
} else {
$wrapped = '';
$delimiter = '';
foreach ($question->response as $rkey => $response) {
$wrapped .= $delimiter.$this->extract_response_id($rkey);
$delimiter = ',';
}
return $wrapped;
}
}
function create_response($question, $nameprefix, $questionsinuse) {
// It's for question types like RANDOMSAMATCH and RANDOM that
// the true power of the pattern with this function comes to the surface.
// This implementation will stand even after a possible exclusion of
// the funtions extract_response and convert_to_response_answer_field
if (!isset($this->catrandoms[$question->category])) {
/// Need to fetch the shortanswer question ids for the category:
$saquestions = get_records_select('quiz_questions',
" category='$question->category'
AND qtype='".SHORTANSWER."'
AND id NOT IN ($questionsinuse) ");
$this->catrandoms[$question->category] = array_keys($saquestions);
shuffle($this->catrandoms[$question->category]);
}
/// Access question options to find out how many short-answer
/// questions we are supposed to pick...
if ($options = get_record('quiz_randomsamatch',
'question', $question->id)) {
$questionstopick = $options->choose;
} else {
notify("Error: Missing question options! - Try to pick two shortanswer questions anyway");
$questionstopick = 2;
}
/// Pick the short-answer question ids and create the $response array
$response = array();
while ($questionstopick) {
$said = array_pop($this->catrandoms[$question->category]);
if (!ereg("(^|,)$said(,|$)", $questionsinuse)) {
$response[$nameprefix.$said] = '0';
--$questionstopick;
}
}
if ($questionstopick) {
notify("Error: could not get enough Short-Answer questions!");
$count = count($response);
$wanted = $count + $questionstopick;
notify("Got $count Short-Answer questions, but wanted $wanted.");
}
return $response;
}
function extract_response($rawresponse, $nameprefix) {
/// Simple implementation that does not check with the database
/// and thus - does not bother to check whether there has been
/// any changes to the question options.
$response = array();
$rawitems = explode(',', $rawresponse->answer);
foreach ($rawitems as $rawitem) {
$splits = explode('-', $rawitem, 2);
$response[$nameprefix.$splits[0]] = $splits[1];
}
return $response;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
// Print question formulation
echo format_text($question->questiontext,
$question->questiontextformat, NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
// Summarize shortanswer questions answer alternatives:
if (empty($correctanswers)) {
// Get them using the grade_response method
$tempresult = $this->grade_response($question, $nameprefix);
$saanswers = $tempresult->correctanswers;
} else {
$saanswers = $correctanswers;
}
foreach ($saanswers as $key => $saanswer) {
unset($saanswers[$key]); // Unsets the nameprefix occurence
$saanswers[$saanswer->id] = trim($saanswer->answer);
}
$saanswers = draw_rand_array($saanswers, count($saanswers));
// Print the shortanswer questions and input controls:
echo '<table border="0" cellpadding="10">';
foreach ($question->response as $inputname => $response) {
if (!($saquestion = get_record('quiz_questions', 'id',
quiz_extract_posted_id($inputname, $nameprefix)))) {
notify("Error: cannot find shortanswer question for $inputname ");
continue;
}
echo '<tr><td align="left" valign="top">';
echo $saquestion->questiontext;
echo '</td>';
echo '<td align="right" valign="top">';
if (!empty($correctanswers)
&& $correctanswers[$inputname]->id == $response) {
echo '<span="highlight">';
choose_from_menu($saanswers, $inputname, $response);
echo '</span><br />';
} else {
choose_from_menu($saanswers, $inputname, $response);
if ($readonly && $quiz->correctanswers
&& isset($correctanswer[$inputname])) {
quiz_print_correctanswer($correctanswer[$inputname]->answer);
}
}
if ($quiz->feedback && isset($answers[$inputname])
&& $answers[$inputname]->feedback) {
quiz_print_comment($answers[$inputname]->feedback);
}
echo '</td></tr>';
}
echo '</table>';
}
function grade_response($question, $nameprefix) {
global $QUIZ_QTYPES;
$result->answers = array();
$result->correctanswers = array();
$result->grade = 0.0;
foreach ($question->response as $inputname => $subresponse) {
if ($subquestion = get_record('quiz_questions',
'id', quiz_extract_posted_id($inputname, $nameprefix),
// These two query conditions are security checks that prevents cheating...
'qtype', SHORTANSWER,
'category', $question->category)) {
if ($subresponse = get_record('quiz_answers',
'id', $subresponse)) {
$subquestion->response[$inputname] = $subresponse->answer;
} else {
$subquestion->response[$inputname] = '';
}
// Use the shortanswer framework to for grading...
$subresult = $QUIZ_QTYPES[SHORTANSWER]
->grade_response($subquestion, $inputname);
// Summarize shortanswer results
if (isset($subresult->answers[$inputname])) {
$result->answers[$inputname] =
$subresult->answers[$inputname];
$result->grade += $result->answers[$inputname]->fraction;
if ($result->answers[$inputname]->fraction >= 1.0) {
$result->correctanswers[$inputname] =
$result->answers[$inputname];
continue;
}
}
// Pick the first correctanswer:
foreach ($subresult->correctanswers as $correct) {
$result->correctanswers[$inputname] = $correct;
break;
}
}
}
if ($result->grade) {
$result->grade /= count($question->response);
}
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[RANDOMSAMATCH]= new quiz_randomsamatch_qtype();
?>

View File

@ -0,0 +1,98 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?PHP echo $categories[$question->category]; ?>
<input type="hidden" name="category" value="<?PHP echo "$question->category"; ?>">
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<?PHP
if (empty($question->name)) {
$question->name = get_string("randomsamatch", "quiz");
}
?>
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align=right><p><b><?php print_string("introduction", "quiz") ?>:</b></p></td>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
if (empty($question->questiontext)) {
$question->questiontext = get_string("randomsamatchintro", "quiz");
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!$question->questiontextformat) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("randomsamatchnumber", "quiz") ?>:</B></P></TD>
<TD>
<?php
if ($numberavailable < 2) {
echo "ERROR";
$maxrandom=2;
} else if ($numberavailable < 6) {
$maxrandom = $numberavailable;
} else {
$maxrandom = QUIZ_MAX_NUMBER_ANSWERS;
}
for ($i=2;$i<=$maxrandom;$i++) {
$menu[$i] = $i;
}
choose_from_menu($menu, "choose", "$options->choose", "");
unset($menu);
?>
</TD>
</TR>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,23 @@
<?PHP // $Id$
if (!empty($question->id)) {
$options = get_record("quiz_shortanswer", "question", $question->id);
} else {
$options->usecase = 0;
}
if (!empty($options->answers)) {
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
}
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
$answers[] = ""; // Make answer slots, default as blank
}
if (!empty($answersraw)) {
$i=0;
foreach ($answersraw as $answer) {
$answers[$i] = $answer; // insert answers into slots
$i++;
}
}
print_heading_with_help(get_string("editingshortanswer", "quiz"), "shortanswer", "quiz");
require("shortanswer.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 B

View File

@ -0,0 +1,198 @@
<?PHP // $Id$
///////////////////
/// SHORTANSWER ///
///////////////////
/// QUESTION TYPE CLASS //////////////////
///
/// This class contains some special features in order to make the
/// question type embeddable within a multianswer (cloze) question
///
class quiz_shortanswer_qtype extends quiz_default_questiontype {
function get_answers($question, $addedcondition='') {
// The added condition is one addition that has been added
// to the behaviour of this question type in order to make
// it embeddable within a multianswer (embedded cloze) question
global $CFG;
// There can be multiple answers
return get_records_sql("SELECT a.*, sa.usecase
FROM {$CFG->prefix}quiz_shortanswer sa,
{$CFG->prefix}quiz_answers a
WHERE sa.question = '$question->id'
AND sa.question = a.question "
. $addedcondition);
}
function name() {
return 'shortanswer';
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
$oldanswers = array();
}
$answers = array();
$maxfraction = -1;
// Insert all the new answers
foreach ($question->answer as $key => $dataanswer) {
if ($dataanswer != "") {
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
$answer = $oldanswer;
$answer->answer = trim($dataanswer);
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!update_record("quiz_answers", $answer)) {
$result->error = "Could not update quiz answer! (id=$answer->id)";
return $result;
}
} else { // This is a completely new answer
unset($answer);
$answer->answer = trim($dataanswer);
$answer->question = $question->id;
$answer->fraction = $question->fraction[$key];
$answer->feedback = $question->feedback[$key];
if (!$answer->id = insert_record("quiz_answers", $answer)) {
$result->error = "Could not insert quiz answer!";
return $result;
}
}
$answers[] = $answer->id;
if ($question->fraction[$key] > $maxfraction) {
$maxfraction = $question->fraction[$key];
}
}
}
if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
$options->answers = implode(",",$answers);
$options->usecase = $question->usecase;
if (!update_record("quiz_shortanswer", $options)) {
$result->error = "Could not update quiz shortanswer options! (id=$options->id)";
return $result;
}
} else {
unset($options);
$options->question = $question->id;
$options->answers = implode(",",$answers);
$options->usecase = $question->usecase;
if (!insert_record("quiz_shortanswer", $options)) {
$result->error = "Could not insert quiz shortanswer options!";
return $result;
}
}
/// Perform sanity checks on fractional grades
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
return $result;
} else {
return true;
}
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
/// This implementation is also used by question type NUMERICAL
/// Print question text and media
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
/// Print input controls
$stranswer = get_string("answer", "quiz");
if (isset($question->response[$nameprefix])) {
$value = ' value="'.$question->response[$nameprefix].'" ';
} else {
$value = ' value="" ';
}
$inputname = ' name="'.$nameprefix.'" ';
echo "<p align=\"right\">$stranswer: <input type=\"text\" $readonly $inputname size=\"80\" $value /></p>";
if ($quiz->feedback && isset($answers[$nameprefix])
&& $feedback = $answers[$nameprefix]->feedback) {
quiz_print_comment("<p align=\"right\">$feedback</p>");
}
if ($readonly && $quiz->correctanswers) {
$delimiter = '';
$correct = '';
foreach ($correctanswers as $correctanswer) {
$correct .= $delimiter.$correctanswer->answer;
$delimiter = ', ';
}
quiz_print_correctanswer($correct);
}
}
function grade_response($question, $nameprefix, $addedanswercondition='') {
if (isset($question->response[$nameprefix])) {
$response0 = trim(stripslashes($question->response[$nameprefix]));
} else {
$response0 = '';
}
$answers = $this->get_answers($question, $addedanswercondition);
/// Determine ->answers[]
$result->answers = array();
if ('' !== $response0) {
foreach ($answers as $answer) {
$answer->answer = trim($answer->answer); // Just in case
if (empty($result->answers) || $answer->fraction
> $result->answers[$nameprefix]->fraction) {
if (!$answer->usecase) { // Don't compare case
$response0 = strtolower($response0);
$answer0 = strtolower($answer->answer);
} else {
$answer0 = $answer->answer;
}
if (strpos(' '.$answer0, '*')) {
$answer0 = str_replace('\*','@@@@@@',$answer0);
$answer0 = str_replace('*','.*',$answer0);
$answer0 = str_replace('@@@@@@', '\*',$answer0);
$answer0 = str_replace('+', '\+',$answer0);
if (ereg('^'.$answer0.'$', $response0)) {
$result->answers[$nameprefix] = $answer;
}
} else if ($answer0 == $response0) {
$result->answers[$nameprefix] = $answer;
}
}
}
}
$result->grade = isset($result->answers[$nameprefix])
? $result->answers[$nameprefix]->fraction
: 0.0;
$result->correctanswers = quiz_extract_correctanswers($answers,
$nameprefix);
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[SHORTANSWER]= new quiz_shortanswer_qtype();
?>

View File

@ -0,0 +1,126 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php quiz_category_select_menu($course->id, true, true); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=50 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!isset($question->questiontextformat)) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("casesensitive", "quiz") ?>:</B></P></TD>
<TD>
<?php
unset($menu);
$menu[0] = get_string("caseno", "quiz");
$menu[1] = get_string("caseyes", "quiz");
choose_from_menu($menu, "usecase", "$options->usecase", "");
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("correctanswers", "quiz") ?></B>:</P></TD>
<TD>
<P><?php print_string("filloutoneanswer", "quiz") ?></P>
</TD>
<?PHP
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
?>
<TR valign=top>
<TD align=right><P><B><?php echo get_string("answer", "quiz")." $i"; ?>:</B></P></TD>
<TD>
<INPUT type="text" name="answer[]" size=50 value="<?php p($answers[$i-1]->answer) ?>">&nbsp;&nbsp;
<?php print_string("grade");
echo ":&nbsp;";
choose_from_menu($gradeoptions, "fraction[]", $answers[$i-1]->fraction,""); ?>
<BR>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("feedback", "quiz") ?>:</B></P></TD>
<TD>
<textarea name="feedback[]" rows=2 cols=50 wrap="virtual"><?php p($answers[$i-1]->feedback) ?></textarea>
</TD>
</TR>
<TR valign=top>
<TD colspan=2>&nbsp;</TD>
</TR>
<?PHP
}
?>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -0,0 +1,28 @@
<?PHP // $Id$
if (!empty($question->id)) {
$options = get_record("quiz_truefalse", "question", "$question->id");
}
if (!empty($options->trueanswer)) {
$true = get_record("quiz_answers", "id", $options->trueanswer);
} else {
$true->fraction = 1;
$true->feedback = "";
}
if (!empty($options->falseanswer)) {
$false = get_record("quiz_answers", "id", "$options->falseanswer");
} else {
$false->fraction = 0;
$false->feedback = "";
}
if ($true->fraction > $false->fraction) {
$question->answer = 1;
} else {
$question->answer = 0;
}
print_heading_with_help(get_string("editingtruefalse", "quiz"), "truefalse", "quiz");
require("truefalse.html");
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

View File

@ -0,0 +1,179 @@
<?PHP // $Id$
/////////////////
/// TRUEFALSE ///
/////////////////
/// QUESTION TYPE CLASS //////////////////
class quiz_truefalse_qtype extends quiz_default_questiontype {
function name() {
return 'truefalse';
}
function save_question_options($question) {
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
$oldanswers = array();
}
if ($true = array_shift($oldanswers)) { // Existing answer, so reuse it
$true->answer = get_string("true", "quiz");
$true->fraction = $question->answer;
$true->feedback = $question->feedbacktrue;
if (!update_record("quiz_answers", $true)) {
$result->error = "Could not update quiz answer \"true\")!";
return $result;
}
} else {
unset($true);
$true->answer = get_string("true", "quiz");
$true->question = $question->id;
$true->fraction = $question->answer;
$true->feedback = $question->feedbacktrue;
if (!$true->id = insert_record("quiz_answers", $true)) {
$result->error = "Could not insert quiz answer \"true\")!";
return $result;
}
}
if ($false = array_shift($oldanswers)) { // Existing answer, so reuse it
$false->answer = get_string("false", "quiz");
$false->fraction = 1 - (int)$question->answer;
$false->feedback = $question->feedbackfalse;
if (!update_record("quiz_answers", $false)) {
$result->error = "Could not insert quiz answer \"false\")!";
return $result;
}
} else {
unset($false);
$false->answer = get_string("false", "quiz");
$false->question = $question->id;
$false->fraction = 1 - (int)$question->answer;
$false->feedback = $question->feedbackfalse;
if (!$false->id = insert_record("quiz_answers", $false)) {
$result->error = "Could not insert quiz answer \"false\")!";
return $result;
}
}
if ($options = get_record("quiz_truefalse", "question", $question->id)) {
// No need to do anything, since the answer IDs won't have changed
// But we'll do it anyway, just for robustness
$options->trueanswer = $true->id;
$options->falseanswer = $false->id;
if (!update_record("quiz_truefalse", $options)) {
$result->error = "Could not update quiz truefalse options! (id=$options->id)";
return $result;
}
} else {
unset($options);
$options->question = $question->id;
$options->trueanswer = $true->id;
$options->falseanswer = $false->id;
if (!insert_record("quiz_truefalse", $options)) {
$result->error = "Could not insert quiz truefalse options!";
return $result;
}
}
return true;
}
function print_question_formulation_and_controls($question,
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
// Get additional information from database
if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
notify("Error: Missing question options!");
}
if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) {
notify("Error: Missing question answers!");
}
if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) {
notify("Error: Missing question answers!");
}
// Print question formulation
echo format_text($question->questiontext,
$question->questiontextformat,
NULL, $quiz->course);
quiz_print_possible_question_image($quiz->id, $question);
// Print input controls
$stranswer = get_string("answer", "quiz");
if (!$true->answer) {
$true->answer = get_string("true", "quiz");
}
if (!$false->answer) {
$false->answer = get_string("false", "quiz");
}
$truechecked = "";
$falsechecked = "";
if (!isset($question->response[$nameprefix])) {
$question->response[$nameprefix] = '';
}
if ($true->id == $question->response[$nameprefix]) {
$truechecked = 'checked="checked"';
} else if ($false->id == $question->response[$nameprefix]) {
$falsechecked = 'checked="checked"';
}
if ($readonly) {
$readonly = ' readonly="readonly" disabled="disabled" ';
}
$truecorrect = "";
$falsecorrect = "";
if ($readonly && $quiz->correctanswers) {
if (!empty($correctanswers[$nameprefix.$true->id])) {
$truecorrect = 'class="highlight"';
}
if (!empty($correctanswers[$nameprefix.$false->id])) {
$falsecorrect = 'class="highlight"';
}
}
$inputname = ' name="'.$nameprefix.'" ';
echo "<table align=\"right\" cellpadding=\"5\"><tr><td align=\"right\">$stranswer:&nbsp;&nbsp;";
echo "<td $truecorrect>";
echo "<input $truechecked type=\"radio\" $readonly $inputname value=\"$true->id\" />$true->answer";
echo "</td><td $falsecorrect>";
echo "<input $falsechecked type=\"radio\" $readonly $inputname value=\"$false->id\" />$false->answer";
echo "</td></tr></table><br clear=\"all\">";// changed from CLEAR=ALL jm
if ($quiz->feedback && isset($answers[$nameprefix])
&& $feedback = $answers[$nameprefix]->feedback) {
quiz_print_comment(
"<p align=\"right\">$feedback</p>");
}
}
function grade_response($question, $nameprefix) {
$answers = get_records("quiz_answers", "question", $question->id);
if (isset($question->response[$nameprefix])
&& isset($answers[$question->response[$nameprefix]])) {
$result->answers = array($nameprefix
=> $answers[$question->response[$nameprefix]]);
$result->grade = $result->answers[$nameprefix]->fraction;
} else {
$result->answers = array();
$result->grade = 0.0;
}
$result->correctanswers = quiz_extract_correctanswers($answers,
$nameprefix);
return $result;
}
}
//// END OF CLASS ////
//////////////////////////////////////////////////////////////////////////
//// INITIATION - Without this line the question type is not in use... ///
//////////////////////////////////////////////////////////////////////////
$QUIZ_QTYPES[TRUEFALSE]= new quiz_truefalse_qtype();
?>

View File

@ -0,0 +1,103 @@
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?php
quiz_category_select_menu($course->id, true, true);
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<INPUT type="text" name="name" size=50 value="<?php p($question->name) ?>">
<?php if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<tr valign=top>
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
<br />
<br />
<br />
<p><font SIZE="1">
<?php
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
} else {
helpbutton("text", get_string("helptext"), "moodle", true, true);
}
?>
</font></p>
</td>
<td>
<?php if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<br />";
}
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) { /// Trying this out for a while
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
} else {
echo "<div align=right>";
print_string("formattexttype");
echo ":&nbsp;";
if (!isset($question->questiontextformat)) {
$question->questiontextformat = FORMAT_MOODLE;
}
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
helpbutton("textformat", get_string("helpformatting"));
echo "</div>";
}
?>
</td>
</tr>
<TR valign=top>
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
<TD>
<?php if (empty($images)) {
print_string("noimagesyet");
} else {
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("correctanswer", "quiz") ?>:</B></P></TD>
<TD>
<?php $menu[0] = get_string("false", "quiz");
$menu[1] = get_string("true", "quiz");
choose_from_menu($menu, "answer", "$question->answer", ""); ?>
<BR>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("feedback", "quiz") ?> (<?php print_string("true", "quiz") ?>):</B></P></TD>
<TD>
<textarea name="feedbacktrue" rows=2 cols=50 wrap="virtual"><?php p($true->feedback) ?></textarea>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><?php print_string("feedback", "quiz") ?> (<?php print_string("false", "quiz") ?>):</B></P></TD>
<TD>
<textarea name="feedbackfalse" rows=2 cols=50 wrap="virtual"><?php p($false->feedback) ?></textarea>
</TD>
</TR>
</TABLE>
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
</CENTER>
</FORM>
<?php
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>

View File

@ -29,12 +29,11 @@ class quiz_report extends quiz_default_report {
$count->attempt++;
if (! $questions = quiz_get_attempt_responses($attempt, $quiz)) {
if (! $questions = quiz_get_attempt_questions($quiz, $attempt)) {
error("Could not reconstruct quiz results for attempt $attempt->id!");
}
quiz_remove_unwanted_questions($questions, $quiz);
if (!$result = quiz_grade_attempt_results($quiz, $questions)) {
if (!$result = quiz_grade_responses($quiz, $questions)) {
error("Could not re-grade this quiz attempt!");
}

View File

@ -2,6 +2,445 @@
/// Overview report just displays a big table of all the attempts
////////////////////////////////////////////////////////////////
/// With the refactoring of the quiz module in July-2004, some
/// of the functions in lib.php were moved here instead as they
/// are no longer in use by the other quiz components.
/// These functions are quiz_get_attempt_responses,
/// quiz_grade_attempt, quiz_grade_attempt_results,
/// quiz_remove_unwanted_questions and quiz_get_answers.
/// They were all properly renamed by exchanging quiz_
/// with quiz_report_simplestat_
//////////////////////////////////////////////////////////
function quiz_report_simplestat_get_attempt_responses($attempt) {
// Given an attempt object, this function gets all the
// stored responses and returns them in a format suitable
// for regrading using quiz_grade_attempt_results()
global $CFG;
if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext,
q.defaultgrade, q.image, r.answer
FROM {$CFG->prefix}quiz_responses r,
{$CFG->prefix}quiz_questions q
WHERE r.attempt = '$attempt->id'
AND q.id = r.question")) {
notify("Could not find any responses for that attempt!");
return false;
}
foreach ($responses as $key => $response) {
if ($response->qtype == RANDOM) {
$responses[$key]->random = $response->answer;
$responses[$response->answer]->delete = true;
$realanswer = $responses[$response->answer]->answer;
if (is_array($realanswer)) {
$responses[$key]->answer = $realanswer;
} else {
$responses[$key]->answer = explode(",", $realanswer);
}
} else if ($response->qtype == NUMERICAL or $response->qtype == SHORTANSWER) {
$responses[$key]->answer = array($response->answer);
} else {
$responses[$key]->answer = explode(",",$response->answer);
}
}
foreach ($responses as $key => $response) {
if (!empty($response->delete)) {
unset($responses[$key]);
}
}
return $responses;
}
function quiz_report_simplestat_grade_attempt_question_result($question,
$answers,
$gradecanbenegative= false)
{
$grade = 0; // default
$correct = array();
$feedback = array();
$response = array();
switch ($question->qtype) {
case SHORTANSWER:
if ($question->answer) {
$question->answer = trim(stripslashes($question->answer[0]));
} else {
$question->answer = "";
}
$response[0] = $question->answer;
$feedback[0] = ''; // Default
foreach ($answers as $answer) { // There might be multiple right answers
$answer->answer = trim($answer->answer); // Just in case
if ($answer->fraction >= 1.0) {
$correct[] = $answer->answer;
}
if (!$answer->usecase) { // Don't compare case
$answer->answer = strtolower($answer->answer);
$question->answer = strtolower($question->answer);
}
$potentialgrade = (float)$answer->fraction * $question->grade;
if ($potentialgrade >= $grade and (strpos(' '.$answer->answer, '*'))) {
$answer->answer = str_replace('\*','@@@@@@',$answer->answer);
$answer->answer = str_replace('*','.*',$answer->answer);
$answer->answer = str_replace('@@@@@@', '\*',$answer->answer);
$answer->answer = str_replace('+', '\+',$answer->answer);
if (eregi('^'.$answer->answer.'$', $question->answer)) {
$feedback[0] = $answer->feedback;
$grade = $potentialgrade;
}
} else if ($answer->answer == $question->answer) {
$feedback[0] = $answer->feedback;
$grade = $potentialgrade;
}
}
break;
case NUMERICAL:
if ($question->answer) {
$question->answer = trim(stripslashes($question->answer[0]));
} else {
$question->answer = "";
}
$response[0] = $question->answer;
$bestshortanswer = 0;
foreach ($answers as $answer) { // There might be multiple right answers
if ($answer->fraction > $bestshortanswer) {
$correct[$answer->id] = $answer->answer;
$bestshortanswer = $answer->fraction;
$feedback[0] = $answer->feedback; // Show feedback for best answer
}
if ('' != $question->answer // Must not be mixed up with zero!
&& (float)$answer->fraction > (float)$grade // Do we need to bother?
and // and has lower procedence than && and ||.
strtolower($question->answer) == strtolower($answer->answer)
|| '' != trim($answer->min)
&& ((float)$question->answer >= (float)$answer->min)
&& ((float)$question->answer <= (float)$answer->max))
{
//$feedback[0] = $answer->feedback; No feedback was shown for wrong answers
$grade = (float)$answer->fraction;
}
}
$grade *= $question->grade; // Normalize to correct weight
break;
case TRUEFALSE:
if ($question->answer) {
$question->answer = $question->answer[0];
} else {
$question->answer = NULL;
}
foreach($answers as $answer) { // There should be two answers (true and false)
$feedback[$answer->id] = $answer->feedback;
if ($answer->fraction > 0) {
$correct[$answer->id] = true;
}
if ($question->answer == $answer->id) {
$grade = (float)$answer->fraction * $question->grade;
$response[$answer->id] = true;
}
}
break;
case MULTICHOICE:
foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
$feedback[$answer->id] = $answer->feedback;
if ($answer->fraction > 0) {
$correct[$answer->id] = true;
}
if (!empty($question->answer)) {
foreach ($question->answer as $questionanswer) {
if ($questionanswer == $answer->id) {
$response[$answer->id] = true;
if ($answer->single) {
$grade = (float)$answer->fraction * $question->grade;
continue;
} else {
$grade += (float)$answer->fraction * $question->grade;
}
}
}
}
}
break;
case MATCH:
$matchcount = $totalcount = 0;
foreach ($question->answer as $questionanswer) { // Each answer is "subquestionid-answerid"
$totalcount++;
$qarr = explode('-', $questionanswer); // Extract subquestion/answer.
$subquestionid = $qarr[0];
$subanswerid = $qarr[1];
if ($subquestionid and $subanswerid and (($subquestionid == $subanswerid) or
($answers[$subquestionid]->answertext == $answers[$subanswerid]->answertext))) {
// Either the ids match exactly, or the answertexts match exactly
// (in case two subquestions had the same answer)
$matchcount++;
$correct[$subquestionid] = true;
} else {
$correct[$subquestionid] = false;
}
$response[$subquestionid] = $subanswerid;
}
$grade = $question->grade * $matchcount / $totalcount;
break;
case RANDOMSAMATCH:
$bestanswer = array();
foreach ($answers as $answer) { // Loop through them all looking for correct answers
if (empty($bestanswer[$answer->question])) {
$bestanswer[$answer->question] = 0;
$correct[$answer->question] = "";
}
if ($answer->fraction > $bestanswer[$answer->question]) {
$bestanswer[$answer->question] = $answer->fraction;
$correct[$answer->question] = $answer->answer;
}
}
$answerfraction = 1.0 / (float) count($question->answer);
foreach ($question->answer as $questionanswer) { // For each random answered question
$rqarr = explode('-', $questionanswer); // Extract question/answer.
$rquestion = $rqarr[0];
$ranswer = $rqarr[1];
$response[$rquestion] = $questionanswer;
if (isset($answers[$ranswer])) { // If the answer exists in the list
$answer = $answers[$ranswer];
$feedback[$rquestion] = $answer->feedback;
if ($answer->question == $rquestion) { // Check that this answer matches the question
$grade += (float)$answer->fraction * $question->grade * $answerfraction;
}
}
}
break;
case MULTIANSWER:
// Default setting that avoids a possible divide by zero:
$subquestion->grade = 1.0;
foreach ($question->answer as $questionanswer) {
// Resetting default values for subresult:
$subresult->grade = 0.0;
$subresult->correct = array();
$subresult->feedback = array();
// Resetting subquestion responses:
$subquestion->answer = array();
$qarr = explode('-', $questionanswer, 2);
$subquestion->answer[] = $qarr[1]; // Always single answer for subquestions
foreach ($answers as $multianswer) {
if ($multianswer->id == $qarr[0]) {
$subquestion->qtype = $multianswer->answertype;
$subquestion->grade = $multianswer->norm;
$subresult = quiz_report_simplestat_grade_attempt_question_result($subquestion, $multianswer->subanswers, true);
break;
}
}
// Summarize subquestion results:
$grade += $subresult->grade;
$feedback[] = $subresult->feedback[0];
$correct[] = $subresult->correct[0];
// Each response instance also contains the partial
// fraction grade for the response:
$response[] = $subresult->grade/$subquestion->grade
. '-' . $subquestion->answer[0];
}
// Normalize grade:
$grade *= $question->grade/($question->defaultgrade);
break;
case DESCRIPTION: // Descriptions are not graded.
break;
case RANDOM: // Returns a recursive call with the real question
$realquestion = get_record
('quiz_questions', 'id', $question->random);
$realquestion->answer = $question->answer;
$realquestion->grade = $question->grade;
return quiz_report_simplestat_grade_attempt_question_result($realquestion, $answers);
}
$result->grade =
$gradecanbenegative ? $grade // Grade can be negative
: max(0.0, $grade); // Grade must not be negative
$result->correct = $correct;
$result->feedback = $feedback;
$result->response = $response;
return $result;
}
function quiz_report_simplestat_remove_unwanted_questions(&$questions, $quiz) {
/// Given an array of questions, and a list of question IDs,
/// this function removes unwanted questions from the array
/// Used by review.php and attempt.php to counter changing quizzes
$quizquestions = array();
$quizids = explode(",", $quiz->questions);
foreach ($quizids as $quizid) {
$quizquestions[$quizid] = true;
}
foreach ($questions as $key => $question) {
if (!isset($quizquestions[$question->id])) {
unset($questions[$key]);
}
}
}
function quiz_report_simplestat_get_answers($question, $answerids=NULL) {
// Given a question, returns the correct answers for a given question
global $CFG;
if (empty($answerids)) {
$answeridconstraint = '';
} else {
$answeridconstraint = " AND a.id IN ($answerids) ";
}
switch ($question->qtype) {
case SHORTANSWER: // Could be multiple answers
return get_records_sql("SELECT a.*, sa.usecase
FROM {$CFG->prefix}quiz_shortanswer sa,
{$CFG->prefix}quiz_answers a
WHERE sa.question = '$question->id'
AND sa.question = a.question "
. $answeridconstraint);
case TRUEFALSE: // Should be always two answers
return get_records("quiz_answers", "question", $question->id);
case MULTICHOICE: // Should be multiple answers
return get_records_sql("SELECT a.*, mc.single
FROM {$CFG->prefix}quiz_multichoice mc,
{$CFG->prefix}quiz_answers a
WHERE mc.question = '$question->id'
AND mc.question = a.question "
. $answeridconstraint);
case MATCH:
return get_records("quiz_match_sub", "question", $question->id);
case RANDOMSAMATCH: // Could be any of many answers, return them all
return get_records_sql("SELECT a.*
FROM {$CFG->prefix}quiz_questions q,
{$CFG->prefix}quiz_answers a
WHERE q.category = '$question->category'
AND q.qtype = ".SHORTANSWER."
AND q.id = a.question ");
case NUMERICAL: // Logical support for multiple answers
return get_records_sql("SELECT a.*, n.min, n.max
FROM {$CFG->prefix}quiz_numerical n,
{$CFG->prefix}quiz_answers a
WHERE a.question = '$question->id'
AND n.answer = a.id "
. $answeridconstraint);
case DESCRIPTION:
return true; // there are no answers for description
case RANDOM:
return quiz_get_answers
(get_record('quiz_questions', 'id', $question->random));
case MULTIANSWER: // Includes subanswers
$answers = array();
$virtualquestion->id = $question->id;
if ($multianswers = get_records('quiz_multianswers', 'question', $question->id)) {
foreach ($multianswers as $multianswer) {
$virtualquestion->qtype = $multianswer->answertype;
// Recursive call for subanswers
$multianswer->subanswers = quiz_get_answers($virtualquestion, $multianswer->answers);
$answers[] = $multianswer;
}
}
return $answers;
default:
return false;
}
}
function quiz_report_simplestat_grade_attempt_results($quiz, $questions) {
/// Given a list of questions (including answers for each one)
/// this function does all the hard work of calculating the
/// grades for each question, as well as a total grade for
/// for the whole quiz. It returns everything in a structure
/// that looks like:
/// $result->sumgrades (sum of all grades for all questions)
/// $result->percentage (Percentage of grades that were correct)
/// $result->grade (final grade result for the whole quiz)
/// $result->grades[] (array of grades, indexed by question id)
/// $result->response[] (array of response arrays, indexed by question id)
/// $result->feedback[] (array of feedback arrays, indexed by question id)
/// $result->correct[] (array of feedback arrays, indexed by question id)
if (!$questions) {
error("No questions!");
}
if (!$grades = get_records_menu("quiz_question_grades", "quiz", $quiz->id, "", "question,grade")) {
error("No grades defined for these quiz questions!");
}
$result->sumgrades = 0;
foreach ($questions as $question) {
$question->grade = $grades[$question->id];
if (!$answers = quiz_report_simplestat_get_answers($question)) {
error("No answers defined for question id $question->id!");
}
$questionresult = quiz_report_simplestat_grade_attempt_question_result($question,
$answers);
// if time limit is enabled and exceeded, return zero grades
if($quiz->timelimit > 0) {
if(($quiz->timelimit + 60) <= $quiz->timesincestart) {
$questionresult->grade = 0;
}
}
$result->grades[$question->id] = round($questionresult->grade, 2);
$result->sumgrades += $questionresult->grade;
$result->feedback[$question->id] = $questionresult->feedback;
$result->response[$question->id] = $questionresult->response;
$result->correct[$question->id] = $questionresult->correct;
}
$fraction = (float)($result->sumgrades / $quiz->sumgrades);
$result->percentage = format_float($fraction * 100.0);
$result->grade = format_float($fraction * $quiz->grade);
$result->sumgrades = round($result->sumgrades, 2);
return $result;
}
class quiz_report extends quiz_default_report {
function display($quiz, $cm, $course) { /// This function just displays the report
@ -48,12 +487,12 @@ class quiz_report extends quiz_default_report {
if (!$bestattempt = quiz_calculate_best_attempt($quiz, $attempts)) {
continue;
}
if (!$questions = quiz_get_attempt_responses($bestattempt, $quiz)) {
if (!$questions = quiz_report_simplestat_get_attempt_responses($bestattempt, $quiz)) {
continue;
}
quiz_remove_unwanted_questions($questions, $quiz);
quiz_report_simplestat_remove_unwanted_questions($questions, $quiz);
if (!$results = quiz_grade_attempt_results($quiz, $questions)) {
if (!$results = quiz_report_simplestat_grade_attempt_results($quiz, $questions)) {
error("Could not re-grade this quiz attempt!");
}

View File

@ -85,20 +85,11 @@
print_heading($quiz->name);
if (! $questions = quiz_get_attempt_responses($attempt)) {
if ($user = get_record("user", "id", $attempt->userid)) {
$fullname = fullname($user);
} else {
$fullname = "????";
}
print_heading(get_string("attemptincomplete", "quiz", $fullname));
print_footer($course);
exit;
if (!($questions = quiz_get_attempt_questions($quiz, $attempt))) {
error("Unable to get questions from database for quiz $quiz->id attempt $attempt->id number $attempt->attempt");
}
quiz_remove_unwanted_questions($questions, $quiz);
if (!$result = quiz_grade_attempt_results($quiz, $questions)) {
if (!$result = quiz_grade_responses($quiz, $questions)) {
error("Could not re-grade this quiz attempt!");
}
@ -128,7 +119,7 @@
$quiz->correctanswers = true;
$quiz->shuffleanswers = false;
$quiz->shufflequestions = false;
quiz_print_quiz_questions($quiz, $result, $questions);
quiz_print_quiz_questions($quiz, $questions, $result);
if (isteacher($course->id)) {
print_continue("report.php?q=$quiz->id");