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