Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.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 meta course enrolment plugin.
      19   *
      20   * @package    enrol_meta
      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 meta 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_meta_handler {
      35  
      36      /**
      37       * Synchronise meta enrolments of this user in this course
      38       * @static
      39       * @param int $courseid
      40       * @param int $userid
      41       * @return void
      42       */
      43      protected static function sync_course_instances($courseid, $userid) {
      44          global $DB;
      45  
      46          static $preventrecursion = false;
      47  
      48          // does anything want to sync with this parent?
      49          if (!$enrols = $DB->get_records('enrol', array('customint1'=>$courseid, 'enrol'=>'meta'), 'id ASC')) {
      50              return;
      51          }
      52  
      53          if ($preventrecursion) {
      54              return;
      55          }
      56  
      57          $preventrecursion = true;
      58  
      59          try {
      60              foreach ($enrols as $enrol) {
      61                  self::sync_with_parent_course($enrol, $userid);
      62              }
      63          } catch (Exception $e) {
      64              $preventrecursion = false;
      65              throw $e;
      66          }
      67  
      68          $preventrecursion = false;
      69      }
      70  
      71      /**
      72       * Synchronise user enrolments in given instance as fast as possible.
      73       *
      74       * All roles are removed if the meta plugin disabled.
      75       *
      76       * @static
      77       * @param stdClass $instance
      78       * @param int $userid
      79       * @return void
      80       */
      81      protected static function sync_with_parent_course(stdClass $instance, $userid) {
      82          global $DB, $CFG;
      83          require_once($CFG->dirroot . '/group/lib.php');
      84  
      85          $plugin = enrol_get_plugin('meta');
      86  
      87          if ($instance->customint1 == $instance->courseid) {
      88              // can not sync with self!!!
      89              return;
      90          }
      91  
      92          $context = context_course::instance($instance->courseid);
      93  
      94          // list of enrolments in parent course (we ignore meta enrols in parents completely)
      95          list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
      96          $params['userid'] = $userid;
      97          $params['parentcourse'] = $instance->customint1;
      98          $sql = "SELECT ue.*, e.status AS enrolstatus
      99                    FROM {user_enrolments} ue
     100                    JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol <> 'meta' AND e.courseid = :parentcourse AND e.enrol $enabled)
     101                   WHERE ue.userid = :userid";
     102          $parentues = $DB->get_records_sql($sql, $params);
     103          // current enrolments for this instance
     104          $ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid));
     105  
     106          // first deal with users that are not enrolled in parent
     107          if (empty($parentues)) {
     108              self::user_not_supposed_to_be_here($instance, $ue, $context, $plugin);
     109              return;
     110          }
     111  
     112          if (!$parentcontext = context_course::instance($instance->customint1, IGNORE_MISSING)) {
     113              // Weird, we should not get here.
     114              return;
     115          }
     116  
     117          $skiproles = $plugin->get_config('nosyncroleids', '');
     118          $skiproles = empty($skiproles) ? array() : explode(',', $skiproles);
     119          $syncall   = $plugin->get_config('syncall', 1);
     120  
     121          // roles in parent course (meta enrols must be ignored!)
     122          $parentroles = array();
     123          list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
     124          $params['contextid'] = $parentcontext->id;
     125          $params['userid'] = $userid;
     126          $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
     127          foreach($DB->get_records_select('role_assignments', $select, $params) as $ra) {
     128              $parentroles[$ra->roleid] = $ra->roleid;
     129          }
     130  
     131          // roles from this instance
     132          $roles = array();
     133          $ras = $DB->get_records('role_assignments', array('contextid'=>$context->id, 'userid'=>$userid, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
     134          foreach($ras as $ra) {
     135              $roles[$ra->roleid] = $ra->roleid;
     136          }
     137          unset($ras);
     138  
     139          // do we want users without roles?
     140          if (!$syncall and empty($parentroles)) {
     141              self::user_not_supposed_to_be_here($instance, $ue, $context, $plugin);
     142              return;
     143          }
     144  
     145          // Is parent enrol active? Find minimum timestart and maximum timeend of all active enrolments.
     146          $parentstatus = ENROL_USER_SUSPENDED;
     147          $parenttimeend = null;
     148          $parenttimestart = null;
     149          foreach ($parentues as $pue) {
     150              if ($pue->status == ENROL_USER_ACTIVE && $pue->enrolstatus == ENROL_INSTANCE_ENABLED) {
     151                  $parentstatus = ENROL_USER_ACTIVE;
     152                  if ($parenttimeend === null || $pue->timeend == 0 || ($parenttimeend && $parenttimeend < $pue->timeend)) {
     153                      $parenttimeend = $pue->timeend;
     154                  }
     155                  if ($parenttimestart === null || $parenttimestart > $pue->timestart) {
     156                      $parenttimestart = $pue->timestart;
     157                  }
     158              }
     159          }
     160  
     161          // Enrol user if not enrolled yet or fix status/timestart/timeend. Use the minimum timestart and maximum timeend found above.
     162          if ($ue) {
     163              if ($parentstatus != $ue->status ||
     164                      ($parentstatus == ENROL_USER_ACTIVE && ($parenttimestart != $ue->timestart || $parenttimeend != $ue->timeend))) {
     165                  $plugin->update_user_enrol($instance, $userid, $parentstatus, $parenttimestart, $parenttimeend);
     166                  $ue->status = $parentstatus;
     167                  $ue->timestart = $parenttimestart;
     168                  $ue->timeend = $parenttimeend;
     169              }
     170          } else {
     171              $plugin->enrol_user($instance, $userid, NULL, (int)$parenttimestart, (int)$parenttimeend, $parentstatus);
     172              $ue = new stdClass();
     173              $ue->userid = $userid;
     174              $ue->enrolid = $instance->id;
     175              $ue->status = $parentstatus;
     176              if ($instance->customint2) {
     177                  groups_add_member($instance->customint2, $userid, 'enrol_meta', $instance->id);
     178              }
     179          }
     180  
     181          $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
     182  
     183          // Only active users in enabled instances are supposed to have roles (we can reassign the roles any time later).
     184          if ($ue->status != ENROL_USER_ACTIVE or $instance->status != ENROL_INSTANCE_ENABLED or
     185                  ($parenttimeend and $parenttimeend < time()) or ($parenttimestart > time())) {
     186              if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
     187                  // Always keep the roles.
     188              } else if ($roles) {
     189                  // This will only unassign roles that were assigned in this enrolment method, leaving all manual role assignments intact.
     190                  role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
     191              }
     192              return;
     193          }
     194  
     195          // add new roles
     196          foreach ($parentroles as $rid) {
     197              if (!isset($roles[$rid])) {
     198                  role_assign($rid, $userid, $context->id, 'enrol_meta', $instance->id);
     199              }
     200          }
     201  
     202          if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
     203              // Always keep the roles.
     204              return;
     205          }
     206  
     207          // remove roles
     208          foreach ($roles as $rid) {
     209              if (!isset($parentroles[$rid])) {
     210                  role_unassign($rid, $userid, $context->id, 'enrol_meta', $instance->id);
     211              }
     212          }
     213      }
     214  
     215      /**
     216       * Deal with users that are not supposed to be enrolled via this instance
     217       * @static
     218       * @param stdClass $instance
     219       * @param stdClass $ue
     220       * @param context_course $context
     221       * @param enrol_meta $plugin
     222       * @return void
     223       */
     224      protected static function user_not_supposed_to_be_here($instance, $ue, context_course $context, $plugin) {
     225          if (!$ue) {
     226              // Not enrolled yet - simple!
     227              return;
     228          }
     229  
     230          $userid = $ue->userid;
     231          $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
     232  
     233          if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
     234              // Purges grades, group membership, preferences, etc. - admins were warned!
     235              $plugin->unenrol_user($instance, $userid);
     236  
     237          } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
     238              if ($ue->status != ENROL_USER_SUSPENDED) {
     239                  $plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
     240              }
     241  
     242          } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
     243              if ($ue->status != ENROL_USER_SUSPENDED) {
     244                  $plugin->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
     245              }
     246              role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
     247  
     248          } else {
     249              debugging('Unknown unenrol action '.$unenrolaction);
     250          }
     251      }
     252  }
     253  
     254  /**
     255   * Sync all meta course links.
     256   *
     257   * @param int $courseid one course, empty mean all
     258   * @param bool $verbose verbose CLI output
     259   * @return int 0 means ok, 1 means error, 2 means plugin disabled
     260   */
     261  function enrol_meta_sync($courseid = NULL, $verbose = false) {
     262      global $CFG, $DB;
     263      require_once("{$CFG->dirroot}/group/lib.php");
     264  
     265      // purge all roles if meta sync disabled, those can be recreated later here in cron
     266      if (!enrol_is_enabled('meta')) {
     267          if ($verbose) {
     268              mtrace('Meta sync plugin is disabled, unassigning all plugin roles and stopping.');
     269          }
     270          role_unassign_all(array('component'=>'enrol_meta'));
     271          return 2;
     272      }
     273  
     274      // unfortunately this may take a long time, execution can be interrupted safely
     275      core_php_time_limit::raise();
     276      raise_memory_limit(MEMORY_HUGE);
     277  
     278      if ($verbose) {
     279          mtrace('Starting user enrolment synchronisation...');
     280      }
     281  
     282      $instances = array(); // cache instances
     283  
     284      $meta = enrol_get_plugin('meta');
     285  
     286      $unenrolaction = $meta->get_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
     287      $skiproles     = $meta->get_config('nosyncroleids', '');
     288      $skiproles     = empty($skiproles) ? array() : explode(',', $skiproles);
     289      $syncall       = $meta->get_config('syncall', 1);
     290  
     291      $allroles = get_all_roles();
     292  
     293  
     294      // Iterate through all not enrolled yet users. For each active enrolment of each user find the minimum
     295      // enrolment startdate and maximum enrolment enddate.
     296      // This SQL relies on the fact that ENROL_USER_ACTIVE < ENROL_USER_SUSPENDED
     297      // and ENROL_INSTANCE_ENABLED < ENROL_INSTANCE_DISABLED. Condition "pue.status + pe.status = 0" means
     298      // that enrolment is active. When MIN(pue.status + pe.status)=0 it means there exists an active
     299      // enrolment.
     300      $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     301      list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
     302      $params['courseid'] = $courseid;
     303      $sql = "SELECT pue.userid, e.id AS enrolid, MIN(pue.status + pe.status) AS status,
     304                        MIN(CASE WHEN (pue.status + pe.status = 0) THEN pue.timestart ELSE 9999999999 END) AS timestart,
     305                        MAX(CASE WHEN (pue.status + pe.status = 0) THEN
     306                                  (CASE WHEN pue.timeend = 0 THEN 9999999999 ELSE pue.timeend END)
     307                                  ELSE 0 END) AS timeend
     308                FROM {user_enrolments} pue
     309                JOIN {enrol} pe ON (pe.id = pue.enrolid AND pe.enrol <> 'meta' AND pe.enrol $enabled)
     310                JOIN {enrol} e ON (e.customint1 = pe.courseid AND e.enrol = 'meta' AND e.status = :enrolstatus $onecourse)
     311                JOIN {user} u ON (u.id = pue.userid AND u.deleted = 0)
     312           LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = pue.userid)
     313               WHERE ue.id IS NULL
     314               GROUP BY pue.userid, e.id";
     315      $params['enrolstatus'] = ENROL_INSTANCE_ENABLED;
     316  
     317      $rs = $DB->get_recordset_sql($sql, $params);
     318      foreach($rs as $ue) {
     319          if (!isset($instances[$ue->enrolid])) {
     320              $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
     321          }
     322          $instance = $instances[$ue->enrolid];
     323  
     324          if (!$syncall) {
     325              // this may be slow if very many users are ignored in sync
     326              $parentcontext = context_course::instance($instance->customint1);
     327              list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
     328              $params['contextid'] = $parentcontext->id;
     329              $params['userid'] = $ue->userid;
     330              $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
     331              if (!$DB->record_exists_select('role_assignments', $select, $params)) {
     332                  // bad luck, this user does not have any role we want in parent course
     333                  if ($verbose) {
     334                      mtrace("  skipping enrolling: $ue->userid ==> $instance->courseid (user without role)");
     335                  }
     336                  continue;
     337              }
     338          }
     339  
     340          // So now we have aggregated values that we will use for the meta enrolment status, timeend and timestart.
     341          // Again, we use the fact that active=0 and disabled/suspended=1. Only when MIN(pue.status + pe.status)=0 the enrolment is active:
     342          $ue->status = ($ue->status == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
     343          // Timeend 9999999999 was used instead of 0 in the "MAX()" function:
     344          $ue->timeend = ($ue->timeend == 9999999999) ? 0 : (int)$ue->timeend;
     345          // Timestart 9999999999 is only possible when there are no active enrolments:
     346          $ue->timestart = ($ue->timestart == 9999999999) ? 0 : (int)$ue->timestart;
     347  
     348          $meta->enrol_user($instance, $ue->userid, null, $ue->timestart, $ue->timeend, $ue->status);
     349          if ($instance->customint2) {
     350              groups_add_member($instance->customint2, $ue->userid, 'enrol_meta', $instance->id);
     351          }
     352          if ($verbose) {
     353              mtrace("  enrolling: $ue->userid ==> $instance->courseid");
     354          }
     355      }
     356      $rs->close();
     357  
     358  
     359      // unenrol as necessary - ignore enabled flag, we want to get rid of existing enrols in any case
     360      $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     361      list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
     362      $params['courseid'] = $courseid;
     363      $sql = "SELECT ue.*
     364                FROM {user_enrolments} ue
     365                JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
     366           LEFT JOIN ({user_enrolments} xpue
     367                        JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta' AND xpe.enrol $enabled)
     368                     ) ON (xpe.courseid = e.customint1 AND xpue.userid = ue.userid)
     369               WHERE xpue.userid IS NULL";
     370      $rs = $DB->get_recordset_sql($sql, $params);
     371      foreach($rs as $ue) {
     372          if (!isset($instances[$ue->enrolid])) {
     373              $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
     374          }
     375          $instance = $instances[$ue->enrolid];
     376  
     377          if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
     378              $meta->unenrol_user($instance, $ue->userid);
     379              if ($verbose) {
     380                  mtrace("  unenrolling: $ue->userid ==> $instance->courseid");
     381              }
     382  
     383          } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND) {
     384              if ($ue->status != ENROL_USER_SUSPENDED) {
     385                  $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
     386                  if ($verbose) {
     387                      mtrace("  suspending: $ue->userid ==> $instance->courseid");
     388                  }
     389              }
     390  
     391          } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
     392              if ($ue->status != ENROL_USER_SUSPENDED) {
     393                  $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
     394                  $context = context_course::instance($instance->courseid);
     395                  role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_meta', 'itemid'=>$instance->id));
     396                  if ($verbose) {
     397                      mtrace("  suspending and removing all roles: $ue->userid ==> $instance->courseid");
     398                  }
     399              }
     400          }
     401      }
     402      $rs->close();
     403  
     404  
     405      // Update status - meta enrols are ignored to avoid recursion.
     406      // Note the trick here is that the active enrolment and instance constants have value 0.
     407      $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     408      list($enabled, $params) = $DB->get_in_or_equal(explode(',', $CFG->enrol_plugins_enabled), SQL_PARAMS_NAMED, 'e');
     409      $params['courseid'] = $courseid;
     410      // The query builds a a list of all the non-meta enrolments that are on courses (the children) that are linked to by a meta
     411      // enrolment, it then groups them by the course that linked to them (the parents).
     412      //
     413      // It will only return results where the there is a difference between the status of the parent and the lowest status
     414      // of the children (remember that 0 is active, any other status is some form of inactive), or the time the earliest non-zero
     415      // start time of a child is different to the parent, or the longest effective end date has changed.
     416      //
     417      // The last two case statements in the HAVING clause are designed to ignore any inactive child records when calculating
     418      // the start and end time.
     419      $sql = "SELECT ue.userid, ue.enrolid,
     420                     MIN(xpue.status + xpe.status) AS pstatus,
     421                     MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE 9999999999 END) AS ptimestart,
     422                     MAX(CASE WHEN (xpue.status + xpe.status = 0) THEN
     423                                   (CASE WHEN xpue.timeend = 0 THEN 9999999999 ELSE xpue.timeend END)
     424                              ELSE 0 END) AS ptimeend
     425                FROM {user_enrolments} ue
     426                JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
     427                JOIN {user_enrolments} xpue ON (xpue.userid = ue.userid)
     428                JOIN {enrol} xpe ON (xpe.id = xpue.enrolid AND xpe.enrol <> 'meta'
     429                     AND xpe.enrol $enabled AND xpe.courseid = e.customint1)
     430            GROUP BY ue.userid, ue.enrolid
     431              HAVING (MIN(xpue.status + xpe.status) = 0 AND MIN(ue.status) > 0)
     432                     OR (MIN(xpue.status + xpe.status) > 0 AND MIN(ue.status) = 0)
     433                     OR ((CASE WHEN
     434                                    MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE 9999999999 END) = 9999999999
     435                               THEN 0
     436                               ELSE
     437                                    MIN(CASE WHEN (xpue.status + xpe.status = 0) THEN xpue.timestart ELSE 9999999999 END)
     438                                END) <> MIN(ue.timestart))
     439                     OR ((CASE
     440                           WHEN MAX(CASE WHEN (xpue.status + xpe.status = 0)
     441                                         THEN (CASE WHEN xpue.timeend = 0 THEN 9999999999 ELSE xpue.timeend END)
     442                                         ELSE 0 END) = 9999999999
     443                           THEN 0 ELSE MAX(CASE WHEN (xpue.status + xpe.status = 0)
     444                                                THEN (CASE WHEN xpue.timeend = 0 THEN 9999999999 ELSE xpue.timeend END)
     445                                                ELSE 0 END)
     446                            END) <> MAX(ue.timeend))";
     447      $rs = $DB->get_recordset_sql($sql, $params);
     448      foreach($rs as $ue) {
     449          if (!isset($instances[$ue->enrolid])) {
     450              $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
     451          }
     452          $instance = $instances[$ue->enrolid];
     453          $ue->pstatus = ($ue->pstatus == ENROL_USER_ACTIVE + ENROL_INSTANCE_ENABLED) ? ENROL_USER_ACTIVE : ENROL_USER_SUSPENDED;
     454          $ue->ptimeend = ($ue->ptimeend == 9999999999) ? 0 : (int)$ue->ptimeend;
     455          $ue->ptimestart = ($ue->ptimestart == 9999999999) ? 0 : (int)$ue->ptimestart;
     456  
     457          if ($ue->pstatus == ENROL_USER_ACTIVE and (!$ue->ptimeend || $ue->ptimeend > time())
     458                  and !$syncall and $unenrolaction != ENROL_EXT_REMOVED_UNENROL) {
     459              // this may be slow if very many users are ignored in sync
     460              $parentcontext = context_course::instance($instance->customint1);
     461              list($ignoreroles, $params) = $DB->get_in_or_equal($skiproles, SQL_PARAMS_NAMED, 'ri', false, -1);
     462              $params['contextid'] = $parentcontext->id;
     463              $params['userid'] = $ue->userid;
     464              $select = "contextid = :contextid AND userid = :userid AND component <> 'enrol_meta' AND roleid $ignoreroles";
     465              if (!$DB->record_exists_select('role_assignments', $select, $params)) {
     466                  // bad luck, this user does not have any role we want in parent course
     467                  if ($verbose) {
     468                      mtrace("  skipping unsuspending: $ue->userid ==> $instance->courseid (user without role)");
     469                  }
     470                  continue;
     471              }
     472          }
     473  
     474          $meta->update_user_enrol($instance, $ue->userid, $ue->pstatus, $ue->ptimestart, $ue->ptimeend);
     475          if ($verbose) {
     476              if ($ue->pstatus == ENROL_USER_ACTIVE) {
     477                  mtrace("  unsuspending: $ue->userid ==> $instance->courseid");
     478              } else {
     479                  mtrace("  suspending: $ue->userid ==> $instance->courseid");
     480              }
     481          }
     482      }
     483      $rs->close();
     484  
     485  
     486      // now assign all necessary roles
     487      $enabled = explode(',', $CFG->enrol_plugins_enabled);
     488      foreach($enabled as $k=>$v) {
     489          if ($v === 'meta') {
     490              continue; // no meta sync of meta roles
     491          }
     492          $enabled[$k] = 'enrol_'.$v;
     493      }
     494      $enabled[] = ''; // manual assignments are replicated too
     495  
     496      $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     497      list($enabled, $params) = $DB->get_in_or_equal($enabled, SQL_PARAMS_NAMED, 'e');
     498      $params['coursecontext'] = CONTEXT_COURSE;
     499      $params['courseid'] = $courseid;
     500      $params['activeuser'] = ENROL_USER_ACTIVE;
     501      $params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
     502      $sql = "SELECT DISTINCT pra.roleid, pra.userid, c.id AS contextid, e.id AS enrolid, e.courseid
     503                FROM {role_assignments} pra
     504                JOIN {user} u ON (u.id = pra.userid AND u.deleted = 0)
     505                JOIN {context} pc ON (pc.id = pra.contextid AND pc.contextlevel = :coursecontext AND pra.component $enabled)
     506                JOIN {enrol} e ON (e.customint1 = pc.instanceid AND e.enrol = 'meta' $onecourse AND e.status = :enabledinstance)
     507                JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = u.id AND ue.status = :activeuser)
     508                JOIN {context} c ON (c.contextlevel = pc.contextlevel AND c.instanceid = e.courseid)
     509           LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = pra.userid AND ra.roleid = pra.roleid AND ra.itemid = e.id AND ra.component = 'enrol_meta')
     510               WHERE ra.id IS NULL";
     511  
     512      if ($ignored = $meta->get_config('nosyncroleids')) {
     513          list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
     514          $params = array_merge($params, $xparams);
     515          $sql = "$sql AND pra.roleid $notignored";
     516      }
     517  
     518      $rs = $DB->get_recordset_sql($sql, $params);
     519      foreach($rs as $ra) {
     520          role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->enrolid);
     521          if ($verbose) {
     522              mtrace("  assigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
     523          }
     524      }
     525      $rs->close();
     526  
     527  
     528      // remove unwanted roles - include ignored roles and disabled plugins too
     529      $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     530      $params = array();
     531      $params['coursecontext'] = CONTEXT_COURSE;
     532      $params['courseid'] = $courseid;
     533      $params['activeuser'] = ENROL_USER_ACTIVE;
     534      $params['enabledinstance'] = ENROL_INSTANCE_ENABLED;
     535      if ($ignored = $meta->get_config('nosyncroleids')) {
     536          list($notignored, $xparams) = $DB->get_in_or_equal(explode(',', $ignored), SQL_PARAMS_NAMED, 'ig', false);
     537          $params = array_merge($params, $xparams);
     538          $notignored = "AND pra.roleid $notignored";
     539      } else {
     540          $notignored = "";
     541      }
     542  
     543      $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
     544                FROM {role_assignments} ra
     545                JOIN {enrol} e ON (e.id = ra.itemid AND ra.component = 'enrol_meta' AND e.enrol = 'meta' $onecourse)
     546                JOIN {context} pc ON (pc.instanceid = e.customint1 AND pc.contextlevel = :coursecontext)
     547           LEFT JOIN {role_assignments} pra ON (pra.contextid = pc.id AND pra.userid = ra.userid AND pra.roleid = ra.roleid AND pra.component <> 'enrol_meta' $notignored)
     548           LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :activeuser)
     549               WHERE pra.id IS NULL OR ue.id IS NULL OR e.status <> :enabledinstance";
     550  
     551      if ($unenrolaction != ENROL_EXT_REMOVED_SUSPEND) {
     552          $rs = $DB->get_recordset_sql($sql, $params);
     553          foreach($rs as $ra) {
     554              role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_meta', $ra->itemid);
     555              if ($verbose) {
     556                  mtrace("  unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
     557              }
     558          }
     559          $rs->close();
     560      }
     561  
     562  
     563      // kick out or suspend users without synced roles if syncall disabled
     564      if (!$syncall) {
     565          if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
     566              $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     567              $params = array();
     568              $params['coursecontext'] = CONTEXT_COURSE;
     569              $params['courseid'] = $courseid;
     570              $sql = "SELECT ue.userid, ue.enrolid
     571                        FROM {user_enrolments} ue
     572                        JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
     573                        JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
     574                   LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
     575                       WHERE ra.id IS NULL";
     576              $ues = $DB->get_recordset_sql($sql, $params);
     577              foreach($ues as $ue) {
     578                  if (!isset($instances[$ue->enrolid])) {
     579                      $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
     580                  }
     581                  $instance = $instances[$ue->enrolid];
     582                  $meta->unenrol_user($instance, $ue->userid);
     583                  if ($verbose) {
     584                      mtrace("  unenrolling: $ue->userid ==> $instance->courseid (user without role)");
     585                  }
     586              }
     587              $ues->close();
     588  
     589          } else {
     590              // just suspend the users
     591              $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
     592              $params = array();
     593              $params['coursecontext'] = CONTEXT_COURSE;
     594              $params['courseid'] = $courseid;
     595              $params['active'] = ENROL_USER_ACTIVE;
     596              $sql = "SELECT ue.userid, ue.enrolid
     597                        FROM {user_enrolments} ue
     598                        JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'meta' $onecourse)
     599                        JOIN {context} c ON (e.courseid = c.instanceid AND c.contextlevel = :coursecontext)
     600                   LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.itemid = e.id AND ra.userid = ue.userid)
     601                       WHERE ra.id IS NULL AND ue.status = :active";
     602              $ues = $DB->get_recordset_sql($sql, $params);
     603              foreach($ues as $ue) {
     604                  if (!isset($instances[$ue->enrolid])) {
     605                      $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
     606                  }
     607                  $instance = $instances[$ue->enrolid];
     608                  $meta->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
     609                  if ($verbose) {
     610                      mtrace("  suspending: $ue->userid ==> $instance->courseid (user without role)");
     611                  }
     612              }
     613              $ues->close();
     614          }
     615      }
     616  
     617      // Finally sync groups.
     618      $affectedusers = groups_sync_with_enrolment('meta', $courseid);
     619      if ($verbose) {
     620          foreach ($affectedusers['removed'] as $gm) {
     621              mtrace("removing user from group: $gm->userid ==> $gm->courseid - $gm->groupname", 1);
     622          }
     623          foreach ($affectedusers['added'] as $ue) {
     624              mtrace("adding user to group: $ue->userid ==> $ue->courseid - $ue->groupname", 1);
     625          }
     626      }
     627  
     628      if ($verbose) {
     629          mtrace('...user enrolment synchronisation finished.');
     630      }
     631  
     632      return 0;
     633  }
     634  
     635  /**
     636   * Create a new group with the course's name.
     637   *
     638   * @param int $courseid
     639   * @param int $linkedcourseid
     640   * @return int $groupid Group ID for this cohort.
     641   */
     642  function enrol_meta_create_new_group($courseid, $linkedcourseid) {
     643      global $DB, $CFG;
     644  
     645      require_once($CFG->dirroot.'/group/lib.php');
     646  
     647      $coursename = $DB->get_field('course', 'fullname', array('id' => $linkedcourseid), MUST_EXIST);
     648      $a = new stdClass();
     649      $a->name = $coursename;
     650      $a->increment = '';
     651      $inc = 1;
     652      $groupname = trim(get_string('defaultgroupnametext', 'enrol_meta', $a));
     653      // Check to see if the group name already exists in this course. Add an incremented number if it does.
     654      while ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) {
     655          $a->increment = '(' . (++$inc) . ')';
     656          $groupname = trim(get_string('defaultgroupnametext', 'enrol_meta', $a));
     657      }
     658      // Create a new group for the course meta sync.
     659      $groupdata = new stdClass();
     660      $groupdata->courseid = $courseid;
     661      $groupdata->name = $groupname;
     662      $groupid = groups_create_group($groupdata);
     663  
     664      return $groupid;
     665  }