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("RANDOMSAMATCH", "6"); $QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"), TRUEFALSE => get_string("truefalse", "quiz"), SHORTANSWER => get_string("shortanswer", "quiz"), MATCH => get_string("match", "quiz"), RANDOM => get_string("random", "quiz"), RANDOMSAMATCH => get_string("randomsamatch", "quiz") ); $QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"), "missingword" => get_string("missingword", "quiz"), "blackboard" => get_string("blackboard", "quiz"), "aon" => "AON" ); define("QUIZ_PICTURE_DEFAULT_HEIGHT", "200"); define("QUIZ_MAX_NUMBER_ANSWERS", "8"); /// 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_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_random_categories($questionlist) { /// Given an array of questions, this function looks for random /// questions among them and returns a list of categories with /// an associated count of random questions for each. global $CFG; return get_records_sql_menu("SELECT category,count(*) FROM {$CFG->prefix}quiz_questions WHERE id IN ($questionlist) AND qtype = '".RANDOM."' GROUP BY category "); } 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 for a given question global $CFG; 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 "); break; case TRUEFALSE: // Should be always two answers return get_records("quiz_answers", "question", $question->id); break; 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 "); break; case MATCH: return get_records("quiz_match_sub", "question", $question->id); break; 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 "); break; default: return false; } } function quiz_get_attempt_responses($attempt, $quiz) { // 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) { if ($response->qtype == RANDOM) { $responses[$key]->random = $response->answer; $responses[$key]->answer = explode(",",$responses[$response->answer]->answer); $responses[$response->answer]->delete = true; } else { $responses[$key]->answer = explode(",",$response->answer); } } foreach ($responses as $key => $response) { if (!empty($response->delete)) { unset($responses[$key]); } } 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 "".text_to_html($text, true, false).""; } function quiz_print_correctanswer($text) { global $THEME; echo "

$text

"; } function quiz_print_question_icon($question, $editlink=true) { // Prints a question icon global $QUIZ_QUESTION_TYPE; if ($editlink) { 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; case MATCH: echo ""; break; case RANDOMSAMATCH: echo ""; break; } if ($editlink) { echo "\n"; } } function quiz_print_question($number, $question, $grade, $courseid, $feedback=NULL, $response=NULL, $actualgrade=NULL, $correct=NULL, $realquestion=NULL, $shuffleanswers=false) { /// Prints a quiz question, any format /// $question is provided as an object 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 "
"; if (empty($realquestion)) { $realquestion->id = $question->id; } else { // Add a marker to connect this question to the actual random parent echo "id}rq$question->id\" value=\"x\">\n"; } 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 "

$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, 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 "
$stranswer:  "; echo ""; echo "id\" VALUE=\"$true->id\">$true->answer"; echo ""; echo "id\" VALUE=\"$false->id\">$false->answer"; 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, QUIZ_PICTURE_DEFAULT_HEIGHT); } echo ""; echo "
$stranswer:  "; echo ""; $answerids = explode(",", $options->answers); if ($shuffleanswers) { $answerids = swapshuffle($answerids); } 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 ""; if (empty($feedback) or empty($correct[$answer->id])) { echo ""; } else { echo ""; } if (!empty($feedback)) { echo ""; } echo ""; } echo "
"; if ($options->single) { echo "id VALUE=\"$answer->id\">"; } else { echo "id"."a$answer->id VALUE=\"$answer->id\">"; } echo "$qnumchar. $answer->answer$qnumchar. $answer->answer "; if (!empty($response[$answerid])) { quiz_print_comment($feedback[$answerid]); } echo "
"; echo "
"; break; case MATCH: if (!$options = get_record("quiz_match", "question", $question->id)) { notify("Error: Missing question options!"); } if (!$subquestions = get_records_list("quiz_match_sub", "id", $options->subquestions)) { notify("Error: Missing subquestions for this question!"); } if (!empty($question->questiontext)) { echo text_to_html($question->questiontext); } if (!empty($question->image)) { print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT); } if ($shuffleanswers) { $subquestions = draw_rand_array($subquestions, count($subquestions)); } foreach ($subquestions as $subquestion) { $answers[$subquestion->id] = $subquestion->answertext; } $answers = draw_rand_array($answers, count($answers)); echo ""; foreach ($subquestions as $key => $subquestion) { echo ""; if (empty($response)) { echo ""; } echo "
"; echo $subquestion->questiontext; echo ""; choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id"); } else { if (empty($response[$key])) { echo ""; choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id"); } else { if ($response[$key] == $correct[$key]) { echo ""; choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]); } else { echo ""; choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]); } } if (!empty($feedback[$key])) { quiz_print_comment($feedback[$key]); } } echo "
"; break; case RANDOMSAMATCH: if (!$options = get_record("quiz_randomsamatch", "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 ""; foreach ($randomquestions as $key => $randomquestion) { echo ""; echo ""; } echo "
"; echo $randomquestion->questiontext; echo ""; if (empty($response)) { choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id"); } else { if (!empty($correct[$key])) { if ($randomanswers[$responseanswer[$key]] == $correct[$key]) { echo ""; choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]); echo "
"; } else { choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]); quiz_print_correctanswer($correct[$key]); } } else { choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]); } if (!empty($feedback[$key])) { quiz_print_comment($feedback[$key]); } } echo "
"; break; case RANDOM: echo "

Random questions should not be printed this way!

"; break; default: notify("Error: Unknown question type!"); } echo "
"; } function quiz_print_quiz_questions($quiz, $results=NULL, $questions=NULL, $shuffleorder=NULL) { // Prints a whole quiz on one page. /// Get the questions if (!$questions) { if (empty($quiz->questions)) { notify("No questions have been defined!"); return false; } if (!$questions = get_records_list("quiz_questions", "id", $quiz->questions, "")) { notify("Error when reading questions from the database!"); return false; } if (!empty($quiz->shufflequestions)) { // Mix everything up $questions = swapshuffle_assoc($questions); } } if ($shuffleorder) { // Order has been defined, so reorder questions $oldquestions = $questions; $questions = array(); foreach ($shuffleorder as $key) { $questions[] = $oldquestions[$key]; // This loses the index key, but doesn't matter } } if (!$grades = get_records_list("quiz_question_grades", "question", $quiz->questions, "", "question,grade")) { notify("No grades were found for these questions!"); return false; } /// Examine the set of questions for random questions, and retrieve them if (empty($results)) { // Choose some new random questions if ($randomcats = quiz_get_random_categories($quiz->questions)) { foreach ($randomcats as $randomcat => $randomdraw) { /// Get the appropriate amount of random questions from this category if (!$catquestions[$randomcat] = quiz_choose_random_questions($randomcat, $randomdraw)) { notify(get_string("toomanyrandom", "quiz", $randomcat)); return false; } } } } else { // Get the previously chosen questions $chosen = array(); foreach ($questions as $question) { if (isset($question->random)) { $chosen[] = $question->random; } } if ($chosen) { $chosenlist = implode(",", $chosen); if (!$chosen = get_records_list("quiz_questions", "id", $chosenlist, "")) { notify("Error when reading questions from the database!"); return false; } } } $strconfirmattempt = addslashes(get_string("readytosend", "quiz")); echo "
"; echo "id\">"; $count = 0; $questionorder = array(); foreach ($questions as $question) { $count++; $questionorder[] = $question->id; $feedback = NULL; $response = NULL; $actualgrades = NULL; $correct = NULL; $randomquestion = NULL; if (empty($results)) { if ($question->qtype == RANDOM ) { // Set up random questions $randomquestion = $question; $question = array_pop($catquestions[$randomquestion->category]); $grades[$question->id]->grade = $grades[$randomquestion->id]->grade; } } else { if (!empty($results->feedback[$question->id])) { $feedback = $results->feedback[$question->id]; } if (!empty($results->response[$question->id])) { $response = $results->response[$question->id]; } if (!empty($results->grades[$question->id])) { $actualgrades = $results->grades[$question->id]; } if ($quiz->correctanswers) { if (!empty($results->correct[$question->id])) { $correct = $results->correct[$question->id]; } } if (!empty($question->random)) { $randomquestion = $question; $question = $chosen[$question->random]; $grades[$question->id]->grade = $grades[$randomquestion->id]->grade; } } print_simple_box_start("CENTER", "90%"); quiz_print_question($count, $question, $grades[$question->id]->grade, $quiz->course, $feedback, $response, $actualgrades, $correct, $randomquestion, $quiz->shuffleanswers); print_simple_box_end(); echo "
"; } if (empty($results)) { if (!empty($quiz->shufflequestions)) { // Things have been mixed up, so pass the question order $shuffleorder = implode(',', $questionorder); echo "\n"; } echo "
"; } echo "
"; return true; } function quiz_get_default_category($courseid) { /// Returns the current category 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) { /// Returns the list of categories $publish = ""; 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 "
"; echo "
"; echo "$strcategory: "; choose_from_menu($catmenu, "cat", "$current"); echo ""; echo "
"; echo "
"; echo "
"; echo "id\">"; echo ""; echo "
"; echo "
"; } function quiz_choose_random_questions($category, $draws) { /// Given a question category and a number of draws, this function /// creates a random subset of that size - returned as an array of questions if (!$pool = get_records_select_menu("quiz_questions", "category = '$category' AND qtype <> ".RANDOM, "", "id,qtype")) { return false; } $countpool = count($pool); if ($countpool == $draws) { $chosen = $pool; } else if ($countpool < $draws) { return false; } else { $chosen = draw_rand_array($pool, $draws); } $chosenlist = implode(",", array_keys($chosen)); return get_records_list("quiz_questions", "id", $chosenlist); } 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 if (empty($questionlist)) { return array(); } $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 "

"; print_string("noquestions", "quiz"); echo "

"; return; } $order = explode(",", $questionlist); if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) { echo "

"; print_string("noquestions", "quiz"); echo "

"; return; } $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 "
"; echo ""; echo ""; foreach ($order as $qnum) { if (empty($questions[$qnum])) { continue; } $count++; echo "cellcontent\">"; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; $sumgrade += $grades[$qnum]; } echo ""; echo "
$strorder$strquestionname$strtype$strgrade$stredit
$count"; if ($count != 1) { echo ""; } echo ""; if ($count != $total) { echo ""; } echo "".$questions[$qnum]->name.""; quiz_print_question_icon($questions[$qnum]); echo ""; choose_from_menu($gradesmenu, "q$qnum", (string)$grades[$qnum], ""); echo ""; echo " "; echo ""; echo "
"; echo ""; echo ""; echo "cellcontent\">"; echo "$sumgrade"; echo "
"; 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"); $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"); $strcreatemultiple = get_string("createmultiple", "quiz"); if (!$categoryid) { echo "

"; print_string("selectcategoryabove", "quiz"); echo "

"; return; } if (!$category = get_record("quiz_categories", "id", "$categoryid")) { notify("Category not found!"); return; } echo "
"; echo text_to_html($category->info); echo ""; echo ""; echo "
$straddquestions:"; echo "
"; choose_from_menu($QUIZ_QUESTION_TYPE, "qtype", "", ""); echo "id\">"; echo ""; helpbutton("questiontypes", $strcreatenewquestion, "quiz"); echo "
"; echo "
"; echo "id\">"; echo ""; helpbutton("import", $strimportquestions, "quiz"); echo "
"; echo "
"; echo "id\">"; echo ""; helpbutton("createmultiple", $strcreatemultiple, "quiz"); echo "
"; echo "
"; echo "
"; if (!$questions = get_records("quiz_questions", "category", $category->id, "qtype ASC")) { echo "

"; print_string("noquestions", "quiz"); echo "

"; return; } $canedit = isteacher($category->course); echo "
"; echo ""; echo ""; if ($canedit) { echo ""; } echo ""; foreach ($questions as $question) { echo "cellcontent\">"; echo ""; echo ""; echo ""; if ($canedit) { echo ""; } echo ""; } echo ""; echo "
$strselect$strquestionname$strtype$stredit
"; echo "id VALUE=\"1\">"; echo "".$question->name.""; quiz_print_question_icon($question); echo ""; echo "id&delete=$question->id\"> "; echo "id\">"; echo "
"; echo ""; //echo ""; echo ""; echo "
"; 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 = 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->random)) { // First save the response of the random question // the answer is the id of the REAL response $response->answer = $question->random; if (!insert_record("quiz_responses", $response)) { notify("Error while saving response"); return false; } $response->question = $question->random; } 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!"); } 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) { if (!empty($question->random)) { // This question has been randomly chosen $randomquestion = $question; // Save it for later if (!$question = get_record("quiz_questions", "id", $question->random)) { error("Could not find the real question behind this random question!"); } if (isset($randomquestion->answer)) { $question->answer = $randomquestion->answer; } else { $question->answer = ""; } $question->grade = $grades[$randomquestion->id]; } else { $question->grade = $grades[$question->id]; } 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 * $question->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 * $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; } if (!empty($randomquestion)) { // This question has been randomly chosen $question = $randomquestion; // Restore the question->id unset($randomquestion); } 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 /// /// If this is an update, and old answers already exist, then /// these are overwritten using an update(). To do this, it /// it is assumed that the IDs in quiz_answers are in the same /// sort order as the new answers being saved. This should always /// be true, but it's something to keep in mind if fiddling with /// question.php /// /// Returns $result->error or $result->notice switch ($question->qtype) { case SHORTANSWER: 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_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->notice = get_string("fractionsnomax", "quiz", $maxfraction); return $result; } break; case TRUEFALSE: if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) { $oldanswers = array(); } if ($true = array_shift($oldanswers)) { // Existing answer, so reuse it $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->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; } } break; case MULTICHOICE: if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) { $oldanswers = array(); } $totalfraction = 0; $maxfraction = -1; $answers = array(); // Insert all the new answers 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->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 MATCH: if (!$oldsubquestions = get_records("quiz_match_sub", "question", $question->id, "id ASC")) { $oldsubquestions = array(); } $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->notice = 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; } } break; case RANDOMSAMATCH: $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; } } break; case RANDOM: break; default: $result->error = "Unsupported question type ($question->qtype)!"; return $result; break; } return true; } ?>