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