~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to mod/quiz/report/statistics/report.php

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
23
 */
24
24
 
25
 
 
26
25
defined('MOODLE_INTERNAL') || die();
27
26
 
28
27
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_form.php');
29
28
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_table.php');
30
29
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statistics_question_table.php');
31
 
require_once($CFG->dirroot . '/mod/quiz/report/statistics/qstats.php');
32
 
require_once($CFG->dirroot . '/mod/quiz/report/statistics/responseanalysis.php');
33
 
 
34
 
 
 
30
require_once($CFG->dirroot . '/mod/quiz/report/statistics/statisticslib.php');
35
31
/**
36
32
 * The quiz statistics report provides summary information about each question in
37
33
 * a quiz, compared to the whole quiz. It also provides a drill-down to more
41
37
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
38
 */
43
39
class quiz_statistics_report extends quiz_default_report {
44
 
    /** @var integer Time after which statistics are automatically recomputed. */
45
 
    const TIME_TO_CACHE_STATS = 900; // 15 minutes.
46
 
 
47
 
    /** @var object instance of table class used for main questions stats table. */
 
40
 
 
41
    /**
 
42
     * @var context_module
 
43
     */
 
44
    protected $context;
 
45
 
 
46
    /** @var quiz_statistics_table instance of table class used for main questions stats table. */
48
47
    protected $table;
49
48
 
50
49
    /**
55
54
 
56
55
        $this->context = context_module::instance($cm->id);
57
56
 
 
57
        if (!quiz_questions_in_quiz($quiz->questions)) {
 
58
            $this->print_header_and_tabs($cm, $course, $quiz, 'statistics');
 
59
            echo quiz_no_questions_message($quiz, $cm, $this->context);
 
60
            return true;
 
61
        }
 
62
 
58
63
        // Work out the display options.
59
64
        $download = optional_param('download', '', PARAM_ALPHA);
60
65
        $everything = optional_param('everything', 0, PARAM_BOOL);
61
66
        $recalculate = optional_param('recalculate', 0, PARAM_BOOL);
62
 
        // A qid paramter indicates we should display the detailed analysis of a question.
 
67
        // A qid paramter indicates we should display the detailed analysis of a sub question.
63
68
        $qid = optional_param('qid', 0, PARAM_INT);
64
69
        $slot = optional_param('slot', 0, PARAM_INT);
 
70
        $whichattempts = optional_param('whichattempts', $quiz->grademethod, PARAM_INT);
65
71
 
66
72
        $pageoptions = array();
67
73
        $pageoptions['id'] = $cm->id;
70
76
        $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
71
77
 
72
78
        $mform = new quiz_statistics_settings_form($reporturl);
 
79
 
 
80
        $mform->set_data(array('whichattempts' => $whichattempts));
 
81
 
73
82
        if ($fromform = $mform->get_data()) {
74
 
            $useallattempts = $fromform->useallattempts;
75
 
            if ($fromform->useallattempts) {
76
 
                set_user_preference('quiz_report_statistics_useallattempts',
77
 
                        $fromform->useallattempts);
78
 
            } else {
79
 
                unset_user_preference('quiz_report_statistics_useallattempts');
80
 
            }
 
83
            $whichattempts = $fromform->whichattempts;
 
84
        }
81
85
 
82
 
        } else {
83
 
            $useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0);
 
86
        if ($whichattempts != $quiz->grademethod) {
 
87
            $reporturl->param('whichattempts', $whichattempts);
84
88
        }
85
89
 
86
90
        // Find out current groups mode.
104
108
            }
105
109
        }
106
110
 
 
111
        $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
 
112
 
107
113
        // If recalculate was requested, handle that.
108
114
        if ($recalculate && confirm_sesskey()) {
109
 
            $this->clear_cached_data($quiz->id, $currentgroup, $useallattempts);
 
115
            $this->clear_cached_data($qubaids);
110
116
            redirect($reporturl);
111
117
        }
112
118
 
122
128
        $filename = quiz_report_download_filename($report, $courseshortname, $quiz->name);
123
129
        $this->table->is_downloading($download, $filename,
124
130
                get_string('quizstructureanalysis', 'quiz_statistics'));
125
 
 
126
 
        // Load the questions.
127
 
        $questions = quiz_report_get_significant_questions($quiz);
128
 
        $questionids = array();
129
 
        foreach ($questions as $question) {
130
 
            $questionids[] = $question->id;
131
 
        }
132
 
        $fullquestions = question_load_questions($questionids);
133
 
        foreach ($questions as $qno => $question) {
134
 
            $q = $fullquestions[$question->id];
135
 
            $q->maxmark = $question->maxmark;
136
 
            $q->slot = $qno;
137
 
            $q->number = $question->number;
138
 
            $questions[$qno] = $q;
139
 
        }
140
 
 
141
 
        // Get the data to be displayed.
142
 
        list($quizstats, $questions, $subquestions, $s) =
143
 
                $this->get_quiz_and_questions_stats($quiz, $currentgroup,
144
 
                        $nostudentsingroup, $useallattempts, $groupstudents, $questions);
145
 
        $quizinfo = $this->get_formatted_quiz_info_data($course, $cm, $quiz, $quizstats);
 
131
        $questions = $this->load_and_initialise_questions_for_calculations($quiz);
 
132
 
 
133
        if (!$nostudentsingroup) {
 
134
            // Get the data to be displayed.
 
135
            list($quizstats, $questionstats, $subquestionstats) =
 
136
                $this->get_quiz_and_questions_stats($quiz, $whichattempts, $groupstudents, $questions);
 
137
        } else {
 
138
            // Or create empty stats containers.
 
139
            $quizstats = new quiz_statistics_calculated($whichattempts);
 
140
            $questionstats = array();
 
141
            $subquestionstats = array();
 
142
        }
146
143
 
147
144
        // Set up the table, if there is data.
148
 
        if ($s) {
149
 
            $this->table->statistics_setup($quiz, $cm->id, $reporturl, $s);
 
145
        if ($quizstats->s()) {
 
146
            $this->table->statistics_setup($quiz, $cm->id, $reporturl, $quizstats->s());
150
147
        }
151
148
 
152
149
        // Print the page header stuff (if not downloading.
160
157
                }
161
158
            }
162
159
 
163
 
            if (!quiz_questions_in_quiz($quiz->questions)) {
164
 
                echo quiz_no_questions_message($quiz, $cm, $this->context);
165
 
            } else if (!$this->table->is_downloading() && $s == 0) {
 
160
            if (!$this->table->is_downloading() && $quizstats->s() == 0) {
166
161
                echo $OUTPUT->notification(get_string('noattempts', 'quiz'));
167
162
            }
168
163
 
169
164
            // Print display options form.
170
 
            $mform->set_data(array('useallattempts' => $useallattempts));
171
165
            $mform->display();
172
166
        }
173
167
 
174
168
        if ($everything) { // Implies is downloading.
175
169
            // Overall report, then the analysis of each question.
 
170
            $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
176
171
            $this->download_quiz_info_table($quizinfo);
177
172
 
178
 
            if ($s) {
179
 
                $this->output_quiz_structure_analysis_table($s, $questions, $subquestions);
 
173
            if ($quizstats->s()) {
 
174
                $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
180
175
 
181
 
                if ($this->table->is_downloading() == 'xhtml') {
182
 
                    $this->output_statistics_graph($quizstats->id, $s);
 
176
                if ($this->table->is_downloading() == 'xhtml' && $quizstats->s() != 0) {
 
177
                    $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
183
178
                }
184
179
 
185
 
                foreach ($questions as $question) {
 
180
                foreach ($questions as $slot => $question) {
186
181
                    if (question_bank::get_qtype(
187
182
                            $question->qtype, false)->can_analyse_responses()) {
188
183
                        $this->output_individual_question_response_analysis(
189
 
                                $question, $reporturl, $quizstats);
 
184
                                $question, $questionstats[$slot]->s, $reporturl, $qubaids);
190
185
 
191
 
                    } else if (!empty($question->_stats->subquestions)) {
192
 
                        $subitemstodisplay = explode(',', $question->_stats->subquestions);
 
186
                    } else if (!empty($questionstats[$slot]->subquestions)) {
 
187
                        $subitemstodisplay = explode(',', $questionstats[$slot]->subquestions);
193
188
                        foreach ($subitemstodisplay as $subitemid) {
194
189
                            $this->output_individual_question_response_analysis(
195
 
                                    $subquestions[$subitemid], $reporturl, $quizstats);
 
190
                                $subquestionstats[$subitemid]->question, $subquestionstats[$subitemid]->s, $reporturl, $qubaids);
196
191
                        }
197
192
                    }
198
193
                }
206
201
                print_error('questiondoesnotexist', 'question');
207
202
            }
208
203
 
209
 
            $this->output_individual_question_data($quiz, $questions[$slot]);
210
 
            $this->output_individual_question_response_analysis(
211
 
                    $questions[$slot], $reporturl, $quizstats);
 
204
            $this->output_individual_question_data($quiz, $questionstats[$slot]);
 
205
            $this->output_individual_question_response_analysis($questions[$slot], $questionstats[$slot]->s, $reporturl, $qubaids);
212
206
 
213
207
            // Back to overview link.
214
208
            echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
217
211
 
218
212
        } else if ($qid) {
219
213
            // Report on an individual sub-question indexed questionid.
220
 
            if (!isset($subquestions[$qid])) {
 
214
            if (!isset($subquestionstats[$qid])) {
221
215
                print_error('questiondoesnotexist', 'question');
222
216
            }
223
217
 
224
 
            $this->output_individual_question_data($quiz, $subquestions[$qid]);
225
 
            $this->output_individual_question_response_analysis(
226
 
                    $subquestions[$qid], $reporturl, $quizstats);
 
218
            $this->output_individual_question_data($quiz, $subquestionstats[$qid]);
 
219
            $this->output_individual_question_response_analysis($subquestionstats[$qid]->question,
 
220
                                                                $subquestionstats[$qid]->s, $reporturl, $qubaids);
227
221
 
228
222
            // Back to overview link.
229
223
            echo $OUTPUT->box('<a href="' . $reporturl->out() . '">' .
232
226
 
233
227
        } else if ($this->table->is_downloading()) {
234
228
            // Downloading overview report.
 
229
            $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
235
230
            $this->download_quiz_info_table($quizinfo);
236
 
            $this->output_quiz_structure_analysis_table($s, $questions, $subquestions);
 
231
            $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
237
232
            $this->table->finish_output();
238
233
 
239
234
        } else {
240
235
            // On-screen display of overview report.
241
 
            echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'));
242
 
            echo $this->output_caching_info($quizstats, $quiz->id, $currentgroup,
243
 
                    $groupstudents, $useallattempts, $reporturl);
 
236
            echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
 
237
            echo $this->output_caching_info($quizstats, $quiz->id, $groupstudents, $whichattempts, $reporturl);
244
238
            echo $this->everything_download_options();
 
239
            $quizinfo = $quizstats->get_formatted_quiz_info_data($course, $cm, $quiz);
245
240
            echo $this->output_quiz_info_table($quizinfo);
246
 
            if ($s) {
247
 
                echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'));
248
 
                $this->output_quiz_structure_analysis_table($s, $questions, $subquestions);
249
 
                $this->output_statistics_graph($quizstats->id, $s);
 
241
            if ($quizstats->s()) {
 
242
                echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics'), 3);
 
243
                $this->output_quiz_structure_analysis_table($quizstats->s(), $questionstats, $subquestionstats);
 
244
                $this->output_statistics_graph($quiz->id, $currentgroup, $whichattempts);
250
245
            }
251
246
        }
252
247
 
256
251
    /**
257
252
     * Display the statistical and introductory information about a question.
258
253
     * Only called when not downloading.
259
 
     * @param object $quiz the quiz settings.
260
 
     * @param object $question the question to report on.
261
 
     * @param moodle_url $reporturl the URL to resisplay this report.
262
 
     * @param object $quizstats Holds the quiz statistics.
 
254
     * @param object                                         $quiz         the quiz settings.
 
255
     * @param \core_question\statistics\questions\calculated $questionstat the question to report on.
263
256
     */
264
 
    protected function output_individual_question_data($quiz, $question) {
 
257
    protected function output_individual_question_data($quiz, $questionstat) {
265
258
        global $OUTPUT;
266
259
 
267
260
        // On-screen display. Show a summary of the question's place in the quiz,
268
261
        // and the question statistics.
269
 
        $datumfromtable = $this->table->format_row($question);
 
262
        $datumfromtable = $this->table->format_row($questionstat);
270
263
 
271
264
        // Set up the question info table.
272
265
        $questioninfotable = new html_table();
277
270
        $questioninfotable->data = array();
278
271
        $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name);
279
272
        $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'),
280
 
                $question->name.'&nbsp;'.$datumfromtable['actions']);
 
273
                $questionstat->question->name.'&nbsp;'.$datumfromtable['actions']);
281
274
        $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'),
282
275
                $datumfromtable['icon'] . '&nbsp;' .
283
 
                question_bank::get_qtype($question->qtype, false)->menu_name() . '&nbsp;' .
 
276
                question_bank::get_qtype($questionstat->question->qtype, false)->menu_name() . '&nbsp;' .
284
277
                $datumfromtable['icon']);
285
278
        $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'),
286
 
                $question->_stats->positions);
 
279
                $questionstat->positions);
287
280
 
288
281
        // Set up the question statistics table.
289
282
        $questionstatstable = new html_table();
312
305
        }
313
306
 
314
307
        // Display the various bits.
315
 
        echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'));
 
308
        echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics'), 3);
316
309
        echo html_writer::table($questioninfotable);
317
 
        echo $this->render_question_text($question);
318
 
        echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'));
 
310
        echo $this->render_question_text($questionstat->question);
 
311
        echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics'), 3);
319
312
        echo html_writer::table($questionstatstable);
320
313
    }
321
314
 
326
319
    protected function render_question_text($question) {
327
320
        global $OUTPUT;
328
321
 
329
 
        $text = question_rewrite_questiontext_preview_urls($question->questiontext,
330
 
                $this->context->id, 'quiz_statistics', $question->id);
 
322
        $text = question_rewrite_question_preview_urls($question->questiontext, $question->id,
 
323
                $question->contextid, 'question', 'questiontext', $question->id,
 
324
                $this->context->id, 'quiz_statistics');
331
325
 
332
326
        return $OUTPUT->box(format_text($text, $question->questiontextformat,
333
327
                array('noclean' => true, 'para' => false, 'overflowdiv' => true)),
336
330
 
337
331
    /**
338
332
     * Display the response analysis for a question.
339
 
     * @param object $question the question to report on.
340
 
     * @param moodle_url $reporturl the URL to resisplay this report.
341
 
     * @param object $quizstats Holds the quiz statistics.
 
333
     * @param object           $question  the question to report on.
 
334
     * @param int              $s
 
335
     * @param moodle_url       $reporturl the URL to redisplay this report.
 
336
     * @param qubaid_condition $qubaids
342
337
     */
343
 
    protected function output_individual_question_response_analysis($question,
344
 
            $reporturl, $quizstats) {
 
338
    protected function output_individual_question_response_analysis($question, $s, $reporturl, $qubaids) {
345
339
        global $OUTPUT;
346
340
 
347
341
        if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses()) {
353
347
        $qtable->export_class_instance($exportclass);
354
348
        if (!$this->table->is_downloading()) {
355
349
            // Output an appropriate title.
356
 
            echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'));
 
350
            echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics'), 3);
357
351
 
358
352
        } else {
359
353
            // Work out an appropriate title.
362
356
                $questiontabletitle = '(' . $question->number . ') ' . $questiontabletitle;
363
357
            }
364
358
            if ($this->table->is_downloading() == 'xhtml') {
365
 
                $questiontabletitle = get_string('analysisofresponsesfor',
366
 
                        'quiz_statistics', $questiontabletitle);
 
359
                $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle);
367
360
            }
368
361
 
369
362
            // Set up the table.
374
367
            }
375
368
        }
376
369
 
377
 
        $responesstats = new quiz_statistics_response_analyser($question);
378
 
        $responesstats->load_cached($quizstats->id);
 
370
        $responesanalyser = new \core_question\statistics\responses\analyser($question);
 
371
        $responseanalysis = $responesanalyser->load_cached($qubaids);
379
372
 
380
 
        $qtable->question_setup($reporturl, $question, $responesstats);
 
373
        $qtable->question_setup($reporturl, $question, $s, $responseanalysis);
381
374
        if ($this->table->is_downloading()) {
382
375
            $exportclass->output_headers($qtable->headers);
383
376
        }
384
 
 
385
 
        foreach ($responesstats->responseclasses as $partid => $partclasses) {
386
 
            $rowdata = new stdClass();
387
 
            $rowdata->part = $partid;
388
 
            foreach ($partclasses as $responseclassid => $responseclass) {
389
 
                $rowdata->responseclass = $responseclass->responseclass;
390
 
 
391
 
                $responsesdata = $responesstats->responses[$partid][$responseclassid];
392
 
                if (empty($responsesdata)) {
393
 
                    if (!array_key_exists('responseclass', $qtable->columns)) {
394
 
                        $rowdata->response = $responseclass->responseclass;
395
 
                    } else {
396
 
                        $rowdata->response = '';
397
 
                    }
398
 
                    $rowdata->fraction = $responseclass->fraction;
399
 
                    $rowdata->count = 0;
400
 
                    $qtable->add_data_keyed($qtable->format_row($rowdata));
401
 
                    continue;
402
 
                }
403
 
 
404
 
                foreach ($responsesdata as $response => $data) {
405
 
                    $rowdata->response = $response;
406
 
                    $rowdata->fraction = $data->fraction;
407
 
                    $rowdata->count = $data->count;
408
 
                    $qtable->add_data_keyed($qtable->format_row($rowdata));
 
377
        foreach ($responseanalysis->get_subpart_ids() as $partid) {
 
378
            $subpart = $responseanalysis->get_subpart($partid);
 
379
            foreach ($subpart->get_response_class_ids() as $responseclassid) {
 
380
                $responseclass = $subpart->get_response_class($responseclassid);
 
381
                $tabledata = $responseclass->data_for_question_response_table($subpart->has_multiple_response_classes(), $partid);
 
382
                foreach ($tabledata as $row) {
 
383
                    $qtable->add_data_keyed($qtable->format_row($row));
409
384
                }
410
385
            }
411
386
        }
416
391
    /**
417
392
     * Output the table that lists all the questions in the quiz with their statistics.
418
393
     * @param int $s number of attempts.
419
 
     * @param array $questions the questions in the quiz.
420
 
     * @param array $subquestions the subquestions of any random questions.
 
394
     * @param \core_question\statistics\questions\calculated[] $questionstats the stats for the main questions in the quiz.
 
395
     * @param \core_question\statistics\questions\calculated_for_subquestion[] $subquestionstats the stats of any random questions.
421
396
     */
422
 
    protected function output_quiz_structure_analysis_table($s, $questions, $subquestions) {
 
397
    protected function output_quiz_structure_analysis_table($s, $questionstats, $subquestionstats) {
423
398
        if (!$s) {
424
399
            return;
425
400
        }
426
401
 
427
 
        foreach ($questions as $question) {
428
 
            // Output the data for this questions.
429
 
            $this->table->add_data_keyed($this->table->format_row($question));
 
402
        foreach ($questionstats as $questionstat) {
 
403
            // Output the data for these question statistics.
 
404
            $this->table->add_data_keyed($this->table->format_row($questionstat));
430
405
 
431
 
            if (empty($question->_stats->subquestions)) {
 
406
            if (empty($questionstat->subquestions)) {
432
407
                continue;
433
408
            }
434
409
 
435
410
            // And its subquestions, if it has any.
436
 
            $subitemstodisplay = explode(',', $question->_stats->subquestions);
 
411
            $subitemstodisplay = explode(',', $questionstat->subquestions);
437
412
            foreach ($subitemstodisplay as $subitemid) {
438
 
                $subquestions[$subitemid]->maxmark = $question->maxmark;
439
 
                $this->table->add_data_keyed($this->table->format_row($subquestions[$subitemid]));
 
413
                $subquestionstats[$subitemid]->maxmark = $questionstat->maxmark;
 
414
                $this->table->add_data_keyed($this->table->format_row($subquestionstats[$subitemid]));
440
415
            }
441
416
        }
442
417
 
443
418
        $this->table->finish_output(!$this->table->is_downloading());
444
419
    }
445
420
 
446
 
    protected function get_formatted_quiz_info_data($course, $cm, $quiz, $quizstats) {
447
 
 
448
 
        // You can edit this array to control which statistics are displayed.
449
 
        $todisplay = array('firstattemptscount' => 'number',
450
 
                    'allattemptscount' => 'number',
451
 
                    'firstattemptsavg' => 'summarks_as_percentage',
452
 
                    'allattemptsavg' => 'summarks_as_percentage',
453
 
                    'median' => 'summarks_as_percentage',
454
 
                    'standarddeviation' => 'summarks_as_percentage',
455
 
                    'skewness' => 'number_format',
456
 
                    'kurtosis' => 'number_format',
457
 
                    'cic' => 'number_format_percent',
458
 
                    'errorratio' => 'number_format_percent',
459
 
                    'standarderror' => 'summarks_as_percentage');
460
 
 
461
 
        // General information about the quiz.
462
 
        $quizinfo = array();
463
 
        $quizinfo[get_string('quizname', 'quiz_statistics')] = format_string($quiz->name);
464
 
        $quizinfo[get_string('coursename', 'quiz_statistics')] = format_string($course->fullname);
465
 
        if ($cm->idnumber) {
466
 
            $quizinfo[get_string('idnumbermod')] = $cm->idnumber;
467
 
        }
468
 
        if ($quiz->timeopen) {
469
 
            $quizinfo[get_string('quizopen', 'quiz')] = userdate($quiz->timeopen);
470
 
        }
471
 
        if ($quiz->timeclose) {
472
 
            $quizinfo[get_string('quizclose', 'quiz')] = userdate($quiz->timeclose);
473
 
        }
474
 
        if ($quiz->timeopen && $quiz->timeclose) {
475
 
            $quizinfo[get_string('duration', 'quiz_statistics')] =
476
 
                    format_time($quiz->timeclose - $quiz->timeopen);
477
 
        }
478
 
 
479
 
        // The statistics.
480
 
        foreach ($todisplay as $property => $format) {
481
 
            if (!isset($quizstats->$property) || !$format) {
482
 
                continue;
483
 
            }
484
 
            $value = $quizstats->$property;
485
 
 
486
 
            switch ($format) {
487
 
                case 'summarks_as_percentage':
488
 
                    $formattedvalue = quiz_report_scale_summarks_as_percentage($value, $quiz);
489
 
                    break;
490
 
                case 'number_format_percent':
491
 
                    $formattedvalue = quiz_format_grade($quiz, $value) . '%';
492
 
                    break;
493
 
                case 'number_format':
494
 
                    // 2 extra decimal places, since not a percentage,
495
 
                    // and we want the same number of sig figs.
496
 
                    $formattedvalue = format_float($value, $quiz->decimalpoints + 2);
497
 
                    break;
498
 
                case 'number':
499
 
                    $formattedvalue = $value + 0;
500
 
                    break;
501
 
                default:
502
 
                    $formattedvalue = $value;
503
 
            }
504
 
 
505
 
            $quizinfo[get_string($property, 'quiz_statistics',
506
 
                    $this->using_attempts_string(!empty($quizstats->allattempts)))] =
507
 
                    $formattedvalue;
508
 
        }
509
 
 
510
 
        return $quizinfo;
511
 
    }
512
 
 
513
421
    /**
514
422
     * Output the table of overall quiz statistics.
515
423
     * @param array $quizinfo as returned by {@link get_formatted_quiz_info_data()}.
539
447
 
540
448
        // XHTML download is a special case.
541
449
        if ($this->table->is_downloading() == 'xhtml') {
542
 
            echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'));
 
450
            echo $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 3);
543
451
            echo $this->output_quiz_info_table($quizinfo);
544
452
            return;
545
453
        }
562
470
 
563
471
    /**
564
472
     * Output the HTML needed to show the statistics graph.
565
 
     * @param int $quizstatsid the id of the statistics to show in the graph.
 
473
     * @param $quizid
 
474
     * @param $currentgroup
 
475
     * @param $whichattempts
566
476
     */
567
 
    protected function output_statistics_graph($quizstatsid, $s) {
 
477
    protected function output_statistics_graph($quizid, $currentgroup, $whichattempts) {
568
478
        global $PAGE;
569
479
 
570
 
        if ($s == 0) {
571
 
            return;
572
 
        }
573
 
 
574
480
        $output = $PAGE->get_renderer('mod_quiz');
575
481
        $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
576
 
                array('id' => $quizstatsid));
 
482
                                    compact('quizid', 'currentgroup', 'whichattempts'));
577
483
        $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
578
484
        echo $output->graph($imageurl, $graphname);
579
485
    }
580
486
 
581
487
    /**
582
 
     * Return the stats data for when there are no stats to show.
583
 
     *
584
 
     * @param array $questions question definitions.
585
 
     * @param int $firstattemptscount number of first attempts (optional).
586
 
     * @param int $firstattemptscount total number of attempts (optional).
587
 
     * @return array with three elements:
588
 
     *      - integer $s Number of attempts included in the stats (0).
589
 
     *      - array $quizstats The statistics for overall attempt scores.
590
 
     *      - array $qstats The statistics for each question.
591
 
     */
592
 
    protected function get_emtpy_stats($questions, $firstattemptscount = 0,
593
 
            $allattemptscount = 0) {
594
 
        $quizstats = new stdClass();
595
 
        $quizstats->firstattemptscount = $firstattemptscount;
596
 
        $quizstats->allattemptscount = $allattemptscount;
597
 
 
598
 
        $qstats = new stdClass();
599
 
        $qstats->questions = $questions;
600
 
        $qstats->subquestions = array();
601
 
        $qstats->responses = array();
602
 
 
603
 
        return array(0, $quizstats, false);
604
 
    }
605
 
 
606
 
    /**
607
 
     * Compute the quiz statistics.
608
 
     *
609
 
     * @param object $quizid the quiz id.
610
 
     * @param int $currentgroup the current group. 0 for none.
611
 
     * @param bool $nostudentsingroup true if there a no students.
612
 
     * @param bool $useallattempts use all attempts, or just first attempts.
613
 
     * @param array $groupstudents students in this group.
614
 
     * @param array $questions question definitions.
615
 
     * @return array with three elements:
616
 
     *      - integer $s Number of attempts included in the stats.
617
 
     *      - array $quizstats The statistics for overall attempt scores.
618
 
     *      - array $qstats The statistics for each question.
619
 
     */
620
 
    protected function compute_stats($quizid, $currentgroup, $nostudentsingroup,
621
 
            $useallattempts, $groupstudents, $questions) {
622
 
        global $DB;
623
 
 
624
 
        // Calculating MEAN of marks for all attempts by students
625
 
        // http://docs.moodle.org/dev/Quiz_item_analysis_calculations_in_practise
626
 
        //     #Calculating_MEAN_of_grades_for_all_attempts_by_students.
627
 
        if ($nostudentsingroup) {
628
 
            return $this->get_emtpy_stats($questions);
629
 
        }
630
 
 
631
 
        list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql(
632
 
                $quizid, $currentgroup, $groupstudents, true);
633
 
 
634
 
        $attempttotals = $DB->get_records_sql("
635
 
                SELECT
636
 
                    CASE WHEN attempt = 1 THEN 1 ELSE 0 END AS isfirst,
637
 
                    COUNT(1) AS countrecs,
638
 
                    SUM(sumgrades) AS total
639
 
                FROM $fromqa
640
 
                WHERE $whereqa
641
 
                GROUP BY CASE WHEN attempt = 1 THEN 1 ELSE 0 END", $qaparams);
642
 
 
643
 
        if (!$attempttotals) {
644
 
            return $this->get_emtpy_stats($questions);
645
 
        }
646
 
 
647
 
        if (isset($attempttotals[1])) {
648
 
            $firstattempts = $attempttotals[1];
649
 
            $firstattempts->average = $firstattempts->total / $firstattempts->countrecs;
650
 
        } else {
651
 
            $firstattempts = new stdClass();
652
 
            $firstattempts->countrecs = 0;
653
 
            $firstattempts->total = 0;
654
 
            $firstattempts->average = null;
655
 
        }
656
 
 
657
 
        $allattempts = new stdClass();
658
 
        if (isset($attempttotals[0])) {
659
 
            $allattempts->countrecs = $firstattempts->countrecs + $attempttotals[0]->countrecs;
660
 
            $allattempts->total = $firstattempts->total + $attempttotals[0]->total;
661
 
        } else {
662
 
            $allattempts->countrecs = $firstattempts->countrecs;
663
 
            $allattempts->total = $firstattempts->total;
664
 
        }
665
 
 
666
 
        if ($useallattempts) {
667
 
            $usingattempts = $allattempts;
668
 
            $usingattempts->sql = '';
669
 
        } else {
670
 
            $usingattempts = $firstattempts;
671
 
            $usingattempts->sql = 'AND quiza.attempt = 1 ';
672
 
        }
673
 
 
674
 
        $s = $usingattempts->countrecs;
675
 
        if ($s == 0) {
676
 
            return $this->get_emtpy_stats($questions, $firstattempts->countrecs,
677
 
                    $allattempts->countrecs);
678
 
        }
679
 
        $summarksavg = $usingattempts->total / $usingattempts->countrecs;
680
 
 
681
 
        $quizstats = new stdClass();
682
 
        $quizstats->allattempts = $useallattempts;
683
 
        $quizstats->firstattemptscount = $firstattempts->countrecs;
684
 
        $quizstats->allattemptscount = $allattempts->countrecs;
685
 
        $quizstats->firstattemptsavg = $firstattempts->average;
686
 
        $quizstats->allattemptsavg = $allattempts->total / $allattempts->countrecs;
687
 
 
688
 
        // Recalculate sql again this time possibly including test for first attempt.
689
 
        list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql(
690
 
                $quizid, $currentgroup, $groupstudents, $useallattempts);
691
 
 
692
 
        // Median ...
693
 
        if ($s % 2 == 0) {
694
 
            // An even number of attempts.
695
 
            $limitoffset = $s/2 - 1;
696
 
            $limit = 2;
697
 
        } else {
698
 
            $limitoffset = floor($s/2);
699
 
            $limit = 1;
700
 
        }
701
 
        $sql = "SELECT id, sumgrades
702
 
                FROM $fromqa
703
 
                WHERE $whereqa
704
 
                ORDER BY sumgrades";
705
 
 
706
 
        $medianmarks = $DB->get_records_sql_menu($sql, $qaparams, $limitoffset, $limit);
707
 
 
708
 
        $quizstats->median = array_sum($medianmarks) / count($medianmarks);
709
 
        if ($s > 1) {
710
 
            // Fetch the sum of squared, cubed and power 4d
711
 
            // differences between marks and mean mark.
712
 
            $mean = $usingattempts->total / $s;
713
 
            $sql = "SELECT
714
 
                    SUM(POWER((quiza.sumgrades - $mean), 2)) AS power2,
715
 
                    SUM(POWER((quiza.sumgrades - $mean), 3)) AS power3,
716
 
                    SUM(POWER((quiza.sumgrades - $mean), 4)) AS power4
717
 
                    FROM $fromqa
718
 
                    WHERE $whereqa";
719
 
            $params = array('mean1' => $mean, 'mean2' => $mean, 'mean3' => $mean)+$qaparams;
720
 
 
721
 
            $powers = $DB->get_record_sql($sql, $params, MUST_EXIST);
722
 
 
723
 
            // Standard_Deviation:
724
 
            // see http://docs.moodle.org/dev/Quiz_item_analysis_calculations_in_practise
725
 
            //         #Standard_Deviation.
726
 
 
727
 
            $quizstats->standarddeviation = sqrt($powers->power2 / ($s - 1));
728
 
 
729
 
            // Skewness.
730
 
            if ($s > 2) {
731
 
                // See http://docs.moodle.org/dev/
732
 
                //      Quiz_item_analysis_calculations_in_practise#Skewness_and_Kurtosis.
733
 
                $m2= $powers->power2 / $s;
734
 
                $m3= $powers->power3 / $s;
735
 
                $m4= $powers->power4 / $s;
736
 
 
737
 
                $k2= $s*$m2/($s-1);
738
 
                $k3= $s*$s*$m3/(($s-1)*($s-2));
739
 
                if ($k2) {
740
 
                    $quizstats->skewness = $k3 / (pow($k2, 3/2));
741
 
                }
742
 
            }
743
 
 
744
 
            // Kurtosis.
745
 
            if ($s > 3) {
746
 
                $k4= $s*$s*((($s+1)*$m4)-(3*($s-1)*$m2*$m2))/(($s-1)*($s-2)*($s-3));
747
 
                if ($k2) {
748
 
                    $quizstats->kurtosis = $k4 / ($k2*$k2);
749
 
                }
750
 
            }
751
 
        }
752
 
 
753
 
        $qstats = new quiz_statistics_question_stats($questions, $s, $summarksavg);
754
 
        $qstats->load_step_data($quizid, $currentgroup, $groupstudents, $useallattempts);
755
 
        $qstats->compute_statistics();
756
 
 
757
 
        if ($s > 1) {
758
 
            $p = count($qstats->questions); // Number of positions.
759
 
            if ($p > 1 && isset($k2)) {
760
 
                $quizstats->cic = (100 * $p / ($p -1)) *
761
 
                        (1 - ($qstats->get_sum_of_mark_variance()) / $k2);
762
 
                $quizstats->errorratio = 100 * sqrt(1 - ($quizstats->cic / 100));
763
 
                $quizstats->standarderror = $quizstats->errorratio *
764
 
                        $quizstats->standarddeviation / 100;
765
 
            }
766
 
        }
767
 
 
768
 
        return array($s, $quizstats, $qstats);
769
 
    }
770
 
 
771
 
    /**
772
 
     * Load the cached statistics from the database.
773
 
     *
774
 
     * @param object $quiz the quiz settings
775
 
     * @param int $currentgroup the current group. 0 for none.
776
 
     * @param bool $nostudentsingroup true if there a no students.
777
 
     * @param bool $useallattempts use all attempts, or just first attempts.
778
 
     * @param array $groupstudents students in this group.
779
 
     * @param array $questions question definitions.
780
 
     * @return array with 4 elements:
781
 
     *     - $quizstats The statistics for overall attempt scores.
782
 
     *     - $questions The questions, with an additional _stats field.
783
 
     *     - $subquestions The subquestions, if any, with an additional _stats field.
784
 
     *     - $s Number of attempts included in the stats.
785
 
     * If there is no cached data in the database, returns an array of four nulls.
786
 
     */
787
 
    protected function try_loading_cached_stats($quiz, $currentgroup,
788
 
            $nostudentsingroup, $useallattempts, $groupstudents, $questions) {
789
 
        global $DB;
790
 
 
791
 
        $timemodified = time() - self::TIME_TO_CACHE_STATS;
792
 
        $quizstats = $DB->get_record_select('quiz_statistics',
793
 
                'quizid = ? AND groupid = ? AND allattempts = ? AND timemodified > ?',
794
 
                array($quiz->id, $currentgroup, $useallattempts, $timemodified));
795
 
 
796
 
        if (!$quizstats) {
797
 
            // No cached data found.
798
 
            return array(null, $questions, null, null);
799
 
        }
800
 
 
801
 
        if ($useallattempts) {
802
 
            $s = $quizstats->allattemptscount;
803
 
        } else {
804
 
            $s = $quizstats->firstattemptscount;
805
 
        }
806
 
 
807
 
        $subquestions = array();
808
 
        $questionstats = $DB->get_records('quiz_question_statistics',
809
 
                array('quizstatisticsid' => $quizstats->id));
810
 
 
811
 
        $subquestionstats = array();
812
 
        foreach ($questionstats as $stat) {
813
 
            if ($stat->slot) {
814
 
                $questions[$stat->slot]->_stats = $stat;
815
 
            } else {
816
 
                $subquestionstats[$stat->questionid] = $stat;
817
 
            }
818
 
        }
819
 
 
820
 
        if (!empty($subquestionstats)) {
821
 
            $subqstofetch = array_keys($subquestionstats);
822
 
            $subquestions = question_load_questions($subqstofetch);
823
 
            foreach ($subquestions as $subqid => $subq) {
824
 
                $subquestions[$subqid]->_stats = $subquestionstats[$subqid];
825
 
                $subquestions[$subqid]->maxmark = $subq->defaultmark;
826
 
            }
827
 
        }
828
 
 
829
 
        return array($quizstats, $questions, $subquestions, $s);
830
 
    }
831
 
 
832
 
    /**
833
 
     * Store the statistics in the cache tables in the database.
834
 
     *
835
 
     * @param object $quizid the quiz id.
836
 
     * @param int $currentgroup the current group. 0 for none.
837
 
     * @param bool $useallattempts use all attempts, or just first attempts.
838
 
     * @param object $quizstats The statistics for overall attempt scores.
839
 
     * @param array $questions The questions, with an additional _stats field.
840
 
     * @param array $subquestions The subquestions, if any, with an additional _stats field.
841
 
     */
842
 
    protected function cache_stats($quizid, $currentgroup,
843
 
            $quizstats, $questions, $subquestions) {
844
 
        global $DB;
845
 
 
846
 
        $toinsert = clone($quizstats);
847
 
        $toinsert->quizid = $quizid;
848
 
        $toinsert->groupid = $currentgroup;
849
 
        $toinsert->timemodified = time();
850
 
 
851
 
        // Fix up some dodgy data.
852
 
        if (isset($toinsert->errorratio) && is_nan($toinsert->errorratio)) {
853
 
            $toinsert->errorratio = null;
854
 
        }
855
 
        if (isset($toinsert->standarderror) && is_nan($toinsert->standarderror)) {
856
 
            $toinsert->standarderror = null;
857
 
        }
858
 
 
859
 
        // Store the data.
860
 
        $quizstats->id = $DB->insert_record('quiz_statistics', $toinsert);
861
 
 
862
 
        foreach ($questions as $question) {
863
 
            $question->_stats->quizstatisticsid = $quizstats->id;
864
 
            $DB->insert_record('quiz_question_statistics', $question->_stats, false);
865
 
        }
866
 
 
867
 
        foreach ($subquestions as $subquestion) {
868
 
            $subquestion->_stats->quizstatisticsid = $quizstats->id;
869
 
            $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false);
870
 
        }
871
 
 
872
 
        return $quizstats->id;
873
 
    }
874
 
 
875
 
    /**
876
488
     * Get the quiz and question statistics, either by loading the cached results,
877
489
     * or by recomputing them.
878
490
     *
879
491
     * @param object $quiz the quiz settings.
880
 
     * @param int $currentgroup the current group. 0 for none.
881
 
     * @param bool $nostudentsingroup true if there a no students.
882
 
     * @param bool $useallattempts use all attempts, or just first attempts.
 
492
     * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
 
493
     *                                   $quiz->grademethod ie.
 
494
     *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
 
495
     *                                   we calculate stats based on which attempts would affect the grade for each student.
883
496
     * @param array $groupstudents students in this group.
884
 
     * @param array $questions question definitions.
 
497
     * @param array $questions full question data.
885
498
     * @return array with 4 elements:
886
499
     *     - $quizstats The statistics for overall attempt scores.
887
 
     *     - $questions The questions, with an additional _stats field.
888
 
     *     - $subquestions The subquestions, if any, with an additional _stats field.
889
 
     *     - $s Number of attempts included in the stats.
 
500
     *     - $questionstats array of \core_question\statistics\questions\calculated objects keyed by slot.
 
501
     *     - $subquestionstats array of \core_question\statistics\questions\calculated_for_subquestion objects keyed by question id.
890
502
     */
891
 
    protected function get_quiz_and_questions_stats($quiz, $currentgroup,
892
 
            $nostudentsingroup, $useallattempts, $groupstudents, $questions) {
893
 
 
894
 
        list($quizstats, $questions, $subquestions, $s) =
895
 
                $this->try_loading_cached_stats($quiz, $currentgroup, $nostudentsingroup,
896
 
                        $useallattempts, $groupstudents, $questions);
897
 
 
898
 
        if (is_null($quizstats)) {
899
 
            list($s, $quizstats, $qstats) = $this->compute_stats($quiz->id,
900
 
                    $currentgroup, $nostudentsingroup, $useallattempts, $groupstudents, $questions);
901
 
 
902
 
            if ($s) {
903
 
                $questions = $qstats->questions;
904
 
                $subquestions = $qstats->subquestions;
905
 
 
906
 
                $quizstatisticsid = $this->cache_stats($quiz->id, $currentgroup,
907
 
                        $quizstats, $questions, $subquestions);
908
 
 
909
 
                $this->analyse_responses($quizstatisticsid, $quiz->id, $currentgroup,
910
 
                        $nostudentsingroup, $useallattempts, $groupstudents,
911
 
                        $questions, $subquestions);
 
503
    public function get_quiz_and_questions_stats($quiz, $whichattempts, $groupstudents, $questions) {
 
504
 
 
505
        $qubaids = quiz_statistics_qubaids_condition($quiz->id, $groupstudents, $whichattempts);
 
506
 
 
507
        $qcalc = new \core_question\statistics\questions\calculator($questions);
 
508
 
 
509
        $quizcalc = new quiz_statistics_calculator();
 
510
 
 
511
        if ($quizcalc->get_last_calculated_time($qubaids) === false) {
 
512
            // Recalculate now.
 
513
            list($questionstats, $subquestionstats) = $qcalc->calculate($qubaids);
 
514
 
 
515
            $quizstats = $quizcalc->calculate($quiz->id, $whichattempts, $groupstudents, count($questions),
 
516
                                              $qcalc->get_sum_of_mark_variance());
 
517
 
 
518
            if ($quizstats->s()) {
 
519
                $this->analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats);
912
520
            }
 
521
        } else {
 
522
            $quizstats = $quizcalc->get_cached($qubaids);
 
523
            list($questionstats, $subquestionstats) = $qcalc->get_cached($qubaids);
913
524
        }
914
525
 
915
 
        return array($quizstats, $questions, $subquestions, $s);
 
526
        return array($quizstats, $questionstats, $subquestionstats);
916
527
    }
917
528
 
918
 
    protected function analyse_responses($quizstatisticsid, $quizid, $currentgroup,
919
 
            $nostudentsingroup, $useallattempts, $groupstudents, $questions, $subquestions) {
920
 
 
921
 
        $qubaids = quiz_statistics_qubaids_condition(
922
 
                $quizid, $currentgroup, $groupstudents, $useallattempts);
 
529
    protected function analyse_responses_for_all_questions_and_subquestions($qubaids, $questions, $subquestionstats) {
923
530
 
924
531
        $done = array();
925
532
        foreach ($questions as $question) {
928
535
            }
929
536
            $done[$question->id] = 1;
930
537
 
931
 
            $responesstats = new quiz_statistics_response_analyser($question);
932
 
            $responesstats->analyse($qubaids);
933
 
            $responesstats->store_cached($quizstatisticsid);
 
538
            $responesstats = new \core_question\statistics\responses\analyser($question);
 
539
            $responesstats->calculate($qubaids);
934
540
        }
935
541
 
936
 
        foreach ($subquestions as $question) {
937
 
            if (!question_bank::get_qtype($question->qtype, false)->can_analyse_responses() ||
938
 
                    isset($done[$question->id])) {
 
542
        foreach ($subquestionstats as $subquestionstat) {
 
543
            if (!question_bank::get_qtype($subquestionstat->question->qtype, false)->can_analyse_responses() ||
 
544
                    isset($done[$subquestionstat->question->id])) {
939
545
                continue;
940
546
            }
941
 
            $done[$question->id] = 1;
 
547
            $done[$subquestionstat->question->id] = 1;
942
548
 
943
 
            $responesstats = new quiz_statistics_response_analyser($question);
944
 
            $responesstats->analyse($qubaids);
945
 
            $responesstats->store_cached($quizstatisticsid);
 
549
            $responesstats = new \core_question\statistics\responses\analyser($subquestionstat->question);
 
550
            $responesstats->calculate($qubaids);
946
551
        }
947
552
    }
948
553
 
970
575
    /**
971
576
     * Generate the snipped of HTML that says when the stats were last caculated,
972
577
     * with a recalcuate now button.
973
 
     * @param object $quizstats the overall quiz statistics.
974
 
     * @param int $quizid the quiz id.
975
 
     * @param int $currentgroup the id of the currently selected group, or 0.
976
 
     * @param array $groupstudents ids of students in the group.
977
 
     * @param bool $useallattempts whether to use all attempts, instead of just
978
 
     *      first attempts.
 
578
     * @param object $quizstats      the overall quiz statistics.
 
579
     * @param int    $quizid         the quiz id.
 
580
     * @param array  $groupstudents  ids of students in the group or empty array if groups not used.
 
581
     * @param string $whichattempts which attempts to use, represented internally as one of the constants as used in
 
582
     *                                   $quiz->grademethod ie.
 
583
     *                                   QUIZ_GRADEAVERAGE, QUIZ_GRADEHIGHEST, QUIZ_ATTEMPTLAST or QUIZ_ATTEMPTFIRST
 
584
     *                                   we calculate stats based on which attempts would affect the grade for each student.
 
585
     * @param moodle_url $reporturl url for this report
979
586
     * @return string a HTML snipped saying when the stats were last computed,
980
587
     *      or blank if that is not appropriate.
981
588
     */
982
 
    protected function output_caching_info($quizstats, $quizid, $currentgroup,
983
 
            $groupstudents, $useallattempts, $reporturl) {
 
589
    protected function output_caching_info($quizstats, $quizid, $groupstudents, $whichattempts, $reporturl) {
984
590
        global $DB, $OUTPUT;
985
591
 
986
592
        if (empty($quizstats->timemodified)) {
988
594
        }
989
595
 
990
596
        // Find the number of attempts since the cached statistics were computed.
991
 
        list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql(
992
 
                $quizid, $currentgroup, $groupstudents, $useallattempts, true);
 
597
        list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $groupstudents, $whichattempts, true);
993
598
        $count = $DB->count_records_sql("
994
599
                SELECT COUNT(1)
995
600
                FROM $fromqa
1021
626
    /**
1022
627
     * Clear the cached data for a particular report configuration. This will
1023
628
     * trigger a re-computation the next time the report is displayed.
1024
 
     * @param int $quizid the quiz id.
1025
 
     * @param int $currentgroup a group id, or 0.
1026
 
     * @param bool $useallattempts whether all attempts, or just first attempts are included.
 
629
     * @param $qubaids qubaid_condition
1027
630
     */
1028
 
    protected function clear_cached_data($quizid, $currentgroup, $useallattempts) {
 
631
    protected function clear_cached_data($qubaids) {
1029
632
        global $DB;
1030
 
 
1031
 
        $todelete = $DB->get_records_menu('quiz_statistics', array('quizid' => $quizid,
1032
 
                'groupid' => $currentgroup, 'allattempts' => $useallattempts), '', 'id, 1');
1033
 
 
1034
 
        if (!$todelete) {
1035
 
            return;
1036
 
        }
1037
 
 
1038
 
        list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete));
1039
 
 
1040
 
        $DB->delete_records_select('quiz_question_statistics',
1041
 
                'quizstatisticsid ' . $todeletesql, $todeleteparams);
1042
 
        $DB->delete_records_select('quiz_question_response_stats',
1043
 
                'quizstatisticsid ' . $todeletesql, $todeleteparams);
1044
 
        $DB->delete_records_select('quiz_statistics',
1045
 
                'id ' . $todeletesql, $todeleteparams);
 
633
        $DB->delete_records('quiz_statistics', array('hashcode' => $qubaids->get_hash_code()));
 
634
        $DB->delete_records('question_statistics', array('hashcode' => $qubaids->get_hash_code()));
 
635
        $DB->delete_records('question_response_analysis', array('hashcode' => $qubaids->get_hash_code()));
1046
636
    }
1047
637
 
1048
638
    /**
1049
 
     * @param bool $useallattempts whether we are using all attempts.
1050
 
     * @return the appropriate lang string to describe this option.
 
639
     * @param object $quiz the quiz.
 
640
     * @return array of questions for this quiz.
1051
641
     */
1052
 
    protected function using_attempts_string($useallattempts) {
1053
 
        if ($useallattempts) {
1054
 
            return get_string('allattempts', 'quiz_statistics');
1055
 
        } else {
1056
 
            return get_string('firstattempts', 'quiz_statistics');
1057
 
        }
1058
 
    }
1059
 
}
1060
 
 
1061
 
function quiz_statistics_attempts_sql($quizid, $currentgroup, $groupstudents,
1062
 
        $allattempts = true, $includeungraded = false) {
1063
 
    global $DB;
1064
 
 
1065
 
    $fromqa = '{quiz_attempts} quiza ';
1066
 
 
1067
 
    $whereqa = 'quiza.quiz = :quizid AND quiza.preview = 0 AND quiza.state = :quizstatefinished';
1068
 
    $qaparams = array('quizid' => $quizid, 'quizstatefinished' => quiz_attempt::FINISHED);
1069
 
 
1070
 
    if (!empty($currentgroup) && $groupstudents) {
1071
 
        list($grpsql, $grpparams) = $DB->get_in_or_equal(array_keys($groupstudents),
1072
 
                SQL_PARAMS_NAMED, 'u');
1073
 
        $whereqa .= " AND quiza.userid $grpsql";
1074
 
        $qaparams += $grpparams;
1075
 
    }
1076
 
 
1077
 
    if (!$allattempts) {
1078
 
        $whereqa .= ' AND quiza.attempt = 1';
1079
 
    }
1080
 
 
1081
 
    if (!$includeungraded) {
1082
 
        $whereqa .= ' AND quiza.sumgrades IS NOT NULL';
1083
 
    }
1084
 
 
1085
 
    return array($fromqa, $whereqa, $qaparams);
1086
 
}
1087
 
 
1088
 
/**
1089
 
 * Return a {@link qubaid_condition} from the values returned by
1090
 
 * {@link quiz_statistics_attempts_sql}
1091
 
 * @param string $fromqa from quiz_statistics_attempts_sql.
1092
 
 * @param string $whereqa from quiz_statistics_attempts_sql.
1093
 
 */
1094
 
function quiz_statistics_qubaids_condition($quizid, $currentgroup, $groupstudents,
1095
 
        $allattempts = true, $includeungraded = false) {
1096
 
    list($fromqa, $whereqa, $qaparams) = quiz_statistics_attempts_sql($quizid, $currentgroup,
1097
 
            $groupstudents, $allattempts, $includeungraded);
1098
 
    return new qubaid_join($fromqa, 'quiza.uniqueid', $whereqa, $qaparams);
1099
 
}
 
642
    public function load_and_initialise_questions_for_calculations($quiz) {
 
643
        // Load the questions.
 
644
        $questions = quiz_report_get_significant_questions($quiz);
 
645
        $questionids = array();
 
646
        foreach ($questions as $question) {
 
647
            $questionids[] = $question->id;
 
648
        }
 
649
        $fullquestions = question_load_questions($questionids);
 
650
        foreach ($questions as $qno => $question) {
 
651
            $q = $fullquestions[$question->id];
 
652
            $q->maxmark = $question->maxmark;
 
653
            $q->slot = $qno;
 
654
            $q->number = $question->number;
 
655
            $questions[$qno] = $q;
 
656
        }
 
657
        return $questions;
 
658
    }
 
659
}
 
660