create_clickreport_table($hotpot, $cm, $course, $users, $attempts, $questions, $options, $tables); // print the tables $this->print_report($course, $hotpot, $tables, $options); return true; } function create_clickreport_table(&$hotpot, &$cm, &$course, &$users, &$attempts, &$questions, &$options, &$tables) { global $CFG; $is_html = ($options['reportformat']=='htm'); // time and date format strings // date format strings $strftimetime = '%H:%M:%S'; $strftimedate = get_string('strftimedate'); // get the current time and max execution time $start_report_time = microtime(); $max_execution_time = ini_get('max_execution_time'); $correct = get_string('reportcorrectsymbol', 'hotpot'); $wrong = get_string('reportwrongsymbol', 'hotpot'); $nottried = get_string('reportnottriedsymbol', 'hotpot'); // shortcuts for font tags $blank = $is_html ? ' ' : ""; // store question count $questioncount = count($questions); // array to map columns onto question ids ($col => $id) $questionids = array_keys($questions); // store exercise type $exercisetype = $this->get_exercisetype($questions, $questionids, $blank); // initialize details ('events' must go last) $details = array('checks', 'status', 'answers', 'changes', 'hints', 'clues', 'events'); // initialize $table unset($table); $table->border = 1; $table->width = '100%'; // initialize legend, if necessary if (!empty($options['reportshowlegend'])) { $table->legend = array(); } // start $table headings $this->set_head($options, $table, 'exercise'); $this->set_head($options, $table, 'user'); $this->set_head($options, $table, 'attempt'); $this->set_head($options, $table, 'click'); // store clicktype column number $clicktype_col = count($table->head)-1; // finish $table headings $this->set_head($options, $table, 'details', $exercisetype, $details, $questioncount); $this->set_head($options, $table, 'totals', $exercisetype); // set align and wrap $this->set_align_and_wrap($table); // is link to review allowed? $allow_review = ($is_html && (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE, $course->id)) || $hotpot->review)); // initialize array of data values $this->data = array(); // set exercise data values $this->set_data_exercise($cm, $course, $hotpot, $questions, $questionids, $questioncount, $blank); // add details of users' responses foreach ($users as $user) { $this->set_data_user($options, $course, $user); unset($clickreportid); foreach ($user->attempts as $attempt) { // initialize totals for $click = array( 'qnumber' => array(), 'correct' => array(), 'wrong' => array(), 'answers' => array(), 'hints' => array(), 'clues' => array(), 'changes' => array(), 'checks' => array(), 'events' => array(), 'score' => array(), 'weighting' => array() ); $clicktypes = array(); // is the start of a new attempt? // (clicks in the same attempt have the same clickreportid) if (!isset($clickreportid) || $clickreportid != $attempt->clickreportid) { $clickcount = 1; $clickreportid = $attempt->clickreportid; // initialize totals for all clicks in this attempt $clicks = $click; // $click has just been initialized $this->set_data_attempt($attempt, $strftimedate, $strftimetime, $blank); } $cells = array(); $this->set_data($cells, 'exercise'); $this->set_data($cells, 'user'); $this->set_data($cells, 'attempt'); // get responses to questions in this attempt foreach ($attempt->responses as $response) { // set $q(uestion number) $q = array_search($response->question, $questionids); $click['qnumber'][$q] = true; // was this question answered correctly? if ($answer = hotpot_strings($response->correct)) { // mark the question as correctly answered if (empty($clicks['correct'][$q])) { $click['correct'][$q] = true; $clicks['correct'][$q] = true; } // unset 'wrong' flags, if necessary if (isset($click['wrong'][$q])) { unset($click['wrong'][$q]); } if (isset($clicks['wrong'][$q])) { unset($clicks['wrong'][$q]); } // otherwise, was the question answered wrongly? } else if ($answer = hotpot_strings($response->wrong)) { // mark the question as wrongly answered $click['wrong'][$q] = true; $clicks['wrong'][$q] = true; } else { // not correct or wrong (curious?!) unset($answer); } if (!empty($click['correct'][$q]) || !empty($click['wrong'][$q])) { $click['score'][$q] = $response->score; $clicks['score'][$q] = $response->score; $weighting = isset($response->weighting) ? $response->weighting : 100; $click['weighting'][$q] = $weighting; $clicks['weighting'][$q] =$weighting; } foreach($details as $detail) { switch ($detail) { case 'answers': if (isset($answer) && is_string($answer) && !empty($answer)) { $click[$detail][$q] = $answer; } break; case 'hints': case 'clues': case 'checks': if (isset($response->$detail) && is_numeric($response->$detail) && $response->$detail>0) { if (!isset($click[$detail][$q]) || $click[$detail][$q] < $response->$detail) { $click[$detail][$q] = $response->$detail; } } break; } } // end foreach $detail } // end foreach $response $click['types'] = array(); $this->data['details'] = array(); foreach($details as $detail) { for ($q=0; $q<$questioncount; $q++) { switch ($detail) { case 'status': if (isset($clicks['correct'][$q])) { $this->data['details'][] = $correct; } else if (isset($clicks['wrong'][$q])) { $this->data['details'][] = $wrong; } else if (isset($click['qnumber'][$q])) { $this->data['details'][] = $nottried; } else { // this question did not appear in this attempt $this->data['details'][] = $blank; } break; case 'answers': case 'hints': case 'clues': case 'checks': if (!isset($clicks[$detail][$q])) { if (!isset($click[$detail][$q])) { $this->data['details'][] = $blank; } else { $clicks[$detail][$q] = $click[$detail][$q]; if ($detail=='answers') { $this->set_legend($table, $q, $click[$detail][$q], $questions[$questionids[$q]]); } $this->data['details'][] = $click[$detail][$q]; $this->update_event_count($click, $detail, $q); } } else { if (!isset($click[$detail][$q])) { $this->data['details'][] = $blank; } else { $difference = ''; if ($detail=='answers') { if ($click[$detail][$q] != $clicks[$detail][$q]) { $pattern = '/^'.preg_quote($clicks[$detail][$q], '/').',/'; $difference = preg_replace($pattern, '', $click[$detail][$q], 1); } } else { // hints, clues, checks if ($click[$detail][$q] > $clicks[$detail][$q]) { $difference = $click[$detail][$q] - $clicks[$detail][$q]; } } if ($difference) { $clicks[$detail][$q] = $click[$detail][$q]; $click[$detail][$q] = $difference; if ($detail=='answers') { $this->set_legend($table, $q, $difference, $questions[$questionids[$q]]); } $this->data['details'][] = $difference; $this->update_event_count($click, $detail, $q); } else { unset($click[$detail][$q]); $this->data['details'][] = $blank; } } } break; case 'changes': case 'events': if (empty($click[$detail][$q])) { $this->data['details'][] = $blank; } else { $this->data['details'][] = $click[$detail][$q]; } break; default: // do nothing break; } // end switch } // for $q } // foreach $detail // set data cell values for $this->set_data_click( $allow_review ? ''.$clickcount.'' : $clickcount, trim(userdate($attempt->timefinish, $strftimetime)), $exercisetype, $click ); $this->set_data($cells, 'click'); $this->set_data($cells, 'details'); $this->set_data_totals($click, $clicks, $questioncount, $blank, $attempt); $this->set_data($cells, 'totals'); $table->data[] = $cells; $clickcount++; } // end foreach $attempt // insert 'tabledivider' between users $table->data[] = 'hr'; } // end foreach $user // remove final 'hr' from data rows array_pop($table->data); if ($is_html && $CFG->hotpot_showtimes) { $count = count($users); $duration = sprintf("%0.3f", microtime_diff($start_report_time, microtime())); print "$count users processed in $duration seconds (".sprintf("%0.3f", $duration/$count).' secs/user)
'."\n"; } $tables[] = &$table; $this->create_legend_table($tables, $table); } // end function function get_exercisetype(&$questions, &$questionids, &$blank) { if (empty($questions)) { $type = $blank; } else { switch ($questions[$questionids[0]]->type) { case HOTPOT_JCB: $type = "JCB"; break; case HOTPOT_JCLOZE : $type = "JCloze"; break; case HOTPOT_JCROSS : $type = "JCross"; break; case HOTPOT_JMATCH : $type = "JMatch"; break; case HOTPOT_JMIX : $type = "JMix"; break; case HOTPOT_JQUIZ : $type = "JQuiz"; break; case HOTPOT_TEXTOYS_RHUBARB : $type = "Rhubarb"; break; case HOTPOT_TEXTOYS_SEQUITUR : $type = "Sequitur"; break; default: $type = $blank; } } return $type; } function set_head(&$options, &$table, $zone, $exercisetype='', $details=array(), $questioncount=0) { if (empty($table->head)) { $table->head = array(); } switch ($zone) { case 'exercise': array_push($table->head, get_string('reportcoursename', 'hotpot'), get_string('reportsectionnumber', 'hotpot'), get_string('reportexercisenumber', 'hotpot'), get_string('reportexercisename', 'hotpot'), get_string('reportexercisetype', 'hotpot'), get_string('reportnumberofquestions', 'hotpot') ); break; case 'user': array_push($table->head, get_string('reportstudentid', 'hotpot'), get_string('reportlogindate', 'hotpot'), get_string('reportlogintime', 'hotpot'), get_string('reportlogofftime', 'hotpot') ); break; case 'attempt': array_push($table->head, get_string('reportattemptnumber', 'hotpot'), get_string('reportattemptstart', 'hotpot'), get_string('reportattemptfinish', 'hotpot') ); break; case 'click': array_push($table->head, get_string('reportclicknumber', 'hotpot'), get_string('reportclicktime', 'hotpot'), get_string('reportclicktype', 'hotpot') ); break; case 'details': foreach($details as $detail) { if ($exercisetype=='JQuiz' && $detail=='clues') { $detail = 'showanswer'; } $detail = get_string("report$detail", 'hotpot'); for ($i=0; $i<$questioncount; $i++) { $str = get_string('questionshort', 'hotpot', $i+1); if ($i==0 || $options['reportformat']!='htm') { $str = "$detail $str"; } $table->head[] = $str; } } break; case 'totals': $reportpercentscore =get_string('reportpercentscore', 'hotpot'); if (!function_exists('clean_getstring_data')) { // Moodle 1.4 (and less) $reportpercentscore = str_replace('%', '%%', $reportpercentscore); } array_push($table->head, get_string('reportthisclick', 'hotpot', get_string('reportquestionstried', 'hotpot')), get_string('reportsofar', 'hotpot', get_string('reportquestionstried', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportright', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportwrong', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportnottried', 'hotpot')), get_string('reportsofar', 'hotpot', get_string('reportright', 'hotpot')), get_string('reportsofar', 'hotpot', get_string('reportwrong', 'hotpot')), get_string('reportsofar', 'hotpot', get_string('reportnottried', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportanswers', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reporthints', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string($exercisetype=='JQuiz' ? 'reportshowanswer' : 'reportclues', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportevents', 'hotpot')), get_string('reportsofar', 'hotpot', get_string('reporthints', 'hotpot')), get_string('reportsofar', 'hotpot', get_string($exercisetype=='JQuiz' ? 'reportshowanswer' : 'reportclues', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportrawscore', 'hotpot')), get_string('reportthisclick', 'hotpot', get_string('reportmaxscore', 'hotpot')), get_string('reportthisclick', 'hotpot', $reportpercentscore), get_string('reportsofar', 'hotpot', get_string('reportrawscore', 'hotpot')), get_string('reportsofar', 'hotpot', get_string('reportmaxscore', 'hotpot')), get_string('reportsofar', 'hotpot', $reportpercentscore), get_string('reporthotpotscore', 'hotpot') ); break; } // end switch } function set_align_and_wrap(&$table) { $count = count($table->head); for ($i=0; $i<$count; $i++) { if ($i==0 || $i==1 || $i==2 || $i==4 || $i==5 || $i>=7) { // numeric (and short text) columns $table->align[] = 'center'; $table->wrap[] = ''; } else { // text columns $table->align[] = 'left'; $table->wrap[] = 'nowrap'; } } } function set_data_exercise(&$cm, &$course, &$hotpot, &$questions, &$questionids, &$questioncount, &$blank) { // get exercise details (course name, section number, activity number, quiztype and question count) $record = get_record("course_sections", "id", $cm->section); $this->data['exercise'] = array( 'course' => $course->shortname, 'section' => empty($record) ? $blank : $record->section+1, 'number' => empty($record) ? $blank : array_search($cm->id, explode(',', $record->sequence))+1, 'name' => $hotpot->name, 'type' => $this->get_exercisetype($questions, $questionids, $blank), 'questioncount' => $questioncount ); } function set_data_user(&$options, &$course, &$user) { global $CFG; // shortcut to first attempt record (which also hold user info) $attempt = &$user->attempts[0]; $idnumber = $attempt->idnumber; if (empty($idnumber)) { $idnumber = fullname($attempt); } if ($options['reportformat']=='htm') { $idnumber = ''.$idnumber.''; } $this->data['user'] = array( 'idnumber' => $idnumber, ); } function set_data_attempt(&$attempt, &$strftimedate, &$strftimetime, &$blank) { global $CFG; $records = get_records_sql_menu(" SELECT userid, MAX(time) AS logintime FROM {$CFG->prefix}log WHERE userid=$attempt->userid AND action='login' AND time<$attempt->timestart GROUP BY userid "); if (empty($records)) { $logindate = $blank; $logintime = $blank; } else { $logintime = $records[$attempt->userid]; $logindate = trim(userdate($logintime, $strftimedate)); $logintime = trim(userdate($logintime, $strftimetime)); } $records = get_records_sql_menu(" SELECT userid, MIN(time) AS logouttime FROM {$CFG->prefix}log WHERE userid=$attempt->userid AND action='logout' AND time>$attempt->cr_timefinish GROUP BY userid "); if (empty($records)) { $logouttime = $blank; } else { $logouttime = $records[$attempt->userid]; $logouttime = trim(userdate($logouttime, $strftimetime)); } $this->data['attempt'] = array( 'logindate' => $logindate, 'logintime' => $logintime, 'logouttime' => $logouttime, 'number' => $attempt->attempt, 'start' => trim(userdate($attempt->timestart, $strftimetime)), 'finish' => trim(userdate($attempt->cr_timefinish, $strftimetime)), ); } function set_data_click($number, $time, $exercisetype, $click) { $types = array(); foreach (array_keys($click['types']) as $type) { if ($exercisetype=='JQuiz' && $type=='clues') { $type = 'showanswer'; } else { // remove final 's' $type = substr($type, 0, strlen($type)-1); } // $types[] = get_string($type, 'hotpot'); $types[] = $type; } $this->data['click'] = array( 'number' => $number, 'time' => $time, 'type' => empty($types) ? '??' : implode(',', $types) ); } function set_data_totals(&$click, &$clicks, &$questioncount, &$blank, &$attempt) { $count= array( 'click' => array( 'correct' => count($click['correct']), 'wrong' => count($click['wrong']), 'answers' => count($click['answers']), 'hints' => array_sum($click['hints']), 'clues' => array_sum($click['clues']), 'events' => array_sum($click['events']), 'score' => array_sum($click['score']), 'maxscore' => array_sum($click['weighting']), ), 'clicks' => array( 'correct' => count($clicks['correct']), 'wrong' => count($clicks['wrong']), 'answers' => count($clicks['answers']), 'hints' => array_sum($clicks['hints']), 'clues' => array_sum($clicks['clues']), 'score' => array_sum($clicks['score']), 'maxscore' => array_sum($clicks['weighting']), ) ); foreach ($count as $period=>$values) { $count[$period]['nottried'] = $questioncount - ($values['correct'] + $values['wrong']); $count[$period]['percent'] = empty($values['maxscore']) ? $blank : round(100 * $values['score'] / $values['maxscore'], 0); // blank out zero click values if ($period=='click') { foreach ($values as $detail=>$value) { if ($detail=='answers' || $detail=='hints' || $detail=='clues' || $detail=='events') { if (empty($value)) { $count[$period][$detail] = $blank; } } } } } $this->data['totals'] = array( $count['click']['answers'], // "q's tried" $count['clicks']['answers'], // "q's tried so far" $count['click']['correct'], // "right" $count['click']['wrong'], // "wrong" $count['click']['nottried'], // "not tried" $count['clicks']['correct'], // "right so far" $count['clicks']['wrong'], // "wrong so far" $count['clicks']['nottried'], // "not tried so far" $count['click']['answers'], // "answers", $count['click']['hints'], // "hints", $count['click']['clues'], // "clues", $count['click']['events'], // "answers", $count['clicks']['hints'], // "hints so far", $count['clicks']['clues'], // "clues so far", $count['click']['score'], // 'raw score', $count['click']['maxscore'], // 'max score', $count['click']['percent'], // '% score' $count['clicks']['score'], // 'raw score, $count['clicks']['maxscore'], // 'max score, $count['clicks']['percent'], // '% score $attempt->score // 'hotpot score' ); } function update_event_count(&$click, $detail, $q) { if ($detail=='checks' || $detail=='hints' || $detail=='clues') { $click['types'][$detail] = true; } if ($detail=='answers' || $detail=='hints' || $detail=='clues') { $click['events'][$q] = isset($click['events'][$q]) ? $click['events'][$q]+1 : 1; } if ($detail=='answers') { $click['changes'][$q] = isset($click['changes'][$q]) ? $click['changes'][$q]+1 : 1; } } function set_data(&$cells, $zone) { foreach ($this->data[$zone] as $name=>$value) { $cells[] = $value; } } } // end class ?>