mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +02:00

of questions. These use 2 or more short answer questions at random to construct a questions where you have to match answers to questions. Only lightly tested so far. Quiz questions can now be edited with Richtext editor. Sundry little fixes along the way.
1436 lines
53 KiB
PHP
1436 lines
53 KiB
PHP
<?PHP // $Id$
|
|
|
|
/// Library of function for module quiz
|
|
|
|
/// CONSTANTS ///////////////////////////////////////////////////////////////////
|
|
|
|
define("GRADEHIGHEST", "1");
|
|
define("GRADEAVERAGE", "2");
|
|
define("ATTEMPTFIRST", "3");
|
|
define("ATTEMPTLAST", "4");
|
|
$QUIZ_GRADE_METHOD = array ( GRADEHIGHEST => get_string("gradehighest", "quiz"),
|
|
GRADEAVERAGE => get_string("gradeaverage", "quiz"),
|
|
ATTEMPTFIRST => get_string("attemptfirst", "quiz"),
|
|
ATTEMPTLAST => get_string("attemptlast", "quiz"));
|
|
|
|
define("SHORTANSWER", "1");
|
|
define("TRUEFALSE", "2");
|
|
define("MULTICHOICE", "3");
|
|
define("RANDOM", "4");
|
|
define("MATCH", "5");
|
|
define("RANDOMMATCH", "6");
|
|
|
|
$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
|
|
TRUEFALSE => get_string("truefalse", "quiz"),
|
|
SHORTANSWER => get_string("shortanswer", "quiz"),
|
|
RANDOMMATCH => get_string("randommatch", "quiz") );
|
|
|
|
$QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"),
|
|
"webct" => get_string("webct", "quiz"),
|
|
"qti" => get_string("qti", "quiz"),
|
|
"missingword" => get_string("missingword", "quiz") );
|
|
|
|
define("QUIZ_PICTURE_DEFAULT_HEIGHT", "200");
|
|
|
|
/// FUNCTIONS ///////////////////////////////////////////////////////////////////
|
|
|
|
function quiz_add_instance($quiz) {
|
|
/// Given an object containing all the necessary data,
|
|
/// (defined by the form in mod.html) this function
|
|
/// will create a new instance and return the id number
|
|
/// of the new instance.
|
|
|
|
$quiz->created = time();
|
|
$quiz->timemodified = time();
|
|
$quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
|
|
$quiz->openhour, $quiz->openminute, 0);
|
|
$quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
|
|
$quiz->closehour, $quiz->closeminute, 0);
|
|
|
|
if (!$quiz->id = insert_record("quiz", $quiz)) {
|
|
return false; // some error occurred
|
|
}
|
|
|
|
// The grades for every question in this quiz are stored in an array
|
|
if ($quiz->grades) {
|
|
foreach ($quiz->grades as $question => $grade) {
|
|
if ($question and $grade) {
|
|
unset($questiongrade);
|
|
$questiongrade->quiz = $quiz->id;
|
|
$questiongrade->question = $question;
|
|
$questiongrade->grade = $grade;
|
|
if (!insert_record("quiz_question_grades", $questiongrade)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $quiz->id;
|
|
}
|
|
|
|
|
|
function quiz_update_instance($quiz) {
|
|
/// Given an object containing all the necessary data,
|
|
/// (defined by the form in mod.html) this function
|
|
/// will update an existing instance with new data.
|
|
|
|
$quiz->timemodified = time();
|
|
$quiz->timeopen = make_timestamp($quiz->openyear, $quiz->openmonth, $quiz->openday,
|
|
$quiz->openhour, $quiz->openminute, 0);
|
|
$quiz->timeclose = make_timestamp($quiz->closeyear, $quiz->closemonth, $quiz->closeday,
|
|
$quiz->closehour, $quiz->closeminute, 0);
|
|
$quiz->id = $quiz->instance;
|
|
|
|
if (!update_record("quiz", $quiz)) {
|
|
return false; // some error occurred
|
|
}
|
|
|
|
|
|
// The grades for every question in this quiz are stored in an array
|
|
// Insert or update records as appropriate
|
|
|
|
$existing = get_records("quiz_question_grades", "quiz", $quiz->id, "", "question,grade,id");
|
|
|
|
if ($quiz->grades) {
|
|
foreach ($quiz->grades as $question => $grade) {
|
|
if ($question and $grade) {
|
|
unset($questiongrade);
|
|
$questiongrade->quiz = $quiz->id;
|
|
$questiongrade->question = $question;
|
|
$questiongrade->grade = $grade;
|
|
if (isset($existing[$question])) {
|
|
if ($existing[$question]->grade != $grade) {
|
|
$questiongrade->id = $existing[$question]->id;
|
|
if (!update_record("quiz_question_grades", $questiongrade)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (!insert_record("quiz_question_grades", $questiongrade)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
function quiz_delete_instance($id) {
|
|
/// Given an ID of an instance of this module,
|
|
/// this function will permanently delete the instance
|
|
/// and any data that depends on it.
|
|
|
|
if (! $quiz = get_record("quiz", "id", "$id")) {
|
|
return false;
|
|
}
|
|
|
|
$result = true;
|
|
|
|
if ($attempts = get_record("quiz_attempts", "quiz", "$quiz->id")) {
|
|
foreach ($attempts as $attempt) {
|
|
if (! delete_records("quiz_responses", "attempt", "$attempt->id")) {
|
|
$result = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) {
|
|
$result = false;
|
|
}
|
|
|
|
if (! delete_records("quiz_grades", "quiz", "$quiz->id")) {
|
|
$result = false;
|
|
}
|
|
|
|
if (! delete_records("quiz_question_grades", "quiz", "$quiz->id")) {
|
|
$result = false;
|
|
}
|
|
|
|
if (! delete_records("quiz", "id", "$quiz->id")) {
|
|
$result = false;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
function quiz_user_outline($course, $user, $mod, $quiz) {
|
|
/// Return a small object with summary information about what a
|
|
/// user has done with a given particular instance of this module
|
|
/// Used for user activity reports.
|
|
/// $return->time = the time they did it
|
|
/// $return->info = a short text description
|
|
if ($grade = get_record("quiz_grades", "userid", $user->id, "quiz", $quiz->id)) {
|
|
|
|
if ($grade->grade) {
|
|
$result->info = get_string("grade").": $grade->grade";
|
|
}
|
|
$result->time = $grade->timemodified;
|
|
return $result;
|
|
}
|
|
return NULL;
|
|
|
|
return $return;
|
|
}
|
|
|
|
function quiz_user_complete($course, $user, $mod, $quiz) {
|
|
/// Print a detailed representation of what a user has done with
|
|
/// a given particular instance of this module, for user activity reports.
|
|
|
|
return true;
|
|
}
|
|
|
|
function quiz_print_recent_activity(&$logs, $isteacher=false) {
|
|
/// Given a list of logs, assumed to be those since the last login
|
|
/// this function prints a short list of changes related to this module
|
|
/// If isteacher is true then perhaps additional information is printed.
|
|
/// This function is called from course/lib.php: print_recent_activity()
|
|
|
|
global $CFG, $COURSE_TEACHER_COLOR;
|
|
|
|
$content = "";
|
|
|
|
return $content; // True if anything was printed, otherwise false
|
|
}
|
|
|
|
function quiz_cron () {
|
|
/// Function to be run periodically according to the moodle cron
|
|
/// This function searches for things that need to be done, such
|
|
/// as sending out mail, toggling flags etc ...
|
|
|
|
global $CFG;
|
|
|
|
return true;
|
|
}
|
|
|
|
function quiz_grades($quizid) {
|
|
/// Must return an array of grades, indexed by user, and a max grade.
|
|
|
|
$return->grades = get_records_menu("quiz_grades", "quiz", $quizid, "", "userid,grade");
|
|
$return->maxgrade = get_field("quiz", "grade", "id", "$quizid");
|
|
return $return;
|
|
}
|
|
|
|
|
|
/// SQL FUNCTIONS ////////////////////////////////////////////////////////////////////
|
|
|
|
function quiz_move_questions($category1, $category2) {
|
|
global $CFG;
|
|
return execute_sql("UPDATE {$CFG->prefix}quiz_questions
|
|
SET category = '$category2'
|
|
WHERE category = '$category1'",
|
|
false);
|
|
}
|
|
|
|
function quiz_get_question_grades($quizid, $questionlist) {
|
|
global $CFG;
|
|
|
|
return get_records_sql("SELECT question,grade
|
|
FROM {$CFG->prefix}quiz_question_grades
|
|
WHERE quiz = '$quizid'
|
|
AND question IN ($questionlist)");
|
|
}
|
|
|
|
function quiz_get_grade_records($quiz) {
|
|
/// Gets all info required to display the table of quiz results
|
|
/// for report.php
|
|
global $CFG;
|
|
|
|
return get_records_sql("SELECT qg.*, u.firstname, u.lastname, u.picture
|
|
FROM {$CFG->prefix}quiz_grades qg,
|
|
{$CFG->prefix}user u
|
|
WHERE qg.quiz = '$quiz->id'
|
|
AND qg.userid = u.id");
|
|
}
|
|
|
|
function quiz_get_answers($question) {
|
|
// Given a question, returns the correct answers and grades
|
|
global $CFG;
|
|
|
|
switch ($question->qtype) {
|
|
case SHORTANSWER: // Could be multiple answers
|
|
return get_records_sql("SELECT a.*, sa.usecase, g.grade
|
|
FROM {$CFG->prefix}quiz_shortanswer sa,
|
|
{$CFG->prefix}quiz_answers a,
|
|
{$CFG->prefix}quiz_question_grades g
|
|
WHERE sa.question = '$question->id'
|
|
AND sa.question = a.question
|
|
AND sa.question = g.question");
|
|
break;
|
|
|
|
case TRUEFALSE: // Should be always two answers
|
|
return get_records_sql("SELECT a.*, g.grade
|
|
FROM {$CFG->prefix}quiz_answers a,
|
|
{$CFG->prefix}quiz_question_grades g
|
|
WHERE a.question = '$question->id'
|
|
AND a.question = g.question");
|
|
break;
|
|
|
|
case MULTICHOICE: // Should be multiple answers
|
|
return get_records_sql("SELECT a.*, mc.single, g.grade
|
|
FROM {$CFG->prefix}quiz_multichoice mc,
|
|
{$CFG->prefix}quiz_answers a,
|
|
{$CFG->prefix}quiz_question_grades g
|
|
WHERE mc.question = '$question->id'
|
|
AND mc.question = a.question
|
|
AND mc.question = g.question");
|
|
break;
|
|
|
|
case RANDOM:
|
|
return false; // Not done yet
|
|
break;
|
|
|
|
case RANDOMMATCH: // Could be any of many answers, return them all
|
|
return get_records_sql("SELECT a.*, g.grade
|
|
FROM {$CFG->prefix}quiz_questions q,
|
|
{$CFG->prefix}quiz_answers a,
|
|
{$CFG->prefix}quiz_question_grades g
|
|
WHERE q.category = '$question->category'
|
|
AND q.qtype = ".SHORTANSWER."
|
|
AND q.id = a.question
|
|
AND g.question = '$question->id'");
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
function quiz_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, 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) {
|
|
$responses[$key]->answer = explode(",",$response->answer);
|
|
}
|
|
|
|
return $responses;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
/// Any other quiz functions go here. Each of them must have a name that
|
|
/// starts with quiz_
|
|
|
|
function quiz_print_comment($text) {
|
|
global $THEME;
|
|
|
|
echo "<SPAN CLASS=feedbacktext>".text_to_html($text, true, false)."</SPAN>";
|
|
}
|
|
|
|
function quiz_print_correctanswer($text) {
|
|
global $THEME;
|
|
|
|
echo "<P ALIGN=RIGHT><SPAN CLASS=highlight>$text</SPAN></P>";
|
|
}
|
|
|
|
function quiz_print_question_icon($question) {
|
|
// Prints a question icon
|
|
|
|
global $QUIZ_QUESTION_TYPE;
|
|
|
|
echo "<A HREF=\"question.php?id=$question->id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->qtype]."\">";
|
|
switch ($question->qtype) {
|
|
case SHORTANSWER:
|
|
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/sa.gif\">";
|
|
break;
|
|
case TRUEFALSE:
|
|
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/tf.gif\">";
|
|
break;
|
|
case MULTICHOICE:
|
|
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/mc.gif\">";
|
|
break;
|
|
case RANDOM:
|
|
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
|
|
break;
|
|
case RANDOMMATCH:
|
|
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rm.gif\">";
|
|
break;
|
|
}
|
|
echo "</A>\n";
|
|
}
|
|
|
|
function quiz_print_question($number, $questionid, $grade, $courseid,
|
|
$feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL) {
|
|
/// Prints a quiz question, any format
|
|
|
|
if (!$question = get_record("quiz_questions", "id", $questionid)) {
|
|
notify("Error: Question not found!");
|
|
}
|
|
|
|
if (empty($actualgrade)) {
|
|
$actualgrade = 0;
|
|
}
|
|
|
|
$stranswer = get_string("answer", "quiz");
|
|
$strmarks = get_string("marks", "quiz");
|
|
|
|
echo "<TABLE WIDTH=100% CELLSPACING=10><TR><TD NOWRAP WIDTH=100 VALIGN=top>";
|
|
echo "<P ALIGN=CENTER><B>$number</B></P>";
|
|
if ($feedback or $response) {
|
|
echo "<P ALIGN=CENTER><FONT SIZE=1>$strmarks: $actualgrade/$grade</FONT></P>";
|
|
} else {
|
|
echo "<P ALIGN=CENTER><FONT SIZE=1>$grade $strmarks</FONT></P>";
|
|
}
|
|
print_spacer(1,100);
|
|
echo "</TD><TD VALIGN=TOP>";
|
|
|
|
switch ($question->qtype) {
|
|
case SHORTANSWER:
|
|
if (!$options = get_record("quiz_shortanswer", "question", $question->id)) {
|
|
notify("Error: Missing question options!");
|
|
}
|
|
echo text_to_html($question->questiontext);
|
|
if ($question->image) {
|
|
print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
|
|
}
|
|
if ($response) {
|
|
$value = "VALUE=\"$response[0]\"";
|
|
} else {
|
|
$value = "";
|
|
}
|
|
echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$question->id SIZE=20 $value></P>";
|
|
if ($feedback) {
|
|
quiz_print_comment("<P ALIGN=right>$feedback[0]</P>");
|
|
}
|
|
if ($correct) {
|
|
$correctanswers = implode(", ", $correct);
|
|
quiz_print_correctanswer($correctanswers);
|
|
}
|
|
break;
|
|
|
|
case TRUEFALSE:
|
|
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!");
|
|
}
|
|
if (!$true->answer) {
|
|
$true->answer = get_string("true", "quiz");
|
|
}
|
|
if (!$false->answer) {
|
|
$false->answer = get_string("false", "quiz");
|
|
}
|
|
echo text_to_html($question->questiontext);
|
|
if ($question->image) {
|
|
print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
|
|
}
|
|
|
|
$truechecked = "";
|
|
$falsechecked = "";
|
|
|
|
if (!empty($response[$true->id])) {
|
|
$truechecked = "CHECKED";
|
|
$feedbackid = $true->id;
|
|
} else if (!empty($response[$false->id])) {
|
|
$falsechecked = "CHECKED";
|
|
$feedbackid = $false->id;
|
|
}
|
|
|
|
$truecorrect = "";
|
|
$falsecorrect = "";
|
|
if ($correct) {
|
|
if (!empty($correct[$true->id])) {
|
|
$truecorrect = "CLASS=highlight";
|
|
}
|
|
if (!empty($correct[$false->id])) {
|
|
$falsecorrect = "CLASS=highlight";
|
|
}
|
|
}
|
|
echo "<TABLE ALIGN=right cellpadding=5><TR><TD align=right>$stranswer: ";
|
|
echo "<TD $truecorrect>";
|
|
echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$true->id\">$true->answer";
|
|
echo "</TD><TD $falsecorrect>";
|
|
echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$question->id\" VALUE=\"$false->id\">$false->answer</P>";
|
|
echo "</TD></TR></TABLE><BR CLEAR=ALL>";
|
|
if ($feedback) {
|
|
quiz_print_comment("<P ALIGN=right>$feedback[$feedbackid]</P>");
|
|
}
|
|
|
|
break;
|
|
|
|
case MULTICHOICE:
|
|
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!");
|
|
}
|
|
echo text_to_html($question->questiontext);
|
|
if ($question->image) {
|
|
print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
|
|
}
|
|
echo "<TABLE ALIGN=right>";
|
|
echo "<TR><TD valign=top>$stranswer: </TD><TD>";
|
|
echo "<TABLE>";
|
|
$answerids = explode(",", $options->answers);
|
|
|
|
foreach ($answerids as $key => $answerid) {
|
|
$answer = $answers[$answerid];
|
|
$qnumchar = chr(ord('a') + $key);
|
|
|
|
if (empty($feedback) or empty($response[$answerid])) {
|
|
$checked = "";
|
|
} else {
|
|
$checked = "CHECKED";
|
|
}
|
|
echo "<TR><TD valign=top>";
|
|
if ($options->single) {
|
|
echo "<INPUT $checked TYPE=RADIO NAME=q$question->id VALUE=\"$answer->id\">";
|
|
} else {
|
|
echo "<INPUT $checked TYPE=CHECKBOX NAME=q$question->id"."a$answer->id VALUE=\"$answer->id\">";
|
|
}
|
|
echo "</TD>";
|
|
if (empty($feedback) or empty($correct[$answer->id])) {
|
|
echo "<TD valign=top>$qnumchar. $answer->answer</TD>";
|
|
} else {
|
|
echo "<TD valign=top CLASS=highlight>$qnumchar. $answer->answer</TD>";
|
|
}
|
|
if (!empty($feedback)) {
|
|
echo "<TD valign=top> ";
|
|
if (!empty($response[$answerid])) {
|
|
quiz_print_comment($feedback[$answerid]);
|
|
}
|
|
echo "</TD>";
|
|
}
|
|
echo "</TR>";
|
|
}
|
|
echo "</TABLE>";
|
|
echo "</TABLE>";
|
|
break;
|
|
|
|
case RANDOM:
|
|
echo "<P>Random questions not supported yet</P>";
|
|
break;
|
|
|
|
case RANDOMMATCH:
|
|
if (!$options = get_record("quiz_randommatch", "question", $question->id)) {
|
|
notify("Error: Missing question options!");
|
|
}
|
|
echo text_to_html($question->questiontext);
|
|
if ($question->image) {
|
|
print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
|
|
}
|
|
|
|
/// First, get all the questions available
|
|
|
|
$allquestions = get_records_select("quiz_questions",
|
|
"category = $question->category AND qtype = ".SHORTANSWER);
|
|
if (count($allquestions) < $options->choose) {
|
|
notify("Error: could not find enough Short Answer questions in the database!");
|
|
notify("Found ".count($allquestions).", need $options->choose.");
|
|
break;
|
|
}
|
|
|
|
if (empty($response)) { // Randomly pick the questions
|
|
if (!$randomquestions = draw_rand_array($allquestions, $options->choose)) {
|
|
notify("Error choosing $options->choose random questions");
|
|
break;
|
|
}
|
|
} else { // Use existing questions
|
|
$randomquestions = array();
|
|
foreach ($response as $key => $rrr) {
|
|
$rrr = explode("-", $rrr);
|
|
$randomquestions[$key] = $allquestions[$key];
|
|
$responseanswer[$key] = $rrr[1];
|
|
}
|
|
}
|
|
|
|
/// For each selected, find the best matching answers
|
|
|
|
foreach ($randomquestions as $randomquestion) {
|
|
$shortanswerquestion = get_record("quiz_shortanswer", "question", $randomquestion->id);
|
|
$questionanswers = get_records_list("quiz_answers", "id", $shortanswerquestion->answers);
|
|
$bestfraction = 0;
|
|
$bestanswer = NULL;
|
|
foreach ($questionanswers as $questionanswer) {
|
|
if ($questionanswer->fraction > $bestfraction) {
|
|
$bestanswer = $questionanswer;
|
|
}
|
|
}
|
|
if (empty($bestanswer)) {
|
|
notify("Error: Could not find the best answer for question: ".$randomquestions->name);
|
|
break;
|
|
}
|
|
$randomanswers[$bestanswer->id] = trim($bestanswer->answer);
|
|
}
|
|
|
|
if (!$randomanswers = draw_rand_array($randomanswers, $options->choose)) { // Mix them up
|
|
notify("Error randomising answers!");
|
|
break;
|
|
}
|
|
|
|
echo "<table border=0 cellpadding=10>";
|
|
foreach ($randomquestions as $key => $randomquestion) {
|
|
echo "<tr><td align=left valign=top>";
|
|
echo $randomquestion->questiontext;
|
|
echo "</td>";
|
|
echo "<td align=right valign=top>";
|
|
if (empty($response)) {
|
|
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id");
|
|
} else {
|
|
if (!empty($correct[$key])) {
|
|
if ($randomanswers[$responseanswer[$key]] == $correct[$key]) {
|
|
echo "<span=highlight>";
|
|
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
|
|
echo "</span><br \>";
|
|
} else {
|
|
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
|
|
quiz_print_correctanswer($correct[$key]);
|
|
}
|
|
} else {
|
|
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
|
|
}
|
|
if (!empty($feedback[$key])) {
|
|
quiz_print_comment($feedback[$key]);
|
|
}
|
|
}
|
|
echo "</td></tr>";
|
|
}
|
|
echo "</table>";
|
|
break;
|
|
|
|
|
|
default:
|
|
notify("Error: Unknown question type!");
|
|
}
|
|
|
|
echo "</TD></TR></TABLE>";
|
|
}
|
|
|
|
function quiz_print_quiz_questions($quiz, $results=NULL) {
|
|
// Prints a whole quiz on one page.
|
|
|
|
if (empty($quiz->questions)) {
|
|
notify("No questions have been defined!", "view.php?id=$cm->id");
|
|
return false;
|
|
}
|
|
|
|
$questions = explode(",", $quiz->questions);
|
|
|
|
if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) {
|
|
notify("No grades were found for these questions!");
|
|
return false;
|
|
}
|
|
|
|
$strconfirmattempt = addslashes(get_string("readytosend", "quiz"));
|
|
|
|
echo "<FORM METHOD=POST ACTION=attempt.php onsubmit=\"return confirm('$strconfirmattempt');\">";
|
|
echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";
|
|
|
|
foreach ($questions as $key => $questionid) {
|
|
$feedback = NULL;
|
|
$response = NULL;
|
|
$actualgrades = NULL;
|
|
$correct = NULL;
|
|
if (!empty($results)) {
|
|
if (!empty($results->feedback[$questionid])) {
|
|
$feedback = $results->feedback[$questionid];
|
|
}
|
|
if (!empty($results->response[$questionid])) {
|
|
$response = $results->response[$questionid];
|
|
}
|
|
if (!empty($results->grades[$questionid])) {
|
|
$actualgrades = $results->grades[$questionid];
|
|
}
|
|
if ($quiz->correctanswers) {
|
|
if (!empty($results->correct[$questionid])) {
|
|
$correct = $results->correct[$questionid];
|
|
}
|
|
}
|
|
}
|
|
|
|
print_simple_box_start("CENTER", "90%");
|
|
quiz_print_question($key+1, $questionid, $grades[$questionid]->grade, $quiz->course,
|
|
$feedback, $response, $actualgrades, $correct);
|
|
print_simple_box_end();
|
|
echo "<BR>";
|
|
}
|
|
|
|
if (empty($results)) {
|
|
echo "<CENTER><INPUT TYPE=submit VALUE=\"".get_string("savemyanswers", "quiz")."\"></CENTER>";
|
|
}
|
|
echo "</FORM>";
|
|
|
|
return true;
|
|
}
|
|
|
|
function quiz_get_default_category($courseid) {
|
|
if ($categories = get_records("quiz_categories", "course", $courseid, "id")) {
|
|
foreach ($categories as $category) {
|
|
return $category; // Return the first one (lowest id)
|
|
}
|
|
}
|
|
|
|
// Otherwise, we need to make one
|
|
$category->name = get_string("default", "quiz");
|
|
$category->info = get_string("defaultinfo", "quiz");
|
|
$category->course = $courseid;
|
|
$category->publish = 0;
|
|
|
|
if (!$category->id = insert_record("quiz_categories", $category)) {
|
|
notify("Error creating a default category!");
|
|
return false;
|
|
}
|
|
return $category;
|
|
}
|
|
|
|
function quiz_get_category_menu($courseid, $published=false) {
|
|
if ($published) {
|
|
$publish = "OR publish = '1'";
|
|
}
|
|
return get_records_select_menu("quiz_categories", "course='$courseid' $publish", "name ASC", "id,name");
|
|
}
|
|
|
|
function quiz_print_category_form($course, $current) {
|
|
// Prints a form to choose categories
|
|
|
|
if (!$categories = get_records_select("quiz_categories", "course='$course->id' OR publish = '1'", "name ASC")) {
|
|
if (!$category = quiz_get_default_category($course->id)) {
|
|
notify("Error creating a default category!");
|
|
return false;
|
|
}
|
|
$categories[$category->id] = $category;
|
|
}
|
|
foreach ($categories as $key => $category) {
|
|
if ($category->publish) {
|
|
if ($catcourse = get_record("course", "id", $category->course)) {
|
|
$category->name .= " ($catcourse->shortname)";
|
|
}
|
|
}
|
|
$catmenu[$category->id] = $category->name;
|
|
}
|
|
$strcategory = get_string("category", "quiz");
|
|
$strshow = get_string("show", "quiz");
|
|
$streditcats = get_string("editcategories", "quiz");
|
|
|
|
echo "<TABLE width=\"100%\"><TR><TD NOWRAP>";
|
|
echo "<FORM METHOD=POST ACTION=edit.php>";
|
|
echo "<B>$strcategory:</B> ";
|
|
choose_from_menu($catmenu, "cat", "$current");
|
|
echo "<INPUT TYPE=submit VALUE=\"$strshow\">";
|
|
echo "</FORM>";
|
|
echo "</TD><TD align=right>";
|
|
echo "<FORM METHOD=GET ACTION=category.php>";
|
|
echo "<INPUT TYPE=hidden NAME=id VALUE=\"$course->id\">";
|
|
echo "<INPUT TYPE=submit VALUE=\"$streditcats\">";
|
|
echo "</FORM>";
|
|
echo "</TD></TR></TABLE>";
|
|
}
|
|
|
|
|
|
function quiz_get_all_question_grades($questionlist, $quizid) {
|
|
// Given a list of question IDs, finds grades or invents them to
|
|
// create an array of matching grades
|
|
|
|
$questions = quiz_get_question_grades($quizid, $questionlist);
|
|
|
|
$list = explode(",", $questionlist);
|
|
$grades = array();
|
|
|
|
foreach ($list as $qid) {
|
|
if (isset($questions[$qid])) {
|
|
$grades[$qid] = $questions[$qid]->grade;
|
|
} else {
|
|
$grades[$qid] = 1;
|
|
}
|
|
}
|
|
return $grades;
|
|
}
|
|
|
|
|
|
function quiz_print_question_list($questionlist, $grades) {
|
|
// Prints a list of quiz questions in a small layout form with knobs
|
|
// $questionlist is comma-separated list
|
|
// $grades is an array of corresponding grades
|
|
|
|
global $THEME;
|
|
|
|
if (!$questionlist) {
|
|
echo "<P align=center>";
|
|
print_string("noquestions", "quiz");
|
|
echo "</P>";
|
|
return;
|
|
}
|
|
|
|
$order = explode(",", $questionlist);
|
|
|
|
if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
|
|
error("No questions were found!");
|
|
}
|
|
|
|
$strorder = get_string("order");
|
|
$strquestionname = get_string("questionname", "quiz");
|
|
$strgrade = get_string("grade");
|
|
$strdelete = get_string("delete");
|
|
$stredit = get_string("edit");
|
|
$strmoveup = get_string("moveup");
|
|
$strmovedown = get_string("movedown");
|
|
$strsavegrades = get_string("savegrades", "quiz");
|
|
$strtype = get_string("type", "quiz");
|
|
|
|
for ($i=10; $i>=0; $i--) {
|
|
$gradesmenu[$i] = $i;
|
|
}
|
|
$count = 0;
|
|
$sumgrade = 0;
|
|
$total = count($order);
|
|
echo "<FORM METHOD=post ACTION=edit.php>";
|
|
echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
|
|
echo "<TR><TH WIDTH=\"*\" COLSPAN=3 NOWRAP>$strorder</TH><TH align=left WIDTH=\"100%\" NOWRAP>$strquestionname</TH><TH width=\"*\" NOWRAP>$strtype</TH><TH WIDTH=\"*\" NOWRAP>$strgrade</TH><TH WIDTH=\"*\" NOWRAP>$stredit</TH></TR>";
|
|
foreach ($order as $qnum) {
|
|
if (empty($questions[$qnum])) {
|
|
continue;
|
|
}
|
|
$count++;
|
|
echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
|
|
echo "<TD>$count</TD>";
|
|
echo "<TD>";
|
|
if ($count != 1) {
|
|
echo "<A TITLE=\"$strmoveup\" HREF=\"edit.php?up=$qnum\"><IMG
|
|
SRC=\"../../pix/t/up.gif\" BORDER=0></A>";
|
|
}
|
|
echo "</TD>";
|
|
echo "<TD>";
|
|
if ($count != $total) {
|
|
echo "<A TITLE=\"$strmovedown\" HREF=\"edit.php?down=$qnum\"><IMG
|
|
SRC=\"../../pix/t/down.gif\" BORDER=0></A>";
|
|
}
|
|
echo "</TD>";
|
|
echo "<TD>".$questions[$qnum]->name."</TD>";
|
|
echo "<TD ALIGN=CENTER>";
|
|
quiz_print_question_icon($questions[$qnum]);
|
|
echo "</TD>";
|
|
echo "<TD>";
|
|
choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], "");
|
|
echo "<TD>";
|
|
echo "<A TITLE=\"$strdelete\" HREF=\"edit.php?delete=$qnum\"><IMG
|
|
SRC=\"../../pix/t/delete.gif\" BORDER=0></A> ";
|
|
echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$qnum\"><IMG
|
|
SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
|
|
echo "</TD>";
|
|
|
|
$sumgrade += $grades[$qnum];
|
|
}
|
|
echo "<TR><TD COLSPAN=5 ALIGN=right>";
|
|
echo "<INPUT TYPE=submit VALUE=\"$strsavegrades:\">";
|
|
echo "<INPUT TYPE=hidden NAME=setgrades VALUE=\"save\">";
|
|
echo "<TD ALIGN=LEFT BGCOLOR=\"$THEME->cellcontent\">";
|
|
echo "<B>$sumgrade</B>";
|
|
echo "</TD><TD></TD></TR>";
|
|
echo "</TABLE>";
|
|
echo "</FORM>";
|
|
|
|
return $sumgrade;
|
|
}
|
|
|
|
|
|
function quiz_print_cat_question_list($categoryid) {
|
|
// Prints a form to choose categories
|
|
|
|
global $THEME, $QUIZ_QUESTION_TYPE;
|
|
|
|
$strcategory = get_string("category", "quiz");
|
|
$strquestion = get_string("question", "quiz");
|
|
$straddquestions = get_string("addquestions", "quiz");
|
|
$strimportquestions = get_string("importquestions", "quiz");
|
|
$strnoquestions = get_string("noquestions", "quiz");
|
|
$strselect = get_string("select", "quiz");
|
|
$strselectall = get_string("selectall", "quiz");
|
|
$strcreatenewquestion = get_string("createnewquestion", "quiz");
|
|
$strquestionname = get_string("questionname", "quiz");
|
|
$strdelete = get_string("delete");
|
|
$stredit = get_string("edit");
|
|
$straddselectedtoquiz = get_string("addselectedtoquiz", "quiz");
|
|
$strtype = get_string("type", "quiz");
|
|
|
|
if (!$categoryid) {
|
|
echo "<P align=center>";
|
|
print_string("selectcategoryabove", "quiz");
|
|
echo "</P>";
|
|
return;
|
|
}
|
|
|
|
if (!$category = get_record("quiz_categories", "id", "$categoryid")) {
|
|
notify("Category not found!");
|
|
return;
|
|
}
|
|
echo "<CENTER>";
|
|
echo text_to_html($category->info);
|
|
|
|
echo "<TABLE><TR>";
|
|
echo "<TD valign=top><B>$straddquestions:</B></TD>";
|
|
echo "<TD valign=top align=right>";
|
|
echo "<FORM METHOD=GET ACTION=question.php>";
|
|
choose_from_menu($QUIZ_QUESTION_TYPE, "qtype", "", "");
|
|
echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
|
|
echo "<INPUT TYPE=submit VALUE=\"$strcreatenewquestion\">";
|
|
helpbutton("questiontypes", $strcreatenewquestion, "quiz");
|
|
echo "</FORM>";
|
|
|
|
echo "<FORM METHOD=GET ACTION=import.php>";
|
|
echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
|
|
echo "<INPUT TYPE=submit VALUE=\"$strimportquestions\">";
|
|
helpbutton("import", $strimportquestions, "quiz");
|
|
echo "</FORM>";
|
|
|
|
echo "</TR></TABLE>";
|
|
|
|
echo "</CENTER>";
|
|
|
|
if (!$questions = get_records("quiz_questions", "category", $category->id)) {
|
|
echo "<P align=center>";
|
|
print_string("noquestions", "quiz");
|
|
echo "</P>";
|
|
return;
|
|
}
|
|
|
|
$canedit = isteacher($category->course);
|
|
|
|
echo "<FORM METHOD=post ACTION=edit.php>";
|
|
echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
|
|
echo "<TR><TH width=\"*\" NOWRAP>$strselect</TH><TH width=\"100%\" align=left NOWRAP>$strquestionname</TH><TH WIDTH=\"*\" NOWRAP>$strtype</TH>";
|
|
if ($canedit) {
|
|
echo "<TH width=\"*\" NOWRAP>$stredit</TH>";
|
|
}
|
|
echo "</TR>";
|
|
foreach ($questions as $question) {
|
|
echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
|
|
echo "<TD ALIGN=CENTER>";
|
|
echo "<INPUT TYPE=CHECKBOX NAME=q$question->id VALUE=\"1\">";
|
|
echo "</TD>";
|
|
echo "<TD>".$question->name."</TD>";
|
|
echo "<TD ALIGN=CENTER>";
|
|
quiz_print_question_icon($question);
|
|
echo "</TD>";
|
|
if ($canedit) {
|
|
echo "<TD>";
|
|
echo "<A TITLE=\"$strdelete\" HREF=\"question.php?id=$question->id&delete=$question->id\"><IMG
|
|
SRC=\"../../pix/t/delete.gif\" BORDER=0></A> ";
|
|
echo "<A TITLE=\"$stredit\" HREF=\"question.php?id=$question->id\"><IMG
|
|
SRC=\"../../pix/t/edit.gif\" BORDER=0></A>";
|
|
echo "</TD></TR>";
|
|
}
|
|
echo "</TR>";
|
|
}
|
|
echo "<TR><TD COLSPAN=3>";
|
|
echo "<INPUT TYPE=submit NAME=add VALUE=\"<< $straddselectedtoquiz\">";
|
|
//echo "<INPUT TYPE=submit NAME=delete VALUE=\"XX Delete selected\">";
|
|
echo "<INPUT type=button onclick=\"checkall()\" value=\"$strselectall\">";
|
|
echo "</TD></TR>";
|
|
echo "</TABLE>";
|
|
echo "</FORM>";
|
|
}
|
|
|
|
|
|
function quiz_start_attempt($quizid, $userid, $numattempt) {
|
|
$attempt->quiz = $quizid;
|
|
$attempt->userid = $userid;
|
|
$attempt->attempt = $numattempt;
|
|
$attempt->timestart = time();
|
|
$attempt->timefinish = 0;
|
|
$attempt->timemodified = time();
|
|
|
|
return insert_record("quiz_attempts", $attempt);
|
|
}
|
|
|
|
function quiz_get_user_attempt_unfinished($quizid, $userid) {
|
|
// Returns an object containing an unfinished attempt (if there is one)
|
|
return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0);
|
|
}
|
|
|
|
function quiz_get_user_attempts($quizid, $userid) {
|
|
// Returns a list of all attempts by a user
|
|
return get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0",
|
|
"attempt ASC");
|
|
}
|
|
|
|
|
|
function quiz_get_user_attempts_string($quiz, $attempts, $bestgrade) {
|
|
/// Returns a simple little comma-separated list of all attempts,
|
|
/// with each grade linked to the feedback report and with the best grade highlighted
|
|
|
|
$bestgrade = format_float($bestgrade);
|
|
foreach ($attempts as $attempt) {
|
|
$attemptgrade = format_float(($attempt->sumgrades / $quiz->sumgrades) * $quiz->grade);
|
|
if ($attemptgrade == $bestgrade) {
|
|
$userattempts[] = "<SPAN CLASS=highlight><A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A></SPAN>";
|
|
} else {
|
|
$userattempts[] = "<A HREF=\"report.php?q=$quiz->id&attempt=$attempt->id\">$attemptgrade</A>";
|
|
}
|
|
}
|
|
return implode(",", $userattempts);
|
|
}
|
|
|
|
function quiz_get_best_grade($quizid, $userid) {
|
|
/// Get the best current grade for a particular user in a quiz
|
|
if (!$grade = get_record("quiz_grades", "quiz", $quizid, "userid", $userid)) {
|
|
return 0;
|
|
}
|
|
|
|
return (round($grade->grade,0));
|
|
}
|
|
|
|
function quiz_save_best_grade($quiz, $userid) {
|
|
/// Calculates the best grade out of all attempts at a quiz for a user,
|
|
/// and then saves that grade in the quiz_grades table.
|
|
|
|
if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
|
|
notify("Could not find any user attempts");
|
|
return false;
|
|
}
|
|
|
|
$bestgrade = quiz_calculate_best_grade($quiz, $attempts);
|
|
$bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
|
|
|
|
if ($grade = get_record("quiz_grades", "quiz", $quiz->id, "userid", $userid)) {
|
|
$grade->grade = round($bestgrade, 2);
|
|
$grade->timemodified = time();
|
|
if (!update_record("quiz_grades", $grade)) {
|
|
notify("Could not update best grade");
|
|
return false;
|
|
}
|
|
} else {
|
|
$grade->quiz = $quiz->id;
|
|
$grade->userid = $userid;
|
|
$grade->grade = round($bestgrade, 2);
|
|
$grade->timemodified = time();
|
|
if (!insert_record("quiz_grades", $grade)) {
|
|
notify("Could not insert new best grade");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
function quiz_calculate_best_grade($quiz, $attempts) {
|
|
/// Calculate the best grade for a quiz given a number of attempts by a particular user.
|
|
|
|
switch ($quiz->grademethod) {
|
|
|
|
case ATTEMPTFIRST:
|
|
foreach ($attempts as $attempt) {
|
|
return $attempt->sumgrades;
|
|
}
|
|
break;
|
|
|
|
case ATTEMPTLAST:
|
|
foreach ($attempts as $attempt) {
|
|
$final = $attempt->sumgrades;
|
|
}
|
|
return $final;
|
|
|
|
case GRADEAVERAGE:
|
|
$sum = 0;
|
|
$count = 0;
|
|
foreach ($attempts as $attempt) {
|
|
$sum += $attempt->sumgrades;
|
|
$count++;
|
|
}
|
|
return (float)$sum/$count;
|
|
|
|
default:
|
|
case GRADEHIGHEST:
|
|
$max = 0;
|
|
foreach ($attempts as $attempt) {
|
|
if ($attempt->sumgrades > $max) {
|
|
$max = $attempt->sumgrades;
|
|
}
|
|
}
|
|
return $max;
|
|
}
|
|
}
|
|
|
|
function quiz_save_attempt($quiz, $questions, $result, $attemptnum) {
|
|
/// Given a quiz, a list of attempted questions and a total grade
|
|
/// this function saves EVERYTHING so it can be reconstructed later
|
|
/// if necessary.
|
|
|
|
global $USER;
|
|
|
|
// First find the attempt in the database (start of attempt)
|
|
|
|
if (!$attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
|
|
notify("Trying to save an attempt that was not started!");
|
|
return false;
|
|
}
|
|
|
|
if ($attempt->attempt != $attemptnum) { // Double check.
|
|
notify("Number of this attempt is different to the unfinished one!");
|
|
return false;
|
|
}
|
|
|
|
// Now let's complete this record and save it
|
|
|
|
$attempt->sumgrades = $result->sumgrades;
|
|
$attempt->timefinish = time();
|
|
$attempt->timemodified = time();
|
|
|
|
if (! update_record("quiz_attempts", $attempt)) {
|
|
notify("Error while saving attempt");
|
|
return false;
|
|
}
|
|
|
|
// Now let's save all the questions for this attempt
|
|
|
|
foreach ($questions as $question) {
|
|
$response->attempt = $attempt->id;
|
|
$response->question = $question->id;
|
|
$response->grade = $result->grades[$question->id];
|
|
if (!empty($question->answer)) {
|
|
$response->answer = implode(",",$question->answer);
|
|
} else {
|
|
$response->answer = "";
|
|
}
|
|
if (!insert_record("quiz_responses", $response)) {
|
|
notify("Error while saving response");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
function quiz_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!");
|
|
}
|
|
|
|
$result->sumgrades = 0;
|
|
|
|
foreach ($questions as $question) {
|
|
if (!$answers = quiz_get_answers($question)) {
|
|
error("No answers defined for question id $question->id!");
|
|
}
|
|
|
|
$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;
|
|
$bestshortanswer = 0;
|
|
foreach($answers as $answer) { // There might be multiple right answers
|
|
if ($answer->fraction > $bestshortanswer) {
|
|
$correct[$answer->id] = $answer->answer;
|
|
$bestshortanswer = $answer->fraction;
|
|
}
|
|
if (!$answer->usecase) { // Don't compare case
|
|
$answer->answer = strtolower($answer->answer);
|
|
$question->answer = strtolower($question->answer);
|
|
}
|
|
if ($question->answer == $answer->answer) {
|
|
$feedback[0] = $answer->feedback;
|
|
$grade = (float)$answer->fraction * $answer->grade;
|
|
}
|
|
}
|
|
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 * $answer->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 * $answer->grade;
|
|
continue;
|
|
} else {
|
|
$grade += (float)$answer->fraction * $answer->grade;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RANDOMMATCH:
|
|
$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 * $answer->grade * $answerfraction;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
if ($grade < 0.0) { // No negative grades
|
|
$grade = 0.0;
|
|
}
|
|
|
|
$result->grades[$question->id] = round($grade, 2);
|
|
$result->sumgrades += $grade;
|
|
$result->feedback[$question->id] = $feedback;
|
|
$result->response[$question->id] = $response;
|
|
$result->correct[$question->id] = $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;
|
|
}
|
|
|
|
|
|
function quiz_save_question_options($question) {
|
|
/// Given some question info and some data about the the answers
|
|
/// this function parses, organises and saves the question
|
|
/// It is used by question.php when saving new data from a
|
|
/// form, and also by import.php when importing questions
|
|
///
|
|
/// Returns $result->error or $result->notice
|
|
|
|
switch ($question->qtype) {
|
|
case SHORTANSWER:
|
|
// Delete all the old answers
|
|
// FIXME - instead of deleting, update existing answers
|
|
// so as not to break existing references to them
|
|
delete_records("quiz_answers", "question", $question->id);
|
|
delete_records("quiz_shortanswer", "question", $question->id);
|
|
|
|
$answers = array();
|
|
$maxfraction = -1;
|
|
|
|
// Insert all the new answers
|
|
foreach ($question->answer as $key => $dataanswer) {
|
|
if ($dataanswer != "") {
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
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->notice = get_string("fractionsnomax", "quiz", $maxfraction);
|
|
return $result;
|
|
}
|
|
break;
|
|
case TRUEFALSE:
|
|
// FIXME - instead of deleting, update existing answers
|
|
// so as not to break existing references to them
|
|
delete_records("quiz_answers", "question", $question->id);
|
|
delete_records("quiz_truefalse", "question", $question->id);
|
|
|
|
$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;
|
|
}
|
|
|
|
$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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
break;
|
|
case MULTICHOICE:
|
|
// FIXME - instead of deleting, update existing answers
|
|
// so as not to break existing references to them
|
|
delete_records("quiz_answers", "question", $question->id);
|
|
delete_records("quiz_multichoice", "question", $question->id);
|
|
|
|
$totalfraction = 0;
|
|
$maxfraction = -1;
|
|
|
|
$answers = array();
|
|
|
|
// Insert all the new answers
|
|
foreach ($question->answer as $key => $dataanswer) {
|
|
if ($dataanswer != "") {
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
|
|
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->notice = get_string("fractionsnomax", "quiz", $maxfraction);
|
|
return $result;
|
|
}
|
|
} else {
|
|
$totalfraction = round($totalfraction,2);
|
|
if ($totalfraction != 1) {
|
|
$totalfraction = $totalfraction * 100;
|
|
$result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
|
|
return $result;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RANDOMMATCH:
|
|
$options->question = $question->id;
|
|
$options->choose = $question->choose;
|
|
if ($existing = get_record("quiz_randommatch", "question", $options->question)) {
|
|
$options->id = $existing->id;
|
|
if (!update_record("quiz_randommatch", $options)) {
|
|
$result->error = "Could not update quiz randommatch options!";
|
|
return $result;
|
|
}
|
|
} else {
|
|
if (!insert_record("quiz_randommatch", $options)) {
|
|
$result->error = "Could not insert quiz randommatch options!";
|
|
return $result;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
$result->error = "Unsupported question type ($question->qtype)!";
|
|
return $result;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
?>
|