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.
/enrol/ -> locallib.php (source)

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 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   * This file contains the course_enrolment_manager class which is used to interface
  19   * with the functions that exist in enrollib.php in relation to a single course.
  20   *
  21   * @package    core_enrol
  22   * @copyright  2010 Sam Hemelryk
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  use core_user\fields;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  /**
  31   * This class provides a targeted tied together means of interfacing the enrolment
  32   * tasks together with a course.
  33   *
  34   * It is provided as a convenience more than anything else.
  35   *
  36   * @copyright 2010 Sam Hemelryk
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class course_enrolment_manager {
  40  
  41      /**
  42       * The course context
  43       * @var context
  44       */
  45      protected $context;
  46      /**
  47       * The course we are managing enrolments for
  48       * @var stdClass
  49       */
  50      protected $course = null;
  51      /**
  52       * Limits the focus of the manager to one enrolment plugin instance
  53       * @var string
  54       */
  55      protected $instancefilter = null;
  56      /**
  57       * Limits the focus of the manager to users with specified role
  58       * @var int
  59       */
  60      protected $rolefilter = 0;
  61      /**
  62       * Limits the focus of the manager to users who match search string
  63       * @var string
  64       */
  65      protected $searchfilter = '';
  66      /**
  67       * Limits the focus of the manager to users in specified group
  68       * @var int
  69       */
  70      protected $groupfilter = 0;
  71      /**
  72       * Limits the focus of the manager to users who match status active/inactive
  73       * @var int
  74       */
  75      protected $statusfilter = -1;
  76  
  77      /**
  78       * The total number of users enrolled in the course
  79       * Populated by course_enrolment_manager::get_total_users
  80       * @var int
  81       */
  82      protected $totalusers = null;
  83      /**
  84       * An array of users currently enrolled in the course
  85       * Populated by course_enrolment_manager::get_users
  86       * @var array
  87       */
  88      protected $users = array();
  89  
  90      /**
  91       * An array of users who have roles within this course but who have not
  92       * been enrolled in the course
  93       * @var array
  94       */
  95      protected $otherusers = array();
  96  
  97      /**
  98       * The total number of users who hold a role within the course but who
  99       * arn't enrolled.
 100       * @var int
 101       */
 102      protected $totalotherusers = null;
 103  
 104      /**
 105       * The current moodle_page object
 106       * @var moodle_page
 107       */
 108      protected $moodlepage = null;
 109  
 110      /**#@+
 111       * These variables are used to cache the information this class uses
 112       * please never use these directly instead use their get_ counterparts.
 113       * @access private
 114       * @var array
 115       */
 116      private $_instancessql = null;
 117      private $_instances = null;
 118      private $_inames = null;
 119      private $_plugins = null;
 120      private $_allplugins = null;
 121      private $_roles = null;
 122      private $_visibleroles = null;
 123      private $_assignableroles = null;
 124      private $_assignablerolesothers = null;
 125      private $_groups = null;
 126      /**#@-*/
 127  
 128      /**
 129       * Constructs the course enrolment manager
 130       *
 131       * @param moodle_page $moodlepage
 132       * @param stdClass $course
 133       * @param string $instancefilter
 134       * @param int $rolefilter If non-zero, filters to users with specified role
 135       * @param string $searchfilter If non-blank, filters to users with search text
 136       * @param int $groupfilter if non-zero, filter users with specified group
 137       * @param int $statusfilter if not -1, filter users with active/inactive enrollment.
 138       */
 139      public function __construct(moodle_page $moodlepage, $course, $instancefilter = null,
 140              $rolefilter = 0, $searchfilter = '', $groupfilter = 0, $statusfilter = -1) {
 141          $this->moodlepage = $moodlepage;
 142          $this->context = context_course::instance($course->id);
 143          $this->course = $course;
 144          $this->instancefilter = $instancefilter;
 145          $this->rolefilter = $rolefilter;
 146          $this->searchfilter = $searchfilter;
 147          $this->groupfilter = $groupfilter;
 148          $this->statusfilter = $statusfilter;
 149      }
 150  
 151      /**
 152       * Returns the current moodle page
 153       * @return moodle_page
 154       */
 155      public function get_moodlepage() {
 156          return $this->moodlepage;
 157      }
 158  
 159      /**
 160       * Returns the total number of enrolled users in the course.
 161       *
 162       * If a filter was specificed this will be the total number of users enrolled
 163       * in this course by means of that instance.
 164       *
 165       * @global moodle_database $DB
 166       * @return int
 167       */
 168      public function get_total_users() {
 169          global $DB;
 170          if ($this->totalusers === null) {
 171              list($instancessql, $params, $filter) = $this->get_instance_sql();
 172              list($filtersql, $moreparams) = $this->get_filter_sql();
 173              $params += $moreparams;
 174              $sqltotal = "SELECT COUNT(DISTINCT u.id)
 175                             FROM {user} u
 176                             JOIN {user_enrolments} ue ON (ue.userid = u.id  AND ue.enrolid $instancessql)
 177                             JOIN {enrol} e ON (e.id = ue.enrolid)";
 178              if ($this->groupfilter) {
 179                  $sqltotal .= " LEFT JOIN ({groups_members} gm JOIN {groups} g ON (g.id = gm.groupid))
 180                                           ON (u.id = gm.userid AND g.courseid = e.courseid)";
 181              }
 182              $sqltotal .= "WHERE $filtersql";
 183              $this->totalusers = (int)$DB->count_records_sql($sqltotal, $params);
 184          }
 185          return $this->totalusers;
 186      }
 187  
 188      /**
 189       * Returns the total number of enrolled users in the course.
 190       *
 191       * If a filter was specificed this will be the total number of users enrolled
 192       * in this course by means of that instance.
 193       *
 194       * @global moodle_database $DB
 195       * @return int
 196       */
 197      public function get_total_other_users() {
 198          global $DB;
 199          if ($this->totalotherusers === null) {
 200              list($ctxcondition, $params) = $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'ctx');
 201              $params['courseid'] = $this->course->id;
 202              $sql = "SELECT COUNT(DISTINCT u.id)
 203                        FROM {role_assignments} ra
 204                        JOIN {user} u ON u.id = ra.userid
 205                        JOIN {context} ctx ON ra.contextid = ctx.id
 206                   LEFT JOIN (
 207                             SELECT ue.id, ue.userid
 208                               FROM {user_enrolments} ue
 209                          LEFT JOIN {enrol} e ON e.id=ue.enrolid
 210                              WHERE e.courseid = :courseid
 211                           ) ue ON ue.userid=u.id
 212                       WHERE ctx.id $ctxcondition AND
 213                             ue.id IS NULL";
 214              $this->totalotherusers = (int)$DB->count_records_sql($sql, $params);
 215          }
 216          return $this->totalotherusers;
 217      }
 218  
 219      /**
 220       * Gets all of the users enrolled in this course.
 221       *
 222       * If a filter was specified this will be the users who were enrolled
 223       * in this course by means of that instance. If role or search filters were
 224       * specified then these will also be applied.
 225       *
 226       * @global moodle_database $DB
 227       * @param string $sort
 228       * @param string $direction ASC or DESC
 229       * @param int $page First page should be 0
 230       * @param int $perpage Defaults to 25
 231       * @return array
 232       */
 233      public function get_users($sort, $direction='ASC', $page=0, $perpage=25) {
 234          global $DB;
 235          if ($direction !== 'ASC') {
 236              $direction = 'DESC';
 237          }
 238          $key = md5("$sort-$direction-$page-$perpage");
 239          if (!array_key_exists($key, $this->users)) {
 240              list($instancessql, $params, $filter) = $this->get_instance_sql();
 241              list($filtersql, $moreparams) = $this->get_filter_sql();
 242              $params += $moreparams;
 243              $userfields = fields::for_identity($this->get_context())->with_userpic()->excluding('lastaccess');
 244              ['selects' => $fieldselect, 'joins' => $fieldjoin, 'params' => $fieldjoinparams] =
 245                      (array)$userfields->get_sql('u', true, '', '', false);
 246              $params += $fieldjoinparams;
 247              $sql = "SELECT DISTINCT $fieldselect, COALESCE(ul.timeaccess, 0) AS lastcourseaccess
 248                        FROM {user} u
 249                        JOIN {user_enrolments} ue ON (ue.userid = u.id  AND ue.enrolid $instancessql)
 250                        JOIN {enrol} e ON (e.id = ue.enrolid)
 251                             $fieldjoin
 252                   LEFT JOIN {user_lastaccess} ul ON (ul.courseid = e.courseid AND ul.userid = u.id)";
 253              if ($this->groupfilter) {
 254                  $sql .= " LEFT JOIN ({groups_members} gm JOIN {groups} g ON (g.id = gm.groupid))
 255                                      ON (u.id = gm.userid AND g.courseid = e.courseid)";
 256              }
 257              $sql .= "WHERE $filtersql
 258                    ORDER BY $sort $direction";
 259              $this->users[$key] = $DB->get_records_sql($sql, $params, $page*$perpage, $perpage);
 260          }
 261          return $this->users[$key];
 262      }
 263  
 264      /**
 265       * Obtains WHERE clause to filter results by defined search and role filter
 266       * (instance filter is handled separately in JOIN clause, see
 267       * get_instance_sql).
 268       *
 269       * @return array Two-element array with SQL and params for WHERE clause
 270       */
 271      protected function get_filter_sql() {
 272          global $DB;
 273  
 274          // Search condition.
 275          // TODO Does not support custom user profile fields (MDL-70456).
 276          $extrafields = fields::get_identity_fields($this->get_context(), false);
 277          list($sql, $params) = users_search_sql($this->searchfilter, 'u', true, $extrafields);
 278  
 279          // Role condition.
 280          if ($this->rolefilter) {
 281              // Get context SQL.
 282              $contextids = $this->context->get_parent_context_ids();
 283              $contextids[] = $this->context->id;
 284              list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
 285              $params += $contextparams;
 286  
 287              // Role check condition.
 288              $sql .= " AND (SELECT COUNT(1) FROM {role_assignments} ra WHERE ra.userid = u.id " .
 289                      "AND ra.roleid = :roleid AND ra.contextid $contextsql) > 0";
 290              $params['roleid'] = $this->rolefilter;
 291          }
 292  
 293          // Group condition.
 294          if ($this->groupfilter) {
 295              if ($this->groupfilter < 0) {
 296                  // Show users who are not in any group.
 297                  $sql .= " AND gm.groupid IS NULL";
 298              } else {
 299                  $sql .= " AND gm.groupid = :groupid";
 300                  $params['groupid'] = $this->groupfilter;
 301              }
 302          }
 303  
 304          // Status condition.
 305          if ($this->statusfilter === ENROL_USER_ACTIVE) {
 306              $sql .= " AND ue.status = :active AND e.status = :enabled AND ue.timestart < :now1
 307                      AND (ue.timeend = 0 OR ue.timeend > :now2)";
 308              $now = round(time(), -2); // rounding helps caching in DB
 309              $params += array('enabled' => ENROL_INSTANCE_ENABLED,
 310                               'active' => ENROL_USER_ACTIVE,
 311                               'now1' => $now,
 312                               'now2' => $now);
 313          } else if ($this->statusfilter === ENROL_USER_SUSPENDED) {
 314              $sql .= " AND (ue.status = :inactive OR e.status = :disabled OR ue.timestart > :now1
 315                      OR (ue.timeend <> 0 AND ue.timeend < :now2))";
 316              $now = round(time(), -2); // rounding helps caching in DB
 317              $params += array('disabled' => ENROL_INSTANCE_DISABLED,
 318                               'inactive' => ENROL_USER_SUSPENDED,
 319                               'now1' => $now,
 320                               'now2' => $now);
 321          }
 322  
 323          return array($sql, $params);
 324      }
 325  
 326      /**
 327       * Gets and array of other users.
 328       *
 329       * Other users are users who have been assigned roles or inherited roles
 330       * within this course but who have not been enrolled in the course
 331       *
 332       * @global moodle_database $DB
 333       * @param string $sort
 334       * @param string $direction
 335       * @param int $page
 336       * @param int $perpage
 337       * @return array
 338       */
 339      public function get_other_users($sort, $direction='ASC', $page=0, $perpage=25) {
 340          global $DB;
 341          if ($direction !== 'ASC') {
 342              $direction = 'DESC';
 343          }
 344          $key = md5("$sort-$direction-$page-$perpage");
 345          if (!array_key_exists($key, $this->otherusers)) {
 346              list($ctxcondition, $params) = $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'ctx');
 347              $params['courseid'] = $this->course->id;
 348              $params['cid'] = $this->course->id;
 349              $userfields = fields::for_identity($this->get_context())->with_userpic();
 350              ['selects' => $fieldselect, 'joins' => $fieldjoin, 'params' => $fieldjoinparams] =
 351                      (array)$userfields->get_sql('u', true);
 352              $params += $fieldjoinparams;
 353              $sql = "SELECT ra.id as raid, ra.contextid, ra.component, ctx.contextlevel, ra.roleid,
 354                             coalesce(u.lastaccess,0) AS lastaccess
 355                             $fieldselect
 356                        FROM {role_assignments} ra
 357                        JOIN {user} u ON u.id = ra.userid
 358                        JOIN {context} ctx ON ra.contextid = ctx.id
 359                             $fieldjoin
 360                   LEFT JOIN (
 361                         SELECT ue.id, ue.userid
 362                           FROM {user_enrolments} ue
 363                           JOIN {enrol} e ON e.id = ue.enrolid
 364                          WHERE e.courseid = :courseid
 365                         ) ue ON ue.userid=u.id
 366                       WHERE ctx.id $ctxcondition AND
 367                             ue.id IS NULL
 368                    ORDER BY $sort $direction, ctx.depth DESC";
 369              $this->otherusers[$key] = $DB->get_records_sql($sql, $params, $page*$perpage, $perpage);
 370          }
 371          return $this->otherusers[$key];
 372      }
 373  
 374      /**
 375       * Helper method used by {@link get_potential_users()} and {@link search_other_users()}.
 376       *
 377       * @param string $search the search term, if any.
 378       * @param bool $searchanywhere Can the search term be anywhere, or must it be at the start.
 379       * @return array with three elements:
 380       *     string list of fields to SELECT,
 381       *     string possible database joins for user fields
 382       *     string contents of SQL WHERE clause,
 383       *     array query params. Note that the SQL snippets use named parameters.
 384       */
 385      protected function get_basic_search_conditions($search, $searchanywhere) {
 386          global $DB, $CFG;
 387  
 388          // Get custom user field SQL used for querying all the fields we need (identity, name, and
 389          // user picture).
 390          $userfields = fields::for_identity($this->context)->with_name()->with_userpic()
 391                  ->excluding('username', 'lastaccess', 'maildisplay');
 392          ['selects' => $fieldselects, 'joins' => $fieldjoins, 'params' => $params, 'mappings' => $mappings] =
 393                  (array)$userfields->get_sql('u', true, '', '', false);
 394  
 395          // Searchable fields are only the identity and name ones (not userpic, and without exclusions).
 396          $searchablefields = fields::for_identity($this->context)->with_name();
 397          $searchable = array_fill_keys($searchablefields->get_required_fields(), true);
 398          if (array_key_exists('username', $searchable)) {
 399              // Add the username into the mappings list from the other query, because it was excluded.
 400              $mappings['username'] = 'u.username';
 401          }
 402  
 403          // Add some additional sensible conditions
 404          $tests = array("u.id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1');
 405          $params['guestid'] = $CFG->siteguest;
 406          if (!empty($search)) {
 407              // Include identity and name fields as conditions.
 408              foreach ($mappings as $fieldname => $fieldsql) {
 409                  if (array_key_exists($fieldname, $searchable)) {
 410                      $conditions[] = $fieldsql;
 411                  }
 412              }
 413              $conditions[] = $DB->sql_fullname('u.firstname', 'u.lastname');
 414              if ($searchanywhere) {
 415                  $searchparam = '%' . $search . '%';
 416              } else {
 417                  $searchparam = $search . '%';
 418              }
 419              $i = 0;
 420              foreach ($conditions as $key => $condition) {
 421                  $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false);
 422                  $params["con{$i}00"] = $searchparam;
 423                  $i++;
 424              }
 425              $tests[] = '(' . implode(' OR ', $conditions) . ')';
 426          }
 427          $wherecondition = implode(' AND ', $tests);
 428  
 429          $selects = $fieldselects . ', u.username, u.lastaccess, u.maildisplay';
 430          return [$selects, $fieldjoins, $params, $wherecondition];
 431      }
 432  
 433      /**
 434       * Helper method used by {@link get_potential_users()} and {@link search_other_users()}.
 435       *
 436       * @param string $search the search string, if any.
 437       * @param string $fields the first bit of the SQL when returning some users.
 438       * @param string $countfields fhe first bit of the SQL when counting the users.
 439       * @param string $sql the bulk of the SQL statement.
 440       * @param array $params query parameters.
 441       * @param int $page which page number of the results to show.
 442       * @param int $perpage number of users per page.
 443       * @param int $addedenrollment number of users added to enrollment.
 444       * @param bool $returnexactcount Return the exact total users using count_record or not.
 445       * @return array with two or three elements:
 446       *      int totalusers Number users matching the search. (This element only exist if $returnexactcount was set to true)
 447       *      array users List of user objects returned by the query.
 448       *      boolean moreusers True if there are still more users, otherwise is False.
 449       * @throws dml_exception
 450       */
 451      protected function execute_search_queries($search, $fields, $countfields, $sql, array $params, $page, $perpage,
 452              $addedenrollment = 0, $returnexactcount = false) {
 453          global $DB, $CFG;
 454  
 455          list($sort, $sortparams) = users_order_by_sql('u', $search, $this->get_context());
 456          $order = ' ORDER BY ' . $sort;
 457  
 458          $totalusers = 0;
 459          $moreusers = false;
 460          $results = [];
 461  
 462          $availableusers = $DB->get_records_sql($fields . $sql . $order,
 463                  array_merge($params, $sortparams), ($page * $perpage) - $addedenrollment, $perpage + 1);
 464          if ($availableusers) {
 465              $totalusers = count($availableusers);
 466              $moreusers = $totalusers > $perpage;
 467  
 468              if ($moreusers) {
 469                  // We need to discard the last record.
 470                  array_pop($availableusers);
 471              }
 472  
 473              if ($returnexactcount && $moreusers) {
 474                  // There is more data. We need to do the exact count.
 475                  $totalusers = $DB->count_records_sql($countfields . $sql, $params);
 476              }
 477          }
 478  
 479          $results['users'] = $availableusers;
 480          $results['moreusers'] = $moreusers;
 481  
 482          if ($returnexactcount) {
 483              // Include totalusers in result if $returnexactcount flag is true.
 484              $results['totalusers'] = $totalusers;
 485          }
 486  
 487          return $results;
 488      }
 489  
 490      /**
 491       * Gets an array of the users that can be enrolled in this course.
 492       *
 493       * @global moodle_database $DB
 494       * @param int $enrolid
 495       * @param string $search
 496       * @param bool $searchanywhere
 497       * @param int $page Defaults to 0
 498       * @param int $perpage Defaults to 25
 499       * @param int $addedenrollment Defaults to 0
 500       * @param bool $returnexactcount Return the exact total users using count_record or not.
 501       * @return array with two or three elements:
 502       *      int totalusers Number users matching the search. (This element only exist if $returnexactcount was set to true)
 503       *      array users List of user objects returned by the query.
 504       *      boolean moreusers True if there are still more users, otherwise is False.
 505       * @throws dml_exception
 506       */
 507      public function get_potential_users($enrolid, $search = '', $searchanywhere = false, $page = 0, $perpage = 25,
 508              $addedenrollment = 0, $returnexactcount = false) {
 509          global $DB;
 510  
 511          [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere);
 512  
 513          $fields      = 'SELECT '.$ufields;
 514          $countfields = 'SELECT COUNT(1)';
 515          $sql = " FROM {user} u
 516                        $joins
 517              LEFT JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = :enrolid)
 518                  WHERE $wherecondition
 519                        AND ue.id IS NULL";
 520          $params['enrolid'] = $enrolid;
 521  
 522          return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, $addedenrollment,
 523                  $returnexactcount);
 524      }
 525  
 526      /**
 527       * Searches other users and returns paginated results
 528       *
 529       * @global moodle_database $DB
 530       * @param string $search
 531       * @param bool $searchanywhere
 532       * @param int $page Starting at 0
 533       * @param int $perpage
 534       * @param bool $returnexactcount Return the exact total users using count_record or not.
 535       * @return array with two or three elements:
 536       *      int totalusers Number users matching the search. (This element only exist if $returnexactcount was set to true)
 537       *      array users List of user objects returned by the query.
 538       *      boolean moreusers True if there are still more users, otherwise is False.
 539       * @throws dml_exception
 540       */
 541      public function search_other_users($search = '', $searchanywhere = false, $page = 0, $perpage = 25, $returnexactcount = false) {
 542          global $DB, $CFG;
 543  
 544          [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere);
 545  
 546          $fields      = 'SELECT ' . $ufields;
 547          $countfields = 'SELECT COUNT(u.id)';
 548          $sql   = " FROM {user} u
 549                          $joins
 550                LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid = :contextid)
 551                    WHERE $wherecondition
 552                      AND ra.id IS NULL";
 553          $params['contextid'] = $this->context->id;
 554  
 555          return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, 0, $returnexactcount);
 556      }
 557  
 558      /**
 559       * Searches through the enrolled users in this course.
 560       *
 561       * @param string $search The search term.
 562       * @param bool $searchanywhere Can the search term be anywhere, or must it be at the start.
 563       * @param int $page Starting at 0.
 564       * @param int $perpage Number of users returned per page.
 565       * @param bool $returnexactcount Return the exact total users using count_record or not.
 566       * @return array with two or three elements:
 567       *      int totalusers Number users matching the search. (This element only exist if $returnexactcount was set to true)
 568       *      array users List of user objects returned by the query.
 569       *      boolean moreusers True if there are still more users, otherwise is False.
 570       */
 571      public function search_users(string $search = '', bool $searchanywhere = false, int $page = 0, int $perpage = 25,
 572              bool $returnexactcount = false) {
 573          global $USER;
 574  
 575          [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere);
 576  
 577          $groupmode = groups_get_course_groupmode($this->course);
 578          if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $this->context)) {
 579              $groups = groups_get_all_groups($this->course->id, $USER->id, 0, 'g.id');
 580              $groupids = array_column($groups, 'id');
 581          } else {
 582              $groupids = [];
 583          }
 584  
 585          [$enrolledsql, $enrolledparams] = get_enrolled_sql($this->context, '', $groupids);
 586  
 587          $fields      = 'SELECT ' . $ufields;
 588          $countfields = 'SELECT COUNT(u.id)';
 589          $sql = " FROM {user} u
 590                        $joins
 591                   JOIN ($enrolledsql) je ON je.id = u.id
 592                  WHERE $wherecondition";
 593  
 594          $params = array_merge($params, $enrolledparams);
 595  
 596          return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, 0, $returnexactcount);
 597      }
 598  
 599      /**
 600       * Gets an array containing some SQL to user for when selecting, params for
 601       * that SQL, and the filter that was used in constructing the sql.
 602       *
 603       * @global moodle_database $DB
 604       * @return array
 605       */
 606      protected function get_instance_sql() {
 607          global $DB;
 608          if ($this->_instancessql === null) {
 609              $instances = $this->get_enrolment_instances();
 610              $filter = $this->get_enrolment_filter();
 611              if ($filter && array_key_exists($filter, $instances)) {
 612                  $sql = " = :ifilter";
 613                  $params = array('ifilter'=>$filter);
 614              } else {
 615                  $filter = 0;
 616                  if ($instances) {
 617                      list($sql, $params) = $DB->get_in_or_equal(array_keys($this->get_enrolment_instances()), SQL_PARAMS_NAMED);
 618                  } else {
 619                      // no enabled instances, oops, we should probably say something
 620                      $sql = "= :never";
 621                      $params = array('never'=>-1);
 622                  }
 623              }
 624              $this->instancefilter = $filter;
 625              $this->_instancessql = array($sql, $params, $filter);
 626          }
 627          return $this->_instancessql;
 628      }
 629  
 630      /**
 631       * Returns all of the enrolment instances for this course.
 632       *
 633       * @param bool $onlyenabled Whether to return data from enabled enrolment instance names only.
 634       * @return array
 635       */
 636      public function get_enrolment_instances($onlyenabled = false) {
 637          if ($this->_instances === null) {
 638              $this->_instances = enrol_get_instances($this->course->id, $onlyenabled);
 639          }
 640          return $this->_instances;
 641      }
 642  
 643      /**
 644       * Returns the names for all of the enrolment instances for this course.
 645       *
 646       * @param bool $onlyenabled Whether to return data from enabled enrolment instance names only.
 647       * @return array
 648       */
 649      public function get_enrolment_instance_names($onlyenabled = false) {
 650          if ($this->_inames === null) {
 651              $instances = $this->get_enrolment_instances($onlyenabled);
 652              $plugins = $this->get_enrolment_plugins(false);
 653              foreach ($instances as $key=>$instance) {
 654                  if (!isset($plugins[$instance->enrol])) {
 655                      // weird, some broken stuff in plugin
 656                      unset($instances[$key]);
 657                      continue;
 658                  }
 659                  $this->_inames[$key] = $plugins[$instance->enrol]->get_instance_name($instance);
 660              }
 661          }
 662          return $this->_inames;
 663      }
 664  
 665      /**
 666       * Gets all of the enrolment plugins that are available for this course.
 667       *
 668       * @param bool $onlyenabled return only enabled enrol plugins
 669       * @return array
 670       */
 671      public function get_enrolment_plugins($onlyenabled = true) {
 672          if ($this->_plugins === null) {
 673              $this->_plugins = enrol_get_plugins(true);
 674          }
 675  
 676          if ($onlyenabled) {
 677              return $this->_plugins;
 678          }
 679  
 680          if ($this->_allplugins === null) {
 681              // Make sure we have the same objects in _allplugins and _plugins.
 682              $this->_allplugins = $this->_plugins;
 683              foreach (enrol_get_plugins(false) as $name=>$plugin) {
 684                  if (!isset($this->_allplugins[$name])) {
 685                      $this->_allplugins[$name] = $plugin;
 686                  }
 687              }
 688          }
 689  
 690          return $this->_allplugins;
 691      }
 692  
 693      /**
 694       * Gets all of the roles this course can contain.
 695       *
 696       * @return array
 697       */
 698      public function get_all_roles() {
 699          if ($this->_roles === null) {
 700              $this->_roles = role_fix_names(get_all_roles($this->context), $this->context);
 701          }
 702          return $this->_roles;
 703      }
 704  
 705      /**
 706       * Gets all of the roles this course can contain.
 707       *
 708       * @return array
 709       */
 710      public function get_viewable_roles() {
 711          if ($this->_visibleroles === null) {
 712              $this->_visibleroles = get_viewable_roles($this->context);
 713          }
 714          return $this->_visibleroles;
 715      }
 716  
 717      /**
 718       * Gets all of the assignable roles for this course.
 719       *
 720       * @return array
 721       */
 722      public function get_assignable_roles($otherusers = false) {
 723          if ($this->_assignableroles === null) {
 724              $this->_assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false); // verifies unassign access control too
 725          }
 726  
 727          if ($otherusers) {
 728              if (!is_array($this->_assignablerolesothers)) {
 729                  $this->_assignablerolesothers = array();
 730                  list($courseviewroles, $ignored) = get_roles_with_cap_in_context($this->context, 'moodle/course:view');
 731                  foreach ($this->_assignableroles as $roleid=>$role) {
 732                      if (isset($courseviewroles[$roleid])) {
 733                          $this->_assignablerolesothers[$roleid] = $role;
 734                      }
 735                  }
 736              }
 737              return $this->_assignablerolesothers;
 738          } else {
 739              return $this->_assignableroles;
 740          }
 741      }
 742  
 743      /**
 744       * Gets all of the assignable roles for this course, wrapped in an array to ensure
 745       * role sort order is not lost during json deserialisation.
 746       *
 747       * @param boolean $otherusers whether to include the assignable roles for other users
 748       * @return array
 749       */
 750      public function get_assignable_roles_for_json($otherusers = false) {
 751          $rolesarray = array();
 752          $assignable = $this->get_assignable_roles($otherusers);
 753          foreach ($assignable as $id => $role) {
 754              $rolesarray[] = array('id' => $id, 'name' => $role);
 755          }
 756          return $rolesarray;
 757      }
 758  
 759      /**
 760       * Gets all of the groups for this course.
 761       *
 762       * @return array
 763       */
 764      public function get_all_groups() {
 765          if ($this->_groups === null) {
 766              $this->_groups = groups_get_all_groups($this->course->id);
 767              foreach ($this->_groups as $gid=>$group) {
 768                  $this->_groups[$gid]->name = format_string($group->name);
 769              }
 770          }
 771          return $this->_groups;
 772      }
 773  
 774      /**
 775       * Unenrols a user from the course given the users ue entry
 776       *
 777       * @global moodle_database $DB
 778       * @param stdClass $ue
 779       * @return bool
 780       */
 781      public function unenrol_user($ue) {
 782          global $DB;
 783          list ($instance, $plugin) = $this->get_user_enrolment_components($ue);
 784          if ($instance && $plugin && $plugin->allow_unenrol_user($instance, $ue) && has_capability("enrol/$instance->enrol:unenrol", $this->context)) {
 785              $plugin->unenrol_user($instance, $ue->userid);
 786              return true;
 787          }
 788          return false;
 789      }
 790  
 791      /**
 792       * Given a user enrolment record this method returns the plugin and enrolment
 793       * instance that relate to it.
 794       *
 795       * @param stdClass|int $userenrolment
 796       * @return array array($instance, $plugin)
 797       */
 798      public function get_user_enrolment_components($userenrolment) {
 799          global $DB;
 800          if (is_numeric($userenrolment)) {
 801              $userenrolment = $DB->get_record('user_enrolments', array('id'=>(int)$userenrolment));
 802          }
 803          $instances = $this->get_enrolment_instances();
 804          $plugins = $this->get_enrolment_plugins(false);
 805          if (!$userenrolment || !isset($instances[$userenrolment->enrolid])) {
 806              return array(false, false);
 807          }
 808          $instance = $instances[$userenrolment->enrolid];
 809          $plugin = $plugins[$instance->enrol];
 810          return array($instance, $plugin);
 811      }
 812  
 813      /**
 814       * Removes an assigned role from a user.
 815       *
 816       * @global moodle_database $DB
 817       * @param int $userid
 818       * @param int $roleid
 819       * @return bool
 820       */
 821      public function unassign_role_from_user($userid, $roleid) {
 822          global $DB;
 823          // Admins may unassign any role, others only those they could assign.
 824          if (!is_siteadmin() and !array_key_exists($roleid, $this->get_assignable_roles())) {
 825              if (defined('AJAX_SCRIPT')) {
 826                  throw new moodle_exception('invalidrole');
 827              }
 828              return false;
 829          }
 830          $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
 831          $ras = $DB->get_records('role_assignments', array('contextid'=>$this->context->id, 'userid'=>$user->id, 'roleid'=>$roleid));
 832          foreach ($ras as $ra) {
 833              if ($ra->component) {
 834                  if (strpos($ra->component, 'enrol_') !== 0) {
 835                      continue;
 836                  }
 837                  if (!$plugin = enrol_get_plugin(substr($ra->component, 6))) {
 838                      continue;
 839                  }
 840                  if ($plugin->roles_protected()) {
 841                      continue;
 842                  }
 843              }
 844              role_unassign($ra->roleid, $ra->userid, $ra->contextid, $ra->component, $ra->itemid);
 845          }
 846          return true;
 847      }
 848  
 849      /**
 850       * Assigns a role to a user.
 851       *
 852       * @param int $roleid
 853       * @param int $userid
 854       * @return int|false
 855       */
 856      public function assign_role_to_user($roleid, $userid) {
 857          require_capability('moodle/role:assign', $this->context);
 858          if (!array_key_exists($roleid, $this->get_assignable_roles())) {
 859              if (defined('AJAX_SCRIPT')) {
 860                  throw new moodle_exception('invalidrole');
 861              }
 862              return false;
 863          }
 864          return role_assign($roleid, $userid, $this->context->id, '', NULL);
 865      }
 866  
 867      /**
 868       * Adds a user to a group
 869       *
 870       * @param stdClass $user
 871       * @param int $groupid
 872       * @return bool
 873       */
 874      public function add_user_to_group($user, $groupid) {
 875          require_capability('moodle/course:managegroups', $this->context);
 876          $group = $this->get_group($groupid);
 877          if (!$group) {
 878              return false;
 879          }
 880          return groups_add_member($group->id, $user->id);
 881      }
 882  
 883      /**
 884       * Removes a user from a group
 885       *
 886       * @global moodle_database $DB
 887       * @param StdClass $user
 888       * @param int $groupid
 889       * @return bool
 890       */
 891      public function remove_user_from_group($user, $groupid) {
 892          global $DB;
 893          require_capability('moodle/course:managegroups', $this->context);
 894          $group = $this->get_group($groupid);
 895          if (!groups_remove_member_allowed($group, $user)) {
 896              return false;
 897          }
 898          if (!$group) {
 899              return false;
 900          }
 901          return groups_remove_member($group, $user);
 902      }
 903  
 904      /**
 905       * Gets the requested group
 906       *
 907       * @param int $groupid
 908       * @return stdClass|int
 909       */
 910      public function get_group($groupid) {
 911          $groups = $this->get_all_groups();
 912          if (!array_key_exists($groupid, $groups)) {
 913              return false;
 914          }
 915          return $groups[$groupid];
 916      }
 917  
 918      /**
 919       * Edits an enrolment
 920       *
 921       * @param stdClass $userenrolment
 922       * @param stdClass $data
 923       * @return bool
 924       */
 925      public function edit_enrolment($userenrolment, $data) {
 926          //Only allow editing if the user has the appropriate capability
 927          //Already checked in /user/index.php but checking again in case this function is called from elsewhere
 928          list($instance, $plugin) = $this->get_user_enrolment_components($userenrolment);
 929          if ($instance && $plugin && $plugin->allow_manage($instance) && has_capability("enrol/$instance->enrol:manage", $this->context)) {
 930              if (!isset($data->status)) {
 931                  $data->status = $userenrolment->status;
 932              }
 933              $plugin->update_user_enrol($instance, $userenrolment->userid, $data->status, $data->timestart, $data->timeend);
 934              return true;
 935          }
 936          return false;
 937      }
 938  
 939      /**
 940       * Returns the current enrolment filter that is being applied by this class
 941       * @return string
 942       */
 943      public function get_enrolment_filter() {
 944          return $this->instancefilter;
 945      }
 946  
 947      /**
 948       * Gets the roles assigned to this user that are applicable for this course.
 949       *
 950       * @param int $userid
 951       * @return array
 952       */
 953      public function get_user_roles($userid) {
 954          $roles = array();
 955          $ras = get_user_roles($this->context, $userid, true, 'c.contextlevel DESC, r.sortorder ASC');
 956          $plugins = $this->get_enrolment_plugins(false);
 957          foreach ($ras as $ra) {
 958              if ($ra->contextid != $this->context->id) {
 959                  if (!array_key_exists($ra->roleid, $roles)) {
 960                      $roles[$ra->roleid] = null;
 961                  }
 962                  // higher ras, course always takes precedence
 963                  continue;
 964              }
 965              if (array_key_exists($ra->roleid, $roles) && $roles[$ra->roleid] === false) {
 966                  continue;
 967              }
 968              $changeable = true;
 969              if ($ra->component) {
 970                  $changeable = false;
 971                  if (strpos($ra->component, 'enrol_') === 0) {
 972                      $plugin = substr($ra->component, 6);
 973                      if (isset($plugins[$plugin])) {
 974                          $changeable = !$plugins[$plugin]->roles_protected();
 975                      }
 976                  }
 977              }
 978  
 979              $roles[$ra->roleid] = $changeable;
 980          }
 981          return $roles;
 982      }
 983  
 984      /**
 985       * Gets the enrolments this user has in the course - including all suspended plugins and instances.
 986       *
 987       * @global moodle_database $DB
 988       * @param int $userid
 989       * @return array
 990       */
 991      public function get_user_enrolments($userid) {
 992          global $DB;
 993          list($instancessql, $params, $filter) = $this->get_instance_sql();
 994          $params['userid'] = $userid;
 995          $userenrolments = $DB->get_records_select('user_enrolments', "enrolid $instancessql AND userid = :userid", $params);
 996          $instances = $this->get_enrolment_instances();
 997          $plugins = $this->get_enrolment_plugins(false);
 998          $inames = $this->get_enrolment_instance_names();
 999          foreach ($userenrolments as &$ue) {
1000              $ue->enrolmentinstance     = $instances[$ue->enrolid];
1001              $ue->enrolmentplugin       = $plugins[$ue->enrolmentinstance->enrol];
1002              $ue->enrolmentinstancename = $inames[$ue->enrolmentinstance->id];
1003          }
1004          return $userenrolments;
1005      }
1006  
1007      /**
1008       * Gets the groups this user belongs to
1009       *
1010       * @param int $userid
1011       * @return array
1012       */
1013      public function get_user_groups($userid) {
1014          return groups_get_all_groups($this->course->id, $userid, 0, 'g.id');
1015      }
1016  
1017      /**
1018       * Retursn an array of params that would go into the URL to return to this
1019       * exact page.
1020       *
1021       * @return array
1022       */
1023      public function get_url_params() {
1024          $args = array(
1025              'id' => $this->course->id
1026          );
1027          if (!empty($this->instancefilter)) {
1028              $args['ifilter'] = $this->instancefilter;
1029          }
1030          if (!empty($this->rolefilter)) {
1031              $args['role'] = $this->rolefilter;
1032          }
1033          if ($this->searchfilter !== '') {
1034              $args['search'] = $this->searchfilter;
1035          }
1036          if (!empty($this->groupfilter)) {
1037              $args['filtergroup'] = $this->groupfilter;
1038          }
1039          if ($this->statusfilter !== -1) {
1040              $args['status'] = $this->statusfilter;
1041          }
1042          return $args;
1043      }
1044  
1045      /**
1046       * Returns the course this object is managing enrolments for
1047       *
1048       * @return stdClass
1049       */
1050      public function get_course() {
1051          return $this->course;
1052      }
1053  
1054      /**
1055       * Returns the course context
1056       *
1057       * @return context
1058       */
1059      public function get_context() {
1060          return $this->context;
1061      }
1062  
1063      /**
1064       * Gets an array of other users in this course ready for display.
1065       *
1066       * Other users are users who have been assigned or inherited roles within this
1067       * course but have not been enrolled.
1068       *
1069       * @param core_enrol_renderer $renderer
1070       * @param moodle_url $pageurl
1071       * @param string $sort
1072       * @param string $direction ASC | DESC
1073       * @param int $page Starting from 0
1074       * @param int $perpage
1075       * @return array
1076       */
1077      public function get_other_users_for_display(core_enrol_renderer $renderer, moodle_url $pageurl, $sort, $direction, $page, $perpage) {
1078  
1079          $userroles = $this->get_other_users($sort, $direction, $page, $perpage);
1080          $roles = $this->get_all_roles();
1081          $plugins = $this->get_enrolment_plugins(false);
1082  
1083          $context    = $this->get_context();
1084          $now = time();
1085          // TODO Does not support custom user profile fields (MDL-70456).
1086          $extrafields = fields::get_identity_fields($context, false);
1087  
1088          $users = array();
1089          foreach ($userroles as $userrole) {
1090              $contextid = $userrole->contextid;
1091              unset($userrole->contextid); // This would collide with user avatar.
1092              if (!array_key_exists($userrole->id, $users)) {
1093                  $users[$userrole->id] = $this->prepare_user_for_display($userrole, $extrafields, $now);
1094              }
1095              $a = new stdClass;
1096              $a->role = $roles[$userrole->roleid]->localname;
1097              if ($contextid == $this->context->id) {
1098                  $changeable = true;
1099                  if ($userrole->component) {
1100                      $changeable = false;
1101                      if (strpos($userrole->component, 'enrol_') === 0) {
1102                          $plugin = substr($userrole->component, 6);
1103                          if (isset($plugins[$plugin])) {
1104                              $changeable = !$plugins[$plugin]->roles_protected();
1105                          }
1106                      }
1107                  }
1108                  $roletext = get_string('rolefromthiscourse', 'enrol', $a);
1109              } else {
1110                  $changeable = false;
1111                  switch ($userrole->contextlevel) {
1112                      case CONTEXT_COURSE :
1113                          // Meta course
1114                          $roletext = get_string('rolefrommetacourse', 'enrol', $a);
1115                          break;
1116                      case CONTEXT_COURSECAT :
1117                          $roletext = get_string('rolefromcategory', 'enrol', $a);
1118                          break;
1119                      case CONTEXT_SYSTEM:
1120                      default:
1121                          $roletext = get_string('rolefromsystem', 'enrol', $a);
1122                          break;
1123                  }
1124              }
1125              if (!isset($users[$userrole->id]['roles'])) {
1126                  $users[$userrole->id]['roles'] = array();
1127              }
1128              $users[$userrole->id]['roles'][$userrole->roleid] = array(
1129                  'text' => $roletext,
1130                  'unchangeable' => !$changeable
1131              );
1132          }
1133          return $users;
1134      }
1135  
1136      /**
1137       * Gets an array of users for display, this includes minimal user information
1138       * as well as minimal information on the users roles, groups, and enrolments.
1139       *
1140       * @param core_enrol_renderer $renderer
1141       * @param moodle_url $pageurl
1142       * @param int $sort
1143       * @param string $direction ASC or DESC
1144       * @param int $page
1145       * @param int $perpage
1146       * @return array
1147       */
1148      public function get_users_for_display(course_enrolment_manager $manager, $sort, $direction, $page, $perpage) {
1149          $pageurl = $manager->get_moodlepage()->url;
1150          $users = $this->get_users($sort, $direction, $page, $perpage);
1151  
1152          $now = time();
1153          $straddgroup = get_string('addgroup', 'group');
1154          $strunenrol = get_string('unenrol', 'enrol');
1155          $stredit = get_string('edit');
1156  
1157          $visibleroles   = $this->get_viewable_roles();
1158          $assignable = $this->get_assignable_roles();
1159          $allgroups  = $this->get_all_groups();
1160          $context    = $this->get_context();
1161          $canmanagegroups = has_capability('moodle/course:managegroups', $context);
1162  
1163          $url = new moodle_url($pageurl, $this->get_url_params());
1164          // TODO Does not support custom user profile fields (MDL-70456).
1165          $extrafields = fields::get_identity_fields($context, false);
1166  
1167          $enabledplugins = $this->get_enrolment_plugins(true);
1168  
1169          $userdetails = array();
1170          foreach ($users as $user) {
1171              $details = $this->prepare_user_for_display($user, $extrafields, $now);
1172  
1173              // Roles
1174              $details['roles'] = array();
1175              foreach ($this->get_user_roles($user->id) as $rid=>$rassignable) {
1176                  $unchangeable = !$rassignable;
1177                  if (!is_siteadmin() and !isset($assignable[$rid])) {
1178                      $unchangeable = true;
1179                  }
1180  
1181                  if (isset($visibleroles[$rid])) {
1182                      $label = $visibleroles[$rid];
1183                  } else {
1184                      $label = get_string('novisibleroles', 'role');
1185                      $unchangeable = true;
1186                  }
1187  
1188                  $details['roles'][$rid] = array('text' => $label, 'unchangeable' => $unchangeable);
1189              }
1190  
1191              // Users
1192              $usergroups = $this->get_user_groups($user->id);
1193              $details['groups'] = array();
1194              foreach($usergroups as $gid=>$unused) {
1195                  $details['groups'][$gid] = $allgroups[$gid]->name;
1196              }
1197  
1198              // Enrolments
1199              $details['enrolments'] = array();
1200              foreach ($this->get_user_enrolments($user->id) as $ue) {
1201                  if (!isset($enabledplugins[$ue->enrolmentinstance->enrol])) {
1202                      $details['enrolments'][$ue->id] = array(
1203                          'text' => $ue->enrolmentinstancename,
1204                          'period' => null,
1205                          'dimmed' =>  true,
1206                          'actions' => array()
1207                      );
1208                      continue;
1209                  } else if ($ue->timestart and $ue->timeend) {
1210                      $period = get_string('periodstartend', 'enrol', array('start'=>userdate($ue->timestart), 'end'=>userdate($ue->timeend)));
1211                      $periodoutside = ($ue->timestart && $ue->timeend && ($now < $ue->timestart || $now > $ue->timeend));
1212                  } else if ($ue->timestart) {
1213                      $period = get_string('periodstart', 'enrol', userdate($ue->timestart));
1214                      $periodoutside = ($ue->timestart && $now < $ue->timestart);
1215                  } else if ($ue->timeend) {
1216                      $period = get_string('periodend', 'enrol', userdate($ue->timeend));
1217                      $periodoutside = ($ue->timeend && $now > $ue->timeend);
1218                  } else {
1219                      // If there is no start or end show when user was enrolled.
1220                      $period = get_string('periodnone', 'enrol', userdate($ue->timecreated));
1221                      $periodoutside = false;
1222                  }
1223                  $details['enrolments'][$ue->id] = array(
1224                      'text' => $ue->enrolmentinstancename,
1225                      'period' => $period,
1226                      'dimmed' =>  ($periodoutside or $ue->status != ENROL_USER_ACTIVE or $ue->enrolmentinstance->status != ENROL_INSTANCE_ENABLED),
1227                      'actions' => $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue)
1228                  );
1229              }
1230              $userdetails[$user->id] = $details;
1231          }
1232          return $userdetails;
1233      }
1234  
1235      /**
1236       * Prepare a user record for display
1237       *
1238       * This function is called by both {@link get_users_for_display} and {@link get_other_users_for_display} to correctly
1239       * prepare user fields for display
1240       *
1241       * Please note that this function does not check capability for moodle/coures:viewhiddenuserfields
1242       *
1243       * @param object $user The user record
1244       * @param array $extrafields The list of fields as returned from \core_user\fields::get_identity_fields used to determine which
1245       * additional fields may be displayed
1246       * @param int $now The time used for lastaccess calculation
1247       * @return array The fields to be displayed including userid, courseid, picture, firstname, lastcourseaccess, lastaccess and any
1248       * additional fields from $extrafields
1249       */
1250      private function prepare_user_for_display($user, $extrafields, $now) {
1251          $details = array(
1252              'userid'              => $user->id,
1253              'courseid'            => $this->get_course()->id,
1254              'picture'             => new user_picture($user),
1255              'userfullnamedisplay' => fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context())),
1256              'lastaccess'          => get_string('never'),
1257              'lastcourseaccess'    => get_string('never'),
1258          );
1259  
1260          foreach ($extrafields as $field) {
1261              $details[$field] = s($user->{$field});
1262          }
1263  
1264          // Last time user has accessed the site.
1265          if (!empty($user->lastaccess)) {
1266              $details['lastaccess'] = format_time($now - $user->lastaccess);
1267          }
1268  
1269          // Last time user has accessed the course.
1270          if (!empty($user->lastcourseaccess)) {
1271              $details['lastcourseaccess'] = format_time($now - $user->lastcourseaccess);
1272          }
1273          return $details;
1274      }
1275  
1276      public function get_manual_enrol_buttons() {
1277          $plugins = $this->get_enrolment_plugins(true); // Skip disabled plugins.
1278          $buttons = array();
1279          foreach ($plugins as $plugin) {
1280              $newbutton = $plugin->get_manual_enrol_button($this);
1281              if (is_array($newbutton)) {
1282                  $buttons += $newbutton;
1283              } else if ($newbutton instanceof enrol_user_button) {
1284                  $buttons[] = $newbutton;
1285              }
1286          }
1287          return $buttons;
1288      }
1289  
1290      public function has_instance($enrolpluginname) {
1291          // Make sure manual enrolments instance exists
1292          foreach ($this->get_enrolment_instances() as $instance) {
1293              if ($instance->enrol == $enrolpluginname) {
1294                  return true;
1295              }
1296          }
1297          return false;
1298      }
1299  
1300      /**
1301       * Returns the enrolment plugin that the course manager was being filtered to.
1302       *
1303       * If no filter was being applied then this function returns false.
1304       *
1305       * @return enrol_plugin
1306       */
1307      public function get_filtered_enrolment_plugin() {
1308          $instances = $this->get_enrolment_instances();
1309          $plugins = $this->get_enrolment_plugins(false);
1310  
1311          if (empty($this->instancefilter) || !array_key_exists($this->instancefilter, $instances)) {
1312              return false;
1313          }
1314  
1315          $instance = $instances[$this->instancefilter];
1316          return $plugins[$instance->enrol];
1317      }
1318  
1319      /**
1320       * Returns and array of users + enrolment details.
1321       *
1322       * Given an array of user id's this function returns and array of user enrolments for those users
1323       * as well as enough user information to display the users name and picture for each enrolment.
1324       *
1325       * @global moodle_database $DB
1326       * @param array $userids
1327       * @return array
1328       */
1329      public function get_users_enrolments(array $userids) {
1330          global $DB;
1331  
1332          $instances = $this->get_enrolment_instances();
1333          $plugins = $this->get_enrolment_plugins(false);
1334  
1335          if  (!empty($this->instancefilter)) {
1336              $instancesql = ' = :instanceid';
1337              $instanceparams = array('instanceid' => $this->instancefilter);
1338          } else {
1339              list($instancesql, $instanceparams) = $DB->get_in_or_equal(array_keys($instances), SQL_PARAMS_NAMED, 'instanceid0000');
1340          }
1341  
1342          $userfieldsapi = \core_user\fields::for_userpic();
1343          $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
1344          list($idsql, $idparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'userid0000');
1345  
1346          list($sort, $sortparams) = users_order_by_sql('u');
1347  
1348          $sql = "SELECT ue.id AS ueid, ue.status, ue.enrolid, ue.userid, ue.timestart, ue.timeend, ue.modifierid, ue.timecreated, ue.timemodified, $userfields
1349                    FROM {user_enrolments} ue
1350               LEFT JOIN {user} u ON u.id = ue.userid
1351                   WHERE ue.enrolid $instancesql AND
1352                         u.id $idsql
1353                ORDER BY $sort";
1354  
1355          $rs = $DB->get_recordset_sql($sql, $idparams + $instanceparams + $sortparams);
1356          $users = array();
1357          foreach ($rs as $ue) {
1358              $user = user_picture::unalias($ue);
1359              $ue->id = $ue->ueid;
1360              unset($ue->ueid);
1361              if (!array_key_exists($user->id, $users)) {
1362                  $user->enrolments = array();
1363                  $users[$user->id] = $user;
1364              }
1365              $ue->enrolmentinstance = $instances[$ue->enrolid];
1366              $ue->enrolmentplugin = $plugins[$ue->enrolmentinstance->enrol];
1367              $users[$user->id]->enrolments[$ue->id] = $ue;
1368          }
1369          $rs->close();
1370          return $users;
1371      }
1372  }
1373  
1374  /**
1375   * A button that is used to enrol users in a course
1376   *
1377   * @copyright 2010 Sam Hemelryk
1378   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1379   */
1380  class enrol_user_button extends single_button {
1381  
1382      /**
1383       * An array containing JS YUI modules required by this button
1384       * @var array
1385       */
1386      protected $jsyuimodules = array();
1387  
1388      /**
1389       * An array containing JS initialisation calls required by this button
1390       * @var array
1391       */
1392      protected $jsinitcalls = array();
1393  
1394      /**
1395       * An array strings required by JS for this button
1396       * @var array
1397       */
1398      protected $jsstrings = array();
1399  
1400      /**
1401       * Initialises the new enrol_user_button
1402       *
1403       * @staticvar int $count The number of enrol user buttons already created
1404       * @param moodle_url $url
1405       * @param string $label The text to display in the button
1406       * @param string $method Either post or get
1407       */
1408      public function __construct(moodle_url $url, $label, $method = 'post') {
1409          static $count = 0;
1410          $count ++;
1411          parent::__construct($url, $label, $method);
1412          $this->class = 'singlebutton enrolusersbutton';
1413          $this->formid = 'enrolusersbutton-'.$count;
1414      }
1415  
1416      /**
1417       * Adds a YUI module call that will be added to the page when the button is used.
1418       *
1419       * @param string|array $modules One or more modules to require
1420       * @param string $function The JS function to call
1421       * @param array $arguments An array of arguments to pass to the function
1422       * @param string $galleryversion Deprecated: The gallery version to use
1423       * @param bool $ondomready If true the call is postponed until the DOM is finished loading
1424       */
1425      public function require_yui_module($modules, $function, array $arguments = null, $galleryversion = null, $ondomready = false) {
1426          if ($galleryversion != null) {
1427              debugging('The galleryversion parameter to yui_module has been deprecated since Moodle 2.3.', DEBUG_DEVELOPER);
1428          }
1429  
1430          $js = new stdClass;
1431          $js->modules = (array)$modules;
1432          $js->function = $function;
1433          $js->arguments = $arguments;
1434          $js->ondomready = $ondomready;
1435          $this->jsyuimodules[] = $js;
1436      }
1437  
1438      /**
1439       * Adds a JS initialisation call to the page when the button is used.
1440       *
1441       * @param string $function The function to call
1442       * @param array $extraarguments An array of arguments to pass to the function
1443       * @param bool $ondomready If true the call is postponed until the DOM is finished loading
1444       * @param array $module A module definition
1445       */
1446      public function require_js_init_call($function, array $extraarguments = null, $ondomready = false, array $module = null) {
1447          $js = new stdClass;
1448          $js->function = $function;
1449          $js->extraarguments = $extraarguments;
1450          $js->ondomready = $ondomready;
1451          $js->module = $module;
1452          $this->jsinitcalls[] = $js;
1453      }
1454  
1455      /**
1456       * Requires strings for JS that will be loaded when the button is used.
1457       *
1458       * @param type $identifiers
1459       * @param string $component
1460       * @param mixed $a
1461       */
1462      public function strings_for_js($identifiers, $component = 'moodle', $a = null) {
1463          $string = new stdClass;
1464          $string->identifiers = (array)$identifiers;
1465          $string->component = $component;
1466          $string->a = $a;
1467          $this->jsstrings[] = $string;
1468      }
1469  
1470      /**
1471       * Initialises the JS that is required by this button
1472       *
1473       * @param moodle_page $page
1474       */
1475      public function initialise_js(moodle_page $page) {
1476          foreach ($this->jsyuimodules as $js) {
1477              $page->requires->yui_module($js->modules, $js->function, $js->arguments, null, $js->ondomready);
1478          }
1479          foreach ($this->jsinitcalls as $js) {
1480              $page->requires->js_init_call($js->function, $js->extraarguments, $js->ondomready, $js->module);
1481          }
1482          foreach ($this->jsstrings as $string) {
1483              $page->requires->strings_for_js($string->identifiers, $string->component, $string->a);
1484          }
1485      }
1486  }
1487  
1488  /**
1489   * User enrolment action
1490   *
1491   * This class is used to manage a renderable ue action such as editing an user enrolment or deleting
1492   * a user enrolment.
1493   *
1494   * @copyright  2011 Sam Hemelryk
1495   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1496   */
1497  class user_enrolment_action implements renderable {
1498  
1499      /**
1500       * The icon to display for the action
1501       * @var pix_icon
1502       */
1503      protected $icon;
1504  
1505      /**
1506       * The title for the action
1507       * @var string
1508       */
1509      protected $title;
1510  
1511      /**
1512       * The URL to the action
1513       * @var moodle_url
1514       */
1515      protected $url;
1516  
1517      /**
1518       * An array of HTML attributes
1519       * @var array
1520       */
1521      protected $attributes = array();
1522  
1523      /**
1524       * Constructor
1525       * @param pix_icon $icon
1526       * @param string $title
1527       * @param moodle_url $url
1528       * @param array $attributes
1529       */
1530      public function __construct(pix_icon $icon, $title, $url, array $attributes = null) {
1531          $this->icon = $icon;
1532          $this->title = $title;
1533          $this->url = new moodle_url($url);
1534          if (!empty($attributes)) {
1535              $this->attributes = $attributes;
1536          }
1537          $this->attributes['title'] = $title;
1538      }
1539  
1540      /**
1541       * Returns the icon for this action
1542       * @return pix_icon
1543       */
1544      public function get_icon() {
1545          return $this->icon;
1546      }
1547  
1548      /**
1549       * Returns the title for this action
1550       * @return string
1551       */
1552      public function get_title() {
1553          return $this->title;
1554      }
1555  
1556      /**
1557       * Returns the URL for this action
1558       * @return moodle_url
1559       */
1560      public function get_url() {
1561          return $this->url;
1562      }
1563  
1564      /**
1565       * Returns the attributes to use for this action
1566       * @return array
1567       */
1568      public function get_attributes() {
1569          return $this->attributes;
1570      }
1571  }
1572  
1573  class enrol_ajax_exception extends moodle_exception {
1574      /**
1575       * Constructor
1576       * @param string $errorcode The name of the string from error.php to print
1577       * @param string $module name of module
1578       * @param string $link The url where the user will be prompted to continue. If no url is provided the user will be directed to the site index page.
1579       * @param object $a Extra words and phrases that might be required in the error string
1580       * @param string $debuginfo optional debugging information
1581       */
1582      public function __construct($errorcode, $link = '', $a = NULL, $debuginfo = null) {
1583          parent::__construct($errorcode, 'enrol', $link, $a, $debuginfo);
1584      }
1585  }
1586  
1587  /**
1588   * This class is used to manage a bulk operations for enrolment plugins.
1589   *
1590   * @copyright 2011 Sam Hemelryk
1591   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1592   */
1593  abstract class enrol_bulk_enrolment_operation {
1594  
1595      /**
1596       * The course enrolment manager
1597       * @var course_enrolment_manager
1598       */
1599      protected $manager;
1600  
1601      /**
1602       * The enrolment plugin to which this operation belongs
1603       * @var enrol_plugin
1604       */
1605      protected $plugin;
1606  
1607      /**
1608       * Contructor
1609       * @param course_enrolment_manager $manager
1610       * @param stdClass $plugin
1611       */
1612      public function __construct(course_enrolment_manager $manager, enrol_plugin $plugin = null) {
1613          $this->manager = $manager;
1614          $this->plugin = $plugin;
1615      }
1616  
1617      /**
1618       * Returns a moodleform used for this operation, or false if no form is required and the action
1619       * should be immediatly processed.
1620       *
1621       * @param moodle_url|string $defaultaction
1622       * @param mixed $defaultcustomdata
1623       * @return enrol_bulk_enrolment_change_form|moodleform|false
1624       */
1625      public function get_form($defaultaction = null, $defaultcustomdata = null) {
1626          return false;
1627      }
1628  
1629      /**
1630       * Returns the title to use for this bulk operation
1631       *
1632       * @return string
1633       */
1634      abstract public function get_title();
1635  
1636      /**
1637       * Returns the identifier for this bulk operation.
1638       * This should be the same identifier used by the plugins function when returning
1639       * all of its bulk operations.
1640       *
1641       * @return string
1642       */
1643      abstract public function get_identifier();
1644  
1645      /**
1646       * Processes the bulk operation on the given users
1647       *
1648       * @param course_enrolment_manager $manager
1649       * @param array $users
1650       * @param stdClass $properties
1651       */
1652      abstract public function process(course_enrolment_manager $manager, array $users, stdClass $properties);
1653  }