See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Activity progress reports 19 * 20 * @package report 21 * @subpackage progress 22 * @copyright 2008 Sam Marshall 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 use core\report_helper; 27 use \report_progress\local\helper; 28 29 require('../../config.php'); 30 require_once($CFG->libdir . '/completionlib.php'); 31 32 // Get course 33 $id = required_param('course',PARAM_INT); 34 $course = $DB->get_record('course',array('id'=>$id)); 35 if (!$course) { 36 throw new \moodle_exception('invalidcourseid'); 37 } 38 $context = context_course::instance($course->id); 39 40 // Sort (default lastname, optionally firstname) 41 $sort = optional_param('sort','',PARAM_ALPHA); 42 $firstnamesort = $sort == 'firstname'; 43 44 // CSV format 45 $format = optional_param('format','',PARAM_ALPHA); 46 $excel = $format == 'excelcsv'; 47 $csv = $format == 'csv' || $excel; 48 49 // Paging, sorting and filtering. 50 $page = optional_param('page', 0, PARAM_INT); 51 $sifirst = optional_param('sifirst', 'all', PARAM_NOTAGS); 52 $silast = optional_param('silast', 'all', PARAM_NOTAGS); 53 $groupid = optional_param('group', 0, PARAM_INT); 54 $activityinclude = optional_param('activityinclude', 'all', PARAM_TEXT); 55 $activityorder = optional_param('activityorder', 'orderincourse', PARAM_TEXT); 56 57 // Whether to show extra user identity information 58 $userfields = \core_user\fields::for_identity($context); 59 $extrafields = $userfields->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]); 60 $leftcols = 1 + count($extrafields); 61 62 function csv_quote($value) { 63 global $excel; 64 if ($excel) { 65 return core_text::convert('"'.str_replace('"',"'",$value).'"','UTF-8','UTF-16LE'); 66 } else { 67 return '"'.str_replace('"',"'",$value).'"'; 68 } 69 } 70 71 $url = new moodle_url('/report/progress/index.php', array('course'=>$id)); 72 if ($sort !== '') { 73 $url->param('sort', $sort); 74 } 75 if ($format !== '') { 76 $url->param('format', $format); 77 } 78 if ($page !== 0) { 79 $url->param('page', $page); 80 } 81 if ($sifirst !== 'all') { 82 $url->param('sifirst', $sifirst); 83 } 84 if ($silast !== 'all') { 85 $url->param('silast', $silast); 86 } 87 if ($groupid !== 0) { 88 $url->param('group', $groupid); 89 } 90 if ($activityinclude !== '') { 91 $url->param('activityinclude', $activityinclude); 92 } 93 if ($activityorder !== '') { 94 $url->param('activityorder', $activityorder); 95 } 96 97 $PAGE->set_url($url); 98 $PAGE->set_pagelayout('report'); 99 100 require_login($course); 101 102 // Check basic permission 103 require_capability('report/progress:view',$context); 104 105 // Get group mode 106 $group = groups_get_course_group($course,true); // Supposed to verify group 107 if ($group===0 && $course->groupmode==SEPARATEGROUPS) { 108 require_capability('moodle/site:accessallgroups',$context); 109 } 110 111 // Get data on activities and progress of all users, and give error if we've 112 // nothing to display (no users or no activities). 113 $completion = new completion_info($course); 114 list($activitytypes, $activities) = helper::get_activities_to_show($completion, $activityinclude, $activityorder); 115 $output = $PAGE->get_renderer('report_progress'); 116 117 if ($sifirst !== 'all') { 118 set_user_preference('ifirst', $sifirst); 119 } 120 if ($silast !== 'all') { 121 set_user_preference('ilast', $silast); 122 } 123 124 if (!empty($USER->preference['ifirst'])) { 125 $sifirst = $USER->preference['ifirst']; 126 } else { 127 $sifirst = 'all'; 128 } 129 130 if (!empty($USER->preference['ilast'])) { 131 $silast = $USER->preference['ilast']; 132 } else { 133 $silast = 'all'; 134 } 135 136 // Generate where clause 137 $where = array(); 138 $where_params = array(); 139 140 if ($sifirst !== 'all') { 141 $where[] = $DB->sql_like('u.firstname', ':sifirst', false, false); 142 $where_params['sifirst'] = $sifirst.'%'; 143 } 144 145 if ($silast !== 'all') { 146 $where[] = $DB->sql_like('u.lastname', ':silast', false, false); 147 $where_params['silast'] = $silast.'%'; 148 } 149 150 // Get user match count 151 $total = $completion->get_num_tracked_users(implode(' AND ', $where), $where_params, $group); 152 153 // Total user count 154 $grandtotal = $completion->get_num_tracked_users('', array(), $group); 155 156 // Get user data 157 $progress = array(); 158 159 if ($total) { 160 $progress = $completion->get_progress_all( 161 implode(' AND ', $where), 162 $where_params, 163 $group, 164 $firstnamesort ? 'u.firstname ASC, u.lastname ASC' : 'u.lastname ASC, u.firstname ASC', 165 $csv ? 0 : helper::COMPLETION_REPORT_PAGE, 166 $csv ? 0 : $page * helper::COMPLETION_REPORT_PAGE, 167 $context 168 ); 169 } 170 171 if ($csv && $grandtotal && count($activities)>0) { // Only show CSV if there are some users/actvs 172 173 $shortname = format_string($course->shortname, true, array('context' => $context)); 174 header('Content-Disposition: attachment; filename=progress.'. 175 preg_replace('/[^a-z0-9-]/','_',core_text::strtolower(strip_tags($shortname))).'.csv'); 176 // Unicode byte-order mark for Excel 177 if ($excel) { 178 header('Content-Type: text/csv; charset=UTF-16LE'); 179 print chr(0xFF).chr(0xFE); 180 $sep="\t".chr(0); 181 $line="\n".chr(0); 182 } else { 183 header('Content-Type: text/csv; charset=UTF-8'); 184 $sep=","; 185 $line="\n"; 186 } 187 } else { 188 189 // Navigation and header 190 $strreports = get_string("reports"); 191 $strcompletion = get_string('activitycompletion', 'completion'); 192 193 $PAGE->set_title($strcompletion); 194 $PAGE->set_heading($course->fullname); 195 echo $OUTPUT->header(); 196 197 // Print the selected dropdown. 198 $pluginname = get_string('pluginname', 'report_progress'); 199 report_helper::print_report_selector($pluginname); 200 $PAGE->requires->js_call_amd('report_progress/completion_override', 'init', [fullname($USER)]); 201 202 // Handle groups (if enabled). 203 echo $output->render_groups_select($url, $course); 204 205 // Display include activity filter. 206 echo $output->render_include_activity_select($url, $activitytypes, $activityinclude); 207 208 // Display activity order options. 209 echo $output->render_activity_order_select($url, $activityorder); 210 211 } 212 213 if (count($activities)==0) { 214 echo $OUTPUT->container(get_string('err_noactivities', 'completion'), 'errorbox errorboxcontent'); 215 echo $OUTPUT->footer(); 216 exit; 217 } 218 219 // If no users in this course what-so-ever 220 if (!$grandtotal) { 221 echo $OUTPUT->container(get_string('err_nousers', 'completion'), 'errorbox errorboxcontent'); 222 echo $OUTPUT->footer(); 223 exit; 224 } 225 226 // Build link for paging 227 $link = $CFG->wwwroot.'/report/progress/?course='.$course->id; 228 if (strlen($sort)) { 229 $link .= '&sort='.$sort; 230 } 231 $link .= '&start='; 232 233 $pagingbar = ''; 234 235 // Initials bar. 236 $prefixfirst = 'sifirst'; 237 $prefixlast = 'silast'; 238 239 // The URL used in the initials bar should reset the 'start' parameter. 240 $initialsbarurl = fullclone($url); 241 $initialsbarurl->remove_params('page'); 242 243 $pagingbar .= $OUTPUT->initials_bar($sifirst, 'firstinitial mt-2', get_string('firstname'), $prefixfirst, $initialsbarurl); 244 $pagingbar .= $OUTPUT->initials_bar($silast, 'lastinitial', get_string('lastname'), $prefixlast, $initialsbarurl); 245 $pagingbar .= $OUTPUT->paging_bar($total, $page, helper::COMPLETION_REPORT_PAGE, $url); 246 247 // Okay, let's draw the table of progress info, 248 249 // Start of table 250 if (!$csv) { 251 print '<br class="clearer"/>'; // ugh 252 253 print $pagingbar; 254 255 if (!$total) { 256 echo $OUTPUT->heading(get_string('nothingtodisplay')); 257 echo $OUTPUT->footer(); 258 exit; 259 } 260 261 print '<div id="completion-progress-wrapper" class="no-overflow">'; 262 print '<table id="completion-progress" class="generaltable flexible boxaligncenter"><thead><tr style="vertical-align:top">'; 263 264 // User heading / sort option 265 print '<th scope="col" class="completion-sortchoice">'; 266 267 $sorturl = fullclone($url); 268 if ($firstnamesort) { 269 $sorturl->param('sort', 'lastname'); 270 $sortlink = html_writer::link($sorturl, get_string('lastname')); 271 print 272 get_string('firstname') . " / $sortlink"; 273 } else { 274 $sorturl->param('sort', 'firstname'); 275 $sortlink = html_writer::link($sorturl, get_string('firstname')); 276 print "$sortlink / " . get_string('lastname'); 277 } 278 print '</th>'; 279 280 // Print user identity columns 281 foreach ($extrafields as $field) { 282 echo '<th scope="col" class="completion-identifyfield">' . 283 \core_user\fields::get_display_name($field) . '</th>'; 284 } 285 } else { 286 foreach ($extrafields as $field) { 287 echo $sep . csv_quote(\core_user\fields::get_display_name($field)); 288 } 289 } 290 291 // Activities 292 $formattedactivities = array(); 293 foreach($activities as $activity) { 294 $datepassed = $activity->completionexpected && $activity->completionexpected <= time(); 295 $datepassedclass = $datepassed ? 'completion-expired' : ''; 296 297 if ($activity->completionexpected) { 298 if ($csv) { 299 $datetext = userdate($activity->completionexpected, "%F %T"); 300 } else { 301 $datetext = userdate($activity->completionexpected, get_string('strftimedate', 'langconfig')); 302 } 303 } else { 304 $datetext=''; 305 } 306 307 // Some names (labels) come URL-encoded and can be very long, so shorten them 308 $displayname = format_string($activity->name, true, array('context' => $activity->context)); 309 310 if ($csv) { 311 print $sep.csv_quote($displayname).$sep.csv_quote($datetext); 312 } else { 313 $shortenedname = shorten_text($displayname); 314 print '<th scope="col" class="completion-header '.$datepassedclass.'">'. 315 '<a href="'.$CFG->wwwroot.'/mod/'.$activity->modname. 316 '/view.php?id='.$activity->id.'" title="' . s($displayname) . '">'. 317 '<div class="rotated-text-container"><span class="rotated-text">'.$shortenedname.'</span></div>'. 318 '<div class="modicon">'. 319 $OUTPUT->image_icon('monologo', get_string('modulename', $activity->modname), $activity->modname) . 320 '</div>'. 321 '</a>'; 322 if ($activity->completionexpected) { 323 print '<div class="completion-expected"><span>'.$datetext.'</span></div>'; 324 } 325 print '</th>'; 326 } 327 $formattedactivities[$activity->id] = (object)array( 328 'datepassedclass' => $datepassedclass, 329 'displayname' => $displayname, 330 ); 331 } 332 333 if ($csv) { 334 print $line; 335 } else { 336 print '</tr></thead><tbody>'; 337 } 338 339 // Row for each user 340 foreach($progress as $user) { 341 // User name 342 if ($csv) { 343 print csv_quote(fullname($user, has_capability('moodle/site:viewfullnames', $context))); 344 foreach ($extrafields as $field) { 345 echo $sep . csv_quote($user->{$field}); 346 } 347 } else { 348 print '<tr><th scope="row"><a href="' . $CFG->wwwroot . '/user/view.php?id=' . 349 $user->id . '&course=' . $course->id . '">' . 350 fullname($user, has_capability('moodle/site:viewfullnames', $context)) . '</a></th>'; 351 foreach ($extrafields as $field) { 352 echo '<td>' . s($user->{$field}) . '</td>'; 353 } 354 } 355 356 // Progress for each activity 357 foreach($activities as $activity) { 358 359 // Get progress information and state 360 if (array_key_exists($activity->id, $user->progress)) { 361 $thisprogress = $user->progress[$activity->id]; 362 $state = $thisprogress->completionstate; 363 $overrideby = $thisprogress->overrideby; 364 $date = userdate($thisprogress->timemodified); 365 } else { 366 $state = COMPLETION_INCOMPLETE; 367 $overrideby = 0; 368 $date = ''; 369 } 370 371 // Work out how it corresponds to an icon 372 switch($state) { 373 case COMPLETION_INCOMPLETE : 374 $completiontype = 'n'.($overrideby ? '-override' : ''); 375 break; 376 case COMPLETION_COMPLETE : 377 $completiontype = 'y'.($overrideby ? '-override' : ''); 378 break; 379 case COMPLETION_COMPLETE_PASS : 380 $completiontype = 'pass'; 381 break; 382 case COMPLETION_COMPLETE_FAIL : 383 $completiontype = 'fail'; 384 break; 385 } 386 $completiontrackingstring = $activity->completion == COMPLETION_TRACKING_AUTOMATIC ? 'auto' : 'manual'; 387 $completionicon = 'completion-' . $completiontrackingstring. '-' . $completiontype; 388 389 if ($overrideby) { 390 $overridebyuser = \core_user::get_user($overrideby, '*', MUST_EXIST); 391 $describe = get_string('completion-' . $completiontype, 'completion', fullname($overridebyuser)); 392 } else { 393 $describe = get_string('completion-' . $completiontype, 'completion'); 394 } 395 $a=new StdClass; 396 $a->state=$describe; 397 $a->date=$date; 398 $a->user = fullname($user, has_capability('moodle/site:viewfullnames', $context)); 399 $a->activity = $formattedactivities[$activity->id]->displayname; 400 $fulldescribe=get_string('progress-title','completion',$a); 401 402 if ($csv) { 403 if ($date != '') { 404 $date = userdate($thisprogress->timemodified, "%F %T"); 405 } 406 print $sep.csv_quote($describe).$sep.csv_quote($date); 407 } else { 408 $celltext = $OUTPUT->pix_icon('i/' . $completionicon, s($fulldescribe)); 409 if (has_capability('moodle/course:overridecompletion', $context) && 410 $state != COMPLETION_COMPLETE_PASS && $state != COMPLETION_COMPLETE_FAIL) { 411 $newstate = ($state == COMPLETION_COMPLETE) ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; 412 $changecompl = $user->id . '-' . $activity->id . '-' . $newstate; 413 $url = new moodle_url($PAGE->url, ['sesskey' => sesskey()]); 414 $celltext = html_writer::link($url, $celltext, array('class' => 'changecompl', 'data-changecompl' => $changecompl, 415 'data-activityname' => $a->activity, 416 'data-userfullname' => $a->user, 417 'data-completiontracking' => $completiontrackingstring, 418 'role' => 'button')); 419 } 420 print '<td class="completion-progresscell '.$formattedactivities[$activity->id]->datepassedclass.'">'. 421 $celltext . '</td>'; 422 } 423 } 424 425 if ($csv) { 426 print $line; 427 } else { 428 print '</tr>'; 429 } 430 } 431 432 if ($csv) { 433 exit; 434 } 435 print '</tbody></table>'; 436 print '</div>'; 437 438 echo $output->render_download_buttons($url); 439 440 echo $OUTPUT->footer(); 441
title
Description
Body
title
Description
Body
title
Description
Body
title
Body