3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * Display user activity reports for a course
21 * @copyright 1999 Martin Dougiamas http://dougiamas.com
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 require_once("../config.php");
27 require_once("lib.php");
28 require_once($CFG->libdir
.'/completionlib.php');
30 $id = required_param('id',PARAM_INT
); // course id
31 $user = required_param('user',PARAM_INT
); // user id
32 $mode = optional_param('mode', "todaylogs", PARAM_ALPHA
);
33 $page = optional_param('page', 0, PARAM_INT
);
34 $perpage = optional_param('perpage', 100, PARAM_INT
);
36 $url = new moodle_url('/course/user.php', array('id'=>$id,'user'=>$user, 'mode'=>$mode));
38 $url->param('page', $page);
40 if ($perpage !== 100) {
41 $url->param('perpage', $perpage);
45 if (!$course = $DB->get_record('course', array('id'=>$id))) {
46 print_error('invalidcourseid', 'error');
49 if (! $user = $DB->get_record("user", array("id"=>$user))) {
50 print_error('invaliduserid', 'error');
54 $coursecontext = get_context_instance(CONTEXT_COURSE
, $course->id
);
55 $personalcontext = get_context_instance(CONTEXT_USER
, $user->id
);
58 $PAGE->set_pagelayout('admin');
59 if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext) and !is_enrolled($coursecontext)) {
60 // do not require parents to be enrolled in courses ;-)
61 $PAGE->set_course($course);
63 require_login($course);
67 echo $OUTPUT->header();
68 echo $OUTPUT->heading(get_string('userdeleted'));
69 echo $OUTPUT->footer();
73 // prepare list of allowed modes
74 $myreports = ($course->showreports
and $USER->id
== $user->id
);
75 $anyreport = has_capability('moodle/user:viewuseractivitiesreport', $personalcontext);
79 if ($myreports or $anyreport or has_capability('coursereport/outline:view', $coursecontext)) {
83 if ($myreports or $anyreport or has_capability('coursereport/outline:view', $coursecontext)) {
84 $modes[] = 'complete';
87 if ($myreports or $anyreport or has_capability('coursereport/log:viewtoday', $coursecontext)) {
88 $modes[] = 'todaylogs';
91 if ($myreports or $anyreport or has_capability('coursereport/log:view', $coursecontext)) {
95 if ($myreports or $anyreport or has_capability('coursereport/stats:view', $coursecontext)) {
99 if (has_capability('moodle/grade:viewall', $coursecontext)) {
100 //ok - can view all course grades
103 } else if ($course->showgrades
and $user->id
== $USER->id
and has_capability('moodle/grade:view', $coursecontext)) {
104 //ok - can view own grades
107 } else if ($course->showgrades
and has_capability('moodle/grade:viewall', $personalcontext)) {
108 // ok - can view grades of this user - parent most probably
111 } else if ($course->showgrades
and $anyreport) {
112 // ok - can view grades of this user - parent most probably
116 // Course completion tab
117 if (!empty($CFG->enablecompletion
) && ($course->id
== SITEID ||
!empty($course->enablecompletion
)) && // completion enabled
118 ($myreports ||
$anyreport ||
($course->id
== SITEID ||
has_capability('coursereport/completion:view', $coursecontext)))) { // permissions to view the report
120 // Decide if singular or plural
121 if ($course->id
== SITEID
) {
122 $modes[] = 'coursecompletions';
124 $modes[] = 'coursecompletion';
130 require_capability('moodle/user:viewuseractivitiesreport', $personalcontext);
133 if (!in_array($mode, $modes)) {
134 // forbidden or non-existent mode
135 $mode = reset($modes);
138 add_to_log($course->id
, "course", "user report", "user.php?id=$course->id&user=$user->id&mode=$mode", "$user->id");
140 $stractivityreport = get_string("activityreport");
141 $strparticipants = get_string("participants");
142 $stroutline = get_string("outline");
143 $strcomplete = get_string("complete");
144 $stralllogs = get_string("alllogs");
145 $strtodaylogs = get_string("todaylogs");
146 $strmode = get_string($mode);
147 $fullname = fullname($user, true);
150 if ($course->id
!= SITEID
&& has_capability('moodle/course:viewparticipants', $coursecontext)) {
151 $link = new moodle_url('/user/index.php', array('id'=>$course->id
));
154 $PAGE->navigation
->extend_for_user($user);
155 $PAGE->set_title("$course->shortname: $stractivityreport ($mode)");
156 $PAGE->set_heading($course->fullname
);
157 echo $OUTPUT->header();
161 if (empty($CFG->grade_profilereport
) or !file_exists($CFG->dirroot
.'/grade/report/'.$CFG->grade_profilereport
.'/lib.php')) {
162 $CFG->grade_profilereport
= 'user';
164 require_once $CFG->libdir
.'/gradelib.php';
165 require_once $CFG->dirroot
.'/grade/lib.php';
166 require_once $CFG->dirroot
.'/grade/report/'.$CFG->grade_profilereport
.'/lib.php';
168 $functionname = 'grade_report_'.$CFG->grade_profilereport
.'_profilereport';
169 if (function_exists($functionname)) {
170 $functionname($course, $user);
175 echo '<div class="graph">';
176 print_log_graph($course, $user->id
, "userday.png");
178 print_log($course, $user->id
, usergetmidnight(time()), "l.time DESC", $page, $perpage,
179 "user.php?id=$course->id&user=$user->id&mode=$mode");
183 echo '<div class="graph">';
184 print_log_graph($course, $user->id
, "usercourse.png");
186 print_log($course, $user->id
, 0, "l.time DESC", $page, $perpage,
187 "user.php?id=$course->id&user=$user->id&mode=$mode");
191 if (empty($CFG->enablestats
)) {
192 print_error('statsdisable', 'error');
195 require_once($CFG->dirroot
.'/lib/statslib.php');
197 $statsstatus = stats_check_uptodate($course->id
);
198 if ($statsstatus !== NULL) {
199 echo $OUTPUT->notification($statsstatus);
202 $earliestday = $DB->get_field_sql('SELECT timeend FROM {stats_user_daily} ORDER BY timeend');
203 $earliestweek = $DB->get_field_sql('SELECT timeend FROM {stats_user_weekly} ORDER BY timeend');
204 $earliestmonth = $DB->get_field_sql('SELECT timeend FROM {stats_user_monthly} ORDER BY timeend');
206 if (empty($earliestday)) $earliestday = time();
207 if (empty($earliestweek)) $earliestweek = time();
208 if (empty($earliestmonth)) $earliestmonth = time();
210 $now = stats_get_base_daily();
211 $lastweekend = stats_get_base_weekly();
212 $lastmonthend = stats_get_base_monthly();
214 $timeoptions = stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth);
216 if (empty($timeoptions)) {
217 print_error('nostatstodisplay', '', $CFG->wwwroot
.'/course/user.php?id='.$course->id
.'&user='.$user->id
.'&mode=outline');
221 $time = array_pop(array_keys($timeoptions));
223 $param = stats_get_parameters($time,STATS_REPORT_USER_VIEW
,$course->id
,STATS_MODE_DETAILED
);
224 $params = $param->params
;
226 $param->table
= 'user_'.$param->table
;
228 $sql = 'SELECT timeend,'.$param->fields
.' FROM {stats_'.$param->table
.'} WHERE '
229 .(($course->id
== SITEID
) ?
'' : ' courseid = '.$course->id
.' AND ')
230 .' userid = '.$user->id
.' AND timeend >= '.$param->timeafter
.$param->extras
231 .' ORDER BY timeend DESC';
232 $stats = $DB->get_records_sql($sql, $params); //TODO: improve these params!!
235 print_error('nostatstodisplay', '', $CFG->wwwroot
.'/course/user.php?id='.$course->id
.'&user='.$user->id
.'&mode=outline');
238 // MDL-10818, do not display broken graph when user has no permission to view graph
239 if ($myreports or has_capability('coursereport/stats:view', $coursecontext)) {
240 echo '<center><img src="'.$CFG->wwwroot
.'/course/report/stats/graph.php?mode='.STATS_MODE_DETAILED
.'&course='.$course->id
.'&time='.$time.'&report='.STATS_REPORT_USER_VIEW
.'&userid='.$user->id
.'" alt="'.get_string('statisticsgraph').'" /></center>';
243 // What the heck is this about? -- MD
244 $stats = stats_fix_zeros($stats,$param->timeafter
,$param->table
,(!empty($param->line2
)),(!empty($param->line3
)));
246 $table = new html_table();
247 $table->align
= array('left','center','center','center');
248 $param->table
= str_replace('user_','',$param->table
);
249 switch ($param->table
) {
250 case 'daily' : $period = get_string('day'); break;
251 case 'weekly' : $period = get_string('week'); break;
252 case 'monthly': $period = get_string('month', 'form'); break;
253 default : $period = '';
255 $table->head
= array(get_string('periodending','moodle',$period),$param->line1
,$param->line2
,$param->line3
);
256 foreach ($stats as $stat) {
257 if (!empty($stat->zerofixed
)) { // Don't know why this is necessary, see stats_fix_zeros above - MD
260 $a = array(userdate($stat->timeend
,get_string('strftimedate'),$CFG->timezone
),$stat->line1
);
265 echo html_writer
::table($table);
270 get_all_mods($course->id
, $mods, $modnames, $modnamesplural, $modnamesused);
271 $sections = get_all_sections($course->id
);
273 for ($i=0; $i<=$course->numsections
; $i++
) {
275 if (isset($sections[$i])) { // should always be true
277 $section = $sections[$i];
278 $showsection = (has_capability('moodle/course:viewhiddensections', $coursecontext) or $section->visible
or !$course->hiddensections
);
280 if ($showsection) { // prevent hidden sections in user activity. Thanks to Geoff Wilbert!
282 if ($section->sequence
) {
283 echo '<div class="section">';
285 echo get_section_name($course, $section);
288 echo '<div class="content">';
290 if ($mode == "outline") {
291 echo "<table cellpadding=\"4\" cellspacing=\"0\">";
294 $sectionmods = explode(",", $section->sequence
);
295 foreach ($sectionmods as $sectionmod) {
296 if (empty($mods[$sectionmod])) {
299 $mod = $mods[$sectionmod];
301 if (empty($mod->visible
)) {
305 $instance = $DB->get_record("$mod->modname", array("id"=>$mod->instance
));
306 $libfile = "$CFG->dirroot/mod/$mod->modname/lib.php";
308 if (file_exists($libfile)) {
309 require_once($libfile);
313 $user_outline = $mod->modname
."_user_outline";
314 if (function_exists($user_outline)) {
315 $output = $user_outline($course, $user, $mod, $instance);
316 print_outline_row($mod, $instance, $output);
320 $user_complete = $mod->modname
."_user_complete";
321 if (function_exists($user_complete)) {
322 $image = $OUTPUT->pix_icon('icon', $mod->modfullname
, 'mod_'.$mod->modname
, array('class'=>'icon'));
323 echo "<h4>$image $mod->modfullname: ".
324 "<a href=\"$CFG->wwwroot/mod/$mod->modname/view.php?id=$mod->id\">".
325 format_string($instance->name
,true)."</a></h4>";
330 $user_complete($course, $user, $mod, $instance);
333 $output = ob_get_contents();
336 if (str_replace(' ', '', $output) != '<ul></ul>') {
345 if ($mode == "outline") {
348 echo '</div>'; // content
349 echo '</div>'; // section
355 case "coursecompletion":
356 case "coursecompletions":
358 // Display course completion user report
360 // Grab all courses the user is enrolled in and their completion status
368 ON con.instanceid = c.id
370 {role_assignments} ra
371 ON ra.contextid = con.id
377 ON e.id = ue.enrolid AND ra.userid = ue.userid
378 AND ra.userid = {$user->id}
381 // Get roles that are tracked by course completion
382 if ($roles = $CFG->gradebookroles
) {
384 AND ra.roleid IN ('.$roles.')
390 con.contextlevel = '.CONTEXT_COURSE
.'
391 AND c.enablecompletion = 1
395 // If we are looking at a specific course
396 if ($course->id
!= 1) {
398 AND c.id = '.(int)$course->id
.'
402 // Check if result is empty
403 $rs = $DB->get_recordset_sql($sql);
406 if ($course->id
!= 1) {
407 $error = get_string('nocompletions', 'coursereport_completion');
409 $error = get_string('nocompletioncoursesenroled', 'coursereport_completion');
412 echo $OUTPUT->notification($error);
413 $rs->close(); // not going to loop (but break), close rs
417 // Categorize courses by their status
419 'inprogress' => array(),
420 'complete' => array(),
421 'unstarted' => array()
424 // Sort courses by the user's status in each
425 foreach ($rs as $course_completion) {
426 $c_info = new completion_info((object)$course_completion);
428 // Is course complete?
429 $coursecomplete = $c_info->is_course_complete($user->id
);
431 // Has this user completed any criteria?
432 $criteriacomplete = $c_info->count_course_user_data($user->id
);
434 if ($coursecomplete) {
435 $courses['complete'][] = $c_info;
436 } else if ($criteriacomplete) {
437 $courses['inprogress'][] = $c_info;
439 $courses['unstarted'][] = $c_info;
442 $rs->close(); // after loop, close rs
444 // Loop through course status groups
445 foreach ($courses as $type => $infos) {
447 // If there are courses with this status
448 if (!empty($infos)) {
450 echo '<h1 align="center">'.get_string($type, 'coursereport_completion').'</h1>';
451 echo '<table class="generalbox boxaligncenter">';
452 echo '<tr class="ccheader">';
453 echo '<th class="c0 header" scope="col">'.get_string('course').'</th>';
454 echo '<th class="c1 header" scope="col">'.get_string('requiredcriteria', 'completion').'</th>';
455 echo '<th class="c2 header" scope="col">'.get_string('status').'</th>';
456 echo '<th class="c3 header" scope="col" width="15%">'.get_string('info').'</th>';
458 if ($type === 'complete') {
459 echo '<th class="c4 header" scope="col">'.get_string('completiondate', 'coursereport_completion').'</th>';
465 foreach ($infos as $c_info) {
468 $c_course = $DB->get_record('course', array('id' => $c_info->course_id
));
469 $course_name = $c_course->fullname
;
472 $completions = $c_info->get_completions($user->id
);
477 // For aggregating activity completion
478 $activities = array();
479 $activities_complete = 0;
481 // For aggregating prerequisites
482 $prerequisites = array();
483 $prerequisites_complete = 0;
485 // Loop through course criteria
486 foreach ($completions as $completion) {
487 $criteria = $completion->get_criteria();
488 $complete = $completion->is_complete();
490 // Activities are a special case, so cache them and leave them till last
491 if ($criteria->criteriatype
== COMPLETION_CRITERIA_TYPE_ACTIVITY
) {
492 $activities[$criteria->moduleinstance
] = $complete;
495 $activities_complete++
;
501 // Prerequisites are also a special case, so cache them and leave them till last
502 if ($criteria->criteriatype
== COMPLETION_CRITERIA_TYPE_COURSE
) {
503 $prerequisites[$criteria->courseinstance
] = $complete;
506 $prerequisites_complete++
;
513 $row['title'] = $criteria->get_title();
514 $row['status'] = $completion->get_status();
518 // Aggregate activities
519 if (!empty($activities)) {
522 $row['title'] = get_string('activitiescomplete', 'coursereport_completion');
523 $row['status'] = $activities_complete.' of '.count($activities);
527 // Aggregate prerequisites
528 if (!empty($prerequisites)) {
531 $row['title'] = get_string('prerequisitescompleted', 'completion');
532 $row['status'] = $prerequisites_complete.' of '.count($prerequisites);
533 array_splice($rows, 0, 0, array($row));
539 foreach ($rows as $row) {
541 // Display course name on first row
543 echo '<tr><td class="c0"><a href="'.$CFG->wwwroot
.'/course/view.php?id='.$c_course->id
.'">'.format_string($course_name).'</a></td>';
545 echo '<tr><td class="c0"></td>';
548 echo '<td class="c1">';
550 echo '</td><td class="c2">';
552 switch ($row['status']) {
554 echo get_string('complete');
558 echo get_string('incomplete', 'coursereport_completion');
565 // Display link on first row
566 echo '</td><td class="c3">';
568 echo '<a href="'.$CFG->wwwroot
.'/blocks/completionstatus/details.php?course='.$c_course->id
.'&user='.$user->id
.'">'.get_string('detailedview', 'coursereport_completion').'</a>';
572 // Display completion date for completed courses on first row
573 if ($type === 'complete' && $first_row) {
575 'userid' => $user->id
,
576 'course' => $c_course->id
579 $ccompletion = new completion_completion($params);
580 echo '<td class="c4">'.userdate($ccompletion->timecompleted
, '%e %B %G').'</td>';
595 // can not be reached ;-)
599 echo $OUTPUT->footer();
602 function print_outline_row($mod, $instance, $result) {
605 $image = "<img src=\"" . $OUTPUT->pix_url('icon', $mod->modname
) . "\" class=\"icon\" alt=\"$mod->modfullname\" />";
608 echo "<td valign=\"top\">$image</td>";
609 echo "<td valign=\"top\" style=\"width:300\">";
610 echo " <a title=\"$mod->modfullname\"";
611 echo " href=\"../mod/$mod->modname/view.php?id=$mod->id\">".format_string($instance->name
,true)."</a></td>";
612 echo "<td> </td>";
613 echo "<td valign=\"top\">";
614 if (isset($result->info
)) {
615 echo "$result->info";
617 echo "<p style=\"text-align:center\">-</p>";
620 echo "<td> </td>";
621 if (!empty($result->time
)) {
622 $timeago = format_time(time() - $result->time
);
623 echo "<td valign=\"top\" style=\"white-space: nowrap\">".userdate($result->time
)." ($timeago)</td>";