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