Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * 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 = \core_user\fields::get_identity_fields($this->context);
 145          foreach ($extrafields as $field) {
 146              $headers[] = \core_user\fields::get_display_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_default_per_page(20);
 199  
 200          $this->set_attribute('id', 'participants');
 201  
 202          $this->countries = get_string_manager()->get_list_of_countries(true);
 203          $this->extrafields = $extrafields;
 204          if ($canseegroups) {
 205              $this->groups = groups_get_all_groups($this->courseid, 0, 0, 'g.*', true);
 206          }
 207  
 208          // If user has capability to review enrol, show them both role names.
 209          $allrolesnamedisplay = ($canreviewenrol ? ROLENAME_BOTH : ROLENAME_ALIAS);
 210          $this->allroles = role_fix_names(get_all_roles($this->context), $this->context, $allrolesnamedisplay);
 211          $this->assignableroles = get_assignable_roles($this->context, ROLENAME_BOTH, false);
 212          $this->profileroles = get_profile_roles($this->context);
 213          $this->viewableroles = get_viewable_roles($this->context);
 214  
 215          parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
 216  
 217          if (has_capability('moodle/course:enrolreview', $this->context)) {
 218              $params = [
 219                  'contextid' => $this->context->id,
 220                  'uniqueid' => $this->uniqueid,
 221              ];
 222              $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
 223          }
 224      }
 225  
 226      /**
 227       * Generate the select column.
 228       *
 229       * @param \stdClass $data
 230       * @return string
 231       */
 232      public function col_select($data) {
 233          global $OUTPUT;
 234  
 235          $checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
 236              'classes' => 'usercheckbox m-1',
 237              'id' => 'user' . $data->id,
 238              'name' => 'user' . $data->id,
 239              'checked' => false,
 240              'label' => get_string('selectitem', 'moodle', fullname($data)),
 241              'labelclasses' => 'accesshide',
 242          ]);
 243  
 244          return $OUTPUT->render($checkbox);
 245      }
 246  
 247      /**
 248       * Generate the fullname column.
 249       *
 250       * @param \stdClass $data
 251       * @return string
 252       */
 253      public function col_fullname($data) {
 254          global $OUTPUT;
 255  
 256          return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->course->id, 'includefullname' => true));
 257      }
 258  
 259      /**
 260       * User roles column.
 261       *
 262       * @param \stdClass $data
 263       * @return string
 264       */
 265      public function col_roles($data) {
 266          global $OUTPUT;
 267  
 268          $roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
 269          $editable = new \core_user\output\user_roles_editable($this->course,
 270                                                                $this->context,
 271                                                                $data,
 272                                                                $this->allroles,
 273                                                                $this->assignableroles,
 274                                                                $this->profileroles,
 275                                                                $roles,
 276                                                                $this->viewableroles);
 277  
 278          return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
 279      }
 280  
 281      /**
 282       * Generate the groups column.
 283       *
 284       * @param \stdClass $data
 285       * @return string
 286       */
 287      public function col_groups($data) {
 288          global $OUTPUT;
 289  
 290          $usergroups = [];
 291          foreach ($this->groups as $coursegroup) {
 292              if (isset($coursegroup->members[$data->id])) {
 293                  $usergroups[] = $coursegroup->id;
 294              }
 295          }
 296          $editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
 297          return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
 298      }
 299  
 300      /**
 301       * Generate the country column.
 302       *
 303       * @param \stdClass $data
 304       * @return string
 305       */
 306      public function col_country($data) {
 307          if (!empty($this->countries[$data->country])) {
 308              return $this->countries[$data->country];
 309          }
 310          return '';
 311      }
 312  
 313      /**
 314       * Generate the last access column.
 315       *
 316       * @param \stdClass $data
 317       * @return string
 318       */
 319      public function col_lastaccess($data) {
 320          if ($data->lastaccess) {
 321              return format_time(time() - $data->lastaccess);
 322          }
 323  
 324          return get_string('never');
 325      }
 326  
 327      /**
 328       * Generate the status column.
 329       *
 330       * @param \stdClass $data The data object.
 331       * @return string
 332       */
 333      public function col_status($data) {
 334          global $CFG, $OUTPUT, $PAGE;
 335  
 336          $enrolstatusoutput = '';
 337          $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
 338          if ($canreviewenrol) {
 339              $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
 340              $fullname = fullname($data, $canviewfullnames);
 341              $coursename = format_string($this->course->fullname, true, array('context' => $this->context));
 342              require_once($CFG->dirroot . '/enrol/locallib.php');
 343              $manager = new \course_enrolment_manager($PAGE, $this->course);
 344              $userenrolments = $manager->get_user_enrolments($data->id);
 345              foreach ($userenrolments as $ue) {
 346                  $timestart = $ue->timestart;
 347                  $timeend = $ue->timeend;
 348                  $timeenrolled = $ue->timecreated;
 349                  $actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
 350                  $instancename = $ue->enrolmentinstancename;
 351  
 352                  // Default status field label and value.
 353                  $status = get_string('participationactive', 'enrol');
 354                  $statusval = status_field::STATUS_ACTIVE;
 355                  switch ($ue->status) {
 356                      case ENROL_USER_ACTIVE:
 357                          $currentdate = new DateTime();
 358                          $now = $currentdate->getTimestamp();
 359                          $isexpired = $timestart > $now || ($timeend > 0 && $timeend < $now);
 360                          $enrolmentdisabled = $ue->enrolmentinstance->status == ENROL_INSTANCE_DISABLED;
 361                          // If user enrolment status has not yet started/already ended or the enrolment instance is disabled.
 362                          if ($isexpired || $enrolmentdisabled) {
 363                              $status = get_string('participationnotcurrent', 'enrol');
 364                              $statusval = status_field::STATUS_NOT_CURRENT;
 365                          }
 366                          break;
 367                      case ENROL_USER_SUSPENDED:
 368                          $status = get_string('participationsuspended', 'enrol');
 369                          $statusval = status_field::STATUS_SUSPENDED;
 370                          break;
 371                  }
 372  
 373                  $statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
 374                      $actions, $timeenrolled);
 375                  $statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
 376                  $enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
 377              }
 378          }
 379          return $enrolstatusoutput;
 380      }
 381  
 382      /**
 383       * This function is used for the extra user fields.
 384       *
 385       * These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
 386       * the list has the potential to increase in the future and we don't want to have to remember to add
 387       * a new method to this class. We also don't want to pollute this class with unnecessary methods.
 388       *
 389       * @param string $colname The column name
 390       * @param \stdClass $data
 391       * @return string
 392       */
 393      public function other_cols($colname, $data) {
 394          // Do not process if it is not a part of the extra fields.
 395          if (!in_array($colname, $this->extrafields)) {
 396              return '';
 397          }
 398  
 399          return s($data->{$colname});
 400      }
 401  
 402      /**
 403       * Query the database for results to display in the table.
 404       *
 405       * @param int $pagesize size of page for paginated displayed table.
 406       * @param bool $useinitialsbar do you want to use the initials bar.
 407       */
 408      public function query_db($pagesize, $useinitialsbar = true) {
 409          list($twhere, $tparams) = $this->get_sql_where();
 410          $psearch = new participants_search($this->course, $this->context, $this->filterset);
 411  
 412          $total = $psearch->get_total_participants_count($twhere, $tparams);
 413  
 414          $this->pagesize($pagesize, $total);
 415  
 416          $sort = $this->get_sql_sort();
 417          if ($sort) {
 418              $sort = 'ORDER BY ' . $sort;
 419          }
 420  
 421          $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
 422  
 423          $this->rawdata = [];
 424          foreach ($rawdata as $user) {
 425              $this->rawdata[$user->id] = $user;
 426          }
 427          $rawdata->close();
 428  
 429          if ($this->rawdata) {
 430              $this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata),
 431                      true, 'c.contextlevel DESC, r.sortorder ASC');
 432          } else {
 433              $this->allroleassignments = [];
 434          }
 435  
 436          // Set initial bars.
 437          if ($useinitialsbar) {
 438              $this->initialbars(true);
 439          }
 440      }
 441  
 442      /**
 443       * Override the table show_hide_link to not show for select column.
 444       *
 445       * @param string $column the column name, index into various names.
 446       * @param int $index numerical index of the column.
 447       * @return string HTML fragment.
 448       */
 449      protected function show_hide_link($column, $index) {
 450          if ($index > 0) {
 451              return parent::show_hide_link($column, $index);
 452          }
 453          return '';
 454      }
 455  
 456      /**
 457       * Set filters and build table structure.
 458       *
 459       * @param filterset $filterset The filterset object to get the filters from.
 460       */
 461      public function set_filterset(filterset $filterset): void {
 462          // Get the context.
 463          $this->courseid = $filterset->get_filter('courseid')->current();
 464          $this->course = get_course($this->courseid);
 465          $this->context = \context_course::instance($this->courseid, MUST_EXIST);
 466  
 467          // Process the filterset.
 468          parent::set_filterset($filterset);
 469      }
 470  
 471      /**
 472       * Guess the base url for the participants table.
 473       */
 474      public function guess_base_url(): void {
 475          $this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
 476      }
 477  
 478      /**
 479       * Get the context of the current table.
 480       *
 481       * Note: This function should not be called until after the filterset has been provided.
 482       *
 483       * @return context
 484       */
 485      public function get_context(): context {
 486          return $this->context;
 487      }
 488  }