3 require_once($CFG->dirroot
.'/enrol/enrol.class.php');
5 class enrolment_plugin_database
{
10 * For the given user, let's go out and look in an external database
11 * for an authoritative list of enrolments, and then adjust the
12 * local Moodle assignments to match.
14 function setup_enrolments(&$user) {
17 // NOTE: if $this->enrol_connect() succeeds you MUST remember to call
18 // $this->enrol_disconnect() as it is doing some nasty vodoo with $CFG->prefix
19 $enroldb = $this->enrol_connect();
21 error_log('[ENROL_DB] Could not make a connection');
25 // If we are expecting to get role information from our remote db, then
26 // we execute the below code for every role type. Otherwise we just
27 // execute it once with null (hence the dummy array).
28 $roles = !empty($CFG->enrol_db_remoterolefield
) && !empty($CFG->enrol_db_localrolefield
)
32 //error_log('[ENROL_DB] found ' . count($roles) . ' roles:');
34 foreach($roles as $role) {
36 //error_log('[ENROL_DB] setting up enrolments for '.$role->shortname);
38 /// Get the authoritative list of enrolments from the external database table
39 /// We're using the ADOdb functions natively here and not our datalib functions
40 /// because we didn't want to mess with the $db global
42 $useridfield = $enroldb->quote($user->{$CFG->enrol_localuserfield
});
44 list($have_role, $remote_role_name, $remote_role_value) = $this->role_fields($enroldb, $role);
46 /// Check if a particular role has been forced by the plugin site-wide
47 /// (if we aren't doing a role-based select)
48 if (!$have_role && $CFG->enrol_db_defaultcourseroleid
) {
49 $role = get_record('role', 'id', $CFG->enrol_db_defaultcourseroleid
);
52 /// Whether to fetch the default role on a per-course basis (below) or not.
53 $use_default_role = !$role;
57 error_log('[ENROL_DB] Doing role-specific select from db for role: '.$role->shortname);
58 } elseif ($use_default_role) {
59 error_log('[ENROL_DB] Using course default for roles - assuming that database lists defaults');
61 error_log('[ENROL_DB] Using config default for roles: '.$role->shortname);
64 if ($rs = $enroldb->Execute("SELECT {$CFG->enrol_remotecoursefield} as enrolremotecoursefield
65 FROM {$CFG->enrol_dbtable}
66 WHERE {$CFG->enrol_remoteuserfield} = " . $useridfield .
67 (isset($remote_role_name, $remote_role_value) ?
' AND '.$remote_role_name.' = '.$remote_role_value : ''))) {
69 // We'll use this to see what to add and remove
72 SELECT * FROM {$CFG->prefix}role_assignments
73 WHERE userid = {$user->id}
74 AND roleid = {$role->id}")
75 : get_records('role_assignments', 'userid', $user->id
);
82 if (!$rs->EOF
) { // We found some courses
85 $courselist = array();
86 while ($fields_obj = rs_fetch_next_record($rs)) { // Make a nice little array of courses to process
87 $courselist[] = $fields_obj->enrolremotecoursefield
;
92 //error_log('[ENROL_DB] Found '.count($existing).' existing roles and '.$count.' in external database');
94 foreach ($courselist as $coursefield) { /// Check the list of courses against existing
95 $course = get_record('course', $CFG->enrol_localcoursefield
, $coursefield);
96 if (!is_object($course)) {
97 if (empty($CFG->enrol_db_autocreate
)) { // autocreation not allowed
98 if (debugging('',DEBUG_ALL
)) {
99 error_log( "Course $coursefield does not exist, skipping") ;
101 continue; // next foreach course
103 // ok, now then let's create it!
104 // prepare any course properties we actually have
105 $course = new StdClass
;
106 $course->{$CFG->enrol_localcoursefield
} = $coursefield;
107 $course->fullname
= $coursefield;
108 $course->shortname
= $coursefield;
109 if (!($newcourseid = $this->create_course($course, true)
110 and $course = get_record( 'course', 'id', $newcourseid))) {
111 error_log( "Creating course $coursefield failed");
112 continue; // nothing left to do...
116 // if the course is hidden and we don't want to enrol in hidden courses
118 if (!$course->visible
and $CFG->enrol_db_ignorehiddencourse
) {
122 /// If there's no role specified, we get the default course role (usually student)
123 if ($use_default_role) {
124 $role = get_default_course_role($course);
127 $context = get_context_instance(CONTEXT_COURSE
, $course->id
);
129 // Couldn't get a role or context, skip.
130 if (!$role ||
!$context) {
134 // Search the role assignments to see if this user
135 // already has this role in this context. If it is, we
136 // skip to the next course.
137 foreach($existing as $key => $role_assignment) {
138 if ($role_assignment->roleid
== $role->id
139 && $role_assignment->contextid
== $context->id
) {
140 unset($existing[$key]);
141 //error_log('[ENROL_DB] User is already enroled in course '.$course->idnumber);
146 //error_log('[ENROL_DB] Enrolling user in course '.$course->idnumber);
147 role_assign($role->id
, $user->id
, 0, $context->id
, 0, 0, 0, 'database');
149 } // We've processed all external courses found
151 /// We have some courses left that we might need to unenrol from
152 /// Note: we only process enrolments that we (ie 'database' plugin) made
153 /// Do not unenrol anybody if the disableunenrol option is 'yes'
154 if (!$CFG->enrol_db_disableunenrol
) {
155 foreach ($existing as $role_assignment) {
156 if ($role_assignment->enrol
== 'database') {
157 //error_log('[ENROL_DB] Removing user from context '.$role_assignment->contextid);
158 role_unassign($role_assignment->roleid
, $user->id
, '', $role_assignment->contextid
);
163 error_log('[ENROL_DB] Couldn\'t get rows from external db: '.$enroldb->ErrorMsg());
166 $this->enrol_disconnect($enroldb);
170 * sync enrolments with database, create courses if required.
172 * @param object The role to sync for. If no role is specified, defaults are
175 function sync_enrolments($role = null) {
178 error_reporting(E_ALL
);
180 // Connect to the external database
181 $enroldb = $this->enrol_connect();
183 notify("enrol/database cannot connect to server");
188 echo '=== Syncing enrolments for role: '.$role->shortname
." ===\n";
190 echo "=== Syncing enrolments for default role ===\n";
193 // first, pack the sortorder...
194 fix_course_sortorder();
196 list($have_role, $remote_role_name, $remote_role_value) = $this->role_fields($enroldb, $role);
199 if (!empty($CFG->enrol_db_defaultcourseroleid
)
200 and $role = get_record('role', 'id', $CFG->enrol_db_defaultcourseroleid
)) {
201 echo "=== Using enrol_db_defaultcourseroleid: {$role->id} ({$role->shortname}) ===\n";
202 } elseif (isset($role)) {
203 echo "!!! WARNING: Role specified by caller, but no (or invalid) role configuration !!!\n";
207 // get enrolments per-course
208 $sql = "SELECT DISTINCT {$CFG->enrol_remotecoursefield} " .
209 " FROM {$CFG->enrol_dbtable} " .
210 " WHERE {$CFG->enrol_remoteuserfield} IS NOT NULL" .
211 (isset($remote_role_name, $remote_role_value) ?
' AND '.$remote_role_name.' = '.$remote_role_value : '');
213 $rs = $enroldb->Execute($sql);
215 trigger_error($enroldb->ErrorMsg() .' STATEMENT: '. $sql);
218 if ( $rs->EOF
) { // no courses! outta here...
223 $extcourses = array();
224 while ($extcourse_obj = rs_fetch_next_record($rs)) { // there are more course records
225 $extcourse = $extcourse_obj->{$CFG->enrol_remotecoursefield
};
226 array_push($extcourses, $extcourse);
228 // does the course exist in moodle already?
230 $course = get_record( 'course',
231 $CFG->enrol_localcoursefield
,
234 if (!is_object($course)) {
235 if (empty($CFG->enrol_db_autocreate
)) { // autocreation not allowed
236 if (debugging('', DEBUG_ALL
)) {
237 error_log( "Course $extcourse does not exist, skipping");
239 continue; // next foreach course
241 // ok, now then let's create it!
242 // prepare any course properties we actually have
243 $course = new StdClass
;
244 $course->{$CFG->enrol_localcoursefield
} = $extcourse;
245 $course->fullname
= $extcourse;
246 $course->shortname
= $extcourse;
247 if (!($newcourseid = $this->create_course($course, true)
248 and $course = get_record( 'course', 'id', $newcourseid))) {
249 error_log( "Creating course $extcourse failed");
250 continue; // nothing left to do...
255 $context = get_context_instance(CONTEXT_COURSE
, $course->id
);
257 // If we don't have a proper role setup, then we default to the default
258 // role for the current course.
260 $role = get_default_course_role($course);
263 // get a list of the student ids the are enrolled
264 // in the external db -- hopefully it'll fit in memory...
265 $extenrolments = array();
266 $sql = "SELECT {$CFG->enrol_remoteuserfield} " .
267 " FROM {$CFG->enrol_dbtable} " .
268 " WHERE {$CFG->enrol_remotecoursefield} = " . $enroldb->quote($extcourse) .
269 ($have_role ?
' AND '.$remote_role_name.' = '.$remote_role_value : '');
271 $crs = $enroldb->Execute($sql);
273 trigger_error($enroldb->ErrorMsg() .' STATEMENT: '. $sql);
276 if ( $crs->EOF
) { // shouldn't happen, but cover all bases
280 // slurp results into an array
281 while ($crs_obj = rs_fetch_next_record($crs)) {
282 array_push($extenrolments, $crs_obj->{$CFG->enrol_remoteuserfield
});
284 rs_close($crs); // release the handle
288 // hopefully they'll fit in the max buffer size for the RDBMS
290 // TODO: This doesn't work perfectly. If we are operating without
291 // roles in the external DB, then this doesn't handle changes of role
292 // within a course (because the user is still enrolled in the course,
293 // so NOT IN misses the course).
295 // When the user logs in though, their role list will be updated
298 if (!$CFG->enrol_db_disableunenrol
) {
299 $to_prune = get_records_sql("
301 FROM {$CFG->prefix}role_assignments ra
302 JOIN {$CFG->prefix}user u ON ra.userid = u.id
303 WHERE ra.enrol = 'database'
304 AND ra.contextid = {$context->id}
305 AND ra.roleid = ". $role->id
. ($extenrolments
306 ?
" AND u.{$CFG->enrol_localuserfield} NOT IN (".join(", ", array_map(array(&$db, 'quote'), $extenrolments)).")"
310 foreach ($to_prune as $role_assignment) {
311 if (role_unassign($role->id
, $role_assignment->userid
, 0, $role_assignment->contextid
)){
312 error_log( "Unassigned {$role->shortname} assignment #{$role_assignment->id} for course {$course->id} (" . format_string($course->shortname
) . "); user {$role_assignment->userid}");
314 error_log( "Failed to unassign {$role->shortname} assignment #{$role_assignment->id} for course {$course->id} (" . format_string($course->shortname
) . "); user {$role_assignment->userid}");
321 // insert current enrolments
322 // bad we can't do INSERT IGNORE with postgres...
324 foreach ($extenrolments as $member) {
325 // Get the user id and whether is enrolled in one fell swoop
327 SELECT u.id AS userid, ra.id AS enrolmentid
328 FROM {$CFG->prefix}user u
329 LEFT OUTER JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid
330 AND ra.roleid = {$role->id}
331 AND ra.contextid = {$context->id}
332 WHERE u.{$CFG->enrol_localuserfield} = ".$db->quote($member) .
333 " AND (u.deleted IS NULL OR u.deleted=0) ";
335 $ers = $db->Execute($sql);
337 trigger_error($db->ErrorMsg() .' STATEMENT: '. $sql);
340 if ( $ers->EOF
) { // if this returns empty, it means we don't have the student record.
341 // should not happen -- but skip it anyway
342 trigger_error('weird! no user record entry?');
345 $user_obj = rs_fetch_record($ers);
346 $userid = $user_obj->userid
;
347 $enrolmentid = $user_obj->enrolmentid
;
348 rs_close($ers); // release the handle
350 if ($enrolmentid) { // already enrolled - skip
354 if (role_assign($role->id
, $userid, 0, $context->id
, 0, 0, 0, 'database')){
355 error_log( "Assigned role {$role->shortname} to user {$userid} in course {$course->id} (" . format_string($course->shortname
) . ")");
357 error_log( "Failed to assign role {$role->shortname} to user {$userid} in course {$course->id} (" . format_string($course->shortname
) . ")");
360 } // end foreach member
361 } // end while course records
362 rs_close($rs); //Close the main course recordset
365 // prune enrolments to courses that are no longer in ext auth
367 // TODO: This doesn't work perfectly. If we are operating without
368 // roles in the external DB, then this doesn't handle changes of role
369 // within a course (because the user is still enrolled in the course,
370 // so NOT IN misses the course).
372 // When the user logs in though, their role list will be updated
375 if (!$CFG->enrol_db_disableunenrol
) {
377 SELECT ra.roleid, ra.userid, ra.contextid
378 FROM {$CFG->prefix}role_assignments ra
379 LEFT OUTER JOIN ({$CFG->prefix}context cn
380 JOIN {$CFG->prefix}course c ON cn.contextlevel = ".CONTEXT_COURSE
." AND cn.instanceid = c.id)
381 ON ra.contextid = cn.id
382 WHERE ra.enrol = 'database'" .
383 ($have_role ?
' AND ra.roleid = '.$role->id
: '') .
385 ?
" AND (c.id IS NULL OR c.{$CFG->enrol_localcoursefield} NOT IN (" . join(",", array_map(array(&$db, 'quote'), $extcourses)) . "))"
388 $ers = $db->Execute($sql);
390 trigger_error($db->ErrorMsg() .' STATEMENT: '. $sql);
394 while ($user_obj = rs_fetch_next_record($ers)) {
395 $roleid = $user_obj->roleid
;
396 $user = $user_obj->userid
;
397 $contextid = $user_obj->contextid
;
398 if (role_unassign($roleid, $user, 0, $contextid)){
399 error_log( "Unassigned role {$roleid} from user $user in context $contextid");
401 error_log( "Failed unassign role {$roleid} from user $user in context $contextid");
404 rs_close($ers); // release the handle
410 // we are done now, a bit of housekeeping
411 fix_course_sortorder();
413 $this->enrol_disconnect($enroldb);
417 /// Overide the get_access_icons() function
418 function get_access_icons($course) {
422 /// Overide the base config_form() function
423 function config_form($frm) {
426 $vars = array('enrol_dbhost', 'enrol_dbuser', 'enrol_dbpass',
427 'enrol_dbname', 'enrol_dbtable',
428 'enrol_localcoursefield', 'enrol_localuserfield',
429 'enrol_remotecoursefield', 'enrol_remoteuserfield',
430 'enrol_db_autocreate', 'enrol_db_category', 'enrol_db_template',
431 'enrol_db_localrolefield', 'enrol_db_remoterolefield',
432 'enrol_remotecoursefield', 'enrol_remoteuserfield',
433 'enrol_db_ignorehiddencourse', 'enrol_db_defaultcourseroleid',
434 'enrol_db_disableunenrol');
436 foreach ($vars as $var) {
437 if (!isset($frm->$var)) {
441 include("$CFG->dirroot/enrol/database/config.html");
444 /// Override the base process_config() function
445 function process_config($config) {
447 if (!isset($config->enrol_dbtype
)) {
448 $config->enrol_dbtype
= 'mysql';
450 set_config('enrol_dbtype', $config->enrol_dbtype
);
452 if (!isset($config->enrol_dbhost
)) {
453 $config->enrol_dbhost
= '';
455 set_config('enrol_dbhost', $config->enrol_dbhost
);
457 if (!isset($config->enrol_dbuser
)) {
458 $config->enrol_dbuser
= '';
460 set_config('enrol_dbuser', $config->enrol_dbuser
);
462 if (!isset($config->enrol_dbpass
)) {
463 $config->enrol_dbpass
= '';
465 set_config('enrol_dbpass', $config->enrol_dbpass
);
467 if (!isset($config->enrol_dbname
)) {
468 $config->enrol_dbname
= '';
470 set_config('enrol_dbname', $config->enrol_dbname
);
472 if (!isset($config->enrol_dbtable
)) {
473 $config->enrol_dbtable
= '';
475 set_config('enrol_dbtable', $config->enrol_dbtable
);
477 if (!isset($config->enrol_localcoursefield
)) {
478 $config->enrol_localcoursefield
= '';
480 set_config('enrol_localcoursefield', $config->enrol_localcoursefield
);
482 if (!isset($config->enrol_localuserfield
)) {
483 $config->enrol_localuserfield
= '';
485 set_config('enrol_localuserfield', $config->enrol_localuserfield
);
487 if (!isset($config->enrol_remotecoursefield
)) {
488 $config->enrol_remotecoursefield
= '';
490 set_config('enrol_remotecoursefield', $config->enrol_remotecoursefield
);
492 if (!isset($config->enrol_remoteuserfield
)) {
493 $config->enrol_remoteuserfield
= '';
495 set_config('enrol_remoteuserfield', $config->enrol_remoteuserfield
);
497 if (!isset($config->enrol_db_autocreate
)) {
498 $config->enrol_db_autocreate
= '';
500 set_config('enrol_db_autocreate', $config->enrol_db_autocreate
);
502 if (!isset($config->enrol_db_category
)) {
503 $config->enrol_db_category
= '';
505 set_config('enrol_db_category', $config->enrol_db_category
);
507 if (!isset($config->enrol_db_template
)) {
508 $config->enrol_db_template
= '';
510 set_config('enrol_db_template', $config->enrol_db_template
);
512 if (!isset($config->enrol_db_defaultcourseroleid
)) {
513 $config->enrol_db_defaultcourseroleid
= '';
515 set_config('enrol_db_defaultcourseroleid', $config->enrol_db_defaultcourseroleid
);
517 if (!isset($config->enrol_db_localrolefield
)) {
518 $config->enrol_db_localrolefield
= '';
520 set_config('enrol_db_localrolefield', $config->enrol_db_localrolefield
);
522 if (!isset($config->enrol_db_remoterolefield
)) {
523 $config->enrol_db_remoterolefield
= '';
525 set_config('enrol_db_remoterolefield', $config->enrol_db_remoterolefield
);
527 if (!isset($config->enrol_db_ignorehiddencourse
)) {
528 $config->enrol_db_ignorehiddencourse
= '';
530 set_config('enrol_db_ignorehiddencourse', $config->enrol_db_ignorehiddencourse
);
532 if (!isset($config->enrol_db_disableunenrol
)) {
533 $config->enrol_db_disableunenrol
= '';
535 set_config('enrol_db_disableunenrol', $config->enrol_db_disableunenrol
);
540 // will create the moodle course from the template
541 // course_ext is an array as obtained from ldap -- flattened somewhat
542 // NOTE: if you pass true for $skip_fix_course_sortorder
543 // you will want to call fix_course_sortorder() after your are done
544 // with course creation
545 function create_course ($course,$skip_fix_course_sortorder=0){
549 if(!empty($CFG->enrol_db_template
)){
550 $template = get_record("course", 'shortname', $CFG->enrol_db_template
);
551 $template = (array)$template;
555 'startdate' => time() +
3600 * 24,
556 'summary' => get_string("defaultcoursesummary"),
566 'groupmodeforce' => 0,
567 'student' => $site->student
,
568 'students' => $site->students
,
569 'teacher' => $site->teacher
,
570 'teachers' => $site->teachers
,
574 foreach (array_keys($template) AS $key) {
575 if (empty($course->$key)) {
576 $course->$key = $template[$key];
580 $course->category
= 1; // the misc 'catch-all' category
581 if (!empty($CFG->enrol_db_category
)){ //category = 0 or undef will break moodle
582 $course->category
= $CFG->enrol_db_category
;
585 // define the sortorder
586 $sort = get_field_sql('SELECT COALESCE(MAX(sortorder)+1, 100) AS max ' .
587 ' FROM ' . $CFG->prefix
. 'course ' .
588 ' WHERE category=' . $course->category
);
589 $course->sortorder
= $sort;
591 // override with local data
592 $course->startdate
= time() +
3600 * 24;
593 $course->timecreated
= time();
594 $course->visible
= 1;
596 // clear out id just in case
599 // truncate a few key fields
600 $course->idnumber
= substr($course->idnumber
, 0, 100);
601 $course->shortname
= substr($course->shortname
, 0, 100);
604 if ($newcourseid = insert_record("course", addslashes_object($course))) { // Set up new course
606 $section->course
= $newcourseid; // Create a default section.
607 $section->section
= 0;
608 $section->id
= insert_record("course_sections", $section);
609 $page = page_create_object(PAGE_COURSE_VIEW
, $newcourseid);
610 blocks_repopulate_page($page); // Return value no
613 if(!$skip_fix_course_sortorder){
614 fix_course_sortorder();
616 add_to_log($newcourseid, "course", "new", "view.php?id=$newcourseid", "enrol/database auto-creation");
618 trigger_error("Could not create new course $extcourse from from database");
619 notify("Serious Error! Could not create the new course!");
627 /// NOTE: You MUST remember to disconnect
628 /// when you stop using it -- as this call will
629 /// sometimes modify $CFG->prefix for the whole of Moodle!
630 function enrol_connect() {
633 // Try to connect to the external database (forcing new connection)
634 $enroldb = &ADONewConnection($CFG->enrol_dbtype
);
635 if ($enroldb->Connect($CFG->enrol_dbhost
, $CFG->enrol_dbuser
, $CFG->enrol_dbpass
, $CFG->enrol_dbname
, true)) {
636 $enroldb->SetFetchMode(ADODB_FETCH_ASSOC
); ///Set Assoc mode always after DB connection
639 trigger_error("Error connecting to enrolment DB backend with: "
640 . "$CFG->enrol_dbhost,$CFG->enrol_dbuser,$CFG->enrol_dbpass,$CFG->enrol_dbname");
646 function enrol_disconnect($enroldb) {
653 * This function returns the name and value of the role field to query the db
654 * for, or null if there isn't one.
656 * @param object The ADOdb connection
657 * @param object The role
658 * @return array (boolean, string, db quoted string)
660 function role_fields($enroldb, $role) {
663 if ($have_role = !empty($role)
664 && !empty($CFG->enrol_db_remoterolefield
)
665 && !empty($CFG->enrol_db_localrolefield
)
666 && !empty($role->{$CFG->enrol_db_localrolefield
})) {
667 $remote_role_name = $CFG->enrol_db_remoterolefield
;
668 $remote_role_value = $enroldb->quote($role->{$CFG->enrol_db_localrolefield
});
670 $remote_role_name = $remote_role_value = null;
673 return array($have_role, $remote_role_name, $remote_role_value);