PageRenderTime 45ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/enrollib.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 2327 lines | 1292 code | 302 blank | 733 comment | 236 complexity | a1b7184a9727ce5936ae2896ad219b47 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0

Large files files are truncated, but you can click here to view the full file

  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. * This library includes the basic parts of enrol api.
  18. * It is available on each page.
  19. *
  20. * @package core
  21. * @subpackage enrol
  22. * @copyright 2010 Petr Skoda {@link http://skodak.org}
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24. */
  25. defined('MOODLE_INTERNAL') || die();
  26. /** Course enrol instance enabled. (used in enrol->status) */
  27. define('ENROL_INSTANCE_ENABLED', 0);
  28. /** Course enrol instance disabled, user may enter course if other enrol instance enabled. (used in enrol->status)*/
  29. define('ENROL_INSTANCE_DISABLED', 1);
  30. /** User is active participant (used in user_enrolments->status)*/
  31. define('ENROL_USER_ACTIVE', 0);
  32. /** User participation in course is suspended (used in user_enrolments->status) */
  33. define('ENROL_USER_SUSPENDED', 1);
  34. /** @deprecated - enrol caching was reworked, use ENROL_MAX_TIMESTAMP instead */
  35. define('ENROL_REQUIRE_LOGIN_CACHE_PERIOD', 1800);
  36. /** The timestamp indicating forever */
  37. define('ENROL_MAX_TIMESTAMP', 2147483647);
  38. /** When user disappears from external source, the enrolment is completely removed */
  39. define('ENROL_EXT_REMOVED_UNENROL', 0);
  40. /** When user disappears from external source, the enrolment is kept as is - one way sync */
  41. define('ENROL_EXT_REMOVED_KEEP', 1);
  42. /** @deprecated since 2.4 not used any more, migrate plugin to new restore methods */
  43. define('ENROL_RESTORE_TYPE', 'enrolrestore');
  44. /**
  45. * When user disappears from external source, user enrolment is suspended, roles are kept as is.
  46. * In some cases user needs a role with some capability to be visible in UI - suc has in gradebook,
  47. * assignments, etc.
  48. */
  49. define('ENROL_EXT_REMOVED_SUSPEND', 2);
  50. /**
  51. * When user disappears from external source, the enrolment is suspended and roles assigned
  52. * by enrol instance are removed. Please note that user may "disappear" from gradebook and other areas.
  53. * */
  54. define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
  55. /**
  56. * Returns instances of enrol plugins
  57. * @param bool $enabled return enabled only
  58. * @return array of enrol plugins name=>instance
  59. */
  60. function enrol_get_plugins($enabled) {
  61. global $CFG;
  62. $result = array();
  63. if ($enabled) {
  64. // sorted by enabled plugin order
  65. $enabled = explode(',', $CFG->enrol_plugins_enabled);
  66. $plugins = array();
  67. foreach ($enabled as $plugin) {
  68. $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
  69. }
  70. } else {
  71. // sorted alphabetically
  72. $plugins = core_component::get_plugin_list('enrol');
  73. ksort($plugins);
  74. }
  75. foreach ($plugins as $plugin=>$location) {
  76. if (!file_exists("$location/lib.php")) {
  77. continue;
  78. }
  79. include_once("$location/lib.php");
  80. $class = "enrol_{$plugin}_plugin";
  81. if (!class_exists($class)) {
  82. continue;
  83. }
  84. $result[$plugin] = new $class();
  85. }
  86. return $result;
  87. }
  88. /**
  89. * Returns instance of enrol plugin
  90. * @param string $name name of enrol plugin ('manual', 'guest', ...)
  91. * @return enrol_plugin
  92. */
  93. function enrol_get_plugin($name) {
  94. global $CFG;
  95. $name = clean_param($name, PARAM_PLUGIN);
  96. if (empty($name)) {
  97. // ignore malformed or missing plugin names completely
  98. return null;
  99. }
  100. $location = "$CFG->dirroot/enrol/$name";
  101. if (!file_exists("$location/lib.php")) {
  102. return null;
  103. }
  104. include_once("$location/lib.php");
  105. $class = "enrol_{$name}_plugin";
  106. if (!class_exists($class)) {
  107. return null;
  108. }
  109. return new $class();
  110. }
  111. /**
  112. * Returns enrolment instances in given course.
  113. * @param int $courseid
  114. * @param bool $enabled
  115. * @return array of enrol instances
  116. */
  117. function enrol_get_instances($courseid, $enabled) {
  118. global $DB, $CFG;
  119. if (!$enabled) {
  120. return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
  121. }
  122. $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id');
  123. $enabled = explode(',', $CFG->enrol_plugins_enabled);
  124. foreach ($result as $key=>$instance) {
  125. if (!in_array($instance->enrol, $enabled)) {
  126. unset($result[$key]);
  127. continue;
  128. }
  129. if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
  130. // broken plugin
  131. unset($result[$key]);
  132. continue;
  133. }
  134. }
  135. return $result;
  136. }
  137. /**
  138. * Checks if a given plugin is in the list of enabled enrolment plugins.
  139. *
  140. * @param string $enrol Enrolment plugin name
  141. * @return boolean Whether the plugin is enabled
  142. */
  143. function enrol_is_enabled($enrol) {
  144. global $CFG;
  145. if (empty($CFG->enrol_plugins_enabled)) {
  146. return false;
  147. }
  148. return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
  149. }
  150. /**
  151. * Check all the login enrolment information for the given user object
  152. * by querying the enrolment plugins
  153. *
  154. * This function may be very slow, use only once after log-in or login-as.
  155. *
  156. * @param stdClass $user
  157. * @return void
  158. */
  159. function enrol_check_plugins($user) {
  160. global $CFG;
  161. if (empty($user->id) or isguestuser($user)) {
  162. // shortcut - there is no enrolment work for guests and not-logged-in users
  163. return;
  164. }
  165. // originally there was a broken admin test, but accidentally it was non-functional in 2.2,
  166. // which proved it was actually not necessary.
  167. static $inprogress = array(); // To prevent this function being called more than once in an invocation
  168. if (!empty($inprogress[$user->id])) {
  169. return;
  170. }
  171. $inprogress[$user->id] = true; // Set the flag
  172. $enabled = enrol_get_plugins(true);
  173. foreach($enabled as $enrol) {
  174. $enrol->sync_user_enrolments($user);
  175. }
  176. unset($inprogress[$user->id]); // Unset the flag
  177. }
  178. /**
  179. * Do these two students share any course?
  180. *
  181. * The courses has to be visible and enrolments has to be active,
  182. * timestart and timeend restrictions are ignored.
  183. *
  184. * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly
  185. * to true.
  186. *
  187. * @param stdClass|int $user1
  188. * @param stdClass|int $user2
  189. * @return bool
  190. */
  191. function enrol_sharing_course($user1, $user2) {
  192. return enrol_get_shared_courses($user1, $user2, false, true);
  193. }
  194. /**
  195. * Returns any courses shared by the two users
  196. *
  197. * The courses has to be visible and enrolments has to be active,
  198. * timestart and timeend restrictions are ignored.
  199. *
  200. * @global moodle_database $DB
  201. * @param stdClass|int $user1
  202. * @param stdClass|int $user2
  203. * @param bool $preloadcontexts If set to true contexts for the returned courses
  204. * will be preloaded.
  205. * @param bool $checkexistsonly If set to true then this function will return true
  206. * if the users share any courses and false if not.
  207. * @return array|bool An array of courses that both users are enrolled in OR if
  208. * $checkexistsonly set returns true if the users share any courses
  209. * and false if not.
  210. */
  211. function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) {
  212. global $DB, $CFG;
  213. $user1 = isset($user1->id) ? $user1->id : $user1;
  214. $user2 = isset($user2->id) ? $user2->id : $user2;
  215. if (empty($user1) or empty($user2)) {
  216. return false;
  217. }
  218. if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
  219. return false;
  220. }
  221. list($plugins, $params) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee');
  222. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  223. $params['active1'] = ENROL_USER_ACTIVE;
  224. $params['active2'] = ENROL_USER_ACTIVE;
  225. $params['user1'] = $user1;
  226. $params['user2'] = $user2;
  227. $ctxselect = '';
  228. $ctxjoin = '';
  229. if ($preloadcontexts) {
  230. $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  231. $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  232. $params['contextlevel'] = CONTEXT_COURSE;
  233. }
  234. $sql = "SELECT c.* $ctxselect
  235. FROM {course} c
  236. JOIN (
  237. SELECT DISTINCT c.id
  238. FROM {enrol} e
  239. JOIN {user_enrolments} ue1 ON (ue1.enrolid = e.id AND ue1.status = :active1 AND ue1.userid = :user1)
  240. JOIN {user_enrolments} ue2 ON (ue2.enrolid = e.id AND ue2.status = :active2 AND ue2.userid = :user2)
  241. JOIN {course} c ON (c.id = e.courseid AND c.visible = 1)
  242. WHERE e.status = :enabled AND e.enrol $plugins
  243. ) ec ON ec.id = c.id
  244. $ctxjoin";
  245. if ($checkexistsonly) {
  246. return $DB->record_exists_sql($sql, $params);
  247. } else {
  248. $courses = $DB->get_records_sql($sql, $params);
  249. if ($preloadcontexts) {
  250. array_map('context_helper::preload_from_record', $courses);
  251. }
  252. return $courses;
  253. }
  254. }
  255. /**
  256. * This function adds necessary enrol plugins UI into the course edit form.
  257. *
  258. * @param MoodleQuickForm $mform
  259. * @param object $data course edit form data
  260. * @param object $context context of existing course or parent category if course does not exist
  261. * @return void
  262. */
  263. function enrol_course_edit_form(MoodleQuickForm $mform, $data, $context) {
  264. $plugins = enrol_get_plugins(true);
  265. if (!empty($data->id)) {
  266. $instances = enrol_get_instances($data->id, false);
  267. foreach ($instances as $instance) {
  268. if (!isset($plugins[$instance->enrol])) {
  269. continue;
  270. }
  271. $plugin = $plugins[$instance->enrol];
  272. $plugin->course_edit_form($instance, $mform, $data, $context);
  273. }
  274. } else {
  275. foreach ($plugins as $plugin) {
  276. $plugin->course_edit_form(NULL, $mform, $data, $context);
  277. }
  278. }
  279. }
  280. /**
  281. * Validate course edit form data
  282. *
  283. * @param array $data raw form data
  284. * @param object $context context of existing course or parent category if course does not exist
  285. * @return array errors array
  286. */
  287. function enrol_course_edit_validation(array $data, $context) {
  288. $errors = array();
  289. $plugins = enrol_get_plugins(true);
  290. if (!empty($data['id'])) {
  291. $instances = enrol_get_instances($data['id'], false);
  292. foreach ($instances as $instance) {
  293. if (!isset($plugins[$instance->enrol])) {
  294. continue;
  295. }
  296. $plugin = $plugins[$instance->enrol];
  297. $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
  298. }
  299. } else {
  300. foreach ($plugins as $plugin) {
  301. $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
  302. }
  303. }
  304. return $errors;
  305. }
  306. /**
  307. * Update enrol instances after course edit form submission
  308. * @param bool $inserted true means new course added, false course already existed
  309. * @param object $course
  310. * @param object $data form data
  311. * @return void
  312. */
  313. function enrol_course_updated($inserted, $course, $data) {
  314. global $DB, $CFG;
  315. $plugins = enrol_get_plugins(true);
  316. foreach ($plugins as $plugin) {
  317. $plugin->course_updated($inserted, $course, $data);
  318. }
  319. }
  320. /**
  321. * Add navigation nodes
  322. * @param navigation_node $coursenode
  323. * @param object $course
  324. * @return void
  325. */
  326. function enrol_add_course_navigation(navigation_node $coursenode, $course) {
  327. global $CFG;
  328. $coursecontext = context_course::instance($course->id);
  329. $instances = enrol_get_instances($course->id, true);
  330. $plugins = enrol_get_plugins(true);
  331. // we do not want to break all course pages if there is some borked enrol plugin, right?
  332. foreach ($instances as $k=>$instance) {
  333. if (!isset($plugins[$instance->enrol])) {
  334. unset($instances[$k]);
  335. }
  336. }
  337. $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
  338. if ($course->id != SITEID) {
  339. // list all participants - allows assigning roles, groups, etc.
  340. if (has_capability('moodle/course:enrolreview', $coursecontext)) {
  341. $url = new moodle_url('/enrol/users.php', array('id'=>$course->id));
  342. $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/enrolusers', ''));
  343. }
  344. // manage enrol plugin instances
  345. if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
  346. $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
  347. } else {
  348. $url = NULL;
  349. }
  350. $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
  351. // each instance decides how to configure itself or how many other nav items are exposed
  352. foreach ($instances as $instance) {
  353. if (!isset($plugins[$instance->enrol])) {
  354. continue;
  355. }
  356. $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
  357. }
  358. if (!$url) {
  359. $instancesnode->trim_if_empty();
  360. }
  361. }
  362. // Manage groups in this course or even frontpage
  363. if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
  364. $url = new moodle_url('/group/index.php', array('id'=>$course->id));
  365. $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
  366. }
  367. if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
  368. // Override roles
  369. if (has_capability('moodle/role:review', $coursecontext)) {
  370. $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
  371. } else {
  372. $url = NULL;
  373. }
  374. $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
  375. // Add assign or override roles if allowed
  376. if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
  377. if (has_capability('moodle/role:assign', $coursecontext)) {
  378. $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
  379. $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
  380. }
  381. }
  382. // Check role permissions
  383. if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
  384. $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
  385. $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
  386. }
  387. }
  388. // Deal somehow with users that are not enrolled but still got a role somehow
  389. if ($course->id != SITEID) {
  390. //TODO, create some new UI for role assignments at course level
  391. if (has_capability('moodle/role:assign', $coursecontext)) {
  392. $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
  393. $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/assignroles', ''));
  394. }
  395. }
  396. // just in case nothing was actually added
  397. $usersnode->trim_if_empty();
  398. if ($course->id != SITEID) {
  399. if (isguestuser() or !isloggedin()) {
  400. // guest account can not be enrolled - no links for them
  401. } else if (is_enrolled($coursecontext)) {
  402. // unenrol link if possible
  403. foreach ($instances as $instance) {
  404. if (!isset($plugins[$instance->enrol])) {
  405. continue;
  406. }
  407. $plugin = $plugins[$instance->enrol];
  408. if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
  409. $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
  410. $coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
  411. break;
  412. //TODO. deal with multiple unenrol links - not likely case, but still...
  413. }
  414. }
  415. } else {
  416. // enrol link if possible
  417. if (is_viewing($coursecontext)) {
  418. // better not show any enrol link, this is intended for managers and inspectors
  419. } else {
  420. foreach ($instances as $instance) {
  421. if (!isset($plugins[$instance->enrol])) {
  422. continue;
  423. }
  424. $plugin = $plugins[$instance->enrol];
  425. if ($plugin->show_enrolme_link($instance)) {
  426. $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
  427. $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
  428. $coursenode->add(get_string('enrolme', 'core_enrol', $shortname), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
  429. break;
  430. }
  431. }
  432. }
  433. }
  434. }
  435. }
  436. /**
  437. * Returns list of courses current $USER is enrolled in and can access
  438. *
  439. * - $fields is an array of field names to ADD
  440. * so name the fields you really need, which will
  441. * be added and uniq'd
  442. *
  443. * @param string|array $fields
  444. * @param string $sort
  445. * @param int $limit max number of courses
  446. * @return array
  447. */
  448. function enrol_get_my_courses($fields = NULL, $sort = 'visible DESC,sortorder ASC', $limit = 0) {
  449. global $DB, $USER;
  450. // Guest account does not have any courses
  451. if (isguestuser() or !isloggedin()) {
  452. return(array());
  453. }
  454. $basefields = array('id', 'category', 'sortorder',
  455. 'shortname', 'fullname', 'idnumber',
  456. 'startdate', 'visible',
  457. 'groupmode', 'groupmodeforce', 'cacherev');
  458. if (empty($fields)) {
  459. $fields = $basefields;
  460. } else if (is_string($fields)) {
  461. // turn the fields from a string to an array
  462. $fields = explode(',', $fields);
  463. $fields = array_map('trim', $fields);
  464. $fields = array_unique(array_merge($basefields, $fields));
  465. } else if (is_array($fields)) {
  466. $fields = array_unique(array_merge($basefields, $fields));
  467. } else {
  468. throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
  469. }
  470. if (in_array('*', $fields)) {
  471. $fields = array('*');
  472. }
  473. $orderby = "";
  474. $sort = trim($sort);
  475. if (!empty($sort)) {
  476. $rawsorts = explode(',', $sort);
  477. $sorts = array();
  478. foreach ($rawsorts as $rawsort) {
  479. $rawsort = trim($rawsort);
  480. if (strpos($rawsort, 'c.') === 0) {
  481. $rawsort = substr($rawsort, 2);
  482. }
  483. $sorts[] = trim($rawsort);
  484. }
  485. $sort = 'c.'.implode(',c.', $sorts);
  486. $orderby = "ORDER BY $sort";
  487. }
  488. $wheres = array("c.id <> :siteid");
  489. $params = array('siteid'=>SITEID);
  490. if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
  491. // list _only_ this course - anything else is asking for trouble...
  492. $wheres[] = "courseid = :loginas";
  493. $params['loginas'] = $USER->loginascontext->instanceid;
  494. }
  495. $coursefields = 'c.' .join(',c.', $fields);
  496. $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  497. $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  498. $params['contextlevel'] = CONTEXT_COURSE;
  499. $wheres = implode(" AND ", $wheres);
  500. //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
  501. $sql = "SELECT $coursefields $ccselect
  502. FROM {course} c
  503. JOIN (SELECT DISTINCT e.courseid
  504. FROM {enrol} e
  505. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
  506. WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)
  507. ) en ON (en.courseid = c.id)
  508. $ccjoin
  509. WHERE $wheres
  510. $orderby";
  511. $params['userid'] = $USER->id;
  512. $params['active'] = ENROL_USER_ACTIVE;
  513. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  514. $params['now1'] = round(time(), -2); // improves db caching
  515. $params['now2'] = $params['now1'];
  516. $courses = $DB->get_records_sql($sql, $params, 0, $limit);
  517. // preload contexts and check visibility
  518. foreach ($courses as $id=>$course) {
  519. context_helper::preload_from_record($course);
  520. if (!$course->visible) {
  521. if (!$context = context_course::instance($id, IGNORE_MISSING)) {
  522. unset($courses[$id]);
  523. continue;
  524. }
  525. if (!has_capability('moodle/course:viewhiddencourses', $context)) {
  526. unset($courses[$id]);
  527. continue;
  528. }
  529. }
  530. $courses[$id] = $course;
  531. }
  532. //wow! Is that really all? :-D
  533. return $courses;
  534. }
  535. /**
  536. * Returns course enrolment information icons.
  537. *
  538. * @param object $course
  539. * @param array $instances enrol instances of this course, improves performance
  540. * @return array of pix_icon
  541. */
  542. function enrol_get_course_info_icons($course, array $instances = NULL) {
  543. $icons = array();
  544. if (is_null($instances)) {
  545. $instances = enrol_get_instances($course->id, true);
  546. }
  547. $plugins = enrol_get_plugins(true);
  548. foreach ($plugins as $name => $plugin) {
  549. $pis = array();
  550. foreach ($instances as $instance) {
  551. if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
  552. debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
  553. continue;
  554. }
  555. if ($instance->enrol == $name) {
  556. $pis[$instance->id] = $instance;
  557. }
  558. }
  559. if ($pis) {
  560. $icons = array_merge($icons, $plugin->get_info_icons($pis));
  561. }
  562. }
  563. return $icons;
  564. }
  565. /**
  566. * Returns course enrolment detailed information.
  567. *
  568. * @param object $course
  569. * @return array of html fragments - can be used to construct lists
  570. */
  571. function enrol_get_course_description_texts($course) {
  572. $lines = array();
  573. $instances = enrol_get_instances($course->id, true);
  574. $plugins = enrol_get_plugins(true);
  575. foreach ($instances as $instance) {
  576. if (!isset($plugins[$instance->enrol])) {
  577. //weird
  578. continue;
  579. }
  580. $plugin = $plugins[$instance->enrol];
  581. $text = $plugin->get_description_text($instance);
  582. if ($text !== NULL) {
  583. $lines[] = $text;
  584. }
  585. }
  586. return $lines;
  587. }
  588. /**
  589. * Returns list of courses user is enrolled into.
  590. * (Note: use enrol_get_all_users_courses if you want to use the list wihtout any cap checks )
  591. *
  592. * - $fields is an array of fieldnames to ADD
  593. * so name the fields you really need, which will
  594. * be added and uniq'd
  595. *
  596. * @param int $userid
  597. * @param bool $onlyactive return only active enrolments in courses user may see
  598. * @param string|array $fields
  599. * @param string $sort
  600. * @return array
  601. */
  602. function enrol_get_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
  603. global $DB;
  604. $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort);
  605. // preload contexts and check visibility
  606. if ($onlyactive) {
  607. foreach ($courses as $id=>$course) {
  608. context_helper::preload_from_record($course);
  609. if (!$course->visible) {
  610. if (!$context = context_course::instance($id)) {
  611. unset($courses[$id]);
  612. continue;
  613. }
  614. if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
  615. unset($courses[$id]);
  616. continue;
  617. }
  618. }
  619. }
  620. }
  621. return $courses;
  622. }
  623. /**
  624. * Can user access at least one enrolled course?
  625. *
  626. * Cheat if necessary, but find out as fast as possible!
  627. *
  628. * @param int|stdClass $user null means use current user
  629. * @return bool
  630. */
  631. function enrol_user_sees_own_courses($user = null) {
  632. global $USER;
  633. if ($user === null) {
  634. $user = $USER;
  635. }
  636. $userid = is_object($user) ? $user->id : $user;
  637. // Guest account does not have any courses
  638. if (isguestuser($userid) or empty($userid)) {
  639. return false;
  640. }
  641. // Let's cheat here if this is the current user,
  642. // if user accessed any course recently, then most probably
  643. // we do not need to query the database at all.
  644. if ($USER->id == $userid) {
  645. if (!empty($USER->enrol['enrolled'])) {
  646. foreach ($USER->enrol['enrolled'] as $until) {
  647. if ($until > time()) {
  648. return true;
  649. }
  650. }
  651. }
  652. }
  653. // Now the slow way.
  654. $courses = enrol_get_all_users_courses($userid, true);
  655. foreach($courses as $course) {
  656. if ($course->visible) {
  657. return true;
  658. }
  659. context_helper::preload_from_record($course);
  660. $context = context_course::instance($course->id);
  661. if (has_capability('moodle/course:viewhiddencourses', $context, $user)) {
  662. return true;
  663. }
  664. }
  665. return false;
  666. }
  667. /**
  668. * Returns list of courses user is enrolled into without any capability checks
  669. * - $fields is an array of fieldnames to ADD
  670. * so name the fields you really need, which will
  671. * be added and uniq'd
  672. *
  673. * @param int $userid
  674. * @param bool $onlyactive return only active enrolments in courses user may see
  675. * @param string|array $fields
  676. * @param string $sort
  677. * @return array
  678. */
  679. function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = NULL, $sort = 'visible DESC,sortorder ASC') {
  680. global $DB;
  681. // Guest account does not have any courses
  682. if (isguestuser($userid) or empty($userid)) {
  683. return(array());
  684. }
  685. $basefields = array('id', 'category', 'sortorder',
  686. 'shortname', 'fullname', 'idnumber',
  687. 'startdate', 'visible',
  688. 'groupmode', 'groupmodeforce');
  689. if (empty($fields)) {
  690. $fields = $basefields;
  691. } else if (is_string($fields)) {
  692. // turn the fields from a string to an array
  693. $fields = explode(',', $fields);
  694. $fields = array_map('trim', $fields);
  695. $fields = array_unique(array_merge($basefields, $fields));
  696. } else if (is_array($fields)) {
  697. $fields = array_unique(array_merge($basefields, $fields));
  698. } else {
  699. throw new coding_exception('Invalid $fileds parameter in enrol_get_my_courses()');
  700. }
  701. if (in_array('*', $fields)) {
  702. $fields = array('*');
  703. }
  704. $orderby = "";
  705. $sort = trim($sort);
  706. if (!empty($sort)) {
  707. $rawsorts = explode(',', $sort);
  708. $sorts = array();
  709. foreach ($rawsorts as $rawsort) {
  710. $rawsort = trim($rawsort);
  711. if (strpos($rawsort, 'c.') === 0) {
  712. $rawsort = substr($rawsort, 2);
  713. }
  714. $sorts[] = trim($rawsort);
  715. }
  716. $sort = 'c.'.implode(',c.', $sorts);
  717. $orderby = "ORDER BY $sort";
  718. }
  719. $params = array('siteid'=>SITEID);
  720. if ($onlyactive) {
  721. $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
  722. $params['now1'] = round(time(), -2); // improves db caching
  723. $params['now2'] = $params['now1'];
  724. $params['active'] = ENROL_USER_ACTIVE;
  725. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  726. } else {
  727. $subwhere = "";
  728. }
  729. $coursefields = 'c.' .join(',c.', $fields);
  730. $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  731. $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  732. $params['contextlevel'] = CONTEXT_COURSE;
  733. //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
  734. $sql = "SELECT $coursefields $ccselect
  735. FROM {course} c
  736. JOIN (SELECT DISTINCT e.courseid
  737. FROM {enrol} e
  738. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
  739. $subwhere
  740. ) en ON (en.courseid = c.id)
  741. $ccjoin
  742. WHERE c.id <> :siteid
  743. $orderby";
  744. $params['userid'] = $userid;
  745. $courses = $DB->get_records_sql($sql, $params);
  746. return $courses;
  747. }
  748. /**
  749. * Called when user is about to be deleted.
  750. * @param object $user
  751. * @return void
  752. */
  753. function enrol_user_delete($user) {
  754. global $DB;
  755. $plugins = enrol_get_plugins(true);
  756. foreach ($plugins as $plugin) {
  757. $plugin->user_delete($user);
  758. }
  759. // force cleanup of all broken enrolments
  760. $DB->delete_records('user_enrolments', array('userid'=>$user->id));
  761. }
  762. /**
  763. * Called when course is about to be deleted.
  764. * @param stdClass $course
  765. * @return void
  766. */
  767. function enrol_course_delete($course) {
  768. global $DB;
  769. $instances = enrol_get_instances($course->id, false);
  770. $plugins = enrol_get_plugins(true);
  771. foreach ($instances as $instance) {
  772. if (isset($plugins[$instance->enrol])) {
  773. $plugins[$instance->enrol]->delete_instance($instance);
  774. }
  775. // low level delete in case plugin did not do it
  776. $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
  777. $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$instance->enrol));
  778. $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
  779. $DB->delete_records('enrol', array('id'=>$instance->id));
  780. }
  781. }
  782. /**
  783. * Try to enrol user via default internal auth plugin.
  784. *
  785. * For now this is always using the manual enrol plugin...
  786. *
  787. * @param $courseid
  788. * @param $userid
  789. * @param $roleid
  790. * @param $timestart
  791. * @param $timeend
  792. * @return bool success
  793. */
  794. function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
  795. global $DB;
  796. //note: this is hardcoded to manual plugin for now
  797. if (!enrol_is_enabled('manual')) {
  798. return false;
  799. }
  800. if (!$enrol = enrol_get_plugin('manual')) {
  801. return false;
  802. }
  803. if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
  804. return false;
  805. }
  806. $instance = reset($instances);
  807. $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
  808. return true;
  809. }
  810. /**
  811. * Is there a chance users might self enrol
  812. * @param int $courseid
  813. * @return bool
  814. */
  815. function enrol_selfenrol_available($courseid) {
  816. $result = false;
  817. $plugins = enrol_get_plugins(true);
  818. $enrolinstances = enrol_get_instances($courseid, true);
  819. foreach($enrolinstances as $instance) {
  820. if (!isset($plugins[$instance->enrol])) {
  821. continue;
  822. }
  823. if ($instance->enrol === 'guest') {
  824. // blacklist known temporary guest plugins
  825. continue;
  826. }
  827. if ($plugins[$instance->enrol]->show_enrolme_link($instance)) {
  828. $result = true;
  829. break;
  830. }
  831. }
  832. return $result;
  833. }
  834. /**
  835. * This function returns the end of current active user enrolment.
  836. *
  837. * It deals correctly with multiple overlapping user enrolments.
  838. *
  839. * @param int $courseid
  840. * @param int $userid
  841. * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
  842. */
  843. function enrol_get_enrolment_end($courseid, $userid) {
  844. global $DB;
  845. $sql = "SELECT ue.*
  846. FROM {user_enrolments} ue
  847. JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
  848. JOIN {user} u ON u.id = ue.userid
  849. WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
  850. $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
  851. if (!$enrolments = $DB->get_records_sql($sql, $params)) {
  852. return false;
  853. }
  854. $changes = array();
  855. foreach ($enrolments as $ue) {
  856. $start = (int)$ue->timestart;
  857. $end = (int)$ue->timeend;
  858. if ($end != 0 and $end < $start) {
  859. debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
  860. continue;
  861. }
  862. if (isset($changes[$start])) {
  863. $changes[$start] = $changes[$start] + 1;
  864. } else {
  865. $changes[$start] = 1;
  866. }
  867. if ($end === 0) {
  868. // no end
  869. } else if (isset($changes[$end])) {
  870. $changes[$end] = $changes[$end] - 1;
  871. } else {
  872. $changes[$end] = -1;
  873. }
  874. }
  875. // let's sort then enrolment starts&ends and go through them chronologically,
  876. // looking for current status and the next future end of enrolment
  877. ksort($changes);
  878. $now = time();
  879. $current = 0;
  880. $present = null;
  881. foreach ($changes as $time => $change) {
  882. if ($time > $now) {
  883. if ($present === null) {
  884. // we have just went past current time
  885. $present = $current;
  886. if ($present < 1) {
  887. // no enrolment active
  888. return false;
  889. }
  890. }
  891. if ($present !== null) {
  892. // we are already in the future - look for possible end
  893. if ($current + $change < 1) {
  894. return $time;
  895. }
  896. }
  897. }
  898. $current += $change;
  899. }
  900. if ($current > 0) {
  901. return 0;
  902. } else {
  903. return false;
  904. }
  905. }
  906. /**
  907. * Is current user accessing course via this enrolment method?
  908. *
  909. * This is intended for operations that are going to affect enrol instances.
  910. *
  911. * @param stdClass $instance enrol instance
  912. * @return bool
  913. */
  914. function enrol_accessing_via_instance(stdClass $instance) {
  915. global $DB, $USER;
  916. if (empty($instance->id)) {
  917. return false;
  918. }
  919. if (is_siteadmin()) {
  920. // Admins may go anywhere.
  921. return false;
  922. }
  923. return $DB->record_exists('user_enrolments', array('userid'=>$USER->id, 'enrolid'=>$instance->id));
  924. }
  925. /**
  926. * All enrol plugins should be based on this class,
  927. * this is also the main source of documentation.
  928. */
  929. abstract class enrol_plugin {
  930. protected $config = null;
  931. /**
  932. * Returns name of this enrol plugin
  933. * @return string
  934. */
  935. public function get_name() {
  936. // second word in class is always enrol name, sorry, no fancy plugin names with _
  937. $words = explode('_', get_class($this));
  938. return $words[1];
  939. }
  940. /**
  941. * Returns localised name of enrol instance
  942. *
  943. * @param object $instance (null is accepted too)
  944. * @return string
  945. */
  946. public function get_instance_name($instance) {
  947. if (empty($instance->name)) {
  948. $enrol = $this->get_name();
  949. return get_string('pluginname', 'enrol_'.$enrol);
  950. } else {
  951. $context = context_course::instance($instance->courseid);
  952. return format_string($instance->name, true, array('context'=>$context));
  953. }
  954. }
  955. /**
  956. * Returns optional enrolment information icons.
  957. *
  958. * This is used in course list for quick overview of enrolment options.
  959. *
  960. * We are not using single instance parameter because sometimes
  961. * we might want to prevent icon repetition when multiple instances
  962. * of one type exist. One instance may also produce several icons.
  963. *
  964. * @param array $instances all enrol instances of this type in one course
  965. * @return array of pix_icon
  966. */
  967. public function get_info_icons(array $instances) {
  968. return array();
  969. }
  970. /**
  971. * Returns optional enrolment instance description text.
  972. *
  973. * This is used in detailed course information.
  974. *
  975. *
  976. * @param object $instance
  977. * @return string short html text
  978. */
  979. public function get_description_text($instance) {
  980. return null;
  981. }
  982. /**
  983. * Makes sure config is loaded and cached.
  984. * @return void
  985. */
  986. protected function load_config() {
  987. if (!isset($this->config)) {
  988. $name = $this->get_name();
  989. $this->config = get_config("enrol_$name");
  990. }
  991. }
  992. /**
  993. * Returns plugin config value
  994. * @param string $name
  995. * @param string $default value if config does not exist yet
  996. * @return string value or default
  997. */
  998. public function get_config($name, $default = NULL) {
  999. $this->load_config();
  1000. return isset($this->config->$name) ? $this->config->$name : $default;
  1001. }
  1002. /**
  1003. * Sets plugin config value
  1004. * @param string $name name of config
  1005. * @param string $value string config value, null means delete
  1006. * @return string value
  1007. */
  1008. public function set_config($name, $value) {
  1009. $pluginname = $this->get_name();
  1010. $this->load_config();
  1011. if ($value === NULL) {
  1012. unset($this->config->$name);
  1013. } else {
  1014. $this->config->$name = $value;
  1015. }
  1016. set_config($name, $value, "enrol_$pluginname");
  1017. }
  1018. /**
  1019. * Does this plugin assign protected roles are can they be manually removed?
  1020. * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
  1021. */
  1022. public function roles_protected() {
  1023. return true;
  1024. }
  1025. /**
  1026. * Does this plugin allow manual enrolments?
  1027. *
  1028. * @param stdClass $instance course enrol instance
  1029. * All plugins allowing this must implement 'enrol/xxx:enrol' capability
  1030. *
  1031. * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, false means nobody may add more enrolments manually
  1032. */
  1033. public function allow_enrol(stdClass $instance) {
  1034. return false;
  1035. }
  1036. /**
  1037. * Does this plugin allow manual unenrolment of all users?
  1038. * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
  1039. *
  1040. * @param stdClass $instance course enrol instance
  1041. * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
  1042. */
  1043. public function allow_unenrol(stdClass $instance) {
  1044. return false;
  1045. }
  1046. /**
  1047. * Does this plugin allow manual unenrolment of a specific user?
  1048. * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
  1049. *
  1050. * This is useful especially for synchronisation plugins that
  1051. * do suspend instead of full unenrolment.
  1052. *
  1053. * @param stdClass $instance course enrol instance
  1054. * @param stdClass $ue record from user_enrolments table, specifies user
  1055. *
  1056. * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
  1057. */
  1058. public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
  1059. return $this->allow_unenrol($instance);
  1060. }
  1061. /**
  1062. * Does this plugin allow manual changes in user_enrolments table?
  1063. *
  1064. * All plugins allowing this must implement 'enrol/xxx:manage' capability
  1065. *
  1066. * @param stdClass $instance course enrol instance
  1067. * @return bool - true means it is possible to change enrol period and status in user_enrolments table
  1068. */
  1069. public function allow_manage(stdClass $instance) {
  1070. return false;
  1071. }
  1072. /**
  1073. * Does this plugin support some way to user to self enrol?
  1074. *
  1075. * @param stdClass $instance course enrol instance
  1076. *
  1077. * @return bool - true means show "Enrol me in this course" link in course UI
  1078. */
  1079. public function show_enrolme_link(stdClass $instance) {
  1080. return false;
  1081. }
  1082. /**
  1083. * Attempt to automatically enrol current user in course without any interaction,
  1084. * calling code has to make sure the plugin and instance are active.
  1085. *
  1086. * This should return either a timestamp in the future or false.
  1087. *
  1088. * @param stdClass $instance course enrol instance
  1089. * @return bool|int false means not enrolled, integer means timeend
  1090. */
  1091. public function try_autoenrol(stdClass $instance) {
  1092. global $USER;
  1093. return false;
  1094. }
  1095. /**
  1096. * Attempt to automatically gain temporary guest access to course,
  1097. * calling code has to make sure the plugin and instance are active.
  1098. *
  1099. * This should return either a timestamp in the future or false.
  1100. *
  1101. * @param stdClass $instance course enrol instance
  1102. * @return bool|int false means no guest access, integer means timeend
  1103. */
  1104. public function try_guestaccess(stdClass $instance) {
  1105. global $USER;
  1106. return false;
  1107. }
  1108. /**
  1109. * Enrol user into course via enrol instance.
  1110. *
  1111. * @param stdClass $instance
  1112. * @param int $userid
  1113. * @param int $roleid optional role id
  1114. * @param int $timestart 0 means unknown
  1115. * @param int $timeend 0 means forever
  1116. * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
  1117. * @param bool $recovergrades restore grade history
  1118. * @return void
  1119. */
  1120. public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) {
  1121. global $DB, $USER, $CFG; // CFG necessary!!!
  1122. if ($instance->courseid == SITEID) {
  1123. throw new coding_exception('invalid attempt to enrol into frontpage course!');
  1124. }
  1125. $name = $this->get_name();
  1126. $courseid = $instance->courseid;
  1127. if ($instance->enrol !== $name) {
  1128. throw new coding_exception('invalid enrol instance!');
  1129. }
  1130. $context = context_course::instance($instance->courseid, MUST_EXIST);
  1131. if (!isset($recovergrades)) {
  1132. $recovergrades = $CFG->recovergradesdefault;
  1133. }
  1134. $inserted = false;
  1135. $updated = false;
  1136. if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
  1137. //only update if timestart or timeend or status are different.
  1138. if ($ue->timestart != $timestart or $ue->timeend != $timeend or (!is_null($status) and $ue->status != $status)) {
  1139. $this->update_user_enrol($instance, $userid, $status, $timestart, $timeend);
  1140. }
  1141. } else {
  1142. $ue = new stdClass();
  1143. $ue->enrolid = $instance->id;
  1144. $ue->status = is_null($status) ? ENROL_USER_ACTIVE : $status;
  1145. $ue->userid = $userid;
  1146. $ue->timestart = $timestart;
  1147. $ue->timeend = $timeend;
  1148. $ue->modifierid = $USER->id;
  1149. $ue->timecreated = time();
  1150. $ue->timemodified = $ue->timecreated;
  1151. $ue->id = $DB->insert_record('user_enrolments', $ue);
  1152. $inserted = true;
  1153. }
  1154. if ($inserted) {
  1155. // Trigger event.
  1156. $event = \core\event\user_enrolment_created::create(
  1157. array(
  1158. 'objectid' => $ue->id,
  1159. 'courseid' => $courseid,
  1160. 'context' => $context,
  1161. 'relateduserid' => $ue->userid,
  1162. 'other' => array('enrol' => $name)
  1163. )
  1164. );
  1165. $event->trigger();
  1166. }
  1167. if ($roleid) {
  1168. // this must be done after the enrolment event so that the role_assigned event is triggered afterwards
  1169. if ($this->roles_protected()) {
  1170. role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
  1171. } else {
  1172. role_assign($roleid, $userid, $context->id);
  1173. }
  1174. }
  1175. // Recover old grades if present.
  1176. if ($recovergrades) {
  1177. require_once("$CFG->libdir/gradelib.php");
  1178. grade_recover_history_grades($userid, $courseid);
  1179. }
  1180. // reset current user enrolment caching
  1181. if ($userid == $USER->id) {
  1182. if (isset($USER->enrol['enrolled'][$courseid])) {
  1183. unset($USER->enrol['enrolled'][$courseid]);
  1184. }
  1185. if (isset($USER->enrol['tempguest'][$courseid])) {
  1186. unset($USER->enrol['tempguest'][$courseid]);
  1187. remove_temp_course_roles($context);
  1188. }
  1189. }
  1190. }
  1191. /**
  1192. * Store user_enrolments changes and trigger event.
  1193. *
  1194. * @param stdClass $instance
  1195. * @param int $userid
  1196. * @param int $status
  1197. * @param int $timestart
  1198. * @param int $timeend
  1199. * @return void
  1200. */
  1201. public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
  1202. global $DB, $USER;
  1203. $name = $this->get_name();
  1204. if ($instance->enrol !== $name) {
  1205. throw new coding_exception('invalid enrol instance!');
  1206. }
  1207. if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
  1208. // weird, user not enrolled
  1209. return;
  1210. }
  1211. $modified = false;
  1212. if (isset($status) and $ue->status != $status) {
  1213. $ue->status = $status;
  1214. $modified = true;
  1215. }
  1216. if (isset($timestart) and $ue->timestart != $timestart) {
  1217. $ue->timestart = $timestart;
  1218. $modified = true;
  1219. }
  1220. if (isset($timeend) and $ue->timeend != $timeend) {
  1221. $ue->timeend = $timeend;
  1222. $modified = true;
  1223. }
  1224. if (!$modified) {
  1225. // no change
  1226. return;
  1227. }
  1228. $ue->modifierid = $USER->id;
  1229. $DB->update_record('user_enrolments', $ue);
  1230. context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
  1231. // Invalidate core_access cache for get_suspended_userids.
  1232. cache_helper::invalidate_by_definition('core', 'suspended_userids', array(), array($instance->courseid));
  1233. // Trigger event.
  1234. $event = \core\event\user_enrolment_updated::create(
  1235. array(
  1236. 'objectid' => $ue->id,
  1237. 'courseid' => $instance->courseid,
  1238. 'context' => context_course::instance($instance->courseid),
  1239. 'relateduserid' => $ue->userid,
  1240. 'other' => array('enrol' => $name)
  1241. )
  1242. );
  1243. $event->trigger();
  1244. }
  1245. /**
  1246. * Unenrol user from course,
  1247. * the last unenrolment removes all remaining roles.
  1248. *
  1249. * @param stdClass $instance
  1250. * @param int $userid
  1251. * @return void
  1252. */
  1253. public function unenrol_user(stdClass $instance, $userid) {
  1254. global $CFG, $USER, $DB;
  1255. require_once("$CFG->dirroot/group/lib.php");
  1256. $name = $this->get_name();
  1257. $courseid = $instance->courseid;
  1258. if ($instance->enrol !== $name) {
  1259. throw new coding_exception('invalid enrol instance!');
  1260. }
  1261. $context = context_course::instance($instance->courseid, MUST_EXIST);
  1262. if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
  1263. // weird, user not enrolled
  1264. return;
  1265. }
  1266. // Remove all users groups linked to this enrolment instance.
  1267. if ($gms = $DB->get_records('groups_members', array('userid'=>$userid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id))) {
  1268. foreach ($gms as $gm) {
  1269. groups_remove_member($gm->groupid, $gm->userid);
  1270. }
  1271. }
  1272. role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
  1273. $DB->delete_records('user_enrolments', array('id'=>$ue->id));
  1274. // add extra info and trigger event
  1275. $ue->courseid = $courseid;
  1276. $ue->enrol = $name;
  1277. $sql = "SELECT 'x'
  1278. FROM {user_enrolments} ue
  1279. JOIN {enrol} e ON (e.id = ue.enrolid)
  1280. WHERE ue.userid = :userid AND e.courseid = :courseid";
  1281. if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
  1282. $ue->lastenrol = false;
  1283. }

Large files files are truncated, but you can click here to view the full file