Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Local stuff for category enrolment plugin.
  19   *
  20   * @package    enrol_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  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  
  28  /**
  29   * Event handler for category enrolment plugin.
  30   *
  31   * We try to keep everything in sync via listening to events,
  32   * it may fail sometimes, so we always do a full sync in cron too.
  33   */
  34  class enrol_category_observer {
  35      /**
  36       * Triggered when user is assigned a new role.
  37       *
  38       * @param \core\event\role_assigned $event
  39       */
  40      public static function role_assigned(\core\event\role_assigned $event) {
  41          global $DB;
  42  
  43          if (!enrol_is_enabled('category')) {
  44              return;
  45          }
  46  
  47          $ra = new stdClass();
  48          $ra->roleid = $event->objectid;
  49          $ra->userid = $event->relateduserid;
  50          $ra->contextid = $event->contextid;
  51  
  52          //only category level roles are interesting
  53          $parentcontext = context::instance_by_id($ra->contextid);
  54          if ($parentcontext->contextlevel != CONTEXT_COURSECAT) {
  55              return;
  56          }
  57  
  58          // Make sure the role is to be actually synchronised,
  59          // please note we are ignoring overrides of the synchronised capability (for performance reasons in full sync).
  60          $syscontext = context_system::instance();
  61          if (!$DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$ra->roleid, 'capability'=>'enrol/category:synchronised', 'permission'=>CAP_ALLOW))) {
  62              return;
  63          }
  64  
  65          // Add necessary enrol instances.
  66          $plugin = enrol_get_plugin('category');
  67          $sql = "SELECT c.*
  68                    FROM {course} c
  69                    JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
  70               LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
  71                   WHERE e.id IS NULL";
  72          $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%');
  73          $rs = $DB->get_recordset_sql($sql, $params);
  74          foreach ($rs as $course) {
  75              $plugin->add_instance($course);
  76          }
  77          $rs->close();
  78  
  79          // Now look for missing enrolments.
  80          $sql = "SELECT e.*
  81                    FROM {course} c
  82                    JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
  83                    JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
  84               LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)
  85                   WHERE ue.id IS NULL";
  86          $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid);
  87          $rs = $DB->get_recordset_sql($sql, $params);
  88          foreach ($rs as $instance) {
  89              $plugin->enrol_user($instance, $ra->userid, null, time());
  90          }
  91          $rs->close();
  92      }
  93  
  94      /**
  95       * Triggered when user role is unassigned.
  96       *
  97       * @param \core\event\role_unassigned $event
  98       */
  99      public static function role_unassigned(\core\event\role_unassigned $event) {
 100          global $DB;
 101  
 102          if (!enrol_is_enabled('category')) {
 103              return;
 104          }
 105  
 106          $ra = new stdClass();
 107          $ra->userid = $event->relateduserid;
 108          $ra->contextid = $event->contextid;
 109  
 110          // only category level roles are interesting
 111          $parentcontext = context::instance_by_id($ra->contextid);
 112          if ($parentcontext->contextlevel != CONTEXT_COURSECAT) {
 113              return;
 114          }
 115  
 116          // Now this is going to be a bit slow, take all enrolments in child courses and verify each separately.
 117          $syscontext = context_system::instance();
 118          if (!$roles = get_roles_with_capability('enrol/category:synchronised', CAP_ALLOW, $syscontext)) {
 119              return;
 120          }
 121  
 122          $plugin = enrol_get_plugin('category');
 123  
 124          $sql = "SELECT e.*
 125                    FROM {course} c
 126                    JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :courselevel AND ctx.path LIKE :match)
 127                    JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'category')
 128                    JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = :userid)";
 129          $params = array('courselevel'=>CONTEXT_COURSE, 'match'=>$parentcontext->path.'/%', 'userid'=>$ra->userid);
 130          $rs = $DB->get_recordset_sql($sql, $params);
 131  
 132          list($roleids, $params) = $DB->get_in_or_equal(array_keys($roles), SQL_PARAMS_NAMED, 'r');
 133          $params['userid'] = $ra->userid;
 134  
 135          foreach ($rs as $instance) {
 136              $coursecontext = context_course::instance($instance->courseid);
 137              $contextids = $coursecontext->get_parent_context_ids();
 138              array_pop($contextids); // Remove system context, we are interested in categories only.
 139  
 140              list($contextids, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'c');
 141              $params = array_merge($params, $contextparams);
 142  
 143              $sql = "SELECT ra.id
 144                        FROM {role_assignments} ra
 145                       WHERE ra.userid = :userid AND ra.contextid $contextids AND ra.roleid $roleids";
 146              if (!$DB->record_exists_sql($sql, $params)) {
 147                  // User does not have any interesting role in any parent context, let's unenrol.
 148                  $plugin->unenrol_user($instance, $ra->userid);
 149              }
 150          }
 151          $rs->close();
 152      }
 153  }