PageRenderTime 28ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 1ms

/enrol/category/locallib.php

https://github.com/kpike/moodle
PHP | 347 lines | 241 code | 47 blank | 59 comment | 20 complexity | 521dfced49f83d28c5cb51144dae4e96 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Local stuff for category enrolment plugin.
  18. *
  19. * @package enrol
  20. * @subpackage category
  21. * @copyright 2010 Petr Skoda {@link http://skodak.org}
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. /**
  26. * Event handler for category enrolment plugin.
  27. *
  28. * We try to keep everything in sync via listening to events,
  29. * it may fail sometimes, so we always do a full sync in cron too.
  30. */
  31. class enrol_category_handler {
  32. public function role_assigned($ra) {
  33. global $DB;
  34. if (!enrol_is_enabled('category')) {
  35. return true;
  36. }
  37. //only category level roles are interesting
  38. $parentcontext = get_context_instance_by_id($ra->contextid);
  39. if ($parentcontext->contextlevel != CONTEXT_COURSECAT) {
  40. return true;
  41. }
  42. // make sure the role is to be actually synchronised
  43. // please note we are ignoring overrides of the synchronised capability (for performance reasons in full sync)
  44. $syscontext = get_context_instance(CONTEXT_SYSTEM);
  45. if (!$DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$ra->roleid, 'capability'=>'enrol/category:synchronised', 'permission'=>CAP_ALLOW))) {
  46. return true;
  47. }
  48. // add necessary enrol instances
  49. $plugin = enrol_get_plugin('category');
  50. $sql = "SELECT c.*
  51. FROM {course} c
  52. JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
  53. LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
  54. WHERE e.id IS NULL";
  55. $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%');
  56. $rs = $DB->get_recordset_sql($sql, $params);
  57. foreach ($rs as $course) {
  58. $plugin->add_instance($course);
  59. }
  60. $rs->close();
  61. // now look for missing enrols
  62. $sql = "SELECT e.*
  63. FROM {course} c
  64. JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
  65. JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
  66. LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
  67. WHERE ue.id IS NULL";
  68. $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid);
  69. $rs = $DB->get_recordset_sql($sql, $params);
  70. foreach ($rs as $instance) {
  71. $plugin->enrol_user($instance, $ra->userid, null, $ra->timemodified);
  72. }
  73. $rs->close();
  74. return true;
  75. }
  76. public function role_unassigned($ra) {
  77. global $DB;
  78. if (!enrol_is_enabled('category')) {
  79. return true;
  80. }
  81. // only category level roles are interesting
  82. $parentcontext = get_context_instance_by_id($ra->contextid);
  83. if ($parentcontext->contextlevel != CONTEXT_COURSECAT) {
  84. return true;
  85. }
  86. // now this is going to be a bit slow, take all enrolments in child courses and verify each separately
  87. $syscontext = get_context_instance(CONTEXT_SYSTEM);
  88. $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext);
  89. $plugin = enrol_get_plugin('category');
  90. $sql = "SELECT e.*
  91. FROM {course} c
  92. JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
  93. JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
  94. JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)";
  95. $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid);
  96. $rs = $DB->get_recordset_sql($sql, $params);
  97. list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
  98. $params['userid'] = $ra->userid;
  99. foreach ($rs as $instance) {
  100. $coursecontext = get_context_instance(CONTEXT_COURSE, $instance->courseid);
  101. $contextids = get_parent_contexts($coursecontext);
  102. array_pop($contextids); // remove system context, we are interested in categories only
  103. list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c');
  104. $params = array_merge($params, $contextparams);
  105. $sql = "SELECT ra.id
  106. FROM {role_assignments} ra
  107. WHERE ra.userid = :userid AND ra.contextid $contextids AND ra.roleid $roleids";
  108. if (!$DB->record_exists_sql($sql, $params)) {
  109. // user does not have any interesting role in any parent context, let's unenrol
  110. $plugin->unenrol_user($instance, $ra->userid);
  111. }
  112. }
  113. $rs->close();
  114. return true;
  115. }
  116. }
  117. /**
  118. * Sync all category enrolments in one course
  119. * @param int $courseid course id
  120. * @return void
  121. */
  122. function enrol_category_sync_course($course) {
  123. global $DB;
  124. if (!enrol_is_enabled('category')) {
  125. return;
  126. }
  127. $plugin = enrol_get_plugin('category');
  128. $syscontext = get_context_instance(CONTEXT_SYSTEM);
  129. $roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext);
  130. if (!$roles) {
  131. //nothing to sync, so remove the instance completely if exists
  132. if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
  133. foreach ($instances as $instance) {
  134. $plugin->delete_instance($instance);
  135. }
  136. }
  137. return;
  138. }
  139. // first find out if any parent category context contains interesting role assignments
  140. $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
  141. $contextids = get_parent_contexts($coursecontext);
  142. array_pop($contextids); // remove system context, we are interested in categories only
  143. list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
  144. list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c');
  145. $params = array_merge($params, $contextparams);
  146. $params['courseid'] = $course->id;
  147. $sql = "SELECT 'x'
  148. FROM {role_assignments}
  149. WHERE roleid $roleids AND contextid $contextids";
  150. if (!$DB->record_exists_sql($sql, $params)) {
  151. if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
  152. // should be max one instance, but anyway
  153. foreach ($instances as $instance) {
  154. $plugin->delete_instance($instance);
  155. }
  156. }
  157. return;
  158. }
  159. // make sure the enrol instance exists - there should be always only one instance
  160. $delinstances = array();
  161. if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol'=>'category'))) {
  162. $instance = array_shift($instances);
  163. $delinstances = $instances;
  164. } else {
  165. $i = $plugin->add_instance($course);
  166. $instance = $DB->get_record('enrol', array('id'=>$i));
  167. }
  168. // add new enrolments
  169. $sql = "SELECT ra.userid, ra.estart
  170. FROM (SELECT xra.userid, MIN(xra.timemodified) AS estart
  171. FROM {role_assignments} xra
  172. WHERE xra.roleid $roleids AND xra.contextid $contextids
  173. GROUP BY xra.userid
  174. ) ra
  175. LEFT JOIN {user_enrolments} ue ON (ue.enrolid = :instanceid AND ue.userid = ra.userid)
  176. WHERE ue.id IS NULL";
  177. $params['instanceid'] = $instance->id;
  178. $rs = $DB->get_recordset_sql($sql, $params);
  179. foreach ($rs as $ra) {
  180. $plugin->enrol_user($instance, $ra->userid, null, $ra->estart);
  181. }
  182. $rs->close();
  183. // remove unwanted enrolments
  184. $sql = "SELECT DISTINCT ue.userid
  185. FROM {user_enrolments} ue
  186. LEFT JOIN {role_assignments} ra ON (ra.roleid $roleids AND ra.contextid $contextids AND ra.userid = ue.userid)
  187. WHERE ue.enrolid = :instanceid AND ra.id IS NULL";
  188. $rs = $DB->get_recordset_sql($sql, $params);
  189. foreach ($rs as $ra) {
  190. $plugin->unenrol_user($instance, $ra->userid);
  191. }
  192. $rs->close();
  193. if ($delinstances) {
  194. // we have to do this as the last step in order to prevent temporary unenrolment
  195. foreach ($delinstances as $delinstance) {
  196. $plugin->delete_instance($delinstance);
  197. }
  198. }
  199. }
  200. function enrol_category_sync_full() {
  201. global $DB;
  202. if (!enrol_is_enabled('category')) {
  203. return;
  204. }
  205. // we may need a lot of time here
  206. @set_time_limit(0);
  207. $plugin = enrol_get_plugin('category');
  208. $syscontext = get_context_instance(CONTEXT_SYSTEM);
  209. // any interesting roles worth synchronising?
  210. if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) {
  211. // yay, nothing to do, so let's remove all leftovers
  212. if ($instances = $DB->get_records('enrol', array('enrol'=>'category'))) {
  213. foreach ($instances as $instance) {
  214. $plugin->delete_instance($instance);
  215. }
  216. }
  217. return;
  218. }
  219. list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
  220. $params['courselevel'] = CONTEXT_COURSE;
  221. $params['catlevel'] = CONTEXT_COURSECAT;
  222. // first of all add necessary enrol instances to all courses
  223. $parentcat = $DB->sql_concat("cat.path", "'/%'");
  224. // need whole course records to be used by add_instance(), use inner view (ci) to
  225. // get distinct records only.
  226. // TODO: Moodle 2.1. Improve enrol API to accept courseid / courserec
  227. $sql = "SELECT c.*
  228. FROM {course} c
  229. JOIN (
  230. SELECT DISTINCT c.id
  231. FROM {course} c
  232. JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel)
  233. JOIN (SELECT DISTINCT cctx.path
  234. FROM {course_categories} cc
  235. JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
  236. JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
  237. ) cat ON (ctx.path LIKE $parentcat)
  238. LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
  239. WHERE e.id IS NULL) ci ON (c.id = ci.id)";
  240. $rs = $DB->get_recordset_sql($sql, $params);
  241. foreach($rs as $course) {
  242. $plugin->add_instance($course);
  243. }
  244. $rs->close();
  245. // now look for courses that do not have any interesting roles in parent contexts,
  246. // but still have the instance and delete them
  247. $sql = "SELECT e.*
  248. FROM {enrol} e
  249. JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
  250. LEFT JOIN (SELECT DISTINCT cctx.path
  251. FROM {course_categories} cc
  252. JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
  253. JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
  254. ) cat ON (ctx.path LIKE $parentcat)
  255. WHERE e.enrol = 'category' AND cat.path IS NULL";
  256. $rs = $DB->get_recordset_sql($sql, $params);
  257. foreach($rs as $instance) {
  258. $plugin->delete_instance($instance);
  259. }
  260. $rs->close();
  261. // add missing enrolments
  262. $sql = "SELECT e.*, cat.userid, cat.estart
  263. FROM {enrol} e
  264. JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
  265. JOIN (SELECT cctx.path, ra.userid, MIN(ra.timemodified) AS estart
  266. FROM {course_categories} cc
  267. JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
  268. JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
  269. GROUP BY cctx.path, ra.userid
  270. ) cat ON (ctx.path LIKE $parentcat)
  271. LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cat.userid)
  272. WHERE e.enrol = 'category' AND ue.id IS NULL";
  273. $rs = $DB->get_recordset_sql($sql, $params);
  274. foreach($rs as $instance) {
  275. $userid = $instance->userid;
  276. $estart = $instance->estart;
  277. unset($instance->userid);
  278. unset($instance->estart);
  279. $plugin->enrol_user($instance, $userid, null, $estart);
  280. }
  281. $rs->close();
  282. // remove stale enrolments
  283. $sql = "SELECT e.*, ue.userid
  284. FROM {enrol} e
  285. JOIN {context} ctx ON (ctx.instanceid = e.courseid AND ctx.contextlevel = :courselevel)
  286. JOIN {user_enrolments} ue ON (ue.enrolid = e.id)
  287. LEFT JOIN (SELECT DISTINCT cctx.path, ra.userid
  288. FROM {course_categories} cc
  289. JOIN {context} cctx ON (cctx.instanceid = cc.id AND cctx.contextlevel = :catlevel)
  290. JOIN {role_assignments} ra ON (ra.contextid = cctx.id AND ra.roleid $roleids)
  291. ) cat ON (ctx.path LIKE $parentcat AND cat.userid = ue.userid)
  292. WHERE e.enrol = 'category' AND cat.userid IS NULL";
  293. $rs = $DB->get_recordset_sql($sql, $params);
  294. foreach($rs as $instance) {
  295. $userid = $instance->userid;
  296. unset($instance->userid);
  297. $plugin->unenrol_user($instance, $userid);
  298. }
  299. $rs->close();
  300. }