MDL-11075 Now saving to temp file, then outputting using filelib's readfile_chunked...
[moodle-pu.git] / mod / quiz / report / grading / report.php
blob846c741280c8fda4de810e82149fa02933b5c20e
1 <?php // $Id$
2 /**
3 * Quiz report to help teachers manually grade quiz questions that need it.
5 * @package quiz
6 * @subpackage reports
7 */
9 // Flow of the file:
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
13 // Switch:
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');
20 /**
21 * Quiz report to help teachers manually grade quiz questions that need it.
23 * @package quiz
24 * @subpackage reports
26 class quiz_report extends quiz_default_report {
27 /**
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.
46 $key = $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&amp;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
63 confirm_sesskey();
65 // now go through all of the responses and save them.
66 foreach($data->manualgrades as $uniqueid => $response) {
67 // get our attempt
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
100 switch($action) {
101 case 'viewquestions':
102 $this->view_questions($quiz);
103 break;
104 case 'viewquestion':
105 $this->view_question($quiz, $question);
106 break;
107 case 'grade':
108 $this->print_questions_and_form($quiz, $question);
109 break;
111 return true;
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
120 * @return boolean
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);
128 if(empty($users)) {
129 print_heading(get_string("noattempts", "quiz"));
130 return true;
133 // setup the table
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)".
150 " ORDER BY q.name";
151 if (empty($questionlist) or !$questions = get_records_sql($sql)) {
152 print_heading(get_string('noessayquestionsfound', 'quiz'));
153 return false;
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&amp;q=$quiz->id&amp;action=viewquestion&amp;questionid=$question->id\">".
164 $question->name."</a>";
165 // determine the number of ungraded attempts
166 $ungraded = 0;
167 foreach ($attempts as $attempt) {
168 if (!$this->is_graded($question, $attempt)) {
169 $ungraded++;
173 $table->data[] = array($link, $ungraded);
175 print_table($table);
176 } else {
177 print_heading(get_string('noattempts', 'quiz'));
180 return true;
184 * Prints a table with users and their attempts
186 * @return void
187 * @todo Add current grade to the table
188 * Finnish documenting
190 function view_question($quiz, $question) {
191 global $CFG, $db;
193 $users = get_course_students($quiz->course);
194 $userids = implode(',', array_keys($users));
195 $usercount = count($users);
197 // set up table
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&amp;q='.$quiz->id.'&amp;action=viewquestion&amp;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%');
225 // get it ready!
226 $table->setup();
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..
243 } else {
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&amp;course=$quiz->course\">".
260 fullname($attempt, true).'</a>';
262 if (!$this->is_graded($question, $attempt)) {
263 $style = 'class="manual-ungraded"';
264 } else {
265 $style = 'class="manual-graded"';
268 // link for the attempt
269 $attemptlink = "<a $style href=\"report.php?mode=grading&amp;action=grade&amp;q=$quiz->id&amp;questionid=$question->id&amp;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&amp;action=grade&amp;q=$quiz->id&amp;questionid=$question->id&amp;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&amp;action=grade&amp;q=$quiz->id&amp;questionid=$question->id&amp;gradeall=1\">".get_string('gradeall', 'quiz').'</a> | '.
282 "<a href=\"report.php?mode=grading&amp;q=$quiz->id&amp;action=viewquestions\">".get_string('backtoquestionlist', 'quiz').'</a></div>'.
284 // print everything here
285 print_heading($question->name);
286 echo $links;
287 echo '<div id="tablecontainer">';
288 $table->print_html();
289 echo '</div>';
290 echo $links;
294 * Checks to see if a question in a particular attempt is graded
296 * @return boolean
297 * @todo Finnish documenting this function
299 function is_graded($question, $attempt) {
300 global $CFG;
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
316 * @return void
317 * @todo Finish documenting this function
319 function print_questions_and_form($quiz, $question) {
320 global $CFG, $db;
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.
328 $context = NULL;
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.' ';
349 // ignore previews
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');
394 echo '</div>';
396 echo '<div class="boxaligncenter"><input type="submit" value="'.get_string('savechanges').'" /></div>'.
397 '</form>';
399 if ($usehtmleditor) {
400 use_html_editor();