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