Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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   * Course completion progress report
  19   *
  20   * @package    report
  21   * @subpackage completion
  22   * @copyright  2009 Catalyst IT Ltd
  23   * @author     Aaron Barnes <aaronb@catalyst.net.nz>
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  use core\report_helper;
  28  
  29  require_once(__DIR__.'/../../config.php');
  30  require_once("{$CFG->libdir}/completionlib.php");
  31  
  32  /**
  33   * Configuration
  34   */
  35  define('COMPLETION_REPORT_PAGE',        25);
  36  define('COMPLETION_REPORT_COL_TITLES',  true);
  37  
  38  /*
  39   * Setup page, check permissions
  40   */
  41  
  42  // Get course
  43  $courseid = required_param('course', PARAM_INT);
  44  $format = optional_param('format','',PARAM_ALPHA);
  45  $sort = optional_param('sort','',PARAM_ALPHA);
  46  $edituser = optional_param('edituser', 0, PARAM_INT);
  47  
  48  
  49  $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
  50  $context = context_course::instance($course->id);
  51  
  52  $url = new moodle_url('/report/completion/index.php', array('course'=>$course->id));
  53  $PAGE->set_url($url);
  54  $PAGE->set_pagelayout('report');
  55  
  56  $firstnamesort = ($sort == 'firstname');
  57  $excel = ($format == 'excelcsv');
  58  $csv = ($format == 'csv' || $excel);
  59  
  60  // Load CSV library
  61  if ($csv) {
  62      require_once("{$CFG->libdir}/csvlib.class.php");
  63  }
  64  
  65  // Paging
  66  $start   = optional_param('start', 0, PARAM_INT);
  67  $sifirst = optional_param('sifirst', 'all', PARAM_NOTAGS);
  68  $silast  = optional_param('silast', 'all', PARAM_NOTAGS);
  69  
  70  // Whether to show extra user identity information
  71  // TODO Does not support custom user profile fields (MDL-70456).
  72  $extrafields = \core_user\fields::get_identity_fields($context, false);
  73  $leftcols = 1 + count($extrafields);
  74  
  75  // Check permissions
  76  require_login($course);
  77  
  78  require_capability('report/completion:view', $context);
  79  
  80  // Get group mode
  81  $group = groups_get_course_group($course, true); // Supposed to verify group
  82  if ($group === 0 && $course->groupmode == SEPARATEGROUPS) {
  83      require_capability('moodle/site:accessallgroups',$context);
  84  }
  85  
  86  /**
  87   * Load data
  88   */
  89  
  90  // Retrieve course_module data for all modules in the course
  91  $modinfo = get_fast_modinfo($course);
  92  
  93  // Get criteria for course
  94  $completion = new completion_info($course);
  95  
  96  if (!$completion->has_criteria()) {
  97      print_error('nocriteriaset', 'completion', $CFG->wwwroot.'/course/report.php?id='.$course->id);
  98  }
  99  
 100  // Get criteria and put in correct order
 101  $criteria = array();
 102  
 103  foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_COURSE) as $criterion) {
 104      $criteria[] = $criterion;
 105  }
 106  
 107  foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_ACTIVITY) as $criterion) {
 108      $criteria[] = $criterion;
 109  }
 110  
 111  foreach ($completion->get_criteria() as $criterion) {
 112      if (!in_array($criterion->criteriatype, array(
 113              COMPLETION_CRITERIA_TYPE_COURSE, COMPLETION_CRITERIA_TYPE_ACTIVITY))) {
 114          $criteria[] = $criterion;
 115      }
 116  }
 117  
 118  report_helper::save_selected_report($courseid, $url);
 119  
 120  // Can logged in user mark users as complete?
 121  // (if the logged in user has a role defined in the role criteria)
 122  $allow_marking = false;
 123  $allow_marking_criteria = null;
 124  
 125  if (!$csv) {
 126      // Get role criteria
 127      $rcriteria = $completion->get_criteria(COMPLETION_CRITERIA_TYPE_ROLE);
 128  
 129      if (!empty($rcriteria)) {
 130  
 131          foreach ($rcriteria as $rcriterion) {
 132              $users = get_role_users($rcriterion->role, $context, true);
 133  
 134              // If logged in user has this role, allow marking complete
 135              if ($users && in_array($USER->id, array_keys($users))) {
 136                  $allow_marking = true;
 137                  $allow_marking_criteria = $rcriterion->id;
 138                  break;
 139              }
 140          }
 141      }
 142  }
 143  
 144  /*
 145   * Setup page header
 146   */
 147  if ($csv) {
 148  
 149      $shortname = format_string($course->shortname, true, array('context' => $context));
 150      $shortname = preg_replace('/[^a-z0-9-]/', '_',core_text::strtolower(strip_tags($shortname)));
 151  
 152      $export = new csv_export_writer('comma', '"', 'application/download', $excel);
 153      $export->set_filename('completion-'.$shortname);
 154  
 155  } else {
 156      // Navigation and header
 157      $strcompletion = get_string('coursecompletion');
 158  
 159      $PAGE->set_title($strcompletion);
 160      $PAGE->set_heading($course->fullname);
 161  
 162      echo $OUTPUT->header();
 163      // Print the selected dropdown.
 164      $pluginname = get_string('pluginname', 'report_completion');
 165      report_helper::print_report_selector($pluginname);
 166  
 167      // Handle groups (if enabled)
 168      groups_print_course_menu($course, $CFG->wwwroot.'/report/completion/index.php?course='.$course->id);
 169  }
 170  
 171  if ($sifirst !== 'all') {
 172      set_user_preference('ifirst', $sifirst);
 173  }
 174  if ($silast !== 'all') {
 175      set_user_preference('ilast', $silast);
 176  }
 177  
 178  if (!empty($USER->preference['ifirst'])) {
 179      $sifirst = $USER->preference['ifirst'];
 180  } else {
 181      $sifirst = 'all';
 182  }
 183  
 184  if (!empty($USER->preference['ilast'])) {
 185      $silast = $USER->preference['ilast'];
 186  } else {
 187      $silast = 'all';
 188  }
 189  
 190  // Generate where clause
 191  $where = array();
 192  $where_params = array();
 193  
 194  if ($sifirst !== 'all') {
 195      $where[] = $DB->sql_like('u.firstname', ':sifirst', false, false);
 196      $where_params['sifirst'] = $sifirst.'%';
 197  }
 198  
 199  if ($silast !== 'all') {
 200      $where[] = $DB->sql_like('u.lastname', ':silast', false, false);
 201      $where_params['silast'] = $silast.'%';
 202  }
 203  
 204  // Get user match count
 205  $total = $completion->get_num_tracked_users(implode(' AND ', $where), $where_params, $group);
 206  
 207  // Total user count
 208  $grandtotal = $completion->get_num_tracked_users('', array(), $group);
 209  
 210  // If no users in this course what-so-ever
 211  if (!$grandtotal) {
 212      echo $OUTPUT->container(get_string('err_nousers', 'completion'), 'errorbox errorboxcontent');
 213      echo $OUTPUT->footer();
 214      exit;
 215  }
 216  
 217  // Get user data
 218  $progress = array();
 219  
 220  if ($total) {
 221      $progress = $completion->get_progress_all(
 222          implode(' AND ', $where),
 223          $where_params,
 224          $group,
 225          $firstnamesort ? 'u.firstname ASC' : 'u.lastname ASC',
 226          $csv ? 0 : COMPLETION_REPORT_PAGE,
 227          $csv ? 0 : $start,
 228          $context
 229      );
 230  }
 231  
 232  // Build link for paging
 233  $link = $CFG->wwwroot.'/report/completion/index.php?course='.$course->id;
 234  if (strlen($sort)) {
 235      $link .= '&amp;sort='.$sort;
 236  }
 237  $link .= '&amp;start=';
 238  
 239  $pagingbar = '';
 240  
 241  // Initials bar.
 242  $prefixfirst = 'sifirst';
 243  $prefixlast = 'silast';
 244  $pagingbar .= $OUTPUT->initials_bar($sifirst, 'firstinitial', get_string('firstname'), $prefixfirst, $url);
 245  $pagingbar .= $OUTPUT->initials_bar($silast, 'lastinitial', get_string('lastname'), $prefixlast, $url);
 246  
 247  // Do we need a paging bar?
 248  if ($total > COMPLETION_REPORT_PAGE) {
 249  
 250      // Paging bar
 251      $pagingbar .= '<div class="paging">';
 252      $pagingbar .= get_string('page').': ';
 253  
 254      $sistrings = array();
 255      if ($sifirst != 'all') {
 256          $sistrings[] =  "sifirst={$sifirst}";
 257      }
 258      if ($silast != 'all') {
 259          $sistrings[] =  "silast={$silast}";
 260      }
 261      $sistring = !empty($sistrings) ? '&amp;'.implode('&amp;', $sistrings) : '';
 262  
 263      // Display previous link
 264      if ($start > 0) {
 265          $pstart = max($start - COMPLETION_REPORT_PAGE, 0);
 266          $pagingbar .= "(<a class=\"previous\" href=\"{$link}{$pstart}{$sistring}\">".get_string('previous').'</a>)&nbsp;';
 267      }
 268  
 269      // Create page links
 270      $curstart = 0;
 271      $curpage = 0;
 272      while ($curstart < $total) {
 273          $curpage++;
 274  
 275          if ($curstart == $start) {
 276              $pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
 277          }
 278          else {
 279              $pagingbar .= "&nbsp;<a href=\"{$link}{$curstart}{$sistring}\">$curpage</a>&nbsp;";
 280          }
 281  
 282          $curstart += COMPLETION_REPORT_PAGE;
 283      }
 284  
 285      // Display next link
 286      $nstart = $start + COMPLETION_REPORT_PAGE;
 287      if ($nstart < $total) {
 288          $pagingbar .= "&nbsp;(<a class=\"next\" href=\"{$link}{$nstart}{$sistring}\">".get_string('next').'</a>)';
 289      }
 290  
 291      $pagingbar .= '</div>';
 292  }
 293  
 294  /*
 295   * Draw table header
 296   */
 297  
 298  // Start of table
 299  if (!$csv) {
 300      print '<br class="clearer"/>'; // ugh
 301  
 302      $total_header = ($total == $grandtotal) ? $total : "{$total}/{$grandtotal}";
 303      echo $OUTPUT->heading(get_string('allparticipants').": {$total_header}", 3);
 304  
 305      print $pagingbar;
 306  
 307      if (!$total) {
 308          echo $OUTPUT->heading(get_string('nothingtodisplay'), 2);
 309          echo $OUTPUT->footer();
 310          exit;
 311      }
 312  
 313      print '<table id="completion-progress" class="table table-bordered generaltable flexible boxaligncenter
 314          completionreport" style="text-align: left" cellpadding="5" border="1">';
 315  
 316      // Print criteria group names
 317      print PHP_EOL.'<thead><tr style="vertical-align: top">';
 318      echo '<th scope="row" class="rowheader" colspan="' . $leftcols . '">' .
 319              get_string('criteriagroup', 'completion') . '</th>';
 320  
 321      $current_group = false;
 322      $col_count = 0;
 323      for ($i = 0; $i <= count($criteria); $i++) {
 324  
 325          if (isset($criteria[$i])) {
 326              $criterion = $criteria[$i];
 327  
 328              if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
 329                  ++$col_count;
 330                  continue;
 331              }
 332          }
 333  
 334          // Print header cell
 335          if ($col_count) {
 336              print '<th scope="col" colspan="'.$col_count.'" class="colheader criteriagroup">'.$current_group->get_type_title().'</th>';
 337          }
 338  
 339          if (isset($criteria[$i])) {
 340              // Move to next criteria type
 341              $current_group = $criterion;
 342              $col_count = 1;
 343          }
 344      }
 345  
 346      // Overall course completion status
 347      print '<th style="text-align: center;">'.get_string('course').'</th>';
 348  
 349      print '</tr>';
 350  
 351      // Print aggregation methods
 352      print PHP_EOL.'<tr style="vertical-align: top">';
 353      echo '<th scope="row" class="rowheader" colspan="' . $leftcols . '">' .
 354              get_string('aggregationmethod', 'completion').'</th>';
 355  
 356      $current_group = false;
 357      $col_count = 0;
 358      for ($i = 0; $i <= count($criteria); $i++) {
 359  
 360          if (isset($criteria[$i])) {
 361              $criterion = $criteria[$i];
 362  
 363              if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
 364                  ++$col_count;
 365                  continue;
 366              }
 367          }
 368  
 369          // Print header cell
 370          if ($col_count) {
 371              $has_agg = array(
 372                  COMPLETION_CRITERIA_TYPE_COURSE,
 373                  COMPLETION_CRITERIA_TYPE_ACTIVITY,
 374                  COMPLETION_CRITERIA_TYPE_ROLE,
 375              );
 376  
 377              if (in_array($current_group->criteriatype, $has_agg)) {
 378                  // Try load a aggregation method
 379                  $method = $completion->get_aggregation_method($current_group->criteriatype);
 380  
 381                  $method = $method == 1 ? get_string('all') : get_string('any');
 382  
 383              } else {
 384                  $method = '-';
 385              }
 386  
 387              print '<th scope="col" colspan="'.$col_count.'" class="colheader aggheader">'.$method.'</th>';
 388          }
 389  
 390          if (isset($criteria[$i])) {
 391              // Move to next criteria type
 392              $current_group = $criterion;
 393              $col_count = 1;
 394          }
 395      }
 396  
 397      // Overall course aggregation method
 398      print '<th scope="col" class="colheader aggheader aggcriteriacourse">';
 399  
 400      // Get course aggregation
 401      $method = $completion->get_aggregation_method();
 402  
 403      print $method == 1 ? get_string('all') : get_string('any');
 404      print '</th>';
 405  
 406      print '</tr>';
 407  
 408      // Print criteria titles
 409      if (COMPLETION_REPORT_COL_TITLES) {
 410  
 411          print PHP_EOL.'<tr>';
 412          echo '<th scope="row" class="rowheader" colspan="' . $leftcols . '">' .
 413                  get_string('criteria', 'completion') . '</th>';
 414  
 415          foreach ($criteria as $criterion) {
 416              // Get criteria details
 417              $details = $criterion->get_title_detailed();
 418              print '<th scope="col" class="colheader criterianame">';
 419              print '<div class="rotated-text-container"><span class="rotated-text">'.$details.'</span></div>';
 420              print '</th>';
 421          }
 422  
 423          // Overall course completion status
 424          print '<th scope="col" class="colheader criterianame">';
 425          print '<div class="rotated-text-container"><span class="rotated-text">'.get_string('coursecomplete', 'completion').'</span></div>';
 426          print '</th></tr>';
 427      }
 428  
 429      // Print user heading and icons
 430      print '<tr>';
 431  
 432      // User heading / sort option
 433      print '<th scope="col" class="completion-sortchoice" style="clear: both;">';
 434  
 435      $sistring = "&amp;silast={$silast}&amp;sifirst={$sifirst}";
 436  
 437      if ($firstnamesort) {
 438          print
 439              get_string('firstname')." / <a href=\"./index.php?course={$course->id}{$sistring}\">".
 440              get_string('lastname').'</a>';
 441      } else {
 442          print "<a href=\"./index.php?course={$course->id}&amp;sort=firstname{$sistring}\">".
 443              get_string('firstname').'</a> / '.
 444              get_string('lastname');
 445      }
 446      print '</th>';
 447  
 448      // Print user identity columns
 449      foreach ($extrafields as $field) {
 450          echo '<th scope="col" class="completion-identifyfield">' .
 451                  \core_user\fields::get_display_name($field) . '</th>';
 452      }
 453  
 454      ///
 455      /// Print criteria icons
 456      ///
 457      foreach ($criteria as $criterion) {
 458  
 459          // Generate icon details
 460          $iconlink = '';
 461          $iconalt = ''; // Required
 462          $iconattributes = array('class' => 'icon');
 463          switch ($criterion->criteriatype) {
 464  
 465              case COMPLETION_CRITERIA_TYPE_ACTIVITY:
 466  
 467                  // Display icon
 468                  $iconlink = $CFG->wwwroot.'/mod/'.$criterion->module.'/view.php?id='.$criterion->moduleinstance;
 469                  $iconattributes['title'] = $modinfo->cms[$criterion->moduleinstance]->get_formatted_name();
 470                  $iconalt = get_string('modulename', $criterion->module);
 471                  break;
 472  
 473              case COMPLETION_CRITERIA_TYPE_COURSE:
 474                  // Load course
 475                  $crs = $DB->get_record('course', array('id' => $criterion->courseinstance));
 476  
 477                  // Display icon
 478                  $iconlink = $CFG->wwwroot.'/course/view.php?id='.$criterion->courseinstance;
 479                  $iconattributes['title'] = format_string($crs->fullname, true, array('context' => context_course::instance($crs->id, MUST_EXIST)));
 480                  $iconalt = format_string($crs->shortname, true, array('context' => context_course::instance($crs->id)));
 481                  break;
 482  
 483              case COMPLETION_CRITERIA_TYPE_ROLE:
 484                  // Load role
 485                  $role = $DB->get_record('role', array('id' => $criterion->role));
 486  
 487                  // Display icon
 488                  $iconalt = $role->name;
 489                  break;
 490          }
 491  
 492          // Create icon alt if not supplied
 493          if (!$iconalt) {
 494              $iconalt = $criterion->get_title();
 495          }
 496  
 497          // Print icon and cell
 498          print '<th class="criteriaicon">';
 499  
 500          print ($iconlink ? '<a href="'.$iconlink.'" title="'.$iconattributes['title'].'">' : '');
 501          print $OUTPUT->render($criterion->get_icon($iconalt, $iconattributes));
 502          print ($iconlink ? '</a>' : '');
 503  
 504          print '</th>';
 505      }
 506  
 507      // Overall course completion status
 508      print '<th class="criteriaicon">';
 509      print $OUTPUT->pix_icon('i/course', get_string('coursecomplete', 'completion'));
 510      print '</th>';
 511  
 512      print '</tr></thead>';
 513  
 514      echo '<tbody>';
 515  } else {
 516      // The CSV headers
 517      $row = array();
 518  
 519      $row[] = get_string('id', 'report_completion');
 520      $row[] = get_string('name', 'report_completion');
 521      foreach ($extrafields as $field) {
 522          $row[] = \core_user\fields::get_display_name($field);
 523      }
 524  
 525      // Add activity headers
 526      foreach ($criteria as $criterion) {
 527  
 528          // Handle activity completion differently
 529          if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
 530  
 531              // Load activity
 532              $mod = $criterion->get_mod_instance();
 533              $row[] = $formattedname = format_string($mod->name, true,
 534                      array('context' => context_module::instance($criterion->moduleinstance)));
 535              $row[] = $formattedname . ' - ' . get_string('completiondate', 'report_completion');
 536          }
 537          else {
 538              // Handle all other criteria
 539              $row[] = strip_tags($criterion->get_title_detailed());
 540          }
 541      }
 542  
 543      $row[] = get_string('coursecomplete', 'completion');
 544  
 545      $export->add_data($row);
 546  }
 547  
 548  ///
 549  /// Display a row for each user
 550  ///
 551  foreach ($progress as $user) {
 552  
 553      // User name
 554      if ($csv) {
 555          $row = array();
 556          $row[] = $user->id;
 557          $row[] = fullname($user, has_capability('moodle/site:viewfullnames', $context));
 558          foreach ($extrafields as $field) {
 559              $row[] = $user->{$field};
 560          }
 561      } else {
 562          print PHP_EOL.'<tr id="user-'.$user->id.'">';
 563  
 564          if (completion_can_view_data($user->id, $course)) {
 565              $userurl = new moodle_url('/blocks/completionstatus/details.php', array('course' => $course->id, 'user' => $user->id));
 566          } else {
 567              $userurl = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id));
 568          }
 569  
 570          print '<th scope="row"><a href="' . $userurl->out() . '">' .
 571              fullname($user, has_capability('moodle/site:viewfullnames', $context)) . '</a></th>';
 572          foreach ($extrafields as $field) {
 573              echo '<td>'.s($user->{$field}).'</td>';
 574          }
 575      }
 576  
 577      // Progress for each course completion criteria
 578      foreach ($criteria as $criterion) {
 579  
 580          $criteria_completion = $completion->get_user_completion($user->id, $criterion);
 581          $is_complete = $criteria_completion->is_complete();
 582  
 583          // Handle activity completion differently
 584          if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
 585  
 586              // Load activity
 587              $activity = $modinfo->cms[$criterion->moduleinstance];
 588  
 589              // Get progress information and state
 590              if (array_key_exists($activity->id, $user->progress)) {
 591                  $state = $user->progress[$activity->id]->completionstate;
 592              } else if ($is_complete) {
 593                  $state = COMPLETION_COMPLETE;
 594              } else {
 595                  $state = COMPLETION_INCOMPLETE;
 596              }
 597              if ($is_complete) {
 598                  $date = userdate($criteria_completion->timecompleted, get_string('strftimedatetimeshort', 'langconfig'));
 599              } else {
 600                  $date = '';
 601              }
 602  
 603              // Work out how it corresponds to an icon
 604              switch($state) {
 605                  case COMPLETION_INCOMPLETE    : $completiontype = 'n';    break;
 606                  case COMPLETION_COMPLETE      : $completiontype = 'y';    break;
 607                  case COMPLETION_COMPLETE_PASS : $completiontype = 'pass'; break;
 608                  case COMPLETION_COMPLETE_FAIL : $completiontype = 'fail'; break;
 609              }
 610  
 611              $auto = $activity->completion == COMPLETION_TRACKING_AUTOMATIC;
 612              $completionicon = 'completion-'.($auto ? 'auto' : 'manual').'-'.$completiontype;
 613  
 614              $describe = get_string('completion-'.$completiontype, 'completion');
 615              $a = new StdClass();
 616              $a->state     = $describe;
 617              $a->date      = $date;
 618              $a->user      = fullname($user);
 619              $a->activity  = $activity->get_formatted_name();
 620              $fulldescribe = get_string('progress-title', 'completion', $a);
 621  
 622              if ($csv) {
 623                  $row[] = $describe;
 624                  $row[] = $date;
 625              } else {
 626                  print '<td class="completion-progresscell">';
 627  
 628                  print $OUTPUT->pix_icon('i/' . $completionicon, $fulldescribe);
 629  
 630                  print '</td>';
 631              }
 632  
 633              continue;
 634          }
 635  
 636          // Handle all other criteria
 637          $completiontype = $is_complete ? 'y' : 'n';
 638          $completionicon = 'completion-auto-'.$completiontype;
 639  
 640          $describe = get_string('completion-'.$completiontype, 'completion');
 641  
 642          $a = new stdClass();
 643          $a->state    = $describe;
 644  
 645          if ($is_complete) {
 646              $a->date = userdate($criteria_completion->timecompleted, get_string('strftimedatetimeshort', 'langconfig'));
 647          } else {
 648              $a->date = '';
 649          }
 650  
 651          $a->user     = fullname($user);
 652          $a->activity = strip_tags($criterion->get_title());
 653          $fulldescribe = get_string('progress-title', 'completion', $a);
 654  
 655          if ($csv) {
 656              $row[] = $a->date;
 657          } else {
 658  
 659              print '<td class="completion-progresscell">';
 660  
 661              if ($allow_marking_criteria === $criterion->id) {
 662                  $describe = get_string('completion-'.$completiontype, 'completion');
 663  
 664                  $toggleurl = new moodle_url(
 665                      '/course/togglecompletion.php',
 666                      array(
 667                          'user' => $user->id,
 668                          'course' => $course->id,
 669                          'rolec' => $allow_marking_criteria,
 670                          'sesskey' => sesskey()
 671                      )
 672                  );
 673  
 674                  print '<a href="'.$toggleurl->out().'" title="'.s(get_string('clicktomarkusercomplete', 'report_completion')).'">' .
 675                      $OUTPUT->pix_icon('i/completion-manual-' . ($is_complete ? 'y' : 'n'), $describe) . '</a></td>';
 676              } else {
 677                  print $OUTPUT->pix_icon('i/' . $completionicon, $fulldescribe) . '</td>';
 678              }
 679  
 680              print '</td>';
 681          }
 682      }
 683  
 684      // Handle overall course completion
 685  
 686      // Load course completion
 687      $params = array(
 688          'userid'    => $user->id,
 689          'course'    => $course->id
 690      );
 691  
 692      $ccompletion = new completion_completion($params);
 693      $completiontype =  $ccompletion->is_complete() ? 'y' : 'n';
 694  
 695      $describe = get_string('completion-'.$completiontype, 'completion');
 696  
 697      $a = new StdClass;
 698  
 699      if ($ccompletion->is_complete()) {
 700          $a->date = userdate($ccompletion->timecompleted, get_string('strftimedatetimeshort', 'langconfig'));
 701      } else {
 702          $a->date = '';
 703      }
 704  
 705      $a->state    = $describe;
 706      $a->user     = fullname($user);
 707      $a->activity = strip_tags(get_string('coursecomplete', 'completion'));
 708      $fulldescribe = get_string('progress-title', 'completion', $a);
 709  
 710      if ($csv) {
 711          $row[] = $a->date;
 712      } else {
 713  
 714          print '<td class="completion-progresscell">';
 715  
 716          // Display course completion status icon
 717          print $OUTPUT->pix_icon('i/completion-auto-' . $completiontype, $fulldescribe);
 718  
 719          print '</td>';
 720      }
 721  
 722      if ($csv) {
 723          $export->add_data($row);
 724      } else {
 725          print '</tr>';
 726      }
 727  }
 728  
 729  if ($csv) {
 730      $export->download_file();
 731  } else {
 732      echo '</tbody>';
 733  }
 734  
 735  print '</table>';
 736  
 737  $csvurl = new moodle_url('/report/completion/index.php', array('course' => $course->id, 'format' => 'csv'));
 738  $excelurl = new moodle_url('/report/completion/index.php', array('course' => $course->id, 'format' => 'excelcsv'));
 739  
 740  print '<ul class="export-actions">';
 741  print '<li><a href="'.$csvurl->out().'">'.get_string('csvdownload','completion').'</a></li>';
 742  print '<li><a href="'.$excelurl->out().'">'.get_string('excelcsvdownload','completion').'</a></li>';
 743  print '</ul>';
 744  
 745  echo $OUTPUT->footer($course);
 746  
 747  // Trigger a report viewed event.
 748  $event = \report_completion\event\report_viewed::create(array('context' => $context));
 749  $event->trigger();