Merge branch 'MDL-41878-master' of git://github.com/andrewnicols/moodle
[moodle.git] / mod / quiz / report / statistics / report.php
1 <?php
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/>.
17 /**
18  * Quiz statistics report class.
19  *
20  * @package   quiz_statistics
21  * @copyright 2008 Jamie Pratt
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
28 require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
29 require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_question_table.php');
30 require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
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  *
36  * @copyright 2008 Jamie Pratt
37  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class quiz_statistics_report extends quiz_default_report {
41     /**
42      * @var context_module
43      */
44     protected $context;
46     /** @var quiz_statistics_table instance of table class used for main questions stats table. */
47     protected $table;
49     /**
50      * Display the report.
51      */
52     public function display($quiz, $cm, $course) {
53         global $CFG, $DB, $OUTPUT, $PAGE;
55         $this->context = context_module::instance($cm->id);
57         // Work out the display options.
58         $download = optional_param('download', '', PARAM_ALPHA);
59         $everything = optional_param('everything', 0, PARAM_BOOL);
60         $recalculate = optional_param('recalculate', 0, PARAM_BOOL);
61         // A qid paramter indicates we should display the detailed analysis of a question.
62         $qid = optional_param('qid', 0, PARAM_INT);
63         $slot = optional_param('slot', 0, PARAM_INT);
65         $pageoptions = array();
66         $pageoptions['id'] = $cm->id;
67         $pageoptions['mode'] = 'statistics';
69         $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
71         $mform = new quiz_statistics_settings_form($reporturl);
72         if ($fromform = $mform->get_data()) {
73             $useallattempts = $fromform->useallattempts;
74             if ($fromform->useallattempts) {
75                 set_user_preference('quiz_report_statistics_useallattempts',
76                         $fromform->useallattempts);
77             } else {
78                 unset_user_preference('quiz_report_statistics_useallattempts');
79             }
81         } else {
82             $useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0);
83         }
85         // Find out current groups mode.
86         $currentgroup = $this->get_current_group($cm, $course, $this->context);
87         $nostudentsingroup = false; // True if a group is selected and there is no one in it.
88         if (empty($currentgroup)) {
89             $currentgroup = 0;
90             $groupstudents = array();
92         } else if ($currentgroup == self::NO_GROUPS_ALLOWED) {
93             $groupstudents = array();
94             $nostudentsingroup = true;
96         } else {
97             // All users who can attempt quizzes and who are in the currently selected group.
98             $groupstudents = get_users_by_capability($this->context,
99                     array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
100                     '', '', '', '', $currentgroup, '', false);
101             if (!$groupstudents) {
102                 $nostudentsingroup = true;
103             }
104         }
106         $qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
108         // If recalculate was requested, handle that.
109         if ($recalculate && confirm_sesskey()) {
110             $this->clear_cached_data($qubaids);
111             redirect($reporturl);
112         }
114         // Set up the main table.
115         $this->table = new quiz_statistics_table();
116         if ($everything) {
117             $report = get_string('completestatsfilename', 'quiz_statistics');
118         } else {
119             $report = get_string('questionstatsfilename', 'quiz_statistics');
120         }
121         $courseshortname = format_string($course->shortname, true,
122                 array('context' => context_course::instance($course->id)));
123         $filename = quiz_report_download_filename($report, $courseshortname, $quiz->name);
124         $this->table->is_downloading($download, $filename,
125                 get_string('quizstructureanalysis', 'quiz_statistics'));
126         $questions = $this->load_and_initialise_questions_for_calculations($quiz);
128         if (!$nostudentsingroup) {
129             // Get the data to be displayed.
130             list($quizstats, $questionstats, $subquestionstats) =
131                 $this->get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions);
132         } else {
133             // Or create empty stats containers.
134             $quizstats = new quiz_statistics_calculated($useallattempts);
135             $questionstats = array();
136             $subquestionstats = array();
137         }
138         $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
140         // Set up the table, if there is data.
141         if ($quizstats->s()) {
142             $this->table->statistics_setup($quiz, $cm->id, $reporturl, $quizstats->s());
143         }
145         // Print the page header stuff (if not downloading.
146         if (!$this->table->is_downloading()) {
147             $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
149             if (groups_get_activity_groupmode($cm)) {
150                 groups_print_activity_menu($cm, $reporturl->out());
151                 if ($currentgroup && !$groupstudents) {
152                     $OUTPUT->notification(get_string('nostudentsingroup', 'quiz_statistics'));
153                 }
154             }
156             if (!quiz_questions_in_quiz($quiz->questions)) {
157                 echo quiz_no_questions_message($quiz, $cm, $this->context);
158             } else if (!$this->table->is_downloading() && $quizstats->s() == 0) {
159                 echo $OUTPUT->notification(get_string('noattempts', 'quiz'));
160             }
162             // Print display options form.
163             $mform->set_data(array('useallattempts' => $useallattempts));
164             $mform->display();
165         }
167         if ($everything) { // Implies is downloading.
168             // Overall report, then the analysis of each question.
169             $this->download_quiz_info_table($quizinfo);
171             if ($quizstats->s()) {
172                 $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
174                 if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) {
175                     $this->output_statistics_graph($quiz->id, $currentgroup, $useallattempts);
176                 }
178                 foreach ($questions as $slot => $question) {
179                     if (question_bank::get_qtype(
180                             $question->qtype, false)->can_analyse_responses()) {
181                         $this->output_individual_question_response_analysis(
182                                 $question, $questionstats[$slot]->s, $reporturl, $qubaids);
184                     } else if (!empty($questionstats[$slot]->subquestions)) {
185                         $subitemstodisplay = explode(',', $questionstats[$slot]->subquestions);
186                         foreach ($subitemstodisplay as $subitemid) {
187                             $this->output_individual_question_response_analysis(
188                                 $subquestionstats[$subitemid]->question, $subquestionstats[$subitemid]->s, $reporturl, $qubaids);
189                         }
190                     }
191                 }
192             }
194             $this->table->export_class_instance()->finish_document();
196         } else if ($slot) {
197             // Report on an individual question indexed by position.
198             if (!isset($questions[$slot])) {
199                 print_error('questiondoesnotexist', 'question');
200             }
202             $this->output_individual_question_data($quiz, $questionstats[$slot]);
203             $this->output_individual_question_response_analysis($questions[$slot], $questionstats[$slot]->s, $reporturl, $qubaids);
205             // Back to overview link.
206             echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
207                     get_string('backtoquizreport', 'quiz_statistics') . '</a>',
208                     'backtomainstats boxaligncenter generalbox boxwidthnormal mdl-align');
210         } else if ($qid) {
211             // Report on an individual sub-question indexed questionid.
212             if (!isset($subquestions[$qid])) {
213                 print_error('questiondoesnotexist', 'question');
214             }
216             $this->output_individual_question_data($quiz, $subquestionstats[$qid]);
217             $this->output_individual_question_response_analysis($subquestionstats[$qid]->question,
218                                                                 $subquestionstats[$qid]->s, $reporturl, $qubaids);
220             // Back to overview link.
221             echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
222                     get_string('backtoquizreport', 'quiz_statistics') . '</a>',
223                     'boxaligncenter generalbox boxwidthnormal mdl-align');
225         } else if ($this->table->is_downloading()) {
226             // Downloading overview report.
227             $this->download_quiz_info_table($quizinfo);
228             $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
229             $this->table->finish_output();
231         } else {
232             // On-screen display of overview report.
233             echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'));
234             echo $this->output_caching_info($quizstats, $quiz->id, $currentgroup,
235                     $groupstudents, $useallattempts, $reporturl);
236             echo $this->everything_download_options();
237             echo $this->output_quiz_info_table($quizinfo);
238             if ($quizstats->s()) {
239                 echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'));
240                 $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
241                 $this->output_statistics_graph($quiz->id, $currentgroup, $useallattempts);
242             }
243         }
245         return true;
246     }
248     /**
249      * Display the statistical and introductory information about a question.
250      * Only called when not downloading.
251      * @param object $quiz the quiz settings.
252      * @param \core_question\statistics\questions\calculated $questionstat the question to report on.
253      * @param moodle_url $reporturl the URL to resisplay this report.
254      * @param object $quizstats Holds the quiz statistics.
255      */
256     protected function output_individual_question_data($quiz, $questionstat) {
257         global $OUTPUT;
259         // On-screen display. Show a summary of the question's place in the quiz,
260         // and the question statistics.
261         $datumfromtable = $this->table->format_row($questionstat);
263         // Set up the question info table.
264         $questioninfotable = new html_table();
265         $questioninfotable->align = array('center', 'center');
266         $questioninfotable->width = '60%';
267         $questioninfotable->attributes['class'] = 'generaltable titlesleft';
269         $questioninfotable->data = array();
270         $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
271         $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
272                 $questionstat->question->name.'&nbsp;'.$datumfromtable['actions']);
273         $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
274                 $datumfromtable['icon'] . '&nbsp;' .
275                 question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . '&nbsp;' .
276                 $datumfromtable['icon']);
277         $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
278                 $questionstat->positions);
280         // Set up the question statistics table.
281         $questionstatstable = new html_table();
282         $questionstatstable->align = array('center', 'center');
283         $questionstatstable->width = '60%';
284         $questionstatstable->attributes['class'] = 'generaltable titlesleft';
286         unset($datumfromtable['number']);
287         unset($datumfromtable['icon']);
288         $actions = $datumfromtable['actions'];
289         unset($datumfromtable['actions']);
290         unset($datumfromtable['name']);
291         $labels = array(
292             's' => get_string('attempts', 'quiz_statistics'),
293             'facility' => get_string('facility', 'quiz_statistics'),
294             'sd' => get_string('standarddeviationq', 'quiz_statistics'),
295             'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
296             'intended_weight' => get_string('intended_weight', 'quiz_statistics'),
297             'effective_weight' => get_string('effective_weight', 'quiz_statistics'),
298             'discrimination_index' => get_string('discrimination_index', 'quiz_statistics'),
299             'discriminative_efficiency' =>
300                                 get_string('discriminative_efficiency', 'quiz_statistics')
301         );
302         foreach ($datumfromtable as $item => $value) {
303             $questionstatstable->data[] = array($labels[$item], $value);
304         }
306         // Display the various bits.
307         echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'));
308         echo html_writer::table($questioninfotable);
309         echo $this->render_question_text($questionstat->question);
310         echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'));
311         echo html_writer::table($questionstatstable);
312     }
314     /**
315      * @param object $question question data.
316      * @return string HTML of question text, ready for display.
317      */
318     protected function render_question_text($question) {
319         global $OUTPUT;
321         $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
322                 $question->contextid, 'question', 'questiontext', $question->id,
323                 $this->context->id, 'quiz_statistics');
325         return $OUTPUT->box(format_text($text, $question->questiontextformat,
326                 array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
327                 'questiontext boxaligncenter generalbox boxwidthnormal mdl-align');
328     }
330     /**
331      * Display the response analysis for a question.
332      * @param object     $question  the question to report on.
333      * @param moodle_url $reporturl the URL to resisplay this report.
334      * @param qubaid_condition $qubaids
335      */
336     protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
337         global $OUTPUT;
339         if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
340             return;
341         }
343         $qtable = new quiz_statistics_question_table($question->id);
344         $exportclass = $this->table->export_class_instance();
345         $qtable->export_class_instance($exportclass);
346         if (!$this->table->is_downloading()) {
347             // Output an appropriate title.
348             echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'));
350         } else {
351             // Work out an appropriate title.
352             $questiontabletitle = '"' . $question->name . '"';
353             if (!empty($question->number)) {
354                 $questiontabletitle = '(' . $question->number . ') ' . $questiontabletitle;
355             }
356             if ($this->table->is_downloading() == 'xhtml') {
357                 $questiontabletitle = get_string('analysisofresponsesfor',
358                         'quiz_statistics', $questiontabletitle);
359             }
361             // Set up the table.
362             $exportclass->start_table($questiontabletitle);
364             if ($this->table->is_downloading() == 'xhtml') {
365                 echo $this->render_question_text($question);
366             }
367         }
369         $responesstats = new \core_question\statistics\responses\analyser($question);
370         $responesstats->load_cached($qubaids);
372         $qtable->question_setup($reporturl, $question, $s, $responesstats);
373         if ($this->table->is_downloading()) {
374             $exportclass->output_headers($qtable->headers);
375         }
377         foreach ($responesstats->responseclasses as $partid => $partclasses) {
378             $rowdata = new stdClass();
379             $rowdata->part = $partid;
380             foreach ($partclasses as $responseclassid => $responseclass) {
381                 $rowdata->responseclass = $responseclass->responseclass;
383                 $responsesdata = $responesstats->responses[$partid][$responseclassid];
384                 if (empty($responsesdata)) {
385                     if (!array_key_exists('responseclass', $qtable->columns)) {
386                         $rowdata->response = $responseclass->responseclass;
387                     } else {
388                         $rowdata->response = '';
389                     }
390                     $rowdata->fraction = $responseclass->fraction;
391                     $rowdata->count = 0;
392                     $qtable->add_data_keyed($qtable->format_row($rowdata));
393                     continue;
394                 }
396                 foreach ($responsesdata as $response => $data) {
397                     $rowdata->response = $response;
398                     $rowdata->fraction = $data->fraction;
399                     $rowdata->count = $data->count;
400                     $qtable->add_data_keyed($qtable->format_row($rowdata));
401                 }
402             }
403         }
405         $qtable->finish_output(!$this->table->is_downloading());
406     }
408     /**
409      * Output the table that lists all the questions in the quiz with their statistics.
410      * @param int $s number of attempts.
411      * @param \core_question\statistics\questions\calculated[] $questionstats the stats for the main questions in the quiz.
412      * @param \core_question\statistics\questions\calculated_for_subquestion[] $subquestionstats the stats of any random questions.
413      */
414     protected function output_quiz_structure_analysis_table($s, $questionstats, $subquestionstats) {
415         if (!$s) {
416             return;
417         }
419         foreach ($questionstats as $questionstat) {
420             // Output the data for these question statistics.
421             $this->table->add_data_keyed($this->table->format_row($questionstat));
423             if (empty($questionstat->subquestions)) {
424                 continue;
425             }
427             // And its subquestions, if it has any.
428             $subitemstodisplay = explode(',', $questionstat->subquestions);
429             foreach ($subitemstodisplay as $subitemid) {
430                 $subquestionstats[$subitemid]->maxmark = $questionstat->maxmark;
431                 $this->table->add_data_keyed($this->table->format_row($subquestionstats[$subitemid]));
432             }
433         }
435         $this->table->finish_output(!$this->table->is_downloading());
436     }
438     /**
439      * Output the table of overall quiz statistics.
440      * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
441      * @return string the HTML.
442      */
443     protected function output_quiz_info_table($quizinfo) {
445         $quizinfotable = new html_table();
446         $quizinfotable->align = array('center', 'center');
447         $quizinfotable->width = '60%';
448         $quizinfotable->attributes['class'] = 'generaltable titlesleft';
449         $quizinfotable->data = array();
451         foreach ($quizinfo as $heading => $value) {
452              $quizinfotable->data[] = array($heading, $value);
453         }
455         return html_writer::table($quizinfotable);
456     }
458     /**
459      * Download the table of overall quiz statistics.
460      * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
461      */
462     protected function download_quiz_info_table($quizinfo) {
463         global $OUTPUT;
465         // XHTML download is a special case.
466         if ($this->table->is_downloading() == 'xhtml') {
467             echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'));
468             echo $this->output_quiz_info_table($quizinfo);
469             return;
470         }
472         // Reformat the data ready for output.
473         $headers = array();
474         $row = array();
475         foreach ($quizinfo as $heading => $value) {
476             $headers[] = $heading;
477             $row[] = $value;
478         }
480         // Do the output.
481         $exportclass = $this->table->export_class_instance();
482         $exportclass->start_table(get_string('quizinformation', 'quiz_statistics'));
483         $exportclass->output_headers($headers);
484         $exportclass->add_data($row);
485         $exportclass->finish_table();
486     }
488     /**
489      * Output the HTML needed to show the statistics graph.
490      * @param $quizid
491      * @param $currentgroup
492      * @param $useallattempts
493      */
494     protected function output_statistics_graph($quizid, $currentgroup, $useallattempts) {
495         global $PAGE;
497         $output = $PAGE->get_renderer('mod_quiz');
498         $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
499                                     compact('quizid', 'currentgroup', 'useallattempts'));
500         $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
501         echo $output->graph($imageurl, $graphname);
502     }
504     /**
505      * Get the quiz and question statistics, either by loading the cached results,
506      * or by recomputing them.
507      *
508      * @param object $quiz the quiz settings.
509      * @param int $currentgroup the current group. 0 for none.
510      * @param bool $useallattempts use all attempts, or just first attempts.
511      * @param array $groupstudents students in this group.
512      * @param array $questions question definitions.
513      * @return array with 4 elements:
514      *     - $quizstats The statistics for overall attempt scores.
515      *     - $questionstats array of \core_question\statistics\questions\calculated objects keyed by slot.
516      *     - $subquestionstats array of \core_question\statistics\questions\calculated_for_subquestion objects keyed by question id.
517      */
518     protected function get_quiz_and_questions_stats($quiz, $currentgroup, $useallattempts, $groupstudents, $questions) {
520         $qubaids = quiz_statistics_qubaids_condition($quiz->id, $currentgroup, $groupstudents, $useallattempts);
522         $qcalc = new \core_question\statistics\questions\calculator($questions);
524         $quizcalc = new quiz_statistics_calculator();
526         if ($quizcalc->get_last_calculated_time($qubaids) === false) {
527             // Recalculate now.
528             list($questionstats, $subquestionstats) = $qcalc->calculate($qubaids);
530             $quizstats = $quizcalc->calculate($quiz->id, $currentgroup, $useallattempts,
531                                                $groupstudents, count($questions), $qcalc->get_sum_of_mark_variance());
533             if ($quizstats->s()) {
534                 $this->calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats);
535             }
536         } else {
537             $quizstats = $quizcalc->get_cached($qubaids);
538             list($questionstats, $subquestionstats) = $qcalc->get_cached($qubaids);
539         }
541         return array($quizstats, $questionstats, $subquestionstats);
542     }
544     protected function calculate_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats) {
546         $done = array();
547         foreach ($questions as $question) {
548             if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
549                 continue;
550             }
551             $done[$question->id] = 1;
553             $responesstats = new \core_question\statistics\responses\analyser($question);
554             $responesstats->calculate($qubaids);
555         }
557         foreach ($subquestionstats as $subquestionstat) {
558             if (!question_bank::get_qtype($subquestionstat->question->qtype, false)->can_analyse_responses() ||
559                     isset($done[$subquestionstat->question->id])) {
560                 continue;
561             }
562             $done[$subquestionstat->question->id] = 1;
564             $responesstats = new \core_question\statistics\responses\analyser($subquestionstat->question);
565             $responesstats->calculate($qubaids);
566         }
567     }
569     /**
570      * @return string HTML snipped for the Download full report as UI.
571      */
572     protected function everything_download_options() {
573         $downloadoptions = $this->table->get_download_menu();
575         $downloadelements = new stdClass();
576         $downloadelements->formatsmenu = html_writer::select($downloadoptions, 'download',
577                 $this->table->defaultdownloadformat, false);
578         $downloadelements->downloadbutton = '<input type="submit" value="' .
579                 get_string('download') . '"/>';
581         $output = '<form action="'. $this->table->baseurl .'" method="post">';
582         $output .= '<div class="mdl-align">';
583         $output .= '<input type="hidden" name="everything" value="1"/>';
584         $output .= html_writer::tag('label', get_string('downloadeverything', 'quiz_statistics', $downloadelements));
585         $output .= '</div></form>';
587         return $output;
588     }
590     /**
591      * Generate the snipped of HTML that says when the stats were last caculated,
592      * with a recalcuate now button.
593      * @param object $quizstats      the overall quiz statistics.
594      * @param int    $quizid         the quiz id.
595      * @param int    $currentgroup   the id of the currently selected group, or 0.
596      * @param array  $groupstudents  ids of students in the group.
597      * @param bool   $useallattempts whether to use all attempts, instead of just
598      *                               first attempts.
599      * @param moodle_url $reporturl url for this report
600      * @return string a HTML snipped saying when the stats were last computed,
601      *      or blank if that is not appropriate.
602      */
603     protected function output_caching_info($quizstats, $quizid, $currentgroup,
604             $groupstudents, $useallattempts, $reporturl) {
605         global $DB, $OUTPUT;
607         if (empty($quizstats->timemodified)) {
608             return '';
609         }
611         // Find the number of attempts since the cached statistics were computed.
612         list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql(
613                 $quizid, $currentgroup, $groupstudents, $useallattempts, true);
614         $count = $DB->count_records_sql("
615                 SELECT COUNT(1)
616                 FROM $fromqa
617                 WHERE $whereqa
618                 AND quiza.timefinish > {$quizstats->timemodified}", $qaparams);
620         if (!$count) {
621             $count = 0;
622         }
624         // Generate the output.
625         $a = new stdClass();
626         $a->lastcalculated = format_time(time() - $quizstats->timemodified);
627         $a->count = $count;
629         $recalcualteurl = new moodle_url($reporturl,
630                 array('recalculate' => 1, 'sesskey' => sesskey()));
631         $output = '';
632         $output .= $OUTPUT->box_start(
633                 'boxaligncenter generalbox boxwidthnormal mdl-align', 'cachingnotice');
634         $output .= get_string('lastcalculated', 'quiz_statistics', $a);
635         $output .= $OUTPUT->single_button($recalcualteurl,
636                 get_string('recalculatenow', 'quiz_statistics'));
637         $output .= $OUTPUT->box_end(true);
639         return $output;
640     }
642     /**
643      * Clear the cached data for a particular report configuration. This will
644      * trigger a re-computation the next time the report is displayed.
645      * @param $qubaids qubaid_condition
646      */
647     protected function clear_cached_data($qubaids) {
648         global $DB;
649         $DB->delete_records('quiz_statistics', array('hashcode' => $qubaids->get_hash_code()));
650         $DB->delete_records('question_statistics', array('hashcode' => $qubaids->get_hash_code()));
651         $DB->delete_records('question_response_analysis', array('hashcode' => $qubaids->get_hash_code()));
652     }
654     /**
655      * @param object $quiz the quiz.
656      * @return array of questions for this quiz.
657      */
658     public function load_and_initialise_questions_for_calculations($quiz) {
659         // Load the questions.
660         $questions = quiz_report_get_significant_questions($quiz);
661         $questionids = array();
662         foreach ($questions as $question) {
663             $questionids[] = $question->id;
664         }
665         $fullquestions = question_load_questions($questionids);
666         foreach ($questions as $qno => $question) {
667             $q = $fullquestions[$question->id];
668             $q->maxmark = $question->maxmark;
669             $q->slot = $qno;
670             $q->number = $question->number;
671             $questions[$qno] = $q;
672         }
673         return $questions;
674     }