39a929863f613e91ac38d5292060d44484c1a4c0
[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) =
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 = new \core_question\statistics\questions\all_calculated_for_qubaid_condition();
152         }
154         // Set up the table, if there is data.
155         if ($quizstats->s()) {
156             $this->table->statistics_setup($quiz, $cm->id, $reporturl, $quizstats->s());
157         }
159         // Print the rest of the page header stuff (if not downloading.
160         if (!$this->table->is_downloading()) {
162             if (groups_get_activity_groupmode($cm)) {
163                 groups_print_activity_menu($cm, $reporturl->out());
164                 if ($currentgroup && !$groupstudents) {
165                     $OUTPUT->notification(get_string('nostudentsingroup', 'quiz_statistics'));
166                 }
167             }
169             if (!$this->table->is_downloading() && $quizstats->s() == 0) {
170                 echo $OUTPUT->notification(get_string('noattempts', 'quiz'));
171             }
173             foreach($questionstats->any_error_messages() as $errormessage) {
174                 echo $OUTPUT->notification($errormessage);
175             }
177             // Print display options form.
178             $mform->display();
179         }
181         if ($everything) { // Implies is downloading.
182             // Overall report, then the analysis of each question.
183             $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
184             $this->download_quiz_info_table($quizinfo);
186             if ($quizstats->s()) {
187                 $this->output_quiz_structure_analysis_table($questionstats);
189                 if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) {
190                     $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
191                 }
193                 foreach ($questions as $slot => $question) {
194                     if (question_bank::get_qtype(
195                             $question->qtype, false)->can_analyse_responses()) {
196                         $this->output_individual_question_response_analysis(
197                                 $question, $questionstats->for_slot($slot)->s, $reporturl, $qubaids);
199                     } else if ($subqids = $questionstats->for_slot($slot)->get_sub_question_ids()) {
200                         foreach ($subqids as $subqid) {
201                             $this->output_individual_question_response_analysis($questionstats->for_subq($subqid)->question,
202                                                                                 $questionstats->for_subq($subqid)->s,
203                                                                                 $reporturl,
204                                                                                 $qubaids);
205                         }
206                     }
207                 }
208             }
210             $this->table->export_class_instance()->finish_document();
212         } else if ($slot) {
213             // Report on an individual question indexed by position.
214             if (!isset($questions[$slot])) {
215                 print_error('questiondoesnotexist', 'question');
216             }
218             $this->output_individual_question_data($quiz, $questionstats->for_slot($slot));
219             $this->output_individual_question_response_analysis($questions[$slot],
220                                                                 $questionstats->for_slot($slot)->s,
221                                                                 $reporturl,
222                                                                 $qubaids);
224             // Back to overview link.
225             echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
226                     get_string('backtoquizreport', 'quiz_statistics') . '</a>',
227                     'backtomainstats boxaligncenter generalbox boxwidthnormal mdl-align');
229         } else if ($qid) {
230             // Report on an individual sub-question indexed questionid.
231             if (is_null($questionstats->for_subq($qid))) {
232                 print_error('questiondoesnotexist', 'question');
233             }
235             $this->output_individual_question_data($quiz, $questionstats->for_subq($qid));
236             $this->output_individual_question_response_analysis($questionstats->for_subq($qid)->question,
237                                                                 $questionstats->for_subq($qid)->s,
238                                                                 $reporturl,
239                                                                 $qubaids);
241             // Back to overview link.
242             echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
243                     get_string('backtoquizreport', 'quiz_statistics') . '</a>',
244                     'boxaligncenter generalbox boxwidthnormal mdl-align');
246         } else if ($this->table->is_downloading()) {
247             // Downloading overview report.
248             $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
249             $this->download_quiz_info_table($quizinfo);
250             if ($quizstats->s()) {
251                 $this->output_quiz_structure_analysis_table($questionstats);
252             }
253             $this->table->finish_output();
255         } else {
256             // On-screen display of overview report.
257             echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
258             echo $this->output_caching_info($quizstats, $quiz->id, $groupstudents, $whichattempts, $reporturl);
259             echo $this->everything_download_options();
260             $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
261             echo $this->output_quiz_info_table($quizinfo);
262             if ($quizstats->s()) {
263                 echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'), 3);
264                 $this->output_quiz_structure_analysis_table($questionstats);
265                 $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
266             }
267         }
269         return true;
270     }
272     /**
273      * Display the statistical and introductory information about a question.
274      * Only called when not downloading.
275      * @param object                                         $quiz         the quiz settings.
276      * @param \core_question\statistics\questions\calculated $questionstat the question to report on.
277      */
278     protected function output_individual_question_data($quiz, $questionstat) {
279         global $OUTPUT;
281         // On-screen display. Show a summary of the question's place in the quiz,
282         // and the question statistics.
283         $datumfromtable = $this->table->format_row($questionstat);
285         // Set up the question info table.
286         $questioninfotable = new html_table();
287         $questioninfotable->align = array('center', 'center');
288         $questioninfotable->width = '60%';
289         $questioninfotable->attributes['class'] = 'generaltable titlesleft';
291         $questioninfotable->data = array();
292         $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
293         $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
294                 $questionstat->question->name.'&nbsp;'.$datumfromtable['actions']);
295         $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
296                 $datumfromtable['icon'] . '&nbsp;' .
297                 question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . '&nbsp;' .
298                 $datumfromtable['icon']);
299         $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
300                 $questionstat->positions);
302         // Set up the question statistics table.
303         $questionstatstable = new html_table();
304         $questionstatstable->align = array('center', 'center');
305         $questionstatstable->width = '60%';
306         $questionstatstable->attributes['class'] = 'generaltable titlesleft';
308         unset($datumfromtable['number']);
309         unset($datumfromtable['icon']);
310         $actions = $datumfromtable['actions'];
311         unset($datumfromtable['actions']);
312         unset($datumfromtable['name']);
313         $labels = array(
314             's' => get_string('attempts', 'quiz_statistics'),
315             'facility' => get_string('facility', 'quiz_statistics'),
316             'sd' => get_string('standarddeviationq', 'quiz_statistics'),
317             'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
318             'intended_weight' => get_string('intended_weight', 'quiz_statistics'),
319             'effective_weight' => get_string('effective_weight', 'quiz_statistics'),
320             'discrimination_index' => get_string('discrimination_index', 'quiz_statistics'),
321             'discriminative_efficiency' =>
322                                 get_string('discriminative_efficiency', 'quiz_statistics')
323         );
324         foreach ($datumfromtable as $item => $value) {
325             $questionstatstable->data[] = array($labels[$item], $value);
326         }
328         // Display the various bits.
329         echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'), 3);
330         echo html_writer::table($questioninfotable);
331         echo $this->render_question_text($questionstat->question);
332         echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'), 3);
333         echo html_writer::table($questionstatstable);
334     }
336     /**
337      * @param object $question question data.
338      * @return string HTML of question text, ready for display.
339      */
340     protected function render_question_text($question) {
341         global $OUTPUT;
343         $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
344                 $question->contextid, 'question', 'questiontext', $question->id,
345                 $this->context->id, 'quiz_statistics');
347         return $OUTPUT->box(format_text($text, $question->questiontextformat,
348                 array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
349                 'questiontext boxaligncenter generalbox boxwidthnormal mdl-align');
350     }
352     /**
353      * Display the response analysis for a question.
354      * @param object           $question  the question to report on.
355      * @param int              $s
356      * @param moodle_url       $reporturl the URL to redisplay this report.
357      * @param qubaid_condition $qubaids
358      */
359     protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
360         global $OUTPUT;
362         if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
363             return;
364         }
366         $qtable = new quiz_statistics_question_table($question->id);
367         $exportclass = $this->table->export_class_instance();
368         $qtable->export_class_instance($exportclass);
369         if (!$this->table->is_downloading()) {
370             // Output an appropriate title.
371             echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'), 3);
373         } else {
374             // Work out an appropriate title.
375             $questiontabletitle = '"' . $question->name . '"';
376             if (!empty($question->number)) {
377                 $questiontabletitle = '(' . $question->number . ') ' . $questiontabletitle;
378             }
379             if ($this->table->is_downloading() == 'xhtml') {
380                 $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
381             }
383             // Set up the table.
384             $exportclass->start_table($questiontabletitle);
386             if ($this->table->is_downloading() == 'xhtml') {
387                 echo $this->render_question_text($question);
388             }
389         }
391         $responesanalyser = new \core_question\statistics\responses\analyser($question);
392         $responseanalysis = $responesanalyser->load_cached($qubaids);
394         $qtable->question_setup($reporturl, $question, $s, $responseanalysis);
395         if ($this->table->is_downloading()) {
396             $exportclass->output_headers($qtable->headers);
397         }
398         foreach ($responseanalysis->get_subpart_ids() as $partid) {
399             $subpart = $responseanalysis->get_subpart($partid);
400             foreach ($subpart->get_response_class_ids() as $responseclassid) {
401                 $responseclass = $subpart->get_response_class($responseclassid);
402                 $tabledata = $responseclass->data_for_question_response_table($subpart->has_multiple_response_classes(), $partid);
403                 foreach ($tabledata as $row) {
404                     $qtable->add_data_keyed($qtable->format_row($row));
405                 }
406             }
407         }
409         $qtable->finish_output(!$this->table->is_downloading());
410     }
412     /**
413      * Output the table that lists all the questions in the quiz with their statistics.
414      * @param \core_question\statistics\questions\all_calculated_for_qubaid_condition $questionstats the stats for all questions in
415      *                                                                                               the quiz including subqs and
416      *                                                                                               variants.
417      */
418     protected function output_quiz_structure_analysis_table($questionstats) {
419         $tooutput = array();
420         foreach ($questionstats->get_all_slots() as $slot) {
421             // Output the data for these question statistics.
422             $tooutput[] = $questionstats->for_slot($slot);
424             $limitvariants = !$this->table->is_downloading();
425             $tooutput = array_merge($tooutput, $questionstats->all_subq_and_variant_stats_for_slot($slot, $limitvariants));
426         }
427         $this->table->format_and_add_array_of_rows($tooutput);
428     }
430     /**
431      * Output the table of overall quiz statistics.
432      * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
433      * @return string the HTML.
434      */
435     protected function output_quiz_info_table($quizinfo) {
437         $quizinfotable = new html_table();
438         $quizinfotable->align = array('center', 'center');
439         $quizinfotable->width = '60%';
440         $quizinfotable->attributes['class'] = 'generaltable titlesleft';
441         $quizinfotable->data = array();
443         foreach ($quizinfo as $heading => $value) {
444              $quizinfotable->data[] = array($heading, $value);
445         }
447         return html_writer::table($quizinfotable);
448     }
450     /**
451      * Download the table of overall quiz statistics.
452      * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
453      */
454     protected function download_quiz_info_table($quizinfo) {
455         global $OUTPUT;
457         // XHTML download is a special case.
458         if ($this->table->is_downloading() == 'xhtml') {
459             echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
460             echo $this->output_quiz_info_table($quizinfo);
461             return;
462         }
464         // Reformat the data ready for output.
465         $headers = array();
466         $row = array();
467         foreach ($quizinfo as $heading => $value) {
468             $headers[] = $heading;
469             $row[] = $value;
470         }
472         // Do the output.
473         $exportclass = $this->table->export_class_instance();
474         $exportclass->start_table(get_string('quizinformation', 'quiz_statistics'));
475         $exportclass->output_headers($headers);
476         $exportclass->add_data($row);
477         $exportclass->finish_table();
478     }
480     /**
481      * Output the HTML needed to show the statistics graph.
482      * @param $quizid
483      * @param $currentgroup
484      * @param $whichattempts
485      */
486     protected function output_statistics_graph($quizid, $currentgroup, $whichattempts) {
487         global $PAGE;
489         $output = $PAGE->get_renderer('mod_quiz');
490         $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
491                                     compact('quizid', 'currentgroup', 'whichattempts'));
492         $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
493         echo $output->graph($imageurl, $graphname);
494     }
496     /**
497      * Get the quiz and question statistics, either by loading the cached results,
498      * or by recomputing them.
499      *
500      * @param object $quiz               the quiz settings.
501      * @param string $whichattempts      which attempts to use, represented internally as one of the constants as used in
502      *                                   $quiz->grademethod ie.
503      *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
504      *                                   we calculate stats based on which attempts would affect the grade for each student.
505      * @param array  $groupstudents      students in this group.
506      * @param array  $questions          full question data.
507      * @param \core\progress\base|null   $progress
508      * @return array with 2 elements:    - $quizstats The statistics for overall attempt scores.
509      *                                   - $questionstats \core_question\statistics\questions\all_calculated_for_qubaid_condition
510      */
511     public function get_all_stats_and_analysis($quiz, $whichattempts, $groupstudents, $questions, $progress = null) {
513         if ($progress === null) {
514             $progress = new \core\progress\null();
515         }
517         $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
519         $qcalc = new \core_question\statistics\questions\calculator($questions, $progress);
521         $quizcalc = new \quiz_statistics\calculator($progress);
523         if ($quizcalc->get_last_calculated_time($qubaids) === false) {
525             $progress->start_progress('', 3);
527             // Recalculate now.
528             $questionstats = $qcalc->calculate($qubaids);
529             $progress->progress(1);
531             $quizstats = $quizcalc->calculate($quiz->id, $whichattempts, $groupstudents, count($questions),
532                                               $qcalc->get_sum_of_mark_variance());
533             $progress->progress(2);
534             if ($quizstats->s()) {
535                 $subquestions = $questionstats->get_sub_questions();
536                 $this->analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestions, $progress);
537             }
538             $progress->progress(3);
539             $progress->end_progress();
540         } else {
541             $quizstats = $quizcalc->get_cached($qubaids);
542             $questionstats = $qcalc->get_cached($qubaids);
543         }
545         return array($quizstats, $questionstats);
546     }
548     /**
549      * Appropriate instance depending if we want html output for the user or not.
550      *
551      * @return \core\progress\base child of \core\progress\base to handle the display (or not) of task progress.
552      */
553     protected function get_progress_trace_instance() {
554         if ($this->progress === null) {
555             if (!$this->table->is_downloading()) {
556                 $this->progress =  new \core\progress\display_if_slow(get_string('calculatingallstats', 'quiz_statistics'));
557                 $this->progress->set_display_names();
558             } else {
559                 $this->progress = new \core\progress\null();
560             }
561         }
562         return $this->progress;
563     }
565     protected function analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestions,
566                                                                             $progress = null) {
568         if ($progress === null) {
569             $progress = new \core\progress\null();
570         }
572         // Starting response analysis tasks.
573         $progress->start_progress('', count($questions) + count($subquestions));
575         // Starting response analysis of main questions.
576         $progress->start_progress('', count($questions), count($questions));
578         $done = array();
579         $donecount = 1;
580         foreach ($questions as $question) {
581             $progress->progress($donecount);
582             $donecount++;
583             if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
584                 continue;
585             }
586             $done[$question->id] = 1;
588             $responesstats = new \core_question\statistics\responses\analyser($question);
589             $responesstats->calculate($qubaids);
590         }
591         $progress->end_progress();
593         // Starting response analysis of sub-questions.
594         $countsubquestions = count($subquestions);
595         $progress->start_progress('', $countsubquestions, $countsubquestions);
596         $donecount = 1;
597         foreach ($subquestions as $subquestion) {
598             $progress->progress($donecount);
599             $donecount++;
600             if (!question_bank::get_qtype($subquestion->qtype, false)->can_analyse_responses() ||
601                     isset($done[$subquestion->id])) {
602                 continue;
603             }
604             $done[$subquestion->id] = 1;
606             $responesstats = new \core_question\statistics\responses\analyser($subquestion);
607             $responesstats->calculate($qubaids);
608         }
609         // Finished sub-question tasks.
610         $progress->end_progress();
612         // Finished all response analysis tasks.
613         $progress->end_progress();
614     }
616     /**
617      * @return string HTML snipped for the Download full report as UI.
618      */
619     protected function everything_download_options() {
620         $downloadoptions = $this->table->get_download_menu();
622         $downloadelements = new stdClass();
623         $downloadelements->formatsmenu = html_writer::select($downloadoptions, 'download',
624                 $this->table->defaultdownloadformat, false);
625         $downloadelements->downloadbutton = '<input type="submit" value="' .
626                 get_string('download') . '"/>';
628         $output = '<form action="'. $this->table->baseurl .'" method="post">';
629         $output .= '<div class="mdl-align">';
630         $output .= '<input type="hidden" name="everything" value="1"/>';
631         $output .= html_writer::tag('label', get_string('downloadeverything', 'quiz_statistics', $downloadelements));
632         $output .= '</div></form>';
634         return $output;
635     }
637     /**
638      * Generate the snipped of HTML that says when the stats were last caculated,
639      * with a recalcuate now button.
640      * @param object $quizstats      the overall quiz statistics.
641      * @param int    $quizid         the quiz id.
642      * @param array  $groupstudents  ids of students in the group or empty array if groups not used.
643      * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
644      *                                   $quiz->grademethod ie.
645      *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
646      *                                   we calculate stats based on which attempts would affect the grade for each student.
647      * @param moodle_url $reporturl url for this report
648      * @return string a HTML snipped saying when the stats were last computed,
649      *      or blank if that is not appropriate.
650      */
651     protected function output_caching_info($quizstats, $quizid, $groupstudents, $whichattempts, $reporturl) {
652         global $DB, $OUTPUT;
654         if (empty($quizstats->timemodified)) {
655             return '';
656         }
658         // Find the number of attempts since the cached statistics were computed.
659         list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, true);
660         $count = $DB->count_records_sql("
661                 SELECT COUNT(1)
662                 FROM $fromqa
663                 WHERE $whereqa
664                 AND quiza.timefinish > {$quizstats->timemodified}", $qaparams);
666         if (!$count) {
667             $count = 0;
668         }
670         // Generate the output.
671         $a = new stdClass();
672         $a->lastcalculated = format_time(time() - $quizstats->timemodified);
673         $a->count = $count;
675         $recalcualteurl = new moodle_url($reporturl,
676                 array('recalculate' => 1, 'sesskey' => sesskey()));
677         $output = '';
678         $output .= $OUTPUT->box_start(
679                 'boxaligncenter generalbox boxwidthnormal mdl-align', 'cachingnotice');
680         $output .= get_string('lastcalculated', 'quiz_statistics', $a);
681         $output .= $OUTPUT->single_button($recalcualteurl,
682                 get_string('recalculatenow', 'quiz_statistics'));
683         $output .= $OUTPUT->box_end(true);
685         return $output;
686     }
688     /**
689      * Clear the cached data for a particular report configuration. This will
690      * trigger a re-computation the next time the report is displayed.
691      * @param $qubaids qubaid_condition
692      */
693     protected function clear_cached_data($qubaids) {
694         global $DB;
695         $DB->delete_records('quiz_statistics', array('hashcode' => $qubaids->get_hash_code()));
696         $DB->delete_records('question_statistics', array('hashcode' => $qubaids->get_hash_code()));
697         $DB->delete_records('question_response_analysis', array('hashcode' => $qubaids->get_hash_code()));
698     }
700     /**
701      * @param object $quiz the quiz.
702      * @return array of questions for this quiz.
703      */
704     public function load_and_initialise_questions_for_calculations($quiz) {
705         // Load the questions.
706         $questions = quiz_report_get_significant_questions($quiz);
707         $questionids = array();
708         foreach ($questions as $question) {
709             $questionids[] = $question->id;
710         }
711         $fullquestions = question_load_questions($questionids);
712         foreach ($questions as $qno => $question) {
713             $q = $fullquestions[$question->id];
714             $q->maxmark = $question->maxmark;
715             $q->slot = $qno;
716             $q->number = $question->number;
717             $questions[$qno] = $q;
718         }
719         return $questions;
720     }