3 * Quiz report to help teachers manually grade quiz questions that need it.
10 // Get variables, run essential queries
11 // Check for post data submitted. If exists, then process data (the data is the grades and comments for essay questions)
12 // Check for userid, attemptid, or gradeall and for questionid. If found, print out the appropriate essay question attempts
14 // first case: print out all essay questions in quiz and the number of ungraded attempts
15 // second case: print out all users and their attempts for a specific essay question
17 require_once($CFG->dirroot
. "/mod/quiz/editlib.php");
18 require_once($CFG->libdir
. '/tablelib.php');
21 * Quiz report to help teachers manually grade quiz questions that need it.
26 class quiz_report
extends quiz_default_report
{
28 * Displays the report.
30 function display($quiz, $cm, $course) {
32 $action = optional_param('action', 'viewquestions', PARAM_ALPHA
);
33 $questionid = optional_param('questionid', 0, PARAM_INT
);
35 $this->print_header_and_tabs($cm, $course, $quiz, $reportmode="grading");
37 if (!empty($questionid)) {
38 if (! $question = get_record('question', 'id', $questionid)) {
39 error("Question with id $questionid not found");
41 $question->maxgrade
= get_field('quiz_question_instances', 'grade', 'quiz', $quiz->id
, 'question', $question->id
);
43 // Some of the questions code is optimised to work with several questions
44 // at once so it wants the question to be in an array. The array key
45 // must be the question id.
47 $questions[$key] = &$question;
49 // We need to add additional questiontype specific information to
50 // the question objects.
51 if (!get_question_options($questions)) {
52 error("Unable to load questiontype specific question information");
54 // This will have extended the question object so that it now holds
55 // all the information about the questions that may be needed later.
58 add_to_log($course->id
, "quiz", "manualgrading", "report.php?mode=grading&q=$quiz->id", "$quiz->id", "$cm->id");
60 echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
62 if ($data = data_submitted()) { // post data submitted, process it
65 // now go through all of the responses and save them.
66 foreach($data->manualgrades
as $uniqueid => $response) {
68 if (! $attempt = get_record('quiz_attempts', 'uniqueid', $uniqueid)) {
69 error('No such attempt ID exists');
72 // Load the state for this attempt (The questions array was created earlier)
73 $states = get_question_states($questions, $quiz, $attempt);
74 // The $states array is indexed by question id but because we are dealing
75 // with only one question there is only one entry in this array
76 $state = &$states[$question->id
];
78 // the following will update the state and attempt
79 question_process_comment($question, $state, $attempt, $response['comment'], $response['grade']);
81 // If the state has changed save it and update the quiz grade
82 if ($state->changed
) {
83 save_question_session($question, $state);
84 quiz_save_best_grade($quiz, $attempt->userid
);
87 notify(get_string('changessaved', 'quiz'));
90 // our 3 different views
91 // the first one displays all of the manually graded questions in the quiz
92 // with the number of ungraded attempts for each question
94 // the second view displays the users who have answered the essay question
95 // and all of their attempts at answering the question
97 // the third prints the question with a comment
98 // and grade form underneath it
101 case 'viewquestions':
102 $this->view_questions($quiz);
105 $this->view_question($quiz, $question);
108 $this->print_questions_and_form($quiz, $question);
115 * Prints a table containing all manually graded questions
117 * @param object $quiz Quiz object of the currrent quiz
118 * @param object $course Course object of the current course
119 * @param string $userids Comma-separated list of userids in this course
121 * @todo Look for the TODO in this code to see what still needs to be done
123 function view_questions($quiz) {
124 global $CFG, $QTYPE_MANUAL;
126 $users = get_course_students($quiz->course
);
129 print_heading(get_string("noattempts", "quiz"));
134 $table = new stdClass
;
135 $table->head
= array(get_string("essayquestions", "quiz"), get_string("ungraded", "quiz"));
136 $table->align
= array("left", "left");
137 $table->wrap
= array("wrap", "wrap");
138 $table->width
= "20%";
139 $table->size
= array("*", "*");
140 $table->data
= array();
142 // get the essay questions
143 $questionlist = quiz_questions_in_quiz($quiz->questions
);
144 $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
145 " FROM {$CFG->prefix}question q,".
146 " {$CFG->prefix}quiz_question_instances i".
147 " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
148 " AND q.id IN ($questionlist)".
149 " AND q.qtype IN ($QTYPE_MANUAL)".
151 if (empty($questionlist) or !$questions = get_records_sql($sql)) {
152 print_heading(get_string('noessayquestionsfound', 'quiz'));
156 notify(get_string('essayonly', 'quiz_grading'));
158 // get all the finished attempts by the users
159 $userids = implode(', ', array_keys($users));
160 if ($attempts = get_records_select('quiz_attempts', "quiz = $quiz->id and timefinish > 0 AND userid IN ($userids) AND preview = 0", 'userid, attempt')) {
161 foreach($questions as $question) {
163 $link = "<a href=\"report.php?mode=grading&q=$quiz->id&action=viewquestion&questionid=$question->id\">".
164 $question->name
."</a>";
165 // determine the number of ungraded attempts
167 foreach ($attempts as $attempt) {
168 if (!$this->is_graded($question, $attempt)) {
173 $table->data
[] = array($link, $ungraded);
177 print_heading(get_string('noattempts', 'quiz'));
184 * Prints a table with users and their attempts
187 * @todo Add current grade to the table
188 * Finnish documenting
190 function view_question($quiz, $question) {
193 $users = get_course_students($quiz->course
);
194 $userids = implode(',', array_keys($users));
195 $usercount = count($users);
198 $tablecolumns = array('picture', 'fullname', 'timefinish', 'grade');
199 $tableheaders = array('', get_string('fullname'), get_string("completedon", "quiz"), '');
201 $table = new flexible_table('mod-quiz-report-grading');
203 $table->define_columns($tablecolumns);
204 $table->define_headers($tableheaders);
205 $table->define_baseurl($CFG->wwwroot
.'/mod/quiz/report.php?mode=grading&q='.$quiz->id
.'&action=viewquestion&questionid='.$question->id
);
207 $table->sortable(true);
208 $table->initialbars($usercount>20); // will show initialbars if there are more than 20 users
209 $table->pageable(true);
210 $table->collapsible(true);
212 $table->column_suppress('fullname');
213 $table->column_suppress('picture');
214 $table->column_suppress('grade');
216 $table->column_class('picture', 'picture');
218 // attributes in the table tag
219 $table->set_attribute('cellspacing', '0');
220 $table->set_attribute('id', 'attempts');
221 $table->set_attribute('class', 'generaltable generalbox');
222 $table->set_attribute('align', 'center');
223 //$table->set_attribute('width', '50%');
228 // this sql is a join of the attempts table and the user table. I do this so I can sort by user name and attempt number (not id)
229 $select = 'SELECT '.sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS userattemptid, qa.id AS attemptid, qa.uniqueid, qa.attempt, qa.timefinish, u.id AS userid, u.firstname, u.lastname, u.picture ';
230 $from = 'FROM '.$CFG->prefix
.'user u LEFT JOIN '.$CFG->prefix
.'quiz_attempts qa ON (u.id = qa.userid AND qa.quiz = '.$quiz->id
.') ';
231 $where = 'WHERE u.id IN ('.$userids.') ';
232 $where .= 'AND '.$db->IfNull('qa.attempt', '0').' != 0 ';
233 $where .= 'AND '.$db->IfNull('qa.timefinish', '0').' != 0 ';
234 $where .= 'AND preview = 0 '; // ignore previews
236 if($table->get_sql_where()) { // forgot what this does
237 $where .= 'AND '.$table->get_sql_where();
240 // sorting of the table
241 if($sort = $table->get_sql_sort()) {
242 $sort = 'ORDER BY '.$sort; // seems like I would need to have u. or qa. infront of the ORDER BY attribues... but seems to work..
244 // my default sort rule
245 $sort = 'ORDER BY u.firstname, u.lastname, qa.timefinish ASC';
248 // set up the pagesize
249 $total = count_records_sql('SELECT COUNT(DISTINCT('.sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).')) '.$from.$where);
250 $table->pagesize(10, $total);
252 // get the attempts and process them
253 if ($attempts = get_records_sql($select.$from.$where.$sort,$table->get_page_start(), $table->get_page_size())) {
254 foreach($attempts as $attempt) {
256 $picture = print_user_picture($attempt->userid
, $quiz->course
, $attempt->picture
, false, true);
258 // link to student profile
259 $userlink = "<a href=\"$CFG->wwwroot/user/view.php?id=$attempt->userid&course=$quiz->course\">".
260 fullname($attempt, true).'</a>';
262 if (!$this->is_graded($question, $attempt)) {
263 $style = 'class="manual-ungraded"';
265 $style = 'class="manual-graded"';
268 // link for the attempt
269 $attemptlink = "<a $style href=\"report.php?mode=grading&action=grade&q=$quiz->id&questionid=$question->id&attemptid=$attempt->attemptid\">".
270 userdate($attempt->timefinish
, get_string('strftimedatetime')).'</a>';
272 // grade all attempts for this user
273 $gradelink = "<a href=\"report.php?mode=grading&action=grade&q=$quiz->id&questionid=$question->id&userid=$attempt->userid\">".
274 get_string('grade').'</a>';
276 $table->add_data( array($picture, $userlink, $attemptlink, $gradelink) );
280 // grade all and "back" links
281 $links = "<div class=\"boxaligncenter\"><a href=\"report.php?mode=grading&action=grade&q=$quiz->id&questionid=$question->id&gradeall=1\">".get_string('gradeall', 'quiz').'</a> | '.
282 "<a href=\"report.php?mode=grading&q=$quiz->id&action=viewquestions\">".get_string('backtoquestionlist', 'quiz').'</a></div>'.
284 // print everything here
285 print_heading($question->name
);
287 echo '<div id="tablecontainer">';
288 $table->print_html();
294 * Checks to see if a question in a particular attempt is graded
297 * @todo Finnish documenting this function
299 function is_graded($question, $attempt) {
302 if (!$state = get_record_sql("SELECT state.id, state.event FROM
303 {$CFG->prefix}question_states state, {$CFG->prefix}question_sessions sess
304 WHERE sess.newest = state.id AND
305 sess.attemptid = $attempt->uniqueid AND
306 sess.questionid = $question->id")) {
307 error('Could not find question state');
310 return question_state_is_graded($state);
314 * Prints questions with comment and grade form underneath each question
317 * @todo Finish documenting this function
319 function print_questions_and_form($quiz, $question) {
322 // grade question specific parameters
323 $gradeall = optional_param('gradeall', 0, PARAM_INT
);
324 $userid = optional_param('userid', 0, PARAM_INT
);
325 $attemptid = optional_param('attemptid', 0, PARAM_INT
);
327 // TODO get the context, and put in proper roles an permissions checks.
330 $questions[$question->id
] = &$question;
331 $usehtmleditor = can_use_richtext_editor();
332 $users = get_course_students($quiz->course
);
333 $userids = implode(',', array_keys($users));
335 // this sql joins the attempts table and the user table
336 $select = 'SELECT '.sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')).' AS userattemptid,
337 qa.id AS attemptid, qa.uniqueid, qa.attempt, qa.timefinish, qa.preview,
338 u.id AS userid, u.firstname, u.lastname, u.picture ';
339 $from = 'FROM '.$CFG->prefix
.'user u LEFT JOIN '.$CFG->prefix
.'quiz_attempts qa ON (u.id = qa.userid AND qa.quiz = '.$quiz->id
.') ';
341 if ($gradeall) { // get all user attempts
342 $where = 'WHERE u.id IN ('.$userids.') ';
343 } else if ($userid) { // get all the attempts for a specific user
344 $where = 'WHERE u.id='.$userid.' ';
345 } else { // get a specific attempt
346 $where = 'WHERE qa.id='.$attemptid.' ';
350 $where .= ' AND preview = 0 ';
352 $where .= 'AND '.$db->IfNull('qa.attempt', '0').' != 0 ';
353 $where .= 'AND '.$db->IfNull('qa.timefinish', '0').' != 0 ';
354 $sort = 'ORDER BY u.firstname, u.lastname, qa.attempt ASC';
355 $attempts = get_records_sql($select.$from.$where.$sort);
357 // Display the form with one part for each selected attempt
359 echo '<form method="post" action="report.php">'.
360 '<input type="hidden" name="mode" value="grading" />'.
361 '<input type="hidden" name="q" value="'.$quiz->id
.'">'.
362 '<input type="hidden" name="sesskey" value="'.sesskey().'" />'.
363 '<input type="hidden" name="action" value="viewquestion" />'.
364 '<input type="hidden" name="questionid" value="'.$question->id
.'">';
366 foreach ($attempts as $attempt) {
368 // Load the state for this attempt (The questions array was created earlier)
369 $states = get_question_states($questions, $quiz, $attempt);
370 // The $states array is indexed by question id but because we are dealing
371 // with only one question there is only one entry in this array
372 $state = &$states[$question->id
];
374 $options = quiz_get_reviewoptions($quiz, $attempt, $context);
375 unset($options->questioncommentlink
);
376 $copy = $state->manualcomment
;
377 $state->manualcomment
= '';
379 $options->readonly
= 1;
381 // print the user name, attempt count, the question, and some more hidden fields
382 echo '<div class="boxaligncenter" width="80%" style="padding:15px;">'.
383 fullname($attempt, true).': '.
384 get_string('attempt', 'quiz').$attempt->attempt
;
386 print_question($question, $state, '', $quiz, $options);
388 $prefix = "manualgrades[$attempt->uniqueid]";
389 $grade = round($state->last_graded
->grade
, 3);
390 $state->manualcomment
= $copy;
392 include($CFG->dirroot
. '/question/comment.html');
396 echo '<div class="boxaligncenter"><input type="submit" value="'.get_string('savechanges').'" /></div>'.
399 if ($usehtmleditor) {