2 /// Overview report just displays a big table of all the attempts
3 class hotpot_report
extends hotpot_default_report
{
4 function display(&$hotpot, &$cm, &$course, &$users, &$attempts, &$questions, &$options) {
8 $this->create_responses_table($hotpot, $course, $users, $attempts, $questions, $options, $tables);
9 $this->create_analysis_table($users, $attempts, $questions, $options, $tables);
11 $this->print_report($course, $hotpot, $tables, $options);
14 function create_responses_table(&$hotpot, &$course, &$users, &$attempts, &$questions, &$options, &$tables) {
16 $is_html = ($options['reportformat']=='htm');
17 // shortcuts for font tags
18 $br = $is_html ?
"<br />\n" : "\n";
19 $blank = $is_html ?
' ' : "";
20 $font_end = $is_html ?
'</font>' : '';
21 $font_red = $is_html ?
'<font color="red">' : '';
22 $font_blue = $is_html ?
'<font color="blue">' : '';
23 $font_brown = $is_html ?
'<font color="brown">' : '';
24 $font_green = $is_html ?
'<font color="green">' : '';
25 $font_small = $is_html ?
'<font size="-2">' : '';
26 $nobr_start = $is_html ?
'<nobr>' : '';
27 $nobr_end = $is_html ?
'</nobr>' : '';
28 // is review allowed? (do this once here, to save time later)
29 $allow_review = ($is_html && (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE
, $course->id
)) ||
$hotpot->review
));
30 // assume penalties column is NOT required
31 $show_penalties = false;
35 $table->width
= '100%';
36 // initialize legend, if necessary
37 if (!empty($options['reportshowlegend'])) {
38 $table->legend
= array();
40 // headings for name, attempt number, score/grade and penalties
43 hotpot_grade_heading($hotpot, $options),
44 get_string('attempt', 'quiz'),
46 $table->align
= array('left', 'center', 'center');
47 $table->size
= array(150, 80, 10);
48 $table->wrap
= array(0, 0, 0);
49 $table->fontsize
= array(0, 0, 0);
51 $this->add_question_headings($questions, $table, 'left', 0, false, 2);
52 // penalties (not always needed) and raw score
53 array_push($table->head
,
54 get_string('penalties', 'hotpot'),
55 get_string('score', 'quiz')
57 array_push($table->align
, 'center', 'center');
58 array_push($table->size
, 50, 50);
59 array_push($table->wrap
, 0, 0);
60 array_push($table->fontsize
, 0, 0);
62 $strnoresponse = get_string('noresponse', 'quiz');
63 // array to map columns onto question ids ($col => $id)
64 $questionids = array_keys($questions);
65 // add details of users' responses
66 foreach ($users as $user) {
67 // shortcut to user info held in first attempt record
68 $u = &$user->attempts
[0];
69 if (function_exists("fullname")) {
72 $name = "$u->firstname $u->lastname";
75 $name = '<a href="'.$CFG->wwwroot
.'/user/view.php?id='.$u->userid
.'&course='.$course->id
.'">'.$name.'</a>';
77 $grade = isset($user->grade
) ?
$user->grade
: $blank;
78 foreach ($user->attempts
as $attempt) {
79 $attemptnumber = $attempt->attempt
;
81 $attemptnumber = ' <a href="review.php?hp='.$hotpot->id
.'&attempt='.$attempt->id
.'">'.$attemptnumber.'</a>';
83 $cells = array ($name, $grade, $attemptnumber);
84 // $name and $grade are only printed on first line per user
87 $start_col = count($cells);
88 foreach ($questionids as $col => $id) {
89 $cells[$start_col +
$col] = "$font_brown($strnoresponse)$font_end";
91 if (isset($attempt->penalties
)) {
92 $show_penalties = true;
93 $penalties = $attempt->penalties
;
97 array_push($cells, $penalties, hotpot_format_score($attempt));
98 // get responses to questions in this attempt
99 foreach ($attempt->responses
as $response) {
100 // check this question id is OK (should be)
101 $col = array_search($response->question
, $questionids);
102 if (is_numeric($col)) {
104 if ($value = hotpot_strings($response->correct
)) {
105 $this->set_legend($table, $col, $value, $questions[$response->question
]);
107 $value = "($strnoresponse)";
109 $cell = $font_red.$value.$font_end;
111 if ($value = hotpot_strings($response->wrong
)) {
112 if (isset($table->legend
)) {
114 foreach (explode(',', $value) as $v) {
115 $this->set_legend($table, $col, $v, $questions[$response->question
]);
118 $value = implode(',', $values);
120 $cell .= $br.$font_blue.$value.$font_end;
123 if ($value = hotpot_strings($response->ignored
)) {
124 if (isset($table->legend
)) {
126 foreach (explode(',', $value) as $v) {
127 $this->set_legend($table, $col, $v, $questions[$response->question
]);
130 $value = implode(',', $values);
132 $cell .= $br.$font_brown.$value.$font_end;
135 if (is_numeric($response->score
)) {
136 if (empty($table->caption
)) {
137 $table->caption
= get_string('indivresp', 'quiz');
139 $table->caption
.= helpbutton('responsestable', $table->caption
, 'hotpot', true, false, '', true);
142 $hints = empty($response->hints
) ?
0 : $response->hints
;
143 $clues = empty($response->clues
) ?
0 : $response->clues
;
144 $checks = empty($response->checks
) ?
0 : $response->checks
;
145 $numeric = $response->score
.'% '.$blank.' ('.$hints.','.$clues.','.$checks.')';
146 $cell .= $br.$nobr_start.$font_green.$numeric.$font_end.$nobr_end;
148 $cells[$start_col +
$col] = $cell;
151 $table->data
[] = $cells;
153 // insert 'tabledivider' between users
154 $table->data
[] = 'hr';
155 } // end foreach $users
156 // remove final 'hr' from data rows
157 array_pop($table->data
);
158 if (!$show_penalties) {
159 $col = 3 +
count($questionids);
160 $this->remove_column($table, $col);
164 function create_analysis_table(&$users, &$attempts, &$questions, &$options, &$tables) {
165 $is_html = ($options['reportformat']=='htm');
166 // the fields we are interested in, in the order we want them
167 $fields = array('correct', 'wrong', 'ignored', 'hints', 'clues', 'checks', 'weighting');
168 $string_fields = array('correct', 'wrong', 'ignored');
169 $q = array(); // statistics about the $q(uestions)
170 $f = array(); // statistics about the $f(ields)
171 ////////////////////////////////////////////
172 // compile the statistics about the questions
173 ////////////////////////////////////////////
174 foreach ($questions as $id=>$question) {
175 // extract scores for attempts at this question
177 foreach ($question->attempts
as $attempt) {
178 $scores[] = $attempt->score
;
180 // sort scores values (in ascending order)
182 // get the borderline high and low scores
183 $count = count($scores);
191 $hi_score = $scores[0];
194 $lo_score = $scores[round($count*1/3)];
195 $hi_score = $scores[round($count*2/3)];
198 // get statistics for each attempt which includes this question
199 foreach ($question->attempts
as $attempt) {
200 $is_hi_score = ($attempt->score
>= $hi_score);
201 $is_lo_score = ($attempt->score
< $lo_score);
202 // reference to the response to the current question
203 $response = &$attempt->responses
[$id];
204 // update statistics for fields in this response
205 foreach($fields as $field) {
206 if (!isset($q[$id])) {
209 if (!isset($f[$field])) {
210 $f[$field] = array('count' => 0);
212 if (!isset($q[$id][$field])) {
213 $q[$id][$field] = array('count' => 0);
215 $values = explode(',', $response->$field);
216 $values = array_unique($values);
217 foreach($values as $value) {
218 // $value should be an integer (string_id or count)
219 if (is_numeric($value)) {
220 $f[$field]['count']++
;
221 if (!isset($q[$id][$field][$value])) {
222 $q[$id][$field][$value] = 0;
224 $q[$id][$field]['count']++
;
225 $q[$id][$field][$value]++
;
228 } // end foreach $field
229 // initialize counters for this question, if necessary
230 if (!isset($q[$id]['count'])) {
231 $q[$id]['count'] = array('hi'=>0, 'lo'=>0, 'correct'=>0, 'total'=>0, 'sum'=>0);
233 // increment counters
234 $q[$id]['count']['sum'] +
= $response->score
;
235 $q[$id]['count']['total']++
;
236 if ($response->score
==100) {
237 $q[$id]['count']['correct']++
;
239 $q[$id]['count']['hi']++
;
240 } else if ($is_lo_score) {
241 $q[$id]['count']['lo']++
;
244 } // end foreach attempt
245 } // end foreach question
246 // check we have some details
248 $showhideid = 'showhide';
249 // shortcuts for html tags
250 $bold_start = $is_html ?
'<strong>' : "";
251 $bold_end = $is_html ?
'</strong>' : "";
252 $div_start = $is_html ?
'<div id="'.$showhideid.'">' : "";
253 $div_end = $is_html ?
'</div>' : "";
254 $font_red = $is_html ?
'<font color="red" size="-2">' : '';
255 $font_blue = $is_html ?
'<font color="blue" size="-2">' : '';
256 $font_green = $is_html ?
'<font color="green" size="-2">' : '';
257 $font_brown = $is_html ?
'<font color="brown" size="-2">' : '';
258 $font_end = $is_html ?
'</font>'."\n" : '';
259 $br = $is_html ?
'<br />' : "\n";
260 $space = $is_html ?
' ' : "";
261 $no_value = $is_html ?
'--' : "";
262 $help_button = $is_html ?
helpbutton("discrimination", "", "quiz", true, false, "", true) : "";
266 $table->width
= '100%';
267 $table->caption
= get_string('itemanal', 'quiz');
269 $table->caption
.= helpbutton('analysistable', $table->caption
, 'hotpot', true, false, '', true);
271 // initialize legend, if necessary
272 if (!empty($options['reportshowlegend'])) {
273 if (empty($tables) ||
empty($tables[0]->legend
)) {
274 $table->legend
= array();
276 $table->legend
= $tables[0]->legend
;
277 unset($tables[0]->legend
);
280 // headings for name, attempt number and score/grade
281 $table->head
= array($space);
282 $table->align
= array('right');
283 $table->size
= array(80);
285 $this->add_question_headings($questions, $table, 'left', 0);
286 // initialize statistics
287 $table->stat
= array();
288 $table->statheadercols
= array(0);
289 // add headings for the $foot of the $table
290 $table->foot
= array();
291 $table->foot
[0] = array(get_string('average', 'hotpot'));
292 $table->foot
[1] = array(get_string('percentcorrect', 'quiz'));
293 $table->foot
[2] = array(get_string('discrimination', 'quiz').$help_button);
294 // maximum discrimination index (also default the default value)
296 ////////////////////////////////////////////
297 // format the statistics into the $table
298 ////////////////////////////////////////////
299 // add $stat(istics) and $foot of $table
300 $questionids = array_keys($q);
301 foreach ($questionids as $col => $id) {
303 // print the question text if there is no legend
304 if (empty($table->legend
)) {
305 // add button to show/hide question text
306 if (!isset($table->stat
[0])) {
307 $button = $is_html ?
hotpot_showhide_button($showhideid) : "";
308 $table->stat
[0] = array(get_string('question', 'quiz').$button);
310 // add the question name/text
311 $name = hotpot_get_question_name($questions[$id]);
312 $table->stat
[$row++
][$col+
1] = $div_start.$bold_start.$name.$bold_end.$div_end.$space;
314 // add details about each field
315 foreach ($fields as $field) {
316 // check this row is required
317 if ($f[$field]['count']) {
319 $string_type = array_search($field, $string_fields);
320 // get the value of each response to this field
321 // and the count of that value
322 foreach ($q[$id][$field] as $value => $count) {
323 if (is_numeric($value) && $count) {
324 if (is_numeric($string_type)) {
325 $value = hotpot_string($value);
326 $this->set_legend($table, $col, $value, $questions[$id]);
327 switch ($string_type) {
329 $font_start = $font_red;
332 $font_start = $font_blue;
335 $font_start = $font_brown;
338 } else { // numeric field
339 $font_start = $font_green;
341 $values[] = $font_start.round(100*$count/$q[$id]['count']['total']).'%'.$font_end.' '.$value;
343 } // end foreach $value => $count
344 // initialize stat(istics) row for this field, if required
345 if (!isset($table->stat
[$row])) {
346 $table->stat
[$row] = array(get_string($field, 'hotpot'));
348 // sort the values by frequency (using user-defined function)
349 usort($values, "hotpot_sort_stat_values");
350 // add stat(istics) values for this field
351 $table->stat
[$row++
][$col+
1] = count($values) ?
implode($br, $values) : $space;
353 } // end foreach field
354 // default percent correct and discrimination index for this question
355 $average = $no_value;
356 $percent = $no_value;
357 $d_index = $no_value;
358 if (isset($q[$id]['count'])) {
359 // average and percent correct
360 if ($q[$id]['count']['total']) {
361 $average = round($q[$id]['count']['sum'] / $q[$id]['count']['total']).'%';
362 $percent = round(100*$q[$id]['count']['correct'] / $q[$id]['count']['total']).'%';
363 $percent .= ' ('.$q[$id]['count']['correct'].'/'.$q[$id]['count']['total'].')';
365 // discrimination index
366 if ($q[$id]['count']['lo']) {
367 $d_index = min($max_d_index, round($q[$id]['count']['hi'] / $q[$id]['count']['lo'], 1));
369 $d_index = $q[$id]['count']['hi'] ?
$max_d_index : 0;
371 $d_index .= ' ('.$q[$id]['count']['hi'].'/'.$q[$id]['count']['lo'].')';
373 $table->foot
[0][$col+
1] = $average;
374 $table->foot
[1][$col+
1] = $percent;
375 $table->foot
[2][$col+
1] = $d_index;
376 } // end foreach $question ($col)
377 // add javascript to show/hide question text
378 if (isset($table->stat
[0]) && $is_html && empty($table->legend
)) {
379 $i = count($table->stat
[0]);
380 $table->stat
[0][$i-1] .= hotpot_showhide_set($showhideid);
383 $this->create_legend_table($tables, $table);
384 } // end if (empty($q)
387 function hotpot_sort_stat_values($a, $b) {
388 // sorts in descending order
389 // assumes first chars in $a and $b are a percentage
390 $a_val = intval(strip_tags($a));
391 $b_val = intval(strip_tags($b));
392 return ($a_val<$b_val) ?
1 : ($a_val==$b_val ?
0 : -1);
394 function hotpot_showhide_button($id) {
395 $show = get_string('show');
396 $hide = get_string('hide');
398 $text = ($pref=='1' ?
$hide : $show);
399 return <<<SHOWHIDE_BUTTON
400 <script type="text/javascript">
402 function showhide (id, toggle) {
404 obj = document.getElementById(id+'pref');
406 show = (obj.value=='1');
409 obj.value = (show ? '1' : '0');
412 obj = document.getElementById(id+'button');
414 obj.value = (show ? '$hide' : '$show');
416 obj = document.getElementsByName(id);
417 var i_max = obj.length;
418 for (var i=0; i<i_max; i++) {
419 obj[i].style.display = (show ? 'block' : 'none');
422 var showhide_allowed = (document.getElementById && document.getElementsByName);
423 if (showhide_allowed) {
425 html += '<form onsubmit="return false">';
426 html += '<input type="button" value="$text" id="{$id}button" onClick="javascript: return showhide(\\'$id\\', true);" />';
427 html += '<input type="hidden" name="{$id}pref" id="{$id}pref" value="$pref" />';
429 document.writeln(html);
436 function hotpot_showhide_set($id) {
437 return <<<SHOWHIDE_SET
438 <script type="text/javascript">
440 if (showhide_allowed) {