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"); $QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"), TRUEFALSE => get_string("truefalse", "quiz"), SHORTANSWER => get_string("shortanswer", "quiz") ); /// 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; 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, 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 "cellheading2\">".text_to_html($text, true, false).""; } function quiz_print_correctanswer($text) { global $THEME; echo "
$text
"; } function quiz_print_question_icon($question) { // Prints a question icon global $QUIZ_QUESTION_TYPE; echo "id\" TITLE=\"".$QUIZ_QUESTION_TYPE[$question->qtype]."\">"; switch ($question->qtype) { case SHORTANSWER: echo ""; break; case TRUEFALSE: echo ""; break; case MULTICHOICE: echo ""; break; case RANDOM: echo ""; break; } echo "\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 "";
echo " $number "; if ($feedback or $response) { echo "$strmarks: $actualgrade/$grade "; } else { echo "$grade $strmarks "; } print_spacer(1,100); echo " | ";
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, 200);
}
if ($response) {
$value = "VALUE=\"$response[0]\"";
} else {
$value = "";
}
echo " $stranswer: id SIZE=20 $value> "; if ($feedback) { quiz_print_comment("$feedback[0] "); } 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, 200); } $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 "
"; if ($feedback) { quiz_print_comment(" $feedback[$feedbackid] "); } 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, 200); } echo "
Random questions not supported yet "; break; default: notify("Error: Unknown question type!"); } echo " |
"; echo ""; echo " | "; echo ""; echo " |
"; print_string("noquestions", "quiz"); echo "
"; 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 ""; 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"); $strnoquestions = get_string("noquestions", "quiz"); $strselect = get_string("select", "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 ""; print_string("selectcategoryabove", "quiz"); echo "
"; return; } if (!$category = get_record("quiz_categories", "id", "$categoryid")) { notify("Category not found!"); return; } echo ""; print_string("noquestions", "quiz"); echo "
"; return; } $canedit = isteacher($category->course); echo ""; } 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[] = "id&attempt=$attempt->id\">$attemptgrade"; } else { $userattempts[] = "id&attempt=$attempt->id\">$attemptgrade"; } } 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 = $bestgrade; $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 ($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($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 ($question->answer) { foreach ($question->answer as $questionanswer) { if ($questionanswer == $answer->id) { if ($answer->single) { $grade = (float)$answer->fraction * $answer->grade; $response[$answer->id] = true; continue; } else { $grade += (float)$answer->fraction * $answer->grade; $response[$answer->id] = true; } } } } } break; case RANDOM: // Not done yet 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; } ?>