MDL-32214 quiz regrading: with many attempts, PHP runs out of memory
[moodle.git] / mod / quiz / report / overview / report.php
CommitLineData
2c3968c4 1<?php
e24ee794
TH
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
2c3968c4 17/**
e24ee794 18 * This file defines the quiz overview report class.
2c3968c4 19 *
e24ee794
TH
20 * @package quiz
21 * @subpackage overview
22 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
98f38217 24 */
2c3968c4 25
e24ee794 26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
e24ee794 29require_once($CFG->dirroot.'/mod/quiz/report/attemptsreport.php');
0217f932 30require_once($CFG->dirroot.'/mod/quiz/report/overview/overviewsettings_form.php');
c35f3afc 31require_once($CFG->dirroot.'/mod/quiz/report/overview/overview_table.php');
7bbe08a2 32
7bbe08a2 33
e24ee794
TH
34/**
35 * Quiz report subclass for the overview (grades) report.
36 *
f7970e3c
TH
37 * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
e24ee794
TH
39 */
40class quiz_overview_report extends quiz_attempt_report {
41
c7df5006 42 public function display($quiz, $cm, $course) {
90cd54cb 43 global $CFG, $COURSE, $DB, $OUTPUT;
7bbe08a2 44
98f38217 45 $this->context = get_context_instance(CONTEXT_MODULE, $cm->id);
07a7d859 46
c35f3afc 47 $download = optional_param('download', '', PARAM_ALPHA);
aeb15530 48
e24ee794 49 list($currentgroup, $students, $groupstudents, $allowed) =
e4977ba5 50 $this->load_relevant_students($cm, $course);
aeb15530 51
0217f932 52 $pageoptions = array();
53 $pageoptions['id'] = $cm->id;
0217f932 54 $pageoptions['mode'] = 'overview';
55
a6855934 56 $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
334edb71 57 $qmsubselect = quiz_report_qm_filter_select($quiz);
58
e24ee794 59 $mform = new mod_quiz_report_overview_settings($reporturl,
2daffca5
TH
60 array('qmsubselect' => $qmsubselect, 'quiz' => $quiz,
61 'currentgroup' => $currentgroup, 'context' => $this->context));
e24ee794 62
07a2b2f0 63 if ($fromform = $mform->get_data()) {
98f38217 64 $regradeall = false;
65 $regradealldry = false;
66 $regradealldrydo = false;
0217f932 67 $attemptsmode = $fromform->attemptsmode;
07a2b2f0 68 if ($qmsubselect) {
4469159e 69 $qmfilter = $fromform->qmfilter;
70 } else {
71 $qmfilter = 0;
72 }
e24ee794 73 $regradefilter = !empty($fromform->regradefilter);
0217f932 74 set_user_preference('quiz_report_overview_detailedmarks', $fromform->detailedmarks);
75 set_user_preference('quiz_report_pagesize', $fromform->pagesize);
76 $detailedmarks = $fromform->detailedmarks;
77 $pagesize = $fromform->pagesize;
e24ee794 78
0217f932 79 } else {
98f38217 80 $regradeall = optional_param('regradeall', 0, PARAM_BOOL);
81 $regradealldry = optional_param('regradealldry', 0, PARAM_BOOL);
82 $regradealldrydo = optional_param('regradealldrydo', 0, PARAM_BOOL);
7660aa80 83 $attemptsmode = optional_param('attemptsmode', null, PARAM_INT);
07a2b2f0 84 if ($qmsubselect) {
98f38217 85 $qmfilter = optional_param('qmfilter', 0, PARAM_INT);
86 } else {
87 $qmfilter = 0;
88 }
89 $regradefilter = optional_param('regradefilter', 0, PARAM_INT);
0217f932 90 $detailedmarks = get_user_preferences('quiz_report_overview_detailedmarks', 1);
91 $pagesize = get_user_preferences('quiz_report_pagesize', 0);
92 }
e24ee794
TH
93
94 $this->validate_common_options($attemptsmode, $pagesize, $course, $currentgroup);
95 $displayoptions = array();
96 $displayoptions['attemptsmode'] = $attemptsmode;
97 $displayoptions['qmfilter'] = $qmfilter;
98 $displayoptions['regradefilter'] = $regradefilter;
99
25a03faa
TH
100 $mform->set_data($displayoptions +
101 array('detailedmarks' => $detailedmarks, 'pagesize' => $pagesize));
e24ee794
TH
102
103 if (!$this->should_show_grades($quiz)) {
ca359748 104 $detailedmarks = 0;
105 }
e24ee794 106
2fecd85b 107 // We only want to show the checkbox to delete attempts
108 // if the user has permissions and if the report mode is showing attempts.
554de0d7
TH
109 $includecheckboxes = has_any_capability(
110 array('mod/quiz:regrade', 'mod/quiz:deleteattempts'), $this->context)
f05fedc8 111 && ($attemptsmode != QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO);
2fecd85b 112
f05fedc8 113 if ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
e24ee794
TH
114 // This option is only available to users who can access all groups in
115 // groups mode, so setting allowed to empty (which means all quiz attempts
116 // are accessible, is not a security porblem.
f05fedc8
TH
117 $allowed = array();
118 }
119
8ebbb06a 120 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
0eafc988
TH
121 $courseshortname = format_string($course->shortname, true,
122 array('context' => $coursecontext));
8ebbb06a
SH
123
124 $displaycoursecontext = get_context_instance(CONTEXT_COURSE, $COURSE->id);
0eafc988
TH
125 $displaycourseshortname = format_string($COURSE->shortname, true,
126 array('context' => $displaycoursecontext));
8ebbb06a 127
e24ee794 128 // Load the required questions.
88b3eb8b 129 $questions = quiz_report_get_significant_questions($quiz);
e24ee794
TH
130
131 $table = new quiz_report_overview_table($quiz, $this->context, $qmsubselect,
7ddfd168
TH
132 $qmfilter, $attemptsmode, $groupstudents, $students, $detailedmarks,
133 $questions, $includecheckboxes, $reporturl, $displayoptions);
e24ee794 134 $filename = quiz_report_download_filename(get_string('overviewfilename', 'quiz_overview'),
8ebbb06a 135 $courseshortname, $quiz->name);
e24ee794 136 $table->is_downloading($download, $filename,
8ebbb06a 137 $displaycourseshortname . ' ' . format_string($quiz->name, true));
e24ee794
TH
138 if ($table->is_downloading()) {
139 raise_memory_limit(MEMORY_EXTRA);
140 }
141
142 // Process actions.
db77f410
TH
143 if (empty($currentgroup) || $groupstudents) {
144 if (optional_param('delete', 0, PARAM_BOOL) && confirm_sesskey()) {
18bd7573 145 if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) {
db77f410 146 require_capability('mod/quiz:deleteattempts', $this->context);
e24ee794 147 $this->delete_selected_attempts($quiz, $cm, $attemptids, $allowed);
db77f410
TH
148 redirect($reporturl->out(false, $displayoptions));
149 }
e24ee794 150
db77f410 151 } else if (optional_param('regrade', 0, PARAM_BOOL) && confirm_sesskey()) {
18bd7573 152 if ($attemptids = optional_param_array('attemptid', array(), PARAM_INT)) {
e24ee794
TH
153 require_capability('mod/quiz:regrade', $this->context);
154 $this->regrade_attempts($quiz, false, $groupstudents, $attemptids);
db77f410
TH
155 redirect($reporturl->out(false, $displayoptions));
156 }
157 }
158 }
159
db77f410 160 if ($regradeall && confirm_sesskey()) {
e24ee794
TH
161 require_capability('mod/quiz:regrade', $this->context);
162 $this->regrade_attempts($quiz, false, $groupstudents);
163 redirect($reporturl->out(false, $displayoptions), '', 5);
164
db77f410 165 } else if ($regradealldry && confirm_sesskey()) {
e24ee794
TH
166 require_capability('mod/quiz:regrade', $this->context);
167 $this->regrade_attempts($quiz, true, $groupstudents);
168 redirect($reporturl->out(false, $displayoptions), '', 5);
169
db77f410 170 } else if ($regradealldrydo && confirm_sesskey()) {
e24ee794
TH
171 require_capability('mod/quiz:regrade', $this->context);
172 $this->regrade_attempts_needing_it($quiz, $groupstudents);
b9bc2019 173 redirect($reporturl->out(false, $displayoptions), '', 5);
98f38217 174 }
aeb15530 175
e24ee794
TH
176 // Start output.
177 if (!$table->is_downloading()) {
178 // Only print headers if not asked to download data
179 $this->print_header_and_tabs($cm, $course, $quiz, 'overview');
180 }
181
5ada3c8e 182 if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
c35f3afc 183 if (!$table->is_downloading()) {
ef770037 184 groups_print_activity_menu($cm, $reporturl->out(true, $displayoptions));
970d0fe0 185 }
2d7617c6 186 }
98f38217 187
e24ee794
TH
188 // Print information on the number of existing attempts
189 if (!$table->is_downloading()) { //do not print notices when downloading
190 if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) {
191 echo '<div class="quizattemptcounts">' . $strattemptnum . '</div>';
192 }
193 }
194
3c6185e9
TH
195 $hasquestions = quiz_questions_in_quiz($quiz->questions);
196 if (!$table->is_downloading()) {
197 if (!$hasquestions) {
198 echo quiz_no_questions_message($quiz, $cm, $this->context);
199 } else if (!$students) {
414e7276 200 echo $OUTPUT->notification(get_string('nostudentsyet'));
3c6185e9 201 } else if ($currentgroup && !$groupstudents) {
414e7276
TH
202 echo $OUTPUT->notification(get_string('nostudentsingroup'));
203 }
e24ee794 204
95758992 205 // Print display options
95758992 206 $mform->display();
f6c7f158 207 }
39790bd8 208
3c6185e9
TH
209 $hasstudents = $students && (!$currentgroup || $groupstudents);
210 if ($hasquestions && ($hasstudents || ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL))) {
abe67b24 211 // Construct the SQL
25a03faa
TH
212 $fields = $DB->sql_concat('u.id', "'#'", 'COALESCE(quiza.attempt, 0)') .
213 ' AS uniqueid, ';
55caa1d5 214 if ($qmsubselect) {
215 $fields .=
216 "(CASE " .
217 " WHEN $qmsubselect THEN 1" .
218 " ELSE 0 " .
219 "END) AS gradedattempt, ";
220 }
aeb15530 221
7ddfd168 222 list($fields, $from, $where, $params) = $table->base_sql($allowed);
aeb15530 223
abe67b24 224 $table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);
98f38217 225
e24ee794 226 // Test to see if there are any regraded attempts to be listed.
25a03faa
TH
227 $fields .= ", COALESCE((
228 SELECT MAX(qqr.regraded)
229 FROM {quiz_overview_regrades} qqr
230 WHERE qqr.questionusageid = quiza.uniqueid
231 ), -1) AS regraded";
07a2b2f0 232 if ($regradefilter) {
25a03faa
TH
233 $where .= " AND COALESCE((
234 SELECT MAX(qqr.regraded)
235 FROM {quiz_overview_regrades} qqr
236 WHERE qqr.questionusageid = quiza.uniqueid
237 ), -1) <> -1";
98f38217 238 }
abe67b24 239 $table->set_sql($fields, $from, $where, $params);
aeb15530 240
e24ee794
TH
241 if (!$table->is_downloading()) {
242 // Regrade buttons
07a2b2f0 243 if (has_capability('mod/quiz:regrade', $this->context)) {
e24ee794
TH
244 $regradesneeded = $this->count_question_attempts_needing_regrade(
245 $quiz, $groupstudents);
07a2b2f0 246 if ($currentgroup) {
39790bd8 247 $a= new stdClass();
f29e6691 248 $a->groupname = groups_get_group_name($currentgroup);
9101efd3 249 $a->coursestudents = get_string('participants');
e24ee794 250 $a->countregradeneeded = $regradesneeded;
25a03faa
TH
251 $regradealldrydolabel =
252 get_string('regradealldrydogroup', 'quiz_overview', $a);
253 $regradealldrylabel =
254 get_string('regradealldrygroup', 'quiz_overview', $a);
255 $regradealllabel =
256 get_string('regradeallgroup', 'quiz_overview', $a);
f29e6691 257 } else {
25a03faa
TH
258 $regradealldrydolabel =
259 get_string('regradealldrydo', 'quiz_overview', $regradesneeded);
260 $regradealldrylabel =
261 get_string('regradealldry', 'quiz_overview');
262 $regradealllabel =
263 get_string('regradeall', 'quiz_overview');
f29e6691 264 }
25a03faa
TH
265 $displayurl = new moodle_url($reporturl,
266 $displayoptions + array('sesskey' => sesskey()));
f29e6691 267 echo '<div class="mdl-align">';
eb788065 268 echo '<form action="'.$displayurl->out_omit_querystring().'">';
f29e6691 269 echo '<div>';
6ea66ff3 270 echo html_writer::input_hidden_params($displayurl);
f29e6691 271 echo '<input type="submit" name="regradeall" value="'.$regradealllabel.'"/>';
25a03faa
TH
272 echo '<input type="submit" name="regradealldry" value="' .
273 $regradealldrylabel . '"/>';
e24ee794 274 if ($regradesneeded) {
25a03faa
TH
275 echo '<input type="submit" name="regradealldrydo" value="' .
276 $regradealldrydolabel . '"/>';
f29e6691 277 }
278 echo '</div>';
279 echo '</form>';
280 echo '</div>';
281 }
282 // Print information on the grading method
25a03faa
TH
283 if ($strattempthighlight = quiz_report_highlighting_grading_method(
284 $quiz, $qmsubselect, $qmfilter)) {
f29e6691 285 echo '<div class="quizattemptcounts">' . $strattempthighlight . '</div>';
286 }
287 }
aeb15530 288
e24ee794
TH
289 // Define table columns
290 $columns = array();
291 $headers = array();
aeb15530 292
554de0d7 293 if (!$table->is_downloading() && $includecheckboxes) {
e24ee794 294 $columns[] = 'checkbox';
25a03faa 295 $headers[] = null;
abe67b24 296 }
aeb15530 297
e24ee794 298 $this->add_user_columns($table, $columns, $headers);
aeb15530 299
e24ee794 300 $this->add_time_columns($columns, $headers);
aeb15530 301
abe67b24 302 if ($detailedmarks) {
e24ee794 303 foreach ($questions as $slot => $question) {
abe67b24 304 // Ignore questions of zero length
e24ee794
TH
305 $columns[] = 'qsgrade' . $slot;
306 $header = get_string('qbrief', 'quiz', $question->number);
98f38217 307 if (!$table->is_downloading()) {
e24ee794 308 $header .= '<br />';
98f38217 309 } else {
e24ee794 310 $header .= ' ';
98f38217 311 }
e24ee794 312 $header .= '/' . quiz_rescale_grade($question->maxmark, $quiz, 'question');
98f38217 313 $headers[] = $header;
eacb462e 314 }
abe67b24 315 }
e24ee794
TH
316
317 if (!$table->is_downloading() && has_capability('mod/quiz:regrade', $this->context) &&
318 $this->has_regraded_questions($from, $where, $params)) {
98f38217 319 $columns[] = 'regraded';
320 $headers[] = get_string('regrade', 'quiz_overview');
321 }
aeb15530 322
6d54b0f7 323 $this->add_grade_columns($quiz, $columns, $headers, false);
aeb15530 324
25a03faa
TH
325 $this->set_up_table_columns(
326 $table, $columns, $headers, $reporturl, $displayoptions, false);
e24ee794 327 $table->set_attribute('class', 'generaltable generalbox grades');
aeb15530 328
abe67b24 329 $table->out($pagesize, true);
7bbe08a2 330 }
e24ee794
TH
331
332 if (!$table->is_downloading() && $this->should_show_grades($quiz)) {
07a2b2f0 333 if ($currentgroup && $groupstudents) {
f29e6691 334 list($usql, $params) = $DB->get_in_or_equal($groupstudents);
335 $params[] = $quiz->id;
25a03faa
TH
336 if ($DB->record_exists_select('quiz_grades', "userid $usql AND quiz = ?",
337 $params)) {
338 $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php',
339 array('id' => $quiz->id, 'groupid' => $currentgroup));
340 $graphname = get_string('overviewreportgraphgroup', 'quiz_overview',
341 groups_get_group_name($currentgroup));
90cd54cb 342 echo $OUTPUT->heading($graphname);
25a03faa
TH
343 echo html_writer::tag('div', html_writer::empty_tag('img',
344 array('src' => $imageurl, 'alt' => $graphname)),
345 array('class' => 'graph'));
f29e6691 346 }
347 }
e24ee794 348
07a2b2f0 349 if ($DB->record_exists('quiz_grades', array('quiz'=> $quiz->id))) {
f29e6691 350 $graphname = get_string('overviewreportgraph', 'quiz_overview');
25a03faa
TH
351 $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php',
352 array('id' => $quiz->id));
90cd54cb 353 echo $OUTPUT->heading($graphname);
25a03faa
TH
354 echo html_writer::tag('div', html_writer::empty_tag('img',
355 array('src' => $imageurl, 'alt' => $graphname)),
356 array('class' => 'graph'));
aad5b0fc 357 }
78517b5a 358 }
7bbe08a2 359 return true;
360 }
e24ee794 361
98f38217 362 /**
e24ee794
TH
363 * Regrade a particular quiz attempt. Either for real ($dryrun = false), or
364 * as a pretend regrade to see which fractions would change. The outcome is
365 * stored in the quiz_overview_regrades table.
366 *
367 * Note, $attempt is not upgraded in the database. The caller needs to do that.
368 * However, $attempt->sumgrades is updated, if this is not a dry run.
369 *
370 * @param object $attempt the quiz attempt to regrade.
f7970e3c 371 * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real.
e24ee794
TH
372 * @param array $slots if null, regrade all questions, otherwise, just regrade
373 * the quetsions with those slots.
98f38217 374 */
e24ee794
TH
375 protected function regrade_attempt($attempt, $dryrun = false, $slots = null) {
376 global $DB;
01118710 377 set_time_limit(30);
98f38217 378
e24ee794 379 $transaction = $DB->start_delegated_transaction();
98f38217 380
e24ee794
TH
381 $quba = question_engine::load_questions_usage_by_activity($attempt->uniqueid);
382
383 if (is_null($slots)) {
384 $slots = $quba->get_slots();
98f38217 385 }
e24ee794
TH
386
387 $finished = $attempt->timefinish > 0;
388 foreach ($slots as $slot) {
0ff4bd08 389 $qqr = new stdClass();
e24ee794
TH
390 $qqr->oldfraction = $quba->get_question_fraction($slot);
391
392 $quba->regrade_question($slot, $finished);
393
394 $qqr->newfraction = $quba->get_question_fraction($slot);
395
396 if (abs($qqr->oldfraction - $qqr->newfraction) > 1e-7) {
397 $qqr->questionusageid = $quba->get_id();
398 $qqr->slot = $slot;
399 $qqr->regraded = empty($dryrun);
400 $qqr->timemodified = time();
401 $DB->insert_record('quiz_overview_regrades', $qqr, false);
98f38217 402 }
98f38217 403 }
404
e24ee794
TH
405 if (!$dryrun) {
406 question_engine::save_questions_usage_by_activity($quba);
98f38217 407 }
e24ee794
TH
408
409 $transaction->allow_commit();
01118710
K
410
411 // Really, PHP should not need this hint, but without this, we just run out of memory.
412 $quba = null;
413 $transaction = null;
414 gc_collect_cycles();
98f38217 415 }
e24ee794
TH
416
417 /**
418 * Regrade attempts for this quiz, exactly which attempts are regraded is
419 * controlled by the parameters.
420 * @param object $quiz the quiz settings.
f7970e3c 421 * @param bool $dryrun if true, do a pretend regrade, otherwise do it for real.
e24ee794
TH
422 * @param array $groupstudents blank for all attempts, otherwise regrade attempts
423 * for these users.
424 * @param array $attemptids blank for all attempts, otherwise only regrade
425 * attempts whose id is in this list.
426 */
427 protected function regrade_attempts($quiz, $dryrun = false,
428 $groupstudents = array(), $attemptids = array()) {
98f38217 429 global $DB;
e24ee794
TH
430
431 $where = "quiz = ? AND preview = 0";
432 $params = array($quiz->id);
433
07a2b2f0 434 if ($groupstudents) {
e24ee794
TH
435 list($usql, $uparams) = $DB->get_in_or_equal($groupstudents);
436 $where .= " AND userid $usql";
437 $params = array_merge($params, $uparams);
98f38217 438 }
e24ee794
TH
439
440 if ($attemptids) {
441 list($asql, $aparams) = $DB->get_in_or_equal($attemptids);
442 $where .= " AND id $asql";
443 $params = array_merge($params, $aparams);
98f38217 444 }
98f38217 445
e24ee794
TH
446 $attempts = $DB->get_records_select('quiz_attempts', $where, $params);
447 if (!$attempts) {
448 return;
449 }
98f38217 450
e24ee794 451 $this->clear_regrade_table($quiz, $groupstudents);
98f38217 452
98f38217 453 foreach ($attempts as $attempt) {
e24ee794
TH
454 $this->regrade_attempt($attempt, $dryrun);
455 }
456
457 if (!$dryrun) {
458 $this->update_overall_grades($quiz);
98f38217 459 }
98f38217 460 }
461
e24ee794
TH
462 /**
463 * Regrade those questions in those attempts that are marked as needing regrading
464 * in the quiz_overview_regrades table.
465 * @param object $quiz the quiz settings.
466 * @param array $groupstudents blank for all attempts, otherwise regrade attempts
467 * for these users.
468 */
469 protected function regrade_attempts_needing_it($quiz, $groupstudents) {
98f38217 470 global $DB;
e24ee794
TH
471
472 $where = "quiza.quiz = ? AND quiza.preview = 0 AND qqr.regraded = 0";
473 $params = array($quiz->id);
474
98f38217 475 // Fetch all attempts that need regrading
07a2b2f0 476 if ($groupstudents) {
e24ee794
TH
477 list($usql, $uparams) = $DB->get_in_or_equal($groupstudents);
478 $where .= " AND quiza.userid $usql";
479 $params += $uparams;
98f38217 480 }
23277af8 481
e24ee794
TH
482 $toregrade = $DB->get_records_sql("
483 SELECT quiza.uniqueid, qqr.slot
484 FROM {quiz_attempts} quiza
485 JOIN {quiz_overview_regrades} qqr ON qqr.questionusageid = quiza.uniqueid
486 WHERE $where", $params);
487
488 if (!$toregrade) {
489 return;
98f38217 490 }
491
e24ee794
TH
492 $attemptquestions = array();
493 foreach ($toregrade as $row) {
494 $attemptquestions[$row->uniqueid][] = $row->slot;
98f38217 495 }
25a03faa
TH
496 $attempts = $DB->get_records_list('quiz_attempts', 'uniqueid',
497 array_keys($attemptquestions));
e24ee794
TH
498
499 $this->clear_regrade_table($quiz, $groupstudents);
500
501 foreach ($attempts as $attempt) {
e24ee794 502 $this->regrade_attempt($attempt, false, $attemptquestions[$attempt->uniqueid]);
98f38217 503 }
e24ee794
TH
504
505 $this->update_overall_grades($quiz);
98f38217 506 }
f05fedc8 507
e24ee794
TH
508 /**
509 * Count the number of attempts in need of a regrade.
510 * @param object $quiz the quiz settings.
511 * @param array $groupstudents user ids. If this is given, only data relating
512 * to these users is cleared.
513 */
514 protected function count_question_attempts_needing_regrade($quiz, $groupstudents) {
515 global $DB;
516
517 $usertest = '';
518 $params = array();
519 if ($groupstudents) {
b149b788 520 list($usql, $params) = $DB->get_in_or_equal($groupstudents);
e24ee794 521 $usertest = "quiza.userid $usql AND ";
98f38217 522 }
e24ee794
TH
523
524 $params[] = $quiz->id;
525 $sql = "SELECT COUNT(DISTINCT quiza.id)
526 FROM {quiz_attempts} quiza
527 JOIN {quiz_overview_regrades} qqr ON quiza.uniqueid = qqr.questionusageid
528 WHERE
529 $usertest
530 quiza.quiz = ? AND
531 quiza.preview = 0 AND
532 qqr.regraded = 0";
533 return $DB->count_records_sql($sql, $params);
534 }
535
536 /**
537 * Are there any pending regrades in the table we are going to show?
538 * @param string $from tables used by the main query.
539 * @param string $where where clause used by the main query.
540 * @param array $params required by the SQL.
541 * @return bool whether there are pending regrades.
542 */
543 protected function has_regraded_questions($from, $where, $params) {
544 global $DB;
545 $qubaids = new qubaid_join($from, 'uniqueid', $where, $params);
546 return $DB->record_exists_select('quiz_overview_regrades',
25a03faa
TH
547 'questionusageid ' . $qubaids->usage_id_in(),
548 $qubaids->usage_id_in_params());
98f38217 549 }
f05fedc8 550
e24ee794
TH
551 /**
552 * Remove all information about pending/complete regrades from the database.
553 * @param object $quiz the quiz settings.
554 * @param array $groupstudents user ids. If this is given, only data relating
555 * to these users is cleared.
556 */
557 protected function clear_regrade_table($quiz, $groupstudents) {
98f38217 558 global $DB;
e24ee794
TH
559
560 // Fetch all attempts that need regrading
561 $where = '';
562 $params = array();
07a2b2f0 563 if ($groupstudents) {
98f38217 564 list($usql, $params) = $DB->get_in_or_equal($groupstudents);
e24ee794 565 $where = "userid $usql AND ";
98f38217 566 }
aeb15530 567
98f38217 568 $params[] = $quiz->id;
e24ee794
TH
569 $DB->delete_records_select('quiz_overview_regrades',
570 "questionusageid IN (
571 SELECT uniqueid
572 FROM {quiz_attempts}
573 WHERE $where quiz = ?
574 )", $params);
575 }
98f38217 576
e24ee794
TH
577 /**
578 * Update the final grades for all attempts. This method is used following
579 * a regrade.
580 * @param object $quiz the quiz settings.
581 * @param array $userids only update scores for these userids.
582 * @param array $attemptids attemptids only update scores for these attempt ids.
583 */
584 protected function update_overall_grades($quiz) {
585 quiz_update_all_attempt_sumgrades($quiz);
586 quiz_update_all_final_grades($quiz);
587 quiz_update_grades($quiz);
98f38217 588 }
7bbe08a2 589}