diff --git a/enrol/database/cli/sync.php b/enrol/database/cli/sync.php index 79af47da48c..0054c0ac773 100644 --- a/enrol/database/cli/sync.php +++ b/enrol/database/cli/sync.php @@ -1,5 +1,4 @@ libdir.'/clilib.php'); -if (!enrol_is_enabled('database')) { - die('enrol_database plugin is disabled, sync is disabled'); +// now get cli options +list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help')); + +if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); } +if ($options['help']) { + $help = +"Execute enrol sync with external database. +The enrol_database plugin must be enabled and properly configured. +Options: +-v, --verbose Print verbose progess information +-h, --help Print out this help + +Example: +\$sudo -u www-data /usr/bin/php enrol/database/cli/sync.php + +Sample cron entry: +# 5 minutes past 4am +5 4 * * * \$sudo -u www-data /usr/bin/php /var/www/moodle/enrol/database/cli/sync.php +"; + + echo $help; + die; +} + +if (!enrol_is_enabled('database')) { + echo('enrol_database plugin is disabled, sync is disabled'."\n"); + exit(1); +} + +$verbose = !empty($options['verbose']); $enrol = enrol_get_plugin('database'); -$enrol->sync_courses(); -$enrol->sync_enrolments(); +$result = 0; + +$result = $result | $enrol->sync_courses($verbose); +$result = $result | $enrol->sync_enrolments($verbose); + +exit($result); \ No newline at end of file diff --git a/enrol/database/lib.php b/enrol/database/lib.php index 987e0e73f96..814e723a46d 100644 --- a/enrol/database/lib.php +++ b/enrol/database/lib.php @@ -1,5 +1,4 @@ db_init(); + if (!$extdb = $this->db_init()) { + // can not connect to database, sorry + return; + } // read remote enrols and create instances $sql = $this->db_get_sql($table, array($userfield=>$user->$localuserfield), array(), false); @@ -242,22 +244,33 @@ class enrol_database_plugin extends enrol_plugin { /** * Forces synchronisation of all enrolments with external database. * - * @return void + * @param bool $verbose + * @return int 0 means success, 1 db connect failure, 2 db read failure */ - public function sync_enrolments() { + public function sync_enrolments($verbose = false) { global $CFG, $DB; // we do not create courses here intentionally because it requires full sync and is slow 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')) { - return; + if ($verbose) { + mtrace('User enrolment synchronisation skipped.'); + } + return 0; + } + + if ($verbose) { + mtrace('Starting user enrolment synchronisation...'); + } + + if (!$extdb = $this->db_init()) { + mtrace('Error while communicating with external enrolment database'); + return 1; } // we may need a lot of memory here @set_time_limit(0); raise_memory_limit(MEMORY_HUGE); - $extdb = $this->db_init(); - // second step is to sync instances and users $table = $this->get_config('remoteenroltable'); $coursefield = strtolower($this->get_config('remotecoursefield')); @@ -298,18 +311,20 @@ class enrol_database_plugin extends enrol_plugin { } $rs->Close(); } else { - debugging('Error while communicating with external enrolment database'); + mtrace('Error reading data from the external enrolment table'); $extdb->Close(); - return; + return 2; } $preventfullunenrol = empty($externalcourses); if ($preventfullunenrol and $unenrolaction == ENROL_EXT_REMOVED_UNENROL) { - debugging('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.'); + if ($verbose) { + 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.'); + } } // first find all existing courses with enrol instance $existing = array(); - $sql = "SELECT c.id, c.visible, c.$localcoursefield AS mapping, e.id AS enrolid + $sql = "SELECT c.id, c.visible, c.$localcoursefield AS mapping, e.id AS enrolid, c.shortname FROM {course} c JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'database')"; $rs = $DB->get_recordset_sql($sql); // watch out for idnumber duplicates @@ -328,7 +343,7 @@ class enrol_database_plugin extends enrol_plugin { $localnotempty = "AND c.$localcoursefield <> :lcfe"; $params['lcfe'] = $DB->sql_empty(); } - $sql = "SELECT c.id, c.visible, c.$localcoursefield AS mapping + $sql = "SELECT c.id, c.visible, c.$localcoursefield AS mapping, c.shortname FROM {course} c LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'database') WHERE e.id IS NULL $localnotempty"; @@ -430,7 +445,7 @@ class enrol_database_plugin extends enrol_plugin { } $rs->Close(); } else { - debugging('Error while communicating with external enrolment database'); + mtrace('Error while communicating with external enrolment database'); $extdb->Close(); return; } @@ -443,6 +458,9 @@ class enrol_database_plugin extends enrol_plugin { $this->enrol_user($instance, $userid, $roleid); $current_roles[$userid][$roleid] = $roleid; $current_status[$userid] = ENROL_USER_ACTIVE; + if ($verbose) { + mtrace(" enrolling: $userid ==> $course->shortname as ".$allroles[$roleid]->shortname); + } } } @@ -451,12 +469,18 @@ class enrol_database_plugin extends enrol_plugin { if (empty($userroles[$cr])) { role_unassign($cr, $userid, $context->id, 'enrol_database', $instance->id); unset($current_roles[$userid][$cr]); + if ($verbose) { + mtrace(" unsassigning roles: $userid ==> $course->shortname"); + } } } // reenable enrolment when previously disable enrolment refreshed if ($current_status[$userid] == ENROL_USER_SUSPENDED) { $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$userid)); + if ($verbose) { + mtrace(" unsuspending: $userid ==> $course->shortname"); + } } } @@ -469,6 +493,9 @@ class enrol_database_plugin extends enrol_plugin { continue; } $this->unenrol_user($instance, $userid); + if ($verbose) { + mtrace(" unenrolling: $userid ==> $course->shortname"); + } } } @@ -483,9 +510,15 @@ class enrol_database_plugin extends enrol_plugin { } if ($status != ENROL_USER_SUSPENDED) { $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$userid)); + if ($verbose) { + mtrace(" suspending: $userid ==> $course->shortname"); + } } if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) { role_unassign_all(array('contextid'=>$context->id, 'userid'=>$userid, 'component'=>'enrol_database', 'itemid'=>$instance->id)); + if ($verbose) { + mtrace(" unsassigning all roles: $userid ==> $course->shortname"); + } } } } @@ -493,6 +526,12 @@ class enrol_database_plugin extends enrol_plugin { // close db connection $extdb->Close(); + + if ($verbose) { + mtrace('...user enrolment synchronisation finished.'); + } + + return 0; } /** @@ -500,21 +539,33 @@ class enrol_database_plugin extends enrol_plugin { * * First it creates new courses if necessary, then * enrols and unenrols users. - * @return void + * + * @param bool $verbose + * @return int 0 means success, 1 db connect failure, 4 db read failure */ - public function sync_courses() { + public function sync_courses($verbose = false) { global $CFG, $DB; // make sure we sync either enrolments or courses 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')) { - return; + if ($verbose) { + mtrace('Course synchronisation skipped.'); + } + return 0; + } + + if ($verbose) { + mtrace('Starting course synchronisation...'); } // we may need a lot of memory here @set_time_limit(0); raise_memory_limit(MEMORY_HUGE); - $extdb = $this->db_init(); + if (!$extdb = $this->db_init()) { + mtrace('Error while communicating with external enrolment database'); + return 1; + } // first create new courses $table = $this->get_config('newcoursetable'); @@ -534,24 +585,30 @@ class enrol_database_plugin extends enrol_plugin { $createcourses = array(); if ($rs = $extdb->Execute($sql)) { if (!$rs->EOF) { - $courselist = array(); while ($fields = $rs->FetchRow()) { $fields = array_change_key_case($fields, CASE_LOWER); + $fields = $this->db_decode($fields); if (empty($fields[$shortname]) or empty($fields[$fullname])) { - //invalid record - these two are mandatory + if ($verbose) { + mtrace(' error: invalid external course record, shortname and fullname are mandatory: ' . json_encode($fields)); // hopefully every geek can read JS, right? + } continue; } - $fields = $this->db_decode($fields); if ($DB->record_exists('course', array('shortname'=>$fields[$shortname]))) { // already exists continue; } - if ($idnumber and $DB->record_exists('course', array('idnumber'=>$fields[$idnumber]))) { - // idnumber duplicates are not allowed + // allow empty idnumber but not duplicates + if ($idnumber and $fields[$idnumber] !== '' and $fields[$idnumber] !== null and $DB->record_exists('course', array('idnumber'=>$fields[$idnumber]))) { + if ($verbose) { + mtrace(' error: duplicate idnumber, can not create course: '.$fields[$shortname].' ['.$fields[$idnumber].']'); + } continue; } if ($category and !$DB->record_exists('course_categories', array('id'=>$fields[$category]))) { - // invalid category id, better to skip + if ($verbose) { + mtrace(' error: invalid category id, can not create course: '.$fields[$shortname]); + } continue; } $course = new stdClass(); @@ -564,9 +621,9 @@ class enrol_database_plugin extends enrol_plugin { } $rs->Close(); } else { - debugging('Error while communicating with external enrolment database'); + mtrace('Error reading data from the external course table'); $extdb->Close(); - return; + return 4; } if ($createcourses) { require_once("$CFG->dirroot/course/lib.php"); @@ -599,7 +656,10 @@ class enrol_database_plugin extends enrol_plugin { $newcourse->idnumber = $fields->idnumber; $newcourse->category = $fields->category ? $fields->category : $defaultcategory; - create_course($newcourse); + $c = create_course($newcourse); + if ($verbose) { + mtrace(" creating course: $c->id, $c->fullname, $c->shortname, $c->idnumber, $c->category"); + } } unset($createcourses); @@ -608,6 +668,12 @@ class enrol_database_plugin extends enrol_plugin { // close db connection $extdb->Close(); + + if ($verbose) { + mtrace('...course synchronisation finished.'); + } + + return 0; } protected function db_get_sql($table, array $conditions, array $fields, $distinct = false, $sort = "") { @@ -631,6 +697,11 @@ class enrol_database_plugin extends enrol_plugin { return $sql; } + /** + * Tries to make connection to the external database. + * + * @return null|ADONewConnection + */ protected function db_init() { global $CFG; @@ -643,7 +714,11 @@ class enrol_database_plugin extends enrol_plugin { ob_start(); //start output buffer to allow later use of the page headers } - $extdb->Connect($this->get_config('dbhost'), $this->get_config('dbuser'), $this->get_config('dbpass'), $this->get_config('dbname'), true); + $result = $extdb->Connect($this->get_config('dbhost'), $this->get_config('dbuser'), $this->get_config('dbpass'), $this->get_config('dbname'), true); + if (!$result) { + return null; + } + $extdb->SetFetchMode(ADODB_FETCH_ASSOC); if ($this->get_config('dbsetupsql')) { $extdb->Execute($this->get_config('dbsetupsql'));