Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/enrol/ldap/lib.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00031 defined('MOODLE_INTERNAL') || die();
00032 
00033 class enrol_ldap_plugin extends enrol_plugin {
00034     protected $enrol_localcoursefield = 'idnumber';
00035     protected $enroltype = 'enrol_ldap';
00036     protected $errorlogtag = '[ENROL LDAP] ';
00037 
00043     public function __construct() {
00044         global $CFG;
00045         require_once($CFG->libdir.'/ldaplib.php');
00046 
00047         // Do our own stuff to fix the config (it's easier to do it
00048         // here than using the admin settings infrastructure). We
00049         // don't call $this->set_config() for any of the 'fixups'
00050         // (except the objectclass, as it's critical) because the user
00051         // didn't specify any values and relied on the default values
00052         // defined for the user type she chose.
00053         $this->load_config();
00054 
00055         // Make sure we get sane defaults for critical values.
00056         $this->config->ldapencoding = $this->get_config('ldapencoding', 'utf-8');
00057         $this->config->user_type = $this->get_config('user_type', 'default');
00058 
00059         $ldap_usertypes = ldap_supported_usertypes();
00060         $this->config->user_type_name = $ldap_usertypes[$this->config->user_type];
00061         unset($ldap_usertypes);
00062 
00063         $default = ldap_getdefaults();
00064         // Remove the objectclass default, as the values specified there are for
00065         // users, and we are dealing with groups here.
00066         unset($default['objectclass']);
00067 
00068         // Use defaults if values not given. Dont use this->get_config()
00069         // here to be able to check for 0 and false values too.
00070         foreach ($default as $key => $value) {
00071             // Watch out - 0, false are correct values too, so we can't use $this->get_config()
00072             if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
00073                 $this->config->{$key} = $value[$this->config->user_type];
00074             }
00075         }
00076 
00077         if (empty($this->config->objectclass)) {
00078             // Can't send empty filter. Fix it for now and future occasions
00079             $this->set_config('objectclass', '(objectClass=*)');
00080         } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
00081             // Value is 'objectClass=some-string-here', so just add ()
00082             // around the value (filter _must_ have them).
00083             // Fix it for now and future occasions
00084             $this->set_config('objectclass', '('.$this->config->objectclass.')');
00085         } else if (stripos($this->config->objectclass, '(') !== 0) {
00086             // Value is 'some-string-not-starting-with-left-parentheses',
00087             // which is assumed to be the objectClass matching value.
00088             // So build a valid filter with it.
00089             $this->set_config('objectclass', '(objectClass='.$this->config->objectclass.')');
00090         } else {
00091             // There is an additional possible value
00092             // '(some-string-here)', that can be used to specify any
00093             // valid filter string, to select subsets of users based
00094             // on any criteria. For example, we could select the users
00095             // whose objectClass is 'user' and have the
00096             // 'enabledMoodleUser' attribute, with something like:
00097             //
00098             //   (&(objectClass=user)(enabledMoodleUser=1))
00099             //
00100             // In this particular case we don't need to do anything,
00101             // so leave $this->config->objectclass as is.
00102         }
00103     }
00104 
00111     public function instance_deleteable($instance) {
00112         if (!enrol_is_enabled('ldap')) {
00113             return true;
00114         }
00115 
00116         if (!$this->get_config('ldap_host') or !$this->get_config('objectclass') or !$this->get_config('course_idnumber')) {
00117             return true;
00118         }
00119 
00120         // TODO: connect to external system and make sure no users are to be enrolled in this course
00121         return false;
00122     }
00123 
00131     public function sync_user_enrolments($user) {
00132         global $DB;
00133 
00134         $ldapconnection = $this->ldap_connect();
00135         if (!$ldapconnection) {
00136             return;
00137         }
00138 
00139         if (!is_object($user) or !property_exists($user, 'id')) {
00140             throw new coding_exception('Invalid $user parameter in sync_user_enrolments()');
00141         }
00142 
00143         if (!property_exists($user, 'idnumber')) {
00144             debugging('Invalid $user parameter in sync_user_enrolments(), missing idnumber');
00145             $user = $DB->get_record('user', array('id'=>$user->id));
00146         }
00147 
00148         // We may need a lot of memory here
00149         @set_time_limit(0);
00150         raise_memory_limit(MEMORY_HUGE);
00151 
00152         // Get enrolments for each type of role.
00153         $roles = get_all_roles();
00154         $enrolments = array();
00155         foreach($roles as $role) {
00156             // Get external enrolments according to LDAP server
00157             $enrolments[$role->id]['ext'] = $this->find_ext_enrolments($ldapconnection, $user->idnumber, $role);
00158 
00159             // Get the list of current user enrolments that come from LDAP
00160             $sql= "SELECT e.courseid, ue.status, e.id as enrolid, c.shortname
00161                      FROM {user} u
00162                      JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = 'enrol_ldap' AND ra.roleid = :roleid)
00163                      JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)
00164                      JOIN {enrol} e ON (e.id = ue.enrolid)
00165                      JOIN {course} c ON (c.id = e.courseid)
00166                     WHERE u.deleted = 0 AND u.id = :userid";
00167             $params = array ('roleid'=>$role->id, 'userid'=>$user->id);
00168             $enrolments[$role->id]['current'] = $DB->get_records_sql($sql, $params);
00169         }
00170 
00171         $ignorehidden = $this->get_config('ignorehiddencourses');
00172         $courseidnumber = $this->get_config('course_idnumber');
00173         foreach($roles as $role) {
00174             foreach ($enrolments[$role->id]['ext'] as $enrol) {
00175                 $course_ext_id = $enrol[$courseidnumber][0];
00176                 if (empty($course_ext_id)) {
00177                     error_log($this->errorlogtag.get_string('extcourseidinvalid', 'enrol_ldap'));
00178                     continue; // Next; skip this one!
00179                 }
00180 
00181                 // Create the course if required
00182                 $course = $DB->get_record('course', array($this->enrol_localcoursefield=>$course_ext_id));
00183                 if (empty($course)) { // Course doesn't exist
00184                     if ($this->get_config('autocreate')) { // Autocreate
00185                         error_log($this->errorlogtag.get_string('createcourseextid', 'enrol_ldap',
00186                                                                 array('courseextid'=>$course_ext_id)));
00187                         if ($newcourseid = $this->create_course($enrol)) {
00188                             $course = $DB->get_record('course', array('id'=>$newcourseid));
00189                         }
00190                     } else {
00191                         error_log($this->errorlogtag.get_string('createnotcourseextid', 'enrol_ldap',
00192                                                                 array('courseextid'=>$course_ext_id)));
00193                         continue; // Next; skip this one!
00194                     }
00195                 }
00196 
00197                 // Deal with enrolment in the moodle db
00198                 // Add necessary enrol instance if not present yet;
00199                 $sql = "SELECT c.id, c.visible, e.id as enrolid
00200                           FROM {course} c
00201                           JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'ldap')
00202                          WHERE c.id = :courseid";
00203                 $params = array('courseid'=>$course->id);
00204                 if (!($course_instance = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE))) {
00205                     $course_instance = new stdClass();
00206                     $course_instance->id = $course->id;
00207                     $course_instance->visible = $course->visible;
00208                     $course_instance->enrolid = $this->add_instance($course_instance);
00209                 }
00210 
00211                 if (!$instance = $DB->get_record('enrol', array('id'=>$course_instance->enrolid))) {
00212                     continue; // Weird; skip this one.
00213                 }
00214 
00215                 if ($ignorehidden && !$course_instance->visible) {
00216                     continue;
00217                 }
00218 
00219                 if (empty($enrolments[$role->id]['current'][$course->id])) {
00220                     // Enrol the user in the given course, with that role.
00221                     $this->enrol_user($instance, $user->id, $role->id);
00222                     // Make sure we set the enrolment status to active. If the user wasn't
00223                     // previously enrolled to the course, enrol_user() sets it. But if we
00224                     // configured the plugin to suspend the user enrolments _AND_ remove
00225                     // the role assignments on external unenrol, then enrol_user() doesn't
00226                     // set it back to active on external re-enrolment. So set it
00227                     // unconditionnally to cover both cases.
00228                     $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id));
00229                     error_log($this->errorlogtag.get_string('enroluser', 'enrol_ldap',
00230                                                             array('user_username'=> $user->username,
00231                                                                   'course_shortname'=>$course->shortname,
00232                                                                   'course_id'=>$course->id)));
00233                 } else {
00234                     if ($enrolments[$role->id]['current'][$course->id]->status == ENROL_USER_SUSPENDED) {
00235                         // Reenable enrolment that was previously disabled. Enrolment refreshed
00236                         $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id));
00237                         error_log($this->errorlogtag.get_string('enroluserenable', 'enrol_ldap',
00238                                                                 array('user_username'=> $user->username,
00239                                                                       'course_shortname'=>$course->shortname,
00240                                                                       'course_id'=>$course->id)));
00241                     }
00242                 }
00243 
00244                 // Remove this course from the current courses, to be able to detect
00245                 // which current courses should be unenroled from when we finish processing
00246                 // external enrolments.
00247                 unset($enrolments[$role->id]['current'][$course->id]);
00248             }
00249 
00250             // Deal with unenrolments.
00251             $transaction = $DB->start_delegated_transaction();
00252             foreach ($enrolments[$role->id]['current'] as $course) {
00253                 $context = get_context_instance(CONTEXT_COURSE, $course->courseid);
00254                 $instance = $DB->get_record('enrol', array('id'=>$course->enrolid));
00255                 switch ($this->get_config('unenrolaction')) {
00256                     case ENROL_EXT_REMOVED_UNENROL:
00257                         $this->unenrol_user($instance, $user->id);
00258                         error_log($this->errorlogtag.get_string('extremovedunenrol', 'enrol_ldap',
00259                                                                 array('user_username'=> $user->username,
00260                                                                       'course_shortname'=>$course->shortname,
00261                                                                       'course_id'=>$course->courseid)));
00262                         break;
00263                     case ENROL_EXT_REMOVED_KEEP:
00264                         // Keep - only adding enrolments
00265                         break;
00266                     case ENROL_EXT_REMOVED_SUSPEND:
00267                         if ($course->status != ENROL_USER_SUSPENDED) {
00268                             $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id));
00269                             error_log($this->errorlogtag.get_string('extremovedsuspend', 'enrol_ldap',
00270                                                                     array('user_username'=> $user->username,
00271                                                                           'course_shortname'=>$course->shortname,
00272                                                                           'course_id'=>$course->courseid)));
00273                         }
00274                         break;
00275                     case ENROL_EXT_REMOVED_SUSPENDNOROLES:
00276                         if ($course->status != ENROL_USER_SUSPENDED) {
00277                             $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id));
00278                         }
00279                         role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id));
00280                         error_log($this->errorlogtag.get_string('extremovedsuspendnoroles', 'enrol_ldap',
00281                                                                 array('user_username'=> $user->username,
00282                                                                       'course_shortname'=>$course->shortname,
00283                                                                       'course_id'=>$course->courseid)));
00284                         break;
00285                 }
00286             }
00287             $transaction->allow_commit();
00288         }
00289 
00290         $this->ldap_close($ldapconnection);
00291     }
00292 
00299     public function sync_enrolments() {
00300         global $CFG, $DB;
00301 
00302         $ldapconnection = $this->ldap_connect();
00303         if (!$ldapconnection) {
00304             return;
00305         }
00306 
00307         // we may need a lot of memory here
00308         @set_time_limit(0);
00309         raise_memory_limit(MEMORY_HUGE);
00310 
00311         // Get enrolments for each type of role.
00312         $roles = get_all_roles();
00313         $enrolments = array();
00314         foreach($roles as $role) {
00315             // Get all contexts
00316             $ldap_contexts = explode(';', $this->config->{'contexts_role'.$role->id});
00317 
00318             // Get all the fields we will want for the potential course creation
00319             // as they are light. Don't get membership -- potentially a lot of data.
00320             $ldap_fields_wanted = array('dn', $this->config->course_idnumber);
00321             if (!empty($this->config->course_fullname)) {
00322                 array_push($ldap_fields_wanted, $this->config->course_fullname);
00323             }
00324             if (!empty($this->config->course_shortname)) {
00325                 array_push($ldap_fields_wanted, $this->config->course_shortname);
00326             }
00327             if (!empty($this->config->course_summary)) {
00328                 array_push($ldap_fields_wanted, $this->config->course_summary);
00329             }
00330             array_push($ldap_fields_wanted, $this->config->{'memberattribute_role'.$role->id});
00331 
00332             // Define the search pattern
00333             $ldap_search_pattern = $this->config->objectclass;
00334 
00335             foreach ($ldap_contexts as $ldap_context) {
00336                 $ldap_context = trim($ldap_context);
00337                 if (empty($ldap_context)) {
00338                     continue; // Next;
00339                 }
00340 
00341                 if ($this->config->course_search_sub) {
00342                     // Use ldap_search to find first user from subtree
00343                     $ldap_result = @ldap_search($ldapconnection,
00344                                                 $ldap_context,
00345                                                 $ldap_search_pattern,
00346                                                 $ldap_fields_wanted);
00347                 } else {
00348                     // Search only in this context
00349                     $ldap_result = @ldap_list($ldapconnection,
00350                                               $ldap_context,
00351                                               $ldap_search_pattern,
00352                                               $ldap_fields_wanted);
00353                 }
00354                 if (!$ldap_result) {
00355                     continue; // Next
00356                 }
00357 
00358                 // Check and push results
00359                 $records = ldap_get_entries($ldapconnection, $ldap_result);
00360 
00361                 // LDAP libraries return an odd array, really. fix it:
00362                 $flat_records = array();
00363                 for ($c = 0; $c < $records['count']; $c++) {
00364                     array_push($flat_records, $records[$c]);
00365                 }
00366                 // Free some mem
00367                 unset($records);
00368 
00369                 if (count($flat_records)) {
00370                     $ignorehidden = $this->get_config('ignorehiddencourses');
00371                     foreach($flat_records as $course) {
00372                         $course = array_change_key_case($course, CASE_LOWER);
00373                         $idnumber = $course{$this->config->course_idnumber}[0];
00374                         print_string('synccourserole', 'enrol_ldap',
00375                                      array('idnumber'=>$idnumber, 'role_shortname'=>$role->shortname));
00376 
00377                         // Does the course exist in moodle already?
00378                         $course_obj = $DB->get_record('course', array($this->enrol_localcoursefield=>$idnumber));
00379                         if (empty($course_obj)) { // Course doesn't exist
00380                             if ($this->get_config('autocreate')) { // Autocreate
00381                                 error_log($this->errorlogtag.get_string('createcourseextid', 'enrol_ldap',
00382                                                                         array('courseextid'=>$idnumber)));
00383                                 if ($newcourseid = $this->create_course($course)) {
00384                                     $course_obj = $DB->get_record('course', array('id'=>$newcourseid));
00385                                 }
00386                             } else {
00387                                 error_log($this->errorlogtag.get_string('createnotcourseextid', 'enrol_ldap',
00388                                                                         array('courseextid'=>$idnumber)));
00389                                 continue; // Next; skip this one!
00390                             }
00391                         }
00392 
00393                         // Enrol & unenrol
00394 
00395                         // Pull the ldap membership into a nice array
00396                         // this is an odd array -- mix of hash and array --
00397                         $ldapmembers = array();
00398 
00399                         if (array_key_exists('memberattribute_role'.$role->id, $this->config)
00400                             && !empty($this->config->{'memberattribute_role'.$role->id})
00401                             && !empty($course[$this->config->{'memberattribute_role'.$role->id}])) { // May have no membership!
00402 
00403                             $ldapmembers = $course[$this->config->{'memberattribute_role'.$role->id}];
00404                             unset($ldapmembers['count']); // Remove oddity ;)
00405 
00406                             // If we have enabled nested groups, we need to expand
00407                             // the groups to get the real user list. We need to do
00408                             // this before dealing with 'memberattribute_isdn'.
00409                             if ($this->config->nested_groups) {
00410                                 $users = array();
00411                                 foreach ($ldapmembers as $ldapmember) {
00412                                     $grpusers = $this->ldap_explode_group($ldapconnection,
00413                                                                           $ldapmember,
00414                                                                           $this->config->{'memberattribute_role'.$role->id});
00415 
00416                                     $users = array_merge($users, $grpusers);
00417                                 }
00418                                 $ldapmembers = array_unique($users); // There might be duplicates.
00419                             }
00420 
00421                             // Deal with the case where the member attribute holds distinguished names,
00422                             // but only if the user attribute is not a distinguished name itself.
00423                             if ($this->config->memberattribute_isdn
00424                                 && ($this->config->idnumber_attribute !== 'dn')
00425                                 && ($this->config->idnumber_attribute !== 'distinguishedname')) {
00426                                 // We need to retrieve the idnumber for all the users in $ldapmembers,
00427                                 // as the idnumber does not match their dn and we get dn's from membership.
00428                                 $memberidnumbers = array();
00429                                 foreach ($ldapmembers as $ldapmember) {
00430                                     $result = ldap_read($ldapconnection, $ldapmember, '(objectClass=*)',
00431                                                         array($this->config->idnumber_attribute));
00432                                     $entry = ldap_first_entry($ldapconnection, $result);
00433                                     $values = ldap_get_values($ldapconnection, $entry, $this->config->idnumber_attribute);
00434                                     array_push($memberidnumbers, $values[0]);
00435                                 }
00436 
00437                                 $ldapmembers = $memberidnumbers;
00438                             }
00439                         }
00440 
00441                         // Prune old ldap enrolments
00442                         // hopefully they'll fit in the max buffer size for the RDBMS
00443                         $sql= "SELECT u.id as userid, u.username, ue.status,
00444                                       ra.contextid, ra.itemid as instanceid
00445                                  FROM {user} u
00446                                  JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = 'enrol_ldap' AND ra.roleid = :roleid)
00447                                  JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)
00448                                  JOIN {enrol} e ON (e.id = ue.enrolid)
00449                                 WHERE u.deleted = 0 AND e.courseid = :courseid ";
00450                         $params = array('roleid'=>$role->id, 'courseid'=>$course_obj->id);
00451                         $context = get_context_instance(CONTEXT_COURSE, $course_obj->id);
00452                         if (!empty($ldapmembers)) {
00453                             list($ldapml, $params2) = $DB->get_in_or_equal($ldapmembers, SQL_PARAMS_NAMED, 'm', false);
00454                             $sql .= "AND u.idnumber $ldapml";
00455                             $params = array_merge($params, $params2);
00456                             unset($params2);
00457                         } else {
00458                             $shortname = format_string($course_obj->shortname, true, array('context' => $context));
00459                             print_string('emptyenrolment', 'enrol_ldap',
00460                                          array('role_shortname'=> $role->shortname,
00461                                                'course_shortname' => $shortname));
00462                         }
00463                         $todelete = $DB->get_records_sql($sql, $params);
00464 
00465                         if (!empty($todelete)) {
00466                             $transaction = $DB->start_delegated_transaction();
00467                             foreach ($todelete as $row) {
00468                                 $instance = $DB->get_record('enrol', array('id'=>$row->instanceid));
00469                                 switch ($this->get_config('unenrolaction')) {
00470                                 case ENROL_EXT_REMOVED_UNENROL:
00471                                     $this->unenrol_user($instance, $row->userid);
00472                                     error_log($this->errorlogtag.get_string('extremovedunenrol', 'enrol_ldap',
00473                                                                             array('user_username'=> $row->username,
00474                                                                                   'course_shortname'=>$course_obj->shortname,
00475                                                                                   'course_id'=>$course_obj->id)));
00476                                     break;
00477                                 case ENROL_EXT_REMOVED_KEEP:
00478                                     // Keep - only adding enrolments
00479                                     break;
00480                                 case ENROL_EXT_REMOVED_SUSPEND:
00481                                     if ($row->status != ENROL_USER_SUSPENDED) {
00482                                         $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid));
00483                                         error_log($this->errorlogtag.get_string('extremovedsuspend', 'enrol_ldap',
00484                                                                                 array('user_username'=> $row->username,
00485                                                                                       'course_shortname'=>$course_obj->shortname,
00486                                                                                       'course_id'=>$course_obj->id)));
00487                                     }
00488                                     break;
00489                                 case ENROL_EXT_REMOVED_SUSPENDNOROLES:
00490                                     if ($row->status != ENROL_USER_SUSPENDED) {
00491                                         $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid));
00492                                     }
00493                                     role_unassign_all(array('contextid'=>$row->contextid, 'userid'=>$row->userid, 'component'=>'enrol_ldap', 'itemid'=>$instance->id));
00494                                     error_log($this->errorlogtag.get_string('extremovedsuspendnoroles', 'enrol_ldap',
00495                                                                             array('user_username'=> $row->username,
00496                                                                                   'course_shortname'=>$course_obj->shortname,
00497                                                                                   'course_id'=>$course_obj->id)));
00498                                     break;
00499                                 }
00500                             }
00501                             $transaction->allow_commit();
00502                         }
00503 
00504                         // Insert current enrolments
00505                         // bad we can't do INSERT IGNORE with postgres...
00506 
00507                         // Add necessary enrol instance if not present yet;
00508                         $sql = "SELECT c.id, c.visible, e.id as enrolid
00509                                   FROM {course} c
00510                                   JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'ldap')
00511                                  WHERE c.id = :courseid";
00512                         $params = array('courseid'=>$course_obj->id);
00513                         if (!($course_instance = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE))) {
00514                             $course_instance = new stdClass();
00515                             $course_instance->id = $course_obj->id;
00516                             $course_instance->visible = $course_obj->visible;
00517                             $course_instance->enrolid = $this->add_instance($course_instance);
00518                         }
00519 
00520                         if (!$instance = $DB->get_record('enrol', array('id'=>$course_instance->enrolid))) {
00521                             continue; // Weird; skip this one.
00522                         }
00523 
00524                         if ($ignorehidden && !$course_instance->visible) {
00525                             continue;
00526                         }
00527 
00528                         $transaction = $DB->start_delegated_transaction();
00529                         foreach ($ldapmembers as $ldapmember) {
00530                             $sql = 'SELECT id,username,1 FROM {user} WHERE idnumber = ? AND deleted = 0';
00531                             $member = $DB->get_record_sql($sql, array($ldapmember));
00532                             if(empty($member) || empty($member->id)){
00533                                 print_string ('couldnotfinduser', 'enrol_ldap', $ldapmember);
00534                                 continue;
00535                             }
00536 
00537                             $sql= "SELECT ue.status
00538                                      FROM {user_enrolments} ue
00539                                      JOIN {enrol} e ON (e.id = ue.enrolid)
00540                                      JOIN {role_assignments} ra ON (ra.itemid = e.id AND ra.component = 'enrol_ldap')
00541                                     WHERE e.courseid = :courseid AND ue.userid = :userid";
00542                             $params = array('courseid'=>$course_obj->id, 'userid'=>$member->id);
00543                             $userenrolment = $DB->get_record_sql($sql, $params);
00544 
00545                             if(empty($userenrolment)) {
00546                                 $this->enrol_user($instance, $member->id, $role->id);
00547                                 // Make sure we set the enrolment status to active. If the user wasn't
00548                                 // previously enrolled to the course, enrol_user() sets it. But if we
00549                                 // configured the plugin to suspend the user enrolments _AND_ remove
00550                                 // the role assignments on external unenrol, then enrol_user() doesn't
00551                                 // set it back to active on external re-enrolment. So set it
00552                                 // unconditionnally to cover both cases.
00553                                 $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id));
00554                                 error_log($this->errorlogtag.get_string('enroluser', 'enrol_ldap',
00555                                                                         array('user_username'=> $member->username,
00556                                                                               'course_shortname'=>$course_obj->shortname,
00557                                                                               'course_id'=>$course_obj->id)));
00558 
00559                             } else {
00560                                 if ($userenrolment->status == ENROL_USER_SUSPENDED) {
00561                                     // Reenable enrolment that was previously disabled. Enrolment refreshed
00562                                     $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id));
00563                                     error_log($this->errorlogtag.get_string('enroluserenable', 'enrol_ldap',
00564                                                                             array('user_username'=> $member->username,
00565                                                                                   'course_shortname'=>$course_obj->shortname,
00566                                                                                   'course_id'=>$course_obj->id)));
00567                                 }
00568                             }
00569                         }
00570                         $transaction->allow_commit();
00571                     }
00572                 }
00573             }
00574         }
00575         @$this->ldap_close();
00576     }
00577 
00584     protected function ldap_connect() {
00585         global $CFG;
00586         require_once($CFG->libdir.'/ldaplib.php');
00587 
00588         // Cache ldap connections. They are expensive to set up
00589         // and can drain the TCP/IP ressources on the server if we
00590         // are syncing a lot of users (as we try to open a new connection
00591         // to get the user details). This is the least invasive way
00592         // to reuse existing connections without greater code surgery.
00593         if(!empty($this->ldapconnection)) {
00594             $this->ldapconns++;
00595             return $this->ldapconnection;
00596         }
00597 
00598         if ($ldapconnection = ldap_connect_moodle($this->get_config('host_url'), $this->get_config('ldap_version'),
00599                                                   $this->get_config('user_type'), $this->get_config('bind_dn'),
00600                                                   $this->get_config('bind_pw'), $this->get_config('opt_deref'),
00601                                                   $debuginfo)) {
00602             $this->ldapconns = 1;
00603             $this->ldapconnection = $ldapconnection;
00604             return $ldapconnection;
00605         }
00606 
00607         // Log the problem, but don't show it to the user. She doesn't
00608         // even have a chance to see it, as we redirect instantly to
00609         // the user/front page.
00610         error_log($this->errorlogtag.$debuginfo);
00611 
00612         return false;
00613     }
00614 
00619     protected function ldap_close() {
00620         $this->ldapconns--;
00621         if($this->ldapconns == 0) {
00622             @ldap_close($this->ldapconnection);
00623             unset($this->ldapconnection);
00624         }
00625     }
00626 
00636     protected function find_ext_enrolments ($ldapconnection, $memberuid, $role) {
00637         global $CFG;
00638         require_once($CFG->libdir.'/ldaplib.php');
00639 
00640         if (empty($memberuid)) {
00641             // No "idnumber" stored for this user, so no LDAP enrolments
00642             return array();
00643         }
00644 
00645         $ldap_contexts = trim($this->get_config('contexts_role'.$role->id));
00646         if (empty($ldap_contexts)) {
00647             // No role contexts, so no LDAP enrolments
00648             return array();
00649         }
00650 
00651         $textlib = textlib_get_instance();
00652         $extmemberuid = $textlib->convert($memberuid, 'utf-8', $this->get_config('ldapencoding'));
00653 
00654         if($this->get_config('memberattribute_isdn')) {
00655             if (!($extmemberuid = $this->ldap_find_userdn ($ldapconnection, $extmemberuid))) {
00656                 return array();
00657             }
00658         }
00659 
00660         $ldap_search_pattern = '';
00661         if($this->get_config('nested_groups')) {
00662             $usergroups = $this->ldap_find_user_groups($ldapconnection, $extmemberuid);
00663             if(count($usergroups) > 0) {
00664                 foreach ($usergroups as $group) {
00665                     $ldap_search_pattern .= '('.$this->get_config('memberattribute_role'.$role->id).'='.$group.')';
00666                 }
00667             }
00668         }
00669 
00670         // Default return value
00671         $courses = array();
00672 
00673         // Get all the fields we will want for the potential course creation
00674         // as they are light. don't get membership -- potentially a lot of data.
00675         $ldap_fields_wanted = array('dn', $this->get_config('course_idnumber'));
00676         $fullname  = $this->get_config('course_fullname');
00677         $shortname = $this->get_config('course_shortname');
00678         $summary   = $this->get_config('course_summary');
00679         if (isset($fullname)) {
00680             array_push($ldap_fields_wanted, $fullname);
00681         }
00682         if (isset($shortname)) {
00683             array_push($ldap_fields_wanted, $shortname);
00684         }
00685         if (isset($summary)) {
00686             array_push($ldap_fields_wanted, $summary);
00687         }
00688 
00689         // Define the search pattern
00690         if (empty($ldap_search_pattern)) {
00691             $ldap_search_pattern = '('.$this->get_config('memberattribute_role'.$role->id).'='.ldap_filter_addslashes($extmemberuid).')';
00692         } else {
00693             $ldap_search_pattern = '(|' . $ldap_search_pattern .
00694                                        '('.$this->get_config('memberattribute_role'.$role->id).'='.ldap_filter_addslashes($extmemberuid).')' .
00695                                    ')';
00696         }
00697         $ldap_search_pattern='(&'.$this->get_config('objectclass').$ldap_search_pattern.')';
00698 
00699         // Get all contexts and look for first matching user
00700         $ldap_contexts = explode(';', $ldap_contexts);
00701         foreach ($ldap_contexts as $context) {
00702             $context = trim($context);
00703             if (empty($context)) {
00704                 continue;
00705             }
00706 
00707             if ($this->get_config('course_search_sub')) {
00708                 // Use ldap_search to find first user from subtree
00709                 $ldap_result = @ldap_search($ldapconnection,
00710                                             $context,
00711                                             $ldap_search_pattern,
00712                                             $ldap_fields_wanted);
00713             } else {
00714                 // Search only in this context
00715                 $ldap_result = @ldap_list($ldapconnection,
00716                                           $context,
00717                                           $ldap_search_pattern,
00718                                           $ldap_fields_wanted);
00719             }
00720 
00721             if (!$ldap_result) {
00722                 continue;
00723             }
00724 
00725             // Check and push results. ldap_get_entries() already
00726             // lowercases the attribute index, so there's no need to
00727             // use array_change_key_case() later.
00728             $records = ldap_get_entries($ldapconnection, $ldap_result);
00729 
00730             // LDAP libraries return an odd array, really. Fix it.
00731             $flat_records = array();
00732             for ($c = 0; $c < $records['count']; $c++) {
00733                 array_push($flat_records, $records[$c]);
00734             }
00735             unset($records);
00736 
00737             if (count($flat_records)) {
00738                 $courses = array_merge($courses, $flat_records);
00739             }
00740         }
00741 
00742         return $courses;
00743     }
00744 
00754     protected function ldap_find_userdn($ldapconnection, $userid) {
00755         global $CFG;
00756         require_once($CFG->libdir.'/ldaplib.php');
00757 
00758         $ldap_contexts = explode(';', $this->get_config('user_contexts'));
00759         $ldap_defaults = ldap_getdefaults();
00760 
00761         return ldap_find_userdn($ldapconnection, $userid, $ldap_contexts,
00762                                 '(objectClass='.$ldap_defaults['objectclass'][$this->get_config('user_type')].')',
00763                                 $this->get_config('idnumber_attribute'), $this->get_config('user_search_sub'));
00764     }
00765 
00774     protected function ldap_find_user_groups($ldapconnection, $memberdn) {
00775         $groups = array();
00776 
00777         $this->ldap_find_user_groups_recursively($ldapconnection, $memberdn, $groups);
00778         return $groups;
00779     }
00780 
00791     protected function ldap_find_user_groups_recursively($ldapconnection, $memberdn, &$membergroups) {
00792         $result = @ldap_read ($ldapconnection, $memberdn, '(objectClass=*)', array($this->get_config('group_memberofattribute')));
00793         if (!$result) {
00794             return;
00795         }
00796 
00797         if ($entry = ldap_first_entry($ldapconnection, $result)) {
00798             do {
00799                 $attributes = ldap_get_attributes($ldapconnection, $entry);
00800                 for ($j = 0; $j < $attributes['count']; $j++) {
00801                     $groups = ldap_get_values_len($ldapconnection, $entry, $attributes[$j]);
00802                     foreach ($groups as $key => $group) {
00803                         if ($key === 'count') {  // Skip the entries count
00804                             continue;
00805                         }
00806                         if(!in_array($group, $membergroups)) {
00807                             // Only push and recurse if we haven't 'seen' this group before
00808                             // to prevent loops (MS Active Directory allows them!!).
00809                             array_push($membergroups, $group);
00810                             $this->ldap_find_user_groups_recursively($ldapconnection, $group, $membergroups);
00811                         }
00812                     }
00813                 }
00814             }
00815             while ($entry = ldap_next_entry($ldapconnection, $entry));
00816         }
00817     }
00818 
00831     protected function ldap_explode_group($ldapconnection, $group, $memberattribute) {
00832         switch ($this->get_config('user_type')) {
00833             case 'ad':
00834                 // $group is already the distinguished name to search.
00835                 $dn = $group;
00836 
00837                 $result = ldap_read($ldapconnection, $dn, '(objectClass=*)', array('objectClass'));
00838                 $entry = ldap_first_entry($ldapconnection, $result);
00839                 $objectclass = ldap_get_values($ldapconnection, $entry, 'objectClass');
00840 
00841                 if (!in_array('group', $objectclass)) {
00842                     // Not a group, so return immediately.
00843                     return array($group);
00844                 }
00845 
00846                 $result = ldap_read($ldapconnection, $dn, '(objectClass=*)', array($memberattribute));
00847                 $entry = ldap_first_entry($ldapconnection, $result);
00848                 $members = @ldap_get_values($ldapconnection, $entry, $memberattribute); // Can be empty and throws a warning
00849                 if ($members['count'] == 0) {
00850                     // There are no members in this group, return nothing.
00851                     return array();
00852                 }
00853                 unset($members['count']);
00854 
00855                 $users = array();
00856                 foreach ($members as $member) {
00857                     $group_members = $this->ldap_explode_group($ldapconnection, $member, $memberattribute);
00858                     $users = array_merge($users, $group_members);
00859                 }
00860 
00861                 return ($users);
00862                 break;
00863             default:
00864                 error_log($this->errorlogtag.get_string('explodegroupusertypenotsupported', 'enrol_ldap',
00865                                                         $this->get_config('user_type_name')));
00866 
00867                 return array($group);
00868         }
00869     }
00870 
00882     function create_course($course_ext, $skip_fix_course_sortorder=false) {
00883         global $CFG, $DB;
00884 
00885         require_once("$CFG->dirroot/course/lib.php");
00886 
00887         // Override defaults with template course
00888         $template = false;
00889         if ($this->get_config('template')) {
00890             if ($template = $DB->get_record('course', array('shortname'=>$this->get_config('template')))) {
00891                 unset($template->id); // So we are clear to reinsert the record
00892                 unset($template->fullname);
00893                 unset($template->shortname);
00894                 unset($template->idnumber);
00895             }
00896         }
00897         if (!$template) {
00898             $courseconfig = get_config('moodlecourse');
00899             $template = new stdClass();
00900             $template->summary        = '';
00901             $template->summaryformat  = FORMAT_HTML;
00902             $template->format         = $courseconfig->format;
00903             $template->numsections    = $courseconfig->numsections;
00904             $template->hiddensections = $courseconfig->hiddensections;
00905             $template->newsitems      = $courseconfig->newsitems;
00906             $template->showgrades     = $courseconfig->showgrades;
00907             $template->showreports    = $courseconfig->showreports;
00908             $template->maxbytes       = $courseconfig->maxbytes;
00909             $template->groupmode      = $courseconfig->groupmode;
00910             $template->groupmodeforce = $courseconfig->groupmodeforce;
00911             $template->visible        = $courseconfig->visible;
00912             $template->lang           = $courseconfig->lang;
00913             $template->groupmodeforce = $courseconfig->groupmodeforce;
00914         }
00915         $course = $template;
00916 
00917         $course->category = $this->get_config('category');
00918         if (!$DB->record_exists('course_categories', array('id'=>$this->get_config('category')))) {
00919             $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1);
00920             $first = reset($categories);
00921             $course->category = $first->id;
00922         }
00923 
00924         // Override with required ext data
00925         $course->idnumber  = $course_ext[$this->get_config('course_idnumber')][0];
00926         $course->fullname  = $course_ext[$this->get_config('course_fullname')][0];
00927         $course->shortname = $course_ext[$this->get_config('course_shortname')][0];
00928         if (empty($course->idnumber) || empty($course->fullname) || empty($course->shortname)) {
00929             // We are in trouble!
00930             error_log($this->errorlogtag.get_string('cannotcreatecourse', 'enrol_ldap'));
00931             error_log($this->errorlogtag.var_export($course, true));
00932             return false;
00933         }
00934 
00935         $summary = $this->get_config('course_summary');
00936         if (!isset($summary) || empty($course_ext[$summary][0])) {
00937             $course->summary = '';
00938         } else {
00939             $course->summary = $course_ext[$this->get_config('course_summary')][0];
00940         }
00941 
00942         $newcourse = create_course($course);
00943         return $newcourse->id;
00944     }
00945 }
00946 
 All Data Structures Namespaces Files Functions Variables Enumerations