Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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          $bulkoperations = has_capability('moodle/course:bulkmessaging', $this->context);
 128          if ($bulkoperations) {
 129              $mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
 130                  'id' => 'select-all-participants',
 131                  'name' => 'select-all-participants',
 132                  'label' => get_string('selectall'),
 133                  'labelclasses' => 'sr-only',
 134                  'classes' => 'm-1',
 135                  'checked' => false,
 136              ]);
 137              $headers[] = $OUTPUT->render($mastercheckbox);
 138              $columns[] = 'select';
 139          }
 140  
 141          $headers[] = get_string('fullname');
 142          $columns[] = 'fullname';
 143  
 144          $extrafields = get_extra_user_fields($this->context);
 145          foreach ($extrafields as $field) {
 146              $headers[] = get_user_field_name($field);
 147              $columns[] = $field;
 148          }
 149  
 150          $headers[] = get_string('roles');
 151          $columns[] = 'roles';
 152  
 153          // Get the list of fields we have to hide.
 154          $hiddenfields = array();
 155          if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
 156              $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
 157          }
 158  
 159          // Add column for groups if the user can view them.
 160          $canseegroups = !isset($hiddenfields['groups']);
 161          if ($canseegroups) {
 162              $headers[] = get_string('groups');
 163              $columns[] = 'groups';
 164          }
 165  
 166          // Do not show the columns if it exists in the hiddenfields array.
 167          if (!isset($hiddenfields['lastaccess'])) {
 168              if ($this->courseid == SITEID) {
 169                  $headers[] = get_string('lastsiteaccess');
 170              } else {
 171                  $headers[] = get_string('lastcourseaccess');
 172              }
 173              $columns[] = 'lastaccess';
 174          }
 175  
 176          $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
 177          if ($canreviewenrol && $this->courseid != SITEID) {
 178              $columns[] = 'status';
 179              $headers[] = get_string('participationstatus', 'enrol');
 180              $this->no_sorting('status');
 181          };
 182  
 183          $this->define_columns($columns);
 184          $this->define_headers($headers);
 185  
 186          // The name column is a header.
 187          $this->define_header_column('fullname');
 188  
 189          // Make this table sorted by last name by default.
 190          $this->sortable(true, 'lastname');
 191  
 192          $this->no_sorting('select');
 193          $this->no_sorting('roles');
 194          if ($canseegroups) {
 195              $this->no_sorting('groups');
 196          }
 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          $this->allroles = role_fix_names(get_all_roles($this->context), $this->context);
 206          $this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false);
 207          $this->profileroles = get_profile_roles($this->context);
 208          $this->viewableroles = get_viewable_roles($this->context);
 209  
 210          parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
 211  
 212          if (has_capability('moodle/course:enrolreview', $this->context)) {
 213              $params = [
 214                  'contextid' => $this->context->id,
 215                  'uniqueid' => $this->uniqueid,
 216              ];
 217              $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
 218          }
 219      }
 220  
 221      /**
 222       * Generate the select column.
 223       *
 224       * @param \stdClass $data
 225       * @return string
 226       */
 227      public function col_select($data) {
 228          global $OUTPUT;
 229  
 230          $checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
 231              'classes' => 'usercheckbox m-1',
 232              'id' => 'user' . $data->id,
 233              'name' => 'user' . $data->id,
 234              'checked' => false,
 235              'label' => get_string('selectitem', 'moodle', fullname($data)),
 236              'labelclasses' => 'accesshide',
 237          ]);
 238  
 239          return $OUTPUT->render($checkbox);
 240      }
 241  
 242      /**
 243       * Generate the fullname column.
 244       *
 245       * @param \stdClass $data
 246       * @return string
 247       */
 248      public function col_fullname($data) {
 249          global $OUTPUT;
 250  
 251          return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->course->id, 'includefullname' => true));
 252      }
 253  
 254      /**
 255       * User roles column.
 256       *
 257       * @param \stdClass $data
 258       * @return string
 259       */
 260      public function col_roles($data) {
 261          global $OUTPUT;
 262  
 263          $roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
 264          $editable = new \core_user\output\user_roles_editable($this->course,
 265                                                                $this->context,
 266                                                                $data,
 267                                                                $this->allroles,
 268                                                                $this->assignableroles,
 269                                                                $this->profileroles,
 270                                                                $roles,
 271                                                                $this->viewableroles);
 272  
 273          return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
 274      }
 275  
 276      /**
 277       * Generate the groups column.
 278       *
 279       * @param \stdClass $data
 280       * @return string
 281       */
 282      public function col_groups($data) {
 283          global $OUTPUT;
 284  
 285          $usergroups = [];
 286          foreach ($this->groups as $coursegroup) {
 287              if (isset($coursegroup->members[$data->id])) {
 288                  $usergroups[] = $coursegroup->id;
 289              }
 290          }
 291          $editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
 292          return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
 293      }
 294  
 295      /**
 296       * Generate the country column.
 297       *
 298       * @param \stdClass $data
 299       * @return string
 300       */
 301      public function col_country($data) {
 302          if (!empty($this->countries[$data->country])) {
 303              return $this->countries[$data->country];
 304          }
 305          return '';
 306      }
 307  
 308      /**
 309       * Generate the last access column.
 310       *
 311       * @param \stdClass $data
 312       * @return string
 313       */
 314      public function col_lastaccess($data) {
 315          if ($data->lastaccess) {
 316              return format_time(time() - $data->lastaccess);
 317          }
 318  
 319          return get_string('never');
 320      }
 321  
 322      /**
 323       * Generate the status column.
 324       *
 325       * @param \stdClass $data The data object.
 326       * @return string
 327       */
 328      public function col_status($data) {
 329          global $CFG, $OUTPUT, $PAGE;
 330  
 331          $enrolstatusoutput = '';
 332          $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
 333          if ($canreviewenrol) {
 334              $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
 335              $fullname = fullname($data, $canviewfullnames);
 336              $coursename = format_string($this->course->fullname, true, array('context' => $this->context));
 337              require_once($CFG->dirroot . '/enrol/locallib.php');
 338              $manager = new \course_enrolment_manager($PAGE, $this->course);
 339              $userenrolments = $manager->get_user_enrolments($data->id);
 340              foreach ($userenrolments as $ue) {
 341                  $timestart = $ue->timestart;
 342                  $timeend = $ue->timeend;
 343                  $timeenrolled = $ue->timecreated;
 344                  $actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
 345                  $instancename = $ue->enrolmentinstancename;
 346  
 347                  // Default status field label and value.
 348                  $status = get_string('participationactive', 'enrol');
 349                  $statusval = status_field::STATUS_ACTIVE;
 350                  switch ($ue->status) {
 351                      case ENROL_USER_ACTIVE:
 352                          $currentdate = new DateTime();
 353                          $now = $currentdate->getTimestamp();
 354                          $isexpired = $timestart > $now || ($timeend > 0 && $timeend < $now);
 355                          $enrolmentdisabled = $ue->enrolmentinstance->status == ENROL_INSTANCE_DISABLED;
 356                          // If user enrolment status has not yet started/already ended or the enrolment instance is disabled.
 357                          if ($isexpired || $enrolmentdisabled) {
 358                              $status = get_string('participationnotcurrent', 'enrol');
 359                              $statusval = status_field::STATUS_NOT_CURRENT;
 360                          }
 361                          break;
 362                      case ENROL_USER_SUSPENDED:
 363                          $status = get_string('participationsuspended', 'enrol');
 364                          $statusval = status_field::STATUS_SUSPENDED;
 365                          break;
 366                  }
 367  
 368                  $statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
 369                      $actions, $timeenrolled);
 370                  $statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
 371                  $enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
 372              }
 373          }
 374          return $enrolstatusoutput;
 375      }
 376  
 377      /**
 378       * This function is used for the extra user fields.
 379       *
 380       * These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
 381       * the list has the potential to increase in the future and we don't want to have to remember to add
 382       * a new method to this class. We also don't want to pollute this class with unnecessary methods.
 383       *
 384       * @param string $colname The column name
 385       * @param \stdClass $data
 386       * @return string
 387       */
 388      public function other_cols($colname, $data) {
 389          // Do not process if it is not a part of the extra fields.
 390          if (!in_array($colname, $this->extrafields)) {
 391              return '';
 392          }
 393  
 394          return s($data->{$colname});
 395      }
 396  
 397      /**
 398       * Query the database for results to display in the table.
 399       *
 400       * @param int $pagesize size of page for paginated displayed table.
 401       * @param bool $useinitialsbar do you want to use the initials bar.
 402       */
 403      public function query_db($pagesize, $useinitialsbar = true) {
 404          list($twhere, $tparams) = $this->get_sql_where();
 405          $psearch = new participants_search($this->course, $this->context, $this->filterset);
 406  
 407          $total = $psearch->get_total_participants_count($twhere, $tparams);
 408  
 409          $this->pagesize($pagesize, $total);
 410  
 411          $sort = $this->get_sql_sort();
 412          if ($sort) {
 413              $sort = 'ORDER BY ' . $sort;
 414          }
 415  
 416          $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
 417  
 418          $this->rawdata = [];
 419          foreach ($rawdata as $user) {
 420              $this->rawdata[$user->id] = $user;
 421          }
 422          $rawdata->close();
 423  
 424          if ($this->rawdata) {
 425              $this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata),
 426                      true, 'c.contextlevel DESC, r.sortorder ASC');
 427          } else {
 428              $this->allroleassignments = [];
 429          }
 430  
 431          // Set initial bars.
 432          if ($useinitialsbar) {
 433              $this->initialbars(true);
 434          }
 435      }
 436  
 437      /**
 438       * Override the table show_hide_link to not show for select column.
 439       *
 440       * @param string $column the column name, index into various names.
 441       * @param int $index numerical index of the column.
 442       * @return string HTML fragment.
 443       */
 444      protected function show_hide_link($column, $index) {
 445          if ($index > 0) {
 446              return parent::show_hide_link($column, $index);
 447          }
 448          return '';
 449      }
 450  
 451      /**
 452       * Set filters and build table structure.
 453       *
 454       * @param filterset $filterset The filterset object to get the filters from.
 455       */
 456      public function set_filterset(filterset $filterset): void {
 457          // Get the context.
 458          $this->courseid = $filterset->get_filter('courseid')->current();
 459          $this->course = get_course($this->courseid);
 460          $this->context = \context_course::instance($this->courseid, MUST_EXIST);
 461  
 462          // Process the filterset.
 463          parent::set_filterset($filterset);
 464      }
 465  
 466      /**
 467       * Guess the base url for the participants table.
 468       */
 469      public function guess_base_url(): void {
 470          $this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
 471      }
 472  
 473      /**
 474       * Get the context of the current table.
 475       *
 476       * Note: This function should not be called until after the filterset has been provided.
 477       *
 478       * @return context
 479       */
 480      public function get_context(): context {
 481          return $this->context;
 482      }
 483  }