MDL-41758 quiz statistics : link to full break down of stats for slots
[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             if ($questionstats->for_slot($slot)->get_sub_question_ids() || $questionstats->for_slot($slot)->get_variants()) {
219                 if (!$this->table->is_downloading()) {
220                     $number = $questionstats->for_slot($slot)->question->number;
221                     echo $OUTPUT->heading(get_string('slotstructureanalysis', 'quiz_statistics', $number), 3);
222                 }
223                 $this->table->define_baseurl(new moodle_url($reporturl, array('slot' => $slot)));
224                 $this->table->format_and_add_array_of_rows($questionstats->structure_analysis_for_one_slot($slot));
225             } else {
226                 $this->output_individual_question_data($quiz, $questionstats->for_slot($slot));
227                 $this->output_individual_question_response_analysis($questions[$slot],
228                                                                     $questionstats->for_slot($slot)->s,
229                                                                     $reporturl,
230                                                                     $qubaids);
232             }
233             if (!$this->table->is_downloading()) {
234                 // Back to overview link.
235                 echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
236                         get_string('backtoquizreport', 'quiz_statistics') . '</a>',
237                         'backtomainstats boxaligncenter generalbox boxwidthnormal mdl-align');
238             } else {
239                 $this->table->finish_output();
240             }
242         } else if ($qid) {
243             // Report on an individual sub-question indexed questionid.
244             if (is_null($questionstats->for_subq($qid))) {
245                 print_error('questiondoesnotexist', 'question');
246             }
248             $this->output_individual_question_data($quiz, $questionstats->for_subq($qid));
249             $this->output_individual_question_response_analysis($questionstats->for_subq($qid)->question,
250                                                                 $questionstats->for_subq($qid)->s,
251                                                                 $reporturl,
252                                                                 $qubaids);
254             // Back to overview link.
255             echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
256                     get_string('backtoquizreport', 'quiz_statistics') . '</a>',
257                     'boxaligncenter generalbox boxwidthnormal mdl-align');
259         } else if ($this->table->is_downloading()) {
260             // Downloading overview report.
261             $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
262             $this->download_quiz_info_table($quizinfo);
263             if ($quizstats->s()) {
264                 $this->output_quiz_structure_analysis_table($questionstats);
265             }
266             $this->table->finish_output();
268         } else {
269             // On-screen display of overview report.
270             echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
271             echo $this->output_caching_info($quizstats, $quiz->id, $groupstudents, $whichattempts, $reporturl);
272             echo $this->everything_download_options();
273             $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
274             echo $this->output_quiz_info_table($quizinfo);
275             if ($quizstats->s()) {
276                 echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'), 3);
277                 $this->output_quiz_structure_analysis_table($questionstats);
278                 $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
279             }
280         }
282         return true;
283     }
285     /**
286      * Display the statistical and introductory information about a question.
287      * Only called when not downloading.
288      * @param object                                         $quiz         the quiz settings.
289      * @param \core_question\statistics\questions\calculated $questionstat the question to report on.
290      */
291     protected function output_individual_question_data($quiz, $questionstat) {
292         global $OUTPUT;
294         // On-screen display. Show a summary of the question's place in the quiz,
295         // and the question statistics.
296         $datumfromtable = $this->table->format_row($questionstat);
298         // Set up the question info table.
299         $questioninfotable = new html_table();
300         $questioninfotable->align = array('center', 'center');
301         $questioninfotable->width = '60%';
302         $questioninfotable->attributes['class'] = 'generaltable titlesleft';
304         $questioninfotable->data = array();
305         $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
306         $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
307                 $questionstat->question->name.'&nbsp;'.$datumfromtable['actions']);
308         $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
309                 $datumfromtable['icon'] . '&nbsp;' .
310                 question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . '&nbsp;' .
311                 $datumfromtable['icon']);
312         $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
313                 $questionstat->positions);
315         // Set up the question statistics table.
316         $questionstatstable = new html_table();
317         $questionstatstable->align = array('center', 'center');
318         $questionstatstable->width = '60%';
319         $questionstatstable->attributes['class'] = 'generaltable titlesleft';
321         unset($datumfromtable['number']);
322         unset($datumfromtable['icon']);
323         $actions = $datumfromtable['actions'];
324         unset($datumfromtable['actions']);
325         unset($datumfromtable['name']);
326         $labels = array(
327             's' => get_string('attempts', 'quiz_statistics'),
328             'facility' => get_string('facility', 'quiz_statistics'),
329             'sd' => get_string('standarddeviationq', 'quiz_statistics'),
330             'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'),
331             'intended_weight' => get_string('intended_weight', 'quiz_statistics'),
332             'effective_weight' => get_string('effective_weight', 'quiz_statistics'),
333             'discrimination_index' => get_string('discrimination_index', 'quiz_statistics'),
334             'discriminative_efficiency' =>
335                                 get_string('discriminative_efficiency', 'quiz_statistics')
336         );
337         foreach ($datumfromtable as $item => $value) {
338             $questionstatstable->data[] = array($labels[$item], $value);
339         }
341         // Display the various bits.
342         echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'), 3);
343         echo html_writer::table($questioninfotable);
344         echo $this->render_question_text($questionstat->question);
345         echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'), 3);
346         echo html_writer::table($questionstatstable);
347     }
349     /**
350      * @param object $question question data.
351      * @return string HTML of question text, ready for display.
352      */
353     protected function render_question_text($question) {
354         global $OUTPUT;
356         $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
357                 $question->contextid, 'question', 'questiontext', $question->id,
358                 $this->context->id, 'quiz_statistics');
360         return $OUTPUT->box(format_text($text, $question->questiontextformat,
361                 array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
362                 'questiontext boxaligncenter generalbox boxwidthnormal mdl-align');
363     }
365     /**
366      * Display the response analysis for a question.
367      * @param object           $question  the question to report on.
368      * @param int              $s
369      * @param moodle_url       $reporturl the URL to redisplay this report.
370      * @param qubaid_condition $qubaids
371      */
372     protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
373         global $OUTPUT;
375         if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
376             return;
377         }
379         $qtable = new quiz_statistics_question_table($question->id);
380         $exportclass = $this->table->export_class_instance();
381         $qtable->export_class_instance($exportclass);
382         if (!$this->table->is_downloading()) {
383             // Output an appropriate title.
384             echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'), 3);
386         } else {
387             // Work out an appropriate title.
388             $questiontabletitle = '"' . $question->name . '"';
389             if (!empty($question->number)) {
390                 $questiontabletitle = '(' . $question->number . ') ' . $questiontabletitle;
391             }
392             if ($this->table->is_downloading() == 'xhtml') {
393                 $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
394             }
396             // Set up the table.
397             $exportclass->start_table($questiontabletitle);
399             if ($this->table->is_downloading() == 'xhtml') {
400                 echo $this->render_question_text($question);
401             }
402         }
404         $responesanalyser = new \core_question\statistics\responses\analyser($question);
405         $responseanalysis = $responesanalyser->load_cached($qubaids);
407         $qtable->question_setup($reporturl, $question, $s, $responseanalysis);
408         if ($this->table->is_downloading()) {
409             $exportclass->output_headers($qtable->headers);
410         }
411         foreach ($responseanalysis->get_subpart_ids() as $partid) {
412             $subpart = $responseanalysis->get_subpart($partid);
413             foreach ($subpart->get_response_class_ids() as $responseclassid) {
414                 $responseclass = $subpart->get_response_class($responseclassid);
415                 $tabledata = $responseclass->data_for_question_response_table($subpart->has_multiple_response_classes(), $partid);
416                 foreach ($tabledata as $row) {
417                     $qtable->add_data_keyed($qtable->format_row($row));
418                 }
419             }
420         }
422         $qtable->finish_output(!$this->table->is_downloading());
423     }
425     /**
426      * Output the table that lists all the questions in the quiz with their statistics.
427      * @param \core_question\statistics\questions\all_calculated_for_qubaid_condition $questionstats the stats for all questions in
428      *                                                                                               the quiz including subqs and
429      *                                                                                               variants.
430      */
431     protected function output_quiz_structure_analysis_table($questionstats) {
432         $tooutput = array();
433         $limitvariants = !$this->table->is_downloading();
434         foreach ($questionstats->get_all_slots() as $slot) {
435             // Output the data for these question statistics.
436             $tooutput = array_merge($tooutput, $questionstats->structure_analysis_for_one_slot($slot, $limitvariants));
437         }
438         $this->table->format_and_add_array_of_rows($tooutput);
439     }
441     /**
442      * Output the table of overall quiz statistics.
443      * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
444      * @return string the HTML.
445      */
446     protected function output_quiz_info_table($quizinfo) {
448         $quizinfotable = new html_table();
449         $quizinfotable->align = array('center', 'center');
450         $quizinfotable->width = '60%';
451         $quizinfotable->attributes['class'] = 'generaltable titlesleft';
452         $quizinfotable->data = array();
454         foreach ($quizinfo as $heading => $value) {
455              $quizinfotable->data[] = array($heading, $value);
456         }
458         return html_writer::table($quizinfotable);
459     }
461     /**
462      * Download the table of overall quiz statistics.
463      * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
464      */
465     protected function download_quiz_info_table($quizinfo) {
466         global $OUTPUT;
468         // XHTML download is a special case.
469         if ($this->table->is_downloading() == 'xhtml') {
470             echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
471             echo $this->output_quiz_info_table($quizinfo);
472             return;
473         }
475         // Reformat the data ready for output.
476         $headers = array();
477         $row = array();
478         foreach ($quizinfo as $heading => $value) {
479             $headers[] = $heading;
480             $row[] = $value;
481         }
483         // Do the output.
484         $exportclass = $this->table->export_class_instance();
485         $exportclass->start_table(get_string('quizinformation', 'quiz_statistics'));
486         $exportclass->output_headers($headers);
487         $exportclass->add_data($row);
488         $exportclass->finish_table();
489     }
491     /**
492      * Output the HTML needed to show the statistics graph.
493      * @param $quizid
494      * @param $currentgroup
495      * @param $whichattempts
496      */
497     protected function output_statistics_graph($quizid, $currentgroup, $whichattempts) {
498         global $PAGE;
500         $output = $PAGE->get_renderer('mod_quiz');
501         $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
502                                     compact('quizid', 'currentgroup', 'whichattempts'));
503         $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
504         echo $output->graph($imageurl, $graphname);
505     }
507     /**
508      * Get the quiz and question statistics, either by loading the cached results,
509      * or by recomputing them.
510      *
511      * @param object $quiz               the quiz settings.
512      * @param string $whichattempts      which attempts to use, represented internally as one of the constants as used in
513      *                                   $quiz->grademethod ie.
514      *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
515      *                                   we calculate stats based on which attempts would affect the grade for each student.
516      * @param array  $groupstudents      students in this group.
517      * @param array  $questions          full question data.
518      * @param \core\progress\base|null   $progress
519      * @return array with 2 elements:    - $quizstats The statistics for overall attempt scores.
520      *                                   - $questionstats \core_question\statistics\questions\all_calculated_for_qubaid_condition
521      */
522     public function get_all_stats_and_analysis($quiz, $whichattempts, $groupstudents, $questions, $progress = null) {
524         if ($progress === null) {
525             $progress = new \core\progress\null();
526         }
528         $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
530         $qcalc = new \core_question\statistics\questions\calculator($questions, $progress);
532         $quizcalc = new \quiz_statistics\calculator($progress);
534         if ($quizcalc->get_last_calculated_time($qubaids) === false) {
536             $progress->start_progress('', 3);
538             // Recalculate now.
539             $questionstats = $qcalc->calculate($qubaids);
540             $progress->progress(1);
542             $quizstats = $quizcalc->calculate($quiz->id, $whichattempts, $groupstudents, count($questions),
543                                               $qcalc->get_sum_of_mark_variance());
544             $progress->progress(2);
545             if ($quizstats->s()) {
546                 $subquestions = $questionstats->get_sub_questions();
547                 $this->analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestions, $progress);
548             }
549             $progress->progress(3);
550             $progress->end_progress();
551         } else {
552             $quizstats = $quizcalc->get_cached($qubaids);
553             $questionstats = $qcalc->get_cached($qubaids);
554         }
556         return array($quizstats, $questionstats);
557     }
559     /**
560      * Appropriate instance depending if we want html output for the user or not.
561      *
562      * @return \core\progress\base child of \core\progress\base to handle the display (or not) of task progress.
563      */
564     protected function get_progress_trace_instance() {
565         if ($this->progress === null) {
566             if (!$this->table->is_downloading()) {
567                 $this->progress =  new \core\progress\display_if_slow(get_string('calculatingallstats', 'quiz_statistics'));
568                 $this->progress->set_display_names();
569             } else {
570                 $this->progress = new \core\progress\null();
571             }
572         }
573         return $this->progress;
574     }
576     protected function analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestions,
577                                                                             $progress = null) {
579         if ($progress === null) {
580             $progress = new \core\progress\null();
581         }
583         // Starting response analysis tasks.
584         $progress->start_progress('', count($questions) + count($subquestions));
586         // Starting response analysis of main questions.
587         $progress->start_progress('', count($questions), count($questions));
589         $done = array();
590         $donecount = 1;
591         foreach ($questions as $question) {
592             $progress->progress($donecount);
593             $donecount++;
594             if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
595                 continue;
596             }
597             $done[$question->id] = 1;
599             $responesstats = new \core_question\statistics\responses\analyser($question);
600             $responesstats->calculate($qubaids);
601         }
602         $progress->end_progress();
604         // Starting response analysis of sub-questions.
605         $countsubquestions = count($subquestions);
606         $progress->start_progress('', $countsubquestions, $countsubquestions);
607         $donecount = 1;
608         foreach ($subquestions as $subquestion) {
609             $progress->progress($donecount);
610             $donecount++;
611             if (!question_bank::get_qtype($subquestion->qtype, false)->can_analyse_responses() ||
612                     isset($done[$subquestion->id])) {
613                 continue;
614             }
615             $done[$subquestion->id] = 1;
617             $responesstats = new \core_question\statistics\responses\analyser($subquestion);
618             $responesstats->calculate($qubaids);
619         }
620         // Finished sub-question tasks.
621         $progress->end_progress();
623         // Finished all response analysis tasks.
624         $progress->end_progress();
625     }
627     /**
628      * @return string HTML snipped for the Download full report as UI.
629      */
630     protected function everything_download_options() {
631         $downloadoptions = $this->table->get_download_menu();
633         $downloadelements = new stdClass();
634         $downloadelements->formatsmenu = html_writer::select($downloadoptions, 'download',
635                 $this->table->defaultdownloadformat, false);
636         $downloadelements->downloadbutton = '<input type="submit" value="' .
637                 get_string('download') . '"/>';
639         $output = '<form action="'. $this->table->baseurl .'" method="post">';
640         $output .= '<div class="mdl-align">';
641         $output .= '<input type="hidden" name="everything" value="1"/>';
642         $output .= html_writer::tag('label', get_string('downloadeverything', 'quiz_statistics', $downloadelements));
643         $output .= '</div></form>';
645         return $output;
646     }
648     /**
649      * Generate the snipped of HTML that says when the stats were last caculated,
650      * with a recalcuate now button.
651      * @param object $quizstats      the overall quiz statistics.
652      * @param int    $quizid         the quiz id.
653      * @param array  $groupstudents  ids of students in the group or empty array if groups not used.
654      * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
655      *                                   $quiz->grademethod ie.
656      *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
657      *                                   we calculate stats based on which attempts would affect the grade for each student.
658      * @param moodle_url $reporturl url for this report
659      * @return string a HTML snipped saying when the stats were last computed,
660      *      or blank if that is not appropriate.
661      */
662     protected function output_caching_info($quizstats, $quizid, $groupstudents, $whichattempts, $reporturl) {
663         global $DB, $OUTPUT;
665         if (empty($quizstats->timemodified)) {
666             return '';
667         }
669         // Find the number of attempts since the cached statistics were computed.
670         list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, true);
671         $count = $DB->count_records_sql("
672                 SELECT COUNT(1)
673                 FROM $fromqa
674                 WHERE $whereqa
675                 AND quiza.timefinish > {$quizstats->timemodified}", $qaparams);
677         if (!$count) {
678             $count = 0;
679         }
681         // Generate the output.
682         $a = new stdClass();
683         $a->lastcalculated = format_time(time() - $quizstats->timemodified);
684         $a->count = $count;
686         $recalcualteurl = new moodle_url($reporturl,
687                 array('recalculate' => 1, 'sesskey' => sesskey()));
688         $output = '';
689         $output .= $OUTPUT->box_start(
690                 'boxaligncenter generalbox boxwidthnormal mdl-align', 'cachingnotice');
691         $output .= get_string('lastcalculated', 'quiz_statistics', $a);
692         $output .= $OUTPUT->single_button($recalcualteurl,
693                 get_string('recalculatenow', 'quiz_statistics'));
694         $output .= $OUTPUT->box_end(true);
696         return $output;
697     }
699     /**
700      * Clear the cached data for a particular report configuration. This will
701      * trigger a re-computation the next time the report is displayed.
702      * @param $qubaids qubaid_condition
703      */
704     protected function clear_cached_data($qubaids) {
705         global $DB;
706         $DB->delete_records('quiz_statistics', array('hashcode' => $qubaids->get_hash_code()));
707         $DB->delete_records('question_statistics', array('hashcode' => $qubaids->get_hash_code()));
708         $DB->delete_records('question_response_analysis', array('hashcode' => $qubaids->get_hash_code()));
709     }
711     /**
712      * @param object $quiz the quiz.
713      * @return array of questions for this quiz.
714      */
715     public function load_and_initialise_questions_for_calculations($quiz) {
716         // Load the questions.
717         $questions = quiz_report_get_significant_questions($quiz);
718         $questionids = array();
719         foreach ($questions as $question) {
720             $questionids[] = $question->id;
721         }
722         $fullquestions = question_load_questions($questionids);
723         foreach ($questions as $qno => $question) {
724             $q = $fullquestions[$question->id];
725             $q->maxmark = $question->maxmark;
726             $q->slot = $qno;
727             $q->number = $question->number;
728             $questions[$qno] = $q;
729         }
730         return $questions;
731     }