Merge branch 'wip-MDL-27058-master' of git://github.com/samhemelryk/moodle.git (solve...
[moodle.git] / course / user.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
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.
9 //
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.
14 //
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/>.
18 /**
19  * Display user activity reports for a course
20  *
21  * @copyright 1999 Martin Dougiamas  http://dougiamas.com
22  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  * @package course
24  */
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));
37 if ($page !== 0) {
38     $url->param('page', $page);
39 }
40 if ($perpage !== 100) {
41     $url->param('perpage', $perpage);
42 }
43 $PAGE->set_url($url);
45 if (!$course = $DB->get_record('course', array('id'=>$id))) {
46     print_error('invalidcourseid', 'error');
47 }
49 if (! $user = $DB->get_record("user", array("id"=>$user))) {
50     print_error('invaliduserid', 'error');
51 }
53 require_login();
54 $coursecontext   = get_context_instance(CONTEXT_COURSE, $course->id);
55 $personalcontext = get_context_instance(CONTEXT_USER, $user->id);
57 require_login();
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);
62 } else {
63     require_login($course);
64 }
66 if ($user->deleted) {
67     echo $OUTPUT->header();
68     echo $OUTPUT->heading(get_string('userdeleted'));
69     echo $OUTPUT->footer();
70     die;
71 }
73 // prepare list of allowed modes
74 $myreports  = ($course->showreports and $USER->id == $user->id);
75 $anyreport  = has_capability('moodle/user:viewuseractivitiesreport', $personalcontext);
77 $modes = array();
79 if ($myreports or $anyreport or has_capability('coursereport/outline:view', $coursecontext)) {
80     $modes[] = 'outline';
81 }
83 if ($myreports or $anyreport or has_capability('coursereport/outline:view', $coursecontext)) {
84     $modes[] = 'complete';
85 }
87 if ($myreports or $anyreport or has_capability('coursereport/log:viewtoday', $coursecontext)) {
88     $modes[] = 'todaylogs';
89 }
91 if ($myreports or $anyreport or has_capability('coursereport/log:view', $coursecontext)) {
92     $modes[] = 'alllogs';
93 }
95 if ($myreports or $anyreport or has_capability('coursereport/stats:view', $coursecontext)) {
96     $modes[] = 'stats';
97 }
99 if (has_capability('moodle/grade:viewall', $coursecontext)) {
100     //ok - can view all course grades
101     $modes[] = 'grade';
103 } else if ($course->showgrades and $user->id == $USER->id and has_capability('moodle/grade:view', $coursecontext)) {
104     //ok - can view own grades
105     $modes[] = 'grade';
107 } else if ($course->showgrades and has_capability('moodle/grade:viewall', $personalcontext)) {
108     // ok - can view grades of this user - parent most probably
109     $modes[] = 'grade';
111 } else if ($course->showgrades and $anyreport) {
112     // ok - can view grades of this user - parent most probably
113     $modes[] = 'grade';
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';
123     } else {
124         $modes[] = 'coursecompletion';
125     }
129 if (empty($modes)) {
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&amp;user=$user->id&amp;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);
149 $link = null;
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->navigation->set_userid_for_parent_checks($user->id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed.
156 $PAGE->set_title("$course->shortname: $stractivityreport ($mode)");
157 $PAGE->set_heading($course->fullname);
158 echo $OUTPUT->header();
160 switch ($mode) {
161     case "grade":
162         if (empty($CFG->grade_profilereport) or !file_exists($CFG->dirroot.'/grade/report/'.$CFG->grade_profilereport.'/lib.php')) {
163             $CFG->grade_profilereport = 'user';
164         }
165         require_once $CFG->libdir.'/gradelib.php';
166         require_once $CFG->dirroot.'/grade/lib.php';
167         require_once $CFG->dirroot.'/grade/report/'.$CFG->grade_profilereport.'/lib.php';
169         $functionname = 'grade_report_'.$CFG->grade_profilereport.'_profilereport';
170         if (function_exists($functionname)) {
171             $functionname($course, $user);
172         }
173         break;
175     case "todaylogs" :
176         echo '<div class="graph">';
177         print_log_graph($course, $user->id, "userday.png");
178         echo '</div>';
179         print_log($course, $user->id, usergetmidnight(time()), "l.time DESC", $page, $perpage,
180                   "user.php?id=$course->id&amp;user=$user->id&amp;mode=$mode");
181         break;
183     case "alllogs" :
184         echo '<div class="graph">';
185         print_log_graph($course, $user->id, "usercourse.png");
186         echo '</div>';
187         print_log($course, $user->id, 0, "l.time DESC", $page, $perpage,
188                   "user.php?id=$course->id&amp;user=$user->id&amp;mode=$mode");
189         break;
190     case 'stats':
192         if (empty($CFG->enablestats)) {
193             print_error('statsdisable', 'error');
194         }
196         require_once($CFG->dirroot.'/lib/statslib.php');
198         $statsstatus = stats_check_uptodate($course->id);
199         if ($statsstatus !== NULL) {
200             echo $OUTPUT->notification($statsstatus);
201         }
203         $earliestday   = $DB->get_field_sql('SELECT timeend FROM {stats_user_daily} ORDER BY timeend');
204         $earliestweek  = $DB->get_field_sql('SELECT timeend FROM {stats_user_weekly} ORDER BY timeend');
205         $earliestmonth = $DB->get_field_sql('SELECT timeend FROM {stats_user_monthly} ORDER BY timeend');
207         if (empty($earliestday)) $earliestday = time();
208         if (empty($earliestweek)) $earliestweek = time();
209         if (empty($earliestmonth)) $earliestmonth = time();
211         $now = stats_get_base_daily();
212         $lastweekend = stats_get_base_weekly();
213         $lastmonthend = stats_get_base_monthly();
215         $timeoptions = stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth);
217         if (empty($timeoptions)) {
218             print_error('nostatstodisplay', '', $CFG->wwwroot.'/course/user.php?id='.$course->id.'&user='.$user->id.'&mode=outline');
219         }
221         // use the earliest.
222         $time = array_pop(array_keys($timeoptions));
224         $param = stats_get_parameters($time,STATS_REPORT_USER_VIEW,$course->id,STATS_MODE_DETAILED);
225         $params = $param->params;
227         $param->table = 'user_'.$param->table;
229         $sql = 'SELECT timeend,'.$param->fields.' FROM {stats_'.$param->table.'} WHERE '
230         .(($course->id == SITEID) ? '' : ' courseid = '.$course->id.' AND ')
231             .' userid = '.$user->id.' AND timeend >= '.$param->timeafter .$param->extras
232             .' ORDER BY timeend DESC';
233         $stats = $DB->get_records_sql($sql, $params); //TODO: improve these params!!
235         if (empty($stats)) {
236             print_error('nostatstodisplay', '', $CFG->wwwroot.'/course/user.php?id='.$course->id.'&user='.$user->id.'&mode=outline');
237         }
239         // MDL-10818, do not display broken graph when user has no permission to view graph
240         if ($myreports or has_capability('coursereport/stats:view', $coursecontext)) {
241             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>';
242         }
244         // What the heck is this about?   -- MD
245         $stats = stats_fix_zeros($stats,$param->timeafter,$param->table,(!empty($param->line2)),(!empty($param->line3)));
247         $table = new html_table();
248         $table->align = array('left','center','center','center');
249         $param->table = str_replace('user_','',$param->table);
250         switch ($param->table) {
251             case 'daily'  : $period = get_string('day'); break;
252             case 'weekly' : $period = get_string('week'); break;
253             case 'monthly': $period = get_string('month', 'form'); break;
254             default : $period = '';
255         }
256         $table->head = array(get_string('periodending','moodle',$period),$param->line1,$param->line2,$param->line3);
257         foreach ($stats as $stat) {
258             if (!empty($stat->zerofixed)) {  // Don't know why this is necessary, see stats_fix_zeros above - MD
259                 continue;
260             }
261             $a = array(userdate($stat->timeend,get_string('strftimedate'),$CFG->timezone),$stat->line1);
262             $a[] = $stat->line2;
263             $a[] = $stat->line3;
264             $table->data[] = $a;
265         }
266         echo html_writer::table($table);
267         break;
269     case "outline" :
270     case "complete" :
271         get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
272         $sections = get_all_sections($course->id);
273         $itemsprinted = false;
275         for ($i=0; $i<=$course->numsections; $i++) {
277             if (isset($sections[$i])) {   // should always be true
279                 $section = $sections[$i];
280                 $showsection = (has_capability('moodle/course:viewhiddensections', $coursecontext) or $section->visible or !$course->hiddensections);
282                 if ($showsection) { // prevent hidden sections in user activity. Thanks to Geoff Wilbert!
283                     // Check the section has a sequence. This is the sequence of modules/resources.
284                     // If there is no sequence there is nothing to display.
285                     if ($section->sequence) {
286                         $itemsprinted = true;
287                         echo '<div class="section">';
288                         echo '<h2>';
289                         echo get_section_name($course, $section);
290                         echo "</h2>";
292                         echo '<div class="content">';
294                         if ($mode == "outline") {
295                             echo "<table cellpadding=\"4\" cellspacing=\"0\">";
296                         }
298                         $sectionmods = explode(",", $section->sequence);
299                         foreach ($sectionmods as $sectionmod) {
300                             if (empty($mods[$sectionmod])) {
301                                 continue;
302                             }
303                             $mod = $mods[$sectionmod];
305                             if (empty($mod->visible)) {
306                                 continue;
307                             }
309                             $instance = $DB->get_record("$mod->modname", array("id"=>$mod->instance));
310                             $libfile = "$CFG->dirroot/mod/$mod->modname/lib.php";
312                             if (file_exists($libfile)) {
313                                 require_once($libfile);
315                                 switch ($mode) {
316                                     case "outline":
317                                         $user_outline = $mod->modname."_user_outline";
318                                         if (function_exists($user_outline)) {
319                                             $output = $user_outline($course, $user, $mod, $instance);
320                                             print_outline_row($mod, $instance, $output);
321                                         }
322                                         break;
323                                     case "complete":
324                                         $user_complete = $mod->modname."_user_complete";
325                                         if (function_exists($user_complete)) {
326                                             $image = $OUTPUT->pix_icon('icon', $mod->modfullname, 'mod_'.$mod->modname, array('class'=>'icon'));
327                                             echo "<h4>$image $mod->modfullname: ".
328                                                  "<a href=\"$CFG->wwwroot/mod/$mod->modname/view.php?id=$mod->id\">".
329                                                  format_string($instance->name,true)."</a></h4>";
331                                             ob_start();
333                                             echo "<ul>";
334                                             $user_complete($course, $user, $mod, $instance);
335                                             echo "</ul>";
337                                             $output = ob_get_contents();
338                                             ob_end_clean();
340                                             if (str_replace(' ', '', $output) != '<ul></ul>') {
341                                                 echo $output;
342                                             }
343                                         }
344                                         break;
345                                     }
346                                 }
347                             }
349                         if ($mode == "outline") {
350                             echo "</table>";
351                         }
352                         echo '</div>';  // content
353                         echo '</div>';  // section
354                     }
355                 }
356             }
357         }
359         if (!$itemsprinted) {
360             echo $OUTPUT->notification(get_string('nothingtodisplay'));
361         }
363         break;
364     case "coursecompletion":
365     case "coursecompletions":
367         // Display course completion user report
369         // Grab all courses the user is enrolled in and their completion status
370         $sql = "
371             SELECT DISTINCT
372                 c.id AS id
373             FROM
374                 {course} c
375             INNER JOIN
376                 {context} con
377              ON con.instanceid = c.id
378             INNER JOIN
379                 {role_assignments} ra
380              ON ra.contextid = con.id
381             INNER JOIN
382                 {enrol} e
383              ON c.id = e.courseid
384             INNER JOIN
385                 {user_enrolments} ue
386              ON e.id = ue.enrolid AND ra.userid = ue.userid
387             AND ra.userid = {$user->id}
388         ";
390         // Get roles that are tracked by course completion
391         if ($roles = $CFG->gradebookroles) {
392             $sql .= '
393                 AND ra.roleid IN ('.$roles.')
394             ';
395         }
397         $sql .= '
398             WHERE
399                 con.contextlevel = '.CONTEXT_COURSE.'
400             AND c.enablecompletion = 1
401         ';
404         // If we are looking at a specific course
405         if ($course->id != 1) {
406             $sql .= '
407                 AND c.id = '.(int)$course->id.'
408             ';
409         }
411         // Check if result is empty
412         $rs = $DB->get_recordset_sql($sql);
413         if (!$rs->valid()) {
415             if ($course->id != 1) {
416                 $error = get_string('nocompletions', 'coursereport_completion');
417             } else {
418                 $error = get_string('nocompletioncoursesenroled', 'coursereport_completion');
419             }
421             echo $OUTPUT->notification($error);
422             $rs->close(); // not going to loop (but break), close rs
423             break;
424         }
426         // Categorize courses by their status
427         $courses = array(
428             'inprogress'    => array(),
429             'complete'      => array(),
430             'unstarted'     => array()
431         );
433         // Sort courses by the user's status in each
434         foreach ($rs as $course_completion) {
435             $c_info = new completion_info((object)$course_completion);
437             // Is course complete?
438             $coursecomplete = $c_info->is_course_complete($user->id);
440             // Has this user completed any criteria?
441             $criteriacomplete = $c_info->count_course_user_data($user->id);
443             if ($coursecomplete) {
444                 $courses['complete'][] = $c_info;
445             } else if ($criteriacomplete) {
446                 $courses['inprogress'][] = $c_info;
447             } else {
448                 $courses['unstarted'][] = $c_info;
449             }
450         }
451         $rs->close(); // after loop, close rs
453         // Loop through course status groups
454         foreach ($courses as $type => $infos) {
456             // If there are courses with this status
457             if (!empty($infos)) {
459                 echo '<h1 align="center">'.get_string($type, 'coursereport_completion').'</h1>';
460                 echo '<table class="generalbox boxaligncenter">';
461                 echo '<tr class="ccheader">';
462                 echo '<th class="c0 header" scope="col">'.get_string('course').'</th>';
463                 echo '<th class="c1 header" scope="col">'.get_string('requiredcriteria', 'completion').'</th>';
464                 echo '<th class="c2 header" scope="col">'.get_string('status').'</th>';
465                 echo '<th class="c3 header" scope="col" width="15%">'.get_string('info').'</th>';
467                 if ($type === 'complete') {
468                     echo '<th class="c4 header" scope="col">'.get_string('completiondate', 'coursereport_completion').'</th>';
469                 }
471                 echo '</tr>';
473                 // For each course
474                 foreach ($infos as $c_info) {
476                     // Get course info
477                     $c_course = $DB->get_record('course', array('id' => $c_info->course_id));
478                     $course_context = get_context_instance(CONTEXT_COURSE, $c_course->id, MUST_EXIST);
479                     $course_name = format_string($c_course->fullname, true, array('context' => $course_context));
481                     // Get completions
482                     $completions = $c_info->get_completions($user->id);
484                     // Save row data
485                     $rows = array();
487                     // For aggregating activity completion
488                     $activities = array();
489                     $activities_complete = 0;
491                     // For aggregating prerequisites
492                     $prerequisites = array();
493                     $prerequisites_complete = 0;
495                     // Loop through course criteria
496                     foreach ($completions as $completion) {
497                         $criteria = $completion->get_criteria();
498                         $complete = $completion->is_complete();
500                         // Activities are a special case, so cache them and leave them till last
501                         if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
502                             $activities[$criteria->moduleinstance] = $complete;
504                             if ($complete) {
505                                 $activities_complete++;
506                             }
508                             continue;
509                         }
511                         // Prerequisites are also a special case, so cache them and leave them till last
512                         if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
513                             $prerequisites[$criteria->courseinstance] = $complete;
515                             if ($complete) {
516                                 $prerequisites_complete++;
517                             }
519                             continue;
520                         }
522                         $row = array();
523                         $row['title'] = $criteria->get_title();
524                         $row['status'] = $completion->get_status();
525                         $rows[] = $row;
526                     }
528                     // Aggregate activities
529                     if (!empty($activities)) {
531                         $row = array();
532                         $row['title'] = get_string('activitiescomplete', 'coursereport_completion');
533                         $row['status'] = $activities_complete.' of '.count($activities);
534                         $rows[] = $row;
535                     }
537                     // Aggregate prerequisites
538                     if (!empty($prerequisites)) {
540                         $row = array();
541                         $row['title'] = get_string('prerequisitescompleted', 'completion');
542                         $row['status'] = $prerequisites_complete.' of '.count($prerequisites);
543                         array_splice($rows, 0, 0, array($row));
544                     }
546                     $first_row = true;
548                     // Print table
549                     foreach ($rows as $row) {
551                         // Display course name on first row
552                         if ($first_row) {
553                             echo '<tr><td class="c0"><a href="'.$CFG->wwwroot.'/course/view.php?id='.$c_course->id.'">'.$course_name.'</a></td>';
554                         } else {
555                             echo '<tr><td class="c0"></td>';
556                         }
558                         echo '<td class="c1">';
559                         echo $row['title'];
560                         echo '</td><td class="c2">';
562                         switch ($row['status']) {
563                             case 'Yes':
564                                 echo get_string('complete');
565                                 break;
567                             case 'No':
568                                 echo get_string('incomplete', 'coursereport_completion');
569                                 break;
571                             default:
572                                 echo $row['status'];
573                         }
575                         // Display link on first row
576                         echo '</td><td class="c3">';
577                         if ($first_row) {
578                             echo '<a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$c_course->id.'&user='.$user->id.'">'.get_string('detailedview', 'coursereport_completion').'</a>';
579                         }
580                         echo '</td>';
582                         // Display completion date for completed courses on first row
583                         if ($type === 'complete' && $first_row) {
584                             $params = array(
585                                 'userid'    => $user->id,
586                                 'course'  => $c_course->id
587                             );
589                             $ccompletion = new completion_completion($params);
590                             echo '<td class="c4">'.userdate($ccompletion->timecompleted, '%e %B %G').'</td>';
591                         }
593                         $first_row = false;
594                         echo '</tr>';
595                     }
596                 }
598                 echo '</table>';
599             }
601         }
603         break;
604     default:
605         // can not be reached ;-)
609 echo $OUTPUT->footer();
612 function print_outline_row($mod, $instance, $result) {
613     global $OUTPUT;
615     $image = "<img src=\"" . $OUTPUT->pix_url('icon', $mod->modname) . "\" class=\"icon\" alt=\"$mod->modfullname\" />";
617     echo "<tr>";
618     echo "<td valign=\"top\">$image</td>";
619     echo "<td valign=\"top\" style=\"width:300\">";
620     echo "   <a title=\"$mod->modfullname\"";
621     echo "   href=\"../mod/$mod->modname/view.php?id=$mod->id\">".format_string($instance->name,true)."</a></td>";
622     echo "<td>&nbsp;&nbsp;&nbsp;</td>";
623     echo "<td valign=\"top\">";
624     if (isset($result->info)) {
625         echo "$result->info";
626     } else {
627         echo "<p style=\"text-align:center\">-</p>";
628     }
629     echo "</td>";
630     echo "<td>&nbsp;&nbsp;&nbsp;</td>";
631     if (!empty($result->time)) {
632         $timeago = format_time(time() - $result->time);
633         echo "<td valign=\"top\" style=\"white-space: nowrap\">".userdate($result->time)." ($timeago)</td>";
634     }
635     echo "</tr>";