Search moodle.org's
Developer Documentation

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

    Differences Between: [Versions 310 and 311] [Versions 37 and 311] [Versions 38 and 311] [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   * 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          [$ufields, $joins, $params, $wherecondition] = $this->get_basic_search_conditions($search, $searchanywhere);
     574  
     575          $fields      = 'SELECT ' . $ufields;
     576          $countfields = 'SELECT COUNT(u.id)';
     577          $sql = " FROM {user} u
     578                        $joins
     579                   JOIN {user_enrolments} ue ON ue.userid = u.id
     580                   JOIN {enrol} e ON ue.enrolid = e.id
     581                  WHERE $wherecondition
     582                    AND e.courseid = :courseid";
     583          $params['courseid'] = $this->course->id;
     584  
     585          return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, 0, $returnexactcount);
     586      }
     587  
     588      /**
     589       * Gets an array containing some SQL to user for when selecting, params for
     590       * that SQL, and the filter that was used in constructing the sql.
     591       *
     592       * @global moodle_database $DB
     593       * @return string
     594       */
     595      protected function get_instance_sql() {
     596          global $DB;
     597          if ($this->_instancessql === null) {
     598              $instances = $this->get_enrolment_instances();
     599              $filter = $this->get_enrolment_filter();
     600              if ($filter && array_key_exists($filter, $instances)) {
     601                  $sql = " = :ifilter";
     602                  $params = array('ifilter'=>$filter);
     603              } else {
     604                  $filter = 0;
     605                  if ($instances) {
     606                      list($sql, $params) = $DB->get_in_or_equal(array_keys($this->get_enrolment_instances()), SQL_PARAMS_NAMED);
     607                  } else {
     608                      // no enabled instances, oops, we should probably say something
     609                      $sql = "= :never";
     610                      $params = array('never'=>-1);
     611                  }
     612              }
     613              $this->instancefilter = $filter;
     614              $this->_instancessql = array($sql, $params, $filter);
     615          }
     616          return $this->_instancessql;
     617      }
     618  
     619      /**
     620       * Returns all of the enrolment instances for this course.
     621       *
     622       * @param bool $onlyenabled Whether to return data from enabled enrolment instance names only.
     623       * @return array
     624       */
     625      public function get_enrolment_instances($onlyenabled = false) {
     626          if ($this->_instances === null) {
     627              $this->_instances = enrol_get_instances($this->course->id, $onlyenabled);
     628          }
     629          return $this->_instances;
     630      }
     631  
     632      /**
     633       * Returns the names for all of the enrolment instances for this course.
     634       *
     635       * @param bool $onlyenabled Whether to return data from enabled enrolment instance names only.
     636       * @return array
     637       */
     638      public function get_enrolment_instance_names($onlyenabled = false) {
     639          if ($this->_inames === null) {
     640              $instances = $this->get_enrolment_instances($onlyenabled);
     641              $plugins = $this->get_enrolment_plugins(false);
     642              foreach ($instances as $key=>$instance) {
     643                  if (!isset($plugins[$instance->enrol])) {
     644                      // weird, some broken stuff in plugin
     645                      unset($instances[$key]);
     646                      continue;
     647                  }
     648                  $this->_inames[$key] = $plugins[$instance->enrol]->get_instance_name($instance);
     649              }
     650          }
     651          return $this->_inames;
     652      }
     653  
     654      /**
     655       * Gets all of the enrolment plugins that are available for this course.
     656       *
     657       * @param bool $onlyenabled return only enabled enrol plugins
     658       * @return array
     659       */
     660      public function get_enrolment_plugins($onlyenabled = true) {
     661          if ($this->_plugins === null) {
     662              $this->_plugins = enrol_get_plugins(true);
     663          }
     664  
     665          if ($onlyenabled) {
     666              return $this->_plugins;
     667          }
     668  
     669          if ($this->_allplugins === null) {
     670              // Make sure we have the same objects in _allplugins and _plugins.
     671              $this->_allplugins = $this->_plugins;
     672              foreach (enrol_get_plugins(false) as $name=>$plugin) {
     673                  if (!isset($this->_allplugins[$name])) {
     674                      $this->_allplugins[$name] = $plugin;
     675                  }
     676              }
     677          }
     678  
     679          return $this->_allplugins;
     680      }
     681  
     682      /**
     683       * Gets all of the roles this course can contain.
     684       *
     685       * @return array
     686       */
     687      public function get_all_roles() {
     688          if ($this->_roles === null) {
     689              $this->_roles = role_fix_names(get_all_roles($this->context), $this->context);
     690          }
     691          return $this->_roles;
     692      }
     693  
     694      /**
     695       * Gets all of the roles this course can contain.
     696       *
     697       * @return array
     698       */
     699      public function get_viewable_roles() {
     700          if ($this->_visibleroles === null) {
     701              $this->_visibleroles = get_viewable_roles($this->context);
     702          }
     703          return $this->_visibleroles;
     704      }
     705  
     706      /**
     707       * Gets all of the assignable roles for this course.
     708       *
     709       * @return array
     710       */
     711      public function get_assignable_roles($otherusers = false) {
     712          if ($this->_assignableroles === null) {
     713              $this->_assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false); // verifies unassign access control too
     714          }
     715  
     716          if ($otherusers) {
     717              if (!is_array($this->_assignablerolesothers)) {
     718                  $this->_assignablerolesothers = array();
     719                  list($courseviewroles, $ignored) = get_roles_with_cap_in_context($this->context, 'moodle/course:view');
     720                  foreach ($this->_assignableroles as $roleid=>$role) {
     721                      if (isset($courseviewroles[$roleid])) {
     722                          $this->_assignablerolesothers[$roleid] = $role;
     723                      }
     724                  }
     725              }
     726              return $this->_assignablerolesothers;
     727          } else {
     728              return $this->_assignableroles;
     729          }
     730      }
     731  
     732      /**
     733       * Gets all of the assignable roles for this course, wrapped in an array to ensure
     734       * role sort order is not lost during json deserialisation.
     735       *
     736       * @param boolean $otherusers whether to include the assignable roles for other users
     737       * @return array
     738       */
     739      public function get_assignable_roles_for_json($otherusers = false) {
     740          $rolesarray = array();
     741          $assignable = $this->get_assignable_roles($otherusers);
     742          foreach ($assignable as $id => $role) {
     743              $rolesarray[] = array('id' => $id, 'name' => $role);
     744          }
     745          return $rolesarray;
     746      }
     747  
     748      /**
     749       * Gets all of the groups for this course.
     750       *
     751       * @return array
     752       */
     753      public function get_all_groups() {
     754          if ($this->_groups === null) {
     755              $this->_groups = groups_get_all_groups($this->course->id);
     756              foreach ($this->_groups as $gid=>$group) {
     757                  $this->_groups[$gid]->name = format_string($group->name);
     758              }
     759          }
     760          return $this->_groups;
     761      }
     762  
     763      /**
     764       * Unenrols a user from the course given the users ue entry
     765       *
     766       * @global moodle_database $DB
     767       * @param stdClass $ue
     768       * @return bool
     769       */
     770      public function unenrol_user($ue) {
     771          global $DB;
     772          list ($instance, $plugin) = $this->get_user_enrolment_components($ue);
     773          if ($instance && $plugin && $plugin->allow_unenrol_user($instance, $ue) && has_capability("enrol/$instance->enrol:unenrol", $this->context)) {
     774              $plugin->unenrol_user($instance, $ue->userid);
     775              return true;
     776          }
     777          return false;
     778      }
     779  
     780      /**
     781       * Given a user enrolment record this method returns the plugin and enrolment
     782       * instance that relate to it.
     783       *
     784       * @param stdClass|int $userenrolment
     785       * @return array array($instance, $plugin)
     786       */
     787      public function get_user_enrolment_components($userenrolment) {
     788          global $DB;
     789          if (is_numeric($userenrolment)) {
     790              $userenrolment = $DB->get_record('user_enrolments', array('id'=>(int)$userenrolment));
     791          }
     792          $instances = $this->get_enrolment_instances();
     793          $plugins = $this->get_enrolment_plugins(false);
     794          if (!$userenrolment || !isset($instances[$userenrolment->enrolid])) {
     795              return array(false, false);
     796          }
     797          $instance = $instances[$userenrolment->enrolid];
     798          $plugin = $plugins[$instance->enrol];
     799          return array($instance, $plugin);
     800      }
     801  
     802      /**
     803       * Removes an assigned role from a user.
     804       *
     805       * @global moodle_database $DB
     806       * @param int $userid
     807       * @param int $roleid
     808       * @return bool
     809       */
     810      public function unassign_role_from_user($userid, $roleid) {
     811          global $DB;
     812          // Admins may unassign any role, others only those they could assign.
     813          if (!is_siteadmin() and !array_key_exists($roleid, $this->get_assignable_roles())) {
     814              if (defined('AJAX_SCRIPT')) {
     815                  throw new moodle_exception('invalidrole');
     816              }
     817              return false;
     818          }
     819          $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
     820          $ras = $DB->get_records('role_assignments', array('contextid'=>$this->context->id, 'userid'=>$user->id, 'roleid'=>$roleid));
     821          foreach ($ras as $ra) {
     822              if ($ra->component) {
     823                  if (strpos($ra->component, 'enrol_') !== 0) {
     824                      continue;
     825                  }
     826                  if (!$plugin = enrol_get_plugin(substr($ra->component, 6))) {
     827                      continue;
     828                  }
     829                  if ($plugin->roles_protected()) {
     830                      continue;
     831                  }
     832              }
     833              role_unassign($ra->roleid, $ra->userid, $ra->contextid, $ra->component, $ra->itemid);
     834          }
     835          return true;
     836      }
     837  
     838      /**
     839       * Assigns a role to a user.
     840       *
     841       * @param int $roleid
     842       * @param int $userid
     843       * @return int|false
     844       */
     845      public function assign_role_to_user($roleid, $userid) {
     846          require_capability('moodle/role:assign', $this->context);
     847          if (!array_key_exists($roleid, $this->get_assignable_roles())) {
     848              if (defined('AJAX_SCRIPT')) {
     849                  throw new moodle_exception('invalidrole');
     850              }
     851              return false;
     852          }
     853          return role_assign($roleid, $userid, $this->context->id, '', NULL);
     854      }
     855  
     856      /**
     857       * Adds a user to a group
     858       *
     859       * @param stdClass $user
     860       * @param int $groupid
     861       * @return bool
     862       */
     863      public function add_user_to_group($user, $groupid) {
     864          require_capability('moodle/course:managegroups', $this->context);
     865          $group = $this->get_group($groupid);
     866          if (!$group) {
     867              return false;
     868          }
     869          return groups_add_member($group->id, $user->id);
     870      }
     871  
     872      /**
     873       * Removes a user from a group
     874       *
     875       * @global moodle_database $DB
     876       * @param StdClass $user
     877       * @param int $groupid
     878       * @return bool
     879       */
     880      public function remove_user_from_group($user, $groupid) {
     881          global $DB;
     882          require_capability('moodle/course:managegroups', $this->context);
     883          $group = $this->get_group($groupid);
     884          if (!groups_remove_member_allowed($group, $user)) {
     885              return false;
     886          }
     887          if (!$group) {
     888              return false;
     889          }
     890          return groups_remove_member($group, $user);
     891      }
     892  
     893      /**
     894       * Gets the requested group
     895       *
     896       * @param int $groupid
     897       * @return stdClass|int
     898       */
     899      public function get_group($groupid) {
     900          $groups = $this->get_all_groups();
     901          if (!array_key_exists($groupid, $groups)) {
     902              return false;
     903          }
     904          return $groups[$groupid];
     905      }
     906  
     907      /**
     908       * Edits an enrolment
     909       *
     910       * @param stdClass $userenrolment
     911       * @param stdClass $data
     912       * @return bool
     913       */
     914      public function edit_enrolment($userenrolment, $data) {
     915          //Only allow editing if the user has the appropriate capability
     916          //Already checked in /user/index.php but checking again in case this function is called from elsewhere
     917          list($instance, $plugin) = $this->get_user_enrolment_components($userenrolment);
     918          if ($instance && $plugin && $plugin->allow_manage($instance) && has_capability("enrol/$instance->enrol:manage", $this->context)) {
     919              if (!isset($data->status)) {
     920                  $data->status = $userenrolment->status;
     921              }
     922              $plugin->update_user_enrol($instance, $userenrolment->userid, $data->status, $data->timestart, $data->timeend);
     923              return true;
     924          }
     925          return false;
     926      }
     927  
     928      /**
     929       * Returns the current enrolment filter that is being applied by this class
     930       * @return string
     931       */
     932      public function get_enrolment_filter() {
     933          return $this->instancefilter;
     934      }
     935  
     936      /**
     937       * Gets the roles assigned to this user that are applicable for this course.
     938       *
     939       * @param int $userid
     940       * @return array
     941       */
     942      public function get_user_roles($userid) {
     943          $roles = array();
     944          $ras = get_user_roles($this->context, $userid, true, 'c.contextlevel DESC, r.sortorder ASC');
     945          $plugins = $this->get_enrolment_plugins(false);
     946          foreach ($ras as $ra) {
     947              if ($ra->contextid != $this->context->id) {
     948                  if (!array_key_exists($ra->roleid, $roles)) {
     949                      $roles[$ra->roleid] = null;
     950                  }
     951                  // higher ras, course always takes precedence
     952                  continue;
     953              }
     954              if (array_key_exists($ra->roleid, $roles) && $roles[$ra->roleid] === false) {
     955                  continue;
     956              }
     957              $changeable = true;
     958              if ($ra->component) {
     959                  $changeable = false;
     960                  if (strpos($ra->component, 'enrol_') === 0) {
     961                      $plugin = substr($ra->component, 6);
     962                      if (isset($plugins[$plugin])) {
     963                          $changeable = !$plugins[$plugin]->roles_protected();
     964                      }
     965                  }
     966              }
     967  
     968              $roles[$ra->roleid] = $changeable;
     969          }
     970          return $roles;
     971      }
     972  
     973      /**
     974       * Gets the enrolments this user has in the course - including all suspended plugins and instances.
     975       *
     976       * @global moodle_database $DB
     977       * @param int $userid
     978       * @return array
     979       */
     980      public function get_user_enrolments($userid) {
     981          global $DB;
     982          list($instancessql, $params, $filter) = $this->get_instance_sql();
     983          $params['userid'] = $userid;
     984          $userenrolments = $DB->get_records_select('user_enrolments', "enrolid $instancessql AND userid = :userid", $params);
     985          $instances = $this->get_enrolment_instances();
     986          $plugins = $this->get_enrolment_plugins(false);
     987          $inames = $this->get_enrolment_instance_names();
     988          foreach ($userenrolments as &$ue) {
     989              $ue->enrolmentinstance     = $instances[$ue->enrolid];
     990              $ue->enrolmentplugin       = $plugins[$ue->enrolmentinstance->enrol];
     991              $ue->enrolmentinstancename = $inames[$ue->enrolmentinstance->id];
     992          }
     993          return $userenrolments;
     994      }
     995  
     996      /**
     997       * Gets the groups this user belongs to
     998       *
     999       * @param int $userid
    1000       * @return array
    1001       */
    1002      public function get_user_groups($userid) {
    1003          return groups_get_all_groups($this->course->id, $userid, 0, 'g.id');
    1004      }
    1005  
    1006      /**
    1007       * Retursn an array of params that would go into the URL to return to this
    1008       * exact page.
    1009       *
    1010       * @return array
    1011       */
    1012      public function get_url_params() {
    1013          $args = array(
    1014              'id' => $this->course->id
    1015          );
    1016          if (!empty($this->instancefilter)) {
    1017              $args['ifilter'] = $this->instancefilter;
    1018          }
    1019          if (!empty($this->rolefilter)) {
    1020              $args['role'] = $this->rolefilter;
    1021          }
    1022          if ($this->searchfilter !== '') {
    1023              $args['search'] = $this->searchfilter;
    1024          }
    1025          if (!empty($this->groupfilter)) {
    1026              $args['filtergroup'] = $this->groupfilter;
    1027          }
    1028          if ($this->statusfilter !== -1) {
    1029              $args['status'] = $this->statusfilter;
    1030          }
    1031          return $args;
    1032      }
    1033  
    1034      /**
    1035       * Returns the course this object is managing enrolments for
    1036       *
    1037       * @return stdClass
    1038       */
    1039      public function get_course() {
    1040          return $this->course;
    1041      }
    1042  
    1043      /**
    1044       * Returns the course context
    1045       *
    1046       * @return context
    1047       */
    1048      public function get_context() {
    1049          return $this->context;
    1050      }
    1051  
    1052      /**
    1053       * Gets an array of other users in this course ready for display.
    1054       *
    1055       * Other users are users who have been assigned or inherited roles within this
    1056       * course but have not been enrolled.
    1057       *
    1058       * @param core_enrol_renderer $renderer
    1059       * @param moodle_url $pageurl
    1060       * @param string $sort
    1061       * @param string $direction ASC | DESC
    1062       * @param int $page Starting from 0
    1063       * @param int $perpage
    1064       * @return array
    1065       */
    1066      public function get_other_users_for_display(core_enrol_renderer $renderer, moodle_url $pageurl, $sort, $direction, $page, $perpage) {
    1067  
    1068          $userroles = $this->get_other_users($sort, $direction, $page, $perpage);
    1069          $roles = $this->get_all_roles();
    1070          $plugins = $this->get_enrolment_plugins(false);
    1071  
    1072          $context    = $this->get_context();
    1073          $now = time();
    1074          // TODO Does not support custom user profile fields (MDL-70456).
    1075          $extrafields = fields::get_identity_fields($context, false);
    1076  
    1077          $users = array();
    1078          foreach ($userroles as $userrole) {
    1079              $contextid = $userrole->contextid;
    1080              unset($userrole->contextid); // This would collide with user avatar.
    1081              if (!array_key_exists($userrole->id, $users)) {
    1082                  $users[$userrole->id] = $this->prepare_user_for_display($userrole, $extrafields, $now);
    1083              }
    1084              $a = new stdClass;
    1085              $a->role = $roles[$userrole->roleid]->localname;
    1086              if ($contextid == $this->context->id) {
    1087                  $changeable = true;
    1088                  if ($userrole->component) {
    1089                      $changeable = false;
    1090                      if (strpos($userrole->component, 'enrol_') === 0) {
    1091                          $plugin = substr($userrole->component, 6);
    1092                          if (isset($plugins[$plugin])) {
    1093                              $changeable = !$plugins[$plugin]->roles_protected();
    1094                          }
    1095                      }
    1096                  }
    1097                  $roletext = get_string('rolefromthiscourse', 'enrol', $a);
    1098              } else {
    1099                  $changeable = false;
    1100                  switch ($userrole->contextlevel) {
    1101                      case CONTEXT_COURSE :
    1102                          // Meta course
    1103                          $roletext = get_string('rolefrommetacourse', 'enrol', $a);
    1104                          break;
    1105                      case CONTEXT_COURSECAT :
    1106                          $roletext = get_string('rolefromcategory', 'enrol', $a);
    1107                          break;
    1108                      case CONTEXT_SYSTEM:
    1109                      default:
    1110                          $roletext = get_string('rolefromsystem', 'enrol', $a);
    1111                          break;
    1112                  }
    1113              }
    1114              if (!isset($users[$userrole->id]['roles'])) {
    1115                  $users[$userrole->id]['roles'] = array();
    1116              }
    1117              $users[$userrole->id]['roles'][$userrole->roleid] = array(
    1118                  'text' => $roletext,
    1119                  'unchangeable' => !$changeable
    1120              );
    1121          }
    1122          return $users;
    1123      }
    1124  
    1125      /**
    1126       * Gets an array of users for display, this includes minimal user information
    1127       * as well as minimal information on the users roles, groups, and enrolments.
    1128       *
    1129       * @param core_enrol_renderer $renderer
    1130       * @param moodle_url $pageurl
    1131       * @param int $sort
    1132       * @param string $direction ASC or DESC
    1133       * @param int $page
    1134       * @param int $perpage
    1135       * @return array
    1136       */
    1137      public function get_users_for_display(course_enrolment_manager $manager, $sort, $direction, $page, $perpage) {
    1138          $pageurl = $manager->get_moodlepage()->url;
    1139          $users = $this->get_users($sort, $direction, $page, $perpage);
    1140  
    1141          $now = time();
    1142          $straddgroup = get_string('addgroup', 'group');
    1143          $strunenrol = get_string('unenrol', 'enrol');
    1144          $stredit = get_string('edit');
    1145  
    1146          $visibleroles   = $this->get_viewable_roles();
    1147          $assignable = $this->get_assignable_roles();
    1148          $allgroups  = $this->get_all_groups();
    1149          $context    = $this->get_context();
    1150          $canmanagegroups = has_capability('moodle/course:managegroups', $context);
    1151  
    1152          $url = new moodle_url($pageurl, $this->get_url_params());
    1153          // TODO Does not support custom user profile fields (MDL-70456).
    1154          $extrafields = fields::get_identity_fields($context, false);
    1155  
    1156          $enabledplugins = $this->get_enrolment_plugins(true);
    1157  
    1158          $userdetails = array();
    1159          foreach ($users as $user) {
    1160              $details = $this->prepare_user_for_display($user, $extrafields, $now);
    1161  
    1162              // Roles
    1163              $details['roles'] = array();
    1164              foreach ($this->get_user_roles($user->id) as $rid=>$rassignable) {
    1165                  $unchangeable = !$rassignable;
    1166                  if (!is_siteadmin() and !isset($assignable[$rid])) {
    1167                      $unchangeable = true;
    1168                  }
    1169  
    1170                  if (isset($visibleroles[$rid])) {
    1171                      $label = $visibleroles[$rid];
    1172                  } else {
    1173                      $label = get_string('novisibleroles', 'role');
    1174                      $unchangeable = true;
    1175                  }
    1176  
    1177                  $details['roles'][$rid] = array('text' => $label, 'unchangeable' => $unchangeable);
    1178              }
    1179  
    1180              // Users
    1181              $usergroups = $this->get_user_groups($user->id);
    1182              $details['groups'] = array();
    1183              foreach($usergroups as $gid=>$unused) {
    1184                  $details['groups'][$gid] = $allgroups[$gid]->name;
    1185              }
    1186  
    1187              // Enrolments
    1188              $details['enrolments'] = array();
    1189              foreach ($this->get_user_enrolments($user->id) as $ue) {
    1190                  if (!isset($enabledplugins[$ue->enrolmentinstance->enrol])) {
    1191                      $details['enrolments'][$ue->id] = array(
    1192                          'text' => $ue->enrolmentinstancename,
    1193                          'period' => null,
    1194                          'dimmed' =>  true,
    1195                          'actions' => array()
    1196                      );
    1197                      continue;
    1198                  } else if ($ue->timestart and $ue->timeend) {
    1199                      $period = get_string('periodstartend', 'enrol', array('start'=>userdate($ue->timestart), 'end'=>userdate($ue->timeend)));
    1200                      $periodoutside = ($ue->timestart && $ue->timeend && ($now < $ue->timestart || $now > $ue->timeend));
    1201                  } else if ($ue->timestart) {
    1202                      $period = get_string('periodstart', 'enrol', userdate($ue->timestart));
    1203                      $periodoutside = ($ue->timestart && $now < $ue->timestart);
    1204                  } else if ($ue->timeend) {
    1205                      $period = get_string('periodend', 'enrol', userdate($ue->timeend));
    1206                      $periodoutside = ($ue->timeend && $now > $ue->timeend);
    1207                  } else {
    1208                      // If there is no start or end show when user was enrolled.
    1209                      $period = get_string('periodnone', 'enrol', userdate($ue->timecreated));
    1210                      $periodoutside = false;
    1211                  }
    1212                  $details['enrolments'][$ue->id] = array(
    1213                      'text' => $ue->enrolmentinstancename,
    1214                      'period' => $period,
    1215                      'dimmed' =>  ($periodoutside or $ue->status != ENROL_USER_ACTIVE or $ue->enrolmentinstance->status != ENROL_INSTANCE_ENABLED),
    1216                      'actions' => $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue)
    1217                  );
    1218              }
    1219              $userdetails[$user->id] = $details;
    1220          }
    1221          return $userdetails;
    1222      }
    1223  
    1224      /**
    1225       * Prepare a user record for display
    1226       *
    1227       * This function is called by both {@link get_users_for_display} and {@link get_other_users_for_display} to correctly
    1228       * prepare user fields for display
    1229       *
    1230       * Please note that this function does not check capability for moodle/coures:viewhiddenuserfields
    1231       *
    1232       * @param object $user The user record
    1233       * @param array $extrafields The list of fields as returned from \core_user\fields::get_identity_fields used to determine which
    1234       * additional fields may be displayed
    1235       * @param int $now The time used for lastaccess calculation
    1236       * @return array The fields to be displayed including userid, courseid, picture, firstname, lastcourseaccess, lastaccess and any
    1237       * additional fields from $extrafields
    1238       */
    1239      private function prepare_user_for_display($user, $extrafields, $now) {
    1240          $details = array(
    1241              'userid'              => $user->id,
    1242              'courseid'            => $this->get_course()->id,
    1243              'picture'             => new user_picture($user),
    1244              'userfullnamedisplay' => fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context())),
    1245              'lastaccess'          => get_string('never'),
    1246              'lastcourseaccess'    => get_string('never'),
    1247          );
    1248  
    1249          foreach ($extrafields as $field) {
    1250              $details[$field] = s($user->{$field});
    1251          }
    1252  
    1253          // Last time user has accessed the site.
    1254          if (!empty($user->lastaccess)) {
    1255              $details['lastaccess'] = format_time($now - $user->lastaccess);
    1256          }
    1257  
    1258          // Last time user has accessed the course.
    1259          if (!empty($user->lastcourseaccess)) {
    1260              $details['lastcourseaccess'] = format_time($now - $user->lastcourseaccess);
    1261          }
    1262          return $details;
    1263      }
    1264  
    1265      public function get_manual_enrol_buttons() {
    1266          $plugins = $this->get_enrolment_plugins(true); // Skip disabled plugins.
    1267          $buttons = array();
    1268          foreach ($plugins as $plugin) {
    1269              $newbutton = $plugin->get_manual_enrol_button($this);
    1270              if (is_array($newbutton)) {
    1271                  $buttons += $newbutton;
    1272              } else if ($newbutton instanceof enrol_user_button) {
    1273                  $buttons[] = $newbutton;
    1274              }
    1275          }
    1276          return $buttons;
    1277      }
    1278  
    1279      public function has_instance($enrolpluginname) {
    1280          // Make sure manual enrolments instance exists
    1281          foreach ($this->get_enrolment_instances() as $instance) {
    1282              if ($instance->enrol == $enrolpluginname) {
    1283                  return true;
    1284              }
    1285          }
    1286          return false;
    1287      }
    1288  
    1289      /**
    1290       * Returns the enrolment plugin that the course manager was being filtered to.
    1291       *
    1292       * If no filter was being applied then this function returns false.
    1293       *
    1294       * @return enrol_plugin
    1295       */
    1296      public function get_filtered_enrolment_plugin() {
    1297          $instances = $this->get_enrolment_instances();
    1298          $plugins = $this->get_enrolment_plugins(false);
    1299  
    1300          if (empty($this->instancefilter) || !array_key_exists($this->instancefilter, $instances)) {
    1301              return false;
    1302          }
    1303  
    1304          $instance = $instances[$this->instancefilter];
    1305          return $plugins[$instance->enrol];
    1306      }
    1307  
    1308      /**
    1309       * Returns and array of users + enrolment details.
    1310       *
    1311       * Given an array of user id's this function returns and array of user enrolments for those users
    1312       * as well as enough user information to display the users name and picture for each enrolment.
    1313       *
    1314       * @global moodle_database $DB
    1315       * @param array $userids
    1316       * @return array
    1317       */
    1318      public function get_users_enrolments(array $userids) {
    1319          global $DB;
    1320  
    1321          $instances = $this->get_enrolment_instances();
    1322          $plugins = $this->get_enrolment_plugins(false);
    1323  
    1324          if  (!empty($this->instancefilter)) {
    1325              $instancesql = ' = :instanceid';
    1326              $instanceparams = array('instanceid' => $this->instancefilter);
    1327          } else {
    1328              list($instancesql, $instanceparams) = $DB->get_in_or_equal(array_keys($instances), SQL_PARAMS_NAMED, 'instanceid0000');
    1329          }
    1330  
    1331          $userfieldsapi = \core_user\fields::for_userpic();
    1332          $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
    1333          list($idsql, $idparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'userid0000');
    1334  
    1335          list($sort, $sortparams) = users_order_by_sql('u');
    1336  
    1337          $sql = "SELECT ue.id AS ueid, ue.status, ue.enrolid, ue.userid, ue.timestart, ue.timeend, ue.modifierid, ue.timecreated, ue.timemodified, $userfields
    1338                    FROM {user_enrolments} ue
    1339               LEFT JOIN {user} u ON u.id = ue.userid
    1340                   WHERE ue.enrolid $instancesql AND
    1341                         u.id $idsql
    1342                ORDER BY $sort";
    1343  
    1344          $rs = $DB->get_recordset_sql($sql, $idparams + $instanceparams + $sortparams);
    1345          $users = array();
    1346          foreach ($rs as $ue) {
    1347              $user = user_picture::unalias($ue);
    1348              $ue->id = $ue->ueid;
    1349              unset($ue->ueid);
    1350              if (!array_key_exists($user->id, $users)) {
    1351                  $user->enrolments = array();
    1352                  $users[$user->id] = $user;
    1353              }
    1354              $ue->enrolmentinstance = $instances[$ue->enrolid];
    1355              $ue->enrolmentplugin = $plugins[$ue->enrolmentinstance->enrol];
    1356              $users[$user->id]->enrolments[$ue->id] = $ue;
    1357          }
    1358          $rs->close();
    1359          return $users;
    1360      }
    1361  }
    1362  
    1363  /**
    1364   * A button that is used to enrol users in a course
    1365   *
    1366   * @copyright 2010 Sam Hemelryk
    1367   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1368   */
    1369  class enrol_user_button extends single_button {
    1370  
    1371      /**
    1372       * An array containing JS YUI modules required by this button
    1373       * @var array
    1374       */
    1375      protected $jsyuimodules = array();
    1376  
    1377      /**
    1378       * An array containing JS initialisation calls required by this button
    1379       * @var array
    1380       */
    1381      protected $jsinitcalls = array();
    1382  
    1383      /**
    1384       * An array strings required by JS for this button
    1385       * @var array
    1386       */
    1387      protected $jsstrings = array();
    1388  
    1389      /**
    1390       * Initialises the new enrol_user_button
    1391       *
    1392       * @staticvar int $count The number of enrol user buttons already created
    1393       * @param moodle_url $url
    1394       * @param string $label The text to display in the button
    1395       * @param string $method Either post or get
    1396       */
    1397      public function __construct(moodle_url $url, $label, $method = 'post') {
    1398          static $count = 0;
    1399          $count ++;
    1400          parent::__construct($url, $label, $method);
    1401          $this->class = 'singlebutton enrolusersbutton';
    1402          $this->formid = 'enrolusersbutton-'.$count;
    1403      }
    1404  
    1405      /**
    1406       * Adds a YUI module call that will be added to the page when the button is used.
    1407       *
    1408       * @param string|array $modules One or more modules to require
    1409       * @param string $function The JS function to call
    1410       * @param array $arguments An array of arguments to pass to the function
    1411       * @param string $galleryversion Deprecated: The gallery version to use
    1412       * @param bool $ondomready If true the call is postponed until the DOM is finished loading
    1413       */
    1414      public function require_yui_module($modules, $function, array $arguments = null, $galleryversion = null, $ondomready = false) {
    1415          if ($galleryversion != null) {
    1416              debugging('The galleryversion parameter to yui_module has been deprecated since Moodle 2.3.', DEBUG_DEVELOPER);
    1417          }
    1418  
    1419          $js = new stdClass;
    1420          $js->modules = (array)$modules;
    1421          $js->function = $function;
    1422          $js->arguments = $arguments;
    1423          $js->ondomready = $ondomready;
    1424          $this->jsyuimodules[] = $js;
    1425      }
    1426  
    1427      /**
    1428       * Adds a JS initialisation call to the page when the button is used.
    1429       *
    1430       * @param string $function The function to call
    1431       * @param array $extraarguments An array of arguments to pass to the function
    1432       * @param bool $ondomready If true the call is postponed until the DOM is finished loading
    1433       * @param array $module A module definition
    1434       */
    1435      public function require_js_init_call($function, array $extraarguments = null, $ondomready = false, array $module = null) {
    1436          $js = new stdClass;
    1437          $js->function = $function;
    1438          $js->extraarguments = $extraarguments;
    1439          $js->ondomready = $ondomready;
    1440          $js->module = $module;
    1441          $this->jsinitcalls[] = $js;
    1442      }
    1443  
    1444      /**
    1445       * Requires strings for JS that will be loaded when the button is used.
    1446       *
    1447       * @param type $identifiers
    1448       * @param string $component
    1449       * @param mixed $a
    1450       */
    1451      public function strings_for_js($identifiers, $component = 'moodle', $a = null) {
    1452          $string = new stdClass;
    1453          $string->identifiers = (array)$identifiers;
    1454          $string->component = $component;
    1455          $string->a = $a;
    1456          $this->jsstrings[] = $string;
    1457      }
    1458  
    1459      /**
    1460       * Initialises the JS that is required by this button
    1461       *
    1462       * @param moodle_page $page
    1463       */
    1464      public function initialise_js(moodle_page $page) {
    1465          foreach ($this->jsyuimodules as $js) {
    1466              $page->requires->yui_module($js->modules, $js->function, $js->arguments, null, $js->ondomready);
    1467          }
    1468          foreach ($this->jsinitcalls as $js) {
    1469              $page->requires->js_init_call($js->function, $js->extraarguments, $js->ondomready, $js->module);
    1470          }
    1471          foreach ($this->jsstrings as $string) {
    1472              $page->requires->strings_for_js($string->identifiers, $string->component, $string->a);
    1473          }
    1474      }
    1475  }
    1476  
    1477  /**
    1478   * User enrolment action
    1479   *
    1480   * This class is used to manage a renderable ue action such as editing an user enrolment or deleting
    1481   * a user enrolment.
    1482   *
    1483   * @copyright  2011 Sam Hemelryk
    1484   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1485   */
    1486  class user_enrolment_action implements renderable {
    1487  
    1488      /**
    1489       * The icon to display for the action
    1490       * @var pix_icon
    1491       */
    1492      protected $icon;
    1493  
    1494      /**
    1495       * The title for the action
    1496       * @var string
    1497       */
    1498      protected $title;
    1499  
    1500      /**
    1501       * The URL to the action
    1502       * @var moodle_url
    1503       */
    1504      protected $url;
    1505  
    1506      /**
    1507       * An array of HTML attributes
    1508       * @var array
    1509       */
    1510      protected $attributes = array();
    1511  
    1512      /**
    1513       * Constructor
    1514       * @param pix_icon $icon
    1515       * @param string $title
    1516       * @param moodle_url $url
    1517       * @param array $attributes
    1518       */
    1519      public function __construct(pix_icon $icon, $title, $url, array $attributes = null) {
    1520          $this->icon = $icon;
    1521          $this->title = $title;
    1522          $this->url = new moodle_url($url);
    1523          if (!empty($attributes)) {
    1524              $this->attributes = $attributes;
    1525          }
    1526          $this->attributes['title'] = $title;
    1527      }
    1528  
    1529      /**
    1530       * Returns the icon for this action
    1531       * @return pix_icon
    1532       */
    1533      public function get_icon() {
    1534          return $this->icon;
    1535      }
    1536  
    1537      /**
    1538       * Returns the title for this action
    1539       * @return string
    1540       */
    1541      public function get_title() {
    1542          return $this->title;
    1543      }
    1544  
    1545      /**
    1546       * Returns the URL for this action
    1547       * @return moodle_url
    1548       */
    1549      public function get_url() {
    1550          return $this->url;
    1551      }
    1552  
    1553      /**
    1554       * Returns the attributes to use for this action
    1555       * @return array
    1556       */
    1557      public function get_attributes() {
    1558          return $this->attributes;
    1559      }
    1560  }
    1561  
    1562  class enrol_ajax_exception extends moodle_exception {
    1563      /**
    1564       * Constructor
    1565       * @param string $errorcode The name of the string from error.php to print
    1566       * @param string $module name of module
    1567       * @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.
    1568       * @param object $a Extra words and phrases that might be required in the error string
    1569       * @param string $debuginfo optional debugging information
    1570       */
    1571      public function __construct($errorcode, $link = '', $a = NULL, $debuginfo = null) {
    1572          parent::__construct($errorcode, 'enrol', $link, $a, $debuginfo);
    1573      }
    1574  }
    1575  
    1576  /**
    1577   * This class is used to manage a bulk operations for enrolment plugins.
    1578   *
    1579   * @copyright 2011 Sam Hemelryk
    1580   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
    1581   */
    1582  abstract class enrol_bulk_enrolment_operation {
    1583  
    1584      /**
    1585       * The course enrolment manager
    1586       * @var course_enrolment_manager
    1587       */
    1588      protected $manager;
    1589  
    1590      /**
    1591       * The enrolment plugin to which this operation belongs
    1592       * @var enrol_plugin
    1593       */
    1594      protected $plugin;
    1595  
    1596      /**
    1597       * Contructor
    1598       * @param course_enrolment_manager $manager
    1599       * @param stdClass $plugin
    1600       */
    1601      public function __construct(course_enrolment_manager $manager, enrol_plugin $plugin = null) {
    1602          $this->manager = $manager;
    1603          $this->plugin = $plugin;
    1604      }
    1605  
    1606      /**
    1607       * Returns a moodleform used for this operation, or false if no form is required and the action
    1608       * should be immediatly processed.
    1609       *
    1610       * @param moodle_url|string $defaultaction
    1611       * @param mixed $defaultcustomdata
    1612       * @return enrol_bulk_enrolment_change_form|moodleform|false
    1613       */
    1614      public function get_form($defaultaction = null, $defaultcustomdata = null) {
    1615          return false;
    1616      }
    1617  
    1618      /**
    1619       * Returns the title to use for this bulk operation
    1620       *
    1621       * @return string
    1622       */
    1623      abstract public function get_title();
    1624  
    1625      /**
    1626       * Returns the identifier for this bulk operation.
    1627       * This should be the same identifier used by the plugins function when returning
    1628       * all of its bulk operations.
    1629       *
    1630       * @return string
    1631       */
    1632      abstract public function get_identifier();
    1633  
    1634      /**
    1635       * Processes the bulk operation on the given users
    1636       *
    1637       * @param course_enrolment_manager $manager
    1638       * @param array $users
    1639       * @param stdClass $properties
    1640       */
    1641      abstract public function process(course_enrolment_manager $manager, array $users, stdClass $properties);
    1642  }