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.
This commit is contained in:
tjhunt
2006-08-22 17:31:26 +00:00
parent 2efaf3f8ab
commit 212b7b8cd9
17 changed files with 750 additions and 307 deletions

View File

@@ -0,0 +1,14 @@
<p align="center"><b>Overall feedback</b></p>
<p>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.</p>
<p>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.</p>
<p>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.</p>

View File

@@ -180,6 +180,11 @@ $string['exportnameformat'] = '%%Y%%m%%d-%%H%%M';
$string['exportquestions'] = 'Export questions to file'; $string['exportquestions'] = 'Export questions to file';
$string['false'] = 'False'; $string['false'] = 'False';
$string['feedback'] = 'Feedback'; $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['file'] = 'File';
$string['fileformat'] = 'File format'; $string['fileformat'] = 'File format';
$string['fillcorrect'] = 'Fill with correct'; $string['fillcorrect'] = 'Fill with correct';
@@ -203,6 +208,7 @@ $string['geometric'] = 'Geometric';
$string['gift'] = 'GIFT format'; $string['gift'] = 'GIFT format';
$string['grade'] = 'Grade'; $string['grade'] = 'Grade';
$string['gradeaverage'] = 'Average grade'; $string['gradeaverage'] = 'Average grade';
$string['gradeboundary'] = 'Grade boundary';
$string['gradehighest'] = 'Highest grade'; $string['gradehighest'] = 'Highest grade';
$string['grademethod'] = 'Grading method'; $string['grademethod'] = 'Grading method';
$string['gradingdetails'] = 'Marks for this submission: $a->raw/$a->max. '; $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['onlyteachersexport'] = 'Only teachers can export questions';
$string['optional'] = 'optional'; $string['optional'] = 'optional';
$string['outof'] = '$a->grade out of a maximum of $a->maxgrade'; $string['outof'] = '$a->grade out of a maximum of $a->maxgrade';
$string['overallfeedback'] = 'Overall feedback';
$string['overdue'] = 'Overdue'; $string['overdue'] = 'Overdue';
$string['pagesize'] = 'Attempts shown per page: '; $string['pagesize'] = 'Attempts shown per page: ';
$string['paragraphquestion'] = 'Paragraph Question not supported at line $a. The question will be ignored'; $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['quiznotavailable'] = 'The quiz will not be available until: $a';
$string['quizopen'] = 'Open the quiz'; $string['quizopen'] = 'Open the quiz';
$string['quizopens'] = 'Quiz opens'; $string['quizopens'] = 'Quiz opens';
$string['quizsettings'] = 'Quiz settings';
$string['quiztimelimit'] = 'Time limit: $a'; $string['quiztimelimit'] = 'Time limit: $a';
$string['quiztimer'] = 'Quiz Timer'; $string['quiztimer'] = 'Quiz Timer';
$string['pleaseclose'] = 'Your request has been processed. You can now close this window'; $string['pleaseclose'] = 'Your request has been processed. You can now close this window';

View File

@@ -9,12 +9,12 @@
// (CL,pk->id) // (CL,pk->id)
// | // |
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// | | | | // | | | | |
// | quiz_grades | quiz_question_versions // | quiz_grades | quiz_question_versions |
// | (UL,pk->id,fk->quiz) | (CL,pk->id,fk->quiz) // | (UL,pk->id,fk->quiz) | (CL,pk->id,fk->quiz) |
// | | // | | |
// quiz_attempts quiz_question_instances // quiz_attempts quiz_question_instances quiz_feedback
// (UL,pk->id,fk->quiz) (CL,pk->id,fk->quiz,question) // (UL,pk->id,fk->quiz) (CL,pk->id,fk->quiz,question) (CL,pk->id,fk->quiz)
// //
// Meaning: pk->primary key field of the table // Meaning: pk->primary key field of the table
// fk->foreign key to link with parent // fk->foreign key to link with parent
@@ -204,6 +204,8 @@
fwrite ($bf,full_tag("DELAY2",4,false,$quiz->delay2)); fwrite ($bf,full_tag("DELAY2",4,false,$quiz->delay2));
//Now we print to xml question_instances (Course Level) //Now we print to xml question_instances (Course Level)
$status = backup_quiz_question_instances($bf,$preferences,$quiz->id); $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) //Now we print to xml question_versions (Course Level)
$status = backup_quiz_question_versions($bf,$preferences,$quiz->id); $status = backup_quiz_question_versions($bf,$preferences,$quiz->id);
//if we've selected to backup users info, then execute: //if we've selected to backup users info, then execute:
@@ -242,9 +244,6 @@
//Backup quiz_question_instances contents (executed from quiz_backup_mods) //Backup quiz_question_instances contents (executed from quiz_backup_mods)
function backup_quiz_question_instances ($bf,$preferences,$quiz) { function backup_quiz_question_instances ($bf,$preferences,$quiz) {
global $CFG;
$status = true; $status = true;
$quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id"); $quiz_question_instances = get_records("quiz_question_instances","quiz",$quiz,"id");
@@ -269,11 +268,41 @@
return $status; 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) //Backup quiz_question_versions contents (executed from quiz_backup_mods)
function backup_quiz_question_versions ($bf,$preferences,$quiz) { function backup_quiz_question_versions ($bf,$preferences,$quiz) {
global $CFG;
$status = true; $status = true;
$quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id"); $quiz_question_versions = get_records("quiz_question_versions","quiz",$quiz,"id");
@@ -304,9 +333,6 @@
//Backup quiz_grades contents (executed from quiz_backup_mods) //Backup quiz_grades contents (executed from quiz_backup_mods)
function backup_quiz_grades ($bf,$preferences,$quiz) { function backup_quiz_grades ($bf,$preferences,$quiz) {
global $CFG;
$status = true; $status = true;
$quiz_grades = get_records("quiz_grades","quiz",$quiz,"id"); $quiz_grades = get_records("quiz_grades","quiz",$quiz,"id");
@@ -334,9 +360,6 @@
//Backup quiz_attempts contents (executed from quiz_backup_mods) //Backup quiz_attempts contents (executed from quiz_backup_mods)
function backup_quiz_attempts ($bf,$preferences,$quiz) { function backup_quiz_attempts ($bf,$preferences,$quiz) {
global $CFG;
$status = true; $status = true;
$quiz_attempts = get_records("quiz_attempts","quiz",$quiz,"id"); $quiz_attempts = get_records("quiz_attempts","quiz",$quiz,"id");

View File

@@ -1113,6 +1113,25 @@ function quiz_upgrade($oldversion) {
(($CFG->quiz_review & QUIZ_REVIEW_FEEDBACK) << 3)); (($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; return $success;
} }

View File

@@ -62,6 +62,16 @@ CREATE TABLE prefix_quiz_question_versions (
PRIMARY KEY (id) PRIMARY KEY (id)
) TYPE=MyISAM COMMENT='The mapping between old and new versions of a question'; ) 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. -- Quiz module, quiz runtime data.
-- -------------------------------------------------------- -- --------------------------------------------------------

View File

@@ -1435,6 +1435,24 @@ function quiz_upgrade($oldversion) {
(($CFG->quiz_review & QUIZ_REVIEW_FEEDBACK) << 3)); (($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; return $success;
} }

View File

@@ -59,6 +59,15 @@ CREATE TABLE prefix_quiz_question_versions (
timestamp integer NOT NULL default '0' 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. -- Quiz module, quiz runtime data.
-- -------------------------------------------------------- -- --------------------------------------------------------
@@ -207,7 +216,7 @@ CREATE TABLE prefix_question_states (
penalty real NOT NULL default '0' penalty real NOT NULL default '0'
); );
CREATE INDEX prefix_question_states_attempt_idx ON prefix_question_states (attempt); 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. -- Quiz log actions.

View File

@@ -274,9 +274,8 @@ if (self.name == 'editquestion') {
// If rescaling is required save the new maximum // If rescaling is required save the new maximum
if (isset($_REQUEST['maxgrade'])) { if (isset($_REQUEST['maxgrade'])) {
$modform->grade = optional_param('maxgrade', 0); if (!quiz_set_grade(optional_param('maxgrade', 0), $modform)) {
if (!set_field('quiz', 'grade', $modform->grade, 'id', $modform->instance)) { error('Could not set a new maximum grade for the quiz');
error('Could not set new maximal grade for quiz');
} }
} }
} }
@@ -308,7 +307,7 @@ if (self.name == 'editquestion') {
// Print basic page layout. // 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 // one column layout with table of questions used in this quiz
$strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext) $strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext)
? update_module_button($modform->cmid, $course->id, get_string('modulename', 'quiz')) ? update_module_button($modform->cmid, $course->id, get_string('modulename', 'quiz'))

View File

@@ -121,20 +121,23 @@
// or the quiz has no grade, display nothing in grade col // or the quiz has no grade, display nothing in grade col
if ($bestgrade === NULL || $quiz->grade == 0) { if ($bestgrade === NULL || $quiz->grade == 0) {
$gradecol = ""; $gradecol = "";
$feedbackcol = '';
} else { } else {
//If all quiz's attempts have visible results, show bestgrade //If all quiz's attempts have visible results, show bestgrade
if(all_attempt_results_visible($quiz, $USER)) { if(all_attempt_results_visible($quiz, $USER)) {
$gradecol = "$bestgrade / $quiz->grade"; $gradecol = "$bestgrade / $quiz->grade";
$feedbackcol = quiz_get_feedback($quiz, $bestgrade);
} else { } else {
$gradecol = ""; $gradecol = "";
$feedbackcol = '';
} }
} }
} }
if ($course->format == "weeks" or $course->format == "topics") { if ($course->format == "weeks" or $course->format == "topics") {
$table->data[] = array ($printsection, $link, $closequiz, $gradecol); $table->data[] = array ($printsection, $link, $closequiz, $gradecol, $feedbackcol);
} else { } else {
$table->data[] = array ($link, $closequiz, $gradecol); $table->data[] = array ($link, $closequiz, $gradecol, $feedbackcol);
} }
} }
@@ -146,4 +149,4 @@
print_footer($course); print_footer($course);
?> ?>

View File

@@ -58,14 +58,18 @@ define("QUIZ_MAX_EVENT_LENGTH", "432000"); // 5 days maximum
* of the new instance. * of the new instance.
* *
* @param object $quiz the data that came from the form. * @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) { function quiz_add_instance($quiz) {
// Process the options from the form. // Process the options from the form.
$quiz->created = time(); $quiz->created = time();
quiz_process_options($quiz);
$quiz->questions = ''; $quiz->questions = '';
$result = quiz_process_options($quiz);
if ($result && is_string($result)) {
return $result;
}
// Try to store it in the database. // Try to store it in the database.
if (!$quiz->id = insert_record("quiz", $quiz)) { if (!$quiz->id = insert_record("quiz", $quiz)) {
@@ -84,12 +88,15 @@ function quiz_add_instance($quiz) {
* will update an existing instance with new data. * will update an existing instance with new data.
* *
* @param object $quiz the data that came from the form. * @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) { function quiz_update_instance($quiz) {
// Process the options from the form. // 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. // Update the database.
$quiz->id = $quiz->instance; $quiz->id = $quiz->instance;
@@ -120,7 +127,7 @@ function quiz_delete_instance($id) {
if ($attempts = get_records("quiz_attempts", "quiz", "$quiz->id")) { if ($attempts = get_records("quiz_attempts", "quiz", "$quiz->id")) {
foreach ($attempts as $attempt) { 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")) { if (! delete_records("question_states", "attempt", "$attempt->uniqueid")) {
$result = false; $result = false;
} }
@@ -130,20 +137,18 @@ function quiz_delete_instance($id) {
} }
} }
if (! delete_records("quiz_attempts", "quiz", "$quiz->id")) { $tables_to_purge = array(
$result = false; 'quiz_attempts' => 'quiz',
} 'quiz_grades' => 'quiz',
'quiz_question_instances' => 'quiz',
if (! delete_records("quiz_grades", "quiz", "$quiz->id")) { 'quiz_grades' => 'quiz',
$result = false; 'quiz_feedback' => 'quizid',
} 'quiz' => 'id'
);
if (! delete_records("quiz_question_instances", "quiz", "$quiz->id")) { foreach ($tables_to_purge as $table => $keyfield) {
$result = false; if (!delete_records($table, $keyfield, $quiz->id)) {
} $result = false;
}
if (! delete_records("quiz", "id", "$quiz->id")) {
$result = false;
} }
$pagetypes = page_import_types('mod/quiz/'); $pagetypes = page_import_types('mod/quiz/');
@@ -495,7 +500,58 @@ function quiz_process_options(&$quiz) {
$quiz->timelimit = 0; $quiz->timelimit = 0;
} }
$quiz->timelimit = round($quiz->timelimit); $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. // Settings that get combined to go into the optionflags column.
$quiz->optionflags = 0; $quiz->optionflags = 0;
if (!empty($quiz->adaptive)) { if (!empty($quiz->adaptive)) {
@@ -593,6 +649,20 @@ function quiz_process_options(&$quiz) {
*/ */
function quiz_after_add_or_update($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. // Remember whether this user likes the advanced settings visible or hidden.
if (isset($quiz->optionsettingspref)) { if (isset($quiz->optionsettingspref)) {
set_user_preference('quiz_optionsettingspref', $quiz->optionsettingspref); set_user_preference('quiz_optionsettingspref', $quiz->optionsettingspref);

View File

@@ -83,10 +83,18 @@ function quiz_get_user_attempt_unfinished($quizid, $userid) {
return get_record("quiz_attempts", "quiz", $quizid, "userid", $userid, "timefinish", 0); 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) { function quiz_get_user_attempts($quizid, $userid) {
// Returns a list of all attempts by a user if ($attempts = get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0",
return get_records_select("quiz_attempts", "quiz = '$quizid' AND userid = '$userid' AND timefinish > 0", "attempt ASC")) {
"attempt ASC"); return $attempts;
} else {
return array();
}
} }
@@ -249,30 +257,132 @@ function quiz_get_all_question_grades($quiz) {
return $grades; 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) { function quiz_get_best_grade($quiz, $userid) {
/// Get the best current grade for a particular user in a quiz $grade = get_field('quiz_grades', 'grade', 'quiz', $quiz->id, 'userid', $userid);
if (!$grade = get_record('quiz_grades', '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 NULL;
} }
return (round($grade->grade,$quiz->decimalpoints));
} }
/** /**
* Save the overall grade for a user at a quiz in the quiz_grades table * Convert the raw grade stored in $attempt into a grade out of the maximum
* * grade for this quiz.
* @return boolean Indicates success or failure. *
* @param object $quiz The quiz for which the best grade is to be calculated * @param float $rawgrade the unadjusted grade, fof example $attempt->sumgrades
* and then saved. * @param object $quiz the quiz object. Only the fields grade, sumgrades and decimalpoints are used.
* @param integer $userid The id of the user to save the best grade for. Can be * @return float the rescaled grade.
* null in which case the current user is assumed. */
*/ function quiz_rescale_grade($rawgrade, $quiz) {
function quiz_save_best_grade($quiz, $userid=null) { if ($quiz->sumgrades) {
return round($rawgrade*$quiz->grade/$quiz->sumgrades, $quiz->decimalpoints);
} else {
return 0;
}
}
/**
* 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; global $USER;
// Assume the current user if $userid is null if (empty($userid)) {
if (is_null($userid)) {
$userid = $USER->id; $userid = $USER->id;
} }
@@ -284,12 +394,10 @@ function quiz_save_best_grade($quiz, $userid=null) {
// Calculate the best grade // Calculate the best grade
$bestgrade = quiz_calculate_best_grade($quiz, $attempts); $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
$bestgrade = $quiz->sumgrades ? (($bestgrade / $quiz->sumgrades) * $quiz->grade) : 0; $bestgrade = quiz_rescale_grade($bestgrade, $quiz);
$bestgrade = round($bestgrade, $quiz->decimalpoints);
// Save the best grade in the database // Save the best grade in the database
if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', $userid)) {
$userid)) {
$grade->grade = $bestgrade; $grade->grade = $bestgrade;
$grade->timemodified = time(); $grade->timemodified = time();
if (!update_record('quiz_grades', $grade)) { if (!update_record('quiz_grades', $grade)) {

View File

@@ -1,7 +1,7 @@
<!-- This page defines the form to create or edit an instance of this module -->
<!-- It is used from /course/mod.php. The whole instance is available as $form. -->
<?php <?php
// This page defines the form to create or edit an instance of this module -->
// It is used from /course/mod.php. The whole instance is available as $form. -->
require_once("$CFG->dirroot/mod/quiz/locallib.php"); require_once("$CFG->dirroot/mod/quiz/locallib.php");
// Set any form variables that have not been initialized to their default value. // Set any form variables that have not been initialized to their default value.
@@ -77,6 +77,37 @@
if (!isset($form->delay2)) { if (!isset($form->delay2)) {
$form->delay2 = $CFG->quiz_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 // The following are used for drop-down menus
$yesnooptions = array(get_string("no"), get_string("yes")); $yesnooptions = array(get_string("no"), get_string("yes"));
@@ -186,11 +217,47 @@
// This time out put the ones that were not fixed. // This time out put the ones that were not fixed.
$fix = output_quiz_options_fields($form, 0); $fix = output_quiz_options_fields($form, 0);
// Output standard module settings.
print_standard_coursemodule_settings($form); print_standard_coursemodule_settings($form);
// Output the boxes for typing feedback depending on overall quiz score.
?>
<tr><td colspan="2">
<?php print_heading_with_help(get_string('overallfeedback', 'quiz'), 'overallfeedback', 'quiz'); ?>
</td></tr>
<tr valign="top">
<td align="right"><b><?php print_string('gradeboundary', 'quiz') ?>:</b></td>
<td align="left">100%</td>
</tr>
<?php for ($i = 0; $i < count($form->feedbacktext); $i = $i + 1) { ?>
<tr valign="top">
<td align="right"><b><?php print_string('feedback', 'quiz') ?>:</b></td>
<td align="left">
<input type="text" name="feedbacktext[]" size="60" value="<?php p($form->feedbacktext[$i]) ?>" />
</td>
</tr>
<?php if ($i < count($form->feedbacktext) - 1) { ?>
<tr valign="top">
<td align="right"><b><?php print_string('gradeboundary', 'quiz') ?>:</b></td>
<td align="left">
<input type="text" name="feedbackboundaries[]" size="20" value="<?php p($form->feedbackboundaries[$i]) ?>" />
</td>
</tr>
<?php } ?>
<?php } ?>
<tr valign="top">
<td align="right"><b><?php print_string('gradeboundary', 'quiz') ?>:</b></td>
<td align="left">0%</td>
</tr>
<?php
if ($fix) { if ($fix) {
// Some options were fixed by the admin. Show them, but hidden behind an Advanced button. // Some options were fixed by the admin. Show them, but hidden behind an Advanced button.
?> ?>
<tr> <tr>
<td align="right"><b><?php print_string('advancedsettings') ?>:</b> <td align="right"><b><?php print_string('advancedsettings') ?>:</b>
@@ -499,4 +566,4 @@ function output_quiz_options_fields($form, $showfixed) {
<?php <?php
return $hidden; return $hidden;
} }
?> ?>

View File

@@ -88,6 +88,7 @@ class quiz_report extends quiz_default_report {
$noattempts = optional_param('noattempts', 0, PARAM_INT); $noattempts = optional_param('noattempts', 0, PARAM_INT);
$detailedmarks = optional_param('detailedmarks', 0, PARAM_INT); $detailedmarks = optional_param('detailedmarks', 0, PARAM_INT);
$pagesize = optional_param('pagesize', 10, 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 // Now check if asked download of data
if ($download) { if ($download) {
@@ -134,6 +135,11 @@ class quiz_report extends quiz_default_report {
} }
} }
if ($hasfeedback) {
$tablecolumns[] = 'feedback';
$tableheaders[] = get_string('feedback', 'quiz');
}
if (!$download) { if (!$download) {
// Set up the table // Set up the table
@@ -200,6 +206,9 @@ class quiz_report extends quiz_default_report {
$headers[] = '#'.$questions[$id]->number; $headers[] = '#'.$questions[$id]->number;
} }
} }
if ($hasfeedback) {
$headers[] = get_string('feedback', 'quiz');
}
$colnum = 0; $colnum = 0;
foreach ($headers as $item) { foreach ($headers as $item) {
$myxls->write(0,$colnum,$item,$formatbc); $myxls->write(0,$colnum,$item,$formatbc);
@@ -225,6 +234,9 @@ class quiz_report extends quiz_default_report {
$headers .= "\t#".$question->number; $headers .= "\t#".$question->number;
} }
} }
if ($hasfeedback) {
$headers .= "\t" . get_string('feedback', 'quiz');
}
echo $headers." \n"; echo $headers." \n";
} }
@@ -319,7 +331,7 @@ class quiz_report extends quiz_default_report {
if (empty($sort)) { if (empty($sort)) {
$sort = ' ORDER BY uniqueid'; $sort = ' ORDER BY uniqueid';
} }
// Now it is time to page the data // Now it is time to page the data
if (!isset($pagesize) || ((int)$pagesize < 1) ) { if (!isset($pagesize) || ((int)$pagesize < 1) ) {
$pagesize = 10; $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 // 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. 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); $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) { if (!$download) {
$table->add_data($row); $table->add_data($row);
} else if ($download == 'Excel') { } else if ($download == 'Excel') {

View File

@@ -103,6 +103,8 @@
$mod->id, $newid); $mod->id, $newid);
//We have to restore the question_instances now (course level table) //We have to restore the question_instances now (course level table)
$status = quiz_question_instances_restore_mods($newid,$info,$restore); $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) //We have to restore the question_versions now (course level table)
$status = quiz_question_versions_restore_mods($newid,$info,$restore); $status = quiz_question_versions_restore_mods($newid,$info,$restore);
//Now check if want to restore user data and do it. //Now check if want to restore user data and do it.
@@ -132,7 +134,11 @@
$status = true; $status = true;
//Get the quiz_question_instances array //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 //Iterate over question_instances
for($i = 0; $i < sizeof($instances); $i++) { for($i = 0; $i < sizeof($instances); $i++) {
@@ -181,6 +187,52 @@
return $status; 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 //This function restores the quiz_question_versions
function quiz_question_versions_restore_mods($quiz_id,$info,$restore) { function quiz_question_versions_restore_mods($quiz_id,$info,$restore) {

View File

@@ -30,6 +30,9 @@
if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) { if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) {
error("The course module for the quiz with id $quiz->id is missing"); 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)) { if (!count_records('question_sessions', 'attemptid', $attempt->uniqueid)) {
// this question has not yet been upgraded to the new model // this question has not yet been upgraded to the new model
@@ -194,13 +197,16 @@
$a = new stdClass; $a = new stdClass;
$percentage = round(($attempt->sumgrades/$quiz->sumgrades)*100, 0); $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; $a->maxgrade = $quiz->grade;
$rawscore = round($attempt->sumgrades, $CFG->quiz_decimalpoints); $rawscore = round($attempt->sumgrades, $CFG->quiz_decimalpoints);
$table->data[] = array("$strscore:", "$rawscore/$quiz->sumgrades ($percentage %)"); $table->data[] = array("$strscore:", "$rawscore/$quiz->sumgrades ($percentage %)");
$table->data[] = array("$strgrade:", get_string('outof', 'quiz', $a)); $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) { if ($isteacher and $attempt->userid == $USER->id) {
// the teacher is at the end of a preview. Print button to start new preview // the teacher is at the end of a preview. Print button to start new preview
unset($buttonoptions); unset($buttonoptions);

View File

@@ -5,7 +5,7 @@
// This fragment is called by moodle_needs_upgrading() and /admin/index.php // 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->requires = 2006080900; // Requires this Moodle version
$module->cron = 0; // How often should cron check this module (seconds)? $module->cron = 0; // How often should cron check this module (seconds)?

View File

@@ -48,15 +48,13 @@
$timenow = time(); $timenow = time();
// Initialize $PAGE, compute blocks // Initialize $PAGE, compute blocks
$PAGE = page_create_instance($quiz->id); $PAGE = page_create_instance($quiz->id);
$pageblocks = blocks_setup($PAGE); $pageblocks = blocks_setup($PAGE);
$blocks_preferred_width = bounded_number(180, blocks_preferred_width($pageblocks[BLOCK_POS_LEFT]), 210); $blocks_preferred_width = bounded_number(180, blocks_preferred_width($pageblocks[BLOCK_POS_LEFT]), 210);
// Print the page header // Print the page header
if ($edit != -1 and $PAGE->user_allowed_editing()) {
if (($edit != -1) and $PAGE->user_allowed_editing()) {
$USER->editing = $edit; $USER->editing = $edit;
} }
@@ -77,13 +75,15 @@
$available = ($quiz->timeopen < $timenow and ($timenow < $quiz->timeclose or !$quiz->timeclose)) || $isteacher; $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 // Print heading and tabs for teacher
if ($isteacher) { if ($isteacher) {
$currenttab = 'info'; $currenttab = 'info';
include('tabs.php'); include('tabs.php');
} }
// Print quiz name and description.
print_heading(format_string($quiz->name)); print_heading(format_string($quiz->name));
if (trim(strip_tags($quiz->intro))) { if (trim(strip_tags($quiz->intro))) {
@@ -91,12 +91,15 @@
print_simple_box(format_text($quiz->intro, FORMAT_MOODLE, $formatoptions), "center"); print_simple_box(format_text($quiz->intro, FORMAT_MOODLE, $formatoptions), "center");
} }
// Print information about number of attempts and grading method.
if ($quiz->attempts > 1) { if ($quiz->attempts > 1) {
echo "<p align=\"center\">".get_string("attemptsallowed", "quiz").": $quiz->attempts</p>"; echo "<p align=\"center\">".get_string("attemptsallowed", "quiz").": $quiz->attempts</p>";
}
if ($quiz->attempts != 1) {
echo "<p align=\"center\">".get_string("grademethod", "quiz").": ".$QUIZ_GRADE_METHOD[$quiz->grademethod]."</p>"; echo "<p align=\"center\">".get_string("grademethod", "quiz").": ".$QUIZ_GRADE_METHOD[$quiz->grademethod]."</p>";
} else {
echo "<br />";
} }
// Print information about timings.
if ($available) { if ($available) {
if ($quiz->timelimit) { if ($quiz->timelimit) {
echo "<p align=\"center\">".get_string("quiztimelimit","quiz", format_time($quiz->timelimit * 60))."</p>"; echo "<p align=\"center\">".get_string("quiztimelimit","quiz", format_time($quiz->timelimit * 60))."</p>";
@@ -118,296 +121,282 @@
notify("<a href=\"report.php?mode=overview&amp;id=$cm->id\">".get_string('numattempts', 'quiz', $a).'</a>'); notify("<a href=\"report.php?mode=overview&amp;id=$cm->id\">".get_string('numattempts', 'quiz', $a).'</a>');
} }
echo '</td></tr></table>'; end_page($course);
print_footer($course);
exit; exit;
} }
// Guests can't do a quiz, so offer them a choice of logging in going back.
if (isguest()) { if (isguest()) {
$loginurl = $CFG->wwwroot.'/login/index.php';
$wwwroot = $CFG->wwwroot.'/login/index.php';
if (!empty($CFG->loginhttps)) { if (!empty($CFG->loginhttps)) {
$wwwroot = str_replace('http:','https:', $wwwroot); $loginurl = str_replace('http:','https:', $loginurl);
} }
notice_yesno(get_string('guestsno', 'quiz').'<br /><br />'.get_string('liketologin'), notice_yesno('<p>' . get_string('guestsno', 'quiz') . "</p>\n\n</p>" .
$wwwroot, $_SERVER['HTTP_REFERER']); get_string('liketologin') . '</p>', $loginurl, $_SERVER['HTTP_REFERER']);
print_footer($course);
echo '</td></tr></table>'; end_page($course);
exit; exit;
} }
if ($attempts = quiz_get_user_attempts($quiz->id, $USER->id)) { // Get this user's attempts.
$numattempts = count($attempts); $attempts = quiz_get_user_attempts($quiz->id, $USER->id);
} else {
$numattempts = 0;
}
$unfinished = false; $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; $attempts[] = $unfinishedattempt;
$unfinished = true; $unfinished = true;
} }
$numattempts = count($attempts);
$strattempt = get_string("attempt", "quiz"); $strattempt = get_string("attempt", "quiz");
$strtimetaken = get_string("timetaken", "quiz"); $strtimetaken = get_string("timetaken", "quiz");
$strtimecompleted = get_string("timecompleted", "quiz"); $strtimecompleted = get_string("timecompleted", "quiz");
$strgrade = get_string("grade"); $strgrade = get_string("grade");
$strmarks = get_string('marks', 'quiz'); $strmarks = get_string('marks', 'quiz');
$strbestgrade = $QUIZ_GRADE_METHOD[$quiz->grademethod]; $strfeedback = get_string('feedback', 'quiz');
$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";
$mygrade = quiz_get_best_grade($quiz, $USER->id); $mygrade = quiz_get_best_grade($quiz, $USER->id);
/// Now print table with existing attempts
$gradecolumn=0;
$overallstats=1;
if ($attempts) { if ($attempts) {
// Print table with existing attempts
//step thru each attempt, checking there are any attempts
//for which the score can be displayed (need grade columns), // Work out which columns we need, taking account what data is available in each attempt.
//and checking if overall grades can be displayed - no attempts for $gradecolumn = 0;
//which the score cannot be displayed $overallstats = 1;
foreach ($attempts as $attempt) { foreach ($attempts as $attempt) {
$attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $isteacher); $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->head = array($strattempt, $strtimecompleted);
$table->align = array("center", "left"); $table->align = array("center", "left");
$table->size = array("", ""); $table->size = array("", "");
if ($gradecolumn && $quiz->grade and $quiz->sumgrades) { // Grades used so have more columns in table if ($markcolumn) {
if ($quiz->grade <> $quiz->sumgrades) { $table->head[] = "$strmarks / $quiz->sumgrades";
$table->head[] = "$strmarks / $quiz->sumgrades"; $table->align[] = 'right';
$table->align[] = 'right'; $table->size[] = '';
$table->size[] = ''; }
} if ($gradecolumn) {
$table->head[] = "$strgrade / $quiz->grade"; $table->head[] = "$strgrade / $quiz->grade";
$table->align[] = 'right'; $table->align[] = 'right';
$table->size[] = ''; $table->size[] = '';
} }
if ($feedbackcolumn) {
$table->head[] = $strfeedback;
$table->align[] = 'left';
$table->size[] = '';
}
if (isset($quiz->showtimetaken)) { if (isset($quiz->showtimetaken)) {
$table->head[] = $strtimetaken; $table->head[] = $strtimetaken;
$table->align[] = 'center'; $table->align[] = 'left';
$table->size[] = ''; $table->size[] = '';
} }
/// One row for each attempt // One row for each attempt
foreach ($attempts as $attempt) { foreach ($attempts as $attempt) {
$attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $isteacher);
/// prepare strings for time taken and date completed $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 = ''; $timetaken = '';
$datecompleted = ''; $datecompleted = '';
if ($attempt->timefinish > 0) { // attempt has finished if ($attempt->timefinish > 0) {
// attempt has finished
$timetaken = format_time($attempt->timefinish - $attempt->timestart); $timetaken = format_time($attempt->timefinish - $attempt->timestart);
$datecompleted = userdate($attempt->timefinish); $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); $timetaken = format_time(time() - $attempt->timestart);
$datecompleted = "\n".'<script language="javascript" type="text/javascript">'; $datecompleted = '';
$datecompleted .= "\n<!--\n"; // --> } else if ($quiz->timeclose) {
if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) { // The attempt was not completed but is also not available any more becuase the quiz is closed.
$attempturl=sid_process_url("attempt.php?id=$cm->id");
} else {
$attempturl="attempt.php?id=$cm->id";
};
if (!empty($quiz->popup)) {
$datecompleted .= "var 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';\n";
$jslink = "javascript:var popup = window.open(\\'$attempturl\\', \\'quizpopup\\', windowoptions);";
} else {
$jslink = $attempturl;
}
$linktext = get_string('continueattemptquiz', 'quiz');
$datecompleted .= "document.write('<a href=\"$jslink\" alt=\"$linktext\">$linktext</a>');";
$datecompleted .= "\n-->\n";
$datecompleted .= '</script>';
$datecompleted .= '<noscript>';
$datecompleted .= '<strong>'.get_string('noscript', 'quiz').'</strong>';
$datecompleted .= '</noscript>';
} else { // attempt was not completed but is also not available any more.
$timetaken = format_time($quiz->timeclose - $attempt->timestart); $timetaken = format_time($quiz->timeclose - $attempt->timestart);
$datecompleted = $quiz->timeclose ? userdate($quiz->timeclose) : ''; $datecompleted = userdate($quiz->timeclose);
} else {
// Something wheird happened.
$timetaken = '';
$datecompleted = '';
} }
$row[] = $datecompleted;
$attemptoptions = quiz_get_reviewoptions($quiz, $attempt, $isteacher);
/// prepare strings for attempt number, mark and grade if ($markcolumn) {
//if attempt's score is allowed to be viewed, & qz->sumgrades and qz->sumgrades defined: if ($attemptoptions->scores) {
if ($attemptoptions->scores && $quiz->grade and $quiz->sumgrades) { $row[] = make_review_link(round($attempt->sumgrades, $quiz->decimalpoints), $quiz, $attempt);
$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 = "<span class=\"highlight\">$attemptgrade</span>";
}
// 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&amp;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&amp;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&amp;attempt=$attempt->id", 'quizpopup', "#$attempt->attempt", '+window.screen.height+', '+window.screen.width+', '', $windowoptions, true);
} else {
$attemptmark = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">".round($attempt->sumgrades,$quiz->decimalpoints).'</a>';
$attemptgrade = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">$attemptgrade</a>";
$attempt->attempt = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">#$attempt->attempt</a>";
}
}
if ($quiz->grade <> $quiz->sumgrades) {
$table->data[] = array( $attempt->attempt,
$datecompleted,
$attemptmark, $attemptgrade);
} else { } else {
$table->data[] = array( $attempt->attempt, $row[] = '';
$datecompleted,
$attemptgrade);
}
} else { // No grades are being used
if (quiz_review_allowed($quiz)) {
if($attempt->timefinish > 0) {
$attempt->attempt = "<a href=\"review.php?q=$quiz->id&amp;attempt=$attempt->id\">#$attempt->attempt</a>";
} else {
$attempt->attempt = "<a href=\"attempt.php?id=$id\">#$attempt->attempt</a>";
}
} }
}
$helpbutton=helpbutton('missing\ grade', get_string('wheregrade', 'quiz'), 'quiz', true, false, '',true); // Ouside the if becuase we may be showing feedback but not grades.
if($gradecolumn) { $attemptgrade = quiz_rescale_grade($attempt->sumgrades, $quiz);
$table->data[] = array( $attempt->attempt, if ($gradecolumn) {
$datecompleted, if ($attemptoptions->scores) {
$helpbutton); // highlight the highest grade if appropriate
if ($overallstats && !is_null($mygrade) && $attemptgrade == $mygrade && $quiz->grademethod == QUIZ_GRADEHIGHEST) {
$formattedgrade = "<span class='highlight'>$attemptgrade</span>";
} else {
$formattedgrade = $attemptgrade;
}
$row[] = make_review_link($formattedgrade, $quiz, $attempt);
} else { } else {
$table->data[] = array( $attempt->attempt, $row[] = '';
$datecompleted);
} }
} }
if ($feedbackcolumn) {
if ($attemptoptions->feedback) {
$row[] = quiz_feedback_for_grade($attemptgrade, $quiz->id);
} else {
$row[] = '';
}
}
if (isset($quiz->showtimetaken)) { if (isset($quiz->showtimetaken)) {
$table->data[] = $timetaken; $row[] = $timetaken;
} }
$table->data[] = $row;
} }
print_table($table); print_table($table);
} }
if (!$quiz->questions) { // Print information about the student's best score for this quiz if possible.
print_heading(get_string("noquestions", "quiz")); $moreattempts = $numattempts < $quiz->attempts || $quiz->attempts == 0;
} else { if (!$moreattempts) {
if ($numattempts < $quiz->attempts or !$quiz->attempts) { print_heading(get_string("nomoreattempts", "quiz"));
}
if ($available) {
$options["id"] = $cm->id; if ($numattempts && $quiz->sumgrades) {
//if overall stats are allowed (no attemps' grade not visible), if (!is_null($mygrade)) {
//and there is at least one attempt, and quiz->grade: if ($available && $moreattempts) {
if ($overallstats and $numattempts and $quiz->grade) { $strbestgrade = $QUIZ_GRADE_METHOD[$quiz->grademethod];
print_heading("$strbestgrade: $mygrade / $quiz->grade."); $grademessage = "$strbestgrade: $mygrade / $quiz->grade.";
} } else {
$grademessage = get_string("yourfinalgradeis", "quiz", "$mygrade / $quiz->grade");
echo "<br />";
echo "</p>";
echo "<div align=\"center\">";
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') . '<b>'. userdate($lastattempt + $quiz->delay1). '<b>';
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') . '<b>'. userdate($lastattempt + $quiz->delay2). '<b>';
print_simple_box($notify_msg, "center");
}
} else {
print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm);
}
} else {
print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm);
}
echo "</div>\n";
} }
} else {
print_heading(get_string("nomoreattempts", "quiz")); if ($overallstats) {
//if $quiz->grade and $quiz->sumgrades, and student is allowed to print_heading($grademessage);
//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);
if ($feedbackcolumn) {
echo '<p align="center">', quiz_feedback_for_grade($mygrade, $quiz->id), '</p>';
}
}
if (!($moreattempts && $available)) {
print_continue($CFG->webroot . '/course/view.php?id=' . $course->id);
} }
} }
// Finish the page
echo '</td></tr></table>'; if ($quiz->questions) {
// Print a button to start the quiz if appropriate.
if ($available && $moreattempts) {
echo "<br />";
echo "<div align=\"center\">";
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') . '<b>'. userdate($lastattempt + $quiz->delay1). '<b>';
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') . '<b>'. userdate($lastattempt + $quiz->delay2). '<b>';
print_simple_box($notify_msg, "center");
}
} else {
print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm);
}
} else {
print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm);
}
echo "</div>\n";
}
} else {
// No questions in quiz.
print_heading(get_string("noquestions", "quiz"));
}
// Finish the page - this needs to be the same as in the if teacher block above.
echo '</td></tr></table>';
print_footer($course); print_footer($course);
function quiz_review_allowed($quiz) { // Utility functions =================================================================
// If not even responses are to be shown in review then we
// don't allow any review function quiz_review_allowed($quiz) {
if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) { // If not even responses are to be shown in review then we
return false; // don't allow any review
} if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) {
if ((!$quiz->timeclose or time() < $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_OPEN)) { return false;
return false;
}
if (($quiz->timeclose and time() > $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_CLOSED)) {
return false;
}
return true;
} }
if ((!$quiz->timeclose or time() < $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_OPEN)) {
return false;
function print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm) { }
$strconfirmstartattempt = ''; if (($quiz->timeclose and time() > $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_CLOSED)) {
return false;
if ($unfinished) { }
$buttontext = get_string('continueattemptquiz', 'quiz'); return true;
} 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 = 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"; function print_start_quiz_button($quiz, $attempts, $numattempts, $unfinished, $cm) {
if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) { $strconfirmstartattempt = '';
$attempturl = sid_process_url($attempturl);
if ($unfinished) {
$buttontext = get_string('continueattemptquiz', '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 = 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);
}
?> ?>
<script language="javascript" type="text/javascript"> <script language="javascript" type="text/javascript">
<!-- <!--
@@ -422,6 +411,27 @@ document.write('<input type="button" value="<?php echo $buttontext ?>" onclick="
<strong><?php print_string('noscript', 'quiz'); ?></strong> <strong><?php print_string('noscript', 'quiz'); ?></strong>
</noscript> </noscript>
<?php <?php
}
function make_review_link($linktext, $quiz, $attempt) {
$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";
$link = $linktext;
if ($attempt->timefinish && quiz_review_allowed($quiz)) {
$url = "review.php?q=$quiz->id&amp;attempt=$attempt->id";
if ($quiz->popup) {
$link = link_to_popup_window('/mod/quiz/' . $url, 'quizpopup', $linktext, '+window.screen.height+', '+window.screen.width+', '', $windowoptions, true);
} else {
$link = "<a href='$url'>$linktext</a>";
}
} }
return $link;
}
function end_page($course) {
echo '</td></tr></table>';
print_footer($course);
}
?> ?>