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