PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/enrollib.php

http://github.com/moodle/moodle
PHP | 3244 lines | 1814 code | 415 blank | 1015 comment | 330 complexity | 9494a44d2a403c7e8a84f4dd3f0303f0 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  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. // Re-Arrange the course sorting according to the admin settings.
  490. $sort = enrol_get_courses_sortingsql($sort);
  491. // Guest account does not have any enrolled courses.
  492. if (!$allaccessible && (isguestuser() or !isloggedin())) {
  493. return array();
  494. }
  495. $basefields = array('id', 'category', 'sortorder',
  496. 'shortname', 'fullname', 'idnumber',
  497. 'startdate', 'visible',
  498. 'groupmode', 'groupmodeforce', 'cacherev');
  499. if (empty($fields)) {
  500. $fields = $basefields;
  501. } else if (is_string($fields)) {
  502. // turn the fields from a string to an array
  503. $fields = explode(',', $fields);
  504. $fields = array_map('trim', $fields);
  505. $fields = array_unique(array_merge($basefields, $fields));
  506. } else if (is_array($fields)) {
  507. $fields = array_unique(array_merge($basefields, $fields));
  508. } else {
  509. throw new coding_exception('Invalid $fields parameter in enrol_get_my_courses()');
  510. }
  511. if (in_array('*', $fields)) {
  512. $fields = array('*');
  513. }
  514. $orderby = "";
  515. $sort = trim($sort);
  516. $sorttimeaccess = false;
  517. $allowedsortprefixes = array('c', 'ul', 'ue');
  518. if (!empty($sort)) {
  519. $rawsorts = explode(',', $sort);
  520. $sorts = array();
  521. foreach ($rawsorts as $rawsort) {
  522. $rawsort = trim($rawsort);
  523. if (preg_match('/^ul\.(\S*)\s(asc|desc)/i', $rawsort, $matches)) {
  524. if (strcasecmp($matches[2], 'asc') == 0) {
  525. $sorts[] = 'COALESCE(ul.' . $matches[1] . ', 0) ASC';
  526. } else {
  527. $sorts[] = 'COALESCE(ul.' . $matches[1] . ', 0) DESC';
  528. }
  529. $sorttimeaccess = true;
  530. } else if (strpos($rawsort, '.') !== false) {
  531. $prefix = explode('.', $rawsort);
  532. if (in_array($prefix[0], $allowedsortprefixes)) {
  533. $sorts[] = trim($rawsort);
  534. } else {
  535. throw new coding_exception('Invalid $sort parameter in enrol_get_my_courses()');
  536. }
  537. } else {
  538. $sorts[] = 'c.'.trim($rawsort);
  539. }
  540. }
  541. $sort = implode(',', $sorts);
  542. $orderby = "ORDER BY $sort";
  543. }
  544. $wheres = array("c.id <> :siteid");
  545. $params = array('siteid'=>SITEID);
  546. if (isset($USER->loginascontext) and $USER->loginascontext->contextlevel == CONTEXT_COURSE) {
  547. // list _only_ this course - anything else is asking for trouble...
  548. $wheres[] = "courseid = :loginas";
  549. $params['loginas'] = $USER->loginascontext->instanceid;
  550. }
  551. $coursefields = 'c.' .join(',c.', $fields);
  552. $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  553. $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  554. $params['contextlevel'] = CONTEXT_COURSE;
  555. $wheres = implode(" AND ", $wheres);
  556. $timeaccessselect = "";
  557. $timeaccessjoin = "";
  558. if (!empty($courseids)) {
  559. list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
  560. $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
  561. $params = array_merge($params, $courseidsparams);
  562. }
  563. if (!empty($excludecourses)) {
  564. list($courseidssql, $courseidsparams) = $DB->get_in_or_equal($excludecourses, SQL_PARAMS_NAMED, 'param', false);
  565. $wheres = sprintf("%s AND c.id %s", $wheres, $courseidssql);
  566. $params = array_merge($params, $courseidsparams);
  567. }
  568. $courseidsql = "";
  569. // Logged-in, non-guest users get their enrolled courses.
  570. if (!isguestuser() && isloggedin()) {
  571. $courseidsql .= "
  572. SELECT DISTINCT e.courseid
  573. FROM {enrol} e
  574. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid1)
  575. WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1
  576. AND (ue.timeend = 0 OR ue.timeend > :now2)";
  577. $params['userid1'] = $USER->id;
  578. $params['active'] = ENROL_USER_ACTIVE;
  579. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  580. $params['now1'] = round(time(), -2); // Improves db caching.
  581. $params['now2'] = $params['now1'];
  582. if ($sorttimeaccess) {
  583. $params['userid2'] = $USER->id;
  584. $timeaccessselect = ', ul.timeaccess as lastaccessed';
  585. $timeaccessjoin = "LEFT JOIN {user_lastaccess} ul ON (ul.courseid = c.id AND ul.userid = :userid2)";
  586. }
  587. }
  588. // When including non-enrolled but accessible courses...
  589. if ($allaccessible) {
  590. if (is_siteadmin()) {
  591. // Site admins can access all courses.
  592. $courseidsql = "SELECT DISTINCT c2.id AS courseid FROM {course} c2";
  593. } else {
  594. // If we used the enrolment as well, then this will be UNIONed.
  595. if ($courseidsql) {
  596. $courseidsql .= " UNION ";
  597. }
  598. // Include courses with guest access and no password.
  599. $courseidsql .= "
  600. SELECT DISTINCT e.courseid
  601. FROM {enrol} e
  602. WHERE e.enrol = 'guest' AND e.password = :emptypass AND e.status = :enabled2";
  603. $params['emptypass'] = '';
  604. $params['enabled2'] = ENROL_INSTANCE_ENABLED;
  605. // Include courses where the current user is currently using guest access (may include
  606. // those which require a password).
  607. $courseids = [];
  608. $accessdata = get_user_accessdata($USER->id);
  609. foreach ($accessdata['ra'] as $contextpath => $roles) {
  610. if (array_key_exists($CFG->guestroleid, $roles)) {
  611. // Work out the course id from context path.
  612. $context = context::instance_by_id(preg_replace('~^.*/~', '', $contextpath));
  613. if ($context instanceof context_course) {
  614. $courseids[$context->instanceid] = true;
  615. }
  616. }
  617. }
  618. // Include courses where the current user has moodle/course:view capability.
  619. $courses = get_user_capability_course('moodle/course:view', null, false);
  620. if (!$courses) {
  621. $courses = [];
  622. }
  623. foreach ($courses as $course) {
  624. $courseids[$course->id] = true;
  625. }
  626. // If there are any in either category, list them individually.
  627. if ($courseids) {
  628. list ($allowedsql, $allowedparams) = $DB->get_in_or_equal(
  629. array_keys($courseids), SQL_PARAMS_NAMED);
  630. $courseidsql .= "
  631. UNION
  632. SELECT DISTINCT c3.id AS courseid
  633. FROM {course} c3
  634. WHERE c3.id $allowedsql";
  635. $params = array_merge($params, $allowedparams);
  636. }
  637. }
  638. }
  639. // Note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why
  640. // we have the subselect there.
  641. $sql = "SELECT $coursefields $ccselect $timeaccessselect
  642. FROM {course} c
  643. JOIN ($courseidsql) en ON (en.courseid = c.id)
  644. $timeaccessjoin
  645. $ccjoin
  646. WHERE $wheres
  647. $orderby";
  648. $courses = $DB->get_records_sql($sql, $params, $offset, $limit);
  649. // preload contexts and check visibility
  650. foreach ($courses as $id=>$course) {
  651. context_helper::preload_from_record($course);
  652. if (!$course->visible) {
  653. if (!$context = context_course::instance($id, IGNORE_MISSING)) {
  654. unset($courses[$id]);
  655. continue;
  656. }
  657. if (!has_capability('moodle/course:viewhiddencourses', $context)) {
  658. unset($courses[$id]);
  659. continue;
  660. }
  661. }
  662. $courses[$id] = $course;
  663. }
  664. //wow! Is that really all? :-D
  665. return $courses;
  666. }
  667. /**
  668. * Returns course enrolment information icons.
  669. *
  670. * @param object $course
  671. * @param array $instances enrol instances of this course, improves performance
  672. * @return array of pix_icon
  673. */
  674. function enrol_get_course_info_icons($course, array $instances = NULL) {
  675. $icons = array();
  676. if (is_null($instances)) {
  677. $instances = enrol_get_instances($course->id, true);
  678. }
  679. $plugins = enrol_get_plugins(true);
  680. foreach ($plugins as $name => $plugin) {
  681. $pis = array();
  682. foreach ($instances as $instance) {
  683. if ($instance->status != ENROL_INSTANCE_ENABLED or $instance->courseid != $course->id) {
  684. debugging('Invalid instances parameter submitted in enrol_get_info_icons()');
  685. continue;
  686. }
  687. if ($instance->enrol == $name) {
  688. $pis[$instance->id] = $instance;
  689. }
  690. }
  691. if ($pis) {
  692. $icons = array_merge($icons, $plugin->get_info_icons($pis));
  693. }
  694. }
  695. return $icons;
  696. }
  697. /**
  698. * Returns SQL ORDER arguments which reflect the admin settings to sort my courses.
  699. *
  700. * @param string|null $sort SQL ORDER arguments which were originally requested (optionally).
  701. * @return string SQL ORDER arguments.
  702. */
  703. function enrol_get_courses_sortingsql($sort = null) {
  704. global $CFG;
  705. // Prepare the visible SQL fragment as empty.
  706. $visible = '';
  707. // Only create a visible SQL fragment if the caller didn't already pass a sort order which contains the visible field.
  708. if ($sort === null || strpos($sort, 'visible') === false) {
  709. // If the admin did not explicitly want to have shown and hidden courses sorted as one list, we will sort hidden
  710. // courses to the end of the course list.
  711. if (!isset($CFG->navsortmycourseshiddenlast) || $CFG->navsortmycourseshiddenlast == true) {
  712. $visible = 'visible DESC, ';
  713. }
  714. }
  715. // Only create a sortorder SQL fragment if the caller didn't already pass one.
  716. if ($sort === null) {
  717. // If the admin has configured a course sort order, we will use this.
  718. if (!empty($CFG->navsortmycoursessort)) {
  719. $sort = $CFG->navsortmycoursessort . ' ASC';
  720. // Otherwise we will fall back to the sortorder sorting.
  721. } else {
  722. $sort = 'sortorder ASC';
  723. }
  724. }
  725. return $visible . $sort;
  726. }
  727. /**
  728. * Returns course enrolment detailed information.
  729. *
  730. * @param object $course
  731. * @return array of html fragments - can be used to construct lists
  732. */
  733. function enrol_get_course_description_texts($course) {
  734. $lines = array();
  735. $instances = enrol_get_instances($course->id, true);
  736. $plugins = enrol_get_plugins(true);
  737. foreach ($instances as $instance) {
  738. if (!isset($plugins[$instance->enrol])) {
  739. //weird
  740. continue;
  741. }
  742. $plugin = $plugins[$instance->enrol];
  743. $text = $plugin->get_description_text($instance);
  744. if ($text !== NULL) {
  745. $lines[] = $text;
  746. }
  747. }
  748. return $lines;
  749. }
  750. /**
  751. * Returns list of courses user is enrolled into.
  752. *
  753. * Note: Use {@link enrol_get_all_users_courses()} if you need the list without any capability checks.
  754. *
  755. * The $fields param is a list of field names to ADD so name just the fields you really need,
  756. * which will be added and uniq'd.
  757. *
  758. * @param int $userid User whose courses are returned, defaults to the current user.
  759. * @param bool $onlyactive Return only active enrolments in courses user may see.
  760. * @param string|array $fields Extra fields to be returned (array or comma-separated list).
  761. * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
  762. * @return array
  763. */
  764. function enrol_get_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
  765. global $DB;
  766. $courses = enrol_get_all_users_courses($userid, $onlyactive, $fields, $sort);
  767. // preload contexts and check visibility
  768. if ($onlyactive) {
  769. foreach ($courses as $id=>$course) {
  770. context_helper::preload_from_record($course);
  771. if (!$course->visible) {
  772. if (!$context = context_course::instance($id)) {
  773. unset($courses[$id]);
  774. continue;
  775. }
  776. if (!has_capability('moodle/course:viewhiddencourses', $context, $userid)) {
  777. unset($courses[$id]);
  778. continue;
  779. }
  780. }
  781. }
  782. }
  783. return $courses;
  784. }
  785. /**
  786. * Returns list of roles per users into course.
  787. *
  788. * @param int $courseid Course id.
  789. * @return array Array[$userid][$roleid] = role_assignment.
  790. */
  791. function enrol_get_course_users_roles(int $courseid) : array {
  792. global $DB;
  793. $context = context_course::instance($courseid);
  794. $roles = array();
  795. $records = $DB->get_recordset('role_assignments', array('contextid' => $context->id));
  796. foreach ($records as $record) {
  797. if (isset($roles[$record->userid]) === false) {
  798. $roles[$record->userid] = array();
  799. }
  800. $roles[$record->userid][$record->roleid] = $record;
  801. }
  802. $records->close();
  803. return $roles;
  804. }
  805. /**
  806. * Can user access at least one enrolled course?
  807. *
  808. * Cheat if necessary, but find out as fast as possible!
  809. *
  810. * @param int|stdClass $user null means use current user
  811. * @return bool
  812. */
  813. function enrol_user_sees_own_courses($user = null) {
  814. global $USER;
  815. if ($user === null) {
  816. $user = $USER;
  817. }
  818. $userid = is_object($user) ? $user->id : $user;
  819. // Guest account does not have any courses
  820. if (isguestuser($userid) or empty($userid)) {
  821. return false;
  822. }
  823. // Let's cheat here if this is the current user,
  824. // if user accessed any course recently, then most probably
  825. // we do not need to query the database at all.
  826. if ($USER->id == $userid) {
  827. if (!empty($USER->enrol['enrolled'])) {
  828. foreach ($USER->enrol['enrolled'] as $until) {
  829. if ($until > time()) {
  830. return true;
  831. }
  832. }
  833. }
  834. }
  835. // Now the slow way.
  836. $courses = enrol_get_all_users_courses($userid, true);
  837. foreach($courses as $course) {
  838. if ($course->visible) {
  839. return true;
  840. }
  841. context_helper::preload_from_record($course);
  842. $context = context_course::instance($course->id);
  843. if (has_capability('moodle/course:viewhiddencourses', $context, $user)) {
  844. return true;
  845. }
  846. }
  847. return false;
  848. }
  849. /**
  850. * Returns list of courses user is enrolled into without performing any capability checks.
  851. *
  852. * The $fields param is a list of field names to ADD so name just the fields you really need,
  853. * which will be added and uniq'd.
  854. *
  855. * @param int $userid User whose courses are returned, defaults to the current user.
  856. * @param bool $onlyactive Return only active enrolments in courses user may see.
  857. * @param string|array $fields Extra fields to be returned (array or comma-separated list).
  858. * @param string|null $sort Comma separated list of fields to sort by, defaults to respecting navsortmycoursessort.
  859. * @return array
  860. */
  861. function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = null, $sort = null) {
  862. global $DB;
  863. // Re-Arrange the course sorting according to the admin settings.
  864. $sort = enrol_get_courses_sortingsql($sort);
  865. // Guest account does not have any courses
  866. if (isguestuser($userid) or empty($userid)) {
  867. return(array());
  868. }
  869. $basefields = array('id', 'category', 'sortorder',
  870. 'shortname', 'fullname', 'idnumber',
  871. 'startdate', 'visible',
  872. 'defaultgroupingid',
  873. 'groupmode', 'groupmodeforce');
  874. if (empty($fields)) {
  875. $fields = $basefields;
  876. } else if (is_string($fields)) {
  877. // turn the fields from a string to an array
  878. $fields = explode(',', $fields);
  879. $fields = array_map('trim', $fields);
  880. $fields = array_unique(array_merge($basefields, $fields));
  881. } else if (is_array($fields)) {
  882. $fields = array_unique(array_merge($basefields, $fields));
  883. } else {
  884. throw new coding_exception('Invalid $fields parameter in enrol_get_all_users_courses()');
  885. }
  886. if (in_array('*', $fields)) {
  887. $fields = array('*');
  888. }
  889. $orderby = "";
  890. $sort = trim($sort);
  891. if (!empty($sort)) {
  892. $rawsorts = explode(',', $sort);
  893. $sorts = array();
  894. foreach ($rawsorts as $rawsort) {
  895. $rawsort = trim($rawsort);
  896. if (strpos($rawsort, 'c.') === 0) {
  897. $rawsort = substr($rawsort, 2);
  898. }
  899. $sorts[] = trim($rawsort);
  900. }
  901. $sort = 'c.'.implode(',c.', $sorts);
  902. $orderby = "ORDER BY $sort";
  903. }
  904. $params = array('siteid'=>SITEID);
  905. if ($onlyactive) {
  906. $subwhere = "WHERE ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
  907. $params['now1'] = round(time(), -2); // improves db caching
  908. $params['now2'] = $params['now1'];
  909. $params['active'] = ENROL_USER_ACTIVE;
  910. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  911. } else {
  912. $subwhere = "";
  913. }
  914. $coursefields = 'c.' .join(',c.', $fields);
  915. $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
  916. $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
  917. $params['contextlevel'] = CONTEXT_COURSE;
  918. //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there
  919. $sql = "SELECT $coursefields $ccselect
  920. FROM {course} c
  921. JOIN (SELECT DISTINCT e.courseid
  922. FROM {enrol} e
  923. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
  924. $subwhere
  925. ) en ON (en.courseid = c.id)
  926. $ccjoin
  927. WHERE c.id <> :siteid
  928. $orderby";
  929. $params['userid'] = $userid;
  930. $courses = $DB->get_records_sql($sql, $params);
  931. return $courses;
  932. }
  933. /**
  934. * Called when user is about to be deleted.
  935. * @param object $user
  936. * @return void
  937. */
  938. function enrol_user_delete($user) {
  939. global $DB;
  940. $plugins = enrol_get_plugins(true);
  941. foreach ($plugins as $plugin) {
  942. $plugin->user_delete($user);
  943. }
  944. // force cleanup of all broken enrolments
  945. $DB->delete_records('user_enrolments', array('userid'=>$user->id));
  946. }
  947. /**
  948. * Called when course is about to be deleted.
  949. * @param stdClass $course
  950. * @return void
  951. */
  952. function enrol_course_delete($course) {
  953. global $DB;
  954. $instances = enrol_get_instances($course->id, false);
  955. $plugins = enrol_get_plugins(true);
  956. foreach ($instances as $instance) {
  957. if (isset($plugins[$instance->enrol])) {
  958. $plugins[$instance->enrol]->delete_instance($instance);
  959. }
  960. // low level delete in case plugin did not do it
  961. $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
  962. $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$instance->enrol));
  963. $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
  964. $DB->delete_records('enrol', array('id'=>$instance->id));
  965. }
  966. }
  967. /**
  968. * Try to enrol user via default internal auth plugin.
  969. *
  970. * For now this is always using the manual enrol plugin...
  971. *
  972. * @param $courseid
  973. * @param $userid
  974. * @param $roleid
  975. * @param $timestart
  976. * @param $timeend
  977. * @return bool success
  978. */
  979. function enrol_try_internal_enrol($courseid, $userid, $roleid = null, $timestart = 0, $timeend = 0) {
  980. global $DB;
  981. //note: this is hardcoded to manual plugin for now
  982. if (!enrol_is_enabled('manual')) {
  983. return false;
  984. }
  985. if (!$enrol = enrol_get_plugin('manual')) {
  986. return false;
  987. }
  988. if (!$instances = $DB->get_records('enrol', array('enrol'=>'manual', 'courseid'=>$courseid, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder,id ASC')) {
  989. return false;
  990. }
  991. $instance = reset($instances);
  992. $enrol->enrol_user($instance, $userid, $roleid, $timestart, $timeend);
  993. return true;
  994. }
  995. /**
  996. * Is there a chance users might self enrol
  997. * @param int $courseid
  998. * @return bool
  999. */
  1000. function enrol_selfenrol_available($courseid) {
  1001. $result = false;
  1002. $plugins = enrol_get_plugins(true);
  1003. $enrolinstances = enrol_get_instances($courseid, true);
  1004. foreach($enrolinstances as $instance) {
  1005. if (!isset($plugins[$instance->enrol])) {
  1006. continue;
  1007. }
  1008. if ($instance->enrol === 'guest') {
  1009. // blacklist known temporary guest plugins
  1010. continue;
  1011. }
  1012. if ($plugins[$instance->enrol]->show_enrolme_link($instance)) {
  1013. $result = true;
  1014. break;
  1015. }
  1016. }
  1017. return $result;
  1018. }
  1019. /**
  1020. * This function returns the end of current active user enrolment.
  1021. *
  1022. * It deals correctly with multiple overlapping user enrolments.
  1023. *
  1024. * @param int $courseid
  1025. * @param int $userid
  1026. * @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
  1027. */
  1028. function enrol_get_enrolment_end($courseid, $userid) {
  1029. global $DB;
  1030. $sql = "SELECT ue.*
  1031. FROM {user_enrolments} ue
  1032. JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
  1033. JOIN {user} u ON u.id = ue.userid
  1034. WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
  1035. $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
  1036. if (!$enrolments = $DB->get_records_sql($sql, $params)) {
  1037. return false;
  1038. }
  1039. $changes = array();
  1040. foreach ($enrolments as $ue) {
  1041. $start = (int)$ue->timestart;
  1042. $end = (int)$ue->timeend;
  1043. if ($end != 0 and $end < $start) {
  1044. debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
  1045. continue;
  1046. }
  1047. if (isset($changes[$start])) {
  1048. $changes[$start] = $changes[$start] + 1;
  1049. } else {
  1050. $changes[$start] = 1;
  1051. }
  1052. if ($end === 0) {
  1053. // no end
  1054. } else if (isset($changes[$end])) {
  1055. $changes[$end] = $changes[$end] - 1;
  1056. } else {
  1057. $changes[$end] = -1;
  1058. }
  1059. }
  1060. // let's sort then enrolment starts&ends and go through them chronologically,
  1061. // looking for current status and the next future end of enrolment
  1062. ksort($changes);
  1063. $now = time();
  1064. $current = 0;
  1065. $present = null;
  1066. foreach ($changes as $time => $change) {
  1067. if ($time > $now) {
  1068. if ($present === null) {
  1069. // we have just went past current time
  1070. $present = $current;
  1071. if ($present < 1) {
  1072. // no enrolment active
  1073. return false;
  1074. }
  1075. }
  1076. if ($present !== null) {
  1077. // we are already in the future - look for possible end
  1078. if ($current + $change < 1) {
  1079. return $time;
  1080. }
  1081. }
  1082. }
  1083. $current += $change;
  1084. }
  1085. if ($current > 0) {
  1086. return 0;
  1087. } else {
  1088. return false;
  1089. }
  1090. }
  1091. /**
  1092. * Is current user accessing course via this enrolment method?
  1093. *
  1094. * This is intended for operations that are going to affect enrol instances.
  1095. *
  1096. * @param stdClass $instance enrol instance
  1097. * @return bool
  1098. */
  1099. function enrol_accessing_via_instance(stdClass $instance) {
  1100. global $DB, $USER;
  1101. if (empty($instance->id)) {
  1102. return false;
  1103. }
  1104. if (is_siteadmin()) {
  1105. // Admins may go anywhere.
  1106. return false;
  1107. }
  1108. return $DB->record_exists('user_enrolments', array('userid'=>$USER->id, 'enrolid'=>$instance->id));
  1109. }
  1110. /**
  1111. * Returns true if user is enrolled (is participating) in course
  1112. * this is intended for students and teachers.
  1113. *
  1114. * Since 2.2 the result for active enrolments and current user are cached.
  1115. *
  1116. * @param context $context
  1117. * @param int|stdClass $user if null $USER is used, otherwise user object or id expected
  1118. * @param string $withcapability extra capability name
  1119. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1120. * @return bool
  1121. */
  1122. function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) {
  1123. global $USER, $DB;
  1124. // First find the course context.
  1125. $coursecontext = $context->get_course_context();
  1126. // Make sure there is a real user specified.
  1127. if ($user === null) {
  1128. $userid = isset($USER->id) ? $USER->id : 0;
  1129. } else {
  1130. $userid = is_object($user) ? $user->id : $user;
  1131. }
  1132. if (empty($userid)) {
  1133. // Not-logged-in!
  1134. return false;
  1135. } else if (isguestuser($userid)) {
  1136. // Guest account can not be enrolled anywhere.
  1137. return false;
  1138. }
  1139. // Note everybody participates on frontpage, so for other contexts...
  1140. if ($coursecontext->instanceid != SITEID) {
  1141. // Try cached info first - the enrolled flag is set only when active enrolment present.
  1142. if ($USER->id == $userid) {
  1143. $coursecontext->reload_if_dirty();
  1144. if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) {
  1145. if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) {
  1146. if ($withcapability and !has_capability($withcapability, $context, $userid)) {
  1147. return false;
  1148. }
  1149. return true;
  1150. }
  1151. }
  1152. }
  1153. if ($onlyactive) {
  1154. // Look for active enrolments only.
  1155. $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid);
  1156. if ($until === false) {
  1157. return false;
  1158. }
  1159. if ($USER->id == $userid) {
  1160. if ($until == 0) {
  1161. $until = ENROL_MAX_TIMESTAMP;
  1162. }
  1163. $USER->enrol['enrolled'][$coursecontext->instanceid] = $until;
  1164. if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) {
  1165. unset($USER->enrol['tempguest'][$coursecontext->instanceid]);
  1166. remove_temp_course_roles($coursecontext);
  1167. }
  1168. }
  1169. } else {
  1170. // Any enrolment is good for us here, even outdated, disabled or inactive.
  1171. $sql = "SELECT 'x'
  1172. FROM {user_enrolments} ue
  1173. JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
  1174. JOIN {user} u ON u.id = ue.userid
  1175. WHERE ue.userid = :userid AND u.deleted = 0";
  1176. $params = array('userid' => $userid, 'courseid' => $coursecontext->instanceid);
  1177. if (!$DB->record_exists_sql($sql, $params)) {
  1178. return false;
  1179. }
  1180. }
  1181. }
  1182. if ($withcapability and !has_capability($withcapability, $context, $userid)) {
  1183. return false;
  1184. }
  1185. return true;
  1186. }
  1187. /**
  1188. * Returns an array of joins, wheres and params that will limit the group of
  1189. * users to only those enrolled and with given capability (if specified).
  1190. *
  1191. * Note this join will return duplicate rows for users who have been enrolled
  1192. * several times (e.g. as manual enrolment, and as self enrolment). You may
  1193. * need to use a SELECT DISTINCT in your query (see get_enrolled_sql for example).
  1194. *
  1195. * @param context $context
  1196. * @param string $prefix optional, a prefix to the user id column
  1197. * @param string|array $capability optional, may include a capability name, or array of names.
  1198. * If an array is provided then this is the equivalent of a logical 'OR',
  1199. * i.e. the user needs to have one of these capabilities.
  1200. * @param int $group optional, 0 indicates no current group and USERSWITHOUTGROUP users without any group; otherwise the group id
  1201. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1202. * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
  1203. * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
  1204. * @return \core\dml\sql_join Contains joins, wheres, params
  1205. */
  1206. function get_enrolled_with_capabilities_join(context $context, $prefix = '', $capability = '', $group = 0,
  1207. $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
  1208. $uid = $prefix . 'u.id';
  1209. $joins = array();
  1210. $wheres = array();
  1211. $enrolledjoin = get_enrolled_join($context, $uid, $onlyactive, $onlysuspended, $enrolid);
  1212. $joins[] = $enrolledjoin->joins;
  1213. $wheres[] = $enrolledjoin->wheres;
  1214. $params = $enrolledjoin->params;
  1215. if (!empty($capability)) {
  1216. $capjoin = get_with_capability_join($context, $capability, $uid);
  1217. $joins[] = $capjoin->joins;
  1218. $wheres[] = $capjoin->wheres;
  1219. $params = array_merge($params, $capjoin->params);
  1220. }
  1221. if ($group) {
  1222. $groupjoin = groups_get_members_join($group, $uid, $context);
  1223. $joins[] = $groupjoin->joins;
  1224. $params = array_merge($params, $groupjoin->params);
  1225. if (!empty($groupjoin->wheres)) {
  1226. $wheres[] = $groupjoin->wheres;
  1227. }
  1228. }
  1229. $joins = implode("\n", $joins);
  1230. $wheres[] = "{$prefix}u.deleted = 0";
  1231. $wheres = implode(" AND ", $wheres);
  1232. return new \core\dml\sql_join($joins, $wheres, $params);
  1233. }
  1234. /**
  1235. * Returns array with sql code and parameters returning all ids
  1236. * of users enrolled into course.
  1237. *
  1238. * This function is using 'eu[0-9]+_' prefix for table names and parameters.
  1239. *
  1240. * @param context $context
  1241. * @param string $withcapability
  1242. * @param int $groupid 0 means ignore groups, USERSWITHOUTGROUP without any group and any other value limits the result by group id
  1243. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1244. * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
  1245. * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
  1246. * @return array list($sql, $params)
  1247. */
  1248. function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false,
  1249. $enrolid = 0) {
  1250. // Use unique prefix just in case somebody makes some SQL magic with the result.
  1251. static $i = 0;
  1252. $i++;
  1253. $prefix = 'eu' . $i . '_';
  1254. $capjoin = get_enrolled_with_capabilities_join(
  1255. $context, $prefix, $withcapability, $groupid, $onlyactive, $onlysuspended, $enrolid);
  1256. $sql = "SELECT DISTINCT {$prefix}u.id
  1257. FROM {user} {$prefix}u
  1258. $capjoin->joins
  1259. WHERE $capjoin->wheres";
  1260. return array($sql, $capjoin->params);
  1261. }
  1262. /**
  1263. * Returns array with sql joins and parameters returning all ids
  1264. * of users enrolled into course.
  1265. *
  1266. * This function is using 'ej[0-9]+_' prefix for table names and parameters.
  1267. *
  1268. * @throws coding_exception
  1269. *
  1270. * @param context $context
  1271. * @param string $useridcolumn User id column used the calling query, e.g. u.id
  1272. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1273. * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
  1274. * @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
  1275. * @return \core\dml\sql_join Contains joins, wheres, params
  1276. */
  1277. function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
  1278. // Use unique prefix just in case somebody makes some SQL magic with the result.
  1279. static $i = 0;
  1280. $i++;
  1281. $prefix = 'ej' . $i . '_';
  1282. // First find the course context.
  1283. $coursecontext = $context->get_course_context();
  1284. $isfrontpage = ($coursecontext->instanceid == SITEID);
  1285. if ($onlyactive && $onlysuspended) {
  1286. throw new coding_exception("Both onlyactive and onlysuspended are set, this is probably not what you want!");
  1287. }
  1288. if ($isfrontpage && $onlysuspended) {
  1289. throw new coding_exception("onlysuspended is not supported on frontpage; please add your own early-exit!");
  1290. }
  1291. $joins = array();
  1292. $wheres = array();
  1293. $params = array();
  1294. $wheres[] = "1 = 1"; // Prevent broken where clauses later on.
  1295. // Note all users are "enrolled" on the frontpage, but for others...
  1296. if (!$isfrontpage) {
  1297. $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
  1298. $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
  1299. $enrolconditions = array(
  1300. "{$prefix}e.id = {$prefix}ue.enrolid",
  1301. "{$prefix}e.courseid = :{$prefix}courseid",
  1302. );
  1303. if ($enrolid) {
  1304. $enrolconditions[] = "{$prefix}e.id = :{$prefix}enrolid";
  1305. $params[$prefix . 'enrolid'] = $enrolid;
  1306. }
  1307. $enrolconditionssql = implode(" AND ", $enrolconditions);
  1308. $ejoin = "JOIN {enrol} {$prefix}e ON ($enrolconditionssql)";
  1309. $params[$prefix.'courseid'] = $coursecontext->instanceid;
  1310. if (!$onlysuspended) {
  1311. $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = $useridcolumn";
  1312. $joins[] = $ejoin;
  1313. if ($onlyactive) {
  1314. $wheres[] = "$where1 AND $where2";
  1315. }
  1316. } else {
  1317. // Suspended only where there is enrolment but ALL are suspended.
  1318. // Consider multiple enrols where one is not suspended or plain role_assign.
  1319. $enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2";
  1320. $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = $useridcolumn";
  1321. $enrolconditions = array(
  1322. "{$prefix}e1.id = {$prefix}ue1.enrolid",
  1323. "{$prefix}e1.courseid = :{$prefix}_e1_courseid",
  1324. );
  1325. if ($enrolid) {
  1326. $enrolconditions[] = "{$prefix}e1.id = :{$prefix}e1_enrolid";
  1327. $params[$prefix . 'e1_enrolid'] = $enrolid;
  1328. }
  1329. $enrolconditionssql = implode(" AND ", $enrolconditions);
  1330. $joins[] = "JOIN {enrol} {$prefix}e1 ON ($enrolconditionssql)";
  1331. $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid;
  1332. $wheres[] = "$useridcolumn NOT IN ($enrolselect)";
  1333. }
  1334. if ($onlyactive || $onlysuspended) {
  1335. $now = round(time(), -2); // Rounding helps caching in DB.
  1336. $params = array_merge($params, array($prefix . 'enabled' => ENROL_INSTANCE_ENABLED,
  1337. $prefix . 'active' => ENROL_USER_ACTIVE,
  1338. $prefix . 'now1' => $now, $prefix . 'now2' => $now));
  1339. }
  1340. }
  1341. $joins = implode("\n", $joins);
  1342. $wheres = implode(" AND ", $wheres);
  1343. return new \core\dml\sql_join($joins, $wheres, $params);
  1344. }
  1345. /**
  1346. * Returns list of users enrolled into course.
  1347. *
  1348. * @param context $context
  1349. * @param string $withcapability
  1350. * @param int $groupid 0 means ignore groups, USERSWITHOUTGROUP without any group and any other value limits the result by group id
  1351. * @param string $userfields requested user record fields
  1352. * @param string $orderby
  1353. * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
  1354. * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
  1355. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1356. * @return array of user records
  1357. */
  1358. function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null,
  1359. $limitfrom = 0, $limitnum = 0, $onlyactive = false) {
  1360. global $DB;
  1361. list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
  1362. $sql = "SELECT $userfields
  1363. FROM {user} u
  1364. JOIN ($esql) je ON je.id = u.id
  1365. WHERE u.deleted = 0";
  1366. if ($orderby) {
  1367. $sql = "$sql ORDER BY $orderby";
  1368. } else {
  1369. list($sort, $sortparams) = users_order_by_sql('u');
  1370. $sql = "$sql ORDER BY $sort";
  1371. $params = array_merge($params, $sortparams);
  1372. }
  1373. return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
  1374. }
  1375. /**
  1376. * Counts list of users enrolled into course (as per above function)
  1377. *
  1378. * @param context $context
  1379. * @param string $withcapability
  1380. * @param int $groupid 0 means ignore groups, any other value limits the result by group id
  1381. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1382. * @return array of user records
  1383. */
  1384. function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
  1385. global $DB;
  1386. $capjoin = get_enrolled_with_capabilities_join(
  1387. $context, '', $withcapability, $groupid, $onlyactive);
  1388. $sql = "SELECT COUNT(DISTINCT u.id)
  1389. FROM {user} u
  1390. $capjoin->joins
  1391. WHERE $capjoin->wheres AND u.deleted = 0";
  1392. return $DB->count_records_sql($sql, $capjoin->params);
  1393. }
  1394. /**
  1395. * Send welcome email "from" options.
  1396. *
  1397. * @return array list of from options
  1398. */
  1399. function enrol_send_welcome_email_options() {
  1400. return [
  1401. ENROL_DO_NOT_SEND_EMAIL => get_string('no'),
  1402. ENROL_SEND_EMAIL_FROM_COURSE_CONTACT => get_string('sendfromcoursecontact', 'enrol'),
  1403. ENROL_SEND_EMAIL_FROM_KEY_HOLDER => get_string('sendfromkeyholder', 'enrol'),
  1404. ENROL_SEND_EMAIL_FROM_NOREPLY => get_string('sendfromnoreply', 'enrol')
  1405. ];
  1406. }
  1407. /**
  1408. * Serve the user enrolment form as a fragment.
  1409. *
  1410. * @param array $args List of named arguments for the fragment loader.
  1411. * @return string
  1412. */
  1413. function enrol_output_fragment_user_enrolment_form($args) {
  1414. global $CFG, $DB;
  1415. $args = (object) $args;
  1416. $context = $args->context;
  1417. require_capability('moodle/course:enrolreview', $context);
  1418. $ueid = $args->ueid;
  1419. $userenrolment = $DB->get_record('user_enrolments', ['id' => $ueid], '*', MUST_EXIST);
  1420. $instance = $DB->get_record('enrol', ['id' => $userenrolment->enrolid], '*', MUST_EXIST);
  1421. $plugin = enrol_get_plugin($instance->enrol);
  1422. $customdata = [
  1423. 'ue' => $userenrolment,
  1424. 'modal' => true,
  1425. 'enrolinstancename' => $plugin->get_instance_name($instance)
  1426. ];
  1427. // Set the data if applicable.
  1428. $data = [];
  1429. if (isset($args->formdata)) {
  1430. $serialiseddata = json_decode($args->formdata);
  1431. parse_str($serialiseddata, $data);
  1432. }
  1433. require_once("$CFG->dirroot/enrol/editenrolment_form.php");
  1434. $mform = new \enrol_user_enrolment_form(null, $customdata, 'post', '', null, true, $data);
  1435. if (!empty($data)) {
  1436. $mform->set_data($data);
  1437. $mform->is_validated();
  1438. }
  1439. return $mform->render();
  1440. }
  1441. /**
  1442. * Returns the course where a user enrolment belong to.
  1443. *
  1444. * @param int $ueid user_enrolments id
  1445. * @return stdClass
  1446. */
  1447. function enrol_get_course_by_user_enrolment_id($ueid) {
  1448. global $DB;
  1449. $sql = "SELECT c.* FROM {user_enrolments} ue
  1450. JOIN {enrol} e ON e.id = ue.enrolid
  1451. JOIN {course} c ON c.id = e.courseid
  1452. WHERE ue.id = :ueid";
  1453. return $DB->get_record_sql($sql, array('ueid' => $ueid));
  1454. }
  1455. /**
  1456. * Return all users enrolled in a course.
  1457. *
  1458. * @param int $courseid Course id or false if using $uefilter (user enrolment ids may belong to different courses)
  1459. * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
  1460. * @param array $usersfilter Limit the results obtained to this list of user ids. $uefilter compatibility not guaranteed.
  1461. * @param array $uefilter Limit the results obtained to this list of user enrolment ids. $usersfilter compatibility not guaranteed.
  1462. * @return stdClass[]
  1463. */
  1464. function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfilter = array(), $uefilter = array()) {
  1465. global $DB;
  1466. if (!$courseid && !$usersfilter && !$uefilter) {
  1467. throw new \coding_exception('You should specify at least 1 filter: courseid, users or user enrolments');
  1468. }
  1469. $sql = "SELECT ue.id AS ueid, ue.status AS uestatus, ue.enrolid AS ueenrolid, ue.timestart AS uetimestart,
  1470. ue.timeend AS uetimeend, ue.modifierid AS uemodifierid, ue.timecreated AS uetimecreated,
  1471. ue.timemodified AS uetimemodified, e.status AS estatus,
  1472. u.* FROM {user_enrolments} ue
  1473. JOIN {enrol} e ON e.id = ue.enrolid
  1474. JOIN {user} u ON ue.userid = u.id
  1475. WHERE ";
  1476. $params = array();
  1477. if ($courseid) {
  1478. $conditions[] = "e.courseid = :courseid";
  1479. $params['courseid'] = $courseid;
  1480. }
  1481. if ($onlyactive) {
  1482. $conditions[] = "ue.status = :active AND e.status = :enabled AND ue.timestart < :now1 AND " .
  1483. "(ue.timeend = 0 OR ue.timeend > :now2)";
  1484. // Improves db caching.
  1485. $params['now1'] = round(time(), -2);
  1486. $params['now2'] = $params['now1'];
  1487. $params['active'] = ENROL_USER_ACTIVE;
  1488. $params['enabled'] = ENROL_INSTANCE_ENABLED;
  1489. }
  1490. if ($usersfilter) {
  1491. list($usersql, $userparams) = $DB->get_in_or_equal($usersfilter, SQL_PARAMS_NAMED);
  1492. $conditions[] = "ue.userid $usersql";
  1493. $params = $params + $userparams;
  1494. }
  1495. if ($uefilter) {
  1496. list($uesql, $ueparams) = $DB->get_in_or_equal($uefilter, SQL_PARAMS_NAMED);
  1497. $conditions[] = "ue.id $uesql";
  1498. $params = $params + $ueparams;
  1499. }
  1500. return $DB->get_records_sql($sql . ' ' . implode(' AND ', $conditions), $params);
  1501. }
  1502. /**
  1503. * Get the list of options for the enrolment period dropdown
  1504. *
  1505. * @return array List of options for the enrolment period dropdown
  1506. */
  1507. function enrol_get_period_list() {
  1508. $periodmenu = [];
  1509. $periodmenu[''] = get_string('unlimited');
  1510. for ($i = 1; $i <= 365; $i++) {
  1511. $seconds = $i * DAYSECS;
  1512. $periodmenu[$seconds] = get_string('numdays', '', $i);
  1513. }
  1514. return $periodmenu;
  1515. }
  1516. /**
  1517. * Calculate duration base on start time and end time
  1518. *
  1519. * @param int $timestart Time start
  1520. * @param int $timeend Time end
  1521. * @return float|int Calculated duration
  1522. */
  1523. function enrol_calculate_duration($timestart, $timeend) {
  1524. $duration = floor(($timeend - $timestart) / DAYSECS) * DAYSECS;
  1525. return $duration;
  1526. }
  1527. /**
  1528. * Enrolment plugins abstract class.
  1529. *
  1530. * All enrol plugins should be based on this class,
  1531. * this is also the main source of documentation.
  1532. *
  1533. * @copyright 2010 Petr Skoda {@link http://skodak.org}
  1534. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  1535. */
  1536. abstract class enrol_plugin {
  1537. protected $config = null;
  1538. /**
  1539. * Returns name of this enrol plugin
  1540. * @return string
  1541. */
  1542. public function get_name() {
  1543. // second word in class is always enrol name, sorry, no fancy plugin names with _
  1544. $words = explode('_', get_class($this));
  1545. return $words[1];
  1546. }
  1547. /**
  1548. * Returns localised name of enrol instance
  1549. *
  1550. * @param object $instance (null is accepted too)
  1551. * @return string
  1552. */
  1553. public function get_instance_name($instance) {
  1554. if (empty($instance->name)) {
  1555. $enrol = $this->get_name();
  1556. return get_string('pluginname', 'enrol_'.$enrol);
  1557. } else {
  1558. $context = context_course::instance($instance->courseid);
  1559. return format_string($instance->name, true, array('context'=>$context));
  1560. }
  1561. }
  1562. /**
  1563. * Returns optional enrolment information icons.
  1564. *
  1565. * This is used in course list for quick overview of enrolment options.
  1566. *
  1567. * We are not using single instance parameter because sometimes
  1568. * we might want to prevent icon repetition when multiple instances
  1569. * of one type exist. One instance may also produce several icons.
  1570. *
  1571. * @param array $instances all enrol instances of this type in one course
  1572. * @return array of pix_icon
  1573. */
  1574. public function get_info_icons(array $instances) {
  1575. return array();
  1576. }
  1577. /**
  1578. * Returns optional enrolment instance description text.
  1579. *
  1580. * This is used in detailed course information.
  1581. *
  1582. *
  1583. * @param object $instance
  1584. * @return string short html text
  1585. */
  1586. public function get_description_text($instance) {
  1587. return null;
  1588. }
  1589. /**
  1590. * Makes sure config is loaded and cached.
  1591. * @return void
  1592. */
  1593. protected function load_config() {
  1594. if (!isset($this->config)) {
  1595. $name = $this->get_name();
  1596. $this->config = get_config("enrol_$name");
  1597. }
  1598. }
  1599. /**
  1600. * Returns plugin config value
  1601. * @param string $name
  1602. * @param string $default value if config does not exist yet
  1603. * @return string value or default
  1604. */
  1605. public function get_config($name, $default = NULL) {
  1606. $this->load_config();
  1607. return isset($this->config->$name) ? $this->config->$name : $default;
  1608. }
  1609. /**
  1610. * Sets plugin config value
  1611. * @param string $name name of config
  1612. * @param string $value string config value, null means delete
  1613. * @return string value
  1614. */
  1615. public function set_config($name, $value) {
  1616. $pluginname = $this->get_name();
  1617. $this->load_config();
  1618. if ($value === NULL) {
  1619. unset($this->config->$name);
  1620. } else {
  1621. $this->config->$name = $value;
  1622. }
  1623. set_config($name, $value, "enrol_$pluginname");
  1624. }
  1625. /**
  1626. * Does this plugin assign protected roles are can they be manually removed?
  1627. * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
  1628. */
  1629. public function roles_protected() {
  1630. return true;
  1631. }
  1632. /**
  1633. * Does this plugin allow manual enrolments?
  1634. *
  1635. * @param stdClass $instance course enrol instance
  1636. * All plugins allowing this must implement 'enrol/xxx:enrol' capability
  1637. *
  1638. * @return bool - true means user with 'enrol/xxx:enrol' may enrol others freely, false means nobody may add more enrolments manually
  1639. */
  1640. public function allow_enrol(stdClass $instance) {
  1641. return false;
  1642. }
  1643. /**
  1644. * Does this plugin allow manual unenrolment of all users?
  1645. * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
  1646. *
  1647. * @param stdClass $instance course enrol instance
  1648. * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
  1649. */
  1650. public function allow_unenrol(stdClass $instance) {
  1651. return false;
  1652. }
  1653. /**
  1654. * Does this plugin allow manual unenrolment of a specific user?
  1655. * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
  1656. *
  1657. * This is useful especially for synchronisation plugins that
  1658. * do suspend instead of full unenrolment.
  1659. *
  1660. * @param stdClass $instance course enrol instance
  1661. * @param stdClass $ue record from user_enrolments table, specifies user
  1662. *
  1663. * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
  1664. */
  1665. public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
  1666. return $this->allow_unenrol($instance);
  1667. }
  1668. /**
  1669. * Does this plugin allow manual changes in user_enrolments table?
  1670. *
  1671. * All plugins allowing this must implement 'enrol/xxx:manage' capability
  1672. *
  1673. * @param stdClass $instance course enrol instance
  1674. * @return bool - true means it is possible to change enrol period and status in user_enrolments table
  1675. */
  1676. public function allow_manage(stdClass $instance) {
  1677. return false;
  1678. }
  1679. /**
  1680. * Does this plugin support some way to user to self enrol?
  1681. *
  1682. * @param stdClass $instance course enrol instance
  1683. *
  1684. * @return bool - true means show "Enrol me in this course" link in course UI
  1685. */
  1686. public function show_enrolme_link(stdClass $instance) {
  1687. return false;
  1688. }
  1689. /**
  1690. * Attempt to automatically enrol current user in course without any interaction,
  1691. * calling code has to make sure the plugin and instance are active.
  1692. *
  1693. * This should return either a timestamp in the future or false.
  1694. *
  1695. * @param stdClass $instance course enrol instance
  1696. * @return bool|int false means not enrolled, integer means timeend
  1697. */
  1698. public function try_autoenrol(stdClass $instance) {
  1699. global $USER;
  1700. return false;
  1701. }
  1702. /**
  1703. * Attempt to automatically gain temporary guest access to course,
  1704. * calling code has to make sure the plugin and instance are active.
  1705. *
  1706. * This should return either a timestamp in the future or false.
  1707. *
  1708. * @param stdClass $instance course enrol instance
  1709. * @return bool|int false means no guest access, integer means timeend
  1710. */
  1711. public function try_guestaccess(stdClass $instance) {
  1712. global $USER;
  1713. return false;
  1714. }
  1715. /**
  1716. * Enrol user into course via enrol instance.
  1717. *
  1718. * @param stdClass $instance
  1719. * @param int $userid
  1720. * @param int $roleid optional role id
  1721. * @param int $timestart 0 means unknown
  1722. * @param int $timeend 0 means forever
  1723. * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
  1724. * @param bool $recovergrades restore grade history
  1725. * @return void
  1726. */
  1727. public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) {
  1728. global $DB, $USER, $CFG; // CFG necessary!!!
  1729. if ($instance->courseid == SITEID) {
  1730. throw new coding_exception('invalid attempt to enrol into frontpage course!');
  1731. }
  1732. $name = $this->get_name();
  1733. $courseid = $instance->courseid;
  1734. if ($instance->enrol !== $name) {
  1735. throw new coding_exception('invalid enrol instance!');
  1736. }
  1737. $context = context_course::instance($instance->courseid, MUST_EXIST);
  1738. if (!isset($recovergrades)) {
  1739. $recovergrades = $CFG->recovergradesdefault;
  1740. }
  1741. $inserted = false;
  1742. $updated = false;
  1743. if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
  1744. //only update if timestart or timeend or status are different.
  1745. if ($ue->timestart != $timestart or $ue->timeend != $timeend or (!is_null($status) and $ue->status != $status)) {
  1746. $this->update_user_enrol($instance, $userid, $status, $timestart, $timeend);
  1747. }
  1748. } else {
  1749. $ue = new stdClass();
  1750. $ue->enrolid = $instance->id;
  1751. $ue->status = is_null($status) ? ENROL_USER_ACTIVE : $status;
  1752. $ue->userid = $userid;
  1753. $ue->timestart = $timestart;
  1754. $ue->timeend = $timeend;
  1755. $ue->modifierid = $USER->id;
  1756. $ue->timecreated = time();
  1757. $ue->timemodified = $ue->timecreated;
  1758. $ue->id = $DB->insert_record('user_enrolments', $ue);
  1759. $inserted = true;
  1760. }
  1761. if ($inserted) {
  1762. // Trigger event.
  1763. $event = \core\event\user_enrolment_created::create(
  1764. array(
  1765. 'objectid' => $ue->id,
  1766. 'courseid' => $courseid,
  1767. 'context' => $context,
  1768. 'relateduserid' => $ue->userid,
  1769. 'other' => array('enrol' => $name)
  1770. )
  1771. );
  1772. $event->trigger();
  1773. // Check if course contacts cache needs to be cleared.
  1774. core_course_category::user_enrolment_changed($courseid, $ue->userid,
  1775. $ue->status, $ue->timestart, $ue->timeend);
  1776. }
  1777. if ($roleid) {
  1778. // this must be done after the enrolment event so that the role_assigned event is triggered afterwards
  1779. if ($this->roles_protected()) {
  1780. role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
  1781. } else {
  1782. role_assign($roleid, $userid, $context->id);
  1783. }
  1784. }
  1785. // Recover old grades if present.
  1786. if ($recovergrades) {
  1787. require_once("$CFG->libdir/gradelib.php");
  1788. grade_recover_history_grades($userid, $courseid);
  1789. }
  1790. // reset current user enrolment caching
  1791. if ($userid == $USER->id) {
  1792. if (isset($USER->enrol['enrolled'][$courseid])) {
  1793. unset($USER->enrol['enrolled'][$courseid]);
  1794. }
  1795. if (isset($USER->enrol['tempguest'][$courseid])) {
  1796. unset($USER->enrol['tempguest'][$courseid]);
  1797. remove_temp_course_roles($context);
  1798. }
  1799. }
  1800. }
  1801. /**
  1802. * Store user_enrolments changes and trigger event.
  1803. *
  1804. * @param stdClass $instance
  1805. * @param int $userid
  1806. * @param int $status
  1807. * @param int $timestart
  1808. * @param int $timeend
  1809. * @return void
  1810. */
  1811. public function update_user_enrol(stdClass $instance, $userid, $status = NULL, $timestart = NULL, $timeend = NULL) {
  1812. global $DB, $USER, $CFG;
  1813. $name = $this->get_name();
  1814. if ($instance->enrol !== $name) {
  1815. throw new coding_exception('invalid enrol instance!');
  1816. }
  1817. if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
  1818. // weird, user not enrolled
  1819. return;
  1820. }
  1821. $modified = false;
  1822. if (isset($status) and $ue->status != $status) {
  1823. $ue->status = $status;
  1824. $modified = true;
  1825. }
  1826. if (isset($timestart) and $ue->timestart != $timestart) {
  1827. $ue->timestart = $timestart;
  1828. $modified = true;
  1829. }
  1830. if (isset($timeend) and $ue->timeend != $timeend) {
  1831. $ue->timeend = $timeend;
  1832. $modified = true;
  1833. }
  1834. if (!$modified) {
  1835. // no change
  1836. return;
  1837. }
  1838. $ue->modifierid = $USER->id;
  1839. $ue->timemodified = time();
  1840. $DB->update_record('user_enrolments', $ue);
  1841. // User enrolments have changed, so mark user as dirty.
  1842. mark_user_dirty($userid);
  1843. // Invalidate core_access cache for get_suspended_userids.
  1844. cache_helper::invalidate_by_definition('core', 'suspended_userids', array(), array($instance->courseid));
  1845. // Trigger event.
  1846. $event = \core\event\user_enrolment_updated::create(
  1847. array(
  1848. 'objectid' => $ue->id,
  1849. 'courseid' => $instance->courseid,
  1850. 'context' => context_course::instance($instance->courseid),
  1851. 'relateduserid' => $ue->userid,
  1852. 'other' => array('enrol' => $name)
  1853. )
  1854. );
  1855. $event->trigger();
  1856. core_course_category::user_enrolment_changed($instance->courseid, $ue->userid,
  1857. $ue->status, $ue->timestart, $ue->timeend);
  1858. }
  1859. /**
  1860. * Unenrol user from course,
  1861. * the last unenrolment removes all remaining roles.
  1862. *
  1863. * @param stdClass $instance
  1864. * @param int $userid
  1865. * @return void
  1866. */
  1867. public function unenrol_user(stdClass $instance, $userid) {
  1868. global $CFG, $USER, $DB;
  1869. require_once("$CFG->dirroot/group/lib.php");
  1870. $name = $this->get_name();
  1871. $courseid = $instance->courseid;
  1872. if ($instance->enrol !== $name) {
  1873. throw new coding_exception('invalid enrol instance!');
  1874. }
  1875. $context = context_course::instance($instance->courseid, MUST_EXIST);
  1876. if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
  1877. // weird, user not enrolled
  1878. return;
  1879. }
  1880. // Remove all users groups linked to this enrolment instance.
  1881. if ($gms = $DB->get_records('groups_members', array('userid'=>$userid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id))) {
  1882. foreach ($gms as $gm) {
  1883. groups_remove_member($gm->groupid, $gm->userid);
  1884. }
  1885. }
  1886. role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id));
  1887. $DB->delete_records('user_enrolments', array('id'=>$ue->id));
  1888. // add extra info and trigger event
  1889. $ue->courseid = $courseid;
  1890. $ue->enrol = $name;
  1891. $sql = "SELECT 'x'
  1892. FROM {user_enrolments} ue
  1893. JOIN {enrol} e ON (e.id = ue.enrolid)
  1894. WHERE ue.userid = :userid AND e.courseid = :courseid";
  1895. if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) {
  1896. $ue->lastenrol = false;
  1897. } else {
  1898. // the big cleanup IS necessary!
  1899. require_once("$CFG->libdir/gradelib.php");
  1900. // remove all remaining roles
  1901. role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false);
  1902. //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc.
  1903. groups_delete_group_members($courseid, $userid);
  1904. grade_user_unenrol($courseid, $userid);
  1905. $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid));
  1906. $ue->lastenrol = true; // means user not enrolled any more
  1907. }
  1908. // Trigger event.
  1909. $event = \core\event\user_enrolment_deleted::create(
  1910. array(
  1911. 'courseid' => $courseid,
  1912. 'context' => $context,
  1913. 'relateduserid' => $ue->userid,
  1914. 'objectid' => $ue->id,
  1915. 'other' => array(
  1916. 'userenrolment' => (array)$ue,
  1917. 'enrol' => $name
  1918. )
  1919. )
  1920. );
  1921. $event->trigger();
  1922. // User enrolments have changed, so mark user as dirty.
  1923. mark_user_dirty($userid);
  1924. // Check if courrse contacts cache needs to be cleared.
  1925. core_course_category::user_enrolment_changed($courseid, $ue->userid, ENROL_USER_SUSPENDED);
  1926. // reset current user enrolment caching
  1927. if ($userid == $USER->id) {
  1928. if (isset($USER->enrol['enrolled'][$courseid])) {
  1929. unset($USER->enrol['enrolled'][$courseid]);
  1930. }
  1931. if (isset($USER->enrol['tempguest'][$courseid])) {
  1932. unset($USER->enrol['tempguest'][$courseid]);
  1933. remove_temp_course_roles($context);
  1934. }
  1935. }
  1936. }
  1937. /**
  1938. * Forces synchronisation of user enrolments.
  1939. *
  1940. * This is important especially for external enrol plugins,
  1941. * this function is called for all enabled enrol plugins
  1942. * right after every user login.
  1943. *
  1944. * @param object $user user record
  1945. * @return void
  1946. */
  1947. public function sync_user_enrolments($user) {
  1948. // override if necessary
  1949. }
  1950. /**
  1951. * This returns false for backwards compatibility, but it is really recommended.
  1952. *
  1953. * @since Moodle 3.1
  1954. * @return boolean
  1955. */
  1956. public function use_standard_editing_ui() {
  1957. return false;
  1958. }
  1959. /**
  1960. * Return whether or not, given the current state, it is possible to add a new instance
  1961. * of this enrolment plugin to the course.
  1962. *
  1963. * Default implementation is just for backwards compatibility.
  1964. *
  1965. * @param int $courseid
  1966. * @return boolean
  1967. */
  1968. public function can_add_instance($courseid) {
  1969. $link = $this->get_newinstance_link($courseid);
  1970. return !empty($link);
  1971. }
  1972. /**
  1973. * Return whether or not, given the current state, it is possible to edit an instance
  1974. * of this enrolment plugin in the course. Used by the standard editing UI
  1975. * to generate a link to the edit instance form if editing is allowed.
  1976. *
  1977. * @param stdClass $instance
  1978. * @return boolean
  1979. */
  1980. public function can_edit_instance($instance) {
  1981. $context = context_course::instance($instance->courseid);
  1982. return has_capability('enrol/' . $instance->enrol . ':config', $context);
  1983. }
  1984. /**
  1985. * Returns link to page which may be used to add new instance of enrolment plugin in course.
  1986. * @param int $courseid
  1987. * @return moodle_url page url
  1988. */
  1989. public function get_newinstance_link($courseid) {
  1990. // override for most plugins, check if instance already exists in cases only one instance is supported
  1991. return NULL;
  1992. }
  1993. /**
  1994. * @deprecated since Moodle 2.8 MDL-35864 - please use can_delete_instance() instead.
  1995. */
  1996. public function instance_deleteable($instance) {
  1997. throw new coding_exception('Function enrol_plugin::instance_deleteable() is deprecated, use
  1998. enrol_plugin::can_delete_instance() instead');
  1999. }
  2000. /**
  2001. * Is it possible to delete enrol instance via standard UI?
  2002. *
  2003. * @param stdClass $instance
  2004. * @return bool
  2005. */
  2006. public function can_delete_instance($instance) {
  2007. return false;
  2008. }
  2009. /**
  2010. * Is it possible to hide/show enrol instance via standard UI?
  2011. *
  2012. * @param stdClass $instance
  2013. * @return bool
  2014. */
  2015. public function can_hide_show_instance($instance) {
  2016. debugging("The enrolment plugin '".$this->get_name()."' should override the function can_hide_show_instance().", DEBUG_DEVELOPER);
  2017. return true;
  2018. }
  2019. /**
  2020. * Returns link to manual enrol UI if exists.
  2021. * Does the access control tests automatically.
  2022. *
  2023. * @param object $instance
  2024. * @return moodle_url
  2025. */
  2026. public function get_manual_enrol_link($instance) {
  2027. return NULL;
  2028. }
  2029. /**
  2030. * Returns list of unenrol links for all enrol instances in course.
  2031. *
  2032. * @param int $instance
  2033. * @return moodle_url or NULL if self unenrolment not supported
  2034. */
  2035. public function get_unenrolself_link($instance) {
  2036. global $USER, $CFG, $DB;
  2037. $name = $this->get_name();
  2038. if ($instance->enrol !== $name) {
  2039. throw new coding_exception('invalid enrol instance!');
  2040. }
  2041. if ($instance->courseid == SITEID) {
  2042. return NULL;
  2043. }
  2044. if (!enrol_is_enabled($name)) {
  2045. return NULL;
  2046. }
  2047. if ($instance->status != ENROL_INSTANCE_ENABLED) {
  2048. return NULL;
  2049. }
  2050. if (!file_exists("$CFG->dirroot/enrol/$name/unenrolself.php")) {
  2051. return NULL;
  2052. }
  2053. $context = context_course::instance($instance->courseid, MUST_EXIST);
  2054. if (!has_capability("enrol/$name:unenrolself", $context)) {
  2055. return NULL;
  2056. }
  2057. if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$USER->id, 'status'=>ENROL_USER_ACTIVE))) {
  2058. return NULL;
  2059. }
  2060. return new moodle_url("/enrol/$name/unenrolself.php", array('enrolid'=>$instance->id));
  2061. }
  2062. /**
  2063. * Adds enrol instance UI to course edit form
  2064. *
  2065. * @param object $instance enrol instance or null if does not exist yet
  2066. * @param MoodleQuickForm $mform
  2067. * @param object $data
  2068. * @param object $context context of existing course or parent category if course does not exist
  2069. * @return void
  2070. */
  2071. public function course_edit_form($instance, MoodleQuickForm $mform, $data, $context) {
  2072. // override - usually at least enable/disable switch, has to add own form header
  2073. }
  2074. /**
  2075. * Adds form elements to add/edit instance form.
  2076. *
  2077. * @since Moodle 3.1
  2078. * @param object $instance enrol instance or null if does not exist yet
  2079. * @param MoodleQuickForm $mform
  2080. * @param context $context
  2081. * @return void
  2082. */
  2083. public function edit_instance_form($instance, MoodleQuickForm $mform, $context) {
  2084. // Do nothing by default.
  2085. }
  2086. /**
  2087. * Perform custom validation of the data used to edit the instance.
  2088. *
  2089. * @since Moodle 3.1
  2090. * @param array $data array of ("fieldname"=>value) of submitted data
  2091. * @param array $files array of uploaded files "element_name"=>tmp_file_path
  2092. * @param object $instance The instance data loaded from the DB.
  2093. * @param context $context The context of the instance we are editing
  2094. * @return array of "element_name"=>"error_description" if there are errors,
  2095. * or an empty array if everything is OK.
  2096. */
  2097. public function edit_instance_validation($data, $files, $instance, $context) {
  2098. // No errors by default.
  2099. debugging('enrol_plugin::edit_instance_validation() is missing. This plugin has no validation!', DEBUG_DEVELOPER);
  2100. return array();
  2101. }
  2102. /**
  2103. * Validates course edit form data
  2104. *
  2105. * @param object $instance enrol instance or null if does not exist yet
  2106. * @param array $data
  2107. * @param object $context context of existing course or parent category if course does not exist
  2108. * @return array errors array
  2109. */
  2110. public function course_edit_validation($instance, array $data, $context) {
  2111. return array();
  2112. }
  2113. /**
  2114. * Called after updating/inserting course.
  2115. *
  2116. * @param bool $inserted true if course just inserted
  2117. * @param object $course
  2118. * @param object $data form data
  2119. * @return void
  2120. */
  2121. public function course_updated($inserted, $course, $data) {
  2122. if ($inserted) {
  2123. if ($this->get_config('defaultenrol')) {
  2124. $this->add_default_instance($course);
  2125. }
  2126. }
  2127. }
  2128. /**
  2129. * Add new instance of enrol plugin.
  2130. * @param object $course
  2131. * @param array instance fields
  2132. * @return int id of new instance, null if can not be created
  2133. */
  2134. public function add_instance($course, array $fields = NULL) {
  2135. global $DB;
  2136. if ($course->id == SITEID) {
  2137. throw new coding_exception('Invalid request to add enrol instance to frontpage.');
  2138. }
  2139. $instance = new stdClass();
  2140. $instance->enrol = $this->get_name();
  2141. $instance->status = ENROL_INSTANCE_ENABLED;
  2142. $instance->courseid = $course->id;
  2143. $instance->enrolstartdate = 0;
  2144. $instance->enrolenddate = 0;
  2145. $instance->timemodified = time();
  2146. $instance->timecreated = $instance->timemodified;
  2147. $instance->sortorder = $DB->get_field('enrol', 'COALESCE(MAX(sortorder), -1) + 1', array('courseid'=>$course->id));
  2148. $fields = (array)$fields;
  2149. unset($fields['enrol']);
  2150. unset($fields['courseid']);
  2151. unset($fields['sortorder']);
  2152. foreach($fields as $field=>$value) {
  2153. $instance->$field = $value;
  2154. }
  2155. $instance->id = $DB->insert_record('enrol', $instance);
  2156. \core\event\enrol_instance_created::create_from_record($instance)->trigger();
  2157. return $instance->id;
  2158. }
  2159. /**
  2160. * Update instance of enrol plugin.
  2161. *
  2162. * @since Moodle 3.1
  2163. * @param stdClass $instance
  2164. * @param stdClass $data modified instance fields
  2165. * @return boolean
  2166. */
  2167. public function update_instance($instance, $data) {
  2168. global $DB;
  2169. $properties = array('status', 'name', 'password', 'customint1', 'customint2', 'customint3',
  2170. 'customint4', 'customint5', 'customint6', 'customint7', 'customint8',
  2171. 'customchar1', 'customchar2', 'customchar3', 'customdec1', 'customdec2',
  2172. 'customtext1', 'customtext2', 'customtext3', 'customtext4', 'roleid',
  2173. 'enrolperiod', 'expirynotify', 'notifyall', 'expirythreshold',
  2174. 'enrolstartdate', 'enrolenddate', 'cost', 'currency');
  2175. foreach ($properties as $key) {
  2176. if (isset($data->$key)) {
  2177. $instance->$key = $data->$key;
  2178. }
  2179. }
  2180. $instance->timemodified = time();
  2181. $update = $DB->update_record('enrol', $instance);
  2182. if ($update) {
  2183. \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
  2184. }
  2185. return $update;
  2186. }
  2187. /**
  2188. * Add new instance of enrol plugin with default settings,
  2189. * called when adding new instance manually or when adding new course.
  2190. *
  2191. * Not all plugins support this.
  2192. *
  2193. * @param object $course
  2194. * @return int id of new instance or null if no default supported
  2195. */
  2196. public function add_default_instance($course) {
  2197. return null;
  2198. }
  2199. /**
  2200. * Update instance status
  2201. *
  2202. * Override when plugin needs to do some action when enabled or disabled.
  2203. *
  2204. * @param stdClass $instance
  2205. * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
  2206. * @return void
  2207. */
  2208. public function update_status($instance, $newstatus) {
  2209. global $DB;
  2210. $instance->status = $newstatus;
  2211. $DB->update_record('enrol', $instance);
  2212. $context = context_course::instance($instance->courseid);
  2213. \core\event\enrol_instance_updated::create_from_record($instance)->trigger();
  2214. // Invalidate all enrol caches.
  2215. $context->mark_dirty();
  2216. }
  2217. /**
  2218. * Delete course enrol plugin instance, unenrol all users.
  2219. * @param object $instance
  2220. * @return void
  2221. */
  2222. public function delete_instance($instance) {
  2223. global $DB;
  2224. $name = $this->get_name();
  2225. if ($instance->enrol !== $name) {
  2226. throw new coding_exception('invalid enrol instance!');
  2227. }
  2228. //first unenrol all users
  2229. $participants = $DB->get_recordset('user_enrolments', array('enrolid'=>$instance->id));
  2230. foreach ($participants as $participant) {
  2231. $this->unenrol_user($instance, $participant->userid);
  2232. }
  2233. $participants->close();
  2234. // now clean up all remainders that were not removed correctly
  2235. if ($gms = $DB->get_records('groups_members', array('itemid' => $instance->id, 'component' => 'enrol_' . $name))) {
  2236. foreach ($gms as $gm) {
  2237. groups_remove_member($gm->groupid, $gm->userid);
  2238. }
  2239. }
  2240. $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$name));
  2241. $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
  2242. // finally drop the enrol row
  2243. $DB->delete_records('enrol', array('id'=>$instance->id));
  2244. $context = context_course::instance($instance->courseid);
  2245. \core\event\enrol_instance_deleted::create_from_record($instance)->trigger();
  2246. // Invalidate all enrol caches.
  2247. $context->mark_dirty();
  2248. }
  2249. /**
  2250. * Creates course enrol form, checks if form submitted
  2251. * and enrols user if necessary. It can also redirect.
  2252. *
  2253. * @param stdClass $instance
  2254. * @return string html text, usually a form in a text box
  2255. */
  2256. public function enrol_page_hook(stdClass $instance) {
  2257. return null;
  2258. }
  2259. /**
  2260. * Checks if user can self enrol.
  2261. *
  2262. * @param stdClass $instance enrolment instance
  2263. * @param bool $checkuserenrolment if true will check if user enrolment is inactive.
  2264. * used by navigation to improve performance.
  2265. * @return bool|string true if successful, else error message or false
  2266. */
  2267. public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) {
  2268. return false;
  2269. }
  2270. /**
  2271. * Return information for enrolment instance containing list of parameters required
  2272. * for enrolment, name of enrolment plugin etc.
  2273. *
  2274. * @param stdClass $instance enrolment instance
  2275. * @return array instance info.
  2276. */
  2277. public function get_enrol_info(stdClass $instance) {
  2278. return null;
  2279. }
  2280. /**
  2281. * Adds navigation links into course admin block.
  2282. *
  2283. * By defaults looks for manage links only.
  2284. *
  2285. * @param navigation_node $instancesnode
  2286. * @param stdClass $instance
  2287. * @return void
  2288. */
  2289. public function add_course_navigation($instancesnode, stdClass $instance) {
  2290. if ($this->use_standard_editing_ui()) {
  2291. $context = context_course::instance($instance->courseid);
  2292. $cap = 'enrol/' . $instance->enrol . ':config';
  2293. if (has_capability($cap, $context)) {
  2294. $linkparams = array('courseid' => $instance->courseid, 'id' => $instance->id, 'type' => $instance->enrol);
  2295. $managelink = new moodle_url('/enrol/editinstance.php', $linkparams);
  2296. $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
  2297. }
  2298. }
  2299. }
  2300. /**
  2301. * Returns edit icons for the page with list of instances
  2302. * @param stdClass $instance
  2303. * @return array
  2304. */
  2305. public function get_action_icons(stdClass $instance) {
  2306. global $OUTPUT;
  2307. $icons = array();
  2308. if ($this->use_standard_editing_ui()) {
  2309. $linkparams = array('courseid' => $instance->courseid, 'id' => $instance->id, 'type' => $instance->enrol);
  2310. $editlink = new moodle_url("/enrol/editinstance.php", $linkparams);
  2311. $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
  2312. array('class' => 'iconsmall')));
  2313. }
  2314. return $icons;
  2315. }
  2316. /**
  2317. * Reads version.php and determines if it is necessary
  2318. * to execute the cron job now.
  2319. * @return bool
  2320. */
  2321. public function is_cron_required() {
  2322. global $CFG;
  2323. $name = $this->get_name();
  2324. $versionfile = "$CFG->dirroot/enrol/$name/version.php";
  2325. $plugin = new stdClass();
  2326. include($versionfile);
  2327. if (empty($plugin->cron)) {
  2328. return false;
  2329. }
  2330. $lastexecuted = $this->get_config('lastcron', 0);
  2331. if ($lastexecuted + $plugin->cron < time()) {
  2332. return true;
  2333. } else {
  2334. return false;
  2335. }
  2336. }
  2337. /**
  2338. * Called for all enabled enrol plugins that returned true from is_cron_required().
  2339. * @return void
  2340. */
  2341. public function cron() {
  2342. }
  2343. /**
  2344. * Called when user is about to be deleted
  2345. * @param object $user
  2346. * @return void
  2347. */
  2348. public function user_delete($user) {
  2349. global $DB;
  2350. $sql = "SELECT e.*
  2351. FROM {enrol} e
  2352. JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
  2353. WHERE e.enrol = :name AND ue.userid = :userid";
  2354. $params = array('name'=>$this->get_name(), 'userid'=>$user->id);
  2355. $rs = $DB->get_recordset_sql($sql, $params);
  2356. foreach($rs as $instance) {
  2357. $this->unenrol_user($instance, $user->id);
  2358. }
  2359. $rs->close();
  2360. }
  2361. /**
  2362. * Returns an enrol_user_button that takes the user to a page where they are able to
  2363. * enrol users into the managers course through this plugin.
  2364. *
  2365. * Optional: If the plugin supports manual enrolments it can choose to override this
  2366. * otherwise it shouldn't
  2367. *
  2368. * @param course_enrolment_manager $manager
  2369. * @return enrol_user_button|false
  2370. */
  2371. public function get_manual_enrol_button(course_enrolment_manager $manager) {
  2372. return false;
  2373. }
  2374. /**
  2375. * Gets an array of the user enrolment actions
  2376. *
  2377. * @param course_enrolment_manager $manager
  2378. * @param stdClass $ue
  2379. * @return array An array of user_enrolment_actions
  2380. */
  2381. public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
  2382. $actions = [];
  2383. $context = $manager->get_context();
  2384. $instance = $ue->enrolmentinstance;
  2385. $params = $manager->get_moodlepage()->url->params();
  2386. $params['ue'] = $ue->id;
  2387. // Edit enrolment action.
  2388. if ($this->allow_manage($instance) && has_capability("enrol/{$instance->enrol}:manage", $context)) {
  2389. $title = get_string('editenrolment', 'enrol');
  2390. $icon = new pix_icon('t/edit', $title);
  2391. $url = new moodle_url('/enrol/editenrolment.php', $params);
  2392. $actionparams = [
  2393. 'class' => 'editenrollink',
  2394. 'rel' => $ue->id,
  2395. 'data-action' => ENROL_ACTION_EDIT
  2396. ];
  2397. $actions[] = new user_enrolment_action($icon, $title, $url, $actionparams);
  2398. }
  2399. // Unenrol action.
  2400. if ($this->allow_unenrol_user($instance, $ue) && has_capability("enrol/{$instance->enrol}:unenrol", $context)) {
  2401. $title = get_string('unenrol', 'enrol');
  2402. $icon = new pix_icon('t/delete', $title);
  2403. $url = new moodle_url('/enrol/unenroluser.php', $params);
  2404. $actionparams = [
  2405. 'class' => 'unenrollink',
  2406. 'rel' => $ue->id,
  2407. 'data-action' => ENROL_ACTION_UNENROL
  2408. ];
  2409. $actions[] = new user_enrolment_action($icon, $title, $url, $actionparams);
  2410. }
  2411. return $actions;
  2412. }
  2413. /**
  2414. * Returns true if the plugin has one or more bulk operations that can be performed on
  2415. * user enrolments.
  2416. *
  2417. * @param course_enrolment_manager $manager
  2418. * @return bool
  2419. */
  2420. public function has_bulk_operations(course_enrolment_manager $manager) {
  2421. return false;
  2422. }
  2423. /**
  2424. * Return an array of enrol_bulk_enrolment_operation objects that define
  2425. * the bulk actions that can be performed on user enrolments by the plugin.
  2426. *
  2427. * @param course_enrolment_manager $manager
  2428. * @return array
  2429. */
  2430. public function get_bulk_operations(course_enrolment_manager $manager) {
  2431. return array();
  2432. }
  2433. /**
  2434. * Do any enrolments need expiration processing.
  2435. *
  2436. * Plugins that want to call this functionality must implement 'expiredaction' config setting.
  2437. *
  2438. * @param progress_trace $trace
  2439. * @param int $courseid one course, empty mean all
  2440. * @return bool true if any data processed, false if not
  2441. */
  2442. public function process_expirations(progress_trace $trace, $courseid = null) {
  2443. global $DB;
  2444. $name = $this->get_name();
  2445. if (!enrol_is_enabled($name)) {
  2446. $trace->finished();
  2447. return false;
  2448. }
  2449. $processed = false;
  2450. $params = array();
  2451. $coursesql = "";
  2452. if ($courseid) {
  2453. $coursesql = "AND e.courseid = :courseid";
  2454. }
  2455. // Deal with expired accounts.
  2456. $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
  2457. if ($action == ENROL_EXT_REMOVED_UNENROL) {
  2458. $instances = array();
  2459. $sql = "SELECT ue.*, e.courseid, c.id AS contextid
  2460. FROM {user_enrolments} ue
  2461. JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
  2462. JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
  2463. WHERE ue.timeend > 0 AND ue.timeend < :now $coursesql";
  2464. $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'enrol'=>$name, 'courseid'=>$courseid);
  2465. $rs = $DB->get_recordset_sql($sql, $params);
  2466. foreach ($rs as $ue) {
  2467. if (!$processed) {
  2468. $trace->output("Starting processing of enrol_$name expirations...");
  2469. $processed = true;
  2470. }
  2471. if (empty($instances[$ue->enrolid])) {
  2472. $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
  2473. }
  2474. $instance = $instances[$ue->enrolid];
  2475. if (!$this->roles_protected()) {
  2476. // Let's just guess what extra roles are supposed to be removed.
  2477. if ($instance->roleid) {
  2478. role_unassign($instance->roleid, $ue->userid, $ue->contextid);
  2479. }
  2480. }
  2481. // The unenrol cleans up all subcontexts if this is the only course enrolment for this user.
  2482. $this->unenrol_user($instance, $ue->userid);
  2483. $trace->output("Unenrolling expired user $ue->userid from course $instance->courseid", 1);
  2484. }
  2485. $rs->close();
  2486. unset($instances);
  2487. } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES or $action == ENROL_EXT_REMOVED_SUSPEND) {
  2488. $instances = array();
  2489. $sql = "SELECT ue.*, e.courseid, c.id AS contextid
  2490. FROM {user_enrolments} ue
  2491. JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
  2492. JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
  2493. WHERE ue.timeend > 0 AND ue.timeend < :now
  2494. AND ue.status = :useractive $coursesql";
  2495. $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'useractive'=>ENROL_USER_ACTIVE, 'enrol'=>$name, 'courseid'=>$courseid);
  2496. $rs = $DB->get_recordset_sql($sql, $params);
  2497. foreach ($rs as $ue) {
  2498. if (!$processed) {
  2499. $trace->output("Starting processing of enrol_$name expirations...");
  2500. $processed = true;
  2501. }
  2502. if (empty($instances[$ue->enrolid])) {
  2503. $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
  2504. }
  2505. $instance = $instances[$ue->enrolid];
  2506. if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
  2507. if (!$this->roles_protected()) {
  2508. // Let's just guess what roles should be removed.
  2509. $count = $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid));
  2510. if ($count == 1) {
  2511. role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0));
  2512. } else if ($count > 1 and $instance->roleid) {
  2513. role_unassign($instance->roleid, $ue->userid, $ue->contextid, '', 0);
  2514. }
  2515. }
  2516. // In any case remove all roles that belong to this instance and user.
  2517. role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id), true);
  2518. // Final cleanup of subcontexts if there are no more course roles.
  2519. if (0 == $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid))) {
  2520. role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
  2521. }
  2522. }
  2523. $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
  2524. $trace->output("Suspending expired user $ue->userid in course $instance->courseid", 1);
  2525. }
  2526. $rs->close();
  2527. unset($instances);
  2528. } else {
  2529. // ENROL_EXT_REMOVED_KEEP means no changes.
  2530. }
  2531. if ($processed) {
  2532. $trace->output("...finished processing of enrol_$name expirations");
  2533. } else {
  2534. $trace->output("No expired enrol_$name enrolments detected");
  2535. }
  2536. $trace->finished();
  2537. return $processed;
  2538. }
  2539. /**
  2540. * Send expiry notifications.
  2541. *
  2542. * Plugin that wants to have expiry notification MUST implement following:
  2543. * - expirynotifyhour plugin setting,
  2544. * - configuration options in instance edit form (expirynotify, notifyall and expirythreshold),
  2545. * - notification strings (expirymessageenrollersubject, expirymessageenrollerbody,
  2546. * expirymessageenrolledsubject and expirymessageenrolledbody),
  2547. * - expiry_notification provider in db/messages.php,
  2548. * - upgrade code that sets default thresholds for existing courses (should be 1 day),
  2549. * - something that calls this method, such as cron.
  2550. *
  2551. * @param progress_trace $trace (accepts bool for backwards compatibility only)
  2552. */
  2553. public function send_expiry_notifications($trace) {
  2554. global $DB, $CFG;
  2555. $name = $this->get_name();
  2556. if (!enrol_is_enabled($name)) {
  2557. $trace->finished();
  2558. return;
  2559. }
  2560. // Unfortunately this may take a long time, it should not be interrupted,
  2561. // otherwise users get duplicate notification.
  2562. core_php_time_limit::raise();
  2563. raise_memory_limit(MEMORY_HUGE);
  2564. $expirynotifylast = $this->get_config('expirynotifylast', 0);
  2565. $expirynotifyhour = $this->get_config('expirynotifyhour');
  2566. if (is_null($expirynotifyhour)) {
  2567. debugging("send_expiry_notifications() in $name enrolment plugin needs expirynotifyhour setting");
  2568. $trace->finished();
  2569. return;
  2570. }
  2571. if (!($trace instanceof progress_trace)) {
  2572. $trace = $trace ? new text_progress_trace() : new null_progress_trace();
  2573. debugging('enrol_plugin::send_expiry_notifications() now expects progress_trace instance as parameter!', DEBUG_DEVELOPER);
  2574. }
  2575. $timenow = time();
  2576. $notifytime = usergetmidnight($timenow, $CFG->timezone) + ($expirynotifyhour * 3600);
  2577. if ($expirynotifylast > $notifytime) {
  2578. $trace->output($name.' enrolment expiry notifications were already sent today at '.userdate($expirynotifylast, '', $CFG->timezone).'.');
  2579. $trace->finished();
  2580. return;
  2581. } else if ($timenow < $notifytime) {
  2582. $trace->output($name.' enrolment expiry notifications will be sent at '.userdate($notifytime, '', $CFG->timezone).'.');
  2583. $trace->finished();
  2584. return;
  2585. }
  2586. $trace->output('Processing '.$name.' enrolment expiration notifications...');
  2587. // Notify users responsible for enrolment once every day.
  2588. $sql = "SELECT ue.*, e.expirynotify, e.notifyall, e.expirythreshold, e.courseid, c.fullname
  2589. FROM {user_enrolments} ue
  2590. JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :name AND e.expirynotify > 0 AND e.status = :enabled)
  2591. JOIN {course} c ON (c.id = e.courseid)
  2592. JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0 AND u.suspended = 0)
  2593. WHERE ue.status = :active AND ue.timeend > 0 AND ue.timeend > :now1 AND ue.timeend < (e.expirythreshold + :now2)
  2594. ORDER BY ue.enrolid ASC, u.lastname ASC, u.firstname ASC, u.id ASC";
  2595. $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'now1'=>$timenow, 'now2'=>$timenow, 'name'=>$name);
  2596. $rs = $DB->get_recordset_sql($sql, $params);
  2597. $lastenrollid = 0;
  2598. $users = array();
  2599. foreach($rs as $ue) {
  2600. if ($lastenrollid and $lastenrollid != $ue->enrolid) {
  2601. $this->notify_expiry_enroller($lastenrollid, $users, $trace);
  2602. $users = array();
  2603. }
  2604. $lastenrollid = $ue->enrolid;
  2605. $enroller = $this->get_enroller($ue->enrolid);
  2606. $context = context_course::instance($ue->courseid);
  2607. $user = $DB->get_record('user', array('id'=>$ue->userid));
  2608. $users[] = array('fullname'=>fullname($user, has_capability('moodle/site:viewfullnames', $context, $enroller)), 'timeend'=>$ue->timeend);
  2609. if (!$ue->notifyall) {
  2610. continue;
  2611. }
  2612. if ($ue->timeend - $ue->expirythreshold + 86400 < $timenow) {
  2613. // Notify enrolled users only once at the start of the threshold.
  2614. $trace->output("user $ue->userid was already notified that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
  2615. continue;
  2616. }
  2617. $this->notify_expiry_enrolled($user, $ue, $trace);
  2618. }
  2619. $rs->close();
  2620. if ($lastenrollid and $users) {
  2621. $this->notify_expiry_enroller($lastenrollid, $users, $trace);
  2622. }
  2623. $trace->output('...notification processing finished.');
  2624. $trace->finished();
  2625. $this->set_config('expirynotifylast', $timenow);
  2626. }
  2627. /**
  2628. * Returns the user who is responsible for enrolments for given instance.
  2629. *
  2630. * Override if plugin knows anybody better than admin.
  2631. *
  2632. * @param int $instanceid enrolment instance id
  2633. * @return stdClass user record
  2634. */
  2635. protected function get_enroller($instanceid) {
  2636. return get_admin();
  2637. }
  2638. /**
  2639. * Notify user about incoming expiration of their enrolment,
  2640. * it is called only if notification of enrolled users (aka students) is enabled in course.
  2641. *
  2642. * This is executed only once for each expiring enrolment right
  2643. * at the start of the expiration threshold.
  2644. *
  2645. * @param stdClass $user
  2646. * @param stdClass $ue
  2647. * @param progress_trace $trace
  2648. */
  2649. protected function notify_expiry_enrolled($user, $ue, progress_trace $trace) {
  2650. global $CFG;
  2651. $name = $this->get_name();
  2652. $oldforcelang = force_current_language($user->lang);
  2653. $enroller = $this->get_enroller($ue->enrolid);
  2654. $context = context_course::instance($ue->courseid);
  2655. $a = new stdClass();
  2656. $a->course = format_string($ue->fullname, true, array('context'=>$context));
  2657. $a->user = fullname($user, true);
  2658. $a->timeend = userdate($ue->timeend, '', $user->timezone);
  2659. $a->enroller = fullname($enroller, has_capability('moodle/site:viewfullnames', $context, $user));
  2660. $subject = get_string('expirymessageenrolledsubject', 'enrol_'.$name, $a);
  2661. $body = get_string('expirymessageenrolledbody', 'enrol_'.$name, $a);
  2662. $message = new \core\message\message();
  2663. $message->courseid = $ue->courseid;
  2664. $message->notification = 1;
  2665. $message->component = 'enrol_'.$name;
  2666. $message->name = 'expiry_notification';
  2667. $message->userfrom = $enroller;
  2668. $message->userto = $user;
  2669. $message->subject = $subject;
  2670. $message->fullmessage = $body;
  2671. $message->fullmessageformat = FORMAT_MARKDOWN;
  2672. $message->fullmessagehtml = markdown_to_html($body);
  2673. $message->smallmessage = $subject;
  2674. $message->contexturlname = $a->course;
  2675. $message->contexturl = (string)new moodle_url('/course/view.php', array('id'=>$ue->courseid));
  2676. if (message_send($message)) {
  2677. $trace->output("notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
  2678. } else {
  2679. $trace->output("error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone), 1);
  2680. }
  2681. force_current_language($oldforcelang);
  2682. }
  2683. /**
  2684. * Notify person responsible for enrolments that some user enrolments will be expired soon,
  2685. * it is called only if notification of enrollers (aka teachers) is enabled in course.
  2686. *
  2687. * This is called repeatedly every day for each course if there are any pending expiration
  2688. * in the expiration threshold.
  2689. *
  2690. * @param int $eid
  2691. * @param array $users
  2692. * @param progress_trace $trace
  2693. */
  2694. protected function notify_expiry_enroller($eid, $users, progress_trace $trace) {
  2695. global $DB;
  2696. $name = $this->get_name();
  2697. $instance = $DB->get_record('enrol', array('id'=>$eid, 'enrol'=>$name));
  2698. $context = context_course::instance($instance->courseid);
  2699. $course = $DB->get_record('course', array('id'=>$instance->courseid));
  2700. $enroller = $this->get_enroller($instance->id);
  2701. $admin = get_admin();
  2702. $oldforcelang = force_current_language($enroller->lang);
  2703. foreach($users as $key=>$info) {
  2704. $users[$key] = '* '.$info['fullname'].' - '.userdate($info['timeend'], '', $enroller->timezone);
  2705. }
  2706. $a = new stdClass();
  2707. $a->course = format_string($course->fullname, true, array('context'=>$context));
  2708. $a->threshold = get_string('numdays', '', $instance->expirythreshold / (60*60*24));
  2709. $a->users = implode("\n", $users);
  2710. $a->extendurl = (string)new moodle_url('/user/index.php', array('id'=>$instance->courseid));
  2711. $subject = get_string('expirymessageenrollersubject', 'enrol_'.$name, $a);
  2712. $body = get_string('expirymessageenrollerbody', 'enrol_'.$name, $a);
  2713. $message = new \core\message\message();
  2714. $message->courseid = $course->id;
  2715. $message->notification = 1;
  2716. $message->component = 'enrol_'.$name;
  2717. $message->name = 'expiry_notification';
  2718. $message->userfrom = $admin;
  2719. $message->userto = $enroller;
  2720. $message->subject = $subject;
  2721. $message->fullmessage = $body;
  2722. $message->fullmessageformat = FORMAT_MARKDOWN;
  2723. $message->fullmessagehtml = markdown_to_html($body);
  2724. $message->smallmessage = $subject;
  2725. $message->contexturlname = $a->course;
  2726. $message->contexturl = $a->extendurl;
  2727. if (message_send($message)) {
  2728. $trace->output("notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
  2729. } else {
  2730. $trace->output("error notifying user $enroller->id about all expiring $name enrolments in course $instance->courseid", 1);
  2731. }
  2732. force_current_language($oldforcelang);
  2733. }
  2734. /**
  2735. * Backup execution step hook to annotate custom fields.
  2736. *
  2737. * @param backup_enrolments_execution_step $step
  2738. * @param stdClass $enrol
  2739. */
  2740. public function backup_annotate_custom_fields(backup_enrolments_execution_step $step, stdClass $enrol) {
  2741. // Override as necessary to annotate custom fields in the enrol table.
  2742. }
  2743. /**
  2744. * Automatic enrol sync executed during restore.
  2745. * Useful for automatic sync by course->idnumber or course category.
  2746. * @param stdClass $course course record
  2747. */
  2748. public function restore_sync_course($course) {
  2749. // Override if necessary.
  2750. }
  2751. /**
  2752. * Restore instance and map settings.
  2753. *
  2754. * @param restore_enrolments_structure_step $step
  2755. * @param stdClass $data
  2756. * @param stdClass $course
  2757. * @param int $oldid
  2758. */
  2759. public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
  2760. // Do not call this from overridden methods, restore and set new id there.
  2761. $step->set_mapping('enrol', $oldid, 0);
  2762. }
  2763. /**
  2764. * Restore user enrolment.
  2765. *
  2766. * @param restore_enrolments_structure_step $step
  2767. * @param stdClass $data
  2768. * @param stdClass $instance
  2769. * @param int $oldinstancestatus
  2770. * @param int $userid
  2771. */
  2772. public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
  2773. // Override as necessary if plugin supports restore of enrolments.
  2774. }
  2775. /**
  2776. * Restore role assignment.
  2777. *
  2778. * @param stdClass $instance
  2779. * @param int $roleid
  2780. * @param int $userid
  2781. * @param int $contextid
  2782. */
  2783. public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
  2784. // No role assignment by default, override if necessary.
  2785. }
  2786. /**
  2787. * Restore user group membership.
  2788. * @param stdClass $instance
  2789. * @param int $groupid
  2790. * @param int $userid
  2791. */
  2792. public function restore_group_member($instance, $groupid, $userid) {
  2793. // Implement if you want to restore protected group memberships,
  2794. // usually this is not necessary because plugins should be able to recreate the memberships automatically.
  2795. }
  2796. /**
  2797. * Returns defaults for new instances.
  2798. * @since Moodle 3.1
  2799. * @return array
  2800. */
  2801. public function get_instance_defaults() {
  2802. return array();
  2803. }
  2804. /**
  2805. * Validate a list of parameter names and types.
  2806. * @since Moodle 3.1
  2807. *
  2808. * @param array $data array of ("fieldname"=>value) of submitted data
  2809. * @param array $rules array of ("fieldname"=>PARAM_X types - or "fieldname"=>array( list of valid options )
  2810. * @return array of "element_name"=>"error_description" if there are errors,
  2811. * or an empty array if everything is OK.
  2812. */
  2813. public function validate_param_types($data, $rules) {
  2814. $errors = array();
  2815. $invalidstr = get_string('invaliddata', 'error');
  2816. foreach ($rules as $fieldname => $rule) {
  2817. if (is_array($rule)) {
  2818. if (!in_array($data[$fieldname], $rule)) {
  2819. $errors[$fieldname] = $invalidstr;
  2820. }
  2821. } else {
  2822. if ($data[$fieldname] != clean_param($data[$fieldname], $rule)) {
  2823. $errors[$fieldname] = $invalidstr;
  2824. }
  2825. }
  2826. }
  2827. return $errors;
  2828. }
  2829. }