PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/enrollib.php

https://bitbucket.org/moodle/moodle
PHP | 3308 lines | 1856 code | 419 blank | 1033 comment | 339 complexity | 3b86461fbdce7716244b6aa12eb51c80 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-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. * Do not send email.
  57. */
  58. define('ENROL_DO_NOT_SEND_EMAIL', 0);
  59. /**
  60. * Send email from course contact.
  61. */
  62. define('ENROL_SEND_EMAIL_FROM_COURSE_CONTACT', 1);
  63. /**
  64. * Send email from enrolment key holder.
  65. */
  66. define('ENROL_SEND_EMAIL_FROM_KEY_HOLDER', 2);
  67. /**
  68. * Send email from no reply address.
  69. */
  70. define('ENROL_SEND_EMAIL_FROM_NOREPLY', 3);
  71. /** Edit enrolment action. */
  72. define('ENROL_ACTION_EDIT', 'editenrolment');
  73. /** Unenrol action. */
  74. define('ENROL_ACTION_UNENROL', 'unenrol');
  75. /**
  76. * Returns instances of enrol plugins
  77. * @param bool $enabled return enabled only
  78. * @return array of enrol plugins name=>instance
  79. */
  80. function enrol_get_plugins($enabled) {
  81. global $CFG;
  82. $result = array();
  83. if ($enabled) {
  84. // sorted by enabled plugin order
  85. $enabled = explode(',', $CFG->enrol_plugins_enabled);
  86. $plugins = array();
  87. foreach ($enabled as $plugin) {
  88. $plugins[$plugin] = "$CFG->dirroot/enrol/$plugin";
  89. }
  90. } else {
  91. // sorted alphabetically
  92. $plugins = core_component::get_plugin_list('enrol');
  93. ksort($plugins);
  94. }
  95. foreach ($plugins as $plugin=>$location) {
  96. $class = "enrol_{$plugin}_plugin";
  97. if (!class_exists($class)) {
  98. if (!file_exists("$location/lib.php")) {
  99. continue;
  100. }
  101. include_once("$location/lib.php");
  102. if (!class_exists($class)) {
  103. continue;
  104. }
  105. }
  106. $result[$plugin] = new $class();
  107. }
  108. return $result;
  109. }
  110. /**
  111. * Returns instance of enrol plugin
  112. * @param string $name name of enrol plugin ('manual', 'guest', ...)
  113. * @return enrol_plugin
  114. */
  115. function enrol_get_plugin($name) {
  116. global $CFG;
  117. $name = clean_param($name, PARAM_PLUGIN);
  118. if (empty($name)) {
  119. // ignore malformed or missing plugin names completely
  120. return null;
  121. }
  122. $location = "$CFG->dirroot/enrol/$name";
  123. $class = "enrol_{$name}_plugin";
  124. if (!class_exists($class)) {
  125. if (!file_exists("$location/lib.php")) {
  126. return null;
  127. }
  128. include_once("$location/lib.php");
  129. if (!class_exists($class)) {
  130. return null;
  131. }
  132. }
  133. return new $class();
  134. }
  135. /**
  136. * Returns enrolment instances in given course.
  137. * @param int $courseid
  138. * @param bool $enabled
  139. * @return array of enrol instances
  140. */
  141. function enrol_get_instances($courseid, $enabled) {
  142. global $DB, $CFG;
  143. if (!$enabled) {
  144. return $DB->get_records('enrol', array('courseid'=>$courseid), 'sortorder,id');
  145. }
  146. $result = $DB->get_records('enrol', array('courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id');
  147. $enabled = explode(',', $CFG->enrol_plugins_enabled);
  148. foreach ($result as $key=>$instance) {
  149. if (!in_array($instance->enrol, $enabled)) {
  150. unset($result[$key]);
  151. continue;
  152. }
  153. if (!file_exists("$CFG->dirroot/enrol/$instance->enrol/lib.php")) {
  154. // broken plugin
  155. unset($result[$key]);
  156. continue;
  157. }
  158. }
  159. return $result;
  160. }
  161. /**
  162. * Checks if a given plugin is in the list of enabled enrolment plugins.
  163. *
  164. * @param string $enrol Enrolment plugin name
  165. * @return boolean Whether the plugin is enabled
  166. */
  167. function enrol_is_enabled($enrol) {
  168. global $CFG;
  169. if (empty($CFG->enrol_plugins_enabled)) {
  170. return false;
  171. }
  172. return in_array($enrol, explode(',', $CFG->enrol_plugins_enabled));
  173. }
  174. /**
  175. * Check all the login enrolment information for the given user object
  176. * by querying the enrolment plugins
  177. *
  178. * This function may be very slow, use only once after log-in or login-as.
  179. *
  180. * @param stdClass $user
  181. * @return void
  182. */
  183. function enrol_check_plugins($user) {
  184. global $CFG;
  185. if (empty($user->id) or isguestuser($user)) {
  186. // shortcut - there is no enrolment work for guests and not-logged-in users
  187. return;
  188. }
  189. // originally there was a broken admin test, but accidentally it was non-functional in 2.2,
  190. // which proved it was actually not necessary.
  191. static $inprogress = array(); // To prevent this function being called more than once in an invocation
  192. if (!empty($inprogress[$user->id])) {
  193. return;
  194. }
  195. $inprogress[$user->id] = true; // Set the flag
  196. $enabled = enrol_get_plugins(true);
  197. foreach($enabled as $enrol) {
  198. $enrol->sync_user_enrolments($user);
  199. }
  200. unset($inprogress[$user->id]); // Unset the flag
  201. }
  202. /**
  203. * Do these two students share any course?
  204. *
  205. * The courses has to be visible and enrolments has to be active,
  206. * timestart and timeend restrictions are ignored.
  207. *
  208. * This function calls {@see enrol_get_shared_courses()} setting checkexistsonly
  209. * to true.
  210. *
  211. * @param stdClass|int $user1
  212. * @param stdClass|int $user2
  213. * @return bool
  214. */
  215. function enrol_sharing_course($user1, $user2) {
  216. return enrol_get_shared_courses($user1, $user2, false, true);
  217. }
  218. /**
  219. * Returns any courses shared by the two users
  220. *
  221. * The courses has to be visible and enrolments has to be active,
  222. * timestart and timeend restrictions are ignored.
  223. *
  224. * @global moodle_database $DB
  225. * @param stdClass|int $user1
  226. * @param stdClass|int $user2
  227. * @param bool $preloadcontexts If set to true contexts for the returned courses
  228. * will be preloaded.
  229. * @param bool $checkexistsonly If set to true then this function will return true
  230. * if the users share any courses and false if not.
  231. * @return array|bool An array of courses that both users are enrolled in OR if
  232. * $checkexistsonly set returns true if the users share any courses
  233. * and false if not.
  234. */
  235. function enrol_get_shared_courses($user1, $user2, $preloadcontexts = false, $checkexistsonly = false) {
  236. global $DB, $CFG;
  237. $user1 = isset($user1->id) ? $user1->id : $user1;
  238. $user2 = isset($user2->id) ? $user2->id : $user2;
  239. if (empty($user1) or empty($user2)) {
  240. return false;
  241. }
  242. if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
  243. return false;
  244. }
  245. list($plugins1, $params1) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee1');
  246. list($plugins2, $params2) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee2');
  247. $params = array_merge($params1, $params2);
  248. $params['enabled1'] = ENROL_INSTANCE_ENABLED;
  249. $params['enabled2'] = ENROL_INSTANCE_ENABLED;
  250. $params['active1'] = ENROL_USER_ACTIVE;
  251. $params['active2'] = ENROL_USER_ACTIVE;
  252. $params['user1'] = $user1;
  253. $params['user2'] = $user2;
  254. $ctxselect = '';
  255. $ctxjoin = '';
  256. if ($preloadcontexts) {
  257. $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  258. $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  259. $params['contextlevel'] = CONTEXT_COURSE;
  260. }
  261. $sql = "SELECT c.* $ctxselect
  262. FROM {course} c
  263. JOIN (
  264. SELECT DISTINCT c.id
  265. FROM {course} c
  266. JOIN {enrol} e1 ON (c.id = e1.courseid AND e1.status = :enabled1 AND e1.enrol $plugins1)
  267. JOIN {user_enrolments} ue1 ON (ue1.enrolid = e1.id AND ue1.status = :active1 AND ue1.userid = :user1)
  268. JOIN {enrol} e2 ON (c.id = e2.courseid AND e2.status = :enabled2 AND e2.enrol $plugins2)
  269. JOIN {user_enrolments} ue2 ON (ue2.enrolid = e2.id AND ue2.status = :active2 AND ue2.userid = :user2)
  270. WHERE c.visible = 1
  271. ) ec ON ec.id = c.id
  272. $ctxjoin";
  273. if ($checkexistsonly) {
  274. return $DB->record_exists_sql($sql, $params);
  275. } else {
  276. $courses = $DB->get_records_sql($sql, $params);
  277. if ($preloadcontexts) {
  278. array_map('context_helper::preload_from_record', $courses);
  279. }
  280. return $courses;
  281. }
  282. }
  283. /**
  284. * This function adds necessary enrol plugins UI into the course edit form.
  285. *
  286. * @param MoodleQuickForm $mform
  287. * @param object $data course edit form data
  288. * @param object $context context of existing course or parent category if course does not exist
  289. * @return void
  290. */
  291. function enrol_course_edit_form(MoodleQuickForm $mform, $data, $context) {
  292. $plugins = enrol_get_plugins(true);
  293. if (!empty($data->id)) {
  294. $instances = enrol_get_instances($data->id, false);
  295. foreach ($instances as $instance) {
  296. if (!isset($plugins[$instance->enrol])) {
  297. continue;
  298. }
  299. $plugin = $plugins[$instance->enrol];
  300. $plugin->course_edit_form($instance, $mform, $data, $context);
  301. }
  302. } else {
  303. foreach ($plugins as $plugin) {
  304. $plugin->course_edit_form(NULL, $mform, $data, $context);
  305. }
  306. }
  307. }
  308. /**
  309. * Validate course edit form data
  310. *
  311. * @param array $data raw form data
  312. * @param object $context context of existing course or parent category if course does not exist
  313. * @return array errors array
  314. */
  315. function enrol_course_edit_validation(array $data, $context) {
  316. $errors = array();
  317. $plugins = enrol_get_plugins(true);
  318. if (!empty($data['id'])) {
  319. $instances = enrol_get_instances($data['id'], false);
  320. foreach ($instances as $instance) {
  321. if (!isset($plugins[$instance->enrol])) {
  322. continue;
  323. }
  324. $plugin = $plugins[$instance->enrol];
  325. $errors = array_merge($errors, $plugin->course_edit_validation($instance, $data, $context));
  326. }
  327. } else {
  328. foreach ($plugins as $plugin) {
  329. $errors = array_merge($errors, $plugin->course_edit_validation(NULL, $data, $context));
  330. }
  331. }
  332. return $errors;
  333. }
  334. /**
  335. * Update enrol instances after course edit form submission
  336. * @param bool $inserted true means new course added, false course already existed
  337. * @param object $course
  338. * @param object $data form data
  339. * @return void
  340. */
  341. function enrol_course_updated($inserted, $course, $data) {
  342. global $DB, $CFG;
  343. $plugins = enrol_get_plugins(true);
  344. foreach ($plugins as $plugin) {
  345. $plugin->course_updated($inserted, $course, $data);
  346. }
  347. }
  348. /**
  349. * Add navigation nodes
  350. * @param navigation_node $coursenode
  351. * @param object $course
  352. * @return void
  353. */
  354. function enrol_add_course_navigation(navigation_node $coursenode, $course) {
  355. global $CFG;
  356. $coursecontext = context_course::instance($course->id);
  357. $instances = enrol_get_instances($course->id, true);
  358. $plugins = enrol_get_plugins(true);
  359. // we do not want to break all course pages if there is some borked enrol plugin, right?
  360. foreach ($instances as $k=>$instance) {
  361. if (!isset($plugins[$instance->enrol])) {
  362. unset($instances[$k]);
  363. }
  364. }
  365. $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users');
  366. if ($course->id != SITEID) {
  367. // list all participants - allows assigning roles, groups, etc.
  368. if (has_capability('moodle/course:enrolreview', $coursecontext)) {
  369. $url = new moodle_url('/user/index.php', array('id'=>$course->id));
  370. $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/enrolusers', ''));
  371. }
  372. // manage enrol plugin instances
  373. if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) {
  374. $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id));
  375. } else {
  376. $url = NULL;
  377. }
  378. $instancesnode = $usersnode->add(get_string('enrolmentinstances', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'manageinstances');
  379. // each instance decides how to configure itself or how many other nav items are exposed
  380. foreach ($instances as $instance) {
  381. if (!isset($plugins[$instance->enrol])) {
  382. continue;
  383. }
  384. $plugins[$instance->enrol]->add_course_navigation($instancesnode, $instance);
  385. }
  386. if (!$url) {
  387. $instancesnode->trim_if_empty();
  388. }
  389. }
  390. // Manage groups in this course or even frontpage
  391. if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
  392. $url = new moodle_url('/group/index.php', array('id'=>$course->id));
  393. $usersnode->add(get_string('groups'), $url, navigation_node::TYPE_SETTING, null, 'groups', new pix_icon('i/group', ''));
  394. }
  395. if (has_any_capability(array( 'moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:review'), $coursecontext)) {
  396. // Override roles
  397. if (has_capability('moodle/role:review', $coursecontext)) {
  398. $url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$coursecontext->id));
  399. } else {
  400. $url = NULL;
  401. }
  402. $permissionsnode = $usersnode->add(get_string('permissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'override');
  403. // Add assign or override roles if allowed
  404. if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
  405. if (has_capability('moodle/role:assign', $coursecontext)) {
  406. $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
  407. $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
  408. }
  409. }
  410. // Check role permissions
  411. if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override'), $coursecontext)) {
  412. $url = new moodle_url('/admin/roles/check.php', array('contextid'=>$coursecontext->id));
  413. $permissionsnode->add(get_string('checkpermissions', 'role'), $url, navigation_node::TYPE_SETTING, null, 'permissions', new pix_icon('i/checkpermissions', ''));
  414. }
  415. }
  416. // Deal somehow with users that are not enrolled but still got a role somehow
  417. if ($course->id != SITEID) {
  418. //TODO, create some new UI for role assignments at course level
  419. if (has_capability('moodle/course:reviewotherusers', $coursecontext)) {
  420. $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
  421. $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/assignroles', ''));
  422. }
  423. }
  424. // just in case nothing was actually added
  425. $usersnode->trim_if_empty();
  426. if ($course->id != SITEID) {
  427. if (isguestuser() or !isloggedin()) {
  428. // guest account can not be enrolled - no links for them
  429. } else if (is_enrolled($coursecontext)) {
  430. // unenrol link if possible
  431. foreach ($instances as $instance) {
  432. if (!isset($plugins[$instance->enrol])) {
  433. continue;
  434. }
  435. $plugin = $plugins[$instance->enrol];
  436. if ($unenrollink = $plugin->get_unenrolself_link($instance)) {
  437. $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
  438. $coursenode->add(get_string('unenrolme', 'core_enrol', $shortname), $unenrollink, navigation_node::TYPE_SETTING, null, 'unenrolself', new pix_icon('i/user', ''));
  439. break;
  440. //TODO. deal with multiple unenrol links - not likely case, but still...
  441. }
  442. }
  443. } else {
  444. // enrol link if possible
  445. if (is_viewing($coursecontext)) {
  446. // better not show any enrol link, this is intended for managers and inspectors
  447. } else {
  448. foreach ($instances as $instance) {
  449. if (!isset($plugins[$instance->enrol])) {
  450. continue;
  451. }
  452. $plugin = $plugins[$instance->enrol];
  453. if ($plugin->show_enrolme_link($instance)) {
  454. $url = new moodle_url('/enrol/index.php', array('id'=>$course->id));
  455. $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
  456. $coursenode->add(get_string('enrolme', 'core_enrol', $shortname), $url, navigation_node::TYPE_SETTING, null, 'enrolself', new pix_icon('i/user', ''));
  457. break;
  458. }
  459. }
  460. }
  461. }
  462. }
  463. }
  464. /**
  465. * Returns list of courses current $USER is enrolled in and can access
  466. *
  467. * The $fields param is a list of field names to ADD so name just the fields you really need,
  468. * which will be added and uniq'd.
  469. *
  470. * If $allaccessible is true, this will additionally return courses that the current user is not
  471. * enrolled in, but can access because they are open to the user for other reasons (course view
  472. * permission, currently viewing course as a guest, or course allows guest access without
  473. * password).
  474. *
  475. * @param string|array $fields Extra fields to be returned (array or comma-separated list).
  476. * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
  477. * Allowed prefixes for sort fields are: "ul" for the user_lastaccess table, "c" for the courses table,
  478. * "ue" for the user_enrolments table.
  479. * @param int $limit max number of courses
  480. * @param array $courseids the list of course ids to filter by
  481. * @param bool $allaccessible Include courses user is not enrolled in, but can access
  482. * @param int $offset Offset the result set by this number
  483. * @param array $excludecourses IDs of hidden courses to exclude from search
  484. * @return array
  485. */
  486. function enrol_get_my_courses($fields = null, $sort = null, $limit = 0, $courseids = [], $allaccessible = false,
  487. $offset = 0, $excludecourses = []) {
  488. global $DB, $USER, $CFG;
  489. // Allowed prefixes and field names.
  490. $allowedprefixesandfields = ['c' => array_keys($DB->get_columns('course')),
  491. 'ul' => array_keys($DB->get_columns('user_lastaccess')),
  492. 'ue' => array_keys($DB->get_columns('user_enrolments'))];
  493. // Re-Arrange the course sorting according to the admin settings.
  494. $sort = enrol_get_courses_sortingsql($sort);
  495. // Guest account does not have any enrolled courses.
  496. if (!$allaccessible && (isguestuser() or !isloggedin())) {
  497. return array();
  498. }
  499. $basefields = [
  500. 'id', 'category', 'sortorder',
  501. 'shortname', 'fullname', 'idnumber',
  502. 'startdate', 'visible',
  503. 'groupmode', 'groupmodeforce', 'cacherev',
  504. 'showactivitydates', 'showcompletionconditions',
  505. ];
  506. if (empty($fields)) {
  507. $fields = $basefields;
  508. } else if (is_string($fields)) {
  509. // turn the fields from a string to an array
  510. $fields = explode(',', $fields);
  511. $fields = array_map('trim', $fields);
  512. $fields = array_unique(array_merge($basefields, $fields));
  513. } else if (is_array($fields)) {
  514. $fields = array_unique(array_merge($basefields, $fields));
  515. } else {
  516. throw new coding_exception('Invalid $fields parameter in enrol_get_my_courses()');
  517. }
  518. if (in_array('*', $fields)) {
  519. $fields = array('*');
  520. }
  521. $orderby = "";
  522. $sort = trim($sort);
  523. $sorttimeaccess = false;
  524. if (!empty($sort)) {
  525. $rawsorts = explode(',', $sort);
  526. $sorts = array();
  527. foreach ($rawsorts as $rawsort) {
  528. $rawsort = trim($rawsort);
  529. // Make sure that there are no more white spaces in sortparams after explode.
  530. $sortparams = array_values(array_filter(explode(' ', $rawsort)));
  531. // If more than 2 values present then throw coding_exception.
  532. if (isset($sortparams[2])) {
  533. throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
  534. }
  535. // Check the sort ordering if present, at the beginning.
  536. if (isset($sortparams[1]) && (preg_match("/^(asc|desc)$/i", $sortparams[1]) === 0)) {
  537. throw new coding_exception('Invalid sort direction in $sort parameter in enrol_get_my_courses()');
  538. }
  539. $sortfield = $sortparams[0];
  540. $sortdirection = $sortparams[1] ?? 'asc';
  541. if (strpos($sortfield, '.') !== false) {
  542. $sortfieldparams = explode('.', $sortfield);
  543. // Check if more than one dots present in the prefix field.
  544. if (isset($sortfieldparams[2])) {
  545. throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
  546. }
  547. list($prefix, $fieldname) = [$sortfieldparams[0], $sortfieldparams[1]];
  548. // Check if the field name matches with the allowed prefix.
  549. if (array_key_exists($prefix, $allowedprefixesandfields) &&
  550. (in_array($fieldname, $allowedprefixesandfields[$prefix]))) {
  551. if ($prefix === 'ul') {
  552. $sorts[] = "COALESCE({$prefix}.{$fieldname}, 0) {$sortdirection}";
  553. $sorttimeaccess = true;
  554. } else {
  555. // Check if the field name that matches with the prefix and just append to sorts.
  556. $sorts[] = $rawsort;
  557. }
  558. } else {
  559. throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
  560. }
  561. } else {
  562. // Check if the field name matches with $allowedprefixesandfields.
  563. $found = false;
  564. foreach (array_keys($allowedprefixesandfields) as $prefix) {
  565. if (in_array($sortfield, $allowedprefixesandfields[$prefix])) {
  566. if ($prefix === 'ul') {
  567. $sorts[] = "COALESCE({$prefix}.{$sortfield}, 0) {$sortdirection}";
  568. $sorttimeaccess = true;
  569. } else {
  570. $sorts[] = "{$prefix}.{$sortfield} {$sortdirection}";
  571. }
  572. $found = true;
  573. break;
  574. }
  575. }
  576. if (!$found) {
  577. // The param is not found in $allowedprefixesandfields.
  578. throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
  579. }
  580. }
  581. }
  582. $sort = implode(',', $sorts);
  583. $orderby = "ORDER BY $sort";
  584. }
  585. $wheres = array("c.id <> :siteid");
  586. $params = array('siteid'=>SITEID);
  587. if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
  588. // list _only_ this course - anything else is asking for trouble...
  589. $wheres[] = "courseid = :loginas";
  590. $params['loginas'] = $USER->loginascontext->instanceid;
  591. }
  592. $coursefields = 'c.' .join(',c.', $fields);
  593. $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  594. $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  595. $params['contextlevel'] = CONTEXT_COURSE;
  596. $wheres = implode(" AND ", $wheres);
  597. $timeaccessselect = "";
  598. $timeaccessjoin = "";
  599. if (!empty($courseids)) {
  600. list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
  601. $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
  602. $params = array_merge($params, $courseidsparams);
  603. }
  604. if (!empty($excludecourses)) {
  605. list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($excludecourses, SQL_PARAMS_NAMED, 'param', false);
  606. $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
  607. $params = array_merge($params, $courseidsparams);
  608. }
  609. $courseidsql = "";
  610. // Logged-in, non-guest users get their enrolled courses.
  611. if (!isguestuser() && isloggedin()) {
  612. $courseidsql .= "
  613. SELECT DISTINCT e.courseid
  614. FROM {enrol} e
  615. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid1)
  616. WHERE ue.status = :active AND e.status = :enabled AND ue.timestart <= :now1
  617. AND (ue.timeend = 0 OR ue.timeend > :now2)";
  618. $params['userid1'] = $USER->id;
  619. $params['active'] = ENROL_USER_ACTIVE;
  620. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  621. $params['now1'] = $params['now2'] = time();
  622. if ($sorttimeaccess) {
  623. $params['userid2'] = $USER->id;
  624. $timeaccessselect = ', ul.timeaccess as lastaccessed';
  625. $timeaccessjoin = "LEFT JOIN {user_lastaccess} ul ON (ul.courseid = c.id AND ul.userid = :userid2)";
  626. }
  627. }
  628. // When including non-enrolled but accessible courses...
  629. if ($allaccessible) {
  630. if (is_siteadmin()) {
  631. // Site admins can access all courses.
  632. $courseidsql = "SELECT DISTINCT c2.id AS courseid FROM {course} c2";
  633. } else {
  634. // If we used the enrolment as well, then this will be UNIONed.
  635. if ($courseidsql) {
  636. $courseidsql .= " UNION ";
  637. }
  638. // Include courses with guest access and no password.
  639. $courseidsql .= "
  640. SELECT DISTINCT e.courseid
  641. FROM {enrol} e
  642. WHERE e.enrol = 'guest' AND e.password = :emptypass AND e.status = :enabled2";
  643. $params['emptypass'] = '';
  644. $params['enabled2'] = ENROL_INSTANCE_ENABLED;
  645. // Include courses where the current user is currently using guest access (may include
  646. // those which require a password).
  647. $courseids = [];
  648. $accessdata = get_user_accessdata($USER->id);
  649. foreach ($accessdata['ra'] as $contextpath => $roles) {
  650. if (array_key_exists($CFG->guestroleid, $roles)) {
  651. // Work out the course id from context path.
  652. $context = context::instance_by_id(preg_replace('~^.*/~', '', $contextpath));
  653. if ($context instanceof context_course) {
  654. $courseids[$context->instanceid] = true;
  655. }
  656. }
  657. }
  658. // Include courses where the current user has moodle/course:view capability.
  659. $courses = get_user_capability_course('moodle/course:view', null, false);
  660. if (!$courses) {
  661. $courses = [];
  662. }
  663. foreach ($courses as $course) {
  664. $courseids[$course->id] = true;
  665. }
  666. // If there are any in either category, list them individually.
  667. if ($courseids) {
  668. list ($allowedsql, $allowedparams) = $DB->get_in_or_equal(
  669. array_keys($courseids), SQL_PARAMS_NAMED);
  670. $courseidsql .= "
  671. UNION
  672. SELECT DISTINCT c3.id AS courseid
  673. FROM {course} c3
  674. WHERE c3.id $allowedsql";
  675. $params = array_merge($params, $allowedparams);
  676. }
  677. }
  678. }
  679. // Note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why
  680. // we have the subselect there.
  681. $sql = "SELECT $coursefields $ccselect $timeaccessselect
  682. FROM {course} c
  683. JOIN ($courseidsql) en ON (en.courseid = c.id)
  684. $timeaccessjoin
  685. $ccjoin
  686. WHERE $wheres
  687. $orderby";
  688. $courses = $DB->get_records_sql($sql, $params, $offset, $limit);
  689. // preload contexts and check visibility
  690. foreach ($courses as $id=>$course) {
  691. context_helper::preload_from_record($course);
  692. if (!$course->visible) {
  693. if (!$context = context_course::instance($id, IGNORE_MISSING)) {
  694. unset($courses[$id]);
  695. continue;
  696. }
  697. if (!has_capability('moodle/course:viewhiddencourses', $context)) {
  698. unset($courses[$id]);
  699. continue;
  700. }
  701. }
  702. $courses[$id] = $course;
  703. }
  704. //wow! Is that really all? :-D
  705. return $courses;
  706. }
  707. /**
  708. * Returns course enrolment information icons.
  709. *
  710. * @param object $course
  711. * @param array $instances enrol instances of this course, improves performance
  712. * @return array of pix_icon
  713. */
  714. function enrol_get_course_info_icons($course, array $instances = NULL) {
  715. $icons = array();
  716. if (is_null($instances)) {
  717. $instances = enrol_get_instances($course->id, true);
  718. }
  719. $plugins = enrol_get_plugins(true);
  720. foreach ($plugins as $name => $plugin) {
  721. $pis = array();
  722. foreach ($instances as $instance) {
  723. if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
  724. debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
  725. continue;
  726. }
  727. if ($instance->enrol == $name) {
  728. $pis[$instance->id] = $instance;
  729. }
  730. }
  731. if ($pis) {
  732. $icons = array_merge($icons, $plugin->get_info_icons($pis));
  733. }
  734. }
  735. return $icons;
  736. }
  737. /**
  738. * Returns SQL ORDER arguments which reflect the admin settings to sort my courses.
  739. *
  740. * @param string|null $sort SQL ORDER arguments which were originally requested (optionally).
  741. * @return string SQL ORDER arguments.
  742. */
  743. function enrol_get_courses_sortingsql($sort = null) {
  744. global $CFG;
  745. // Prepare the visible SQL fragment as empty.
  746. $visible = '';
  747. // Only create a visible SQL fragment if the caller didn't already pass a sort order which contains the visible field.
  748. if ($sort === null || strpos($sort, 'visible') === false) {
  749. // If the admin did not explicitly want to have shown and hidden courses sorted as one list, we will sort hidden
  750. // courses to the end of the course list.
  751. if (!isset($CFG->navsortmycourseshiddenlast) || $CFG->navsortmycourseshiddenlast == true) {
  752. $visible = 'visible DESC, ';
  753. }
  754. }
  755. // Only create a sortorder SQL fragment if the caller didn't already pass one.
  756. if ($sort === null) {
  757. // If the admin has configured a course sort order, we will use this.
  758. if (!empty($CFG->navsortmycoursessort)) {
  759. $sort = $CFG->navsortmycoursessort . ' ASC';
  760. // Otherwise we will fall back to the sortorder sorting.
  761. } else {
  762. $sort = 'sortorder ASC';
  763. }
  764. }
  765. return $visible . $sort;
  766. }
  767. /**
  768. * Returns course enrolment detailed information.
  769. *
  770. * @param object $course
  771. * @return array of html fragments - can be used to construct lists
  772. */
  773. function enrol_get_course_description_texts($course) {
  774. $lines = array();
  775. $instances = enrol_get_instances($course->id, true);
  776. $plugins = enrol_get_plugins(true);
  777. foreach ($instances as $instance) {
  778. if (!isset($plugins[$instance->enrol])) {
  779. //weird
  780. continue;
  781. }
  782. $plugin = $plugins[$instance->enrol];
  783. $text = $plugin->get_description_text($instance);
  784. if ($text !== NULL) {
  785. $lines[] = $text;
  786. }
  787. }
  788. return $lines;
  789. }
  790. /**
  791. * Returns list of courses user is enrolled into.
  792. *
  793. * Note: Use {@link enrol_get_all_users_courses()} if you need the list without any capability checks.
  794. *
  795. * The $fields param is a list of field names to ADD so name just the fields you really need,
  796. * which will be added and uniq'd.
  797. *
  798. * @param int $userid User whose courses are returned, defaults to the current user.
  799. * @param bool $onlyactive Return only active enrolments in courses user may see.
  800. * @param string|array $fields Extra fields to be returned (array or comma-separated list).
  801. * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
  802. * @return array
  803. */
  804. function enrol_get_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
  805. global $DB;
  806. $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort);
  807. // preload contexts and check visibility
  808. if ($onlyactive) {
  809. foreach ($courses as $id=>$course) {
  810. context_helper::preload_from_record($course);
  811. if (!$course->visible) {
  812. if (!$context = context_course::instance($id)) {
  813. unset($courses[$id]);
  814. continue;
  815. }
  816. if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
  817. unset($courses[$id]);
  818. continue;
  819. }
  820. }
  821. }
  822. }
  823. return $courses;
  824. }
  825. /**
  826. * Returns list of roles per users into course.
  827. *
  828. * @param int $courseid Course id.
  829. * @return array Array[$userid][$roleid] = role_assignment.
  830. */
  831. function enrol_get_course_users_roles(int $courseid) : array {
  832. global $DB;
  833. $context = context_course::instance($courseid);
  834. $roles = array();
  835. $records = $DB->get_recordset('role_assignments', array('contextid' => $context->id));
  836. foreach ($records as $record) {
  837. if (isset($roles[$record->userid]) === false) {
  838. $roles[$record->userid] = array();
  839. }
  840. $roles[$record->userid][$record->roleid] = $record;
  841. }
  842. $records->close();
  843. return $roles;
  844. }
  845. /**
  846. * Can user access at least one enrolled course?
  847. *
  848. * Cheat if necessary, but find out as fast as possible!
  849. *
  850. * @param int|stdClass $user null means use current user
  851. * @return bool
  852. */
  853. function enrol_user_sees_own_courses($user = null) {
  854. global $USER;
  855. if ($user === null) {
  856. $user = $USER;
  857. }
  858. $userid = is_object($user) ? $user->id : $user;
  859. // Guest account does not have any courses
  860. if (isguestuser($userid) or empty($userid)) {
  861. return false;
  862. }
  863. // Let's cheat here if this is the current user,
  864. // if user accessed any course recently, then most probably
  865. // we do not need to query the database at all.
  866. if ($USER->id == $userid) {
  867. if (!empty($USER->enrol['enrolled'])) {
  868. foreach ($USER->enrol['enrolled'] as $until) {
  869. if ($until > time()) {
  870. return true;
  871. }
  872. }
  873. }
  874. }
  875. // Now the slow way.
  876. $courses = enrol_get_all_users_courses($userid, true);
  877. foreach($courses as $course) {
  878. if ($course->visible) {
  879. return true;
  880. }
  881. context_helper::preload_from_record($course);
  882. $context = context_course::instance($course->id);
  883. if (has_capability('moodle/course:viewhiddencourses', $context, $user)) {
  884. return true;
  885. }
  886. }
  887. return false;
  888. }
  889. /**
  890. * Returns list of courses user is enrolled into without performing any capability checks.
  891. *
  892. * The $fields param is a list of field names to ADD so name just the fields you really need,
  893. * which will be added and uniq'd.
  894. *
  895. * @param int $userid User whose courses are returned, defaults to the current user.
  896. * @param bool $onlyactive Return only active enrolments in courses user may see.
  897. * @param string|array $fields Extra fields to be returned (array or comma-separated list).
  898. * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
  899. * @return array
  900. */
  901. function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
  902. global $DB;
  903. // Re-Arrange the course sorting according to the admin settings.
  904. $sort = enrol_get_courses_sortingsql($sort);
  905. // Guest account does not have any courses
  906. if (isguestuser($userid) or empty($userid)) {
  907. return(array());
  908. }
  909. $basefields = array('id', 'category', 'sortorder',
  910. 'shortname', 'fullname', 'idnumber',
  911. 'startdate', 'visible',
  912. 'defaultgroupingid',
  913. 'groupmode', 'groupmodeforce');
  914. if (empty($fields)) {
  915. $fields = $basefields;
  916. } else if (is_string($fields)) {
  917. // turn the fields from a string to an array
  918. $fields = explode(',', $fields);
  919. $fields = array_map('trim', $fields);
  920. $fields = array_unique(array_merge($basefields, $fields));
  921. } else if (is_array($fields)) {
  922. $fields = array_unique(array_merge($basefields, $fields));
  923. } else {
  924. throw new coding_exception('Invalid $fields parameter in enrol_get_all_users_courses()');
  925. }
  926. if (in_array('*', $fields)) {
  927. $fields = array('*');
  928. }
  929. $orderby = "";
  930. $sort = trim($sort);
  931. if (!empty($sort)) {
  932. $rawsorts = explode(',', $sort);
  933. $sorts = array();
  934. foreach ($rawsorts as $rawsort) {
  935. $rawsort = trim($rawsort);
  936. if (strpos($rawsort, 'c.') === 0) {
  937. $rawsort = substr($rawsort, 2);
  938. }
  939. $sorts[] = trim($rawsort);
  940. }
  941. $sort = 'c.'.implode(',c.', $sorts);
  942. $orderby = "ORDER BY $sort";
  943. }
  944. $params = array('siteid'=>SITEID);
  945. if ($onlyactive) {
  946. $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
  947. $params['now1'] = round(time(), -2); // improves db caching
  948. $params['now2'] = $params['now1'];
  949. $params['active'] = ENROL_USER_ACTIVE;
  950. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  951. } else {
  952. $subwhere = "";
  953. }
  954. $coursefields = 'c.' .join(',c.', $fields);
  955. $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  956. $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  957. $params['contextlevel'] = CONTEXT_COURSE;
  958. //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
  959. $sql = "SELECT $coursefields $ccselect
  960. FROM {course} c
  961. JOIN (SELECT DISTINCT e.courseid
  962. FROM {enrol} e
  963. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
  964. $subwhere
  965. ) en ON (en.courseid = c.id)
  966. $ccjoin
  967. WHERE c.id <> :siteid
  968. $orderby";
  969. $params['userid'] = $userid;
  970. $courses = $DB->get_records_sql($sql, $params);
  971. return $courses;
  972. }
  973. /**
  974. * Called when user is about to be deleted.
  975. * @param object $user
  976. * @return void
  977. */
  978. function enrol_user_delete($user) {
  979. global $DB;
  980. $plugins = enrol_get_plugins(true);
  981. foreach ($plugins as $plugin) {
  982. $plugin->user_delete($user);
  983. }
  984. // force cleanup of all broken enrolments
  985. $DB->delete_records('user_enrolments', array('userid'=>$user->id));
  986. }
  987. /**
  988. * Called when course is about to be deleted.
  989. * If a user id is passed, only enrolments that the user has permission to un-enrol will be removed,
  990. * otherwise all enrolments in the course will be removed.
  991. *
  992. * @param stdClass $course
  993. * @param int|null $userid
  994. * @return void
  995. */
  996. function enrol_course_delete($course, $userid = null) {
  997. global $DB;
  998. $context = context_course::instance($course->id);
  999. $instances = enrol_get_instances($course->id, false);
  1000. $plugins = enrol_get_plugins(true);
  1001. if ($userid) {
  1002. // If the user id is present, include only course enrolment instances which allow manual unenrolment and
  1003. // the given user have a capability to perform unenrolment.
  1004. $instances = array_filter($instances, function($instance) use ($userid, $plugins, $context) {
  1005. $unenrolcap = "enrol/{$instance->enrol}:unenrol";
  1006. return $plugins[$instance->enrol]->allow_unenrol($instance) &&
  1007. has_capability($unenrolcap, $context, $userid);
  1008. });
  1009. }
  1010. foreach ($instances as $instance) {
  1011. if (isset($plugins[$instance->enrol])) {
  1012. $plugins[$instance->enrol]->delete_instance($instance);
  1013. }
  1014. // low level delete in case plugin did not do it
  1015. $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$instance->enrol));
  1016. $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
  1017. $DB->delete_records('enrol', array('id'=>$instance->id));
  1018. }
  1019. }
  1020. /**
  1021. * Try to enrol user via default internal auth plugin.
  1022. *
  1023. * For now this is always using the manual enrol plugin...
  1024. *
  1025. * @param $courseid
  1026. * @param $userid
  1027. * @param $roleid
  1028. * @param $timestart
  1029. * @param $timeend
  1030. * @return bool success
  1031. */
  1032. function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
  1033. global $DB;
  1034. //note: this is hardcoded to manual plugin for now
  1035. if (!enrol_is_enabled('manual')) {
  1036. return false;
  1037. }
  1038. if (!$enrol = enrol_get_plugin('manual')) {
  1039. return false;
  1040. }
  1041. if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
  1042. return false;
  1043. }
  1044. $instance = reset($instances);
  1045. $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
  1046. return true;
  1047. }
  1048. /**
  1049. * Is there a chance users might self enrol
  1050. * @param int $courseid
  1051. * @return bool
  1052. */
  1053. function enrol_selfenrol_available($courseid) {
  1054. $result = false;
  1055. $plugins = enrol_get_plugins(true);
  1056. $enrolinstances = enrol_get_instances($courseid, true);
  1057. foreach($enrolinstances as $instance) {
  1058. if (!isset($plugins[$instance->enrol])) {
  1059. continue;
  1060. }
  1061. if ($instance->enrol === 'guest') {
  1062. continue;
  1063. }
  1064. if ($plugins[$instance->enrol]->show_enrolme_link($instance)) {
  1065. $result = true;
  1066. break;
  1067. }
  1068. }
  1069. return $result;
  1070. }
  1071. /**
  1072. * This function returns the end of current active user enrolment.
  1073. *
  1074. * It deals correctly with multiple overlapping user enrolments.
  1075. *
  1076. * @param int $courseid
  1077. * @param int $userid
  1078. * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
  1079. */
  1080. function enrol_get_enrolment_end($courseid, $userid) {
  1081. global $DB;
  1082. $sql = "SELECT ue.*
  1083. FROM {user_enrolments} ue
  1084. JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
  1085. JOIN {user} u ON u.id = ue.userid
  1086. WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
  1087. $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
  1088. if (!$enrolments = $DB->get_records_sql($sql, $params)) {
  1089. return false;
  1090. }
  1091. $changes = array();
  1092. foreach ($enrolments as $ue) {
  1093. $start = (int)$ue->timestart;
  1094. $end = (int)$ue->timeend;
  1095. if ($end != 0 and $end < $start) {
  1096. debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
  1097. continue;
  1098. }
  1099. if (isset($changes[$start])) {
  1100. $changes[$start] = $changes[$start] + 1;
  1101. } else {
  1102. $changes[$start] = 1;
  1103. }
  1104. if ($end === 0) {
  1105. // no end
  1106. } else if (isset($changes[$end])) {
  1107. $changes[$end] = $changes[$end] - 1;
  1108. } else {
  1109. $changes[$end] = -1;
  1110. }
  1111. }
  1112. // let's sort then enrolment starts&ends and go through them chronologically,
  1113. // looking for current status and the next future end of enrolment
  1114. ksort($changes);
  1115. $now = time();
  1116. $current = 0;
  1117. $present = null;
  1118. foreach ($changes as $time => $change) {
  1119. if ($time > $now) {
  1120. if ($present === null) {
  1121. // we have just went past current time
  1122. $present = $current;
  1123. if ($present < 1) {
  1124. // no enrolment active
  1125. return false;
  1126. }
  1127. }
  1128. if ($present !== null) {
  1129. // we are already in the future - look for possible end
  1130. if ($current + $change < 1) {
  1131. return $time;
  1132. }
  1133. }
  1134. }
  1135. $current += $change;
  1136. }
  1137. if ($current > 0) {
  1138. return 0;
  1139. } else {
  1140. return false;
  1141. }
  1142. }
  1143. /**
  1144. * Is current user accessing course via this enrolment method?
  1145. *
  1146. * This is intended for operations that are going to affect enrol instances.
  1147. *
  1148. * @param stdClass $instance enrol instance
  1149. * @return bool
  1150. */
  1151. function enrol_accessing_via_instance(stdClass $instance) {
  1152. global $DB, $USER;
  1153. if (empty($instance->id)) {
  1154. return false;
  1155. }
  1156. if (is_siteadmin()) {
  1157. // Admins may go anywhere.
  1158. return false;
  1159. }
  1160. return $DB->record_exists('user_enrolments', array('userid'=>$USER->id, 'enrolid'=>$instance->id));
  1161. }
  1162. /**
  1163. * Returns true if user is enrolled (is participating) in course
  1164. * this is intended for students and teachers.
  1165. *
  1166. * Since 2.2 the result for active enrolments and current user are cached.
  1167. *
  1168. * @param context $context
  1169. * @param int|stdClass $user if null $USER is used, otherwise user object or id expected
  1170. * @param string $withcapability extra capability name
  1171. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1172. * @return bool
  1173. */
  1174. function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) {
  1175. global $USER, $DB;
  1176. // First find the course context.
  1177. $coursecontext = $context->get_course_context();
  1178. // Make sure there is a real user specified.
  1179. if ($user === null) {
  1180. $userid = isset($USER->id) ? $USER->id : 0;
  1181. } else {
  1182. $userid = is_object($user) ? $user->id : $user;
  1183. }
  1184. if (empty($userid)) {
  1185. // Not-logged-in!
  1186. return false;
  1187. } else if (isguestuser($userid)) {
  1188. // Guest account can not be enrolled anywhere.
  1189. return false;
  1190. }
  1191. // Note everybody participates on frontpage, so for other contexts...
  1192. if ($coursecontext->instanceid != SITEID) {
  1193. // Try cached info first - the enrolled flag is set only when active enrolment present.
  1194. if ($USER->id == $userid) {
  1195. $coursecontext->reload_if_dirty();
  1196. if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) {
  1197. if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) {
  1198. if ($withcapability and !has_capability($withcapability, $context, $userid)) {
  1199. return false;
  1200. }
  1201. return true;
  1202. }
  1203. }
  1204. }
  1205. if ($onlyactive) {
  1206. // Look for active enrolments only.
  1207. $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid);
  1208. if ($until === false) {
  1209. return false;
  1210. }
  1211. if ($USER->id == $userid) {
  1212. if ($until == 0) {
  1213. $until = ENROL_MAX_TIMESTAMP;

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