From 212b7b8cd9af682fc32c76d1bfd6172ead62a27c Mon Sep 17 00:00:00 2001 From: tjhunt Date: Tue, 22 Aug 2006 17:31:26 +0000 Subject: [PATCH] MDL-5270 - give students a feeback comment for their performance in the entire quiz. Also, along the way I noticed and fixed MDL-6290 student grades not rescaled when quiz maximum grade changed. --- lang/en_utf8/help/quiz/overallfeedback.html | 14 + lang/en_utf8/quiz.php | 8 + mod/quiz/backuplib.php | 59 ++- mod/quiz/db/mysql.php | 19 + mod/quiz/db/mysql.sql | 10 + mod/quiz/db/postgres7.php | 18 + mod/quiz/db/postgres7.sql | 11 +- mod/quiz/edit.php | 7 +- mod/quiz/index.php | 9 +- mod/quiz/lib.php | 110 ++++- mod/quiz/locallib.php | 154 ++++++- mod/quiz/mod.html | 77 +++- mod/quiz/report/overview/report.php | 29 +- mod/quiz/restorelib.php | 54 ++- mod/quiz/review.php | 8 +- mod/quiz/version.php | 2 +- mod/quiz/view.php | 456 ++++++++++---------- 17 files changed, 744 insertions(+), 301 deletions(-) create mode 100644 lang/en_utf8/help/quiz/overallfeedback.html diff --git a/lang/en_utf8/help/quiz/overallfeedback.html b/lang/en_utf8/help/quiz/overallfeedback.html new file mode 100644 index 0000000000..5f8af1b65a --- /dev/null +++ b/lang/en_utf8/help/quiz/overallfeedback.html @@ -0,0 +1,14 @@ +

Overall feedback

+ +

The overall feedback is some text that is shown to a student after +they have completed an attempt at the quiz. The text that is shown +can depend on the grade the student got.

+ +

For example, if you type "Well done" into the first feedback box, type 40% +in the first grade boundary box, and type "Please study this week's work again" +in the second feedback box, then students who score 40% or better will see the +"Well done" message, and students who score less than 40% will see the other message.

+ +

The grade boundaries can be specified either as a percentage, for example "31.41%", or +as a number, for example "7". If your quiz is out of 10 marks, a grade boundary of 7 means +7/10 or better.

\ No newline at end of file diff --git a/lang/en_utf8/quiz.php b/lang/en_utf8/quiz.php index 38162bca45..423f5569fe 100644 --- a/lang/en_utf8/quiz.php +++ b/lang/en_utf8/quiz.php @@ -180,6 +180,11 @@ $string['exportnameformat'] = '%%Y%%m%%d-%%H%%M'; $string['exportquestions'] = 'Export questions to file'; $string['false'] = 'False'; $string['feedback'] = 'Feedback'; +$string['feedbackerrorboundaryformat'] = 'Feedback grade boundaries must be either a percentage or a number. The value you entered in boundary $a is not recognised.'; +$string['feedbackerrorboundaryoutofrange'] = 'Feedback grade boundaries must be between 0%% and 100%%. The value you entered in boundary $a is out of range.'; +$string['feedbackerrorjunkinboundary'] = 'You must fill in the feedback boxes without leaving any gaps.'; +$string['feedbackerrorjunkinfeedback'] = 'You must fill in the feedback boxes without leaving any gaps.'; +$string['feedbackerrororder'] = 'Feedback grade boundaries must in order, highest first. The value you entered in boundary $a is out of sequence.'; $string['file'] = 'File'; $string['fileformat'] = 'File format'; $string['fillcorrect'] = 'Fill with correct'; @@ -203,6 +208,7 @@ $string['geometric'] = 'Geometric'; $string['gift'] = 'GIFT format'; $string['grade'] = 'Grade'; $string['gradeaverage'] = 'Average grade'; +$string['gradeboundary'] = 'Grade boundary'; $string['gradehighest'] = 'Highest grade'; $string['grademethod'] = 'Grading method'; $string['gradingdetails'] = 'Marks for this submission: $a->raw/$a->max. '; @@ -320,6 +326,7 @@ $string['onlyteachersimport'] = 'Only teachers with editing rights can import qu $string['onlyteachersexport'] = 'Only teachers can export questions'; $string['optional'] = 'optional'; $string['outof'] = '$a->grade out of a maximum of $a->maxgrade'; +$string['overallfeedback'] = 'Overall feedback'; $string['overdue'] = 'Overdue'; $string['pagesize'] = 'Attempts shown per page: '; $string['paragraphquestion'] = 'Paragraph Question not supported at line $a. The question will be ignored'; @@ -361,6 +368,7 @@ $string['quizcloses'] = 'Quiz closes'; $string['quiznotavailable'] = 'The quiz will not be available until: $a'; $string['quizopen'] = 'Open the quiz'; $string['quizopens'] = 'Quiz opens'; +$string['quizsettings'] = 'Quiz settings'; $string['quiztimelimit'] = 'Time limit: $a'; $string['quiztimer'] = 'Quiz Timer'; $string['pleaseclose'] = 'Your request has been processed. You can now close this window'; diff --git a/mod/quiz/backuplib.php b/mod/quiz/backuplib.php index 5dd69b3267..19b91502ac 100644 --- a/mod/quiz/backuplib.php +++ b/mod/quiz/backuplib.php @@ -9,12 +9,12 @@ // (CL,pk->id) // | // ------------------------------------------------------------------- - // | | | | - // | quiz_grades | quiz_question_versions - // | (UL,pk->id,fk->quiz) | (CL,pk->id,fk->quiz) - // | | - // quiz_attempts quiz_question_instances - // (UL,pk->id,fk->quiz) (CL,pk->id,fk->quiz,question) + // | | | | | + // | quiz_grades | quiz_question_versions | + // | (UL,pk->id,fk->quiz) | (CL,pk->id,fk->quiz) | + // | | | + // quiz_attempts quiz_question_instances quiz_feedback + // (UL,pk->id,fk->quiz) (CL,pk->id,fk->quiz,question) (CL,pk->id,fk->quiz) // // Meaning: pk->primary key field of the table // fk->foreign key to link with parent @@ -204,6 +204,8 @@ fwrite ($bf,full_tag("DELAY2",4,false,$quiz->delay2)); //Now we print to xml question_instances (Course Level) $status = backup_quiz_question_instances($bf,$preferences,$quiz->id); + //Now we print to xml quiz_feedback (Course Level) + $status = backup_quiz_feedback($bf,$preferences,$quiz->id); //Now we print to xml question_versions (Course Level) $status = backup_quiz_question_versions($bf,$preferences,$quiz->id); //if we've selected to backup users info, then execute: @@ -242,9 +244,6 @@ //Backup quiz_question_instances contents (executed from quiz_backup_mods) function backup_quiz_question_instances ($bf,$preferences,$quiz) { - - global $CFG; - $status = true; $quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id"); @@ -269,11 +268,41 @@ return $status; } + //Backup quiz_question_instances contents (executed from quiz_backup_mods) + function backup_quiz_feedback ($bf,$preferences,$quiz) { + $status = true; + + $quiz_feedback = get_records('quiz_feedback', 'quizid', $quiz, 'id'); + // If there are question_instances ... + if ($quiz_feedback) { + // Write start tag. + $status = $status & fwrite($bf,start_tag('FEEDBACKS', 4, true)); + + // Iterate over each question_instance. + foreach ($quiz_feedback as $feedback) { + + //Start feedback instance + $status = $status & fwrite($bf, start_tag('FEEDBACK',5,true)); + + //Print question_instance contents. + $status = $status & fwrite($bf, full_tag('ID', 6, false, $feedback->id)); + $status = $status & fwrite($bf, full_tag('QUIZID', 6, false, $feedback->quizid)); + $status = $status & fwrite($bf, full_tag('FEEDBACKTEXT', 6, false, $feedback->feedbacktext)); + $status = $status & fwrite($bf, full_tag('MINGRADE', 6, false, $feedback->mingrade)); + $status = $status & fwrite($bf, full_tag('MAXGRADE', 6, false, $feedback->maxgrade)); + + // End feedback instance. + $status = $status & fwrite($bf, end_tag('FEEDBACK', 5, true)); + } + + // Write end tag. + $status = $status & fwrite($bf, end_tag('FEEDBACKS', 4, true)); + } + return $status; + } + //Backup quiz_question_versions contents (executed from quiz_backup_mods) function backup_quiz_question_versions ($bf,$preferences,$quiz) { - - global $CFG; - $status = true; $quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id"); @@ -304,9 +333,6 @@ //Backup quiz_grades contents (executed from quiz_backup_mods) function backup_quiz_grades ($bf,$preferences,$quiz) { - - global $CFG; - $status = true; $quiz_grades = get_records("quiz_grades","quiz",$quiz,"id"); @@ -334,9 +360,6 @@ //Backup quiz_attempts contents (executed from quiz_backup_mods) function backup_quiz_attempts ($bf,$preferences,$quiz) { - - global $CFG; - $status = true; $quiz_attempts = get_records("quiz_attempts","quiz",$quiz,"id"); diff --git a/mod/quiz/db/mysql.php b/mod/quiz/db/mysql.php index e801cec6df..dec47e95f7 100644 --- a/mod/quiz/db/mysql.php +++ b/mod/quiz/db/mysql.php @@ -1113,6 +1113,25 @@ function quiz_upgrade($oldversion) { (($CFG->quiz_review & QUIZ_REVIEW_FEEDBACK) << 3)); } + if ($success && $oldversion < 2006081400) { + $success = $success && modify_database('', " + CREATE TABLE prefix_quiz_feedback ( + id int(10) unsigned NOT NULL auto_increment, + quizid int(10) unsigned NOT NULL default '0', + feedbacktext text NOT NULL default '', + mingrade double NOT NULL default '0', + maxgrade double NOT NULL default '0', + PRIMARY KEY (id), + KEY quizid (quizid) + ) TYPE=MyISAM COMMENT='Feedback given to students based on their overall score on the test'; + "); + + $success = $success && execute_sql(" + INSERT INTO {$CFG->prefix}quiz_feedback (quizid, feedbacktext, maxgrade, mingrade) + SELECT id, '', grade + 1, 0 FROM {$CFG->prefix}quiz; + "); + } + return $success; } diff --git a/mod/quiz/db/mysql.sql b/mod/quiz/db/mysql.sql index 9d603a85fd..3db85ed754 100644 --- a/mod/quiz/db/mysql.sql +++ b/mod/quiz/db/mysql.sql @@ -62,6 +62,16 @@ CREATE TABLE prefix_quiz_question_versions ( PRIMARY KEY (id) ) TYPE=MyISAM COMMENT='The mapping between old and new versions of a question'; +CREATE TABLE prefix_quiz_feedback ( + id int(10) unsigned NOT NULL auto_increment, + quizid int(10) unsigned NOT NULL default '0', + feedbacktext text NOT NULL default '', + mingrade double NOT NULL default '0', + maxgrade double NOT NULL default '0', + PRIMARY KEY (id), + KEY quizid (quizid), +) TYPE=MyISAM COMMENT='Feedback given to students based on their overall score on the test'; + -- -------------------------------------------------------- -- Quiz module, quiz runtime data. -- -------------------------------------------------------- diff --git a/mod/quiz/db/postgres7.php b/mod/quiz/db/postgres7.php index b02028342e..aeb160af8d 100644 --- a/mod/quiz/db/postgres7.php +++ b/mod/quiz/db/postgres7.php @@ -1435,6 +1435,24 @@ function quiz_upgrade($oldversion) { (($CFG->quiz_review & QUIZ_REVIEW_FEEDBACK) << 3)); } + if ($success && $oldversion < 2006081400) { + $success = $success && modify_database('', " + CREATE TABLE prefix_quiz_feedback ( + id SERIAL PRIMARY KEY, + quizid integer NOT NULL default '0', + feedbacktext text NOT NULL default '', + maxgrade real NOT NULL default '0', + mingrade real NOT NULL default '0' + ); + "); + $success = $success && modify_database('', + "CREATE INDEX prefix_quiz_feedback_quizid_idx ON prefix_quiz_feedback (quizid);"); + + $success = $success && execute_sql(" + INSERT INTO {$CFG->prefix}quiz_feedback (quizid, feedbacktext, maxgrade, mingrade) + SELECT id, '', grade + 1, 0 FROM {$CFG->prefix}quiz; + "); + } return $success; } diff --git a/mod/quiz/db/postgres7.sql b/mod/quiz/db/postgres7.sql index 9f7528b292..5f43a8fa26 100644 --- a/mod/quiz/db/postgres7.sql +++ b/mod/quiz/db/postgres7.sql @@ -59,6 +59,15 @@ CREATE TABLE prefix_quiz_question_versions ( timestamp integer NOT NULL default '0' ); +CREATE TABLE prefix_quiz_feedback ( + id SERIAL PRIMARY KEY, + quizid integer NOT NULL default '0', + feedbacktext text NOT NULL default '', + mingrade real NOT NULL default '0', + maxgrade real NOT NULL default '0' +); +CREATE INDEX prefix_quiz_feedback_quizid_idx ON prefix_quiz_feedback (quizid); + -- -------------------------------------------------------- -- Quiz module, quiz runtime data. -- -------------------------------------------------------- @@ -207,7 +216,7 @@ CREATE TABLE prefix_question_states ( penalty real NOT NULL default '0' ); CREATE INDEX prefix_question_states_attempt_idx ON prefix_question_states (attempt); -CREATE INDEX prefix_question_states_question_idx ON prefix_question_states (question);; +CREATE INDEX prefix_question_states_question_idx ON prefix_question_states (question); -- -------------------------------------------------------- -- Quiz log actions. diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 5109cafe89..e2d0d0db93 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -274,9 +274,8 @@ if (self.name == 'editquestion') { // If rescaling is required save the new maximum if (isset($_REQUEST['maxgrade'])) { - $modform->grade = optional_param('maxgrade', 0); - if (!set_field('quiz', 'grade', $modform->grade, 'id', $modform->instance)) { - error('Could not set new maximal grade for quiz'); + if (!quiz_set_grade(optional_param('maxgrade', 0), $modform)) { + error('Could not set a new maximum grade for the quiz'); } } } @@ -308,7 +307,7 @@ if (self.name == 'editquestion') { // Print basic page layout. - if (isset($modform->instance) and record_exists_sql("SELECT * FROM {$CFG->prefix}quiz_attempts WHERE quiz = '$modform->instance' AND preview = '0' LIMIT 1")){ + if (isset($modform->instance) and record_exists_select('quiz_attempts', "quiz = '$modform->instance' AND preview = '0'")){ // one column layout with table of questions used in this quiz $strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext) ? update_module_button($modform->cmid, $course->id, get_string('modulename', 'quiz')) diff --git a/mod/quiz/index.php b/mod/quiz/index.php index 33dce3e16e..3494c9b260 100644 --- a/mod/quiz/index.php +++ b/mod/quiz/index.php @@ -121,20 +121,23 @@ // or the quiz has no grade, display nothing in grade col if ($bestgrade === NULL || $quiz->grade == 0) { $gradecol = ""; + $feedbackcol = ''; } else { //If all quiz's attempts have visible results, show bestgrade if(all_attempt_results_visible($quiz, $USER)) { $gradecol = "$bestgrade / $quiz->grade"; + $feedbackcol = quiz_get_feedback($quiz, $bestgrade); } else { $gradecol = ""; + $feedbackcol = ''; } } } if ($course->format == "weeks" or $course->format == "topics") { - $table->data[] = array ($printsection, $link, $closequiz, $gradecol); + $table->data[] = array ($printsection, $link, $closequiz, $gradecol, $feedbackcol); } else { - $table->data[] = array ($link, $closequiz, $gradecol); + $table->data[] = array ($link, $closequiz, $gradecol, $feedbackcol); } } @@ -146,4 +149,4 @@ print_footer($course); -?> +?> \ No newline at end of file diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 976fa56170..0b38d223bb 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -58,14 +58,18 @@ define("QUIZ_MAX_EVENT_LENGTH", "432000"); // 5 days maximum * of the new instance. * * @param object $quiz the data that came from the form. - * @return integer the id of the new instance. + * @return mixed the id of the new instance on success, + * false or a string error message on failure. */ function quiz_add_instance($quiz) { // Process the options from the form. $quiz->created = time(); - quiz_process_options($quiz); $quiz->questions = ''; + $result = quiz_process_options($quiz); + if ($result && is_string($result)) { + return $result; + } // Try to store it in the database. if (!$quiz->id = insert_record("quiz", $quiz)) { @@ -84,12 +88,15 @@ function quiz_add_instance($quiz) { * will update an existing instance with new data. * * @param object $quiz the data that came from the form. - * @return boolean true on success, false on failure. + * @return mixed true on success, false or a string error message on failure. */ function quiz_update_instance($quiz) { // Process the options from the form. - quiz_process_options($quiz); + $result = quiz_process_options($quiz); + if ($result && is_string($result)) { + return $result; + } // Update the database. $quiz->id = $quiz->instance; @@ -120,7 +127,7 @@ function quiz_delete_instance($id) { if ($attempts = get_records("quiz_attempts", "quiz", "$quiz->id")) { foreach ($attempts as $attempt) { - // TODO: this should use function in questionlib.php + // TODO: this should use the delete_attempt($attempt->uniqueid) function in questionlib.php if (! delete_records("question_states", "attempt", "$attempt->uniqueid")) { $result = false; } @@ -130,20 +137,18 @@ function quiz_delete_instance($id) { } } - 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_instances", "quiz", "$quiz->id")) { - $result = false; - } - - if (! delete_records("quiz", "id", "$quiz->id")) { - $result = false; + $tables_to_purge = array( + 'quiz_attempts' => 'quiz', + 'quiz_grades' => 'quiz', + 'quiz_question_instances' => 'quiz', + 'quiz_grades' => 'quiz', + 'quiz_feedback' => 'quizid', + 'quiz' => 'id' + ); + foreach ($tables_to_purge as $table => $keyfield) { + if (!delete_records($table, $keyfield, $quiz->id)) { + $result = false; + } } $pagetypes = page_import_types('mod/quiz/'); @@ -495,7 +500,58 @@ function quiz_process_options(&$quiz) { $quiz->timelimit = 0; } $quiz->timelimit = round($quiz->timelimit); - + + // Quiz feedback + + // Clean up the boundary text. + for ($i = 0; $i < count($quiz->feedbacktext); $i += 1) { + if (empty($quiz->feedbacktext[$i])) { + $quiz->feedbacktext[$i] = ''; + } else { + $quiz->feedbacktext[$i] = trim($quiz->feedbacktext[$i]); + } + } + + // Check the boundary value is a number or a percentage, and in range. + $i = 0; + while (!empty($quiz->feedbackboundaries[$i])) { + $boundary = trim($quiz->feedbackboundaries[$i]); + if (!is_numeric($boundary)) { + if (strlen($boundary) > 0 && $boundary[strlen($boundary) - 1] == '%') { + $boundary = substr($boundary, 0, -1); + if (is_numeric($boundary)) { + $boundary = $boundary * $quiz->grade / 100.0; + } else { + return get_string('feedbackerrorboundaryformat', 'quiz', $i + 1); + } + } + } + if ($boundary <= 0 || $boundary >= $quiz->grade) { + return get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1); + } + if ($i > 0 && $boundary >= $quiz->feedbackboundaries[$i - 1]) { + return get_string('feedbackerrororder', 'quiz', $i + 1); + } + $quiz->feedbackboundaries[$i] = $boundary; + $i += 1; + } + $numboundaries = $i; + + // Check there is nothing in the remaining unused fields. + for ($i = $numboundaries; $i < count($quiz->feedbackboundaries); $i += 1) { + if (!empty($quiz->feedbackboundaries[$i]) && trim($quiz->feedbackboundaries[$i]) != '') { + return get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1); + } + } + for ($i = $numboundaries + 1; $i < count($quiz->feedbacktext); $i += 1) { + if (!empty($quiz->feedbacktext[$i]) && trim($quiz->feedbacktext[$i]) != '') { + return get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1); + } + } + $quiz->feedbackboundaries[-1] = $quiz->grade + 1; // Needs to be bigger than $quiz->grade because of '<' test in quiz_feedback_for_grade(). + $quiz->feedbackboundaries[$numboundaries] = 0; + $quiz->feedbackboundarycount = $numboundaries; + // Settings that get combined to go into the optionflags column. $quiz->optionflags = 0; if (!empty($quiz->adaptive)) { @@ -593,6 +649,20 @@ function quiz_process_options(&$quiz) { */ function quiz_after_add_or_update($quiz) { + // Save the feedback + delete_records('quiz_feedback', 'quizid', $quiz->id); + + for ($i = 0; $i <= $quiz->feedbackboundarycount; $i += 1) { + $feedback = new stdClass; + $feedback->quizid = $quiz->id; + $feedback->feedbacktext = $quiz->feedbacktext[$i]; + $feedback->mingrade = $quiz->feedbackboundaries[$i]; + $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1]; + if (!insert_record('quiz_feedback', $feedback, false)) { + return "Could not save quiz feedback."; + } + } + // Remember whether this user likes the advanced settings visible or hidden. if (isset($quiz->optionsettingspref)) { set_user_preference('quiz_optionsettingspref', $quiz->optionsettingspref); diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 241f1f31eb..d69f186f0c 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -83,10 +83,18 @@ function quiz_get_user_attempt_unfinished($quizid, $userid) { return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0); } +/** + * @param integer $quizid the quiz id. + * @param integer $userid the userid. + * @return an array of all the ueser's attempts at this quiz. Returns an empty array if there are none. + */ 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"); + if ($attempts = get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0", + "attempt ASC")) { + return $attempts; + } else { + return array(); + } } @@ -249,30 +257,132 @@ function quiz_get_all_question_grades($quiz) { return $grades; } - +/** + * Get the best current grade for a particular user in a quiz. + * + * @param object $quiz the quiz object. + * @param integer $userid the id of the user. + * @return float the user's current grade for this quiz. + */ function quiz_get_best_grade($quiz, $userid) { -/// Get the best current grade for a particular user in a quiz -if (!$grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', $userid)) { + $grade = get_field('quiz_grades', 'grade', 'quiz', $quiz->id, 'userid', $userid); + + // Need to detect errors/no result, without catching 0 scores. + if (is_numeric($grade)) { + return round($grade,$quiz->decimalpoints); + } else { return NULL; } +} - return (round($grade->grade,$quiz->decimalpoints)); +/** + * Convert the raw grade stored in $attempt into a grade out of the maximum + * grade for this quiz. + * + * @param float $rawgrade the unadjusted grade, fof example $attempt->sumgrades + * @param object $quiz the quiz object. Only the fields grade, sumgrades and decimalpoints are used. + * @return float the rescaled grade. + */ +function quiz_rescale_grade($rawgrade, $quiz) { + if ($quiz->sumgrades) { + return round($rawgrade*$quiz->grade/$quiz->sumgrades, $quiz->decimalpoints); + } else { + return 0; + } } /** -* Save the overall grade for a user at a quiz in the quiz_grades table -* -* @return boolean Indicates success or failure. -* @param object $quiz The quiz for which the best grade is to be calculated -* and then saved. -* @param integer $userid The id of the user to save the best grade for. Can be -* null in which case the current user is assumed. -*/ -function quiz_save_best_grade($quiz, $userid=null) { + * Get the feedback text that should be show to a student who + * got this grade on this quiz. + * + * @param float $grade a grade on this quiz. + * @param integer $quizid the id of the quiz object. + * @return string the comment that corresponds to this grade (empty string if there is not one. + */ +function quiz_feedback_for_grade($grade, $quizid) { + $feedback = get_field_select('quiz_feedback', 'feedbacktext', + "quizid = $quizid AND mingrade <= $grade AND $grade < maxgrade"); + + if (empty($feedback)) { + $feedback = ''; + } + + return $feedback; +} + +/** + * @param integer $quizid the id of the quiz object. + * @return boolean Whether this quiz has any non-blank feedback text. + */ +function quiz_has_feedback($quizid) { + static $cache = array(); + if (!array_key_exists($quizid, $cache)) { + $cache[$quizid] = record_exists_select('quiz_feedback', + "quizid = $quizid AND feedbacktext <> ''"); + } + return $cache[$quizid]; +} + +/** + * The quiz grade is the score that student's results are marked out of. When it + * changes, the corresponding data in quiz_grades and quiz_feedback needs to be + * rescaled. + * + * @param float $newgrade the new maximum grade for the quiz. + * @param object $quiz the quiz we are updating. Passed by reference so its grade field can be updated too. + * @return boolean indicating success or failure. + */ +function quiz_set_grade($newgrade, &$quiz) { + // This is potentially expensive, so only do it if necessary. + if (abs($quiz->grade - $newgrade) < 1e-7) { + // Nothing to do. + return true; + } + + // Use a transaction, so that on those databases that support it, this is safer. + begin_sql(); + + // Update the quiz table. + $success = set_field('quiz', 'grade', $newgrade, 'id', $quiz->instance); + + // Rescaling the other data is only possible if the old grade was non-zero. + if ($quiz->grade > 1e-7) { + global $CFG; + + $factor = $newgrade/$quiz->grade; + $quiz->grade = $newgrade; + + // Update the quiz_grades table. + $timemodified = time(); + $success = $success && set_field('quiz_grades', 'grade', + '$factor * grade, timemodified = $timemodified', + 'quiz', $quiz->id); + + // Update the quiz_grades table. + $success = $success && execute_sql('quiz_feedback', 'mingrade', + '$factor * mingrade, maxgrade = $factor * maxgrade', + 'quizid', $quiz->id); + } + + if ($success) { + return commit_sql(); + } else { + rollback_sql(); + return false; + } +} + +/** + * Save the overall grade for a user at a quiz in the quiz_grades table + * + * @param object $quiz The quiz for which the best grade is to be calculated and then saved. + * @param integer $userid The userid to calculate the grade for. Defaults to the current user. + * @return boolean Indicates success or failure. + */ +function quiz_save_best_grade($quiz, $userid = null) { global $USER; - // Assume the current user if $userid is null - if (is_null($userid)) { + if (empty($userid)) { $userid = $USER->id; } @@ -284,12 +394,10 @@ function quiz_save_best_grade($quiz, $userid=null) { // Calculate the best grade $bestgrade = quiz_calculate_best_grade($quiz, $attempts); - $bestgrade = $quiz->sumgrades ? (($bestgrade / $quiz->sumgrades) * $quiz->grade) : 0; - $bestgrade = round($bestgrade, $quiz->decimalpoints); - + $bestgrade = quiz_rescale_grade($bestgrade, $quiz); + // Save the best grade in the database - if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', - $userid)) { + if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', $userid)) { $grade->grade = $bestgrade; $grade->timemodified = time(); if (!update_record('quiz_grades', $grade)) { diff --git a/mod/quiz/mod.html b/mod/quiz/mod.html index abe9e2323f..7544616c69 100644 --- a/mod/quiz/mod.html +++ b/mod/quiz/mod.html @@ -1,7 +1,7 @@ - - - +// It is used from /course/mod.php. The whole instance is available as $form. --> + require_once("$CFG->dirroot/mod/quiz/locallib.php"); // Set any form variables that have not been initialized to their default value. @@ -77,6 +77,37 @@ if (!isset($form->delay2)) { $form->delay2 = $CFG->quiz_delay2; } + + // Get any existing feedback text out of the database. + if (!empty($form->id)) { + $feedbacks = get_records('quiz_feedback', 'quizid', $form->id, 'mingrade DESC'); + } else { + $feedbacks = array(); + } + $form->feedbacktext = array(); + $form->feedbackboundaries = array(); + foreach ($feedbacks as $feedback) { + $form->feedbacktext[] = $feedback->feedbacktext; + if ($feedback->mingrade > 0) { + $form->feedbackboundaries[] = (100.0 * $feedback->mingrade / $form->grade) . '%'; + } + } + + // Make sure there are at least 5 feedbacktexts, or a bit more than the current nubmer. + $numfeedbacks = max( + count($form->feedbacktext) * 1.5, + count($form->feedbackboundaries) * 1.5, + 5 + ); + + for ($i = 0; $i < $numfeedbacks; $i += 1) { + if (!array_key_exists($i, $form->feedbacktext)) { + $form->feedbacktext[$i] = ''; + } + if (!array_key_exists($i, $form->feedbackboundaries)) { + $form->feedbackboundaries[$i] = ''; + } + } // The following are used for drop-down menus $yesnooptions = array(get_string("no"), get_string("yes")); @@ -186,11 +217,47 @@ // This time out put the ones that were not fixed. $fix = output_quiz_options_fields($form, 0); + // Output standard module settings. print_standard_coursemodule_settings($form); + // Output the boxes for typing feedback depending on overall quiz score. +?> + + + + + + : + 100% + + +feedbacktext); $i = $i + 1) { ?> + + + : + + + + + +feedbacktext) - 1) { ?> + + : + + + + + + + + + : + 0% + + + : @@ -499,4 +566,4 @@ function output_quiz_options_fields($form, $showfixed) { +?> \ No newline at end of file diff --git a/mod/quiz/report/overview/report.php b/mod/quiz/report/overview/report.php index beca8a4fd6..c618b580ad 100644 --- a/mod/quiz/report/overview/report.php +++ b/mod/quiz/report/overview/report.php @@ -88,6 +88,7 @@ class quiz_report extends quiz_default_report { $noattempts = optional_param('noattempts', 0, PARAM_INT); $detailedmarks = optional_param('detailedmarks', 0, PARAM_INT); $pagesize = optional_param('pagesize', 10, PARAM_INT); + $hasfeedback = quiz_has_feedback($quiz->id) && $quiz->grade > 1.e-7 && $quiz->sumgrades > 1.e-7; // Now check if asked download of data if ($download) { @@ -134,6 +135,11 @@ class quiz_report extends quiz_default_report { } } + if ($hasfeedback) { + $tablecolumns[] = 'feedback'; + $tableheaders[] = get_string('feedback', 'quiz'); + } + if (!$download) { // Set up the table @@ -200,6 +206,9 @@ class quiz_report extends quiz_default_report { $headers[] = '#'.$questions[$id]->number; } } + if ($hasfeedback) { + $headers[] = get_string('feedback', 'quiz'); + } $colnum = 0; foreach ($headers as $item) { $myxls->write(0,$colnum,$item,$formatbc); @@ -225,6 +234,9 @@ class quiz_report extends quiz_default_report { $headers .= "\t#".$question->number; } } + if ($hasfeedback) { + $headers .= "\t" . get_string('feedback', 'quiz'); + } echo $headers." \n"; } @@ -319,7 +331,7 @@ class quiz_report extends quiz_default_report { if (empty($sort)) { $sort = ' ORDER BY uniqueid'; } - + // Now it is time to page the data if (!isset($pagesize) || ((int)$pagesize < 1) ) { $pagesize = 10; @@ -333,6 +345,14 @@ class quiz_report extends quiz_default_report { } } + // If there is feedback, include it in the query. + if ($hasfeedback) { + $factor = $quiz->grade/$quiz->sumgrades; + $select .= ', qf.feedbacktext '; + $from .= " JOIN {$CFG->prefix}quiz_feedback AS qf ON " . + "qf.quizid = $quiz->id AND qf.mingrade <= qa.sumgrades * $factor AND qa.sumgrades * $factor < qf.maxgrade"; + } + // Fetch the attempts if (!empty($from)) { // if we're in the site course and displaying no attempts, it makes no sense to do the query. $attempts = get_records_sql($select.$from.$where.$sort.$limit); @@ -403,6 +423,13 @@ class quiz_report extends quiz_default_report { } } } + if ($hasfeedback) { + if ($attempt->timefinish) { + $row[] = $attempt->feedbacktext; + } else { + $row[] = '-'; + } + } if (!$download) { $table->add_data($row); } else if ($download == 'Excel') { diff --git a/mod/quiz/restorelib.php b/mod/quiz/restorelib.php index c001f2bae0..179f5c1bf5 100644 --- a/mod/quiz/restorelib.php +++ b/mod/quiz/restorelib.php @@ -103,6 +103,8 @@ $mod->id, $newid); //We have to restore the question_instances now (course level table) $status = quiz_question_instances_restore_mods($newid,$info,$restore); + //We have to restore the feedback now (course level table) + $status = quiz_feedback_restore_mods($newid, $info, $restore, $quiz); //We have to restore the question_versions now (course level table) $status = quiz_question_versions_restore_mods($newid,$info,$restore); //Now check if want to restore user data and do it. @@ -132,7 +134,11 @@ $status = true; //Get the quiz_question_instances array - $instances = $info['MOD']['#']['QUESTION_INSTANCES']['0']['#']['QUESTION_INSTANCE']; + if (array_key_exists('QUESTION_INSTANCES', $info['MOD']['#'])) { + $instances = $info['MOD']['#']['QUESTION_INSTANCES']['0']['#']['QUESTION_INSTANCE']; + } else { + $instances = array(); + } //Iterate over question_instances for($i = 0; $i < sizeof($instances); $i++) { @@ -181,6 +187,52 @@ return $status; } + //This function restores the quiz_question_instances + function quiz_feedback_restore_mods($quiz_id, $info, $restore, $quiz) { + $status = true; + + //Get the quiz_feedback array + if (array_key_exists('FEEDBACKS', $info['MOD']['#'])) { + $feedbacks = $info['MOD']['#']['FEEDBACKS']['0']['#']['FEEDBACK']; + + //Iterate over the feedbacks + foreach ($feedbacks as $feedback_info) { + //traverse_xmlize($feedback_info); //Debug + //print_object ($GLOBALS['traverse_array']); //Debug + //$GLOBALS['traverse_array']=""; //Debug + + //We'll need this later!! + $oldid = backup_todb($feedback_info['#']['ID']['0']['#']); + + //Now, build the quiz_feedback record structure + $feedback = new stdClass(); + $feedback->quizid = $quiz_id; + $feedback->feedbacktext = backup_todb($feedback_info['#']['FEEDBACKTEXT']['0']['#']); + $feedback->mingrade = backup_todb($feedback_info['#']['MINGRADE']['0']['#']); + $feedback->maxgrade = backup_todb($feedback_info['#']['MAXGRADE']['0']['#']); + + //The structure is equal to the db, so insert the quiz_question_instances + $newid = insert_record('quiz_feedback', $feedback); + + if ($newid) { + //We have the newid, update backup_ids + backup_putid($restore->backup_unique_code, 'quiz_feedback', $oldid, $newid); + } else { + $status = false; + } + } + } else { + $feedback = new stdClass(); + $feedback->quizid = $quiz_id; + $feedback->feedbacktext = ''; + $feedback->mingrade = 0; + $feedback->maxgrade = $quiz->grade + 1; + insert_record('quiz_feedback', $feedback); + } + + return $status; + } + //This function restores the quiz_question_versions function quiz_question_versions_restore_mods($quiz_id,$info,$restore) { diff --git a/mod/quiz/review.php b/mod/quiz/review.php index 7905d24407..41e31c868f 100644 --- a/mod/quiz/review.php +++ b/mod/quiz/review.php @@ -30,6 +30,9 @@ if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) { error("The course module for the quiz with id $quiz->id is missing"); } + + $grade = quiz_rescale_grade($attempt->sumgrades, $quiz); + $feedback = quiz_feedback_for_grade($grade, $attempt->quiz); if (!count_records('question_sessions', 'attemptid', $attempt->uniqueid)) { // this question has not yet been upgraded to the new model @@ -194,13 +197,16 @@ $a = new stdClass; $percentage = round(($attempt->sumgrades/$quiz->sumgrades)*100, 0); - $a->grade = round(($attempt->sumgrades/$quiz->sumgrades)*$quiz->grade, $CFG->quiz_decimalpoints); + $a->grade = $grade; $a->maxgrade = $quiz->grade; $rawscore = round($attempt->sumgrades, $CFG->quiz_decimalpoints); $table->data[] = array("$strscore:", "$rawscore/$quiz->sumgrades ($percentage %)"); $table->data[] = array("$strgrade:", get_string('outof', 'quiz', $a)); } } + if ($options->feedback && $feedback) { + $table->data[] = array(get_string('feedback', 'quiz'), $feedback); + } if ($isteacher and $attempt->userid == $USER->id) { // the teacher is at the end of a preview. Print button to start new preview unset($buttonoptions); diff --git a/mod/quiz/version.php b/mod/quiz/version.php index eefc0afbfc..5d7d53dcce 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -5,7 +5,7 @@ // This fragment is called by moodle_needs_upgrading() and /admin/index.php //////////////////////////////////////////////////////////////////////////////// -$module->version = 2006081000; // The (date) version of this module +$module->version = 2006081400; // The (date) version of this module $module->requires = 2006080900; // Requires this Moodle version $module->cron = 0; // How often should cron check this module (seconds)? diff --git a/mod/quiz/view.php b/mod/quiz/view.php index 2219f4c4af..ef34ea0730 100644 --- a/mod/quiz/view.php +++ b/mod/quiz/view.php @@ -48,15 +48,13 @@ $timenow = time(); -// Initialize $PAGE, compute blocks - + // Initialize $PAGE, compute blocks $PAGE = page_create_instance($quiz->id); $pageblocks = blocks_setup($PAGE); $blocks_preferred_width = bounded_number(180, blocks_preferred_width($pageblocks[BLOCK_POS_LEFT]), 210); -// Print the page header - - if (($edit != -1) and $PAGE->user_allowed_editing()) { + // Print the page header + if ($edit != -1 and $PAGE->user_allowed_editing()) { $USER->editing = $edit; } @@ -77,13 +75,15 @@ $available = ($quiz->timeopen < $timenow and ($timenow < $quiz->timeclose or !$quiz->timeclose)) || $isteacher; -// Print the main part of the page + // Print the main part of the page // Print heading and tabs for teacher if ($isteacher) { $currenttab = 'info'; include('tabs.php'); } + + // Print quiz name and description. print_heading(format_string($quiz->name)); if (trim(strip_tags($quiz->intro))) { @@ -91,12 +91,15 @@ print_simple_box(format_text($quiz->intro, FORMAT_MOODLE, $formatoptions), "center"); } + // Print information about number of attempts and grading method. if ($quiz->attempts > 1) { echo "

".get_string("attemptsallowed", "quiz").": $quiz->attempts

"; + } + if ($quiz->attempts != 1) { echo "

".get_string("grademethod", "quiz").": ".$QUIZ_GRADE_METHOD[$quiz->grademethod]."

"; - } else { - echo "
"; } + + // Print information about timings. if ($available) { if ($quiz->timelimit) { echo "

".get_string("quiztimelimit","quiz", format_time($quiz->timelimit * 60))."

"; @@ -118,296 +121,282 @@ notify("id\">".get_string('numattempts', 'quiz', $a).''); } - echo ''; - print_footer($course); + end_page($course); exit; } + // Guests can't do a quiz, so offer them a choice of logging in going back. if (isguest()) { - - $wwwroot = $CFG->wwwroot.'/login/index.php'; + $loginurl = $CFG->wwwroot.'/login/index.php'; if (!empty($CFG->loginhttps)) { - $wwwroot = str_replace('http:','https:', $wwwroot); + $loginurl = str_replace('http:','https:', $loginurl); } - notice_yesno(get_string('guestsno', 'quiz').'

'.get_string('liketologin'), - $wwwroot, $_SERVER['HTTP_REFERER']); - print_footer($course); - echo ''; + notice_yesno('

' . get_string('guestsno', 'quiz') . "

\n\n

" . + get_string('liketologin') . '

', $loginurl, $_SERVER['HTTP_REFERER']); + + end_page($course); exit; } - if ($attempts = quiz_get_user_attempts($quiz->id, $USER->id)) { - $numattempts = count($attempts); - } else { - $numattempts = 0; - } - + // Get this user's attempts. + $attempts = quiz_get_user_attempts($quiz->id, $USER->id); $unfinished = false; - if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { + if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { $attempts[] = $unfinishedattempt; $unfinished = true; } + $numattempts = count($attempts); $strattempt = get_string("attempt", "quiz"); $strtimetaken = get_string("timetaken", "quiz"); $strtimecompleted = get_string("timecompleted", "quiz"); $strgrade = get_string("grade"); $strmarks = get_string('marks', 'quiz'); - $strbestgrade = $QUIZ_GRADE_METHOD[$quiz->grademethod]; - - $windowoptions = "left=0, top=0, channelmode=yes, fullscreen=yes, scrollbars=yes, resizeable=no, directories=no, toolbar=no, titlebar=no, location=no, status=no, menubar=no"; + $strfeedback = get_string('feedback', 'quiz'); $mygrade = quiz_get_best_grade($quiz, $USER->id); -/// Now print table with existing attempts - $gradecolumn=0; - $overallstats=1; - if ($attempts) { - - //step thru each attempt, checking there are any attempts - //for which the score can be displayed (need grade columns), - //and checking if overall grades can be displayed - no attempts for - //which the score cannot be displayed + // Print table with existing attempts + + // Work out which columns we need, taking account what data is available in each attempt. + $gradecolumn = 0; + $overallstats = 1; foreach ($attempts as $attempt) { $attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $isteacher); - $attemptoptions->scores ? $gradecolumn=1 : $overallstats=0; + if ($attemptoptions->scores) { + $gradecolumn = 1; + } else { + $overallstats = 0; + } } - /// prepare table header + $gradecolumn = $gradecolumn && $quiz->grade && $quiz->sumgrades; + $markcolumn = $gradecolumn && ($quiz->grade <> $quiz->sumgrades); + $feedbackcolumn = quiz_has_feedback($quiz->id); + + // prepare table header $table->head = array($strattempt, $strtimecompleted); $table->align = array("center", "left"); $table->size = array("", ""); - if ($gradecolumn && $quiz->grade and $quiz->sumgrades) { // Grades used so have more columns in table - if ($quiz->grade <> $quiz->sumgrades) { - $table->head[] = "$strmarks / $quiz->sumgrades"; - $table->align[] = 'right'; - $table->size[] = ''; - } + if ($markcolumn) { + $table->head[] = "$strmarks / $quiz->sumgrades"; + $table->align[] = 'right'; + $table->size[] = ''; + } + if ($gradecolumn) { $table->head[] = "$strgrade / $quiz->grade"; $table->align[] = 'right'; $table->size[] = ''; } + if ($feedbackcolumn) { + $table->head[] = $strfeedback; + $table->align[] = 'left'; + $table->size[] = ''; + } if (isset($quiz->showtimetaken)) { $table->head[] = $strtimetaken; - $table->align[] = 'center'; + $table->align[] = 'left'; $table->size[] = ''; } - /// One row for each attempt + // One row for each attempt foreach ($attempts as $attempt) { - - /// prepare strings for time taken and date completed + $attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $isteacher); + $row = array(); + + // Add the attempt number, making it a link, if appropriate. + $row[] = make_review_link('#' . $attempt->attempt, $quiz, $attempt); + + // prepare strings for time taken and date completed $timetaken = ''; $datecompleted = ''; - if ($attempt->timefinish > 0) { // attempt has finished + if ($attempt->timefinish > 0) { + // attempt has finished $timetaken = format_time($attempt->timefinish - $attempt->timestart); $datecompleted = userdate($attempt->timefinish); - } else if ($available) { // The student can continue this attempt, so put appropriate link + } else if ($available) { + // The attempt is still in progress. $timetaken = format_time(time() - $attempt->timestart); - $datecompleted = "\n".''; - $datecompleted .= ''; - } else { // attempt was not completed but is also not available any more. + $datecompleted = ''; + } else if ($quiz->timeclose) { + // The attempt was not completed but is also not available any more becuase the quiz is closed. $timetaken = format_time($quiz->timeclose - $attempt->timestart); - $datecompleted = $quiz->timeclose ? userdate($quiz->timeclose) : ''; + $datecompleted = userdate($quiz->timeclose); + } else { + // Something wheird happened. + $timetaken = ''; + $datecompleted = ''; } - - $attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $isteacher); - /// prepare strings for attempt number, mark and grade - //if attempt's score is allowed to be viewed, & qz->sumgrades and qz->sumgrades defined: - if ($attemptoptions->scores && $quiz->grade and $quiz->sumgrades) { - $attemptmark = round($attempt->sumgrades,$quiz->decimalpoints); - $attemptgrade = round(($attempt->sumgrades/$quiz->sumgrades)*$quiz->grade,$quiz->decimalpoints); - - // highlight the highest grade if appropriate - if ($overallstats && $attemptgrade == $mygrade and ($quiz->grademethod == QUIZ_GRADEHIGHEST)) { - $attemptgrade = "$attemptgrade"; - } - - // if attempt is closed and review is allowed then make attemptnumber and - // mark and grade into links to review page - if (quiz_review_allowed($quiz) and $attempt->timefinish > 0) { - if ($quiz->popup) { // need to link to popup window - $attemptmark = link_to_popup_window ("/mod/quiz/review.php?q=$quiz->id&attempt=$attempt->id", 'quizpopup', round($attempt->sumgrades,$quiz->decimalpoints), '+window.screen.height+', '+window.screen.width+', '', $windowoptions, true); - $attemptgrade = link_to_popup_window ("/mod/quiz/review.php?q=$quiz->id&attempt=$attempt->id", 'quizpopup', $attemptgrade, '+window.screen.height+', '+window.screen.width+', '', $windowoptions, true); - $attempt->attempt = link_to_popup_window ("/mod/quiz/review.php?q=$quiz->id&attempt=$attempt->id", 'quizpopup', "#$attempt->attempt", '+window.screen.height+', '+window.screen.width+', '', $windowoptions, true); - } else { - $attemptmark = "id&attempt=$attempt->id\">".round($attempt->sumgrades,$quiz->decimalpoints).''; - $attemptgrade = "id&attempt=$attempt->id\">$attemptgrade"; - $attempt->attempt = "id&attempt=$attempt->id\">#$attempt->attempt"; - } - } - - if ($quiz->grade <> $quiz->sumgrades) { - $table->data[] = array( $attempt->attempt, - $datecompleted, - $attemptmark, $attemptgrade); + $row[] = $datecompleted; + + if ($markcolumn) { + if ($attemptoptions->scores) { + $row[] = make_review_link(round($attempt->sumgrades, $quiz->decimalpoints), $quiz, $attempt); } else { - $table->data[] = array( $attempt->attempt, - $datecompleted, - $attemptgrade); + $row[] = ''; } - } else { // No grades are being used - if (quiz_review_allowed($quiz)) { - if($attempt->timefinish > 0) { - $attempt->attempt = "id&attempt=$attempt->id\">#$attempt->attempt"; + } + + // Ouside the if becuase we may be showing feedback but not grades. + $attemptgrade = quiz_rescale_grade($attempt->sumgrades, $quiz); + if ($gradecolumn) { + if ($attemptoptions->scores) { + // highlight the highest grade if appropriate + if ($overallstats && !is_null($mygrade) && $attemptgrade == $mygrade && $quiz->grademethod == QUIZ_GRADEHIGHEST) { + $formattedgrade = "$attemptgrade"; } else { - $attempt->attempt = "#$attempt->attempt"; + $formattedgrade = $attemptgrade; } - } - - $helpbutton=helpbutton('missing\ grade', get_string('wheregrade', 'quiz'), 'quiz', true, false, '',true); - if($gradecolumn) { - $table->data[] = array( $attempt->attempt, - $datecompleted, - $helpbutton); - + + $row[] = make_review_link($formattedgrade, $quiz, $attempt); } else { - $table->data[] = array( $attempt->attempt, - $datecompleted); + $row[] = ''; } } + + if ($feedbackcolumn) { + if ($attemptoptions->feedback) { + $row[] = quiz_feedback_for_grade($attemptgrade, $quiz->id); + } else { + $row[] = ''; + } + } + if (isset($quiz->showtimetaken)) { - $table->data[] = $timetaken; + $row[] = $timetaken; } + + $table->data[] = $row; } print_table($table); } - if (!$quiz->questions) { - print_heading(get_string("noquestions", "quiz")); - } else { - if ($numattempts < $quiz->attempts or !$quiz->attempts) { - - if ($available) { - $options["id"] = $cm->id; - //if overall stats are allowed (no attemps' grade not visible), - //and there is at least one attempt, and quiz->grade: - if ($overallstats and $numattempts and $quiz->grade) { - print_heading("$strbestgrade: $mygrade / $quiz->grade."); - } - - echo "
"; - echo "

"; - echo "
"; - if ($quiz->delay1 or $quiz->delay2) { - //quiz enforced time delay - $lastattempt_obj = get_record_select('quiz_attempts', "quiz = $quiz->id AND attempt = $numattempts AND userid = $USER->id", 'timefinish'); - if ($lastattempt_obj) { - $lastattempt = $lastattempt_obj->timefinish; + // Print information about the student's best score for this quiz if possible. + $moreattempts = $numattempts < $quiz->attempts || $quiz->attempts == 0; + if (!$moreattempts) { + print_heading(get_string("nomoreattempts", "quiz")); + } + + if ($numattempts && $quiz->sumgrades) { + if (!is_null($mygrade)) { + if ($available && $moreattempts) { + $strbestgrade = $QUIZ_GRADE_METHOD[$quiz->grademethod]; + $grademessage = "$strbestgrade: $mygrade / $quiz->grade."; + } else { + $grademessage = get_string("yourfinalgradeis", "quiz", "$mygrade / $quiz->grade"); + } + + if ($overallstats) { + print_heading($grademessage); + } + + if ($feedbackcolumn) { + echo '

', quiz_feedback_for_grade($mygrade, $quiz->id), '

'; + } + } + + if (!($moreattempts && $available)) { + print_continue($CFG->webroot . '/course/view.php?id=' . $course->id); + } + } + + if ($quiz->questions) { + // Print a button to start the quiz if appropriate. + if ($available && $moreattempts) { + echo "
"; + echo "
"; + if ($quiz->delay1 or $quiz->delay2) { + //quiz enforced time delay + $lastattempt_obj = get_record_select('quiz_attempts', "quiz = $quiz->id AND attempt = $numattempts AND userid = $USER->id", 'timefinish'); + if ($lastattempt_obj) { + $lastattempt = $lastattempt_obj->timefinish; + } + if($numattempts == 1 && $quiz->delay1) { + if ($timenow - $quiz->delay1 > $lastattempt) { + print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); + } else { + $notify_msg = get_string('temporaryblocked', 'quiz') . ''. userdate($lastattempt + $quiz->delay1). ''; + print_simple_box($notify_msg, "center"); } - if($numattempts == 1 && $quiz->delay1) { - if ($timenow - $quiz->delay1 > $lastattempt) { - print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); - } else { - $notify_msg = get_string('temporaryblocked', 'quiz') . ''. userdate($lastattempt + $quiz->delay1). ''; - print_simple_box($notify_msg, "center"); - } - } else if($numattempts > 1 && $quiz->delay2) { - if ($timenow - $quiz->delay2 > $lastattempt) { - print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); - } else { - $notify_msg = get_string('temporaryblocked', 'quiz') . ''. userdate($lastattempt + $quiz->delay2). ''; - print_simple_box($notify_msg, "center"); - } + } else if($numattempts > 1 && $quiz->delay2) { + if ($timenow - $quiz->delay2 > $lastattempt) { + print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); } else { - print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); + $notify_msg = get_string('temporaryblocked', 'quiz') . ''. userdate($lastattempt + $quiz->delay2). ''; + print_simple_box($notify_msg, "center"); } - } else { + } else { print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); - } - echo "
\n"; - } - } else { - print_heading(get_string("nomoreattempts", "quiz")); - //if $quiz->grade and $quiz->sumgrades, and student is allowed to - //see summary statistics (no attempt's grade is concealed), - //show the student their final grade - if ($quiz->grade and $quiz->sumgrades and $overallstats) { - print_heading(get_string("yourfinalgradeis", "quiz", "$mygrade / $quiz->grade")); - } - print_continue('../../course/view.php?id='.$course->id); + } + } else { + print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm); + } + echo "
\n"; } + } else { + // No questions in quiz. + print_heading(get_string("noquestions", "quiz")); } -// Finish the page - echo ''; + // Finish the page - this needs to be the same as in the if teacher block above. + echo ''; print_footer($course); - function quiz_review_allowed($quiz) { - // If not even responses are to be shown in review then we - // don't allow any review - if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) { - return false; - } - if ((!$quiz->timeclose or time() < $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_OPEN)) { - return false; - } - if (($quiz->timeclose and time() > $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_CLOSED)) { - return false; - } - return true; +// Utility functions ================================================================= + +function quiz_review_allowed($quiz) { + // If not even responses are to be shown in review then we + // don't allow any review + if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) { + return false; } + if ((!$quiz->timeclose or time() < $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_OPEN)) { + return false; + } + if (($quiz->timeclose and time() > $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_CLOSED)) { + return false; + } + return true; +} + + +function print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm) { + $strconfirmstartattempt = ''; - - function print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm) { - $strconfirmstartattempt = ''; - - if ($unfinished) { - $buttontext = get_string('continueattemptquiz', 'quiz'); + if ($unfinished) { + $buttontext = get_string('continueattemptquiz', 'quiz'); + } else { + if ($numattempts) { + $buttontext = get_string('reattemptquiz', 'quiz'); } else { - if ($numattempts) { - $buttontext = get_string('reattemptquiz', 'quiz'); - } else { - $buttontext = get_string('attemptquiznow', 'quiz'); - } - if ($quiz->timelimit && $quiz->attempts) { - $strconfirmstartattempt = addslashes(get_string('confirmstartattempttimelimit','quiz', $quiz->attempts)); - } else if ($quiz->timelimit) { - $strconfirmstartattempt = addslashes(get_string('confirmstarttimelimit','quiz')); - } else if ($quiz->attempts) { - $strconfirmstartattempt = addslashes(get_string('confirmstartattemptlimit','quiz', $quiz->attempts)); - } + $buttontext = get_string('attemptquiznow', 'quiz'); } - $buttontext = htmlspecialchars($buttontext, ENT_QUOTES); - - if (!empty($quiz->popup)) { - $window = 'quizpopup'; - $windowoptions = "left=0, top=0, height='+window.screen.height+', " . - "width='+window.screen.width+', channelmode=yes, fullscreen=yes, " . - "scrollbars=yes, resizeable=no, directories=no, toolbar=no, " . - "titlebar=no, location=no, status=no, menubar=no"; - } else { - $window = '_self'; - $windowoptions = ''; + if ($quiz->timelimit && $quiz->attempts) { + $strconfirmstartattempt = addslashes(get_string('confirmstartattempttimelimit','quiz', $quiz->attempts)); + } else if ($quiz->timelimit) { + $strconfirmstartattempt = addslashes(get_string('confirmstarttimelimit','quiz')); + } else if ($quiz->attempts) { + $strconfirmstartattempt = addslashes(get_string('confirmstartattemptlimit','quiz', $quiz->attempts)); } + } + $buttontext = htmlspecialchars($buttontext, ENT_QUOTES); + + if (!empty($quiz->popup)) { + $window = 'quizpopup'; + $windowoptions = "left=0, top=0, height='+window.screen.height+', " . + "width='+window.screen.width+', channelmode=yes, fullscreen=yes, " . + "scrollbars=yes, resizeable=no, directories=no, toolbar=no, " . + "titlebar=no, location=no, status=no, menubar=no"; + } else { + $window = '_self'; + $windowoptions = ''; + } - $attempturl = "attempt.php?id=$cm->id"; - if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) { - $attempturl = sid_process_url($attempturl); - } + $attempturl = "attempt.php?id=$cm->id"; + if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) { + $attempturl = sid_process_url($attempturl); + } ?>