MDL-19812 Converted print_box* to $OUTPUT->box*
[moodle.git] / mod / quiz / report / statistics / report.php
CommitLineData
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 11define('QUIZ_REPORT_TIME_TO_CACHE_STATS', MINSECS * 15);
0c1c764e 12require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_form.php');
13require_once($CFG->dirroot.'/mod/quiz/report/statistics/statistics_table.php');
14
c386eaa3 15class 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.'&nbsp;'.$datumfromtable['actions']);
204 $questioninfotable->data[] = array(get_string('questiontype', 'quiz_statistics'), $datumfromtable['icon'].'&nbsp;'.get_string($question->qtype,'quiz').'&nbsp;'.$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 = '&nbsp;&nbsp;&nbsp;&nbsp;';
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 647function 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?>