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_clickreport_table($hotpot, $cm, $course, $users, $attempts, $questions, $options, $tables);
10 $this->print_report($course, $hotpot, $tables, $options);
13 function create_clickreport_table(&$hotpot, &$cm, &$course, &$users, &$attempts, &$questions, &$options, &$tables) {
15 $is_html = ($options['reportformat']=='htm');
16 // time and date format strings // date format strings
17 $strftimetime = '%H:%M:%S';
18 $strftimedate = get_string('strftimedate');
19 // get the current time and max execution time
20 $start_report_time = microtime();
21 $max_execution_time = ini_get('max_execution_time');
22 $correct = get_string('reportcorrectsymbol', 'hotpot');
23 $wrong = get_string('reportwrongsymbol', 'hotpot');
24 $nottried = get_string('reportnottriedsymbol', 'hotpot');
25 // shortcuts for font tags
26 $blank = $is_html ?
' ' : "";
27 // store question count
28 $questioncount = count($questions);
29 // array to map columns onto question ids ($col => $id)
30 $questionids = array_keys($questions);
31 // store exercise type
32 $exercisetype = $this->get_exercisetype($questions, $questionids, $blank);
33 // initialize details ('events' must go last)
34 $details = array('checks', 'status', 'answers', 'changes', 'hints', 'clues', 'events');
38 $table->width
= '100%';
39 // initialize legend, if necessary
40 if (!empty($options['reportshowlegend'])) {
41 $table->legend
= array();
43 // start $table headings
44 $this->set_head($options, $table, 'exercise');
45 $this->set_head($options, $table, 'user');
46 $this->set_head($options, $table, 'attempt');
47 $this->set_head($options, $table, 'click');
48 // store clicktype column number
49 $clicktype_col = count($table->head
)-1;
50 // finish $table headings
51 $this->set_head($options, $table, 'details', $exercisetype, $details, $questioncount);
52 $this->set_head($options, $table, 'totals', $exercisetype);
54 $this->set_align_and_wrap($table);
55 // is link to review allowed?
56 $allow_review = ($is_html && (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE
, $course->id
)) ||
$hotpot->review
));
57 // initialize array of data values
58 $this->data
= array();
59 // set exercise data values
60 $this->set_data_exercise($cm, $course, $hotpot, $questions, $questionids, $questioncount, $blank);
61 // add details of users' responses
62 foreach ($users as $user) {
63 $this->set_data_user($options, $course, $user);
64 unset($clickreportid);
65 foreach ($user->attempts
as $attempt) {
66 // initialize totals for
78 'weighting' => array()
80 $clicktypes = array();
81 // is the start of a new attempt?
82 // (clicks in the same attempt have the same clickreportid)
83 if (!isset($clickreportid) ||
$clickreportid != $attempt->clickreportid
) {
85 $clickreportid = $attempt->clickreportid
;
86 // initialize totals for all clicks in this attempt
87 $clicks = $click; // $click has just been initialized
88 $this->set_data_attempt($attempt, $strftimedate, $strftimetime, $blank);
91 $this->set_data($cells, 'exercise');
92 $this->set_data($cells, 'user');
93 $this->set_data($cells, 'attempt');
94 // get responses to questions in this attempt
95 foreach ($attempt->responses
as $response) {
96 // set $q(uestion number)
97 $q = array_search($response->question
, $questionids);
98 $click['qnumber'][$q] = true;
99 // was this question answered correctly?
100 if ($answer = hotpot_strings($response->correct
)) {
101 // mark the question as correctly answered
102 if (empty($clicks['correct'][$q])) {
103 $click['correct'][$q] = true;
104 $clicks['correct'][$q] = true;
106 // unset 'wrong' flags, if necessary
107 if (isset($click['wrong'][$q])) {
108 unset($click['wrong'][$q]);
110 if (isset($clicks['wrong'][$q])) {
111 unset($clicks['wrong'][$q]);
113 // otherwise, was the question answered wrongly?
114 } else if ($answer = hotpot_strings($response->wrong
)) {
115 // mark the question as wrongly answered
116 $click['wrong'][$q] = true;
117 $clicks['wrong'][$q] = true;
118 } else { // not correct or wrong (curious?!)
121 if (!empty($click['correct'][$q]) ||
!empty($click['wrong'][$q])) {
122 $click['score'][$q] = $response->score
;
123 $clicks['score'][$q] = $response->score
;
124 $weighting = isset($response->weighting
) ?
$response->weighting
: 100;
125 $click['weighting'][$q] = $weighting;
126 $clicks['weighting'][$q] =$weighting;
128 foreach($details as $detail) {
131 if (isset($answer) && is_string($answer) && !empty($answer)) {
132 $click[$detail][$q] = $answer;
138 if (isset($response->$detail) && is_numeric($response->$detail) && $response->$detail>0) {
139 if (!isset($click[$detail][$q]) ||
$click[$detail][$q] < $response->$detail) {
140 $click[$detail][$q] = $response->$detail;
145 } // end foreach $detail
146 } // end foreach $response
147 $click['types'] = array();
148 $this->data
['details'] = array();
149 foreach($details as $detail) {
150 for ($q=0; $q<$questioncount; $q++
) {
153 if (isset($clicks['correct'][$q])) {
154 $this->data
['details'][] = $correct;
155 } else if (isset($clicks['wrong'][$q])) {
156 $this->data
['details'][] = $wrong;
157 } else if (isset($click['qnumber'][$q])) {
158 $this->data
['details'][] = $nottried;
159 } else { // this question did not appear in this attempt
160 $this->data
['details'][] = $blank;
167 if (!isset($clicks[$detail][$q])) {
168 if (!isset($click[$detail][$q])) {
169 $this->data
['details'][] = $blank;
171 $clicks[$detail][$q] = $click[$detail][$q];
172 if ($detail=='answers') {
173 $this->set_legend($table, $q, $click[$detail][$q], $questions[$questionids[$q]]);
175 $this->data
['details'][] = $click[$detail][$q];
176 $this->update_event_count($click, $detail, $q);
179 if (!isset($click[$detail][$q])) {
180 $this->data
['details'][] = $blank;
183 if ($detail=='answers') {
184 if ($click[$detail][$q] != $clicks[$detail][$q]) {
185 $pattern = '/^'.preg_quote($clicks[$detail][$q], '/').',/';
186 $difference = preg_replace($pattern, '', $click[$detail][$q], 1);
188 } else { // hints, clues, checks
189 if ($click[$detail][$q] > $clicks[$detail][$q]) {
190 $difference = $click[$detail][$q] - $clicks[$detail][$q];
194 $clicks[$detail][$q] = $click[$detail][$q];
195 $click[$detail][$q] = $difference;
196 if ($detail=='answers') {
197 $this->set_legend($table, $q, $difference, $questions[$questionids[$q]]);
199 $this->data
['details'][] = $difference;
200 $this->update_event_count($click, $detail, $q);
202 unset($click[$detail][$q]);
203 $this->data
['details'][] = $blank;
210 if (empty($click[$detail][$q])) {
211 $this->data
['details'][] = $blank;
213 $this->data
['details'][] = $click[$detail][$q];
222 // set data cell values for
223 $this->set_data_click(
224 $allow_review ?
'<a href="review.php?hp='.$hotpot->id
.'&attempt='.$attempt->id
.'">'.$clickcount.'</a>' : $clickcount,
225 trim(userdate($attempt->timefinish
, $strftimetime)),
229 $this->set_data($cells, 'click');
230 $this->set_data($cells, 'details');
231 $this->set_data_totals($click, $clicks, $questioncount, $blank, $attempt);
232 $this->set_data($cells, 'totals');
233 $table->data
[] = $cells;
235 } // end foreach $attempt
236 // insert 'tabledivider' between users
237 $table->data
[] = 'hr';
238 } // end foreach $user
239 // remove final 'hr' from data rows
240 array_pop($table->data
);
241 if ($is_html && $CFG->hotpot_showtimes
) {
242 $count = count($users);
243 $duration = sprintf("%0.3f", microtime_diff($start_report_time, microtime()));
244 print "$count users processed in $duration seconds (".sprintf("%0.3f", $duration/$count).' secs/user)<hr size="1" noshade="noshade" />'."\n";
247 $this->create_legend_table($tables, $table);
249 function get_exercisetype(&$questions, &$questionids, &$blank) {
250 if (empty($questions)) {
253 switch ($questions[$questionids[0]]->type
) {
272 case HOTPOT_TEXTOYS_RHUBARB
:
275 case HOTPOT_TEXTOYS_SEQUITUR
:
284 function set_head(&$options, &$table, $zone, $exercisetype='', $details=array(), $questioncount=0) {
285 if (empty($table->head
)) {
286 $table->head
= array();
290 array_push($table->head
,
291 get_string('reportcoursename', 'hotpot'),
292 get_string('reportsectionnumber', 'hotpot'),
293 get_string('reportexercisenumber', 'hotpot'),
294 get_string('reportexercisename', 'hotpot'),
295 get_string('reportexercisetype', 'hotpot'),
296 get_string('reportnumberofquestions', 'hotpot')
300 array_push($table->head
,
301 get_string('reportstudentid', 'hotpot'),
302 get_string('reportlogindate', 'hotpot'),
303 get_string('reportlogintime', 'hotpot'),
304 get_string('reportlogofftime', 'hotpot')
308 array_push($table->head
,
309 get_string('reportattemptnumber', 'hotpot'),
310 get_string('reportattemptstart', 'hotpot'),
311 get_string('reportattemptfinish', 'hotpot')
315 array_push($table->head
,
316 get_string('reportclicknumber', 'hotpot'),
317 get_string('reportclicktime', 'hotpot'),
318 get_string('reportclicktype', 'hotpot')
322 foreach($details as $detail) {
323 if ($exercisetype=='JQuiz' && $detail=='clues') {
324 $detail = 'showanswer';
326 $detail = get_string("report$detail", 'hotpot');
327 for ($i=0; $i<$questioncount; $i++
) {
328 $str = get_string('questionshort', 'hotpot', $i+
1);
329 if ($i==0 ||
$options['reportformat']!='htm') {
330 $str = "$detail $str";
332 $table->head
[] = $str;
337 $reportpercentscore =get_string('reportpercentscore', 'hotpot');
338 if (!function_exists('clean_getstring_data')) { // Moodle 1.4 (and less)
339 $reportpercentscore = str_replace('%', '%%', $reportpercentscore);
341 array_push($table->head
,
342 get_string('reportthisclick', 'hotpot', get_string('reportquestionstried', 'hotpot')),
343 get_string('reportsofar', 'hotpot', get_string('reportquestionstried', 'hotpot')),
344 get_string('reportthisclick', 'hotpot', get_string('reportright', 'hotpot')),
345 get_string('reportthisclick', 'hotpot', get_string('reportwrong', 'hotpot')),
346 get_string('reportthisclick', 'hotpot', get_string('reportnottried', 'hotpot')),
347 get_string('reportsofar', 'hotpot', get_string('reportright', 'hotpot')),
348 get_string('reportsofar', 'hotpot', get_string('reportwrong', 'hotpot')),
349 get_string('reportsofar', 'hotpot', get_string('reportnottried', 'hotpot')),
350 get_string('reportthisclick', 'hotpot', get_string('reportanswers', 'hotpot')),
351 get_string('reportthisclick', 'hotpot', get_string('reporthints', 'hotpot')),
352 get_string('reportthisclick', 'hotpot', get_string($exercisetype=='JQuiz' ?
'reportshowanswer' : 'reportclues', 'hotpot')),
353 get_string('reportthisclick', 'hotpot', get_string('reportevents', 'hotpot')),
354 get_string('reportsofar', 'hotpot', get_string('reporthints', 'hotpot')),
355 get_string('reportsofar', 'hotpot', get_string($exercisetype=='JQuiz' ?
'reportshowanswer' : 'reportclues', 'hotpot')),
356 get_string('reportthisclick', 'hotpot', get_string('reportrawscore', 'hotpot')),
357 get_string('reportthisclick', 'hotpot', get_string('reportmaxscore', 'hotpot')),
358 get_string('reportthisclick', 'hotpot', $reportpercentscore),
359 get_string('reportsofar', 'hotpot', get_string('reportrawscore', 'hotpot')),
360 get_string('reportsofar', 'hotpot', get_string('reportmaxscore', 'hotpot')),
361 get_string('reportsofar', 'hotpot', $reportpercentscore),
362 get_string('reporthotpotscore', 'hotpot')
367 function set_align_and_wrap(&$table) {
368 $count = count($table->head
);
369 for ($i=0; $i<$count; $i++
) {
370 if ($i==0 ||
$i==1 ||
$i==2 ||
$i==4 ||
$i==5 ||
$i>=7) {
371 // numeric (and short text) columns
372 $table->align
[] = 'center';
376 $table->align
[] = 'left';
377 $table->wrap
[] = 'nowrap';
381 function set_data_exercise(&$cm, &$course, &$hotpot, &$questions, &$questionids, &$questioncount, &$blank) {
382 // get exercise details (course name, section number, activity number, quiztype and question count)
383 $record = get_record("course_sections", "id", $cm->section
);
384 $this->data
['exercise'] = array(
385 'course' => $course->shortname
,
386 'section' => empty($record) ?
$blank : $record->section+
1,
387 'number' => empty($record) ?
$blank : array_search($cm->id
, explode(',', $record->sequence
))+
1,
388 'name' => $hotpot->name
,
389 'type' => $this->get_exercisetype($questions, $questionids, $blank),
390 'questioncount' => $questioncount
393 function set_data_user(&$options, &$course, &$user) {
395 // shortcut to first attempt record (which also hold user info)
396 $attempt = &$user->attempts
[0];
397 $idnumber = $attempt->idnumber
;
398 if (empty($idnumber)) {
399 $idnumber = fullname($attempt);
401 if ($options['reportformat']=='htm') {
402 $idnumber = '<a href="'.$CFG->wwwroot
.'/user/view.php?id='.$attempt->userid
.'&course='.$course->id
.'">'.$idnumber.'</a>';
404 $this->data
['user'] = array(
405 'idnumber' => $idnumber,
408 function set_data_attempt(&$attempt, &$strftimedate, &$strftimetime, &$blank) {
410 $records = get_records_sql_menu("
411 SELECT userid, MAX(time) AS logintime
412 FROM {$CFG->prefix}log
413 WHERE userid=$attempt->userid AND action='login' AND time<$attempt->timestart
416 if (empty($records)) {
420 $logintime = $records[$attempt->userid
];
421 $logindate = trim(userdate($logintime, $strftimedate));
422 $logintime = trim(userdate($logintime, $strftimetime));
424 $records = get_records_sql_menu("
425 SELECT userid, MIN(time) AS logouttime
426 FROM {$CFG->prefix}log
427 WHERE userid=$attempt->userid AND action='logout' AND time>$attempt->cr_timefinish
430 if (empty($records)) {
431 $logouttime = $blank;
433 $logouttime = $records[$attempt->userid
];
434 $logouttime = trim(userdate($logouttime, $strftimetime));
436 $this->data
['attempt'] = array(
437 'logindate' => $logindate,
438 'logintime' => $logintime,
439 'logouttime' => $logouttime,
440 'number' => $attempt->attempt
,
441 'start' => trim(userdate($attempt->timestart
, $strftimetime)),
442 'finish' => trim(userdate($attempt->cr_timefinish
, $strftimetime)),
445 function set_data_click($number, $time, $exercisetype, $click) {
447 foreach (array_keys($click['types']) as $type) {
448 if ($exercisetype=='JQuiz' && $type=='clues') {
449 $type = 'showanswer';
452 $type = substr($type, 0, strlen($type)-1);
454 // $types[] = get_string($type, 'hotpot');
457 $this->data
['click'] = array(
460 'type' => empty($types) ?
'??' : implode(',', $types)
463 function set_data_totals(&$click, &$clicks, &$questioncount, &$blank, &$attempt) {
466 'correct' => count($click['correct']),
467 'wrong' => count($click['wrong']),
468 'answers' => count($click['answers']),
469 'hints' => array_sum($click['hints']),
470 'clues' => array_sum($click['clues']),
471 'events' => array_sum($click['events']),
472 'score' => array_sum($click['score']),
473 'maxscore' => array_sum($click['weighting']),
476 'correct' => count($clicks['correct']),
477 'wrong' => count($clicks['wrong']),
478 'answers' => count($clicks['answers']),
479 'hints' => array_sum($clicks['hints']),
480 'clues' => array_sum($clicks['clues']),
481 'score' => array_sum($clicks['score']),
482 'maxscore' => array_sum($clicks['weighting']),
485 foreach ($count as $period=>$values) {
486 $count[$period]['nottried'] = $questioncount - ($values['correct'] +
$values['wrong']);
487 $count[$period]['percent'] = empty($values['maxscore']) ?
$blank : round(100 * $values['score'] / $values['maxscore'], 0);
488 // blank out zero click values
489 if ($period=='click') {
490 foreach ($values as $detail=>$value) {
491 if ($detail=='answers' ||
$detail=='hints' ||
$detail=='clues' ||
$detail=='events') {
493 $count[$period][$detail] = $blank;
499 $this->data
['totals'] = array(
500 $count['click']['answers'], // "q's tried"
501 $count['clicks']['answers'], // "q's tried so far"
502 $count['click']['correct'], // "right"
503 $count['click']['wrong'], // "wrong"
504 $count['click']['nottried'], // "not tried"
505 $count['clicks']['correct'], // "right so far"
506 $count['clicks']['wrong'], // "wrong so far"
507 $count['clicks']['nottried'], // "not tried so far"
508 $count['click']['answers'], // "answers",
509 $count['click']['hints'], // "hints",
510 $count['click']['clues'], // "clues",
511 $count['click']['events'], // "answers",
512 $count['clicks']['hints'], // "hints so far",
513 $count['clicks']['clues'], // "clues so far",
514 $count['click']['score'], // 'raw score',
515 $count['click']['maxscore'], // 'max score',
516 $count['click']['percent'], // '% score'
517 $count['clicks']['score'], // 'raw score,
518 $count['clicks']['maxscore'], // 'max score,
519 $count['clicks']['percent'], // '% score
520 $attempt->score
// 'hotpot score'
523 function update_event_count(&$click, $detail, $q) {
524 if ($detail=='checks' ||
$detail=='hints' ||
$detail=='clues') {
525 $click['types'][$detail] = true;
527 if ($detail=='answers' ||
$detail=='hints' ||
$detail=='clues') {
528 $click['events'][$q] = isset($click['events'][$q]) ?
$click['events'][$q]+
1 : 1;
530 if ($detail=='answers') {
531 $click['changes'][$q] = isset($click['changes'][$q]) ?
$click['changes'][$q]+
1 : 1;
534 function set_data(&$cells, $zone) {
535 foreach ($this->data
[$zone] as $name=>$value) {