Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [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   * Contains the class used for the displaying the participants table.
  19   *
  20   * @package    core_user
  21   * @copyright  2017 Mark Nelson <markn@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  declare(strict_types=1);
  25  
  26  namespace core_user\table;
  27  
  28  use DateTime;
  29  use context;
  30  use core_table\dynamic as dynamic_table;
  31  use core_table\local\filter\filterset;
  32  use core_user\output\status_field;
  33  use core_user\table\participants_search;
  34  use moodle_url;
  35  
  36  defined('MOODLE_INTERNAL') || die;
  37  
  38  global $CFG;
  39  
  40  require_once($CFG->libdir . '/tablelib.php');
  41  require_once($CFG->dirroot . '/user/lib.php');
  42  
  43  /**
  44   * Class for the displaying the participants table.
  45   *
  46   * @package    core_user
  47   * @copyright  2017 Mark Nelson <markn@moodle.com>
  48   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   */
  50  class participants extends \table_sql implements dynamic_table {
  51  
  52      /**
  53       * @var int $courseid The course id
  54       */
  55      protected $courseid;
  56  
  57      /**
  58       * @var string[] The list of countries.
  59       */
  60      protected $countries;
  61  
  62      /**
  63       * @var \stdClass[] The list of groups with membership info for the course.
  64       */
  65      protected $groups;
  66  
  67      /**
  68       * @var string[] Extra fields to display.
  69       */
  70      protected $extrafields;
  71  
  72      /**
  73       * @var \stdClass $course The course details.
  74       */
  75      protected $course;
  76  
  77      /**
  78       * @var  context $context The course context.
  79       */
  80      protected $context;
  81  
  82      /**
  83       * @var \stdClass[] List of roles indexed by roleid.
  84       */
  85      protected $allroles;
  86  
  87      /**
  88       * @var \stdClass[] List of roles indexed by roleid.
  89       */
  90      protected $allroleassignments;
  91  
  92      /**
  93       * @var \stdClass[] Assignable roles in this course.
  94       */
  95      protected $assignableroles;
  96  
  97      /**
  98       * @var \stdClass[] Profile roles in this course.
  99       */
 100      protected $profileroles;
 101  
 102      /**
 103       * @var filterset Filterset describing which participants to include.
 104       */
 105      protected $filterset;
 106  
 107      /** @var \stdClass[] $viewableroles */
 108      private $viewableroles;
 109  
 110      /** @var moodle_url $baseurl The base URL for the report. */
 111      public $baseurl;
 112  
 113      /**
 114       * Render the participants table.
 115       *
 116       * @param int $pagesize Size of page for paginated displayed table.
 117       * @param bool $useinitialsbar Whether to use the initials bar which will only be used if there is a fullname column defined.
 118       * @param string $downloadhelpbutton
 119       */
 120      public function out($pagesize, $useinitialsbar, $downloadhelpbutton = '') {
 121          global $CFG, $OUTPUT, $PAGE;
 122  
 123          // Define the headers and columns.
 124          $headers = [];
 125          $columns = [];
 126  
 127          // At the very least, the user viewing this table will be able to use bulk actions to export it, so add 'select' column.
 128          $mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
 129              'id' => 'select-all-participants',
 130              'name' => 'select-all-participants',
 131              'label' => get_string('selectall'),
 132              'labelclasses' => 'sr-only',
 133              'classes' => 'm-1',
 134              'checked' => false,
 135          ]);
 136          $headers[] = $OUTPUT->render($mastercheckbox);
 137          $columns[] = 'select';
 138  
 139          $headers[] = get_string('fullname');
 140          $columns[] = 'fullname';
 141  
 142          $extrafields = \core_user\fields::get_identity_fields($this->context);
 143          foreach ($extrafields as $field) {
 144              $headers[] = \core_user\fields::get_display_name($field);
 145              $columns[] = $field;
 146          }
 147  
 148          $headers[] = get_string('roles');
 149          $columns[] = 'roles';
 150  
 151          // Get the list of fields we have to hide.
 152          $hiddenfields = array();
 153          if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
 154              $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
 155          }
 156  
 157          // Add column for groups if the user can view them.
 158          $canseegroups = !isset($hiddenfields['groups']);
 159          if ($canseegroups) {
 160              $headers[] = get_string('groups');
 161              $columns[] = 'groups';
 162          }
 163  
 164          // Do not show the columns if it exists in the hiddenfields array.
 165          if (!isset($hiddenfields['lastaccess'])) {
 166              if ($this->courseid == SITEID) {
 167                  $headers[] = get_string('lastsiteaccess');
 168              } else {
 169                  $headers[] = get_string('lastcourseaccess');
 170              }
 171              $columns[] = 'lastaccess';
 172          }
 173  
 174          $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
 175          if ($canreviewenrol && $this->courseid != SITEID) {
 176              $columns[] = 'status';
 177              $headers[] = get_string('participationstatus', 'enrol');
 178              $this->no_sorting('status');
 179          };
 180  
 181          $this->define_columns($columns);
 182          $this->define_headers($headers);
 183  
 184          // The name column is a header.
 185          $this->define_header_column('fullname');
 186  
 187          // Make this table sorted by last name by default.
 188          $this->sortable(true, 'lastname');
 189  
 190          $this->no_sorting('select');
 191          $this->no_sorting('roles');
 192          if ($canseegroups) {
 193              $this->no_sorting('groups');
 194          }
 195  
 196          $this->set_default_per_page(20);
 197  
 198          $this->set_attribute('id', 'participants');
 199  
 200          $this->countries = get_string_manager()->get_list_of_countries(true);
 201          $this->extrafields = $extrafields;
 202          if ($canseegroups) {
 203              $this->groups = groups_get_all_groups($this->courseid, 0, 0, 'g.*', true);
 204          }
 205  
 206          // If user has capability to review enrol, show them both role names.
 207          $allrolesnamedisplay = ($canreviewenrol ? ROLENAME_BOTH : ROLENAME_ALIAS);
 208          $this->allroles = role_fix_names(get_all_roles($this->context), $this->context, $allrolesnamedisplay);
 209          $this->assignableroles = get_assignable_roles($this->context, ROLENAME_BOTH, false);
 210          $this->profileroles = get_profile_roles($this->context);
 211          $this->viewableroles = get_viewable_roles($this->context);
 212  
 213          parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
 214  
 215          if (has_capability('moodle/course:enrolreview', $this->context)) {
 216              $params = [
 217                  'contextid' => $this->context->id,
 218                  'uniqueid' => $this->uniqueid,
 219              ];
 220              $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
 221          }
 222      }
 223  
 224      /**
 225       * Generate the select column.
 226       *
 227       * @param \stdClass $data
 228       * @return string
 229       */
 230      public function col_select($data) {
 231          global $OUTPUT;
 232  
 233          $checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
 234              'classes' => 'usercheckbox m-1',
 235              'id' => 'user' . $data->id,
 236              'name' => 'user' . $data->id,
 237              'checked' => false,
 238              'label' => get_string('selectitem', 'moodle', fullname($data)),
 239              'labelclasses' => 'accesshide',
 240          ]);
 241  
 242          return $OUTPUT->render($checkbox);
 243      }
 244  
 245      /**
 246       * Generate the fullname column.
 247       *
 248       * @param \stdClass $data
 249       * @return string
 250       */
 251      public function col_fullname($data) {
 252          global $OUTPUT;
 253  
 254          return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->course->id, 'includefullname' => true));
 255      }
 256  
 257      /**
 258       * User roles column.
 259       *
 260       * @param \stdClass $data
 261       * @return string
 262       */
 263      public function col_roles($data) {
 264          global $OUTPUT;
 265  
 266          $roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
 267          $editable = new \core_user\output\user_roles_editable($this->course,
 268                                                                $this->context,
 269                                                                $data,
 270                                                                $this->allroles,
 271                                                                $this->assignableroles,
 272                                                                $this->profileroles,
 273                                                                $roles,
 274                                                                $this->viewableroles);
 275  
 276          return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
 277      }
 278  
 279      /**
 280       * Generate the groups column.
 281       *
 282       * @param \stdClass $data
 283       * @return string
 284       */
 285      public function col_groups($data) {
 286          global $OUTPUT;
 287  
 288          $usergroups = [];
 289          foreach ($this->groups as $coursegroup) {
 290              if (isset($coursegroup->members[$data->id])) {
 291                  $usergroups[] = $coursegroup->id;
 292              }
 293          }
 294          $editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
 295          return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
 296      }
 297  
 298      /**
 299       * Generate the country column.
 300       *
 301       * @param \stdClass $data
 302       * @return string
 303       */
 304      public function col_country($data) {
 305          if (!empty($this->countries[$data->country])) {
 306              return $this->countries[$data->country];
 307          }
 308          return '';
 309      }
 310  
 311      /**
 312       * Generate the last access column.
 313       *
 314       * @param \stdClass $data
 315       * @return string
 316       */
 317      public function col_lastaccess($data) {
 318          if ($data->lastaccess) {
 319              return format_time(time() - $data->lastaccess);
 320          }
 321  
 322          return get_string('never');
 323      }
 324  
 325      /**
 326       * Generate the status column.
 327       *
 328       * @param \stdClass $data The data object.
 329       * @return string
 330       */
 331      public function col_status($data) {
 332          global $CFG, $OUTPUT, $PAGE;
 333  
 334          $enrolstatusoutput = '';
 335          $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
 336          if ($canreviewenrol) {
 337              $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
 338              $fullname = fullname($data, $canviewfullnames);
 339              $coursename = format_string($this->course->fullname, true, array('context' => $this->context));
 340              require_once($CFG->dirroot . '/enrol/locallib.php');
 341              $manager = new \course_enrolment_manager($PAGE, $this->course);
 342              $userenrolments = $manager->get_user_enrolments($data->id);
 343              foreach ($userenrolments as $ue) {
 344                  $timestart = $ue->timestart;
 345                  $timeend = $ue->timeend;
 346                  $timeenrolled = $ue->timecreated;
 347                  $actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
 348                  $instancename = $ue->enrolmentinstancename;
 349  
 350                  // Default status field label and value.
 351                  $status = get_string('participationactive', 'enrol');
 352                  $statusval = status_field::STATUS_ACTIVE;
 353                  switch ($ue->status) {
 354                      case ENROL_USER_ACTIVE:
 355                          $currentdate = new DateTime();
 356                          $now = $currentdate->getTimestamp();
 357                          $isexpired = $timestart > $now || ($timeend > 0 && $timeend < $now);
 358                          $enrolmentdisabled = $ue->enrolmentinstance->status == ENROL_INSTANCE_DISABLED;
 359                          // If user enrolment status has not yet started/already ended or the enrolment instance is disabled.
 360                          if ($isexpired || $enrolmentdisabled) {
 361                              $status = get_string('participationnotcurrent', 'enrol');
 362                              $statusval = status_field::STATUS_NOT_CURRENT;
 363                          }
 364                          break;
 365                      case ENROL_USER_SUSPENDED:
 366                          $status = get_string('participationsuspended', 'enrol');
 367                          $statusval = status_field::STATUS_SUSPENDED;
 368                          break;
 369                  }
 370  
 371                  $statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
 372                      $actions, $timeenrolled);
 373                  $statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
 374                  $enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
 375              }
 376          }
 377          return $enrolstatusoutput;
 378      }
 379  
 380      /**
 381       * This function is used for the extra user fields.
 382       *
 383       * These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
 384       * the list has the potential to increase in the future and we don't want to have to remember to add
 385       * a new method to this class. We also don't want to pollute this class with unnecessary methods.
 386       *
 387       * @param string $colname The column name
 388       * @param \stdClass $data
 389       * @return string
 390       */
 391      public function other_cols($colname, $data) {
 392          // Do not process if it is not a part of the extra fields.
 393          if (!in_array($colname, $this->extrafields)) {
 394              return '';
 395          }
 396  
 397          return s($data->{$colname});
 398      }
 399  
 400      /**
 401       * Query the database for results to display in the table.
 402       *
 403       * @param int $pagesize size of page for paginated displayed table.
 404       * @param bool $useinitialsbar do you want to use the initials bar.
 405       */
 406      public function query_db($pagesize, $useinitialsbar = true) {
 407          list($twhere, $tparams) = $this->get_sql_where();
 408          $psearch = new participants_search($this->course, $this->context, $this->filterset);
 409  
 410          $total = $psearch->get_total_participants_count($twhere, $tparams);
 411  
 412          $this->pagesize($pagesize, $total);
 413  
 414          $sort = $this->get_sql_sort();
 415          if ($sort) {
 416              $sort = 'ORDER BY ' . $sort;
 417          }
 418  
 419          $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
 420  
 421          $this->rawdata = [];
 422          foreach ($rawdata as $user) {
 423              $this->rawdata[$user->id] = $user;
 424          }
 425          $rawdata->close();
 426  
 427          if ($this->rawdata) {
 428              $this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata),
 429                      true, 'c.contextlevel DESC, r.sortorder ASC');
 430          } else {
 431              $this->allroleassignments = [];
 432          }
 433  
 434          // Set initial bars.
 435          if ($useinitialsbar) {
 436              $this->initialbars(true);
 437          }
 438      }
 439  
 440      /**
 441       * Override the table show_hide_link to not show for select column.
 442       *
 443       * @param string $column the column name, index into various names.
 444       * @param int $index numerical index of the column.
 445       * @return string HTML fragment.
 446       */
 447      protected function show_hide_link($column, $index) {
 448          if ($index > 0) {
 449              return parent::show_hide_link($column, $index);
 450          }
 451          return '';
 452      }
 453  
 454      /**
 455       * Set filters and build table structure.
 456       *
 457       * @param filterset $filterset The filterset object to get the filters from.
 458       */
 459      public function set_filterset(filterset $filterset): void {
 460          // Get the context.
 461          $this->courseid = $filterset->get_filter('courseid')->current();
 462          $this->course = get_course($this->courseid);
 463          $this->context = \context_course::instance($this->courseid, MUST_EXIST);
 464  
 465          // Process the filterset.
 466          parent::set_filterset($filterset);
 467      }
 468  
 469      /**
 470       * Guess the base url for the participants table.
 471       */
 472      public function guess_base_url(): void {
 473          $this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
 474      }
 475  
 476      /**
 477       * Get the context of the current table.
 478       *
 479       * Note: This function should not be called until after the filterset has been provided.
 480       *
 481       * @return context
 482       */
 483      public function get_context(): context {
 484          return $this->context;
 485      }
 486  }