Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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