Fix subject lines and corrupted emails from messages MDL-13364 (merged)
[moodle.git] / enrol / ldap / enrol.php
blob121b9cc8df8749a39dbd8dd99945329cccb21f4f
1 <?php // $Id$
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 {
11 var $log;
13 var $enrol_localcoursefield = 'idnumber';
15 /// Overide the base get_student_courses() function
16 function get_student_courses(&$user) {
17 global $CFG;
18 return $this->get_user_courses($user, 'student');
21 /// Overide the base get_teacher_courses() function
22 function get_teacher_courses(&$user) {
23 global $CFG;
24 return $this->get_user_courses($user, 'teacher');
27 /// Overide the base get_student_courses() function
28 function get_user_courses(&$user, $type) {
29 global $CFG;
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");
41 return false;
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,
49 $user->idnumber ,
50 $type);
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,
63 $course_ext_id );
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,
71 $newcourseid);
72 } else {
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]);
80 continue;
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
91 } else {
92 $CFG->debug=10;
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');
102 $CFG->debug=0;
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]);
118 } else {
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);
132 return true;
135 /// sync enrolments with ldap, create courses if required.
136 function sync_enrolments($type, $enrol) {
137 global $CFG;
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');
145 if(!isset($enrol)){
146 $enrol=false;
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");
154 return false;
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);
175 if($enrol){
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.')';
182 } else {
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,
196 $context,
197 $ldap_search_pattern,
198 $ldap_fields_wanted);
200 } else {
201 //search only in this context
202 $ldap_result = ldap_list($ldap_connection,
203 $context,
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?
226 $course_obj = false;
227 $course_obj = get_record( 'course',
228 $this->enrol_localcoursefield,
229 $idnumber );
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)) {
236 print "OK!\n";
237 } else {
238 print "failed\n";
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
259 AND enrol='ldap' ";
260 if (!empty($ldapmembers)) {
261 $sql .= 'AND usr.idnumber NOT IN (\''. join('\',\'', $ldapmembers).'\')';
262 } else {
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";
272 } else {
273 print "Failed to unenrol $type $member into course $course_obj->id ($course_obj->shortname)\n";
275 } else {
276 if (remove_teacher($member, $course_obj->id)){
277 print "Unenrolled $type $member into course $course_obj->id ($course_obj->shortname)\n";
278 } else {
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;
293 if(empty($member)){
294 print "Could not find user $ldapmember, skipping\n";
295 continue;
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";
302 } else {
303 print "Failed to enrol $type $member ($ldapmember) into course $course_obj->id ($course_obj->shortname)\n";
305 } else { // teacher
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";
308 } else {
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);
325 return true;
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) {
336 global $CFG;
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);
436 return true;
440 function enrol_ldap_connect(){
441 /// connects to ldap-server
442 global $CFG;
444 $result = ldap_connect($CFG->enrol_ldap_host_url);
446 if ($result) {
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 );
455 if (!$bind) {
456 notify("Error in binding to LDAP server");
457 trigger_error("Error in binding to LDAP server $!");
461 return $result;
462 } else {
463 notify("LDAP-module cannot connect to server: $CFG->enrol_ldap_host_url");
464 return false;
468 function enrol_ldap_bind($ldap_connection){
469 /// makes bind to ldap for searching users
470 /// uses ldap_bind_dn or anonymous bind
472 global $CFG;
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");
478 return false;
481 } else {
482 //bind anonymously
483 if ( !ldap_bind($ldap_connection)){
484 notify("Error: could not bind ldap anonymously");
485 return false;
489 return true;
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)
495 ///
497 global $CFG;
499 //default return value
500 $courses = array();
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)) {
528 continue; // next;
531 if ($CFG->enrol_ldap_search_sub){
532 //use ldap_search to find first user from subtree
533 $ldap_result = ldap_search($ldap_connection,
534 $context,
535 $ldap_search_pattern,
536 $ldap_fields_wanted);
538 } else {
539 //search only in this context
540 $ldap_result = ldap_list($ldap_connection,
541 $context,
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);
560 return $courses;
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){
569 global $CFG;
571 // set defaults
572 $course = NULL;
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));
596 return false;
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);
611 $sort = $sort->max;
612 $sort++;
613 $course->sortorder = $sort;
615 // override with local data
616 $course->startdate = time();
617 $course->timecreated = time();
618 $course->visible = 1;
620 // store it and log
621 if ($newcourseid = insert_record("course", $course)) { // Set up new course
622 $section = NULL;
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");
634 } else {
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!");
637 return false;
640 return $newcourseid;
643 } // end of class