387f2d150124810778f0445d65d0baccc818e178
[moodle.git] / course / report / completion / index.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/>.
19 /**
20  * Course completion progress report
21  *
22  * @package   moodlecore
23  * @copyright 2009 Catalyst IT Ltd
24  * @author    Aaron Barnes <aaronb@catalyst.net.nz>
25  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
27 require_once('../../../config.php');
28 require_once($CFG->libdir.'/completionlib.php');
30 /**
31  * Configuration
32  */
33 define('COMPLETION_REPORT_PAGE',        25);
34 define('COMPLETION_REPORT_COL_TITLES',  true);
36 /**
37  * Setup page, check permissions
38  */
40 // Get course
41 $courseid = required_param('course', PARAM_INT);
42 $format = optional_param('format','',PARAM_ALPHA);
43 $sort = optional_param('sort','',PARAM_ALPHA);
44 $edituser = optional_param('edituser', 0, PARAM_INT);
47 $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
49 $url = new moodle_url('/course/report/completion/index.php', array('course'=>$course->id));
50 $PAGE->set_url($url);
51 $PAGE->set_pagelayout('standard');
53 $firstnamesort = ($sort == 'firstname');
54 $excel = ($format == 'excelcsv');
55 $csv = ($format == 'csv' || $excel);
57 // Whether to start at a particular position
58 $start = optional_param('start',0,PARAM_INT);
60 // Whether to show idnumber
61 $idnumbers = $CFG->grade_report_showuseridnumber;
63 // Function for quoting csv cell values
64 function csv_quote($value) {
65     global $excel;
66     if($excel) {
67         $tl=textlib_get_instance();
68         return $tl->convert('"'.str_replace('"',"'",$value).'"','UTF-8','UTF-16LE');
69     } else {
70         return '"'.str_replace('"',"'",$value).'"';
71     }
72 }
75 // Check permissions
76 require_login($course);
78 $context=get_context_instance(CONTEXT_COURSE, $course->id);
79 require_capability('coursereport/completion:view', $context);
81 // Get group mode
82 $group = groups_get_course_group($course, true); // Supposed to verify group
83 if($group === 0 && $course->groupmode == SEPARATEGROUPS) {
84     require_capability('moodle/site:accessallgroups',$context);
85 }
87 /**
88  * Load data
89  */
91 // Get criteria for course
92 $completion = new completion_info($course);
94 if (!$completion->has_criteria()) {
95     print_error('err_nocriteria', 'completion', $CFG->wwwroot.'/course/report.php?id='.$course->id);
96 }
98 // Get criteria and put in correct order
99 $criteria = array();
101 foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_COURSE) as $criterion) {
102     $criteria[] = $criterion;
105 foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_ACTIVITY) as $criterion) {
106     $criteria[] = $criterion;
109 foreach ($completion->get_criteria() as $criterion) {
110     if (!in_array($criterion->criteriatype, array(
111             COMPLETION_CRITERIA_TYPE_COURSE, COMPLETION_CRITERIA_TYPE_ACTIVITY))) {
112         $criteria[] = $criterion;
113     }
116 // Can logged in user mark users as complete?
117 // (if the logged in user has a role defined in the role criteria)
118 $allow_marking = false;
119 $allow_marking_criteria = null;
121 if (!$csv) {
122     // Get role criteria
123     $rcriteria = $completion->get_criteria(COMPLETION_CRITERIA_TYPE_ROLE);
125     if (!empty($rcriteria)) {
127         foreach ($rcriteria as $rcriterion) {
128             $users = get_role_users($rcriterion->role, $context, true);
130             // If logged in user has this role, allow marking complete
131             if ($users && in_array($USER->id, array_keys($users))) {
132                 $allow_marking = true;
133                 $allow_marking_criteria = $rcriterion->id;
134                 break;
135             }
136         }
137     }
140 // Get user data
141 $progress = $completion->get_progress_all(
142     $firstnamesort, $group,
143     $csv ? 0 : COMPLETION_REPORT_PAGE,
144     $csv ? 0 : $start);
147 /**
148  * Setup page header
149  */
150 if ($csv) {
151     header('Content-Disposition: attachment; filename=progress.'.
152         preg_replace('/[^a-z0-9-]/','_',strtolower($course->shortname)).'.csv');
153     // Unicode byte-order mark for Excel
154     if($excel) {
155         header('Content-Type: text/csv; charset=UTF-16LE');
156         print chr(0xFF).chr(0xFE);
157         $sep="\t".chr(0);
158         $line="\n".chr(0);
159     } else {
160         header('Content-Type: text/csv; charset=UTF-8');
161         $sep=",";
162         $line="\n";
163     }
165 } else {
166     // Navigation and header
167     $strcompletion = get_string('completionreport','completion');
169     $PAGE->set_title($strcompletion);
170     $PAGE->set_heading($course->fullname);
171     
172     echo $OUTPUT->header();
174     $PAGE->requires->yui2_lib(
175         array(
176             'yahoo',
177             'dom',
178             'element',
179             'event',
180         )
181     );
183     $PAGE->requires->js('/course/report/completion/textrotate.js');
185     // Handle groups (if enabled)
186     groups_print_course_menu($course, $CFG->wwwroot.'/course/report/progress/?course='.$course->id);
189 // Do we need a paging bar?
190 if($progress->total > COMPLETION_REPORT_PAGE) {
191     $pagingbar='<div class="completion_pagingbar">';
193     if($start>0) {
194         $newstart=$start-COMPLETION_REPORT_PAGE;
195         if($newstart<0) {
196             $newstart=0;
197         }
198         $pagingbar.=link_arrow_left(get_string('previous'),'./?course='.$course->id.
199             ($newstart ? '&amp;start='.$newstart : ''),false,'completion_prev');
200     }
202     $a=new StdClass;
203     $a->from=$start+1;
204     $a->to=$start+COMPLETION_REPORT_PAGE;
205     $a->total=$progress->total;
206     $pagingbar.='<p>'.get_string('reportpage','completion',$a).'</p>';
208     if($start+COMPLETION_REPORT_PAGE < $progress->total) {
209         $pagingbar.=link_arrow_right(get_string('next'),'./?course='.$course->id.
210             '&amp;start='.($start+COMPLETION_REPORT_PAGE),false,'completion_next');
211     }
213     $pagingbar.='</div>';
214 } else {
215     $pagingbar='';
219 /**
220  * Draw table header
221  */
223 // Start of table
224 if(!$csv) {
225     print '<br class="clearer"/>'; // ugh
227     if(count($progress->users)==0) {
228         echo $OUTPUT->box_start('errorbox errorboxcontent boxaligncenter boxwidthnormal');
229         print '<p class="nousers">'.get_string('err_nousers','completion').'</p>';
230         print '<p><a href="'.$CFG->wwwroot.'/course/report.php?id='.$course->id.'">'.get_string('continue').'</a></p>';
231         echo $OUTPUT->box_end();
232         echo $OUTPUT->footer($course);
233         exit;
234     }
236     print $pagingbar;
237     print '<table id="completion-progress" class="generaltable flexible boxaligncenter completionreport" style="text-align: left" cellpadding="5" border="1">';
239     // Print criteria group names
240     print PHP_EOL.'<tr style="vertical-align: top">';
241     print '<th scope="row" class="rowheader">'.get_string('criteriagroup', 'completion').'</th>';
243     $current_group = false;
244     $col_count = 0;
245     for ($i = 0; $i <= count($criteria); $i++) {
247         if (isset($criteria[$i])) {
248             $criterion = $criteria[$i];
250             if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
251                 ++$col_count;
252                 continue;
253             }
254         }
256         // Print header cell
257         if ($col_count) {
258             print '<th scope="col" colspan="'.$col_count.'" class="colheader criteriagroup">'.$current_group->get_type_title().'</th>';
259         }
261         if (isset($criteria[$i])) {
262             // Move to next criteria type
263             $current_group = $criterion;
264             $col_count = 1;
265         }
266     }
268     // Overall course completion status
269     print '<th style="text-align: center;">'.get_string('course').'</th>';
271     print '</tr>';
273     // Print aggregation methods
274     print PHP_EOL.'<tr style="vertical-align: top">';
275     print '<th scope="row" class="rowheader">'.get_string('aggregationmethod', 'completion').'</th>';
277     $current_group = false;
278     $col_count = 0;
279     for ($i = 0; $i <= count($criteria); $i++) {
281         if (isset($criteria[$i])) {
282             $criterion = $criteria[$i];
284             if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
285                 ++$col_count;
286                 continue;
287             }
288         }
290         // Print header cell
291         if ($col_count) {
292             $has_agg = array(
293                 COMPLETION_CRITERIA_TYPE_COURSE,
294                 COMPLETION_CRITERIA_TYPE_ACTIVITY,
295                 COMPLETION_CRITERIA_TYPE_ROLE,
296             );
298             if (in_array($current_group->criteriatype, $has_agg)) {
299                 // Try load a aggregation method
300                 $method = $completion->get_aggregation_method($current_group->criteriatype);
302                 $method = $method == 1 ? 'All' : 'Any';
304             } else {
305                 $method = '-';
306             }
308             print '<th scope="col" colspan="'.$col_count.'" class="colheader aggheader">'.$method.'</th>';
309         }
311         if (isset($criteria[$i])) {
312             // Move to next criteria type
313             $current_group = $criterion;
314             $col_count = 1;
315         }
316     }
318     // Overall course aggregation method
319     print '<th scope="col" class="colheader aggheader aggcriteriacourse">';
321     // Get course aggregation
322     $method = $completion->get_aggregation_method();
324     print $method == 1 ? 'All' : 'Any';
325     print '</th>';
327     print '</tr>';
330     // Print criteria titles
331     if (COMPLETION_REPORT_COL_TITLES) {
333         print PHP_EOL.'<tr>';
334         print '<th scope="row" class="rowheader">'.get_string('criteria', 'completion').'</th>';
336         foreach ($criteria as $criterion) {
337             // Get criteria details
338             $details = $criterion->get_title_detailed();
339             print '<th scope="col" class="colheader criterianame">';
340             print '<span class="completion-criterianame">'.$details.'</span>';
341             print '</th>';
342         }
344         // Overall course completion status
345         print '<th scope="col" class="colheader criterianame">';
347         print '<span class="completion-criterianame">'.get_string('coursecomplete', 'completion').'</span>';
349         print '</th></tr>';
350     }
352     // Print user heading and icons
353     print '<tr>';
355     // User heading / sort option
356     print '<th scope="col" class="completion-sortchoice" style="clear: both;">';
357     if($firstnamesort) {
358         print
359             get_string('firstname').' / <a href="./?course='.$course->id.'">'.
360             get_string('lastname').'</a>';
361     } else {
362         print '<a href="./?course='.$course->id.'&amp;sort=firstname">'.
363             get_string('firstname').'</a> / '.
364             get_string('lastname');
365     }
366     print '</th>';
369     // Print user id number column
370     if($idnumbers) {
371         print '<th>'.get_string('idnumber').'</th>';
372     }
374     ///
375     /// Print criteria icons
376     ///
377     foreach ($criteria as $criterion) {
379         // Generate icon details
380         $icon = '';
381         $iconlink = '';
382         $icontitle = ''; // Required if $iconlink set
383         $iconalt = ''; // Required
384         switch ($criterion->criteriatype) {
386             case COMPLETION_CRITERIA_TYPE_ACTIVITY:
387                 // Load activity
388                 $activity = $criterion->get_mod_instance();
390                 // Display icon
391                 $icon = $OUTPUT->pix_url('icon', $criterion->module).'/icon.gif';
392                 $iconlink = $CFG->wwwroot.'/mod/'.$criterion->module.'/view.php?id='.$activity->id;
393                 $icontitle = $activity->name;
394                 $iconalt = get_string('modulename', $criterion->module);
395                 break;
397             case COMPLETION_CRITERIA_TYPE_COURSE:
398                 // Load course
399                 $crs = $DB->get_record('course', array('id' => $criterion->courseinstance));
401                 // Display icon
402                 $iconlink = $CFG->wwwroot.'/course/view.php?id='.$criterion->courseinstance;
403                 $icontitle = $crs->fullname;
404                 $iconalt = $crs->shortname;
405                 break;
407             case COMPLETION_CRITERIA_TYPE_ROLE:
408                 // Load role
409                 $role = $DB->get_record('role', array('id' => $criterion->role));
411                 // Display icon
412                 $iconalt = $role->name;
413                 break;
414         }
416         // Print icon and cell
417         print '<th class="criteriaicon">';
419         // Create icon if not supplied
420         if (!$icon) {
421             $icon = $OUTPUT->pix_url('i/'.$COMPLETION_CRITERIA_TYPES[$criterion->criteriatype].'.gif');
422         }
424         print ($iconlink ? '<a href="'.$iconlink.'" title="'.$icontitle.'">' : '');
425         print '<img src="'.$icon.'" class="icon" alt="'.$iconalt.'" '.(!$iconlink ? 'title="'.$iconalt.'"' : '').' />';
426         print ($iconlink ? '</a>' : '');
428         print '</th>';
429     }
431     // Overall course completion status
432     print '<th class="criteriaicon">';
433     print '<img src="'.$OUTPUT->pix_url('i/course.gif').'" class="icon" alt="Course" title="Course Complete" />';
434     print '</th>';
436     print '</tr>';
439 } else {
440     // TODO
441     if($idnumbers) {
442         print $sep;
443     }
447 ///
448 /// Display a row for each user
449 ///
450 foreach($progress->users as $user) {
452     // User name
453     if($csv) {
454         print csv_quote(fullname($user));
455         if($idnumbers) {
456             print $sep.csv_quote($user->idnumber);
457         }
458     } else {
459         print PHP_EOL.'<tr id="user-'.$user->id.'">';
461         print '<th scope="row"><a href="'.$CFG->wwwroot.'/user/view.php?id='.
462             $user->id.'&amp;course='.$course->id.'">'.fullname($user).'</a></th>';
463         if($idnumbers) {
464             print '<td>'.htmlspecialchars($user->idnumber).'</td>';
465         }
466     }
468     // Progress for each course completion criteria
469     foreach ($criteria as $criterion) {
471         // Handle activity completion differently
472         if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
474             // Load activity
475             $mod = $criterion->get_mod_instance();
476             $activity = $DB->get_record('course_modules', array('id' => $criterion->moduleinstance));
477             $activity->name = $mod->name;
480             // Get progress information and state
481             if(array_key_exists($activity->id,$user->progress)) {
482                 $thisprogress=$user->progress[$activity->id];
483                 $state=$thisprogress->completionstate;
484                 $date=userdate($thisprogress->timemodified);
485             } else {
486                 $state=COMPLETION_INCOMPLETE;
487                 $date='';
488             }
490             $criteria_completion = $completion->get_user_completion($user->id, $criterion);
492             // Work out how it corresponds to an icon
493             switch($state) {
494                 case COMPLETION_INCOMPLETE : $completiontype='n'; break;
495                 case COMPLETION_COMPLETE : $completiontype='y'; break;
496                 case COMPLETION_COMPLETE_PASS : $completiontype='pass'; break;
497                 case COMPLETION_COMPLETE_FAIL : $completiontype='fail'; break;
498             }
500             $completionicon='completion-'.
501                 ($activity->completion==COMPLETION_TRACKING_AUTOMATIC ? 'auto' : 'manual').
502                 '-'.$completiontype;
504             $describe=get_string('completion-alt-auto-'.$completiontype,'completion');
505             $a=new StdClass;
506             $a->state=$describe;
507             $a->date=$date;
508             $a->user=fullname($user);
509             $a->activity=strip_tags($activity->name);
510             $fulldescribe=get_string('progress-title','completion',$a);
512             if($csv) {
513                 print $sep.csv_quote($describe).$sep.csv_quote($date);
514             } else {
515                 print '<td class="completion-progresscell">';
517                 print '<img src="'.$OUTPUT->pix_url('i/'.$completionicon).
518                       '" alt="'.$describe.'" class="icon" title="'.$fulldescribe.'" />';
520                 print '</td>';
521             }
523             continue;
524         }
526         // Handle all other criteria
527         $criteria_completion = $completion->get_user_completion($user->id, $criterion);
528         $is_complete = $criteria_completion->is_complete();
530         $completiontype = $is_complete ? 'y' : 'n';
531         $completionicon = 'completion-auto-'.$completiontype;
533         $describe = get_string('completion-alt-auto-'.$completiontype, 'completion');
535         $a = new Object();
536         $a->state    = $describe;
537         $a->date     = $is_complete ? userdate($criteria_completion->timecompleted) : '';
538         $a->user     = fullname($user);
539         $a->activity = strip_tags($criterion->get_title());
540         $fulldescribe = get_string('progress-title', 'completion', $a);
542         if ($csv) {
543             print $sep.csv_quote($describe);
544         } else {
546             if ($allow_marking_criteria === $criterion->id) {
547                 $describe = get_string('completion-alt-auto-'.$completiontype,'completion');
549                 print '<td class="completion-progresscell">'.
550                     '<a href="'.$CFG->wwwroot.'/course/togglecompletion.php?user='.$user->id.'&course='.$course->id.'&rolec='.$allow_marking_criteria.'">'.
551                     '<img src="'.$OUTPUT->pix_url('i/completion-manual-'.($is_complete ? 'y' : 'n')).
552                     '" alt="'.$describe.'" class="icon" title="Mark as complete" /></a></td>';
553             } else {
554                 print '<td class="completion-progresscell">'.
555                     '<img src="'.$OUTPUT->pix_url('i/'.$completionicon).
556                     '" alt="'.$describe.'" class="icon" title="'.$fulldescribe.'" /></td>';
557             }
558         }
559     }
561     // Handle overall course completion
563     // Load course completion
564     $params = array(
565         'userid'    => $user->id,
566         'course'    => $course->id
567     );
569     $ccompletion = new completion_completion($params);
570     $completiontype =  $ccompletion->is_complete() ? 'y' : 'n';
572     $describe = get_string('completion-alt-auto-'.$completiontype, 'completion');
574     $a = new StdClass;
575     $a->state    = $describe;
576     $a->date     = '';
577     $a->user     = fullname($user);
578     $a->activity = strip_tags(get_string('coursecomplete', 'completion'));
579     $fulldescribe = get_string('progress-title', 'completion', $a);
581     if ($csv) {
582         print $sep.csv_quote($describe);
583     } else {
585         print '<td class="completion-progresscell">';
587         // Display course completion status icon
588         print '<img src="'.$OUTPUT->pix_url('i/completion-auto-'.$completiontype).
589                '" alt="'.$describe.'" class="icon" title="'.$fulldescribe.'" />';
591         print '</td>';
592     }
594     if($csv) {
595         print $line;
596     } else {
597         print '</tr>';
598     }
601 if($csv) {
602     exit;
604 print '</table>';
605 print $pagingbar;
607 print '<ul class="progress-actions"><li><a href="index.php?course='.$course->id.
608     '&amp;format=csv">'.get_string('csvdownload','completion').'</a></li>
609     <li><a href="index.php?course='.$course->id.'&amp;format=excelcsv">'.
610     get_string('excelcsvdownload','completion').'</a></li></ul>';
612 echo $OUTPUT->footer($course);