MDL-13920 adding warning that regrading of assignments may take a while; merged from...
[moodle.git] / mod / quiz / report / analysis / report.php
CommitLineData
0fc59399 1<?php // $Id$
0fc59399 2
0fc59399 3 require_once($CFG->libdir.'/tablelib.php');
4
dd46d88e 5/// Item analysis displays a table of quiz questions and their performance
0fc59399 6class quiz_report extends quiz_default_report {
7
8 function display($quiz, $cm, $course) { /// This function just displays the report
1f21cc88 9 global $CFG, $SESSION, $QTYPES;
0fc59399 10 $strnoattempts = get_string('noattempts','quiz');
376b8cd2 11 /// Only print headers if not asked to download data
12 $download = optional_param('download', NULL);
13 if (!$download) {
14 $this->print_header_and_tabs($cm, $course, $quiz, $reportmode="analysis");
15 }
16 /// Construct the table for this particular report
b9568564 17 // echo "course <pre>";print_r($SESSION);echo "</pre>";
92f48269 18 // echo "course <pre>";print_r($CFG);echo "</pre>";
0fc59399 19 if (!$quiz->questions) {
20 print_heading($strnoattempts);
21 return true;
22 }
39395fd6 23
0fc59399 24 /// Check to see if groups are being used in this quiz
5ada3c8e 25 $currentgroup = groups_get_activity_group($cm, true);
26
27 if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
376b8cd2 28 if (!$download) {
5ada3c8e 29 groups_print_activity_menu($cm, "report.php?id=$cm->id&amp;mode=analysis");
376b8cd2 30 }
0fc59399 31 }
32
7bbe08a2 33 // set Table and Analysis stats options
b9568564 34 $getquestiondata = false ;
35 if(!isset($SESSION->quiz_analysis_table)|| !isset($SESSION->quiz_analysis_table['q']) ||$SESSION->quiz_analysis_table['q'] != $quiz->id ) {
36 $SESSION->quiz_analysis_table = array('attemptselection' => 0, 'lowmarklimit' => 0, 'pagesize' => 10, 'q' => $quiz->id );
37 $getquestiondata = true ;
0fc59399 38 }
b9568564 39 if (optional_param('refesh', NULL) == 'refesh') {
40 $getquestiondata = true ;
41 }
42 if (optional_param('download', NULL) == '') {
43 $getquestiondata = false ;
44 }
45 // here we could choose which calculation to do
46 // if the parameters have not changed then we can go through
0fc59399 47 foreach($SESSION->quiz_analysis_table as $option => $value) {
b9568564 48
0fc59399 49 $urlparam = optional_param($option, NULL);
50 if($urlparam === NULL) {
51 $$option = $value;
52 }
53 else {
b9568564 54 if ( $value === $urlparam ) {
55 // echo "<p> $option = $value not changed </p>";
56 }else {
57 // echo "<p> $option = $value changed to $urlparam </p>";
58 if ($option == 'attemptselection' || $option == 'lowmarklimit' ){
59 $getquestiondata = true ;
60 }
61 }
0fc59399 62 $$option = $SESSION->quiz_analysis_table[$option] = $urlparam;
63 }
64 }
b9568564 65 if (! $getquestiondata && isset($SESSION->quiz_analysis_table['question']) && !empty($SESSION->quiz_analysis_table['question'])){
66 $questions = $SESSION->quiz_analysis_table['question'];
67 // echo "<p> use session question </p>";
68 }else {
69
376b8cd2 70 $scorelimit = $quiz->sumgrades * $lowmarklimit/ 100;
dd46d88e 71
a472ce59 72 // ULPGC ecastro DEBUG this is here to allow for different SQL to select attempts
0fc59399 73 switch ($attemptselection) {
dd46d88e 74 case QUIZ_ALLATTEMPTS :
0fc59399 75 $limit = '';
76 $group = '';
77 break;
78 case QUIZ_HIGHESTATTEMPT :
79 $limit = ', max(qa.sumgrades) ';
80 $group = ' GROUP BY qa.userid ';
81 break;
82 case QUIZ_FIRSTATTEMPT :
83 $limit = ', min(qa.timemodified) ';
dd46d88e 84 $group = ' GROUP BY qa.userid ';
0fc59399 85 break;
dd46d88e 86 case QUIZ_LASTATTEMPT :
0fc59399 87 $limit = ', max(qa.timemodified) ';
dd46d88e 88 $group = ' GROUP BY qa.userid ';
0fc59399 89 break;
90 }
91
376b8cd2 92 if ($attemptselection != QUIZ_ALLATTEMPTS) {
8472a14d 93 $sql = 'SELECT qa.userid '.$limit.
94 'FROM '.$CFG->prefix.'user u LEFT JOIN '.$CFG->prefix.'quiz_attempts qa ON u.id = qa.userid '.
95 'WHERE qa.quiz = '.$quiz->id.' AND qa.preview = 0 '.
96 $group;
376b8cd2 97 $usermax = get_records_sql_menu($sql);
b9568564 98 }else {
99 $usermax = '';
376b8cd2 100 }
101
08103c93
ML
102 $groupmembers = '';
103 $groupwhere = '';
104
105 //Add this to the SQL to show only group users
106 if ($currentgroup) {
1d684195 107 $groupmembers = ", {$CFG->prefix}groups_members gm ";
108 $groupwhere = "AND gm.groupid = '$currentgroup' AND u.id = gm.userid";
dd46d88e 109 }
08103c93
ML
110
111 $sql = 'SELECT qa.* FROM '.$CFG->prefix.'quiz_attempts qa, '.$CFG->prefix.'user u '.$groupmembers.
8472a14d 112 'WHERE u.id = qa.userid AND qa.quiz = '.$quiz->id.' AND qa.preview = 0 AND ( qa.sumgrades >= '.$scorelimit.' ) '.$groupwhere;
08103c93 113
d309fd09 114 // ^^^^^^ es posible seleccionar aqu TODOS los quizzes, como quiere Jussi,
115 // pero habra que llevar la cuenta ed cada quiz para restaura las preguntas (quizquestions, states)
dd46d88e 116
0fc59399 117 /// Fetch the attempts
376b8cd2 118 $attempts = get_records_sql($sql);
a472ce59 119
0fc59399 120 if(empty($attempts)) {
8472a14d 121 print_heading(get_string('nothingtodisplay'));
b9568564 122 $this->print_options_form($quiz, $cm, $attemptselection, $lowmarklimit, $pagesize,$getquestiondata);
0fc59399 123 return true;
124 }
a472ce59 125 $questions = array();
b9568564 126 $this->get_questions_atttempts_data ($quiz,&$questions,&$attempts,$attemptselection,$usermax);
127 }
dd46d88e 128
43f14820 129 /// Now check if asked download of data
130 if ($download = optional_param('download', NULL)) {
43f14820 131 $filename = clean_filename("$course->shortname ".format_string($quiz->name,true));
132 switch ($download) {
133 case "Excel" :
a472ce59 134 $this->Export_Excel($questions, $filename);
43f14820 135 break;
dd46d88e 136 case "ODS":
77c7f0f5 137 $this->Export_ODS($questions, $filename);
43f14820 138 break;
dd46d88e 139 case "CSV":
a472ce59 140 $this->Export_CSV($questions, $filename);
92f48269 141 break;
142 case "HTML":
093c15be 143 $this->Export_HTML($questions, $filename);
43f14820 144 break;
145 }
146 }
dd46d88e 147
1ddb7f4e 148 /// Construct the table for this particular report
149
3b62b2ba 150 $tablecolumns = array('id', 'qname', 'responses', 'credits', 'rcounts', 'rpercent', 'facility', 'qsd','disc_index', 'disc_coeff');
dd46d88e 151 $tableheaders = array(get_string('qidtitle','quiz_analysis'), get_string('qtexttitle','quiz_analysis'),
152 get_string('responsestitle','quiz_analysis'), get_string('rfractiontitle','quiz_analysis'),
153 get_string('rcounttitle','quiz_analysis'), get_string('rpercenttitle','quiz_analysis'),
154 get_string('facilitytitle','quiz_analysis'), get_string('stddevtitle','quiz_analysis'),
155 get_string('dicsindextitle','quiz_analysis'), get_string('disccoefftitle','quiz_analysis'));
43f14820 156
157 $table = new flexible_table('mod-quiz-report-itemanalysis');
0fc59399 158
159 $table->define_columns($tablecolumns);
160 $table->define_headers($tableheaders);
a567590b 161 $table->define_baseurl($CFG->wwwroot.'/mod/quiz/report.php?q='.$quiz->id.'&amp;mode=analysis');
0fc59399 162
163 $table->sortable(true);
92f48269 164 // $table->no_sorting('rpercent');
0fc59399 165 $table->collapsible(true);
083fc55c 166 $table->initialbars(false);
dd46d88e 167
43f14820 168 $table->column_class('id', 'numcol');
169 $table->column_class('credits', 'numcol');
170 $table->column_class('rcounts', 'numcol');
92f48269 171 // $table->column_class('rpercent', 'numcol');
0fc59399 172 $table->column_class('facility', 'numcol');
dd46d88e 173 $table->column_class('qsd', 'numcol');
3b62b2ba 174 $table->column_class('disc_index', 'numcol');
175 $table->column_class('disc_coeff', 'numcol');
dd46d88e 176
43f14820 177 $table->column_suppress('id');
178 $table->column_suppress('qname');
179 $table->column_suppress('facility');
3b62b2ba 180 $table->column_suppress('qsd');
181 $table->column_suppress('disc_index');
182 $table->column_suppress('disc_coeff');
43f14820 183
0fc59399 184 $table->set_attribute('cellspacing', '0');
0fc59399 185 $table->set_attribute('id', 'itemanalysis');
186 $table->set_attribute('class', 'generaltable generalbox');
dd46d88e 187
0fc59399 188 // Start working -- this is necessary as soon as the niceties are over
189 $table->setup();
43f14820 190
191 $tablesort = $table->get_sql_sort();
dd46d88e 192 $sorts = explode(",",trim($tablesort));
43f14820 193 if ($tablesort and is_array($sorts)) {
a472ce59 194 $sortindex = array();
195 $sortorder = array ();
43f14820 196 foreach ($sorts as $sort) {
197 $data = explode(" ",trim($sort));
198 $sortindex[] = trim($data[0]);
dd46d88e 199 $s = trim($data[1]);
43f14820 200 if ($s=="ASC") {
201 $sortorder[] = SORT_ASC;
202 } else {
203 $sortorder[] = SORT_DESC;
204 }
205 }
206 if (count($sortindex)>0) {
207 $sortindex[] = "id";
208 $sortorder[] = SORT_ASC;
209 foreach($questions as $qid => $row){
210 $index1[$qid] = $row[$sortindex[0]];
211 $index2[$qid] = $row[$sortindex[1]];
212 }
213 array_multisort($index1, $sortorder[0], $index2, $sortorder[1], $questions);
214 }
215 }
216
4fa68447 217 $format_options = new stdClass;
218 $format_options->para = false;
219 $format_options->noclean = true;
220 $format_options->newlines = false;
221
dd46d88e 222 // Now it is time to page the data, simply slice the keys in the array
3db6659d 223 if (!isset($pagesize) || ((int)$pagesize < 1) ){
43f14820 224 $pagesize = 10;
225 }
226 $table->pagesize($pagesize, count($questions));
227 $start = $table->get_page_start();
228 $pagequestions = array_slice(array_keys($questions), $start, $pagesize);
dd46d88e 229
43f14820 230 foreach($pagequestions as $qnum) {
231 $q = $questions[$qnum];
232 $qid = $q['id'];
dd46d88e 233 $question = get_record('question', 'id', $qid);
80a5e194 234 $qnumber = " (".link_to_popup_window('/question/question.php?id='.$qid,'editquestion', $qid, 450, 550, get_string('edit'), 'none', true ).") ";
4fa68447 235 $qname = '<div class="qname">'.format_text($question->name." : ", $question->questiontextformat, $format_options, $quiz->course).'</div>';
dcc2ffde 236 $qicon = print_question_icon($question, true);
7d87171b 237 $qreview = quiz_question_preview_button($quiz, $question);
4fa68447 238 $qtext = format_text($question->questiontext, $question->questiontextformat, $format_options, $quiz->course);
43f14820 239 $qquestion = $qname."\n".$qtext."\n";
dd46d88e 240
4fa68447 241 $responses = array();
43f14820 242 foreach ($q['responses'] as $aid=>$resp){
dd46d88e 243 $response = new stdClass;
43f14820 244 if ($q['credits'][$aid] <= 0) {
0fc59399 245 $qclass = 'uncorrect';
43f14820 246 } elseif ($q['credits'][$aid] == 1) {
0fc59399 247 $qclass = 'correct';
248 } else {
249 $qclass = 'partialcorrect';
250 }
dd46d88e 251 $response->credit = '<span class="'.$qclass.'">('.format_float($q['credits'][$aid],2).') </span>';
c767d3b7 252 $response->text = '<span class="'.$qclass.'">'.format_text($resp, FORMAT_MOODLE, $format_options, $quiz->course).' </span>';
43f14820 253 $count = $q['rcounts'][$aid].'/'.$q['count'];
4fa68447 254 $response->rcount = $count;
92f48269 255 $response->rpercent = $q['rpercent'][$aid];
43f14820 256 $responses[] = $response;
257 }
dd46d88e 258
92168978 259 $facility = format_float($q['facility']*100,0)."%";
43f14820 260 $qsd = format_float($q['qsd'],3);
261 $di = format_float($q['disc_index'],2);
262 $dc = format_float($q['disc_coeff'],2);
dd46d88e 263
083fc55c 264 $response = array_shift($responses);
a567590b 265 $table->add_data(array($qnumber."\n<br />".$qicon."\n ".$qreview, $qquestion, $response->text, $response->credit, $response->rcount, $response->rpercent, $facility, $qsd, $di, $dc));
083fc55c 266 foreach($responses as $response) {
267 $table->add_data(array('', '', $response->text, $response->credit, $response->rcount, $response->rpercent, '', '', '', ''));
0fc59399 268 }
0fc59399 269 }
31e95855 270 print_heading_with_help(get_string("analysistitle", "quiz_analysis"),"itemanalysis", "quiz");
0fc59399 271
272 echo '<div id="tablecontainer">';
273 $table->print_html();
a567590b 274 echo '</div>';
0fc59399 275
b9568564 276 $this->print_options_form($quiz, $cm, $attemptselection, $lowmarklimit, $pagesize, $getquestiondata );
0fc59399 277 return true;
278 }
279
b9568564 280function get_questions_atttempts_data ($quiz,&$questions,&$attempts,$attemptselection,$usermax){
281 global $CFG, $SESSION, $QTYPES;
282
283 /// Here we rewiew all attempts and record data to construct the table
284 $statstable = array();
285 $questionarray = array();
286 foreach ($attempts as $attempt) {
287 $questionarray[] = quiz_questions_in_quiz($attempt->layout);
288 }
289 $questionlist = quiz_questions_in_quiz(implode(",", $questionarray));
290 $questionarray = array_unique(explode(",",$questionlist));
291 $questionlist = implode(",", $questionarray);
292 unset($questionarray);
293
294 foreach ($attempts as $attempt) {
295 switch ($attemptselection) {
296 case QUIZ_ALLATTEMPTS :
297 $userscore = 0; // can be anything, not used
298 break;
299 case QUIZ_HIGHESTATTEMPT :
300 $userscore = $attempt->sumgrades;
301 break;
302 case QUIZ_FIRSTATTEMPT :
303 $userscore = $attempt->timemodified;
304 break;
305 case QUIZ_LASTATTEMPT :
306 $userscore = $attempt->timemodified;
307 break;
308 }
309
310 if ($attemptselection == QUIZ_ALLATTEMPTS || $userscore == $usermax[$attempt->userid]) {
311
312 $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
313 " FROM {$CFG->prefix}question q,".
314 " {$CFG->prefix}quiz_question_instances i".
315 " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
316 " AND q.id IN ($questionlist)";
317
318 if (!$quizquestions = get_records_sql($sql)) {
319 error('No questions found');
320 }
321
322 // Load the question type specific information
323 if (!get_question_options($quizquestions)) {
324 error('Could not load question options');
325 }
326 // Restore the question sessions to their most recent states
327 // creating new sessions where required
328 if (!$states = get_question_states($quizquestions, $quiz, $attempt)) {
329 error('Could not restore question sessions');
330 }
331 $numbers = explode(',', $questionlist);
332 $statsrow = array();
333 foreach ($numbers as $i) {
334 if (!isset($quizquestions[$i]) or !isset($states[$i])) {
335 continue;
336 }
337 $qtype = ($quizquestions[$i]->qtype=='random') ? $states[$i]->options->question->qtype : $quizquestions[$i]->qtype;
338 if($quizquestions[$i]->qtype =='randomsamatch'){
339 $quizquestions[$i]->options =$states[$i]->options ;
340 }
341 $q = get_question_responses($quizquestions[$i], $states[$i]);
342 if (empty($q)){
343 continue;
344 }
345 $qid = $q->id;
346 if (!isset($questions[$qid])) {
347 $questions[$qid]['id'] = $qid;
348 $questions[$qid]['qname'] = $quizquestions[$i]->name;
349 foreach ($q->responses as $answer => $r) {
350 $r->count = 0;
351 $questions[$qid]['responses'][$answer] = $r->answer;
352 $questions[$qid]['rcounts'][$answer] = 0;
353 $questions[$qid]['credits'][$answer] = $r->credit;
354 $statsrow[$qid] = 0;
355 }
356 }
357 $responses = get_question_actual_response($quizquestions[$i], $states[$i]);
358 foreach ($responses as $resp){
359 if ($resp) {
360 if ($key = array_search($resp, $questions[$qid]['responses'])) {
361 $questions[$qid]['rcounts'][$key]++;
362 } else {
363 $test = new stdClass;
364 $test->responses = $QTYPES[$quizquestions[$i]->qtype]->get_correct_responses($quizquestions[$i], $states[$i]);
365 if ($key = $QTYPES[$quizquestions[$i]->qtype]->check_response($quizquestions[$i], $states[$i], $test)) {
366 $questions[$qid]['rcounts'][$key]++;
367 } else {
368 $questions[$qid]['responses'][] = $resp;
369 $questions[$qid]['rcounts'][] = 1;
370 $questions[$qid]['credits'][] = 0;
371 }
372 }
373 }
374 }
375 $statsrow[$qid] = get_question_fraction_grade($quizquestions[$i], $states[$i]);
376 }
377 $attemptscores[$attempt->id] = $attempt->sumgrades;
378 $statstable[$attempt->id] = $statsrow;
379 }
380 } // Statistics Data table built
381
382 unset($attempts);
383 unset($quizquestions);
384 unset($states);
0fc59399 385
b9568564 386 // now calculate statistics and set the values in the $questions array
387 $top = max($attemptscores);
388 $bottom = min($attemptscores);
389 $gap = ($top - $bottom)/3;
390 $top -=$gap;
391 $bottom +=$gap;
392 foreach ($questions as $qid=>$q) {
393 $questions[$qid] = $this->report_question_stats($q, $attemptscores, $statstable, $top, $bottom);
394 // calculate rpercent
395 foreach($q['responses']as $aid => $resp){
396 $rpercent = '('.format_float($q['rcounts'][$aid]/$q['count']*100,0).'%)';
397 $questions[$qid]['rpercent'][$aid] = $rpercent ;
398 }
399 $SESSION->quiz_analysis_table['question'][$qid]=$questions[$qid] ;
400
401 }
402
403
404 unset($attemptscores);
405 unset($statstable);
406 }
407 function print_options_form($quiz, $cm, $attempts, $lowlimit=0, $pagesize=10,$getquestiondata) {
43f14820 408 global $CFG, $USER;
0fc59399 409 echo '<div class="controls">';
b7dc2256 410 echo '<form id="options" action="report.php" method="post">';
09275894 411 echo '<fieldset class="invisiblefieldset">';
083fc55c 412 echo '<p class="quiz-report-options">'.get_string('analysisoptions', 'quiz').': </p>';
0fc59399 413 echo '<input type="hidden" name="id" value="'.$cm->id.'" />';
414 echo '<input type="hidden" name="q" value="'.$quiz->id.'" />';
415 echo '<input type="hidden" name="mode" value="analysis" />';
636b8f19 416 echo '<p><label for="menuattemptselection">'.get_string('attemptselection', 'quiz_analysis').'</label> ';
43f14820 417 $options = array ( QUIZ_ALLATTEMPTS => get_string("attemptsall", 'quiz_analysis'),
418 QUIZ_HIGHESTATTEMPT => get_string("attemptshighest", 'quiz_analysis'),
419 QUIZ_FIRSTATTEMPT => get_string("attemptsfirst", 'quiz_analysis'),
420 QUIZ_LASTATTEMPT => get_string("attemptslast", 'quiz_analysis'));
0fc59399 421 choose_from_menu($options, "attemptselection", "$attempts", "");
636b8f19 422 echo '</p>';
423 echo '<p><label for="lowmarklimit">'.get_string('lowmarkslimit', 'quiz_analysis').'</label> ';
424 echo '<input type="text" id="lowmarklimit" name="lowmarklimit" size="1" value="'.$lowlimit.'" /> % </p>';
425 echo '<p><label for="pagesize">'.get_string('pagesize', 'quiz_analysis').'</label> ';
426 echo '<input type="text" id="pagesize" name="pagesize" size="1" value="'.$pagesize.'" /></p>';
427 echo '<p><input type="submit" value="'.get_string('go').'" />';
376b8cd2 428 helpbutton("analysisoptions", get_string("analysisoptions",'quiz_analysis'), 'quiz');
636b8f19 429 echo '</p>';
b9568564 430 if (!$getquestiondata){
431 echo '<p>This table was built from a previous attempts search in the database at '.userdate(time(), get_string("strftimedaydatetime")).'</p>';
432 echo '<p><input type="submit" value="'.get_string('refresh').'" />';
433 }
434 // echo '<p>Download all the data from the actual attempts search in the database </p>';
09275894 435 echo '</fieldset>';
0fc59399 436 echo '</form>';
dd46d88e 437 echo '</div>';
43f14820 438 echo "\n";
dd46d88e 439
09275894 440 echo '<table class="boxaligncenter"><tr>';
77c7f0f5 441 $options = array();
43f14820 442 $options["id"] = "$cm->id";
443 $options["q"] = "$quiz->id";
444 $options["mode"] = "analysis";
445 $options['sesskey'] = $USER->sesskey;
446 $options["noheader"] = "yes";
dd46d88e 447 echo '<td>';
77c7f0f5 448 $options["download"] = "ODS";
449 print_single_button("report.php", $options, get_string("downloadods"));
dd46d88e 450 echo "</td>\n";
451 echo '<td>';
43f14820 452 $options["download"] = "Excel";
453 print_single_button("report.php", $options, get_string("downloadexcel"));
dd46d88e 454 echo "</td>\n";
455
a472ce59 456 if (file_exists("$CFG->libdir/phpdocwriter/lib/include.php")) {
dd46d88e 457 echo '<td>';
a472ce59 458 $options["download"] = "OOo";
459 print_single_button("report.php", $options, get_string("downloadooo", "quiz_analysis"));
460 echo "</td>\n";
461 }
43f14820 462 echo '<td>';
463 $options["download"] = "CSV";
464 print_single_button('report.php', $options, get_string("downloadtext"));
465 echo "</td>\n";
466 echo "<td>";
92f48269 467 echo '<td>';
468 $options["download"] = "HTML";
469 print_single_button('report.php', $options, get_string("downloadhtml"));
470 echo "</td>\n";
471 echo "<td>";
c358d620 472 helpbutton('analysisdownload', get_string('analysisdownload', 'quiz_analysis'), 'quiz');
43f14820 473 echo "</td>\n";
474 echo '</tr></table>';
475}
0fc59399 476
43f14820 477 function report_question_stats(&$q, &$attemptscores, &$questionscores, $top, $bottom) {
a472ce59 478 $qstats = array();
43f14820 479 $qid = $q['id'];
0fc59399 480 $top_scores = $top_count = 0;
481 $bottom_scores = $bottom_count = 0;
482 foreach ($questionscores as $aid => $qrow){
483 if (isset($qrow[$qid])){
484 $qstats[] = array($attemptscores[$aid],$qrow[$qid]);
485 if ($attemptscores[$aid]>=$top){
486 $top_scores +=$qrow[$qid];
487 $top_count++;
488 }
489 if ($attemptscores[$aid]<=$bottom){
490 $bottom_scores +=$qrow[$qid];
491 $bottom_count++;
dd46d88e 492 }
0fc59399 493 }
494 }
0fc59399 495 $n = count($qstats);
376b8cd2 496 $sumx = stats_sumx($qstats, array(0,0));
39395fd6 497 $sumg = $sumx[0];
498 $sumq = $sumx[1];
376b8cd2 499 $sumx2 = stats_sumx2($qstats, array(0,0));
39395fd6 500 $sumg2 = $sumx2[0];
501 $sumq2 = $sumx2[1];
376b8cd2 502 $sumxy = stats_sumxy($qstats, array(0,0));
39395fd6 503 $sumgq = $sumxy[0];
dd46d88e 504
43f14820 505 $q['count'] = $n;
506 $q['facility'] = $sumq/$n;
0fc59399 507 if ($n<2) {
43f14820 508 $q['qsd'] = sqrt(($sumq2 - $sumq*$sumq/$n)/($n));
509 $gsd = sqrt(($sumg2 - $sumg*$sumg/$n)/($n));
0fc59399 510 } else {
43f14820 511 $q['qsd'] = sqrt(($sumq2 - $sumq*$sumq/$n)/($n-1));
512 $gsd = sqrt(($sumg2 - $sumg*$sumg/$n)/($n-1));
0fc59399 513 }
43f14820 514 $q['disc_index'] = ($top_scores - $bottom_scores)/max($top_count, $bottom_count, 1);
515 $div = $n*$gsd*$q['qsd'];
0fc59399 516 if ($div!=0) {
39395fd6 517 $q['disc_coeff'] = ($sumgq - $sumg*$sumq/$n)/$div;
0fc59399 518 } else {
43f14820 519 $q['disc_coeff'] = -999;
0fc59399 520 }
43f14820 521 return $q;
522 }
523
524 function Export_Excel(&$questions, $filename) {
525 global $CFG;
8867a3bb 526 require_once("$CFG->libdir/excellib.class.php");
527
528 /// Calculate file name
a472ce59 529 $filename .= ".xls";
8867a3bb 530 /// Creating a workbook
531 $workbook = new MoodleExcelWorkbook("-");
532 /// Sending HTTP headers
533 $workbook->send($filename);
534 /// Creating the first worksheet
43f14820 535 $sheettitle = get_string('reportanalysis','quiz_analysis');
d8247a98 536 $myxls =& $workbook->add_worksheet($sheettitle);
8867a3bb 537 /// format types
43f14820 538 $format =& $workbook->add_format();
539 $format->set_bold(0);
d8247a98 540 $formatbc =& $workbook->add_format();
541 $formatbc->set_bold(1);
542 $formatb =& $workbook->add_format();
543 $formatb->set_bold(1);
43f14820 544 $formaty =& $workbook->add_format();
545 $formaty->set_bg_color('yellow');
546 $formatyc =& $workbook->add_format();
547 $formatyc->set_bg_color('yellow'); //bold text on yellow bg
548 $formatyc->set_bold(1);
549 $formatyc->set_align('center');
550 $formatc =& $workbook->add_format();
551 $formatc->set_align('center');
43f14820 552 $formatbc->set_align('center');
553 $formatbpct =& $workbook->add_format();
554 $formatbpct->set_bold(1);
555 $formatbpct->set_num_format('0.0%');
556 $formatbrt =& $workbook->add_format();
557 $formatbrt->set_bold(1);
558 $formatbrt->set_align('right');
559 $formatred =& $workbook->add_format();
560 $formatred->set_bold(1);
561 $formatred->set_color('red');
562 $formatred->set_align('center');
563 $formatblue =& $workbook->add_format();
564 $formatblue->set_bold(1);
565 $formatblue->set_color('blue');
566 $formatblue->set_align('center');
8867a3bb 567 /// Here starts workshhet headers
43f14820 568 $myxls->write_string(0,0,$sheettitle,$formatb);
569
b9568564 570 $headers = array(strip_tags(get_string('qidtitle','quiz_analysis')), strip_tags(get_string('qtypetitle','quiz_analysis')),
571 strip_tags(get_string('qnametitle','quiz_analysis')), strip_tags(get_string('qtexttitle','quiz_analysis')),
572 strip_tags(get_string('responsestitle','quiz_analysis')), strip_tags(get_string('rfractiontitle','quiz_analysis')),
573 strip_tags(get_string('rcounttitle','quiz_analysis')), strip_tags(get_string('rpercenttitle','quiz_analysis')),
574 strip_tags(get_string('qcounttitle','quiz_analysis')),
575 strip_tags(get_string('facilitytitle','quiz_analysis')), strip_tags(get_string('stddevtitle','quiz_analysis')),
576 strip_tags(get_string('dicsindextitle','quiz_analysis')), strip_tags(get_string('disccoefftitle','quiz_analysis')));
43f14820 577
578 $col = 0;
579 foreach ($headers as $item) {
580 $myxls->write(2,$col,$item,$formatbc);
581 $col++;
582 }
dd46d88e 583
43f14820 584 $row = 3;
dd46d88e 585 foreach($questions as $q) {
77c7f0f5 586 $rows = $this->print_row_stats_data($q);
43f14820 587 foreach($rows as $rowdata){
588 $col = 0;
589 foreach($rowdata as $item){
d8247a98 590 $myxls->write($row,$col,$item,$format);
43f14820 591 $col++;
592 }
593 $row++;
594 }
595 }
8867a3bb 596 /// Close the workbook
43f14820 597 $workbook->close();
8867a3bb 598
d8247a98 599 exit;
0fc59399 600 }
601
43f14820 602
77c7f0f5 603 function Export_ODS(&$questions, $filename) {
43f14820 604 global $CFG;
77c7f0f5 605 require_once("$CFG->libdir/odslib.class.php");
606
607 /// Calculate file name
608 $filename .= ".ods";
609 /// Creating a workbook
610 $workbook = new MoodleODSWorkbook("-");
611 /// Sending HTTP headers
612 $workbook->send($filename);
613 /// Creating the first worksheet
614 $sheettitle = get_string('reportanalysis','quiz_analysis');
615 $myxls =& $workbook->add_worksheet($sheettitle);
616 /// format types
617 $format =& $workbook->add_format();
618 $format->set_bold(0);
619 $formatbc =& $workbook->add_format();
620 $formatbc->set_bold(1);
621 $formatb =& $workbook->add_format();
622 $formatb->set_bold(1);
623 $formaty =& $workbook->add_format();
624 $formaty->set_bg_color('yellow');
625 $formatyc =& $workbook->add_format();
626 $formatyc->set_bg_color('yellow'); //bold text on yellow bg
627 $formatyc->set_bold(1);
628 $formatyc->set_align('center');
629 $formatc =& $workbook->add_format();
630 $formatc->set_align('center');
631 $formatbc->set_align('center');
632 $formatbpct =& $workbook->add_format();
633 $formatbpct->set_bold(1);
634 $formatbpct->set_num_format('0.0%');
635 $formatbrt =& $workbook->add_format();
636 $formatbrt->set_bold(1);
637 $formatbrt->set_align('right');
638 $formatred =& $workbook->add_format();
639 $formatred->set_bold(1);
640 $formatred->set_color('red');
641 $formatred->set_align('center');
642 $formatblue =& $workbook->add_format();
643 $formatblue->set_bold(1);
644 $formatblue->set_color('blue');
645 $formatblue->set_align('center');
646 /// Here starts workshhet headers
647 $myxls->write_string(0,0,$sheettitle,$formatb);
43f14820 648
b9568564 649 $headers = array(strip_tags(get_string('qidtitle','quiz_analysis')), strip_tags(get_string('qtypetitle','quiz_analysis')),
650 strip_tags(get_string('qnametitle','quiz_analysis')), strip_tags(get_string('qtexttitle','quiz_analysis')),
651 strip_tags(get_string('responsestitle','quiz_analysis')), strip_tags(get_string('rfractiontitle','quiz_analysis')),
652 strip_tags(get_string('rcounttitle','quiz_analysis')), strip_tags(get_string('rpercenttitle','quiz_analysis')),
653 strip_tags(get_string('qcounttitle','quiz_analysis')),
654 strip_tags(get_string('facilitytitle','quiz_analysis')), strip_tags(get_string('stddevtitle','quiz_analysis')),
655 strip_tags(get_string('dicsindextitle','quiz_analysis')), strip_tags(get_string('disccoefftitle','quiz_analysis')));
43f14820 656
77c7f0f5 657 $col = 0;
658 foreach ($headers as $item) {
659 $myxls->write(2,$col,$item,$formatbc);
660 $col++;
43f14820 661 }
dd46d88e 662
77c7f0f5 663 $row = 3;
dd46d88e 664 foreach($questions as $q) {
77c7f0f5 665 $rows = $this->print_row_stats_data($q);
666 foreach($rows as $rowdata){
667 $col = 0;
668 foreach($rowdata as $item){
669 $myxls->write($row,$col,$item,$format);
670 $col++;
671 }
672 $row++;
43f14820 673 }
674 }
77c7f0f5 675 /// Close the workbook
676 $workbook->close();
677
d8247a98 678 exit;
43f14820 679 }
680
681 function Export_CSV(&$questions, $filename) {
682
b9568564 683 $headers = array(strip_tags(get_string('qidtitle','quiz_analysis')), strip_tags(get_string('qtypetitle','quiz_analysis')),
684 strip_tags(get_string('qnametitle','quiz_analysis')), strip_tags(get_string('qtexttitle','quiz_analysis')),
685 strip_tags(get_string('responsestitle','quiz_analysis')), strip_tags(get_string('rfractiontitle','quiz_analysis')),
686 strip_tags(get_string('rcounttitle','quiz_analysis')), strip_tags(get_string('rpercenttitle','quiz_analysis')),
687 strip_tags(get_string('qcounttitle','quiz_analysis')),
688 strip_tags(get_string('facilitytitle','quiz_analysis')), strip_tags(get_string('stddevtitle','quiz_analysis')),
689 strip_tags(get_string('dicsindextitle','quiz_analysis')), strip_tags(get_string('disccoefftitle','quiz_analysis')));
1ddb7f4e 690
691 $text = implode("\t", $headers)." \n";
dd46d88e 692
a472ce59 693 $filename .= ".txt";
1ddb7f4e 694
dd46d88e 695 header("Content-Type: application/download\n");
1ddb7f4e 696 header("Content-Disposition: attachment; filename=\"$filename\"");
697 header("Expires: 0");
698 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
699 header("Pragma: public");
700
701 echo $text;
43f14820 702
dd46d88e 703 foreach($questions as $q) {
b9568564 704 $rows = $this->print_row_stats_data_CSV($q);
43f14820 705 foreach($rows as $row){
706 $text = implode("\t", $row);
1ddb7f4e 707 echo $text." \n";
43f14820 708 }
709 }
1ddb7f4e 710 exit;
43f14820 711 }
b9568564 712 function print_row_stats_data_CSV(&$q) {
713 $qid = $q['id'];
714 $question = get_record('question', 'id', $qid);
715
716 $options = new stdClass;
717 $options->para = false;
718 $options->noclean = true;
719 $options->newlines = false;
720
721 $qtype = $question->qtype;
722
723 $qname = format_text($question->name, FORMAT_MOODLE, $options);
724 $trans = str_replace('\t',' ',$question->questiontext);
725 $trans = substr($trans,0,390);
726 $order = array("\r\n", "\n", "\r");
727 $trans = str_replace($order,' ',$trans);
728 // $trans = explode(',',$trans);
729
730 // $qtext = format_text(implode('__',$trans), FORMAT_MOODLE, $options);
731
732 $qtext = format_text($trans, FORMAT_MOODLE, $options);
733
734 $responses = array();
735 foreach ($q['responses'] as $aid=>$resp){
736 $response = new stdClass;
737 if ($q['credits'][$aid] <= 0) {
738 $qclass = 'uncorrect';
739 } elseif ($q['credits'][$aid] == 1) {
740 $qclass = 'correct';
741 } else {
742 $qclass = 'partialcorrect';
743 }
744 $response->credit = " (".format_float($q['credits'][$aid],2).") ";
745 $response->text = format_text("$resp", FORMAT_MOODLE, $options);
746 $count = $q['rcounts'][$aid].'/'.$q['count'];
747 $response->rcount = $count;
748 $response->rpercent = '('.format_float($q['rcounts'][$aid]/$q['count']*100,0).'%)';
749 $responses[] = $response;
750 }
751 $count = format_float($q['count'],0);
752 $facility = format_float($q['facility']*100,0);
753 $qsd = format_float($q['qsd'],4);
754 $di = format_float($q['disc_index'],3);
755 $dc = format_float($q['disc_coeff'],3);
756
757 $result = array();
758 $response = array_shift($responses);
759 $result[] = array($qid, $qtype, $qname, $qtext, $response->text, $response->credit, $response->rcount, $response->rpercent, $count, $facility, $qsd, $di, $dc);
760 foreach($responses as $response){
761 $result[] = array('', '', '', '', $response->text, $response->credit, $response->rcount, $response->rpercent, '', '', '', '', '');
762 }
763 return $result;
764 }
765
093c15be 766 function Export_HTML(&$questions, $filename) {
92f48269 767 $headers = array();
b9568564 768 $headers[] = '<font color="blue"><strong>'.get_string('qidtitle','quiz_analysis').'</strong></font>';
769 $headers[] = '<font color="blue"><strong>'.get_string('qtypetitle','quiz_analysis').'</strong></font>';
770 $headers[] = '<font color="green"><strong>'.get_string('qnametitle','quiz_analysis').':</strong></font>'.'<br />'.'<font color="blue"><strong>'.get_string('qtexttitle','quiz_analysis').'</strong></font>';
771 $headers[] = '<font color="blue"><strong>'.get_string('responsestitle','quiz_analysis').'</strong></font>';
772 $headers[] = '<font color="blue"><strong>'.get_string('rfractiontitle','quiz_analysis').'</strong></font>';
773 $headers[] = '<font color="blue"><strong>'.get_string('rcounttitle','quiz_analysis').' / '.get_string('qcounttitle','quiz_analysis').'</strong></font>';
774 $headers[] = '<font color="blue"><strong>'.get_string('rpercenttitle','quiz_analysis').'</strong></font>';
775 $headers[] = '<font color="blue"><strong>'.get_string('facilitytitle','quiz_analysis').'</strong></font>';
776 $headers[] = '<font color="blue"><strong>'.get_string('stddevtitle','quiz_analysis').'</strong></font>';
777 $headers[] = '<font color="blue"><strong>'.get_string('dicsindextitle','quiz_analysis').'</strong></font>';
778 $headers[] = '<font color="blue"><strong>'.get_string('disccoefftitle','quiz_analysis').'</strong></font>';
92f48269 779 $text = implode("</td><td>", $headers)." \n";
780
781 $filename .= ".html";
782 header("Content-Type: application/download\n");
783 header("Content-Disposition: attachment; filename=\"$filename\"");
784 header("Expires: 0");
785 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
786 header("Pragma: public");
787 print_header();
b9568564 788 echo '<table border = "1" bordercolor="#808080" cellspacing = "0" >';
92f48269 789 echo '<tr><td>'.$text.'</td></tr>';
790 foreach($questions as $q) {
791 $rows = $this->print_row_stats_data_html($q);
792 foreach($rows as $row){
793 $text = implode("</td><td>", $row);
794 echo '<tr><td>'.$text.'</td></tr>';
795 //echo $text." \n";
796 }
797 }
798 echo '</table></div>';
799 echo '</div></body></html>';
800 exit;
801 }
802 function print_row_stats_data_html(&$q) {
803 global $QTYPE_MENU ;
804 $format_options = new stdClass;
805 $format_options->para = false;
806 $format_options->noclean = true;
807 $format_options->newlines = false;
808
809 $qid = $q['id'];
810 $question = get_record('question', 'id', $qid);
811 $qnumber = "".$qid."";
b9568564 812 $qname = '<font color="green">'.format_text($question->name, $question->questiontextformat, $format_options).':</font><br />';
92f48269 813 $qicon = '';//print_question_icon($question, true);
814 $qreview = ''; //quiz_question_preview_button($quiz, $question);
815 $qtext = format_text($question->questiontext, $question->questiontextformat, $format_options);
b9568564 816 $qquestion = '<table><tr><td>'.$qname."\n".$qtext."\n".'</td></tr></table>';
92f48269 817 $qtype = $QTYPE_MENU[$question->qtype];
818
819 $responses = array();
820 foreach ($q['responses'] as $aid=>$resp){
821 $response = new stdClass;
822 if ($q['credits'][$aid] <= 0) {
b9568564 823 $qclass = 'red';//'uncorrect';
92f48269 824 } elseif ($q['credits'][$aid] == 1) {
b9568564 825 $qclass = 'blue';//'correct';
92f48269 826 } else {
827 $qclass = 'green';//'partialcorrect';
828 }
b9568564 829 $response->credit = '<font color="'.$qclass.'">('.format_float($q['credits'][$aid],2).')</font>';
830 if ($response->text ='') {
831 $response->text ='&nbsp;';
832 }
833 $response->text = '<font color="'.$qclass.'">'.format_text($resp, FORMAT_MOODLE, $format_options).'</font>';
92f48269 834 $count = '&nbsp;'.$q['rcounts'][$aid].'&nbsp;/&nbsp; '.$q['count'];
835 $response->rcount = $count;
836 $response->rpercent = $q['rpercent'][$aid];
837 $responses[] = $response;
838 }
43f14820 839
92f48269 840 $facility = format_float($q['facility']*100,0)."%";
841 $qsd = format_float($q['qsd'],3);
842 $di = format_float($q['disc_index'],2);
843 $dc = format_float($q['disc_coeff'],2);
844
845 $response = array_shift($responses);
846 $result[] =(array($qnumber,$qtype, $qquestion, $response->text, $response->credit, $response->rcount, $response->rpercent, $facility, $qsd, $di, $dc));
847 foreach($responses as $response) {
b9568564 848 $result[]=(array('&nbsp;','&nbsp;','&nbsp;',$response->text, $response->credit, $response->rcount, $response->rpercent, '&nbsp; ', '&nbsp; ', '&nbsp; ', '&nbsp; '));
92f48269 849 }
850 return $result;
851 }
b9568564 852
43f14820 853 function print_row_stats_data(&$q) {
854 $qid = $q['id'];
4f48fb42 855 $question = get_record('question', 'id', $qid);
43f14820 856
dd46d88e 857 $options = new stdClass;
43f14820 858 $options->para = false;
4fa68447 859 $options->noclean = true;
43f14820 860 $options->newlines = false;
861
862 $qtype = $question->qtype;
863
864 $qname = format_text($question->name, FORMAT_MOODLE, $options);
dd46d88e 865 $qtext = format_text($question->questiontext, FORMAT_MOODLE, $options);
866
867 $responses = array();
43f14820 868 foreach ($q['responses'] as $aid=>$resp){
dd46d88e 869 $response = new stdClass;
43f14820 870 if ($q['credits'][$aid] <= 0) {
871 $qclass = 'uncorrect';
872 } elseif ($q['credits'][$aid] == 1) {
873 $qclass = 'correct';
874 } else {
875 $qclass = 'partialcorrect';
876 }
dd46d88e 877 $response->credit = " (".format_float($q['credits'][$aid],2).") ";
43f14820 878 $response->text = format_text("$resp", FORMAT_MOODLE, $options);
879 $count = $q['rcounts'][$aid].'/'.$q['count'];
dd46d88e 880 $response->rcount = $count;
43f14820 881 $response->rpercent = '('.format_float($q['rcounts'][$aid]/$q['count']*100,0).'%)';
882 $responses[] = $response;
883 }
884 $count = format_float($q['count'],0);
885 $facility = format_float($q['facility']*100,0);
886 $qsd = format_float($q['qsd'],4);
887 $di = format_float($q['disc_index'],3);
888 $dc = format_float($q['disc_coeff'],3);
dd46d88e 889
890 $result = array();
891 $response = array_shift($responses);
892 $result[] = array($qid, $qtype, $qname, $qtext, $response->text, $response->credit, $response->rcount, $response->rpercent, $count, $facility, $qsd, $di, $dc);
43f14820 893 foreach($responses as $response){
dd46d88e 894 $result[] = array('', '', '', '', $response->text, $response->credit, $response->rcount, $response->rpercent, '', '', '', '', '');
43f14820 895 }
896 return $result;
897 }
dd46d88e 898}
a472ce59 899
083fc55c 900define('QUIZ_ALLATTEMPTS', 0);
901define('QUIZ_HIGHESTATTEMPT', 1);
902define('QUIZ_FIRSTATTEMPT', 2);
903define('QUIZ_LASTATTEMPT', 3);
904
376b8cd2 905function stats_sumx($data, $initsum){
906 $accum = $initsum;
907 foreach ($data as $v) {
908 $accum[0] += $v[0];
909 $accum[1] += $v[1];
910 }
911 return $accum;
dd46d88e 912}
a472ce59 913
376b8cd2 914function stats_sumx2($data, $initsum){
915 $accum = $initsum;
916 foreach ($data as $v) {
917 $accum[0] += $v[0]*$v[0];
918 $accum[1] += $v[1]*$v[1];
919 }
920 return $accum;
dd46d88e 921}
a472ce59 922
376b8cd2 923function stats_sumxy($data, $initsum){
924 $accum = $initsum;
925 foreach ($data as $v) {
926 $accum[0] += $v[0]*$v[1];
927 $accum[1] += $v[1]*$v[0];
928 }
929 return $accum;
0fc59399 930}
a472ce59 931
ad7e7ba8 932?>