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