306f8b353a9d31679e0cde8bc79e33e4abe236f5
[moodle.git] / enrol / database / lib.php
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/>.
17 /**
18  * Database enrolment plugin.
19  *
20  * This plugin synchronises enrolment and roles with external database table.
21  *
22  * @package    enrol
23  * @subpackage database
24  * @copyright  2010 Petr Skoda {@link http://skodak.org}
25  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 defined('MOODLE_INTERNAL') || die();
30 /**
31  * Database enrolment plugin implementation.
32  * @author  Petr Skoda - based on code by Martin Dougiamas, Martin Langhoff and others
33  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class enrol_database_plugin extends enrol_plugin {
36     /**
37      * Is it possible to delete enrol instance via standard UI?
38      *
39      * @param object $instance
40      * @return bool
41      */
42     public function instance_deleteable($instance) {
43         if (!enrol_is_enabled('database')) {
44             return true;
45         }
46         if (!$this->get_config('dbtype') or !$this->get_config('dbhost') or !$this->get_config('remoteenroltable') or !$this->get_config('remotecoursefield') or !$this->get_config('remoteuserfield')) {
47             return true;
48         }
50         //TODO: connect to external system and make sure no users are to be enrolled in this course
51         return false;
52     }
54     /**
55      * Forces synchronisation of user enrolments with external database,
56      * does not create new courses.
57      *
58      * @param object $user user record
59      * @return void
60      */
61     public function sync_user_enrolments($user) {
62         global $CFG, $DB;
64         // we do not create courses here intentionally because it requires full sync and is slow
65         if (!$this->get_config('dbtype') or !$this->get_config('dbhost') or !$this->get_config('remoteenroltable') or !$this->get_config('remotecoursefield') or !$this->get_config('remoteuserfield')) {
66             return;
67         }
69         $table            = $this->get_config('remoteenroltable');
70         $coursefield      = strtolower($this->get_config('remotecoursefield'));
71         $userfield        = strtolower($this->get_config('remoteuserfield'));
72         $rolefield        = strtolower($this->get_config('remoterolefield'));
74         $localrolefield   = $this->get_config('localrolefield');
75         $localuserfield   = $this->get_config('localuserfield');
76         $localcoursefield = $this->get_config('localcoursefield');
78         $unenrolaction    = $this->get_config('unenrolaction');
79         $defaultrole      = $this->get_config('defaultrole');
81         $ignorehidden     = $this->get_config('ignorehiddencourses');
83         if (!is_object($user) or !property_exists($user, 'id')) {
84             throw new coding_exception('Invalid $user parameter in sync_user_enrolments()');
85         }
87         if (!property_exists($user, $localuserfield)) {
88             debugging('Invalid $user parameter in sync_user_enrolments(), missing '.$localuserfield);
89             $user = $DB->get_record('user', array('id'=>$user->id));
90         }
92         // create roles mapping
93         $allroles = get_all_roles();
94         if (!isset($allroles[$defaultrole])) {
95             $defaultrole = 0;
96         }
97         $roles = array();
98         foreach ($allroles as $role) {
99             $roles[$role->$localrolefield] = $role->id;
100         }
102         $enrols = array();
103         $instances = array();
105         if (!$extdb = $this->db_init()) {
106             // can not connect to database, sorry
107             return;
108         }
110         // read remote enrols and create instances
111         $sql = $this->db_get_sql($table, array($userfield=>$user->$localuserfield), array(), false);
113         if ($rs = $extdb->Execute($sql)) {
114             if (!$rs->EOF) {
115                 while ($fields = $rs->FetchRow()) {
116                     $fields = array_change_key_case($fields, CASE_LOWER);
117                     $fields = $this->db_decode($fields);
119                     if (empty($fields[$coursefield])) {
120                         // missing course info
121                         continue;
122                     }
123                     if (!$course = $DB->get_record('course', array($localcoursefield=>$fields[$coursefield]), 'id,visible')) {
124                         continue;
125                     }
126                     if (!$course->visible and $ignorehidden) {
127                         continue;
128                     }
130                     if (empty($fields[$rolefield]) or !isset($roles[$fields[$rolefield]])) {
131                         if (!$defaultrole) {
132                             // role is mandatory
133                             continue;
134                         }
135                         $roleid = $defaultrole;
136                     } else {
137                         $roleid = $roles[$fields[$rolefield]];
138                     }
140                     if (empty($enrols[$course->id])) {
141                         $enrols[$course->id] = array();
142                     }
143                     $enrols[$course->id][] = $roleid;
145                     if ($instance = $DB->get_record('enrol', array('courseid'=>$course->id, 'enrol'=>'database'), '*', IGNORE_MULTIPLE)) {
146                         $instances[$course->id] = $instance;
147                         continue;
148                     }
150                     $enrolid = $this->add_instance($course);
151                     $instances[$course->id] = $DB->get_record('enrol', array('id'=>$enrolid));
152                 }
153             }
154             $rs->Close();
155             $extdb->Close();
156         } else {
157             // bad luck, something is wrong with the db connection
158             $extdb->Close();
159             return;
160         }
162         // enrol user into courses and sync roles
163         foreach ($enrols as $courseid => $roles) {
164             if (!isset($instances[$courseid])) {
165                 // ignored
166                 continue;
167             }
168             $instance = $instances[$courseid];
170             if ($e = $DB->get_record('user_enrolments', array('userid'=>$user->id, 'enrolid'=>$instance->id))) {
171                 // reenable enrolment when previously disable enrolment refreshed
172                 if ($e->status == ENROL_USER_SUSPENDED) {
173                     $this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE);
174                 }
175             } else {
176                 $roleid = reset($roles);
177                 $this->enrol_user($instance, $user->id, $roleid, 0, 0, ENROL_USER_ACTIVE);
178             }
180             if (!$context = get_context_instance(CONTEXT_COURSE, $instance->courseid)) {
181                 //weird
182                 continue;
183             }
184             $current = $DB->get_records('role_assignments', array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_database', 'itemid'=>$instance->id), '', 'id, roleid');
186             $existing = array();
187             foreach ($current as $r) {
188                 if (in_array($r->id, $roles)) {
189                     $existing[$r->roleid] = $r->roleid;
190                 } else {
191                     role_unassign($r->roleid, $user->id, $context->id, 'enrol_database', $instance->id);
192                 }
193             }
194             foreach ($roles as $rid) {
195                 if (!isset($existing[$rid])) {
196                     role_assign($rid, $user->id, $context->id, 'enrol_database', $instance->id);
197                 }
198             }
199         }
201         // unenrol as necessary
202         $sql = "SELECT e.*, c.visible AS cvisible, ue.status AS ustatus
203                   FROM {enrol} e
204                   JOIN {user_enrolments} ue ON ue.enrolid = e.id
205                   JOIN {course} c ON c.id = e.courseid
206                  WHERE ue.userid = :userid AND e.enrol = 'database'";
207         $rs = $DB->get_recordset_sql($sql, array('userid'=>$user->id));
208         foreach ($rs as $instance) {
209             if (!$instance->cvisible and $ignorehidden) {
210                 continue;
211             }
213             if (!$context = get_context_instance(CONTEXT_COURSE, $instance->courseid)) {
214                 //weird
215                 continue;
216             }
218             if (!empty($enrols[$instance->courseid])) {
219                 // we want this user enrolled
220                 continue;
221             }
223             // deal with enrolments removed from external table
224             if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
225                 // unenrol
226                 $this->unenrol_user($instance, $user->id);
228             } else if ($unenrolaction == ENROL_EXT_REMOVED_KEEP) {
229                 // keep - only adding enrolments
231             } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
232                 // disable
233                 if ($instance->ustatus != ENROL_USER_SUSPENDED) {
234                     $this->update_user_enrol($instance, $user->id, ENROL_USER_SUSPENDED);
235                 }
236                 if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
237                     role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_database', 'itemid'=>$instance->id));
238                 }
239             }
240         }
241         $rs->close();
242     }
244     /**
245      * Forces synchronisation of all enrolments with external database.
246      *
247      * @param bool $verbose
248      * @return int 0 means success, 1 db connect failure, 2 db read failure
249      */
250     public function sync_enrolments($verbose = false) {
251         global $CFG, $DB;
253         // we do not create courses here intentionally because it requires full sync and is slow
254         if (!$this->get_config('dbtype') or !$this->get_config('dbhost') or !$this->get_config('remoteenroltable') or !$this->get_config('remotecoursefield') or !$this->get_config('remoteuserfield')) {
255             if ($verbose) {
256                 mtrace('User enrolment synchronisation skipped.');
257             }
258             return 0;
259         }
261         if ($verbose) {
262             mtrace('Starting user enrolment synchronisation...');
263         }
265         if (!$extdb = $this->db_init()) {
266             mtrace('Error while communicating with external enrolment database');
267             return 1;
268         }
270         // we may need a lot of memory here
271         @set_time_limit(0);
272         raise_memory_limit(MEMORY_HUGE);
274         // second step is to sync instances and users
275         $table            = $this->get_config('remoteenroltable');
276         $coursefield      = strtolower($this->get_config('remotecoursefield'));
277         $userfield        = strtolower($this->get_config('remoteuserfield'));
278         $rolefield        = strtolower($this->get_config('remoterolefield'));
280         $localrolefield   = $this->get_config('localrolefield');
281         $localuserfield   = $this->get_config('localuserfield');
282         $localcoursefield = $this->get_config('localcoursefield');
284         $unenrolaction    = $this->get_config('unenrolaction');
285         $defaultrole      = $this->get_config('defaultrole');
287         // create roles mapping
288         $allroles = get_all_roles();
289         if (!isset($allroles[$defaultrole])) {
290             $defaultrole = 0;
291         }
292         $roles = array();
293         foreach ($allroles as $role) {
294             $roles[$role->$localrolefield] = $role->id;
295         }
297         // get a list of courses to be synced that are in external table
298         $externalcourses = array();
299         $sql = $this->db_get_sql($table, array(), array($coursefield), true);
300         if ($rs = $extdb->Execute($sql)) {
301             if (!$rs->EOF) {
302                 while ($mapping = $rs->FetchRow()) {
303                     $mapping = reset($mapping);
304                     $mapping = $this->db_decode($mapping);
305                     if (empty($mapping)) {
306                         // invalid mapping
307                         continue;
308                     }
309                     $externalcourses[$mapping] = true;
310                 }
311             }
312             $rs->Close();
313         } else {
314             mtrace('Error reading data from the external enrolment table');
315             $extdb->Close();
316             return 2;
317         }
318         $preventfullunenrol = empty($externalcourses);
319         if ($preventfullunenrol and $unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
320             if ($verbose) {
321                 mtrace('  Preventing unenrolment of all current users, because it might result in major data loss, there has to be at least one record in external enrol table, sorry.');
322             }
323         }
325         // first find all existing courses with enrol instance
326         $existing = array();
327         $sql = "SELECT c.id, c.visible, c.$localcoursefield AS mapping, e.id AS enrolid, c.shortname
328                   FROM {course} c
329                   JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'database')";
330         $rs = $DB->get_recordset_sql($sql); // watch out for idnumber duplicates
331         foreach ($rs as $course) {
332             if (empty($course->mapping)) {
333                 continue;
334             }
335             $existing[$course->mapping] = $course;
336         }
337         $rs->close();
339         // add necessary enrol instances that are not present yet
340         $params = array();
341         $localnotempty = "";
342         if ($localcoursefield !== 'id') {
343             $localnotempty =  "AND c.$localcoursefield <> :lcfe";
344             $params['lcfe'] = $DB->sql_empty();
345         }
346         $sql = "SELECT c.id, c.visible, c.$localcoursefield AS mapping, c.shortname
347                   FROM {course} c
348              LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'database')
349                  WHERE e.id IS NULL $localnotempty";
350         $rs = $DB->get_recordset_sql($sql, $params);
351         foreach ($rs as $course) {
352             if (empty($course->mapping)) {
353                 continue;
354             }
355             if (!isset($externalcourses[$course->mapping])) {
356                 // course not synced
357                 continue;
358             }
359             if (isset($existing[$course->mapping])) {
360                 // some duplicate, sorry
361                 continue;
362             }
363             $course->enrolid = $this->add_instance($course);
364             $existing[$course->mapping] = $course;
365         }
366         $rs->close();
368         // free memory
369         unset($externalcourses);
371         // sync enrolments
372         $ignorehidden = $this->get_config('ignorehiddencourses');
373         $sqlfields = array($userfield);
374         if ($rolefield) {
375             $sqlfields[] = $rolefield;
376         }
377         foreach ($existing as $course) {
378             if ($ignorehidden and !$course->visible) {
379                 continue;
380             }
381             if (!$instance = $DB->get_record('enrol', array('id'=>$course->enrolid))) {
382                 continue; //weird
383             }
384             $context = get_context_instance(CONTEXT_COURSE, $course->id);
386             // get current list of enrolled users with their roles
387             $current_roles  = array();
388             $current_status = array();
389             $user_mapping   = array();
390             $sql = "SELECT u.$localuserfield AS mapping, u.id, ue.status, ue.userid, ra.roleid
391                       FROM {user} u
392                       JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = :enrolid)
393                       JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.itemid = ue.enrolid AND ra.component = 'enrol_database')
394                      WHERE u.deleted = 0";
395             $params = array('enrolid'=>$instance->id);
396             if ($localuserfield === 'username') {
397                 $sql .= " AND u.mnethostid = :mnethostid";
398                 $params['mnethostid'] = $CFG->mnet_localhost_id;
399             }
400             $rs = $DB->get_recordset_sql($sql, $params);
401             foreach ($rs as $ue) {
402                 $current_roles[$ue->userid][$ue->roleid] = $ue->roleid;
403                 $current_status[$ue->userid] = $ue->status;
404                 $user_mapping[$ue->mapping] = $ue->userid;
405             }
406             $rs->close();
408             // get list of users that need to be enrolled and their roles
409             $requested_roles = array();
410             $sql = $this->db_get_sql($table, array($coursefield=>$course->mapping), $sqlfields);
411             if ($rs = $extdb->Execute($sql)) {
412                 if (!$rs->EOF) {
413                     if ($localuserfield === 'username') {
414                         $usersearch = array('mnethostid'=>$CFG->mnet_localhost_id, 'deleted' =>0);
415                     }
416                     while ($fields = $rs->FetchRow()) {
417                         $fields = array_change_key_case($fields, CASE_LOWER);
418                         if (empty($fields[$userfield])) {
419                             //user identification is mandatory!
420                         }
421                         $mapping = $fields[$userfield];
422                         if (!isset($user_mapping[$mapping])) {
423                             $usersearch[$localuserfield] = $mapping;
424                             if (!$user = $DB->get_record('user', $usersearch, 'id', IGNORE_MULTIPLE)) {
425                                 // user does not exist or was deleted
426                                 continue;
427                             }
428                             $user_mapping[$mapping] = $user->id;
429                             $userid = $user->id;
430                         } else {
431                             $userid = $user_mapping[$mapping];
432                         }
433                         if (empty($fields[$rolefield]) or !isset($roles[$fields[$rolefield]])) {
434                             if (!$defaultrole) {
435                                 // role is mandatory
436                                 continue;
437                             }
438                             $roleid = $defaultrole;
439                         } else {
440                             $roleid = $roles[$fields[$rolefield]];
441                         }
443                         $requested_roles[$userid][$roleid] = $roleid;
444                     }
445                 }
446                 $rs->Close();
447             } else {
448                 mtrace('Error while communicating with external enrolment database');
449                 $extdb->Close();
450                 return;
451             }
452             unset($user_mapping);
454             // enrol all users and sync roles
455             foreach ($requested_roles as $userid=>$userroles) {
456                 foreach ($userroles as $roleid) {
457                     if (empty($current_roles[$userid])) {
458                         $this->enrol_user($instance, $userid, $roleid, 0, 0, ENROL_USER_ACTIVE);
459                         $current_roles[$userid][$roleid] = $roleid;
460                         $current_status[$userid] = ENROL_USER_ACTIVE;
461                         if ($verbose) {
462                             mtrace("  enrolling: $userid ==> $course->shortname as ".$allroles[$roleid]->shortname);
463                         }
464                     }
465                 }
467                 // assign extra roles
468                 foreach ($userroles as $roleid) {
469                     if (empty($current_roles[$userid][$roleid])) {
470                         role_assign($roleid, $userid, $context->id, 'enrol_database', $instance->id);
471                         $current_roles[$userid][$roleid] = $roleid;
472                         if ($verbose) {
473                             mtrace("  assigning roles: $userid ==> $course->shortname as ".$allroles[$roleid]->shortname);
474                         }
475                     }
476                 }
478                 // unassign removed roles
479                 foreach($current_roles[$userid] as $cr) {
480                     if (empty($userroles[$cr])) {
481                         role_unassign($cr, $userid, $context->id, 'enrol_database', $instance->id);
482                         unset($current_roles[$userid][$cr]);
483                         if ($verbose) {
484                             mtrace("  unsassigning roles: $userid ==> $course->shortname");
485                         }
486                     }
487                 }
489                 // reenable enrolment when previously disable enrolment refreshed
490                 if ($current_status[$userid] == ENROL_USER_SUSPENDED) {
491                     $this->update_user_enrol($instance, $userid, ENROL_USER_ACTIVE);
492                     if ($verbose) {
493                         mtrace("  unsuspending: $userid ==> $course->shortname");
494                     }
495                 }
496             }
498             // deal with enrolments removed from external table
499             if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
500                 if (!$preventfullunenrol) {
501                     // unenrol
502                     foreach ($current_status as $userid=>$status) {
503                         if (isset($requested_roles[$userid])) {
504                             continue;
505                         }
506                         $this->unenrol_user($instance, $userid);
507                         if ($verbose) {
508                             mtrace("  unenrolling: $userid ==> $course->shortname");
509                         }
510                     }
511                 }
513             } else if ($unenrolaction == ENROL_EXT_REMOVED_KEEP) {
514                 // keep - only adding enrolments
516             } else if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
517                 // disable
518                 foreach ($current_status as $userid=>$status) {
519                     if (isset($requested_roles[$userid])) {
520                         continue;
521                     }
522                     if ($status != ENROL_USER_SUSPENDED) {
523                         $this->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED);
524                         if ($verbose) {
525                             mtrace("  suspending: $userid ==> $course->shortname");
526                         }
527                     }
528                     if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
529                         role_unassign_all(array('contextid'=>$context->id, 'userid'=>$userid, 'component'=>'enrol_database', 'itemid'=>$instance->id));
530                         if ($verbose) {
531                             mtrace("  unsassigning all roles: $userid ==> $course->shortname");
532                         }
533                     }
534                 }
535             }
536         }
538         // close db connection
539         $extdb->Close();
541         if ($verbose) {
542             mtrace('...user enrolment synchronisation finished.');
543         }
545         return 0;
546     }
548     /**
549      * Performs a full sync with external database.
550      *
551      * First it creates new courses if necessary, then
552      * enrols and unenrols users.
553      *
554      * @param bool $verbose
555      * @return int 0 means success, 1 db connect failure, 4 db read failure
556      */
557     public function sync_courses($verbose = false) {
558         global $CFG, $DB;
560         // make sure we sync either enrolments or courses
561         if (!$this->get_config('dbtype') or !$this->get_config('dbhost') or !$this->get_config('newcoursetable') or !$this->get_config('newcoursefullname') or !$this->get_config('newcourseshortname')) {
562             if ($verbose) {
563                 mtrace('Course synchronisation skipped.');
564             }
565             return 0;
566         }
568         if ($verbose) {
569             mtrace('Starting course synchronisation...');
570         }
572         // we may need a lot of memory here
573         @set_time_limit(0);
574         raise_memory_limit(MEMORY_HUGE);
576         if (!$extdb = $this->db_init()) {
577             mtrace('Error while communicating with external enrolment database');
578             return 1;
579         }
581         // first create new courses
582         $table     = $this->get_config('newcoursetable');
583         $fullname  = strtolower($this->get_config('newcoursefullname'));
584         $shortname = strtolower($this->get_config('newcourseshortname'));
585         $idnumber  = strtolower($this->get_config('newcourseidnumber'));
586         $category  = strtolower($this->get_config('newcoursecategory'));
588         $sqlfields = array($fullname, $shortname);
589         if ($category) {
590             $sqlfields[] = $category;
591         }
592         if ($idnumber) {
593             $sqlfields[] = $idnumber;
594         }
595         $sql = $this->db_get_sql($table, array(), $sqlfields);
596         $createcourses = array();
597         if ($rs = $extdb->Execute($sql)) {
598             if (!$rs->EOF) {
599                 while ($fields = $rs->FetchRow()) {
600                     $fields = array_change_key_case($fields, CASE_LOWER);
601                     $fields = $this->db_decode($fields);
602                     if (empty($fields[$shortname]) or empty($fields[$fullname])) {
603                         if ($verbose) {
604                             mtrace('  error: invalid external course record, shortname and fullname are mandatory: ' . json_encode($fields)); // hopefully every geek can read JS, right?
605                         }
606                         continue;
607                     }
608                     if ($DB->record_exists('course', array('shortname'=>$fields[$shortname]))) {
609                         // already exists
610                         continue;
611                     }
612                     // allow empty idnumber but not duplicates
613                     if ($idnumber and $fields[$idnumber] !== '' and $fields[$idnumber] !== null and $DB->record_exists('course', array('idnumber'=>$fields[$idnumber]))) {
614                         if ($verbose) {
615                             mtrace('  error: duplicate idnumber, can not create course: '.$fields[$shortname].' ['.$fields[$idnumber].']');
616                         }
617                         continue;
618                     }
619                     if ($category and !$DB->record_exists('course_categories', array('id'=>$fields[$category]))) {
620                         if ($verbose) {
621                             mtrace('  error: invalid category id, can not create course: '.$fields[$shortname]);
622                         }
623                         continue;
624                     }
625                     $course = new stdClass();
626                     $course->fullname  = $fields[$fullname];
627                     $course->shortname = $fields[$shortname];
628                     $course->idnumber  = $idnumber ? $fields[$idnumber] : NULL;
629                     $course->category  = $category ? $fields[$category] : NULL;
630                     $createcourses[] = $course;
631                 }
632             }
633             $rs->Close();
634         } else {
635             mtrace('Error reading data from the external course table');
636             $extdb->Close();
637             return 4;
638         }
639         if ($createcourses) {
640             require_once("$CFG->dirroot/course/lib.php");
642             $template        = $this->get_config('templatecourse');
643             $defaultcategory = $this->get_config('defaultcategory');
645             if ($template) {
646                 if ($template = $DB->get_record('course', array('shortname'=>$template))) {
647                     unset($template->id);
648                     unset($template->fullname);
649                     unset($template->shortname);
650                     unset($template->idnumber);
651                 } else {
652                     $template = new stdClass();
653                 }
654             } else {
655                 $template = new stdClass();
656             }
657             if (!$DB->record_exists('course_categories', array('id'=>$defaultcategory))) {
658                 $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1);
659                 $first = reset($categories);
660                 $defaultcategory = $first->id;
661             }
663             foreach ($createcourses as $fields) {
664                 $newcourse = clone($template);
665                 $newcourse->fullname  = $fields->fullname;
666                 $newcourse->shortname = $fields->shortname;
667                 $newcourse->idnumber  = $fields->idnumber;
668                 $newcourse->category  = $fields->category ? $fields->category : $defaultcategory;
670                 $c = create_course($newcourse);
671                 if ($verbose) {
672                     mtrace("  creating course: $c->id, $c->fullname, $c->shortname, $c->idnumber, $c->category");
673                 }
674             }
676             unset($createcourses);
677             unset($template);
678         }
680         // close db connection
681         $extdb->Close();
683         if ($verbose) {
684             mtrace('...course synchronisation finished.');
685         }
687         return 0;
688     }
690     protected function db_get_sql($table, array $conditions, array $fields, $distinct = false, $sort = "") {
691         $fields = $fields ? implode(',', $fields) : "*";
692         $where = array();
693         if ($conditions) {
694             foreach ($conditions as $key=>$value) {
695                 $value = $this->db_encode($this->db_addslashes($value));
697                 $where[] = "$key = '$value'";
698             }
699         }
700         $where = $where ? "WHERE ".implode(" AND ", $where) : "";
701         $sort = $sort ? "ORDER BY $sort" : "";
702         $distinct = $distinct ? "DISTINCT" : "";
703         $sql = "SELECT $distinct $fields
704                   FROM $table
705                  $where
706                   $sort";
708         return $sql;
709     }
711     /**
712      * Tries to make connection to the external database.
713      *
714      * @return null|ADONewConnection
715      */
716     protected function db_init() {
717         global $CFG;
719         require_once($CFG->libdir.'/adodb/adodb.inc.php');
721         // Connect to the external database (forcing new connection)
722         $extdb = ADONewConnection($this->get_config('dbtype'));
723         if ($this->get_config('debugdb')) {
724             $extdb->debug = true;
725             ob_start(); //start output buffer to allow later use of the page headers
726         }
728         $result = $extdb->Connect($this->get_config('dbhost'), $this->get_config('dbuser'), $this->get_config('dbpass'), $this->get_config('dbname'), true);
729         if (!$result) {
730             return null;
731         }
733         $extdb->SetFetchMode(ADODB_FETCH_ASSOC);
734         if ($this->get_config('dbsetupsql')) {
735             $extdb->Execute($this->get_config('dbsetupsql'));
736         }
737         return $extdb;
738     }
740     protected function db_addslashes($text) {
741         // using custom made function for now
742         if ($this->get_config('dbsybasequoting')) {
743             $text = str_replace('\\', '\\\\', $text);
744             $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
745         } else {
746             $text = str_replace("'", "''", $text);
747         }
748         return $text;
749     }
751     protected function db_encode($text) {
752         $dbenc = $this->get_config('dbencoding');
753         if (empty($dbenc) or $dbenc == 'utf-8') {
754             return $text;
755         }
756         if (is_array($text)) {
757             foreach($text as $k=>$value) {
758                 $text[$k] = $this->db_encode($value);
759             }
760             return $text;
761         } else {
762             return textlib_get_instance()->convert($text, 'utf-8', $dbenc);
763         }
764     }
766     protected function db_decode($text) {
767         $dbenc = $this->get_config('dbencoding');
768         if (empty($dbenc) or $dbenc == 'utf-8') {
769             return $text;
770         }
771         if (is_array($text)) {
772             foreach($text as $k=>$value) {
773                 $text[$k] = $this->db_decode($value);
774             }
775             return $text;
776         } else {
777             return textlib_get_instance()->convert($text, $dbenc, 'utf-8');
778         }
779     }