3 require_once("$CFG->dirroot/enrol/enrol.class.php");
4 require_once("$CFG->dirroot/course/lib.php");
5 require_once("$CFG->dirroot/lib/blocklib.php");
6 require_once("$CFG->dirroot/lib/pagelib.php");
9 class enrolment_plugin_ldap
{
13 var $enrol_localcoursefield = 'idnumber';
15 /// Overide the base get_student_courses() function
16 function get_student_courses(&$user) {
18 return $this->get_user_courses($user, 'student');
21 /// Overide the base get_teacher_courses() function
22 function get_teacher_courses(&$user) {
24 return $this->get_user_courses($user, 'teacher');
27 /// Overide the base get_student_courses() function
28 function get_user_courses(&$user, $type) {
31 if(!isset($type) ||
!($type =='student' ||
$type =='teacher' )){
32 error("Bad call to get_user_courses()!");
36 // Connect to the external database
37 $ldap_connection = $this->enrol_ldap_connect();
38 if (!$ldap_connection) {
39 @ldap_close
($ldap_connection);
40 notify("LDAP-module cannot connect to server: $CFG->ldap_host_url");
44 // we are connected OK, continue...
46 /// Find a record in the external database that matches the local course field and local user field
47 /// to the respective remote fields
48 $enrolments = $this->find_ext_enrolments( $ldap_connection,
52 foreach ($enrolments as $enrol){
54 $course_ext_id = $enrol[$CFG->enrol_ldap_course_idnumber
][0];
55 if(empty($course_ext_id)){
56 error_log("The course external id is invalid!\n");
57 continue; // next; skip this one!
60 // create the course ir required
61 $course_obj = get_record( 'course',
62 $this->enrol_localcoursefield
,
65 if (empty($course_obj)){ // course doesn't exist
66 if($CFG->enrol_ldap_autocreate
){ // autocreate
67 error_log("CREATE User $user->username enrolled to a nonexistant course $course_ext_id \n");
68 $newcourseid = $this->create_course($enrol);
69 $course_obj = get_record( 'course',
70 $this->enrol_localcoursefield
,
73 error_log("User $user->username enrolled to a nonexistant course $course_ext_id \n");
75 } else { // the course object exists before we call...
76 if ($course_obj->visible
==0 && $user->{$type}[$course_obj->id
] == 'ldap') {
77 // non-visible courses don't show up in the enrolled
78 // array, so we should skip them --
79 unset($user->{$type}[$course_obj->id
]);
84 /// Add it to new list
85 $newenrolments[$course_obj->id
] = 'ldap';
87 // deal with enrolment in the moodle db
88 if (!empty($course_obj)) { // does course exist now?
89 if(isset($user->{$type}[$course_obj->id
]) && $user->{$type}[$course_obj->id
] == 'ldap'){
90 unset($user->{$type}[$course_obj->id
]); // remove from old list
93 if ($type === 'student') { // enrol
94 error_log("Enrolling student $user->id ($user->username) in course $course_obj->id ($course_obj->shortname) ");
95 if (! enrol_student($user->id
, $course_obj->id
, 0, 0, 'ldap')){
96 error_log("Failed to enrol student $user->id ($user->username) into course $course_obj->id ($course_obj->shortname)");
98 } else if ($type === 'teacher') {
99 error_log("Enrolling teacher $user->id ($user->username) in course $course_obj->id ($course_obj->shortname)");
100 add_teacher($user->id
, $course_obj->id
, 1, '', 0, 0,'ldap');
107 // ok, if there's any thing still left in the $user->student or $user->teacher
108 // array, those are old enrolments that we want to remove (or are they?)
109 if(!empty($user->{$type})){
110 foreach ($user->{$type} as $courseid => $value){
111 if($value === 'ldap'){ // this was a legacy
112 if ($type === 'student') { // enrol
113 unenrol_student($user->id
, $courseid);
114 } else if ($type === 'teacher') {
115 remove_teacher($user->id
, $courseid);
117 unset($user->{$type}[$course_obj->id
]);
119 // This one is from a non-database
120 // enrolment. Add it to the newenrolments
121 // array, so we don't loose it.
122 $newenrolments[$courseid] = $value;
128 /// Overwrite the old array with the new one
129 $user->{$type} = $newenrolments;
131 @ldap_close
($ldap_connection);
135 /// sync enrolments with ldap, create courses if required.
136 function sync_enrolments($type, $enrol) {
139 if(!isset($type) ||
!($type =='student' ||
$type =='teacher' )){
140 error("Bad call to get_all_courses()!");
142 $table = array( 'teacher' => 'user_teachers',
143 'student' => 'user_students');
149 // Connect to the external database
150 $ldap_connection = $this->enrol_ldap_connect();
151 if (!$ldap_connection) {
152 @ldap_close
($ldap_connection);
153 notify("LDAP-module cannot connect to server: $CFG->ldap_host_url");
157 // we are connected OK, continue...
158 $this->enrol_ldap_bind($ldap_connection);
160 //get all contexts and look for first matching user
161 $ldap_contexts = explode(";",$CFG->{'enrol_ldap_'.$type.'_contexts'});
163 // get all the fields we will want for the potential course creation
164 // as they are light. don't get membership -- potentially a lot of data.
165 $ldap_fields_wanted = array( 'dn', $CFG->enrol_ldap_course_idnumber
);
166 if (!empty($CFG->enrol_ldap_course_fullname
)){
167 array_push($ldap_fields_wanted, $CFG->enrol_ldap_course_fullname
);
169 if (!empty($CFG->enrol_ldap_course_shortname
)){
170 array_push($ldap_fields_wanted, $CFG->enrol_ldap_course_shortname
);
172 if (!empty($CFG->enrol_ldap_course_summary
)){
173 array_push($ldap_fields_wanted, $CFG->enrol_ldap_course_summary
);
176 array_push($ldap_fields_wanted, $CFG->{'enrol_ldap_'.$type.'_memberattribute'});
179 // define the search pattern
180 if (!empty($CFG->enrol_ldap_objectclass
)){
181 $ldap_search_pattern='(objectclass='.$CFG->enrol_ldap_objectclass
.')';
183 $ldap_search_pattern="(objectclass=*)";
187 // first, pack the sortorder...
188 fix_course_sortorder();
190 foreach ($ldap_contexts as $context) {
192 $context == trim($context);
193 if ($CFG->enrol_ldap_search_sub
){
194 //use ldap_search to find first user from subtree
195 $ldap_result = ldap_search($ldap_connection,
197 $ldap_search_pattern,
198 $ldap_fields_wanted);
201 //search only in this context
202 $ldap_result = ldap_list($ldap_connection,
204 $ldap_search_pattern,
205 $ldap_fields_wanted,0,0);
208 // check and push results
209 $records = ldap_get_entries($ldap_connection,$ldap_result);
211 // ldap libraries return an odd array, really. fix it:
212 $flat_records=array();
213 for ($c=0;$c<$records['count'];$c++
) {
214 array_push($flat_records, $records["$c"]);
216 // free mem -- is there a leak?
217 $records=0; $ldap_result=0;
219 if (count($flat_records)) {
222 foreach($flat_records as $course){
223 $idnumber = $course{$CFG->enrol_ldap_course_idnumber
}[0];
224 print "== Synching $idnumber\n";
225 // does the course exist in moodle already?
227 $course_obj = get_record( 'course',
228 $this->enrol_localcoursefield
,
230 if (!is_object($course_obj)) {
231 // ok, now then let's create it!
232 print "Creating Course $idnumber...";
233 $newcourseid = $this->create_course($course, true); // we are skipping fix_course_sortorder()
234 $course_obj = get_record( 'course', 'id', $newcourseid);
235 if (is_object($course_obj)) {
242 // enrol&unenrol if required
243 if($enrol && is_object($course_obj)){
244 // pull the ldap membership into a nice array
245 // this is an odd array -- mix of hash and array --
246 $ldapmembers=array();
247 if(!empty($course{ strtolower($CFG->{'enrol_ldap_'.$type.'_memberattribute'} ) })){ // may have no membership!
249 $ldapmembers = $course{ strtolower($CFG->{'enrol_ldap_'.$type.'_memberattribute'} ) };
250 unset($ldapmembers{'count'}); // remove oddity ;)
253 // prune old ldap enrolments
254 // hopefully they'll fit in the max buffer size for the RDBMS
255 $sql = ' SELECT enr.userid AS user, 1
256 FROM ' . $CFG->prefix
. $table{$type} . ' enr ' .
257 ' JOIN ' . $CFG->prefix
. 'user usr ON usr.id=enr.userid ' .
258 " WHERE course=$course_obj->id
260 if (!empty($ldapmembers)) {
261 $sql .= 'AND usr.idnumber NOT IN (\''. join('\',\'', $ldapmembers).'\')';
263 print ("Empty enrolment for $course_obj->shortname \n");
265 $todelete = get_records_sql($sql);
266 if(!empty($todelete)){
267 foreach ($todelete as $member) {
268 $member = $member->{'user'};
269 if($type==='student'){
270 if (unenrol_student($member, $course_obj->id
)){
271 print "Unenrolled $type $member into course $course_obj->id ($course_obj->shortname)\n";
273 print "Failed to unenrol $type $member into course $course_obj->id ($course_obj->shortname)\n";
276 if (remove_teacher($member, $course_obj->id
)){
277 print "Unenrolled $type $member into course $course_obj->id ($course_obj->shortname)\n";
279 print "Failed to unenrol $type $member into course $course_obj->id ($course_obj->shortname)\n";
285 // insert current enrolments
286 // bad we can't do INSERT IGNORE with postgres...
287 foreach ($ldapmembers as $ldapmember) {
288 $sql = 'SELECT id,1 FROM '.$CFG->prefix
.'user '
289 ." WHERE idnumber='$ldapmember'";
290 $member = get_record_sql($sql);
291 // print "sql: $sql \nidnumber = $ldapmember \n" . var_dump($member);
292 $member = $member->id
;
294 print "Could not find user $ldapmember, skipping\n";
297 if (!get_record($table{$type}, 'course', $course_obj->id
,
298 'userid', $member, 'enrol', 'ldap')){
299 if($type === 'student'){
300 if (enrol_student($member, $course_obj->id
, 0, 0, 'ldap')){
301 print "Enrolled $type $member ($ldapmember) into course $course_obj->id ($course_obj->shortname)\n";
303 print "Failed to enrol $type $member ($ldapmember) into course $course_obj->id ($course_obj->shortname)\n";
306 if (add_teacher($member, $course_obj->id
, 1,'',0,0,'ldap')){
307 print "Enrolled $type $member ($ldapmember) into course $course_obj->id ($course_obj->shortname)\n";
309 print "Failed to enrol $type $member ($ldapmember) into course $course_obj->id ($course_obj->shortname)\n";
321 // we are done now, a bit of housekeeping
322 fix_course_sortorder();
324 @ldap_close
($ldap_connection);
329 /// Overide the get_access_icons() function
330 function get_access_icons($course) {
334 /// Overrise the base config_form() function
335 function config_form($frm) {
337 include("$CFG->dirroot/enrol/ldap/config.html");
340 /// Override the base process_config() function
341 function process_config($config) {
343 if (!isset ($config->enrol_ldap_host_url
)) {
344 $config->enrol_ldap_host_url
= '';
346 set_config('enrol_ldap_host_url', $config->enrol_ldap_host_url
);
348 if (!isset ($config->enrol_ldap_version
)) {
349 $config->enrol_ldap_version
= '';
351 set_config('enrol_ldap_version', $config->enrol_ldap_version
);
353 if (!isset ($config->enrol_ldap_bind_dn
)) {
354 $config->enrol_ldap_bind_dn
= '';
356 set_config('enrol_ldap_bind_dn', $config->enrol_ldap_bind_dn
);
358 if (!isset ($config->enrol_ldap_bind_pw
)) {
359 $config->enrol_ldap_bind_pw
= '';
361 set_config('enrol_ldap_bind_pw', $config->enrol_ldap_bind_pw
);
363 if (!isset ($config->enrol_ldap_student_contexts
)) {
364 $config->enrol_ldap_student_contexts
= '';
366 set_config('enrol_ldap_student_contexts', $config->enrol_ldap_student_contexts
);
368 if (!isset ($config->enrol_ldap_student_memberattribute
)) {
369 $config->enrol_ldap_student_memberattribute
= '';
371 set_config('enrol_ldap_student_memberattribute', $config->enrol_ldap_student_memberattribute
);
372 if (!isset ($config->enrol_ldap_teacher_contexts
)) {
373 $config->enrol_ldap_teacher_contexts
= '';
375 set_config('enrol_ldap_teacher_contexts', $config->enrol_ldap_teacher_contexts
);
377 if (!isset ($config->enrol_ldap_teacher_memberattribute
)) {
378 $config->enrol_ldap_teacher_memberattribute
= '';
380 set_config('enrol_ldap_teacher_memberattribute', $config->enrol_ldap_teacher_memberattribute
);
381 if (!isset ($config->enrol_ldap_objectclass
)) {
382 $config->enrol_ldap_objectclass
= '';
384 set_config('enrol_ldap_objectclass', $config->enrol_ldap_objectclass
);
386 if (!isset ($config->enrol_ldap_category
)) {
387 $config->enrol_ldap_category
= '';
389 set_config('enrol_ldap_category', $config->enrol_ldap_category
);
391 if (!isset ($config->enrol_ldap_template
)) {
392 $config->enrol_ldap_template
= '';
394 set_config('enrol_ldap_template', $config->enrol_ldap_template
);
396 if (!isset ($config->enrol_ldap_course_fullname
)) {
397 $config->enrol_ldap_course_fullname
= '';
399 set_config('enrol_ldap_course_fullname', $config->enrol_ldap_course_fullname
);
401 if (!isset ($config->enrol_ldap_course_shortname
)) {
402 $config->enrol_ldap_course_shortname
= '';
404 set_config('enrol_ldap_course_shortname', $config->enrol_ldap_course_shortname
);
406 if (!isset ($config->enrol_ldap_course_summary
)) {
407 $config->enrol_ldap_course_summary
= '';
409 set_config('enrol_ldap_course_summary', $config->enrol_ldap_course_summary
);
411 if (!isset ($config->enrol_ldap_course_idnumber
)) {
412 $config->enrol_ldap_course_idnumber
= '';
414 set_config('enrol_ldap_course_idnumber', $config->enrol_ldap_course_idnumber
);
416 if (!isset ($config->enrol_localcoursefield
)) {
417 $config->enrol_localcoursefield
= '';
419 set_config('enrol_localcoursefield', $config->enrol_localcoursefield
);
421 if (!isset ($config->enrol_ldap_user_memberfield
)) {
422 $config->enrol_ldap_user_memberfield
= '';
424 set_config('enrol_ldap_user_memberfield', $config->enrol_ldap_user_memberfield
);
426 if (!isset ($config->enrol_ldap_search_sub
)) {
427 $config->enrol_ldap_search_sub
= '0';
429 set_config('enrol_ldap_search_sub', $config->enrol_ldap_search_sub
);
431 if (!isset ($config->enrol_ldap_autocreate
)) {
432 $config->enrol_ldap_autocreate
= '0';
434 set_config('enrol_ldap_autocreate', $config->enrol_ldap_autocreate
);
440 function enrol_ldap_connect(){
441 /// connects to ldap-server
444 $result = ldap_connect($CFG->enrol_ldap_host_url
);
447 if (!empty($CFG->enrol_ldap_version
)) {
448 ldap_set_option($result, LDAP_OPT_PROTOCOL_VERSION
, $CFG->enrol_ldap_version
);
451 if (!empty($CFG->enrol_ldap_bind_dn
)) {
452 $bind = ldap_bind( $result,
453 $CFG->enrol_ldap_bind_dn
,
454 $CFG->enrol_ldap_bind_pw
);
456 notify("Error in binding to LDAP server");
457 trigger_error("Error in binding to LDAP server $!");
463 notify("LDAP-module cannot connect to server: $CFG->enrol_ldap_host_url");
468 function enrol_ldap_bind($ldap_connection){
469 /// makes bind to ldap for searching users
470 /// uses ldap_bind_dn or anonymous bind
474 if ( ! empty($CFG->enrol_ldap_bind_dn
) ){
475 //bind with search-user
476 if (!ldap_bind($ldap_connection, $CFG->enrol_ldap_bind_dn
,$CFG->enrol_ldap_bind_pw
)){
477 notify("Error: could not bind ldap with ldap_bind_dn/pw");
483 if ( !ldap_bind($ldap_connection)){
484 notify("Error: could not bind ldap anonymously");
492 function find_ext_enrolments ($ldap_connection, $memberuid, $type){
493 /// type is teacher or student
494 /// return multidimentional array array with of courses (at least dn and idnumber)
499 //default return value
501 $this->enrol_ldap_bind($ldap_connection);
503 //get all contexts and look for first matching user
504 $ldap_contexts = explode(";",$CFG->{'enrol_ldap_'.$type.'_contexts'});
506 // get all the fields we will want for the potential course creation
507 // as they are light. don't get membership -- potentially a lot of data.
508 $ldap_fields_wanted = array( 'dn', $CFG->enrol_ldap_course_idnumber
);
509 if (!empty($CFG->enrol_ldap_course_fullname
)){
510 array_push($ldap_fields_wanted, $CFG->enrol_ldap_course_fullname
);
512 if (!empty($CFG->enrol_ldap_course_shortname
)){
513 array_push($ldap_fields_wanted, $CFG->enrol_ldap_course_shortname
);
515 if (!empty($CFG->enrol_ldap_course_summary
)){
516 array_push($ldap_fields_wanted, $CFG->enrol_ldap_course_summary
);
519 // define the search pattern
520 $ldap_search_pattern = "(".$CFG->{'enrol_ldap_'.$type.'_memberattribute'}."=".$memberuid.")";
521 if (!empty($CFG->enrol_ldap_objectclass
)){
522 $ldap_search_pattern='(&(objectclass='.$CFG->enrol_ldap_objectclass
.')'.$ldap_search_pattern.')';
525 foreach ($ldap_contexts as $context) {
526 $context == trim($context);
527 if (empty($context)) {
531 if ($CFG->enrol_ldap_search_sub
){
532 //use ldap_search to find first user from subtree
533 $ldap_result = ldap_search($ldap_connection,
535 $ldap_search_pattern,
536 $ldap_fields_wanted);
539 //search only in this context
540 $ldap_result = ldap_list($ldap_connection,
542 $ldap_search_pattern,
543 $ldap_fields_wanted);
546 // check and push results
547 $records = ldap_get_entries($ldap_connection,$ldap_result);
549 // ldap libraries return an odd array, really. fix it:
550 $flat_records=array();
551 for ($c=0;$c<$records['count'];$c++
) {
552 array_push($flat_records, $records["$c"]);
555 if (count($flat_records)) {
556 $courses = array_merge($courses, $flat_records);
563 // will create the moodle course from the template
564 // course_ext is an array as obtained from ldap -- flattened somewhat
565 // NOTE: if you pass true for $skip_fix_course_sortorder
566 // you will want to call fix_course_sortorder() after your are done
567 // with course creation
568 function create_course ($course_ext,$skip_fix_course_sortorder=0){
573 $course->student
= 'Student';
574 $course->students
= 'Students';
575 $course->teacher
= 'Teacher';
576 $course->teachers
= 'Teachers';
577 $course->format
= 'topics';
579 // override defaults with template course
580 if(!empty($CFG->enrol_ldap_template
)){
581 $course = get_record("course", 'shortname', $CFG->enrol_ldap_template
);
582 unset($course->id
); // so we are clear to reinsert the record
583 unset($course->sortorder
);
586 // override with required ext data
587 $course->idnumber
= $course_ext[$CFG->enrol_ldap_course_idnumber
][0];
588 $course->fullname
= addslashes($course_ext[$CFG->enrol_ldap_course_fullname
][0]);
589 $course->shortname
= addslashes($course_ext[$CFG->enrol_ldap_course_shortname
][0]);
590 if ( empty($course->idnumber
)
591 ||
empty($course->fullname
)
592 ||
empty($course->shortname
) ) {
593 // we are in trouble!
594 error_log("Cannot create course: missing required data from the LDAP record!");
595 error_log(var_export($course, true));
599 if(!empty($CFG->enrol_ldap_course_summary
)){ // optional
600 $course->summary
= addslashes($course_ext[$CFG->enrol_ldap_course_summary
][0]);
602 if(!empty($CFG->enrol_ldap_category
)){ // optional ... but ensure it is set!
603 $course->category
= $CFG->enrol_ldap_category
;
605 if ($course->category
== 0){ // must be avoided as it'll break moodle
606 $course->category
= 1; // the misc 'catch-all' category
609 // define the sortorder (yuck)
610 $sort = get_record_sql('SELECT MAX(sortorder) AS max, 1 FROM ' . $CFG->prefix
. 'course WHERE category=' . $course->category
);
613 $course->sortorder
= $sort;
615 // override with local data
616 $course->startdate
= time();
617 $course->timecreated
= time();
618 $course->visible
= 1;
621 if ($newcourseid = insert_record("course", $course)) { // Set up new course
623 $section->course
= $newcourseid; // Create a default section.
624 $section->section
= 0;
625 $section->id
= insert_record("course_sections", $section);
626 $page = page_create_object(PAGE_COURSE_VIEW
, $newcourseid);
627 blocks_repopulate_page($page); // Return value no
630 if(!$skip_fix_course_sortorder){
631 fix_course_sortorder();
633 add_to_log($newcourseid, "course", "new", "view.php?id=$newcourseid", "enrol/ldap auto-creation");
635 error_log("Could not create new course from LDAP from DN:" . $course_ext['dn']);
636 notify("Serious Error! Could not create the new course!");