Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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