[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/enrol/ldap/ -> lib.php (source)

   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   * LDAP enrolment plugin implementation.
  19   *
  20   * This plugin synchronises enrolment and roles with a LDAP server.
  21   *
  22   * @package    enrol_ldap
  23   * @author     Iñaki Arenaza - based on code by Martin Dougiamas, Martin Langhoff and others
  24   * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
  25   * @copyright  2010 Iñaki Arenaza <iarenaza@eps.mondragon.edu>
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  class enrol_ldap_plugin extends enrol_plugin {
  32      protected $enrol_localcoursefield = 'idnumber';
  33      protected $enroltype = 'enrol_ldap';
  34      protected $errorlogtag = '[ENROL LDAP] ';
  35  
  36      /**
  37       * The object class to use when finding users.
  38       *
  39       * @var string $userobjectclass
  40       */
  41      protected $userobjectclass;
  42  
  43      /**
  44       * Constructor for the plugin. In addition to calling the parent
  45       * constructor, we define and 'fix' some settings depending on the
  46       * real settings the admin defined.
  47       */
  48      public function __construct() {
  49          global $CFG;
  50          require_once($CFG->libdir.'/ldaplib.php');
  51  
  52          // Do our own stuff to fix the config (it's easier to do it
  53          // here than using the admin settings infrastructure). We
  54          // don't call $this->set_config() for any of the 'fixups'
  55          // (except the objectclass, as it's critical) because the user
  56          // didn't specify any values and relied on the default values
  57          // defined for the user type she chose.
  58          $this->load_config();
  59  
  60          // Make sure we get sane defaults for critical values.
  61          $this->config->ldapencoding = $this->get_config('ldapencoding', 'utf-8');
  62          $this->config->user_type = $this->get_config('user_type', 'default');
  63  
  64          $ldap_usertypes = ldap_supported_usertypes();
  65          $this->config->user_type_name = $ldap_usertypes[$this->config->user_type];
  66          unset($ldap_usertypes);
  67  
  68          $default = ldap_getdefaults();
  69  
  70          // The objectclass in the defaults is for a user.
  71          // This will be required later, but enrol_ldap uses 'objectclass' for its group objectclass.
  72          // Save the normalised user objectclass for later.
  73          $this->userobjectclass = ldap_normalise_objectclass($default['objectclass'][$this->get_config('user_type')]);
  74  
  75          // Remove the objectclass default, as the values specified there are for users, and we are dealing with groups here.
  76          unset($default['objectclass']);
  77  
  78          // Use defaults if values not given. Dont use this->get_config()
  79          // here to be able to check for 0 and false values too.
  80          foreach ($default as $key => $value) {
  81              // Watch out - 0, false are correct values too, so we can't use $this->get_config()
  82              if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
  83                  $this->config->{$key} = $value[$this->config->user_type];
  84              }
  85          }
  86  
  87          // Normalise the objectclass used for groups.
  88          if (empty($this->config->objectclass)) {
  89              // No objectclass set yet - set a default class.
  90              $this->config->objectclass = ldap_normalise_objectclass(null, '*');
  91              $this->set_config('objectclass', $this->config->objectclass);
  92          } else {
  93              $objectclass = ldap_normalise_objectclass($this->config->objectclass);
  94              if ($objectclass !== $this->config->objectclass) {
  95                  // The objectclass was changed during normalisation.
  96                  // Save it in config, and update the local copy of config.
  97                  $this->set_config('objectclass', $objectclass);
  98                  $this->config->objectclass = $objectclass;
  99              }
 100          }
 101      }
 102  
 103      /**
 104       * Is it possible to delete enrol instance via standard UI?
 105       *
 106       * @param object $instance
 107       * @return bool
 108       */
 109      public function can_delete_instance($instance) {
 110          $context = context_course::instance($instance->courseid);
 111          if (!has_capability('enrol/ldap:manage', $context)) {
 112              return false;
 113          }
 114  
 115          if (!enrol_is_enabled('ldap')) {
 116              return true;
 117          }
 118  
 119          if (!$this->get_config('ldap_host') or !$this->get_config('objectclass') or !$this->get_config('course_idnumber')) {
 120              return true;
 121          }
 122  
 123          // TODO: connect to external system and make sure no users are to be enrolled in this course
 124          return false;
 125      }
 126  
 127      /**
 128       * Is it possible to hide/show enrol instance via standard UI?
 129       *
 130       * @param stdClass $instance
 131       * @return bool
 132       */
 133      public function can_hide_show_instance($instance) {
 134          $context = context_course::instance($instance->courseid);
 135          return has_capability('enrol/ldap:config', $context);
 136      }
 137  
 138      /**
 139       * Forces synchronisation of user enrolments with LDAP server.
 140       * It creates courses if the plugin is configured to do so.
 141       *
 142       * @param object $user user record
 143       * @return void
 144       */
 145      public function sync_user_enrolments($user) {
 146          global $DB;
 147  
 148          // Do not try to print anything to the output because this method is called during interactive login.
 149          if (PHPUNIT_TEST) {
 150              $trace = new null_progress_trace();
 151          } else {
 152              $trace = new error_log_progress_trace($this->errorlogtag);
 153          }
 154  
 155          if (!$this->ldap_connect($trace)) {
 156              $trace->finished();
 157              return;
 158          }
 159  
 160          if (!is_object($user) or !property_exists($user, 'id')) {
 161              throw new coding_exception('Invalid $user parameter in sync_user_enrolments()');
 162          }
 163  
 164          if (!property_exists($user, 'idnumber')) {
 165              debugging('Invalid $user parameter in sync_user_enrolments(), missing idnumber');
 166              $user = $DB->get_record('user', array('id'=>$user->id));
 167          }
 168  
 169          // We may need a lot of memory here
 170          core_php_time_limit::raise();
 171          raise_memory_limit(MEMORY_HUGE);
 172  
 173          // Get enrolments for each type of role.
 174          $roles = get_all_roles();
 175          $enrolments = array();
 176          foreach($roles as $role) {
 177              // Get external enrolments according to LDAP server
 178              $enrolments[$role->id]['ext'] = $this->find_ext_enrolments($user->idnumber, $role);
 179  
 180              // Get the list of current user enrolments that come from LDAP
 181              $sql= "SELECT e.courseid, ue.status, e.id as enrolid, c.shortname
 182                       FROM {user} u
 183                       JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = 'enrol_ldap' AND ra.roleid = :roleid)
 184                       JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)
 185                       JOIN {enrol} e ON (e.id = ue.enrolid)
 186                       JOIN {course} c ON (c.id = e.courseid)
 187                      WHERE u.deleted = 0 AND u.id = :userid";
 188              $params = array ('roleid'=>$role->id, 'userid'=>$user->id);
 189              $enrolments[$role->id]['current'] = $DB->get_records_sql($sql, $params);
 190          }
 191  
 192          $ignorehidden = $this->get_config('ignorehiddencourses');
 193          $courseidnumber = $this->get_config('course_idnumber');
 194          foreach($roles as $role) {
 195              foreach ($enrolments[$role->id]['ext'] as $enrol) {
 196                  $course_ext_id = $enrol[$courseidnumber][0];
 197                  if (empty($course_ext_id)) {
 198                      $trace->output(get_string('extcourseidinvalid', 'enrol_ldap'));
 199                      continue; // Next; skip this one!
 200                  }
 201  
 202                  // Create the course if required
 203                  $course = $DB->get_record('course', array($this->enrol_localcoursefield=>$course_ext_id));
 204                  if (empty($course)) { // Course doesn't exist
 205                      if ($this->get_config('autocreate')) { // Autocreate
 206                          $trace->output(get_string('createcourseextid', 'enrol_ldap', array('courseextid'=>$course_ext_id)));
 207                          if (!$newcourseid = $this->create_course($enrol, $trace)) {
 208                              continue;
 209                          }
 210                          $course = $DB->get_record('course', array('id'=>$newcourseid));
 211                      } else {
 212                          $trace->output(get_string('createnotcourseextid', 'enrol_ldap', array('courseextid'=>$course_ext_id)));
 213                          continue; // Next; skip this one!
 214                      }
 215                  }
 216  
 217                  // Deal with enrolment in the moodle db
 218                  // Add necessary enrol instance if not present yet;
 219                  $sql = "SELECT c.id, c.visible, e.id as enrolid
 220                            FROM {course} c
 221                            JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'ldap')
 222                           WHERE c.id = :courseid";
 223                  $params = array('courseid'=>$course->id);
 224                  if (!($course_instance = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE))) {
 225                      $course_instance = new stdClass();
 226                      $course_instance->id = $course->id;
 227                      $course_instance->visible = $course->visible;
 228                      $course_instance->enrolid = $this->add_instance($course_instance);
 229                  }
 230  
 231                  if (!$instance = $DB->get_record('enrol', array('id'=>$course_instance->enrolid))) {
 232                      continue; // Weird; skip this one.
 233                  }
 234  
 235                  if ($ignorehidden && !$course_instance->visible) {
 236                      continue;
 237                  }
 238  
 239                  if (empty($enrolments[$role->id]['current'][$course->id])) {
 240                      // Enrol the user in the given course, with that role.
 241                      $this->enrol_user($instance, $user->id, $role->id);
 242                      // Make sure we set the enrolment status to active. If the user wasn't
 243                      // previously enrolled to the course, enrol_user() sets it. But if we
 244                      // configured the plugin to suspend the user enrolments _AND_ remove
 245                      // the role assignments on external unenrol, then enrol_user() doesn't
 246                      // set it back to active on external re-enrolment. So set it
 247                      // unconditionnally to cover both cases.
 248                      $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id));
 249                      $trace->output(get_string('enroluser', 'enrol_ldap',
 250                          array('user_username'=> $user->username,
 251                                'course_shortname'=>$course->shortname,
 252                                'course_id'=>$course->id)));
 253                  } else {
 254                      if ($enrolments[$role->id]['current'][$course->id]->status == ENROL_USER_SUSPENDED) {
 255                          // Reenable enrolment that was previously disabled. Enrolment refreshed
 256                          $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id));
 257                          $trace->output(get_string('enroluserenable', 'enrol_ldap',
 258                              array('user_username'=> $user->username,
 259                                    'course_shortname'=>$course->shortname,
 260                                    'course_id'=>$course->id)));
 261                      }
 262                  }
 263  
 264                  // Remove this course from the current courses, to be able to detect
 265                  // which current courses should be unenroled from when we finish processing
 266                  // external enrolments.
 267                  unset($enrolments[$role->id]['current'][$course->id]);
 268              }
 269  
 270              // Deal with unenrolments.
 271              $transaction = $DB->start_delegated_transaction();
 272              foreach ($enrolments[$role->id]['current'] as $course) {
 273                  $context = context_course::instance($course->courseid);
 274                  $instance = $DB->get_record('enrol', array('id'=>$course->enrolid));
 275                  switch ($this->get_config('unenrolaction')) {
 276                      case ENROL_EXT_REMOVED_UNENROL:
 277                          $this->unenrol_user($instance, $user->id);
 278                          $trace->output(get_string('extremovedunenrol', 'enrol_ldap',
 279                              array('user_username'=> $user->username,
 280                                    'course_shortname'=>$course->shortname,
 281                                    'course_id'=>$course->courseid)));
 282                          break;
 283                      case ENROL_EXT_REMOVED_KEEP:
 284                          // Keep - only adding enrolments
 285                          break;
 286                      case ENROL_EXT_REMOVED_SUSPEND:
 287                          if ($course->status != ENROL_USER_SUSPENDED) {
 288                              $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id));
 289                              $trace->output(get_string('extremovedsuspend', 'enrol_ldap',
 290                                  array('user_username'=> $user->username,
 291                                        'course_shortname'=>$course->shortname,
 292                                        'course_id'=>$course->courseid)));
 293                          }
 294                          break;
 295                      case ENROL_EXT_REMOVED_SUSPENDNOROLES:
 296                          if ($course->status != ENROL_USER_SUSPENDED) {
 297                              $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id));
 298                          }
 299                          role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id));
 300                          $trace->output(get_string('extremovedsuspendnoroles', 'enrol_ldap',
 301                              array('user_username'=> $user->username,
 302                                    'course_shortname'=>$course->shortname,
 303                                    'course_id'=>$course->courseid)));
 304                          break;
 305                  }
 306              }
 307              $transaction->allow_commit();
 308          }
 309  
 310          $this->ldap_close();
 311  
 312          $trace->finished();
 313      }
 314  
 315      /**
 316       * Forces synchronisation of all enrolments with LDAP server.
 317       * It creates courses if the plugin is configured to do so.
 318       *
 319       * @param progress_trace $trace
 320       * @param int|null $onecourse limit sync to one course->id, null if all courses
 321       * @return void
 322       */
 323      public function sync_enrolments(progress_trace $trace, $onecourse = null) {
 324          global $CFG, $DB;
 325  
 326          if (!$this->ldap_connect($trace)) {
 327              $trace->finished();
 328              return;
 329          }
 330  
 331          $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'));
 332  
 333          // we may need a lot of memory here
 334          core_php_time_limit::raise();
 335          raise_memory_limit(MEMORY_HUGE);
 336  
 337          $oneidnumber = null;
 338          if ($onecourse) {
 339              if (!$course = $DB->get_record('course', array('id'=>$onecourse), 'id,'.$this->enrol_localcoursefield)) {
 340                  // Course does not exist, nothing to do.
 341                  $trace->output("Requested course $onecourse does not exist, no sync performed.");
 342                  $trace->finished();
 343                  return;
 344              }
 345              if (empty($course->{$this->enrol_localcoursefield})) {
 346                  $trace->output("Requested course $onecourse does not have {$this->enrol_localcoursefield}, no sync performed.");
 347                  $trace->finished();
 348                  return;
 349              }
 350              $oneidnumber = ldap_filter_addslashes(core_text::convert($course->idnumber, 'utf-8', $this->get_config('ldapencoding')));
 351          }
 352  
 353          // Get enrolments for each type of role.
 354          $roles = get_all_roles();
 355          $enrolments = array();
 356          foreach($roles as $role) {
 357              // Get all contexts
 358              $ldap_contexts = explode(';', $this->config->{'contexts_role'.$role->id});
 359  
 360              // Get all the fields we will want for the potential course creation
 361              // as they are light. Don't get membership -- potentially a lot of data.
 362              $ldap_fields_wanted = array('dn', $this->config->course_idnumber);
 363              if (!empty($this->config->course_fullname)) {
 364                  array_push($ldap_fields_wanted, $this->config->course_fullname);
 365              }
 366              if (!empty($this->config->course_shortname)) {
 367                  array_push($ldap_fields_wanted, $this->config->course_shortname);
 368              }
 369              if (!empty($this->config->course_summary)) {
 370                  array_push($ldap_fields_wanted, $this->config->course_summary);
 371              }
 372              array_push($ldap_fields_wanted, $this->config->{'memberattribute_role'.$role->id});
 373  
 374              // Define the search pattern
 375              $ldap_search_pattern = $this->config->objectclass;
 376  
 377              if ($oneidnumber !== null) {
 378                  $ldap_search_pattern = "(&$ldap_search_pattern({$this->config->course_idnumber}=$oneidnumber))";
 379              }
 380  
 381              $ldap_cookie = '';
 382              foreach ($ldap_contexts as $ldap_context) {
 383                  $ldap_context = trim($ldap_context);
 384                  if (empty($ldap_context)) {
 385                      continue; // Next;
 386                  }
 387  
 388                  $flat_records = array();
 389                  do {
 390                      if ($ldap_pagedresults) {
 391                          ldap_control_paged_result($this->ldapconnection, $this->config->pagesize, true, $ldap_cookie);
 392                      }
 393  
 394                      if ($this->config->course_search_sub) {
 395                          // Use ldap_search to find first user from subtree
 396                          $ldap_result = @ldap_search($this->ldapconnection,
 397                                                      $ldap_context,
 398                                                      $ldap_search_pattern,
 399                                                      $ldap_fields_wanted);
 400                      } else {
 401                          // Search only in this context
 402                          $ldap_result = @ldap_list($this->ldapconnection,
 403                                                    $ldap_context,
 404                                                    $ldap_search_pattern,
 405                                                    $ldap_fields_wanted);
 406                      }
 407                      if (!$ldap_result) {
 408                          continue; // Next
 409                      }
 410  
 411                      if ($ldap_pagedresults) {
 412                          ldap_control_paged_result_response($this->ldapconnection, $ldap_result, $ldap_cookie);
 413                      }
 414  
 415                      // Check and push results
 416                      $records = ldap_get_entries($this->ldapconnection, $ldap_result);
 417  
 418                      // LDAP libraries return an odd array, really. fix it:
 419                      for ($c = 0; $c < $records['count']; $c++) {
 420                          array_push($flat_records, $records[$c]);
 421                      }
 422                      // Free some mem
 423                      unset($records);
 424                  } while ($ldap_pagedresults && !empty($ldap_cookie));
 425  
 426                  // If LDAP paged results were used, the current connection must be completely
 427                  // closed and a new one created, to work without paged results from here on.
 428                  if ($ldap_pagedresults) {
 429                      $this->ldap_close();
 430                      $this->ldap_connect($trace);
 431                  }
 432  
 433                  if (count($flat_records)) {
 434                      $ignorehidden = $this->get_config('ignorehiddencourses');
 435                      foreach($flat_records as $course) {
 436                          $course = array_change_key_case($course, CASE_LOWER);
 437                          $idnumber = $course{$this->config->course_idnumber}[0];
 438                          $trace->output(get_string('synccourserole', 'enrol_ldap', array('idnumber'=>$idnumber, 'role_shortname'=>$role->shortname)));
 439  
 440                          // Does the course exist in moodle already?
 441                          $course_obj = $DB->get_record('course', array($this->enrol_localcoursefield=>$idnumber));
 442                          if (empty($course_obj)) { // Course doesn't exist
 443                              if ($this->get_config('autocreate')) { // Autocreate
 444                                  $trace->output(get_string('createcourseextid', 'enrol_ldap', array('courseextid'=>$idnumber)));
 445                                  if (!$newcourseid = $this->create_course($course, $trace)) {
 446                                      continue;
 447                                  }
 448                                  $course_obj = $DB->get_record('course', array('id'=>$newcourseid));
 449                              } else {
 450                                  $trace->output(get_string('createnotcourseextid', 'enrol_ldap', array('courseextid'=>$idnumber)));
 451                                  continue; // Next; skip this one!
 452                              }
 453                          } else {  // Check if course needs update & update as needed.
 454                              $this->update_course($course_obj, $course, $trace);
 455                          }
 456  
 457                          // Enrol & unenrol
 458  
 459                          // Pull the ldap membership into a nice array
 460                          // this is an odd array -- mix of hash and array --
 461                          $ldapmembers = array();
 462  
 463                          if (array_key_exists('memberattribute_role'.$role->id, $this->config)
 464                              && !empty($this->config->{'memberattribute_role'.$role->id})
 465                              && !empty($course[$this->config->{'memberattribute_role'.$role->id}])) { // May have no membership!
 466  
 467                              $ldapmembers = $course[$this->config->{'memberattribute_role'.$role->id}];
 468                              unset($ldapmembers['count']); // Remove oddity ;)
 469  
 470                              // If we have enabled nested groups, we need to expand
 471                              // the groups to get the real user list. We need to do
 472                              // this before dealing with 'memberattribute_isdn'.
 473                              if ($this->config->nested_groups) {
 474                                  $users = array();
 475                                  foreach ($ldapmembers as $ldapmember) {
 476                                      $grpusers = $this->ldap_explode_group($ldapmember,
 477                                                                            $this->config->{'memberattribute_role'.$role->id});
 478  
 479                                      $users = array_merge($users, $grpusers);
 480                                  }
 481                                  $ldapmembers = array_unique($users); // There might be duplicates.
 482                              }
 483  
 484                              // Deal with the case where the member attribute holds distinguished names,
 485                              // but only if the user attribute is not a distinguished name itself.
 486                              if ($this->config->memberattribute_isdn
 487                                  && ($this->config->idnumber_attribute !== 'dn')
 488                                  && ($this->config->idnumber_attribute !== 'distinguishedname')) {
 489                                  // We need to retrieve the idnumber for all the users in $ldapmembers,
 490                                  // as the idnumber does not match their dn and we get dn's from membership.
 491                                  $memberidnumbers = array();
 492                                  foreach ($ldapmembers as $ldapmember) {
 493                                      $result = ldap_read($this->ldapconnection, $ldapmember, $this->userobjectclass,
 494                                                          array($this->config->idnumber_attribute));
 495                                      $entry = ldap_first_entry($this->ldapconnection, $result);
 496                                      $values = ldap_get_values($this->ldapconnection, $entry, $this->config->idnumber_attribute);
 497                                      array_push($memberidnumbers, $values[0]);
 498                                  }
 499  
 500                                  $ldapmembers = $memberidnumbers;
 501                              }
 502                          }
 503  
 504                          // Prune old ldap enrolments
 505                          // hopefully they'll fit in the max buffer size for the RDBMS
 506                          $sql= "SELECT u.id as userid, u.username, ue.status,
 507                                        ra.contextid, ra.itemid as instanceid
 508                                   FROM {user} u
 509                                   JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = 'enrol_ldap' AND ra.roleid = :roleid)
 510                                   JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)
 511                                   JOIN {enrol} e ON (e.id = ue.enrolid)
 512                                  WHERE u.deleted = 0 AND e.courseid = :courseid ";
 513                          $params = array('roleid'=>$role->id, 'courseid'=>$course_obj->id);
 514                          $context = context_course::instance($course_obj->id);
 515                          if (!empty($ldapmembers)) {
 516                              list($ldapml, $params2) = $DB->get_in_or_equal($ldapmembers, SQL_PARAMS_NAMED, 'm', false);
 517                              $sql .= "AND u.idnumber $ldapml";
 518                              $params = array_merge($params, $params2);
 519                              unset($params2);
 520                          } else {
 521                              $shortname = format_string($course_obj->shortname, true, array('context' => $context));
 522                              $trace->output(get_string('emptyenrolment', 'enrol_ldap',
 523                                           array('role_shortname'=> $role->shortname,
 524                                                 'course_shortname' => $shortname)));
 525                          }
 526                          $todelete = $DB->get_records_sql($sql, $params);
 527  
 528                          if (!empty($todelete)) {
 529                              $transaction = $DB->start_delegated_transaction();
 530                              foreach ($todelete as $row) {
 531                                  $instance = $DB->get_record('enrol', array('id'=>$row->instanceid));
 532                                  switch ($this->get_config('unenrolaction')) {
 533                                  case ENROL_EXT_REMOVED_UNENROL:
 534                                      $this->unenrol_user($instance, $row->userid);
 535                                      $trace->output(get_string('extremovedunenrol', 'enrol_ldap',
 536                                          array('user_username'=> $row->username,
 537                                                'course_shortname'=>$course_obj->shortname,
 538                                                'course_id'=>$course_obj->id)));
 539                                      break;
 540                                  case ENROL_EXT_REMOVED_KEEP:
 541                                      // Keep - only adding enrolments
 542                                      break;
 543                                  case ENROL_EXT_REMOVED_SUSPEND:
 544                                      if ($row->status != ENROL_USER_SUSPENDED) {
 545                                          $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid));
 546                                          $trace->output(get_string('extremovedsuspend', 'enrol_ldap',
 547                                              array('user_username'=> $row->username,
 548                                                    'course_shortname'=>$course_obj->shortname,
 549                                                    'course_id'=>$course_obj->id)));
 550                                      }
 551                                      break;
 552                                  case ENROL_EXT_REMOVED_SUSPENDNOROLES:
 553                                      if ($row->status != ENROL_USER_SUSPENDED) {
 554                                          $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid));
 555                                      }
 556                                      role_unassign_all(array('contextid'=>$row->contextid, 'userid'=>$row->userid, 'component'=>'enrol_ldap', 'itemid'=>$instance->id));
 557                                      $trace->output(get_string('extremovedsuspendnoroles', 'enrol_ldap',
 558                                          array('user_username'=> $row->username,
 559                                                'course_shortname'=>$course_obj->shortname,
 560                                                'course_id'=>$course_obj->id)));
 561                                      break;
 562                                  }
 563                              }
 564                              $transaction->allow_commit();
 565                          }
 566  
 567                          // Insert current enrolments
 568                          // bad we can't do INSERT IGNORE with postgres...
 569  
 570                          // Add necessary enrol instance if not present yet;
 571                          $sql = "SELECT c.id, c.visible, e.id as enrolid
 572                                    FROM {course} c
 573                                    JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'ldap')
 574                                   WHERE c.id = :courseid";
 575                          $params = array('courseid'=>$course_obj->id);
 576                          if (!($course_instance = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE))) {
 577                              $course_instance = new stdClass();
 578                              $course_instance->id = $course_obj->id;
 579                              $course_instance->visible = $course_obj->visible;
 580                              $course_instance->enrolid = $this->add_instance($course_instance);
 581                          }
 582  
 583                          if (!$instance = $DB->get_record('enrol', array('id'=>$course_instance->enrolid))) {
 584                              continue; // Weird; skip this one.
 585                          }
 586  
 587                          if ($ignorehidden && !$course_instance->visible) {
 588                              continue;
 589                          }
 590  
 591                          $transaction = $DB->start_delegated_transaction();
 592                          foreach ($ldapmembers as $ldapmember) {
 593                              $sql = 'SELECT id,username,1 FROM {user} WHERE idnumber = ? AND deleted = 0';
 594                              $member = $DB->get_record_sql($sql, array($ldapmember));
 595                              if(empty($member) || empty($member->id)){
 596                                  $trace->output(get_string('couldnotfinduser', 'enrol_ldap', $ldapmember));
 597                                  continue;
 598                              }
 599  
 600                              $sql= "SELECT ue.status
 601                                       FROM {user_enrolments} ue
 602                                       JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'ldap')
 603                                      WHERE e.courseid = :courseid AND ue.userid = :userid";
 604                              $params = array('courseid'=>$course_obj->id, 'userid'=>$member->id);
 605                              $userenrolment = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE);
 606  
 607                              if (empty($userenrolment)) {
 608                                  $this->enrol_user($instance, $member->id, $role->id);
 609                                  // Make sure we set the enrolment status to active. If the user wasn't
 610                                  // previously enrolled to the course, enrol_user() sets it. But if we
 611                                  // configured the plugin to suspend the user enrolments _AND_ remove
 612                                  // the role assignments on external unenrol, then enrol_user() doesn't
 613                                  // set it back to active on external re-enrolment. So set it
 614                                  // unconditionally to cover both cases.
 615                                  $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id));
 616                                  $trace->output(get_string('enroluser', 'enrol_ldap',
 617                                      array('user_username'=> $member->username,
 618                                            'course_shortname'=>$course_obj->shortname,
 619                                            'course_id'=>$course_obj->id)));
 620  
 621                              } else {
 622                                  if (!$DB->record_exists('role_assignments', array('roleid'=>$role->id, 'userid'=>$member->id, 'contextid'=>$context->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id))) {
 623                                      // This happens when reviving users or when user has multiple roles in one course.
 624                                      $context = context_course::instance($course_obj->id);
 625                                      role_assign($role->id, $member->id, $context->id, 'enrol_ldap', $instance->id);
 626                                      $trace->output("Assign role to user '$member->username' in course '$course_obj->shortname ($course_obj->id)'");
 627                                  }
 628                                  if ($userenrolment->status == ENROL_USER_SUSPENDED) {
 629                                      // Reenable enrolment that was previously disabled. Enrolment refreshed
 630                                      $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id));
 631                                      $trace->output(get_string('enroluserenable', 'enrol_ldap',
 632                                          array('user_username'=> $member->username,
 633                                                'course_shortname'=>$course_obj->shortname,
 634                                                'course_id'=>$course_obj->id)));
 635                                  }
 636                              }
 637                          }
 638                          $transaction->allow_commit();
 639                      }
 640                  }
 641              }
 642          }
 643          @$this->ldap_close();
 644          $trace->finished();
 645      }
 646  
 647      /**
 648       * Connect to the LDAP server, using the plugin configured
 649       * settings. It's actually a wrapper around ldap_connect_moodle()
 650       *
 651       * @param progress_trace $trace
 652       * @return bool success
 653       */
 654      protected function ldap_connect(progress_trace $trace = null) {
 655          global $CFG;
 656          require_once($CFG->libdir.'/ldaplib.php');
 657  
 658          if (isset($this->ldapconnection)) {
 659              return true;
 660          }
 661  
 662          if ($ldapconnection = ldap_connect_moodle($this->get_config('host_url'), $this->get_config('ldap_version'),
 663                                                    $this->get_config('user_type'), $this->get_config('bind_dn'),
 664                                                    $this->get_config('bind_pw'), $this->get_config('opt_deref'),
 665                                                    $debuginfo, $this->get_config('start_tls'))) {
 666              $this->ldapconnection = $ldapconnection;
 667              return true;
 668          }
 669  
 670          if ($trace) {
 671              $trace->output($debuginfo);
 672          } else {
 673              error_log($this->errorlogtag.$debuginfo);
 674          }
 675  
 676          return false;
 677      }
 678  
 679      /**
 680       * Disconnects from a LDAP server
 681       *
 682       */
 683      protected function ldap_close() {
 684          if (isset($this->ldapconnection)) {
 685              @ldap_close($this->ldapconnection);
 686              $this->ldapconnection = null;
 687          }
 688          return;
 689      }
 690  
 691      /**
 692       * Return multidimensional array with details of user courses (at
 693       * least dn and idnumber).
 694       *
 695       * @param string $memberuid user idnumber (without magic quotes).
 696       * @param object role is a record from the mdl_role table.
 697       * @return array
 698       */
 699      protected function find_ext_enrolments($memberuid, $role) {
 700          global $CFG;
 701          require_once($CFG->libdir.'/ldaplib.php');
 702  
 703          if (empty($memberuid)) {
 704              // No "idnumber" stored for this user, so no LDAP enrolments
 705              return array();
 706          }
 707  
 708          $ldap_contexts = trim($this->get_config('contexts_role'.$role->id));
 709          if (empty($ldap_contexts)) {
 710              // No role contexts, so no LDAP enrolments
 711              return array();
 712          }
 713  
 714          $extmemberuid = core_text::convert($memberuid, 'utf-8', $this->get_config('ldapencoding'));
 715  
 716          if($this->get_config('memberattribute_isdn')) {
 717              if (!($extmemberuid = $this->ldap_find_userdn($extmemberuid))) {
 718                  return array();
 719              }
 720          }
 721  
 722          $ldap_search_pattern = '';
 723          if($this->get_config('nested_groups')) {
 724              $usergroups = $this->ldap_find_user_groups($extmemberuid);
 725              if(count($usergroups) > 0) {
 726                  foreach ($usergroups as $group) {
 727                      $group = ldap_filter_addslashes($group);
 728                      $ldap_search_pattern .= '('.$this->get_config('memberattribute_role'.$role->id).'='.$group.')';
 729                  }
 730              }
 731          }
 732  
 733          // Default return value
 734          $courses = array();
 735  
 736          // Get all the fields we will want for the potential course creation
 737          // as they are light. don't get membership -- potentially a lot of data.
 738          $ldap_fields_wanted = array('dn', $this->get_config('course_idnumber'));
 739          $fullname  = $this->get_config('course_fullname');
 740          $shortname = $this->get_config('course_shortname');
 741          $summary   = $this->get_config('course_summary');
 742          if (isset($fullname)) {
 743              array_push($ldap_fields_wanted, $fullname);
 744          }
 745          if (isset($shortname)) {
 746              array_push($ldap_fields_wanted, $shortname);
 747          }
 748          if (isset($summary)) {
 749              array_push($ldap_fields_wanted, $summary);
 750          }
 751  
 752          // Define the search pattern
 753          if (empty($ldap_search_pattern)) {
 754              $ldap_search_pattern = '('.$this->get_config('memberattribute_role'.$role->id).'='.ldap_filter_addslashes($extmemberuid).')';
 755          } else {
 756              $ldap_search_pattern = '(|' . $ldap_search_pattern .
 757                                         '('.$this->get_config('memberattribute_role'.$role->id).'='.ldap_filter_addslashes($extmemberuid).')' .
 758                                     ')';
 759          }
 760          $ldap_search_pattern='(&'.$this->get_config('objectclass').$ldap_search_pattern.')';
 761  
 762          // Get all contexts and look for first matching user
 763          $ldap_contexts = explode(';', $ldap_contexts);
 764          $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'));
 765          foreach ($ldap_contexts as $context) {
 766              $context = trim($context);
 767              if (empty($context)) {
 768                  continue;
 769              }
 770  
 771              $ldap_cookie = '';
 772              $flat_records = array();
 773              do {
 774                  if ($ldap_pagedresults) {
 775                      ldap_control_paged_result($this->ldapconnection, $this->config->pagesize, true, $ldap_cookie);
 776                  }
 777  
 778                  if ($this->get_config('course_search_sub')) {
 779                      // Use ldap_search to find first user from subtree
 780                      $ldap_result = @ldap_search($this->ldapconnection,
 781                                                  $context,
 782                                                  $ldap_search_pattern,
 783                                                  $ldap_fields_wanted);
 784                  } else {
 785                      // Search only in this context
 786                      $ldap_result = @ldap_list($this->ldapconnection,
 787                                                $context,
 788                                                $ldap_search_pattern,
 789                                                $ldap_fields_wanted);
 790                  }
 791  
 792                  if (!$ldap_result) {
 793                      continue;
 794                  }
 795  
 796                  if ($ldap_pagedresults) {
 797                      ldap_control_paged_result_response($this->ldapconnection, $ldap_result, $ldap_cookie);
 798                  }
 799  
 800                  // Check and push results. ldap_get_entries() already
 801                  // lowercases the attribute index, so there's no need to
 802                  // use array_change_key_case() later.
 803                  $records = ldap_get_entries($this->ldapconnection, $ldap_result);
 804  
 805                  // LDAP libraries return an odd array, really. Fix it.
 806                  for ($c = 0; $c < $records['count']; $c++) {
 807                      array_push($flat_records, $records[$c]);
 808                  }
 809                  // Free some mem
 810                  unset($records);
 811              } while ($ldap_pagedresults && !empty($ldap_cookie));
 812  
 813              // If LDAP paged results were used, the current connection must be completely
 814              // closed and a new one created, to work without paged results from here on.
 815              if ($ldap_pagedresults) {
 816                  $this->ldap_close();
 817                  $this->ldap_connect();
 818              }
 819  
 820              if (count($flat_records)) {
 821                  $courses = array_merge($courses, $flat_records);
 822              }
 823          }
 824  
 825          return $courses;
 826      }
 827  
 828      /**
 829       * Search specified contexts for the specified userid and return the
 830       * user dn like: cn=username,ou=suborg,o=org. It's actually a wrapper
 831       * around ldap_find_userdn().
 832       *
 833       * @param string $userid the userid to search for (in external LDAP encoding, no magic quotes).
 834       * @return mixed the user dn or false
 835       */
 836      protected function ldap_find_userdn($userid) {
 837          global $CFG;
 838          require_once($CFG->libdir.'/ldaplib.php');
 839  
 840          $ldap_contexts = explode(';', $this->get_config('user_contexts'));
 841  
 842          return ldap_find_userdn($this->ldapconnection, $userid, $ldap_contexts,
 843                                  $this->userobjectclass,
 844                                  $this->get_config('idnumber_attribute'), $this->get_config('user_search_sub'));
 845      }
 846  
 847      /**
 848       * Find the groups a given distinguished name belongs to, both directly
 849       * and indirectly via nested groups membership.
 850       *
 851       * @param string $memberdn distinguished name to search
 852       * @return array with member groups' distinguished names (can be emtpy)
 853       */
 854      protected function ldap_find_user_groups($memberdn) {
 855          $groups = array();
 856  
 857          $this->ldap_find_user_groups_recursively($memberdn, $groups);
 858          return $groups;
 859      }
 860  
 861      /**
 862       * Recursively process the groups the given member distinguished name
 863       * belongs to, adding them to the already processed groups array.
 864       *
 865       * @param string $memberdn distinguished name to search
 866       * @param array reference &$membergroups array with already found
 867       *                        groups, where we'll put the newly found
 868       *                        groups.
 869       */
 870      protected function ldap_find_user_groups_recursively($memberdn, &$membergroups) {
 871          $result = @ldap_read($this->ldapconnection, $memberdn, '(objectClass=*)', array($this->get_config('group_memberofattribute')));
 872          if (!$result) {
 873              return;
 874          }
 875  
 876          if ($entry = ldap_first_entry($this->ldapconnection, $result)) {
 877              do {
 878                  $attributes = ldap_get_attributes($this->ldapconnection, $entry);
 879                  for ($j = 0; $j < $attributes['count']; $j++) {
 880                      $groups = ldap_get_values_len($this->ldapconnection, $entry, $attributes[$j]);
 881                      foreach ($groups as $key => $group) {
 882                          if ($key === 'count') {  // Skip the entries count
 883                              continue;
 884                          }
 885                          if(!in_array($group, $membergroups)) {
 886                              // Only push and recurse if we haven't 'seen' this group before
 887                              // to prevent loops (MS Active Directory allows them!!).
 888                              array_push($membergroups, $group);
 889                              $this->ldap_find_user_groups_recursively($group, $membergroups);
 890                          }
 891                      }
 892                  }
 893              }
 894              while ($entry = ldap_next_entry($this->ldapconnection, $entry));
 895          }
 896      }
 897  
 898      /**
 899       * Given a group name (either a RDN or a DN), get the list of users
 900       * belonging to that group. If the group has nested groups, expand all
 901       * the intermediate groups and return the full list of users that
 902       * directly or indirectly belong to the group.
 903       *
 904       * @param string $group the group name to search
 905       * @param string $memberattibute the attribute that holds the members of the group
 906       * @return array the list of users belonging to the group. If $group
 907       *         is not actually a group, returns array($group).
 908       */
 909      protected function ldap_explode_group($group, $memberattribute) {
 910          switch ($this->get_config('user_type')) {
 911              case 'ad':
 912                  // $group is already the distinguished name to search.
 913                  $dn = $group;
 914  
 915                  $result = ldap_read($this->ldapconnection, $dn, '(objectClass=*)', array('objectClass'));
 916                  $entry = ldap_first_entry($this->ldapconnection, $result);
 917                  $objectclass = ldap_get_values($this->ldapconnection, $entry, 'objectClass');
 918  
 919                  if (!in_array('group', $objectclass)) {
 920                      // Not a group, so return immediately.
 921                      return array($group);
 922                  }
 923  
 924                  $result = ldap_read($this->ldapconnection, $dn, '(objectClass=*)', array($memberattribute));
 925                  $entry = ldap_first_entry($this->ldapconnection, $result);
 926                  $members = @ldap_get_values($this->ldapconnection, $entry, $memberattribute); // Can be empty and throws a warning
 927                  if ($members['count'] == 0) {
 928                      // There are no members in this group, return nothing.
 929                      return array();
 930                  }
 931                  unset($members['count']);
 932  
 933                  $users = array();
 934                  foreach ($members as $member) {
 935                      $group_members = $this->ldap_explode_group($member, $memberattribute);
 936                      $users = array_merge($users, $group_members);
 937                  }
 938  
 939                  return ($users);
 940                  break;
 941              default:
 942                  error_log($this->errorlogtag.get_string('explodegroupusertypenotsupported', 'enrol_ldap',
 943                                                          $this->get_config('user_type_name')));
 944  
 945                  return array($group);
 946          }
 947      }
 948  
 949      /**
 950       * Will create the moodle course from the template
 951       * course_ext is an array as obtained from ldap -- flattened somewhat
 952       *
 953       * @param array $course_ext
 954       * @param progress_trace $trace
 955       * @return mixed false on error, id for the newly created course otherwise.
 956       */
 957      function create_course($course_ext, progress_trace $trace) {
 958          global $CFG, $DB;
 959  
 960          require_once("$CFG->dirroot/course/lib.php");
 961  
 962          // Override defaults with template course
 963          $template = false;
 964          if ($this->get_config('template')) {
 965              if ($template = $DB->get_record('course', array('shortname'=>$this->get_config('template')))) {
 966                  $template = fullclone(course_get_format($template)->get_course());
 967                  unset($template->id); // So we are clear to reinsert the record
 968                  unset($template->fullname);
 969                  unset($template->shortname);
 970                  unset($template->idnumber);
 971              }
 972          }
 973          if (!$template) {
 974              $courseconfig = get_config('moodlecourse');
 975              $template = new stdClass();
 976              $template->summary        = '';
 977              $template->summaryformat  = FORMAT_HTML;
 978              $template->format         = $courseconfig->format;
 979              $template->newsitems      = $courseconfig->newsitems;
 980              $template->showgrades     = $courseconfig->showgrades;
 981              $template->showreports    = $courseconfig->showreports;
 982              $template->maxbytes       = $courseconfig->maxbytes;
 983              $template->groupmode      = $courseconfig->groupmode;
 984              $template->groupmodeforce = $courseconfig->groupmodeforce;
 985              $template->visible        = $courseconfig->visible;
 986              $template->lang           = $courseconfig->lang;
 987              $template->enablecompletion = $courseconfig->enablecompletion;
 988          }
 989          $course = $template;
 990  
 991          $course->category = $this->get_config('category');
 992          if (!$DB->record_exists('course_categories', array('id'=>$this->get_config('category')))) {
 993              $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1);
 994              $first = reset($categories);
 995              $course->category = $first->id;
 996          }
 997  
 998          // Override with required ext data
 999          $course->idnumber  = $course_ext[$this->get_config('course_idnumber')][0];
1000          $course->fullname  = $course_ext[$this->get_config('course_fullname')][0];
1001          $course->shortname = $course_ext[$this->get_config('course_shortname')][0];
1002          if (empty($course->idnumber) || empty($course->fullname) || empty($course->shortname)) {
1003              // We are in trouble!
1004              $trace->output(get_string('cannotcreatecourse', 'enrol_ldap').' '.var_export($course, true));
1005              return false;
1006          }
1007  
1008          $summary = $this->get_config('course_summary');
1009          if (!isset($summary) || empty($course_ext[$summary][0])) {
1010              $course->summary = '';
1011          } else {
1012              $course->summary = $course_ext[$this->get_config('course_summary')][0];
1013          }
1014  
1015          // Check if the shortname already exists if it does - skip course creation.
1016          if ($DB->record_exists('course', array('shortname' => $course->shortname))) {
1017              $trace->output(get_string('duplicateshortname', 'enrol_ldap', $course));
1018              return false;
1019          }
1020  
1021          $newcourse = create_course($course);
1022          return $newcourse->id;
1023      }
1024  
1025      /**
1026       * Will update a moodle course with new values from LDAP
1027       * A field will be updated only if it is marked to be updated
1028       * on sync in plugin settings
1029       *
1030       * @param object $course
1031       * @param array $externalcourse
1032       * @param progress_trace $trace
1033       * @return bool
1034       */
1035      protected function update_course($course, $externalcourse, progress_trace $trace) {
1036          global $CFG, $DB;
1037  
1038          $coursefields = array ('shortname', 'fullname', 'summary');
1039          static $shouldupdate;
1040  
1041          // Initialize $shouldupdate variable. Set to true if one or more fields are marked for update.
1042          if (!isset($shouldupdate)) {
1043              $shouldupdate = false;
1044              foreach ($coursefields as $field) {
1045                  $shouldupdate = $shouldupdate || $this->get_config('course_'.$field.'_updateonsync');
1046              }
1047          }
1048  
1049          // If we should not update return immediately.
1050          if (!$shouldupdate) {
1051              return false;
1052          }
1053  
1054          require_once("$CFG->dirroot/course/lib.php");
1055          $courseupdated = false;
1056          $updatedcourse = new stdClass();
1057          $updatedcourse->id = $course->id;
1058  
1059          // Update course fields if necessary.
1060          foreach ($coursefields as $field) {
1061              // If field is marked to be updated on sync && field data was changed update it.
1062              if ($this->get_config('course_'.$field.'_updateonsync')
1063                      && isset($externalcourse[$this->get_config('course_'.$field)][0])
1064                      && $course->{$field} != $externalcourse[$this->get_config('course_'.$field)][0]) {
1065                  $updatedcourse->{$field} = $externalcourse[$this->get_config('course_'.$field)][0];
1066                  $courseupdated = true;
1067              }
1068          }
1069  
1070          if (!$courseupdated) {
1071              $trace->output(get_string('courseupdateskipped', 'enrol_ldap', $course));
1072              return false;
1073          }
1074  
1075          // Do not allow empty fullname or shortname.
1076          if ((isset($updatedcourse->fullname) && empty($updatedcourse->fullname))
1077                  || (isset($updatedcourse->shortname) && empty($updatedcourse->shortname))) {
1078              // We are in trouble!
1079              $trace->output(get_string('cannotupdatecourse', 'enrol_ldap', $course));
1080              return false;
1081          }
1082  
1083          // Check if the shortname already exists if it does - skip course updating.
1084          if (isset($updatedcourse->shortname)
1085                  && $DB->record_exists('course', array('shortname' => $updatedcourse->shortname))) {
1086              $trace->output(get_string('cannotupdatecourse_duplicateshortname', 'enrol_ldap', $course));
1087              return false;
1088          }
1089  
1090          // Finally - update course in DB.
1091          update_course($updatedcourse);
1092          $trace->output(get_string('courseupdated', 'enrol_ldap', $course));
1093  
1094          return true;
1095      }
1096  
1097      /**
1098       * Automatic enrol sync executed during restore.
1099       * Useful for automatic sync by course->idnumber or course category.
1100       * @param stdClass $course course record
1101       */
1102      public function restore_sync_course($course) {
1103          // TODO: this can not work because restore always nukes the course->idnumber, do not ask me why (MDL-37312)
1104          // NOTE: for now restore does not do any real logging yet, let's do the same here...
1105          $trace = new error_log_progress_trace();
1106          $this->sync_enrolments($trace, $course->id);
1107      }
1108  
1109      /**
1110       * Restore instance and map settings.
1111       *
1112       * @param restore_enrolments_structure_step $step
1113       * @param stdClass $data
1114       * @param stdClass $course
1115       * @param int $oldid
1116       */
1117      public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
1118          global $DB;
1119          // There is only 1 ldap enrol instance per course.
1120          if ($instances = $DB->get_records('enrol', array('courseid'=>$data->courseid, 'enrol'=>'ldap'), 'id')) {
1121              $instance = reset($instances);
1122              $instanceid = $instance->id;
1123          } else {
1124              $instanceid = $this->add_instance($course, (array)$data);
1125          }
1126          $step->set_mapping('enrol', $oldid, $instanceid);
1127      }
1128  
1129      /**
1130       * Restore user enrolment.
1131       *
1132       * @param restore_enrolments_structure_step $step
1133       * @param stdClass $data
1134       * @param stdClass $instance
1135       * @param int $oldinstancestatus
1136       * @param int $userid
1137       */
1138      public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
1139          global $DB;
1140  
1141          if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_UNENROL) {
1142              // Enrolments were already synchronised in restore_instance(), we do not want any suspended leftovers.
1143  
1144          } else if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_KEEP) {
1145              if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1146                  $this->enrol_user($instance, $userid, null, 0, 0, $data->status);
1147              }
1148  
1149          } else {
1150              if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1151                  $this->enrol_user($instance, $userid, null, 0, 0, ENROL_USER_SUSPENDED);
1152              }
1153          }
1154      }
1155  
1156      /**
1157       * Restore role assignment.
1158       *
1159       * @param stdClass $instance
1160       * @param int $roleid
1161       * @param int $userid
1162       * @param int $contextid
1163       */
1164      public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
1165          global $DB;
1166  
1167          if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_UNENROL or $this->get_config('unenrolaction') == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
1168              // Skip any roles restore, they should be already synced automatically.
1169              return;
1170          }
1171  
1172          // Just restore every role.
1173          if ($DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1174              role_assign($roleid, $userid, $contextid, 'enrol_'.$instance->enrol, $instance->id);
1175          }
1176      }
1177  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1