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