MDL-41754 (2) quiz statistics : using Sam's new progress classes
[moodle.git] / mod / quiz / report / statistics / report.php
CommitLineData
0c1c764e 1<?php
04853f27
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
0c1c764e 17/**
04853f27 18 * Quiz statistics report class.
0c1c764e 19 *
8d76124c
TH
20 * @package quiz_statistics
21 * @copyright 2008 Jamie Pratt
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
04853f27
TH
23 */
24
a17b297d
TH
25defined('MOODLE_INTERNAL') || die();
26
04853f27
TH
27require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
28require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
29require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_question_table.php');
e68e4ccf 30require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
04853f27
TH
31/**
32 * The quiz statistics report provides summary information about each question in
33 * a quiz, compared to the whole quiz. It also provides a drill-down to more
34 * detailed information about each question.
35 *
8d76124c
TH
36 * @copyright 2008 Jamie Pratt
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
04853f27 38 */
c386eaa3 39class quiz_statistics_report extends quiz_default_report {
7de1e35b
JP
40
41 /**
42 * @var context_module
43 */
44 protected $context;
3b1d5cc4 45
515b3ae6 46 /** @var quiz_statistics_table instance of table class used for main questions stats table. */
04853f27 47 protected $table;
0c1c764e 48
8da6fc9d
JP
49 /** @var \core\progress\base|null $progress Handles progress reporting or not. */
50 protected $progress = null;
51
0c1c764e 52 /**
53 * Display the report.
54 */
04853f27 55 public function display($quiz, $cm, $course) {
8da6fc9d
JP
56 global $OUTPUT;
57
58 raise_memory_limit(MEMORY_HUGE);
0c1c764e 59
26aded55 60 $this->context = context_module::instance($cm->id);
0c1c764e 61
f16ed06c
JP
62 if (!quiz_questions_in_quiz($quiz->questions)) {
63 $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
64 echo quiz_no_questions_message($quiz, $cm, $this->context);
65 return true;
66 }
67
04853f27 68 // Work out the display options.
0c1c764e 69 $download = optional_param('download', '', PARAM_ALPHA);
869309b8 70 $everything = optional_param('everything', 0, PARAM_BOOL);
d1789d5d 71 $recalculate = optional_param('recalculate', 0, PARAM_BOOL);
d50b05e6 72 // A qid paramter indicates we should display the detailed analysis of a sub question.
43ec99aa 73 $qid = optional_param('qid', 0, PARAM_INT);
04853f27 74 $slot = optional_param('slot', 0, PARAM_INT);
6dd9362e 75 $whichattempts = optional_param('whichattempts', $quiz->grademethod, PARAM_INT);
04853f27 76
0c1c764e 77 $pageoptions = array();
78 $pageoptions['id'] = $cm->id;
0c1c764e 79 $pageoptions['mode'] = 'statistics';
80
a6855934 81 $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
0c1c764e 82
6b4e2d76 83 $mform = new quiz_statistics_settings_form($reporturl);
6dd9362e
JP
84
85 $mform->set_data(array('whichattempts' => $whichattempts));
86
04853f27 87 if ($fromform = $mform->get_data()) {
6dd9362e
JP
88 $whichattempts = $fromform->whichattempts;
89 }
04853f27 90
6dd9362e
JP
91 if ($whichattempts != $quiz->grademethod) {
92 $reporturl->param('whichattempts', $whichattempts);
0c1c764e 93 }
94
768a7588 95 // Find out current groups mode.
490668bb 96 $currentgroup = $this->get_current_group($cm, $course, $this->context);
04853f27
TH
97 $nostudentsingroup = false; // True if a group is selected and there is no one in it.
98 if (empty($currentgroup)) {
99 $currentgroup = 0;
100 $groupstudents = array();
3b1d5cc4 101
e4977ba5
TH
102 } else if ($currentgroup == self::NO_GROUPS_ALLOWED) {
103 $groupstudents = array();
104 $nostudentsingroup = true;
105
04853f27 106 } else {
768a7588 107 // All users who can attempt quizzes and who are in the currently selected group.
fdb5bc03 108 $groupstudents = get_users_by_capability($this->context,
04853f27
TH
109 array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
110 '', '', '', '', $currentgroup, '', false);
111 if (!$groupstudents) {
e72efdd4 112 $nostudentsingroup = true;
0c1c764e 113 }
0c1c764e 114 }
3b1d5cc4 115
6dd9362e 116 $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
e68e4ccf 117
04853f27 118 // If recalculate was requested, handle that.
db77f410 119 if ($recalculate && confirm_sesskey()) {
e68e4ccf 120 $this->clear_cached_data($qubaids);
e42f153c 121 redirect($reporturl);
43ec99aa 122 }
3b1d5cc4 123
04853f27 124 // Set up the main table.
59ea8176 125 $this->table = new quiz_statistics_table();
04853f27
TH
126 if ($everything) {
127 $report = get_string('completestatsfilename', 'quiz_statistics');
128 } else {
129 $report = get_string('questionstatsfilename', 'quiz_statistics');
130 }
0eafc988 131 $courseshortname = format_string($course->shortname, true,
26aded55 132 array('context' => context_course::instance($course->id)));
8ebbb06a 133 $filename = quiz_report_download_filename($report, $courseshortname, $quiz->name);
25a03faa
TH
134 $this->table->is_downloading($download, $filename,
135 get_string('quizstructureanalysis', 'quiz_statistics'));
3652dddd 136 $questions = $this->load_and_initialise_questions_for_calculations($quiz);
04853f27 137
8da6fc9d
JP
138 // Print the page header stuff (if not downloading.
139 if (!$this->table->is_downloading()) {
140 $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
141 }
142
7de1e35b
JP
143 if (!$nostudentsingroup) {
144 // Get the data to be displayed.
8da6fc9d 145 $progress = $this->get_progress_trace_instance();
515b3ae6 146 list($quizstats, $questionstats, $subquestionstats) =
8da6fc9d 147 $this->get_all_stats_and_analysis($quiz, $whichattempts, $groupstudents, $questions, $progress);
7de1e35b
JP
148 } else {
149 // Or create empty stats containers.
8da6fc9d 150 $quizstats = new \quiz_statistics\calculated($whichattempts);
515b3ae6
JP
151 $questionstats = array();
152 $subquestionstats = array();
7de1e35b 153 }
04853f27 154
04853f27 155 // Set up the table, if there is data.
7de1e35b
JP
156 if ($quizstats->s()) {
157 $this->table->statistics_setup($quiz, $cm->id, $reporturl, $quizstats->s());
43ec99aa 158 }
3b1d5cc4 159
8da6fc9d 160 // Print the rest of the page header stuff (if not downloading.
04853f27 161 if (!$this->table->is_downloading()) {
04853f27 162
e4977ba5 163 if (groups_get_activity_groupmode($cm)) {
04853f27
TH
164 groups_print_activity_menu($cm, $reporturl->out());
165 if ($currentgroup && !$groupstudents) {
166 $OUTPUT->notification(get_string('nostudentsingroup', 'quiz_statistics'));
b0e4fa41
TH
167 }
168 }
169
f16ed06c 170 if (!$this->table->is_downloading() && $quizstats->s() == 0) {
3c6185e9
TH
171 echo $OUTPUT->notification(get_string('noattempts', 'quiz'));
172 }
173
04853f27 174 // Print display options form.
04853f27
TH
175 $mform->display();
176 }
177
178 if ($everything) { // Implies is downloading.
179 // Overall report, then the analysis of each question.
d50b05e6 180 $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
04853f27
TH
181 $this->download_quiz_info_table($quizinfo);
182
7de1e35b 183 if ($quizstats->s()) {
515b3ae6 184 $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
04853f27 185
7de1e35b 186 if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) {
6dd9362e 187 $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
869309b8 188 }
04853f27 189
515b3ae6 190 foreach ($questions as $slot => $question) {
25a03faa
TH
191 if (question_bank::get_qtype(
192 $question->qtype, false)->can_analyse_responses()) {
04853f27 193 $this->output_individual_question_response_analysis(
515b3ae6 194 $question, $questionstats[$slot]->s, $reporturl, $qubaids);
04853f27 195
515b3ae6
JP
196 } else if (!empty($questionstats[$slot]->subquestions)) {
197 $subitemstodisplay = explode(',', $questionstats[$slot]->subquestions);
04853f27
TH
198 foreach ($subitemstodisplay as $subitemid) {
199 $this->output_individual_question_response_analysis(
515b3ae6 200 $subquestionstats[$subitemid]->question, $subquestionstats[$subitemid]->s, $reporturl, $qubaids);
869309b8 201 }
202 }
869309b8 203 }
204 }
04853f27
TH
205
206 $this->table->export_class_instance()->finish_document();
207
208 } else if ($slot) {
209 // Report on an individual question indexed by position.
210 if (!isset($questions[$slot])) {
211 print_error('questiondoesnotexist', 'question');
869309b8 212 }
04853f27 213
515b3ae6
JP
214 $this->output_individual_question_data($quiz, $questionstats[$slot]);
215 $this->output_individual_question_response_analysis($questions[$slot], $questionstats[$slot]->s, $reporturl, $qubaids);
04853f27
TH
216
217 // Back to overview link.
218 echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
219 get_string('backtoquizreport', 'quiz_statistics') . '</a>',
2f1ba2ae 220 'backtomainstats boxaligncenter generalbox boxwidthnormal mdl-align');
04853f27
TH
221
222 } else if ($qid) {
223 // Report on an individual sub-question indexed questionid.
d50b05e6 224 if (!isset($subquestionstats[$qid])) {
43ec99aa 225 print_error('questiondoesnotexist', 'question');
71a2b878 226 }
04853f27 227
515b3ae6
JP
228 $this->output_individual_question_data($quiz, $subquestionstats[$qid]);
229 $this->output_individual_question_response_analysis($subquestionstats[$qid]->question,
230 $subquestionstats[$qid]->s, $reporturl, $qubaids);
04853f27
TH
231
232 // Back to overview link.
233 echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
234 get_string('backtoquizreport', 'quiz_statistics') . '</a>',
235 'boxaligncenter generalbox boxwidthnormal mdl-align');
236
237 } else if ($this->table->is_downloading()) {
238 // Downloading overview report.
d50b05e6 239 $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
04853f27 240 $this->download_quiz_info_table($quizinfo);
515b3ae6 241 $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
04853f27
TH
242 $this->table->finish_output();
243
244 } else {
245 // On-screen display of overview report.
c544ee92 246 echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
6dd9362e 247 echo $this->output_caching_info($quizstats, $quiz->id, $groupstudents, $whichattempts, $reporturl);
04853f27 248 echo $this->everything_download_options();
d50b05e6 249 $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
04853f27 250 echo $this->output_quiz_info_table($quizinfo);
7de1e35b 251 if ($quizstats->s()) {
c544ee92 252 echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'), 3);
515b3ae6 253 $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
6dd9362e 254 $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
b0e4fa41 255 }
43ec99aa 256 }
04853f27 257
43ec99aa 258 return true;
259 }
3b1d5cc4 260
04853f27
TH
261 /**
262 * Display the statistical and introductory information about a question.
263 * Only called when not downloading.
d50b05e6 264 * @param object $quiz the quiz settings.
515b3ae6 265 * @param \core_question\statistics\questions\calculated $questionstat the question to report on.
04853f27 266 */
515b3ae6 267 protected function output_individual_question_data($quiz, $questionstat) {
04853f27
TH
268 global $OUTPUT;
269
270 // On-screen display. Show a summary of the question's place in the quiz,
271 // and the question statistics.
515b3ae6 272 $datumfromtable = $this->table->format_row($questionstat);
04853f27
TH
273
274 // Set up the question info table.
275 $questioninfotable = new html_table();
276 $questioninfotable->align = array('center', 'center');
277 $questioninfotable->width = '60%';
278 $questioninfotable->attributes['class'] = 'generaltable titlesleft';
279
280 $questioninfotable->data = array();
281 $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
282 $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
515b3ae6 283 $questionstat->question->name.'&nbsp;'.$datumfromtable['actions']);
04853f27
TH
284 $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
285 $datumfromtable['icon'] . '&nbsp;' .
515b3ae6 286 question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . '&nbsp;' .
04853f27
TH
287 $datumfromtable['icon']);
288 $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
515b3ae6 289 $questionstat->positions);
04853f27
TH
290
291 // Set up the question statistics table.
292 $questionstatstable = new html_table();
293 $questionstatstable->align = array('center', 'center');
294 $questionstatstable->width = '60%';
295 $questionstatstable->attributes['class'] = 'generaltable titlesleft';
296
297 unset($datumfromtable['number']);
298 unset($datumfromtable['icon']);
299 $actions = $datumfromtable['actions'];
300 unset($datumfromtable['actions']);
301 unset($datumfromtable['name']);
25a03faa
TH
302 $labels = array(
303 's' => get_string('attempts', 'quiz_statistics'),
304 'facility' => get_string('facility', 'quiz_statistics'),
305 'sd' => get_string('standarddeviationq', 'quiz_statistics'),
306 'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
307 'intended_weight' => get_string('intended_weight', 'quiz_statistics'),
308 'effective_weight' => get_string('effective_weight', 'quiz_statistics'),
309 'discrimination_index' => get_string('discrimination_index', 'quiz_statistics'),
310 'discriminative_efficiency' =>
311 get_string('discriminative_efficiency', 'quiz_statistics')
312 );
04853f27
TH
313 foreach ($datumfromtable as $item => $value) {
314 $questionstatstable->data[] = array($labels[$item], $value);
869309b8 315 }
04853f27
TH
316
317 // Display the various bits.
c544ee92 318 echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'), 3);
04853f27 319 echo html_writer::table($questioninfotable);
515b3ae6 320 echo $this->render_question_text($questionstat->question);
c544ee92 321 echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'), 3);
04853f27 322 echo html_writer::table($questionstatstable);
869309b8 323 }
3b1d5cc4 324
edfa0d80
TH
325 /**
326 * @param object $question question data.
327 * @return string HTML of question text, ready for display.
328 */
fdb5bc03 329 protected function render_question_text($question) {
edfa0d80 330 global $OUTPUT;
fdb5bc03 331
68d2f6a0
TH
332 $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
333 $question->contextid, 'question', 'questiontext', $question->id,
334 $this->context->id, 'quiz_statistics');
fdb5bc03
TH
335
336 return $OUTPUT->box(format_text($text, $question->questiontextformat,
337 array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
edfa0d80
TH
338 'questiontext boxaligncenter generalbox boxwidthnormal mdl-align');
339 }
340
04853f27
TH
341 /**
342 * Display the response analysis for a question.
d50b05e6
JP
343 * @param object $question the question to report on.
344 * @param int $s
345 * @param moodle_url $reporturl the URL to redisplay this report.
e68e4ccf 346 * @param qubaid_condition $qubaids
04853f27 347 */
515b3ae6 348 protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
04853f27 349 global $OUTPUT;
3b1d5cc4 350
04853f27
TH
351 if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
352 return;
353 }
3b1d5cc4 354
59ea8176 355 $qtable = new quiz_statistics_question_table($question->id);
04853f27
TH
356 $exportclass = $this->table->export_class_instance();
357 $qtable->export_class_instance($exportclass);
358 if (!$this->table->is_downloading()) {
359 // Output an appropriate title.
c544ee92 360 echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'), 3);
3b1d5cc4 361
869309b8 362 } else {
04853f27
TH
363 // Work out an appropriate title.
364 $questiontabletitle = '"' . $question->name . '"';
365 if (!empty($question->number)) {
366 $questiontabletitle = '(' . $question->number . ') ' . $questiontabletitle;
367 }
368 if ($this->table->is_downloading() == 'xhtml') {
d50b05e6 369 $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
869309b8 370 }
04853f27
TH
371
372 // Set up the table.
869309b8 373 $exportclass->start_table($questiontabletitle);
edfa0d80
TH
374
375 if ($this->table->is_downloading() == 'xhtml') {
376 echo $this->render_question_text($question);
377 }
869309b8 378 }
3b1d5cc4 379
d50b05e6
JP
380 $responesanalyser = new \core_question\statistics\responses\analyser($question);
381 $responseanalysis = $responesanalyser->load_cached($qubaids);
04853f27 382
d50b05e6 383 $qtable->question_setup($reporturl, $question, $s, $responseanalysis);
04853f27
TH
384 if ($this->table->is_downloading()) {
385 $exportclass->output_headers($qtable->headers);
386 }
d50b05e6
JP
387 foreach ($responseanalysis->get_subpart_ids() as $partid) {
388 $subpart = $responseanalysis->get_subpart($partid);
389 foreach ($subpart->get_response_class_ids() as $responseclassid) {
390 $responseclass = $subpart->get_response_class($responseclassid);
391 $tabledata = $responseclass->data_for_question_response_table($subpart->has_multiple_response_classes(), $partid);
392 foreach ($tabledata as $row) {
393 $qtable->add_data_keyed($qtable->format_row($row));
869309b8 394 }
869309b8 395 }
43ec99aa 396 }
04853f27
TH
397
398 $qtable->finish_output(!$this->table->is_downloading());
869309b8 399 }
3b1d5cc4 400
04853f27
TH
401 /**
402 * Output the table that lists all the questions in the quiz with their statistics.
f7970e3c 403 * @param int $s number of attempts.
515b3ae6
JP
404 * @param \core_question\statistics\questions\calculated[] $questionstats the stats for the main questions in the quiz.
405 * @param \core_question\statistics\questions\calculated_for_subquestion[] $subquestionstats the stats of any random questions.
04853f27 406 */
515b3ae6 407 protected function output_quiz_structure_analysis_table($s, $questionstats, $subquestionstats) {
04853f27
TH
408 if (!$s) {
409 return;
410 }
411
515b3ae6
JP
412 foreach ($questionstats as $questionstat) {
413 // Output the data for these question statistics.
414 $this->table->add_data_keyed($this->table->format_row($questionstat));
04853f27 415
515b3ae6 416 if (empty($questionstat->subquestions)) {
04853f27 417 continue;
71a2b878 418 }
43ec99aa 419
04853f27 420 // And its subquestions, if it has any.
515b3ae6 421 $subitemstodisplay = explode(',', $questionstat->subquestions);
04853f27 422 foreach ($subitemstodisplay as $subitemid) {
515b3ae6
JP
423 $subquestionstats[$subitemid]->maxmark = $questionstat->maxmark;
424 $this->table->add_data_keyed($this->table->format_row($subquestionstats[$subitemid]));
04853f27 425 }
0c1c764e 426 }
04853f27
TH
427
428 $this->table->finish_output(!$this->table->is_downloading());
43ec99aa 429 }
3b1d5cc4 430
04853f27
TH
431 /**
432 * Output the table of overall quiz statistics.
433 * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
434 * @return string the HTML.
435 */
436 protected function output_quiz_info_table($quizinfo) {
437
438 $quizinfotable = new html_table();
439 $quizinfotable->align = array('center', 'center');
440 $quizinfotable->width = '60%';
441 $quizinfotable->attributes['class'] = 'generaltable titlesleft';
442 $quizinfotable->data = array();
443
444 foreach ($quizinfo as $heading => $value) {
445 $quizinfotable->data[] = array($heading, $value);
71a2b878 446 }
04853f27
TH
447
448 return html_writer::table($quizinfotable);
71a2b878 449 }
43ec99aa 450
04853f27
TH
451 /**
452 * Download the table of overall quiz statistics.
453 * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
454 */
455 protected function download_quiz_info_table($quizinfo) {
456 global $OUTPUT;
3b1d5cc4 457
04853f27
TH
458 // XHTML download is a special case.
459 if ($this->table->is_downloading() == 'xhtml') {
c544ee92 460 echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
04853f27
TH
461 echo $this->output_quiz_info_table($quizinfo);
462 return;
463 }
3b1d5cc4 464
04853f27
TH
465 // Reformat the data ready for output.
466 $headers = array();
467 $row = array();
468 foreach ($quizinfo as $heading => $value) {
469 $headers[] = $heading;
470 $row[] = $value;
471 }
3b1d5cc4 472
04853f27
TH
473 // Do the output.
474 $exportclass = $this->table->export_class_instance();
475 $exportclass->start_table(get_string('quizinformation', 'quiz_statistics'));
476 $exportclass->output_headers($headers);
477 $exportclass->add_data($row);
478 $exportclass->finish_table();
479 }
480
481 /**
482 * Output the HTML needed to show the statistics graph.
e68e4ccf
JP
483 * @param $quizid
484 * @param $currentgroup
6dd9362e 485 * @param $whichattempts
04853f27 486 */
6dd9362e 487 protected function output_statistics_graph($quizid, $currentgroup, $whichattempts) {
2cdcb905 488 global $PAGE;
04853f27 489
2cdcb905 490 $output = $PAGE->get_renderer('mod_quiz');
04853f27 491 $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
6dd9362e 492 compact('quizid', 'currentgroup', 'whichattempts'));
2cdcb905
TH
493 $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
494 echo $output->graph($imageurl, $graphname);
04853f27
TH
495 }
496
04853f27
TH
497 /**
498 * Get the quiz and question statistics, either by loading the cached results,
499 * or by recomputing them.
500 *
8da6fc9d
JP
501 * @param object $quiz the quiz settings.
502 * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
6dd9362e
JP
503 * $quiz->grademethod ie.
504 * QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
505 * we calculate stats based on which attempts would affect the grade for each student.
8da6fc9d
JP
506 * @param array $groupstudents students in this group.
507 * @param array $questions full question data.
04853f27
TH
508 * @return array with 4 elements:
509 * - $quizstats The statistics for overall attempt scores.
515b3ae6
JP
510 * - $questionstats array of \core_question\statistics\questions\calculated objects keyed by slot.
511 * - $subquestionstats array of \core_question\statistics\questions\calculated_for_subquestion objects keyed by question id.
04853f27 512 */
8da6fc9d
JP
513 public function get_all_stats_and_analysis($quiz, $whichattempts, $groupstudents, $questions, $progress = null) {
514
515 if ($progress === null) {
516 $progress = new \core\progress\null();
517 }
04853f27 518
6dd9362e 519 $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
04853f27 520
8da6fc9d 521 $qcalc = new \core_question\statistics\questions\calculator($questions, $progress);
04853f27 522
8da6fc9d 523 $quizcalc = new \quiz_statistics\calculator($progress);
7de1e35b
JP
524
525 if ($quizcalc->get_last_calculated_time($qubaids) === false) {
8da6fc9d
JP
526
527 $progress->start_progress('', 3);
528
e68e4ccf 529 // Recalculate now.
515b3ae6 530 list($questionstats, $subquestionstats) = $qcalc->calculate($qubaids);
8da6fc9d 531 $progress->progress(1);
04853f27 532
6dd9362e
JP
533 $quizstats = $quizcalc->calculate($quiz->id, $whichattempts, $groupstudents, count($questions),
534 $qcalc->get_sum_of_mark_variance());
8da6fc9d 535 $progress->progress(2);
7de1e35b 536 if ($quizstats->s()) {
8da6fc9d 537 $this->analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats, $progress);
e68e4ccf 538 }
8da6fc9d
JP
539 $progress->progress(3);
540 $progress->end_progress();
e68e4ccf 541 } else {
7de1e35b 542 $quizstats = $quizcalc->get_cached($qubaids);
515b3ae6 543 list($questionstats, $subquestionstats) = $qcalc->get_cached($qubaids);
04853f27
TH
544 }
545
515b3ae6 546 return array($quizstats, $questionstats, $subquestionstats);
04853f27
TH
547 }
548
8da6fc9d
JP
549 /**
550 * Appropriate instance depending if we want html output for the user or not.
551 *
552 * @return \core\progress\base child of \core\progress\base to handle the display (or not) of task progress.
553 */
554 protected function get_progress_trace_instance() {
555 if ($this->progress === null) {
556 if (!$this->table->is_downloading()) {
557 $this->progress = new \core\progress\display_if_slow(get_string('calculatingallstats', 'quiz_statistics'));
558 $this->progress->set_display_names();
559 } else {
560 $this->progress = new \core\progress\null();
561 }
562 }
563 return $this->progress;
564 }
565
566 protected function analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats,
567 $progress = null) {
568
569 if ($progress === null) {
570 $progress = new \core\progress\null();
571 }
572
573 // Starting response analysis tasks.
574 $progress->start_progress('', count($questions) + count($subquestionstats));
575
576 // Starting response analysis of main questions.
577 $progress->start_progress('', count($questions), count($questions));
04853f27
TH
578
579 $done = array();
8da6fc9d 580 $donecount = 1;
04853f27 581 foreach ($questions as $question) {
8da6fc9d
JP
582 $progress->progress($donecount);
583 $donecount++;
04853f27
TH
584 if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
585 continue;
43ec99aa 586 }
04853f27
TH
587 $done[$question->id] = 1;
588
515b3ae6 589 $responesstats = new \core_question\statistics\responses\analyser($question);
e68e4ccf 590 $responesstats->calculate($qubaids);
04853f27 591 }
8da6fc9d 592 $progress->end_progress();
04853f27 593
8da6fc9d
JP
594 // Starting response analysis of sub-questions.
595 $countsubquestions = count($subquestionstats);
596 $progress->start_progress('', $countsubquestions, $countsubquestions);
597 $donecount = 1;
515b3ae6 598 foreach ($subquestionstats as $subquestionstat) {
8da6fc9d
JP
599 $progress->progress($donecount);
600 $donecount++;
515b3ae6
JP
601 if (!question_bank::get_qtype($subquestionstat->question->qtype, false)->can_analyse_responses() ||
602 isset($done[$subquestionstat->question->id])) {
04853f27 603 continue;
43ec99aa 604 }
515b3ae6 605 $done[$subquestionstat->question->id] = 1;
04853f27 606
515b3ae6 607 $responesstats = new \core_question\statistics\responses\analyser($subquestionstat->question);
e68e4ccf 608 $responesstats->calculate($qubaids);
04853f27 609 }
8da6fc9d
JP
610 // Finished sub-question tasks.
611 $progress->end_progress();
612
613 // Finished all response analysis tasks.
614 $progress->end_progress();
04853f27
TH
615 }
616
617 /**
618 * @return string HTML snipped for the Download full report as UI.
619 */
620 protected function everything_download_options() {
621 $downloadoptions = $this->table->get_download_menu();
622
0465ef6e
RT
623 $downloadelements = new stdClass();
624 $downloadelements->formatsmenu = html_writer::select($downloadoptions, 'download',
625 $this->table->defaultdownloadformat, false);
626 $downloadelements->downloadbutton = '<input type="submit" value="' .
627 get_string('download') . '"/>';
628
04853f27
TH
629 $output = '<form action="'. $this->table->baseurl .'" method="post">';
630 $output .= '<div class="mdl-align">';
631 $output .= '<input type="hidden" name="everything" value="1"/>';
0465ef6e 632 $output .= html_writer::tag('label', get_string('downloadeverything', 'quiz_statistics', $downloadelements));
04853f27
TH
633 $output .= '</div></form>';
634
635 return $output;
636 }
637
638 /**
639 * Generate the snipped of HTML that says when the stats were last caculated,
640 * with a recalcuate now button.
e68e4ccf
JP
641 * @param object $quizstats the overall quiz statistics.
642 * @param int $quizid the quiz id.
6dd9362e
JP
643 * @param array $groupstudents ids of students in the group or empty array if groups not used.
644 * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
645 * $quiz->grademethod ie.
646 * QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
647 * we calculate stats based on which attempts would affect the grade for each student.
e68e4ccf 648 * @param moodle_url $reporturl url for this report
25a03faa
TH
649 * @return string a HTML snipped saying when the stats were last computed,
650 * or blank if that is not appropriate.
04853f27 651 */
6dd9362e 652 protected function output_caching_info($quizstats, $quizid, $groupstudents, $whichattempts, $reporturl) {
04853f27
TH
653 global $DB, $OUTPUT;
654
655 if (empty($quizstats->timemodified)) {
656 return '';
657 }
658
659 // Find the number of attempts since the cached statistics were computed.
6dd9362e 660 list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, true);
04853f27
TH
661 $count = $DB->count_records_sql("
662 SELECT COUNT(1)
663 FROM $fromqa
664 WHERE $whereqa
665 AND quiza.timefinish > {$quizstats->timemodified}", $qaparams);
666
667 if (!$count) {
668 $count = 0;
669 }
670
671 // Generate the output.
0ff4bd08 672 $a = new stdClass();
04853f27
TH
673 $a->lastcalculated = format_time(time() - $quizstats->timemodified);
674 $a->count = $count;
675
25a03faa
TH
676 $recalcualteurl = new moodle_url($reporturl,
677 array('recalculate' => 1, 'sesskey' => sesskey()));
04853f27 678 $output = '';
25a03faa
TH
679 $output .= $OUTPUT->box_start(
680 'boxaligncenter generalbox boxwidthnormal mdl-align', 'cachingnotice');
04853f27 681 $output .= get_string('lastcalculated', 'quiz_statistics', $a);
25a03faa
TH
682 $output .= $OUTPUT->single_button($recalcualteurl,
683 get_string('recalculatenow', 'quiz_statistics'));
04853f27
TH
684 $output .= $OUTPUT->box_end(true);
685
686 return $output;
687 }
688
689 /**
690 * Clear the cached data for a particular report configuration. This will
691 * trigger a re-computation the next time the report is displayed.
e68e4ccf 692 * @param $qubaids qubaid_condition
04853f27 693 */
e68e4ccf 694 protected function clear_cached_data($qubaids) {
04853f27 695 global $DB;
e68e4ccf
JP
696 $DB->delete_records('quiz_statistics', array('hashcode' => $qubaids->get_hash_code()));
697 $DB->delete_records('question_statistics', array('hashcode' => $qubaids->get_hash_code()));
698 $DB->delete_records('question_response_analysis', array('hashcode' => $qubaids->get_hash_code()));
04853f27
TH
699 }
700
3652dddd
JP
701 /**
702 * @param object $quiz the quiz.
703 * @return array of questions for this quiz.
704 */
705 public function load_and_initialise_questions_for_calculations($quiz) {
706 // Load the questions.
707 $questions = quiz_report_get_significant_questions($quiz);
708 $questionids = array();
709 foreach ($questions as $question) {
710 $questionids[] = $question->id;
711 }
712 $fullquestions = question_load_questions($questionids);
713 foreach ($questions as $qno => $question) {
714 $q = $fullquestions[$question->id];
715 $q->maxmark = $question->maxmark;
716 $q->slot = $qno;
717 $q->number = $question->number;
718 $questions[$qno] = $q;
719 }
720 return $questions;
721 }
0c1c764e 722}
04853f27 723