0c1c764e |
1 | <?php |
2 | /** |
fb94cd48 |
3 | * This script calculates various statistics about student attempts |
0c1c764e |
4 | * |
5 | * @version $Id$ |
fb94cd48 |
6 | * @author Martin Dougiamas, Jamie Pratt, Tim Hunt and others. |
0c1c764e |
7 | * @license http://www.gnu.org/copyleft/gpl.html GNU Public License |
8 | * @package quiz |
71a2b878 |
9 | **/ |
0c1c764e |
10 | |
71a2b878 |
11 | define('QUIZ_REPORT_TIME_TO_CACHE_STATS', MINSECS * 15); |
0c1c764e |
12 | require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_form.php'); |
13 | require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_table.php'); |
14 | |
c386eaa3 |
15 | class quiz_statistics_report extends quiz_default_report { |
43ec99aa |
16 | |
17 | /** |
18 | * @var object instance of table class used for main questions stats table. |
19 | */ |
20 | var $table; |
0c1c764e |
21 | |
22 | /** |
23 | * Display the report. |
24 | */ |
25 | function display($quiz, $cm, $course) { |
90cd54cb |
26 | global $CFG, $DB, $QTYPES, $OUTPUT; |
0c1c764e |
27 | |
28 | $context = get_context_instance(CONTEXT_MODULE, $cm->id); |
29 | |
30 | $download = optional_param('download', '', PARAM_ALPHA); |
869309b8 |
31 | $everything = optional_param('everything', 0, PARAM_BOOL); |
d1789d5d |
32 | $recalculate = optional_param('recalculate', 0, PARAM_BOOL); |
43ec99aa |
33 | //pass the question id for detailed analysis question |
34 | $qid = optional_param('qid', 0, PARAM_INT); |
0c1c764e |
35 | $pageoptions = array(); |
36 | $pageoptions['id'] = $cm->id; |
37 | $pageoptions['q'] = $quiz->id; |
38 | $pageoptions['mode'] = 'statistics'; |
71a2b878 |
39 | |
40 | $questions = quiz_report_load_questions($quiz); |
41 | // Load the question type specific information |
42 | if (!get_question_options($questions)) { |
43 | print_error('cannotloadquestion', 'question'); |
44 | } |
0c1c764e |
45 | |
71a2b878 |
46 | |
0c1c764e |
47 | $reporturl = new moodle_url($CFG->wwwroot.'/mod/quiz/report.php', $pageoptions); |
48 | |
49 | $mform = new mod_quiz_report_statistics($reporturl); |
50 | if ($fromform = $mform->get_data()){ |
51 | $useallattempts = $fromform->useallattempts; |
52 | if ($fromform->useallattempts){ |
53 | set_user_preference('quiz_report_statistics_useallattempts', $fromform->useallattempts); |
54 | } else { |
55 | unset_user_preference('quiz_report_statistics_useallattempts'); |
56 | } |
57 | } else { |
58 | $useallattempts = get_user_preferences('quiz_report_statistics_useallattempts', 0); |
59 | } |
60 | |
61 | /// find out current groups mode |
62 | $currentgroup = groups_get_activity_group($cm, true); |
43ec99aa |
63 | |
0c1c764e |
64 | |
e72efdd4 |
65 | $nostudentsingroup = false;//true if a group is selected and their is noeone in it. |
0c1c764e |
66 | if (!empty($currentgroup)) { |
67 | // all users who can attempt quizzes and who are in the currently selected group |
96c7d771 |
68 | $groupstudents = get_users_by_capability($context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),'','','','',$currentgroup,'',false); |
e72efdd4 |
69 | if (!$groupstudents){ |
70 | $nostudentsingroup = true; |
0c1c764e |
71 | } |
71a2b878 |
72 | } else { |
73 | $groupstudents = array(); |
0c1c764e |
74 | } |
6f51ed72 |
75 | |
43ec99aa |
76 | if ($recalculate){ |
77 | if ($todelete = $DB->get_records_menu('quiz_statistics', array('quizid' => $quiz->id, 'groupid'=> (int)$currentgroup, 'allattempts'=>$useallattempts))){ |
78 | list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete)); |
79 | if (!$DB->delete_records_select('quiz_statistics', "id $todeletesql", $todeleteparams)){ |
869309b8 |
80 | print_error('errordeleting', 'quiz_statistics', '', 'quiz_statistics'); |
43ec99aa |
81 | } |
82 | if (!$DB->delete_records_select('quiz_question_statistics', "quizstatisticsid $todeletesql", $todeleteparams)){ |
869309b8 |
83 | print_error('errordeleting', 'quiz_statistics', '', 'quiz_question_statistics'); |
84 | } |
85 | if (!$DB->delete_records_select('quiz_question_response_stats', "quizstatisticsid $todeletesql", $todeleteparams)){ |
86 | print_error('errordeleting', 'quiz_statistics', '', 'quiz_question_response_stats'); |
43ec99aa |
87 | } |
88 | } |
e42f153c |
89 | redirect($reporturl); |
43ec99aa |
90 | } |
91 | |
92 | |
93 | $this->table = new quiz_report_statistics_table(); |
94 | $filename = "$course->shortname-".format_string($quiz->name,true); |
95 | $this->table->is_downloading($download, $filename, get_string('quizstructureanalysis', 'quiz_statistics')); |
96 | if (!$this->table->is_downloading()) { |
0c1c764e |
97 | // Only print headers if not asked to download data |
98 | $this->print_header_and_tabs($cm, $course, $quiz, "statistics"); |
99 | } |
100 | |
101 | if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used |
43ec99aa |
102 | if (!$this->table->is_downloading()) { |
0c1c764e |
103 | groups_print_activity_menu($cm, $reporturl->out()); |
43ec99aa |
104 | echo '<br />'; |
e72efdd4 |
105 | if ($currentgroup && !$groupstudents){ |
106 | notify(get_string('nostudentsingroup', 'quiz_statistics')); |
107 | } |
0c1c764e |
108 | } |
109 | } |
110 | |
43ec99aa |
111 | if (!$this->table->is_downloading()) { |
71a2b878 |
112 | // Print display options |
113 | $mform->set_data(array('useallattempts' => $useallattempts)); |
114 | $mform->display(); |
115 | } |
71a2b878 |
116 | |
43ec99aa |
117 | list($quizstats, $questions, $subquestions, $s, $usingattemptsstring) |
118 | = $this->quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup, |
119 | $useallattempts, $groupstudents, $questions); |
120 | |
121 | if (!$this->table->is_downloading()){ |
122 | if ($s==0){ |
90cd54cb |
123 | echo $OUTPUT->heading(get_string('noattempts','quiz')); |
71a2b878 |
124 | } |
43ec99aa |
125 | } |
126 | if ($s){ |
127 | $this->table->setup($quiz, $cm->id, $reporturl, $s); |
128 | } |
129 | |
5153422c |
130 | if (!$qid){//main page |
869309b8 |
131 | $this->output_quiz_info_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, $currentgroup, $groupstudents, $useallattempts, $download, $reporturl, $everything); |
132 | $this->output_quiz_structure_analysis_table($s, $questions, $subquestions); |
133 | if (!$this->table->is_downloading() || ($everything && $this->table->is_downloading() == 'xhtml')){ |
134 | if ($s > 1){ |
135 | $imageurl = $CFG->wwwroot.'/mod/quiz/report/statistics/statistics_graph.php?id='.$quizstats->id; |
90cd54cb |
136 | echo $OUTPUT->heading(get_string('statisticsreportgraph', 'quiz_statistics')); |
869309b8 |
137 | echo '<div class="mdl-align"><img src="'.$imageurl.'" alt="'.get_string('statisticsreportgraph', 'quiz_statistics').'" /></div>'; |
138 | } |
139 | } |
140 | if ($this->table->is_downloading()){ |
141 | if ($everything){ |
142 | foreach ($questions as $question){ |
143 | if ($question->qtype != 'random' && $QTYPES[$question->qtype]->show_analysis_of_responses()){ |
144 | $this->output_individual_question_data($quiz, $question, $reporturl, $quizstats); |
145 | } elseif (!empty($question->_stats->subquestions)) { |
146 | $subitemstodisplay = explode(',', $question->_stats->subquestions); |
147 | foreach ($subitemstodisplay as $subitemid){ |
148 | $this->output_individual_question_data($quiz, $subquestions[$subitemid], $reporturl, $quizstats); |
149 | } |
150 | } |
151 | } |
152 | $exportclassinstance =& $this->table->export_class_instance(); |
153 | } else { |
154 | $this->table->finish_output(); |
155 | } |
156 | } |
157 | if ($this->table->is_downloading() && $everything){ |
158 | $exportclassinstance->finish_document(); |
159 | } |
5153422c |
160 | } else {//individual question page |
43ec99aa |
161 | $thisquestion = false; |
162 | if (isset($questions[$qid])){ |
163 | $thisquestion = $questions[$qid]; |
164 | } else if (isset($subquestions[$qid])){ |
165 | $thisquestion = $subquestions[$qid]; |
71a2b878 |
166 | } else { |
43ec99aa |
167 | print_error('questiondoesnotexist', 'question'); |
71a2b878 |
168 | } |
869309b8 |
169 | $this->output_individual_question_data($quiz, $thisquestion, $reporturl, $quizstats); |
43ec99aa |
170 | } |
171 | return true; |
172 | } |
173 | |
869309b8 |
174 | function sort_response_details($detail1, $detail2){ |
175 | if ($detail1->credit == $detail2->credit){ |
176 | return strcmp($detail1->answer, $detail2->answer); |
43ec99aa |
177 | } |
869309b8 |
178 | return ($detail1->credit > $detail2->credit) ? -1 : 1; |
43ec99aa |
179 | } |
869309b8 |
180 | function sort_answers($answer1, $answer2){ |
181 | if ($answer1->rcount == $answer2->rcount){ |
182 | return strcmp($answer1->response, $answer2->response); |
183 | } else { |
184 | return ($answer1->rcount > $answer2->rcount)? -1 : 1; |
185 | } |
186 | } |
187 | |
188 | function output_individual_question_data($quiz, $question, $reporturl, $quizstats){ |
189 | global $CFG, $DB, $QTYPES; |
190 | require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_question_table.php'); |
191 | $this->qtable = new quiz_report_statistics_question_table($question->id); |
192 | $downloadtype = $this->table->is_downloading(); |
193 | if (!$this->table->is_downloading()){ |
194 | $datumfromtable = $this->table->format_row($question); |
195 | |
196 | $questioninfotable = new object(); |
197 | $questioninfotable->align = array('center', 'center'); |
198 | $questioninfotable->width = '60%'; |
199 | $questioninfotable->class = 'generaltable titlesleft'; |
200 | |
201 | $questioninfotable->data = array(); |
202 | $questioninfotable->data[] = array(get_string('modulename', 'quiz'), $quiz->name); |
203 | $questioninfotable->data[] = array(get_string('questionname', 'quiz_statistics'), $question->name.' '.$datumfromtable['actions']); |
204 | $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'), $datumfromtable['icon'].' '.get_string($question->qtype,'quiz').' '.$datumfromtable['icon']); |
205 | $questioninfotable->data[] = array(get_string('positions', 'quiz_statistics'), $question->_stats->positions); |
206 | |
207 | $questionstatstable = new object(); |
208 | $questionstatstable->align = array('center', 'center'); |
209 | $questionstatstable->width = '60%'; |
210 | $questionstatstable->class = 'generaltable titlesleft'; |
211 | |
212 | unset($datumfromtable['number']); |
213 | unset($datumfromtable['icon']); |
214 | $actions = $datumfromtable['actions']; |
215 | unset($datumfromtable['actions']); |
216 | unset($datumfromtable['name']); |
217 | $labels = array('s' => get_string('attempts', 'quiz_statistics'), |
218 | 'facility' => get_string('facility', 'quiz_statistics'), |
219 | 'sd' => get_string('standarddeviationq', 'quiz_statistics'), |
220 | 'random_guess_score' => get_string('random_guess_score', 'quiz_statistics'), |
221 | 'intended_weight'=> get_string('intended_weight', 'quiz_statistics'), |
222 | 'effective_weight'=> get_string('effective_weight', 'quiz_statistics'), |
223 | 'discrimination_index'=> get_string('discrimination_index', 'quiz_statistics'), |
224 | 'discriminative_efficiency'=> get_string('discriminative_efficiency', 'quiz_statistics')); |
225 | foreach ($datumfromtable as $item => $value){ |
226 | $questionstatstable->data[] = array($labels[$item], $value); |
227 | } |
90cd54cb |
228 | echo $OUTPUT->heading(get_string('questioninformation', 'quiz_statistics')); |
869309b8 |
229 | print_table($questioninfotable); |
230 | |
2280e147 |
231 | print_box(format_text($question->questiontext, $question->questiontextformat).$actions, 'boxaligncenter generalbox boxwidthnormal mdl-align'); |
869309b8 |
232 | |
90cd54cb |
233 | echo $OUTPUT->heading(get_string('questionstatistics', 'quiz_statistics')); |
869309b8 |
234 | print_table($questionstatstable); |
235 | |
236 | } else { |
237 | $this->qtable->export_class_instance($this->table->export_class_instance()); |
238 | $questiontabletitle = !empty($question->number)?'('.$question->number.') ':''; |
239 | $questiontabletitle .= "\"{$question->name}\""; |
240 | $questiontabletitle = "<em>$questiontabletitle</em>"; |
241 | if ($downloadtype == 'xhtml'){ |
242 | $questiontabletitle = get_string('analysisofresponsesfor', 'quiz_statistics', $questiontabletitle); |
243 | } |
244 | $exportclass =& $this->table->export_class_instance(); |
245 | $exportclass->start_table($questiontabletitle); |
246 | } |
247 | if ($QTYPES[$question->qtype]->show_analysis_of_responses()){ |
248 | if (!$this->table->is_downloading()){ |
90cd54cb |
249 | echo $OUTPUT->heading(get_string('analysisofresponses', 'quiz_statistics')); |
869309b8 |
250 | } |
251 | $teacherresponses = $QTYPES[$question->qtype]->get_possible_responses($question); |
252 | $this->qtable->setup($reporturl, $question, count($teacherresponses)>1); |
253 | if ($this->table->is_downloading()){ |
254 | $exportclass->output_headers($this->qtable->headers); |
255 | } |
256 | |
257 | $responses = $DB->get_records('quiz_question_response_stats', array('quizstatisticsid' => $quizstats->id, 'questionid' => $question->id), 'credit DESC, subqid ASC, aid ASC, rcount DESC'); |
258 | $responses = quiz_report_index_by_keys($responses, array('subqid', 'aid'), false); |
259 | foreach ($responses as $subqid => $response){ |
260 | foreach (array_keys($responses[$subqid]) as $aid){ |
261 | uasort($responses[$subqid][$aid], array('quiz_statistics_report', 'sort_answers')); |
262 | } |
263 | if (isset($responses[$subqid]['0'])){ |
264 | $wildcardresponse = new object(); |
265 | $wildcardresponse->answer = '*'; |
266 | $wildcardresponse->credit = 0; |
267 | $teacherresponses[$subqid][0] = $wildcardresponse; |
268 | } |
269 | } |
270 | $first = true; |
271 | $subq = 0; |
272 | foreach ($teacherresponses as $subqid => $tresponsesforsubq){ |
273 | $subq++; |
274 | $qhaswildcards = $QTYPES[$question->qtype]->has_wildcards_in_responses($question, $subqid); |
275 | if (!$first){ |
276 | $this->qtable->add_separator(); |
277 | } |
278 | uasort($tresponsesforsubq, array('quiz_statistics_report', 'sort_response_details')); |
279 | foreach ($tresponsesforsubq as $aid => $teacherresponse){ |
280 | $teacherresponserow = new object(); |
281 | $teacherresponserow->response = $teacherresponse->answer; |
282 | $teacherresponserow->rcount = 0; |
283 | $teacherresponserow->subq = $subq; |
284 | $teacherresponserow->credit = $teacherresponse->credit; |
285 | if (isset($responses[$subqid][$aid])){ |
286 | $singleanswer = count($responses[$subqid][$aid])==1 && |
287 | ($responses[$subqid][$aid][0]->response == $teacherresponserow->response); |
288 | if (!$singleanswer && $qhaswildcards){ |
289 | $this->qtable->add_separator(); |
290 | } |
291 | foreach ($responses[$subqid][$aid] as $response){ |
292 | $teacherresponserow->rcount += $response->rcount; |
293 | } |
294 | if ($aid!=0 || $qhaswildcards){ |
295 | $this->qtable->add_data_keyed($this->qtable->format_row($teacherresponserow)); |
296 | } |
297 | if (!$singleanswer){ |
298 | foreach ($responses[$subqid][$aid] as $response){ |
299 | if (!$downloadtype || $downloadtype=='xhtml'){ |
300 | $indent = ' '; |
301 | } else { |
302 | $indent = ' '; |
303 | } |
304 | $response->response = ($qhaswildcards?$indent:'').$response->response; |
305 | $response->subq = $subq; |
306 | if ((count($responses[$subqid][$aid])<2) || ($response->rcount > ($teacherresponserow->rcount / 10))){ |
307 | $this->qtable->add_data_keyed($this->qtable->format_row($response)); |
308 | } |
309 | } |
310 | } |
311 | } else { |
312 | $this->qtable->add_data_keyed($this->qtable->format_row($teacherresponserow)); |
313 | } |
314 | } |
315 | $first = false; |
316 | } |
317 | $this->qtable->finish_output(!$this->table->is_downloading()); |
318 | } |
43ec99aa |
319 | if (!$this->table->is_downloading()){ |
869309b8 |
320 | $url = $reporturl->out(); |
321 | $text = get_string('backtoquizreport', 'quiz_statistics'); |
322 | print_box("<a href=\"$url\">$text</a>", 'boxaligncenter generalbox boxwidthnormal mdl-align'); |
43ec99aa |
323 | } |
869309b8 |
324 | } |
325 | |
326 | function output_quiz_structure_analysis_table($s, $questions, $subquestions){ |
90cd54cb |
327 | global $OUTPUT; |
43ec99aa |
328 | if ($s){ |
869309b8 |
329 | if (!$this->table->is_downloading()){ |
90cd54cb |
330 | echo $OUTPUT->heading(get_string('quizstructureanalysis', 'quiz_statistics')); |
869309b8 |
331 | } |
43ec99aa |
332 | foreach ($questions as $question){ |
333 | $this->table->add_data_keyed($this->table->format_row($question)); |
334 | if (!empty($question->_stats->subquestions)){ |
335 | $subitemstodisplay = explode(',', $question->_stats->subquestions); |
336 | foreach ($subitemstodisplay as $subitemid){ |
337 | $subquestions[$subitemid]->maxgrade = $question->maxgrade; |
338 | $this->table->add_data_keyed($this->table->format_row($subquestions[$subitemid])); |
339 | } |
71a2b878 |
340 | } |
71a2b878 |
341 | } |
43ec99aa |
342 | |
869309b8 |
343 | $this->table->finish_output(!$this->table->is_downloading()); |
0c1c764e |
344 | } |
43ec99aa |
345 | } |
346 | |
869309b8 |
347 | function output_quiz_info_table($course, $cm, $quiz, $quizstats, $usingattemptsstring, |
348 | $currentgroup, $groupstudents, $useallattempts, $download, $reporturl, $everything){ |
90cd54cb |
349 | global $DB, $OUTPUT; |
43ec99aa |
350 | // Print information on the number of existing attempts |
90cd54cb |
351 | $quizinformationtablehtml = $OUTPUT->heading(get_string('quizinformation', 'quiz_statistics'), 2, 'main'); |
43ec99aa |
352 | $quizinformationtable = new object(); |
353 | $quizinformationtable->align = array('center', 'center'); |
354 | $quizinformationtable->width = '60%'; |
355 | $quizinformationtable->class = 'generaltable titlesleft'; |
356 | $quizinformationtable->data = array(); |
357 | $quizinformationtable->data[] = array(get_string('quizname', 'quiz_statistics'), $quiz->name); |
358 | $quizinformationtable->data[] = array(get_string('coursename', 'quiz_statistics'), $course->fullname); |
359 | if ($cm->idnumber){ |
0e13019d |
360 | $quizinformationtable->data[] = array(get_string('idnumbermod'), $cm->idnumber); |
43ec99aa |
361 | } |
362 | if ($quiz->timeopen){ |
363 | $quizinformationtable->data[] = array(get_string('quizopen', 'quiz'), userdate($quiz->timeopen)); |
364 | } |
365 | if ($quiz->timeclose){ |
366 | $quizinformationtable->data[] = array(get_string('quizclose', 'quiz'), userdate($quiz->timeclose)); |
367 | } |
368 | if ($quiz->timeopen && $quiz->timeclose){ |
369 | $quizinformationtable->data[] = array(get_string('duration', 'quiz_statistics'), format_time($quiz->timeclose - $quiz->timeopen)); |
370 | } |
371 | $format = array('firstattemptscount' => '', |
372 | 'allattemptscount' => '', |
373 | 'firstattemptsavg' => 'sumgrades_as_percentage', |
374 | 'allattemptsavg' => 'sumgrades_as_percentage', |
375 | 'median' => 'sumgrades_as_percentage', |
376 | 'standarddeviation' => 'sumgrades_as_percentage', |
377 | 'skewness' => '', |
378 | 'kurtosis' => '', |
379 | 'cic' => 'number_format', |
380 | 'errorratio' => 'number_format', |
381 | 'standarderror' => 'sumgrades_as_percentage'); |
382 | foreach ($quizstats as $property => $value){ |
383 | if (!isset($format[$property])){ |
384 | continue; |
71a2b878 |
385 | } |
43ec99aa |
386 | if (!is_null($value)){ |
71a2b878 |
387 | switch ($format[$property]){ |
388 | case 'sumgrades_as_percentage' : |
389 | $formattedvalue = quiz_report_scale_sumgrades_as_percentage($value, $quiz); |
390 | break; |
391 | case 'number_format' : |
f88fb62c |
392 | $formattedvalue = quiz_format_grade($quiz, $value).'%'; |
71a2b878 |
393 | break; |
394 | default : |
395 | $formattedvalue = $value; |
396 | } |
397 | $quizinformationtable->data[] = array(get_string($property, 'quiz_statistics', $usingattemptsstring), $formattedvalue); |
398 | } |
43ec99aa |
399 | } |
400 | if (!$this->table->is_downloading()){ |
d1789d5d |
401 | if (isset($quizstats->timemodified)){ |
402 | list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quiz->id, $currentgroup, $groupstudents, $useallattempts); |
403 | $sql = 'SELECT COUNT(1) ' . |
404 | 'FROM ' .$fromqa.' '. |
405 | 'WHERE ' .$whereqa.' AND qa.timefinish > :time'; |
406 | $a = new object(); |
407 | $a->lastcalculated = format_time(time() - $quizstats->timemodified); |
408 | if (!$a->count = $DB->count_records_sql($sql, array('time'=>$quizstats->timemodified)+$qaparams)){ |
409 | $a->count = 0; |
43ec99aa |
410 | } |
411 | $quizinformationtablehtml .= print_box_start('boxaligncenter generalbox boxwidthnormal mdl-align', '', true); |
412 | $quizinformationtablehtml .= get_string('lastcalculated', 'quiz_statistics', $a); |
413 | $quizinformationtablehtml .= print_single_button($reporturl->out(true), $reporturl->params()+array('recalculate'=>1), |
414 | get_string('recalculatenow', 'quiz_statistics'), 'post', '', true); |
415 | $quizinformationtablehtml .= print_box_end(true); |
d1789d5d |
416 | } |
869309b8 |
417 | $downloadoptions = $this->table->get_download_menu(); |
418 | $quizinformationtablehtml .= '<form action="'. $this->table->baseurl .'" method="post">'; |
419 | $quizinformationtablehtml .= '<div class="mdl-align">'; |
420 | $quizinformationtablehtml .= '<input type="hidden" name="everything" value="1"/>'; |
421 | $quizinformationtablehtml .= '<input type="submit" value="'.get_string('downloadeverything', 'quiz_statistics').'"/>'; |
422 | $quizinformationtablehtml .= choose_from_menu ($downloadoptions, 'download', $this->table->defaultdownloadformat, '', '', '', true); |
423 | $quizinformationtablehtml .= helpbutton('tableexportformats', get_string('tableexportformats', 'table'), 'moodle', true, false, '', true); |
424 | $quizinformationtablehtml .= '</div></form>'; |
e72efdd4 |
425 | } |
43ec99aa |
426 | $quizinformationtablehtml .= print_table($quizinformationtable, true); |
427 | if (!$this->table->is_downloading()){ |
428 | echo $quizinformationtablehtml; |
869309b8 |
429 | } elseif ($everything) { |
43ec99aa |
430 | $exportclass =& $this->table->export_class_instance(); |
431 | if ($download == 'xhtml'){ |
432 | echo $quizinformationtablehtml; |
433 | } else { |
434 | $exportclass->start_table(get_string('quizinformation', 'quiz_statistics')); |
435 | $headers = array(); |
436 | $row = array(); |
437 | foreach ($quizinformationtable->data as $data){ |
438 | $headers[]= $data[0]; |
439 | $row[] = $data[1]; |
71a2b878 |
440 | } |
43ec99aa |
441 | $exportclass->output_headers($headers); |
442 | $exportclass->add_data($row); |
443 | $exportclass->finish_table(); |
71a2b878 |
444 | } |
71a2b878 |
445 | } |
71a2b878 |
446 | } |
43ec99aa |
447 | |
71a2b878 |
448 | function quiz_stats($nostudentsingroup, $quizid, $currentgroup, $groupstudents, $questions, $useallattempts){ |
449 | global $CFG, $DB; |
e72efdd4 |
450 | if (!$nostudentsingroup){ |
451 | //Calculating_MEAN_of_grades_for_all_attempts_by_students |
452 | //http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Calculating_MEAN_of_grades_for_all_attempts_by_students |
71a2b878 |
453 | |
454 | list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents); |
455 | |
456 | $sql = 'SELECT (CASE WHEN attempt=1 THEN 1 ELSE 0 END) AS isfirst, COUNT(1) AS countrecs, SUM(sumgrades) AS total ' . |
457 | 'FROM '.$fromqa. |
458 | 'WHERE ' .$whereqa. |
459 | 'GROUP BY (attempt=1)'; |
869309b8 |
460 | |
e72efdd4 |
461 | if (!$attempttotals = $DB->get_records_sql($sql, $qaparams)){ |
e72efdd4 |
462 | $s = 0; |
869309b8 |
463 | $usingattemptsstring = ''; |
e72efdd4 |
464 | } else { |
465 | $firstattempt = $attempttotals[1]; |
466 | $allattempts = new object(); |
467 | $allattempts->countrecs = $firstattempt->countrecs + |
468 | (isset($attempttotals[0])?$attempttotals[0]->countrecs:0); |
469 | $allattempts->total = $firstattempt->total + |
470 | (isset($attempttotals[0])?$attempttotals[0]->total:0); |
471 | if ($useallattempts){ |
472 | $usingattempts = $allattempts; |
473 | $usingattempts->attempts = get_string('allattempts', 'quiz_statistics'); |
474 | $usingattempts->sql = ''; |
475 | } else { |
476 | $usingattempts = $firstattempt; |
477 | $usingattempts->attempts = get_string('firstattempts', 'quiz_statistics'); |
478 | $usingattempts->sql = 'AND qa.attempt=1 '; |
479 | } |
71a2b878 |
480 | $usingattemptsstring = $usingattempts->attempts; |
e72efdd4 |
481 | $s = $usingattempts->countrecs; |
ea751786 |
482 | $sumgradesavg = $usingattempts->total / $usingattempts->countrecs; |
e72efdd4 |
483 | } |
0c1c764e |
484 | } else { |
e72efdd4 |
485 | $s = 0; |
0c1c764e |
486 | } |
71a2b878 |
487 | $quizstats = new object(); |
488 | if ($s == 0){ |
489 | $quizstats->firstattemptscount = 0; |
490 | $quizstats->allattemptscount = 0; |
491 | } else { |
492 | $quizstats->firstattemptscount = $firstattempt->countrecs; |
493 | $quizstats->allattemptscount = $allattempts->countrecs; |
494 | $quizstats->firstattemptsavg = $firstattempt->total / $firstattempt->countrecs; |
495 | $quizstats->allattemptsavg = $allattempts->total / $allattempts->countrecs; |
0c1c764e |
496 | } |
71a2b878 |
497 | //recalculate sql again this time possibly including test for first attempt. |
498 | list($fromqa, $whereqa, $qaparams) = quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents, $useallattempts); |
499 | |
0c1c764e |
500 | //get the median |
71a2b878 |
501 | if ($s) { |
e72efdd4 |
502 | |
e72efdd4 |
503 | if (($s%2)==0){ |
0c1c764e |
504 | //even number of attempts |
e72efdd4 |
505 | $limitoffset = ($s/2) - 1; |
0c1c764e |
506 | $limit = 2; |
507 | } else { |
06d13248 |
508 | $limitoffset = (floor($s/2)); |
0c1c764e |
509 | $limit = 1; |
510 | } |
511 | $sql = 'SELECT id, sumgrades ' . |
08a7ead5 |
512 | 'FROM ' .$fromqa. |
513 | 'WHERE ' .$whereqa. |
0c1c764e |
514 | 'ORDER BY sumgrades'; |
e72efdd4 |
515 | if (!$mediangrades = $DB->get_records_sql_menu($sql, $qaparams, $limitoffset, $limit)){ |
0c1c764e |
516 | print_error('errormedian', 'quiz_statistics'); |
517 | } |
869309b8 |
518 | $quizstats->median = array_sum($mediangrades) / count($mediangrades); |
6f51ed72 |
519 | if ($s>1){ |
6f51ed72 |
520 | //fetch sum of squared, cubed and power 4d |
521 | //differences between grades and mean grade |
e72efdd4 |
522 | $mean = $usingattempts->total / $s; |
6f51ed72 |
523 | $sql = "SELECT " . |
e72efdd4 |
524 | "SUM(POWER((qa.sumgrades - :mean1),2)) AS power2, " . |
525 | "SUM(POWER((qa.sumgrades - :mean2),3)) AS power3, ". |
526 | "SUM(POWER((qa.sumgrades - :mean3),4)) AS power4 ". |
6f51ed72 |
527 | 'FROM ' .$fromqa. |
71a2b878 |
528 | 'WHERE ' .$whereqa; |
e72efdd4 |
529 | $params = array('mean1' => $mean, 'mean2' => $mean, 'mean3' => $mean)+$qaparams; |
6f51ed72 |
530 | if (!$powers = $DB->get_record_sql($sql, $params)){ |
531 | print_error('errorpowers', 'quiz_statistics'); |
532 | } |
533 | |
534 | //Standard_Deviation |
535 | //see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Standard_Deviation |
536 | |
71a2b878 |
537 | $quizstats->standarddeviation = sqrt($powers->power2 / ($s -1)); |
538 | |
6f51ed72 |
539 | |
540 | |
541 | //Skewness_and_Kurtosis |
542 | if ($s>2){ |
543 | //see http://docs.moodle.org/en/Development:Quiz_item_analysis_calculations_in_practise#Skewness_and_Kurtosis |
544 | $m2= $powers->power2 / $s; |
545 | $m3= $powers->power3 / $s; |
546 | $m4= $powers->power4 / $s; |
547 | |
548 | $k2= $s*$m2/($s-1); |
549 | $k3= $s*$s*$m3/(($s-1)*($s-2)); |
43ec99aa |
550 | if ($k2){ |
67710da4 |
551 | $quizstats->skewness = $k3 / (pow($k2, 3/2)); |
43ec99aa |
552 | } |
6f51ed72 |
553 | } |
554 | |
555 | |
556 | if ($s>3){ |
67710da4 |
557 | $k4= $s*$s*((($s+1)*$m4)-(3*($s-1)*$m2*$m2))/(($s-1)*($s-2)*($s-3)); |
43ec99aa |
558 | if ($k2){ |
559 | $quizstats->kurtosis = $k4 / ($k2*$k2); |
560 | } |
6f51ed72 |
561 | } |
e72efdd4 |
562 | } |
563 | } |
564 | if ($s){ |
4f5ffac0 |
565 | require_once("$CFG->dirroot/mod/quiz/report/statistics/qstats.php"); |
566 | $qstats = new qstats($questions, $s, $sumgradesavg); |
71a2b878 |
567 | $qstats->get_records($quizid, $currentgroup, $groupstudents, $useallattempts); |
4f5ffac0 |
568 | $qstats->process_states(); |
45cf6fd9 |
569 | $qstats->process_responses(); |
71a2b878 |
570 | } else { |
571 | $qstats = false; |
e72efdd4 |
572 | } |
71a2b878 |
573 | if ($s>1){ |
574 | $p = count($qstats->questions);//no of positions |
575 | if ($p > 1){ |
869309b8 |
576 | if (isset($k2)){ |
43ec99aa |
577 | $quizstats->cic = (100 * $p / ($p -1)) * (1 - ($qstats->sum_of_grade_variance())/$k2); |
578 | $quizstats->errorratio = 100 * sqrt(1-($quizstats->cic/100)); |
579 | $quizstats->standarderror = ($quizstats->errorratio * $quizstats->standarddeviation / 100); |
580 | } |
4f5ffac0 |
581 | } |
08a7ead5 |
582 | } |
71a2b878 |
583 | return array($s, $usingattemptsstring, $quizstats, $qstats); |
0c1c764e |
584 | } |
43ec99aa |
585 | |
586 | function quiz_questions_stats($quiz, $currentgroup, $nostudentsingroup, $useallattempts, $groupstudents, $questions){ |
587 | global $DB; |
588 | $timemodified = time() - QUIZ_REPORT_TIME_TO_CACHE_STATS; |
589 | $params = array('quizid'=>$quiz->id, 'groupid'=>(int)$currentgroup, 'allattempts'=>$useallattempts, 'timemodified'=>$timemodified); |
590 | if (!$quizstats = $DB->get_record_select('quiz_statistics', 'quizid = :quizid AND groupid = :groupid AND allattempts = :allattempts AND timemodified > :timemodified', $params, '*', true)){ |
591 | list($s, $usingattemptsstring, $quizstats, $qstats) = $this->quiz_stats($nostudentsingroup, $quiz->id, $currentgroup, $groupstudents, $questions, $useallattempts); |
869309b8 |
592 | if ($s){ |
593 | $toinsert = (object)((array)$quizstats + $params); |
594 | $toinsert->timemodified = time(); |
595 | $quizstats->id = $DB->insert_record('quiz_statistics', $toinsert); |
596 | foreach ($qstats->questions as $question){ |
597 | $question->_stats->quizstatisticsid = $quizstats->id; |
598 | $DB->insert_record('quiz_question_statistics', $question->_stats, false, true); |
599 | } |
600 | foreach ($qstats->subquestions as $subquestion){ |
601 | $subquestion->_stats->quizstatisticsid = $quizstats->id; |
602 | $DB->insert_record('quiz_question_statistics', $subquestion->_stats, false, true); |
603 | } |
604 | foreach ($qstats->responses as $response){ |
605 | $response->quizstatisticsid = $quizstats->id; |
606 | $DB->insert_record('quiz_question_response_stats', $response, false); |
607 | } |
43ec99aa |
608 | } |
869309b8 |
609 | if ($qstats){ |
43ec99aa |
610 | $questions = $qstats->questions; |
611 | $subquestions = $qstats->subquestions; |
612 | } else { |
613 | $questions = array(); |
614 | $subquestions = array(); |
615 | } |
616 | } else { |
617 | //use cached results |
618 | if ($useallattempts){ |
619 | $usingattemptsstring = get_string('allattempts', 'quiz_statistics'); |
620 | $s = $quizstats->allattemptscount; |
621 | } else { |
622 | $usingattemptsstring = get_string('firstattempts', 'quiz_statistics'); |
623 | $s = $quizstats->firstattemptscount; |
624 | } |
869309b8 |
625 | $subquestions = array(); |
43ec99aa |
626 | $questionstats = $DB->get_records('quiz_question_statistics', array('quizstatisticsid'=>$quizstats->id), 'subquestion ASC'); |
627 | $questionstats = quiz_report_index_by_keys($questionstats, array('subquestion', 'questionid')); |
628 | if (1 < count($questionstats)){ |
629 | list($mainquestionstats, $subquestionstats) = $questionstats; |
630 | $subqstofetch = array_keys($subquestionstats); |
631 | $subquestions = question_load_questions($subqstofetch); |
632 | foreach (array_keys($subquestions) as $subqid){ |
633 | $subquestions[$subqid]->_stats = $subquestionstats[$subqid]; |
634 | } |
869309b8 |
635 | } elseif (count($questionstats)) { |
43ec99aa |
636 | $mainquestionstats = $questionstats[0]; |
43ec99aa |
637 | } |
869309b8 |
638 | if (count($questionstats)) { |
639 | foreach (array_keys($questions) as $qid){ |
640 | $questions[$qid]->_stats = $mainquestionstats[$qid]; |
641 | } |
43ec99aa |
642 | } |
643 | } |
644 | return array($quizstats, $questions, $subquestions, $s, $usingattemptsstring); |
645 | } |
0c1c764e |
646 | } |
71a2b878 |
647 | function quiz_report_attempts_sql($quizid, $currentgroup, $groupstudents, $allattempts = true){ |
06d13248 |
648 | global $DB; |
71a2b878 |
649 | $fromqa = '{quiz_attempts} qa '; |
d1789d5d |
650 | $whereqa = 'qa.quiz = :quizid AND qa.preview=0 AND qa.timefinish !=0 '; |
71a2b878 |
651 | $qaparams = array('quizid'=>$quizid); |
652 | if (!empty($currentgroup) && $groupstudents) { |
653 | list($grpsql, $grpparams) = $DB->get_in_or_equal(array_keys($groupstudents), SQL_PARAMS_NAMED, 'u0000'); |
654 | $whereqa .= 'AND qa.userid '.$grpsql.' '; |
655 | $qaparams += $grpparams; |
656 | } |
657 | if (!$allattempts){ |
658 | $whereqa .= 'AND qa.attempt=1 '; |
659 | } |
660 | return array($fromqa, $whereqa, $qaparams); |
661 | } |
869309b8 |
662 | |
0c1c764e |
663 | ?> |