Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 print_error('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 // TODO Does not support custom user profile fields (MDL-70456). 59 $extrafields = \core_user\fields::get_identity_fields($context, false); 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 report_helper::save_selected_report($id, $url); 160 161 if ($total) { 162 $progress = $completion->get_progress_all( 163 implode(' AND ', $where), 164 $where_params, 165 $group, 166 $firstnamesort ? 'u.firstname ASC, u.lastname ASC' : 'u.lastname ASC, u.firstname ASC', 167 $csv ? 0 : helper::COMPLETION_REPORT_PAGE, 168 $csv ? 0 : $page * helper::COMPLETION_REPORT_PAGE, 169 $context 170 ); 171 } 172 173 if ($csv && $grandtotal && count($activities)>0) { // Only show CSV if there are some users/actvs 174 175 $shortname = format_string($course->shortname, true, array('context' => $context)); 176 header('Content-Disposition: attachment; filename=progress.'. 177 preg_replace('/[^a-z0-9-]/','_',core_text::strtolower(strip_tags($shortname))).'.csv'); 178 // Unicode byte-order mark for Excel 179 if ($excel) { 180 header('Content-Type: text/csv; charset=UTF-16LE'); 181 print chr(0xFF).chr(0xFE); 182 $sep="\t".chr(0); 183 $line="\n".chr(0); 184 } else { 185 header('Content-Type: text/csv; charset=UTF-8'); 186 $sep=","; 187 $line="\n"; 188 } 189 } else { 190 191 // Navigation and header 192 $strreports = get_string("reports"); 193 $strcompletion = get_string('activitycompletion', 'completion'); 194 195 $PAGE->set_title($strcompletion); 196 $PAGE->set_heading($course->fullname); 197 echo $OUTPUT->header(); 198 199 // Print the selected dropdown. 200 $pluginname = get_string('pluginname', 'report_progress'); 201 report_helper::print_report_selector($pluginname); 202 $PAGE->requires->js_call_amd('report_progress/completion_override', 'init', [fullname($USER)]); 203 204 // Handle groups (if enabled). 205 echo $output->render_groups_select($url, $course); 206 207 // Display include activity filter. 208 echo $output->render_include_activity_select($url, $activitytypes, $activityinclude); 209 210 // Display activity order options. 211 echo $output->render_activity_order_select($url, $activityorder); 212 213 } 214 215 if (count($activities)==0) { 216 echo $OUTPUT->container(get_string('err_noactivities', 'completion'), 'errorbox errorboxcontent'); 217 echo $OUTPUT->footer(); 218 exit; 219 } 220 221 // If no users in this course what-so-ever 222 if (!$grandtotal) { 223 echo $OUTPUT->container(get_string('err_nousers', 'completion'), 'errorbox errorboxcontent'); 224 echo $OUTPUT->footer(); 225 exit; 226 } 227 228 // Build link for paging 229 $link = $CFG->wwwroot.'/report/progress/?course='.$course->id; 230 if (strlen($sort)) { 231 $link .= '&sort='.$sort; 232 } 233 $link .= '&start='; 234 235 $pagingbar = ''; 236 237 // Initials bar. 238 $prefixfirst = 'sifirst'; 239 $prefixlast = 'silast'; 240 241 // The URL used in the initials bar should reset the 'start' parameter. 242 $initialsbarurl = fullclone($url); 243 $initialsbarurl->remove_params('page'); 244 245 $pagingbar .= $OUTPUT->initials_bar($sifirst, 'firstinitial mt-2', get_string('firstname'), $prefixfirst, $initialsbarurl); 246 $pagingbar .= $OUTPUT->initials_bar($silast, 'lastinitial', get_string('lastname'), $prefixlast, $initialsbarurl); 247 $pagingbar .= $OUTPUT->paging_bar($total, $page, helper::COMPLETION_REPORT_PAGE, $url); 248 249 // Okay, let's draw the table of progress info, 250 251 // Start of table 252 if (!$csv) { 253 print '<br class="clearer"/>'; // ugh 254 255 print $pagingbar; 256 257 if (!$total) { 258 echo $OUTPUT->heading(get_string('nothingtodisplay')); 259 echo $OUTPUT->footer(); 260 exit; 261 } 262 263 print '<div id="completion-progress-wrapper" class="no-overflow">'; 264 print '<table id="completion-progress" class="generaltable flexible boxaligncenter" style="text-align:left"><thead><tr style="vertical-align:top">'; 265 266 // User heading / sort option 267 print '<th scope="col" class="completion-sortchoice">'; 268 269 $sorturl = fullclone($url); 270 if ($firstnamesort) { 271 $sorturl->param('sort', 'lastname'); 272 $sortlink = html_writer::link($sorturl, get_string('lastname')); 273 print 274 get_string('firstname') . " / $sortlink"; 275 } else { 276 $sorturl->param('sort', 'firstname'); 277 $sortlink = html_writer::link($sorturl, get_string('firstname')); 278 print "$sortlink / " . get_string('lastname'); 279 } 280 print '</th>'; 281 282 // Print user identity columns 283 foreach ($extrafields as $field) { 284 echo '<th scope="col" class="completion-identifyfield">' . 285 \core_user\fields::get_display_name($field) . '</th>'; 286 } 287 } else { 288 foreach ($extrafields as $field) { 289 echo $sep . csv_quote(\core_user\fields::get_display_name($field)); 290 } 291 } 292 293 // Activities 294 $formattedactivities = array(); 295 foreach($activities as $activity) { 296 $datepassed = $activity->completionexpected && $activity->completionexpected <= time(); 297 $datepassedclass = $datepassed ? 'completion-expired' : ''; 298 299 if ($activity->completionexpected) { 300 if ($csv) { 301 $datetext = userdate($activity->completionexpected, "%F %T"); 302 } else { 303 $datetext = userdate($activity->completionexpected, get_string('strftimedate', 'langconfig')); 304 } 305 } else { 306 $datetext=''; 307 } 308 309 // Some names (labels) come URL-encoded and can be very long, so shorten them 310 $displayname = format_string($activity->name, true, array('context' => $activity->context)); 311 312 if ($csv) { 313 print $sep.csv_quote($displayname).$sep.csv_quote($datetext); 314 } else { 315 $shortenedname = shorten_text($displayname); 316 print '<th scope="col" class="completion-header '.$datepassedclass.'">'. 317 '<a href="'.$CFG->wwwroot.'/mod/'.$activity->modname. 318 '/view.php?id='.$activity->id.'" title="' . s($displayname) . '">'. 319 '<div class="rotated-text-container"><span class="rotated-text">'.$shortenedname.'</span></div>'. 320 '<div class="modicon">'. 321 $OUTPUT->image_icon('icon', get_string('modulename', $activity->modname), $activity->modname) . 322 '</div>'. 323 '</a>'; 324 if ($activity->completionexpected) { 325 print '<div class="completion-expected"><span>'.$datetext.'</span></div>'; 326 } 327 print '</th>'; 328 } 329 $formattedactivities[$activity->id] = (object)array( 330 'datepassedclass' => $datepassedclass, 331 'displayname' => $displayname, 332 ); 333 } 334 335 if ($csv) { 336 print $line; 337 } else { 338 print '</tr></thead><tbody>'; 339 } 340 341 // Row for each user 342 foreach($progress as $user) { 343 // User name 344 if ($csv) { 345 print csv_quote(fullname($user, has_capability('moodle/site:viewfullnames', $context))); 346 foreach ($extrafields as $field) { 347 echo $sep . csv_quote($user->{$field}); 348 } 349 } else { 350 print '<tr><th scope="row"><a href="' . $CFG->wwwroot . '/user/view.php?id=' . 351 $user->id . '&course=' . $course->id . '">' . 352 fullname($user, has_capability('moodle/site:viewfullnames', $context)) . '</a></th>'; 353 foreach ($extrafields as $field) { 354 echo '<td>' . s($user->{$field}) . '</td>'; 355 } 356 } 357 358 // Progress for each activity 359 foreach($activities as $activity) { 360 361 // Get progress information and state 362 if (array_key_exists($activity->id, $user->progress)) { 363 $thisprogress = $user->progress[$activity->id]; 364 $state = $thisprogress->completionstate; 365 $overrideby = $thisprogress->overrideby; 366 $date = userdate($thisprogress->timemodified); 367 } else { 368 $state = COMPLETION_INCOMPLETE; 369 $overrideby = 0; 370 $date = ''; 371 } 372 373 // Work out how it corresponds to an icon 374 switch($state) { 375 case COMPLETION_INCOMPLETE : 376 $completiontype = 'n'.($overrideby ? '-override' : ''); 377 break; 378 case COMPLETION_COMPLETE : 379 $completiontype = 'y'.($overrideby ? '-override' : ''); 380 break; 381 case COMPLETION_COMPLETE_PASS : 382 $completiontype = 'pass'; 383 break; 384 case COMPLETION_COMPLETE_FAIL : 385 $completiontype = 'fail'; 386 break; 387 } 388 $completiontrackingstring = $activity->completion == COMPLETION_TRACKING_AUTOMATIC ? 'auto' : 'manual'; 389 $completionicon = 'completion-' . $completiontrackingstring. '-' . $completiontype; 390 391 if ($overrideby) { 392 $overridebyuser = \core_user::get_user($overrideby, '*', MUST_EXIST); 393 $describe = get_string('completion-' . $completiontype, 'completion', fullname($overridebyuser)); 394 } else { 395 $describe = get_string('completion-' . $completiontype, 'completion'); 396 } 397 $a=new StdClass; 398 $a->state=$describe; 399 $a->date=$date; 400 $a->user = fullname($user, has_capability('moodle/site:viewfullnames', $context)); 401 $a->activity = $formattedactivities[$activity->id]->displayname; 402 $fulldescribe=get_string('progress-title','completion',$a); 403 404 if ($csv) { 405 if ($date != '') { 406 $date = userdate($thisprogress->timemodified, "%F %T"); 407 } 408 print $sep.csv_quote($describe).$sep.csv_quote($date); 409 } else { 410 $celltext = $OUTPUT->pix_icon('i/' . $completionicon, s($fulldescribe)); 411 if (has_capability('moodle/course:overridecompletion', $context) && 412 $state != COMPLETION_COMPLETE_PASS && $state != COMPLETION_COMPLETE_FAIL) { 413 $newstate = ($state == COMPLETION_COMPLETE) ? COMPLETION_INCOMPLETE : COMPLETION_COMPLETE; 414 $changecompl = $user->id . '-' . $activity->id . '-' . $newstate; 415 $url = new moodle_url($PAGE->url, ['sesskey' => sesskey()]); 416 $celltext = html_writer::link($url, $celltext, array('class' => 'changecompl', 'data-changecompl' => $changecompl, 417 'data-activityname' => $a->activity, 418 'data-userfullname' => $a->user, 419 'data-completiontracking' => $completiontrackingstring, 420 'role' => 'button')); 421 } 422 print '<td class="completion-progresscell '.$formattedactivities[$activity->id]->datepassedclass.'">'. 423 $celltext . '</td>'; 424 } 425 } 426 427 if ($csv) { 428 print $line; 429 } else { 430 print '</tr>'; 431 } 432 } 433 434 if ($csv) { 435 exit; 436 } 437 print '</tbody></table>'; 438 print '</div>'; 439 440 echo $output->render_download_buttons($url); 441 442 echo $OUTPUT->footer(); 443
title
Description
Body
title
Description
Body
title
Description
Body
title
Body