<?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("RANDOMSAMATCH", "6");
define("DESCRIPTION",   "7");

$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"),
                              DESCRIPTION   => get_string("description", "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_MAX_HEIGHT", "600");   // Not currently implemented
define("QUIZ_PICTURE_MAX_WIDTH",  "600");   // Not currently implemented

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) {
                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) {
                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, q.questiontext, 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 "<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, $editlink=true) {
// Prints a question icon

    global $QUIZ_QUESTION_TYPE;

    if ($editlink) {
        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 MATCH:
            echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/ma.gif\">";
            break;
        case RANDOMSAMATCH:
            echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rm.gif\">";
            break;
        case DESCRIPTION:
            echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/de.gif\">";
            break;
    }
    if ($editlink) {
        echo "</A>\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 ($question->qtype == DESCRIPTION) {  // Special case question - has no answers etc
        echo '<p align="center">';
        echo text_to_html($question->questiontext);
        if ($question->image) {
            print_file_picture($question->image, $courseid);
        }
        echo '</p>';
        return true;
    }

    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>";

    if (empty($realquestion)) { 
        $realquestion->id = $question->id;
    } else {    // Add a marker to connect this question to the actual random parent
        echo "<input type=\"hidden\" name=\"q{$realquestion->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);
           }
           if ($response) {
               $value = "VALUE=\"$response[0]\"";
           } else {
               $value = "";
           }
           echo "<P ALIGN=RIGHT>$stranswer: <INPUT TYPE=TEXT NAME=q$realquestion->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);
           }

           $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:&nbsp;&nbsp;";
           echo "<TD $truecorrect>";
           echo "<INPUT $truechecked TYPE=RADIO NAME=\"q$realquestion->id\" VALUE=\"$true->id\">$true->answer";
           echo "</TD><TD $falsecorrect>";
           echo "<INPUT $falsechecked TYPE=RADIO NAME=\"q$realquestion->id\" VALUE=\"$false->id\">$false->answer";
           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);
           }
           echo "<TABLE ALIGN=right>";
           echo "<TR><TD valign=top>$stranswer:&nbsp;&nbsp;</TD><TD>";
           echo "<TABLE>";
           $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 "<TR><TD valign=top>";
               if ($options->single) {
                   echo "<INPUT $checked TYPE=RADIO NAME=q$realquestion->id VALUE=\"$answer->id\">";
               } else {
                   echo "<INPUT $checked TYPE=CHECKBOX NAME=q$realquestion->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>&nbsp;";
                   if (!empty($response[$answerid])) {
                       quiz_print_comment($feedback[$answerid]);
                   }
                   echo "</TD>";
               }
               echo "</TR>";
           }
           echo "</TABLE>";
           echo "</TABLE>";
           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);
           }

           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 "<table border=0 cellpadding=10 align=right>";
           foreach ($subquestions as $key => $subquestion) {
               echo "<tr><td align=left valign=top>";
               echo $subquestion->questiontext;
               echo "</td>";
               if (empty($response)) {
                   echo "<td align=right valign=top>";
                   choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id");
               } else {
                   if (empty($response[$key])) {
                       echo "<td align=right valign=top>";
                       choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id");
                   } else {
                       if ($response[$key] == $correct[$key]) {
                           echo "<td align=right valign=top class=highlight>";
                           choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]);
                       } else {
                           echo "<td align=right valign=top>";
                           choose_from_menu($answers, "q$realquestion->id"."r$subquestion->id", $response[$key]);
                       }
                   }

                   if (!empty($feedback[$key])) {
                       quiz_print_comment($feedback[$key]);
                   }
               }
               echo "</td></tr>";
           }
           echo "</table>";

           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);
           }

           /// 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$realquestion->id"."r$randomquestion->id");
               } else {
                   if (!empty($correct[$key])) {
                       if ($randomanswers[$responseanswer[$key]] == $correct[$key]) {
                           echo "<span=highlight>";
                           choose_from_menu($randomanswers, "q$realquestion->id"."r$randomquestion->id", $responseanswer[$key]);
                           echo "</span><br \>";
                       } 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 "</td></tr>";
           }
           echo "</table>";
           break;

       case RANDOM:
           echo "<P>Random questions should not be printed this way!</P>";
           break;

       default: 
           notify("Error: Unknown question type!");
    }

    echo "</TD></TR></TABLE>";
}



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 (!$shuffleorder) {
        if (!empty($quiz->shufflequestions)) {              // Mix everything up
            $questions = swapshuffle_assoc($questions);
        } else {
            $shuffleorder = explode(",", $quiz->questions);  // Use originally defined order
        }
    }

    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, $quiz->questions)) {
                    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 "<FORM METHOD=POST ACTION=attempt.php onsubmit=\"return confirm('$strconfirmattempt');\">";
    echo "<INPUT TYPE=hidden NAME=q VALUE=\"$quiz->id\">";

    $count = 0;
    $questionorder = array();

    foreach ($questions as $question) {

        if ($question->qtype != DESCRIPTION) {    // Description questions are not counted
            $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 "<br \>";
    }

    if (empty($results)) {
        if (!empty($quiz->shufflequestions)) {  // Things have been mixed up, so pass the question order
            $shuffleorder = implode(',', $questionorder);
            echo "<input type=hidden name=shuffleorder value=\"$shuffleorder\">\n";
        }
        echo "<center><input type=submit value=\"".get_string("savemyanswers", "quiz")."\"></center>";
    }
    echo "</form>";

    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 "<TABLE width=\"100%\"><TR><TD NOWRAP>";
    echo "<FORM METHOD=POST ACTION=edit.php>"; 
    echo "<B>$strcategory:</B>&nbsp;";
    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_choose_random_questions($category, $draws, $excluded=0) {
/// 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 id NOT IN ($excluded) 
                                        AND qtype <> ".RANDOM." 
                                        AND qtype <> ".DESCRIPTION, 
                                        "", "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 "<P align=center>";
        print_string("noquestions", "quiz");
        echo "</P>";
        return;
    }

    $order = explode(",", $questionlist);

    if (!$questions = get_records_list("quiz_questions", "id", $questionlist)) {
        echo "<P align=center>";
        print_string("noquestions", "quiz");
        echo "</P>";
        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 "<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;
        }
        $question = $questions[$qnum];
        $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>$question->name</TD>";
        echo "<TD ALIGN=CENTER>";
        quiz_print_question_icon($question);
        echo "</TD>";
        echo "<TD>";
        if ($question->qtype == DESCRIPTION) {
            echo "<INPUT TYPE=hidden NAME=q$qnum VALUE=\"0\"> ";
        } else {
            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>&nbsp;";
            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");
    $strcreatemultiple = get_string("createmultiple", "quiz");

    if (!$categoryid) {
        echo "<p align=center><b>";
        print_string("selectcategoryabove", "quiz");
        echo "</b></p>";
        echo "<p>";
        print_string("addingquestions", "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 "<FORM METHOD=GET ACTION=multiple.php>"; 
    echo "<INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
    echo "<INPUT TYPE=submit VALUE=\"$strcreatemultiple\">";
    helpbutton("createmultiple", $strcreatemultiple, "quiz");
    echo "</FORM>";

    echo "</TR></TABLE>";

    echo "</CENTER>";

    if (!$questions = get_records("quiz_questions", "category", $category->id, "qtype ASC")) {
        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>&nbsp;";
                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->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 ($question->qtype != DESCRIPTION) {    // All real questions need answers defined
            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->answer   = get_string("true", "quiz");
                $true->fraction = $question->answer;
                $true->feedback = $question->feedbacktrue;
                if (!update_record("quiz_answers", $true)) {
                    $result->error = "Could not update quiz answer \"true\")!";
                    return $result;
                }
            } else {
                unset($true);
                $true->answer   = get_string("true", "quiz");
                $true->question = $question->id;
                $true->fraction = $question->answer;
                $true->feedback = $question->feedbacktrue;
                if (!$true->id = insert_record("quiz_answers", $true)) {
                    $result->error = "Could not insert quiz answer \"true\")!";
                    return $result;
                }
            }

            if ($false = array_shift($oldanswers)) {  // Existing answer, so reuse it
                $false->answer   = get_string("false", "quiz");
                $false->fraction = 1 - (int)$question->answer;
                $false->feedback = $question->feedbackfalse;
                if (!update_record("quiz_answers", $false)) {
                    $result->error = "Could not insert quiz answer \"false\")!";
                    return $result;
                }
            } else {
                unset($false);
                $false->answer   = get_string("false", "quiz");
                $false->question = $question->id;
                $false->fraction = 1 - (int)$question->answer;
                $false->feedback = $question->feedbackfalse;
                if (!$false->id = insert_record("quiz_answers", $false)) {
                    $result->error = "Could not insert quiz answer \"false\")!";
                    return $result;
                }
            }

            if ($options = get_record("quiz_truefalse", "question", $question->id)) {
                // No need to do anything, since the answer IDs won't have changed
                // But we'll do it anyway, just for robustness
                $options->trueanswer  = $true->id;
                $options->falseanswer = $false->id;
                if (!update_record("quiz_truefalse", $options)) {
                    $result->error = "Could not update quiz truefalse options! (id=$options->id)";
                    return $result;
                }
            } else {
                unset($options);
                $options->question    = $question->id;
                $options->trueanswer  = $true->id;
                $options->falseanswer = $false->id;
                if (!insert_record("quiz_truefalse", $options)) {
                    $result->error = "Could not insert quiz truefalse options!";
                    return $result;
                }
            }
        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;

        case DESCRIPTION:
        break;

        default:
            $result->error = "Unsupported question type ($question->qtype)!";
            return $result;
        break;
    }
    return true;
}




?>
