/** * Return criteria status text for display in reports * * @param completion_completion $completion The user's completion record * @return string */ public function get_status($completion) { return $completion->is_complete() ? get_string('yes') : get_string('no'); }
/** * Review this criteria and decide if the user has completed * * @param completion_completion $completion The user's completion record * @param bool $mark Optionally set false to not save changes to database * @return bool */ public function review($completion, $mark = true) { global $DB; $course = $DB->get_record('course', array('id' => $completion->course)); $cm = $DB->get_record('course_modules', array('id' => $this->moduleinstance)); $info = new completion_info($course); $data = $info->get_data($cm, false, $completion->userid); // If the activity is complete if (in_array($data->completionstate, array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS))) { if ($mark) { $completion->mark_complete(); } return true; } return false; }
/** * Has the supplied user completed this course * * @param int $user_id User's id * @return boolean */ public function is_course_complete($user_id) { $params = array('userid' => $user_id, 'course' => $this->course_id); $ccompletion = new completion_completion($params); return $ccompletion->is_complete(); }
$fulldescribe = get_string('progress-title', 'completion', $a); if ($csv) { print $sep . csv_quote($describe); } else { if ($allow_marking_criteria === $criterion->id) { $describe = get_string('completion-alt-auto-' . $completiontype, 'completion'); print '<td class="completion-progresscell">' . '<a href="' . $CFG->wwwroot . '/course/togglecompletion.php?user='******'&course=' . $course->id . '&rolec=' . $allow_marking_criteria . '&sesskey=' . sesskey() . '">' . '<img src="' . $OUTPUT->pix_url('i/completion-manual-' . ($is_complete ? 'y' : 'n')) . '" alt="' . $describe . '" class="icon" title="' . get_string('markcomplete', 'completion') . '" /></a></td>'; } else { print '<td class="completion-progresscell">' . '<img src="' . $OUTPUT->pix_url('i/' . $completionicon) . '" alt="' . $describe . '" class="icon" title="' . $fulldescribe . '" /></td>'; } } } // Handle overall course completion // Load course completion $params = array('userid' => $user->id, 'course' => $course->id); $ccompletion = new completion_completion($params); $completiontype = $ccompletion->is_complete() ? 'y' : 'n'; $describe = get_string('completion-alt-auto-' . $completiontype, 'completion'); $a = new StdClass(); $a->state = $describe; $a->date = ''; $a->user = fullname($user); $a->activity = strip_tags(get_string('coursecomplete', 'completion')); $fulldescribe = get_string('progress-title', 'completion', $a); if ($csv) { print $sep . csv_quote($describe); } else { print '<td class="completion-progresscell">'; // Display course completion status icon print '<img src="' . $OUTPUT->pix_url('i/completion-auto-' . $completiontype) . '" alt="' . $describe . '" class="icon" title="' . $fulldescribe . '" />'; print '</td>';
/** * Add a record to the facetoface submissions table and sends out an * email confirmation * * @param class $session record from the facetoface_sessions table * @param class $facetoface record from the facetoface table * @param class $course record from the course table * @param string $discountcode code entered by the user * @param integer $notificationtype type of notifications to send to user * @see {{MDL_F2F_INVITE}} * @param integer $statuscode Status code to set * @param integer $userid user to signup * @param bool $notifyuser whether or not to send an email confirmation * @param bool $displayerrors whether or not to return an error page on errors */ function facetoface_user_signup($session, $facetoface, $course, $discountcode, $notificationtype, $statuscode, $userid = false, $notifyuser = true) { global $DB, $OUTPUT, $USER; // Get user id if (!$userid) { $userid = $USER->id; } $return = false; $timenow = time(); // Check to see if a signup already exists if ($existingsignup = $DB->get_record('facetoface_signups', array('sessionid' => $session->id, 'userid' => $userid))) { $usersignup = $existingsignup; } else { // Otherwise, prepare a signup object $usersignup = new stdClass(); $usersignup->sessionid = $session->id; $usersignup->userid = $userid; } $usersignup->mailedreminder = 0; $usersignup->notificationtype = $notificationtype; $usersignup->discountcode = trim(strtoupper($discountcode)); if (empty($usersignup->discountcode)) { $usersignup->discountcode = null; } // Update/insert the signup record if (!empty($usersignup->id)) { $success = $DB->update_record('facetoface_signups', $usersignup); } else { $usersignup->id = $DB->insert_record('facetoface_signups', $usersignup); $success = (bool)$usersignup->id; } if (!$success) { print_error('error:couldnotupdatef2frecord', 'facetoface'); } // Work out which status to use // If approval not required if (!$facetoface->approvalreqd) { $new_status = $statuscode; } else { // If approval required // Get current status (if any) $current_status = $DB->get_field('facetoface_signups_status', 'statuscode', array('signupid' => $usersignup->id, 'superceded' => 0)); // If approved, then no problem if ($current_status == MDL_F2F_STATUS_APPROVED) { $new_status = $statuscode; } else if ($session->datetimeknown) { // If currently on the waitlist they have already been approved, no need to approve them again. if ($current_status == MDL_F2F_STATUS_WAITLISTED) { $new_status = $statuscode; } else { // Otherwise, send manager request. $new_status = MDL_F2F_STATUS_REQUESTED; } } else { $new_status = MDL_F2F_STATUS_WAITLISTED; } } // Update status. if (!facetoface_update_signup_status($usersignup->id, $new_status, $USER->id)) { print_error('error:f2ffailedupdatestatus', 'facetoface'); } // Add to user calendar -- if facetoface usercalentry is set to true if ($facetoface->usercalentry && in_array($new_status, array(MDL_F2F_STATUS_BOOKED, MDL_F2F_STATUS_WAITLISTED))) { facetoface_add_session_to_calendar($session, $facetoface, 'user', $userid, 'booking'); } // If session has already started, do not send a notification if (facetoface_has_session_started($session, $timenow)) { $notifyuser = false; } // Send notification. $notifytype = ((int)$notificationtype == MDL_F2F_NONE ? false : true); $session->notifyuser = $notifyuser && $notifytype; switch ($new_status) { case MDL_F2F_STATUS_BOOKED: $error = facetoface_send_confirmation_notice($facetoface, $session, $userid, $notificationtype, false); break; case MDL_F2F_STATUS_WAITLISTED: $error = facetoface_send_confirmation_notice($facetoface, $session, $userid, $notificationtype, true); break; case MDL_F2F_STATUS_REQUESTED: $error = facetoface_send_request_notice($facetoface, $session, $userid); break; } if (!empty($error)) { if ($error == 'userdoesnotexist') { print_error($error, 'facetoface'); } else { // Don't fail if email isn't sent, just display a warning echo $OUTPUT->notification(get_string($error, 'facetoface'), 'notifyproblem'); } } if ($session->notifyuser) { if (!$DB->update_record('facetoface_signups', $usersignup)) { print_error('error:couldnotupdatef2frecord', 'facetoface'); } } // Update course completion. if (in_array($new_status, array(MDL_F2F_STATUS_BOOKED, MDL_F2F_STATUS_WAITLISTED))) { $completion = new completion_info($course); if ($completion->is_enabled()) { $ccdetails = array( 'course' => $course->id, 'userid' => $userid, ); $cc = new completion_completion($ccdetails); $cc->mark_inprogress($timenow); } } return true; }
/** * Test course completed event. */ public function test_course_completed_event() { global $USER; $this->setup_data(); $this->setAdminUser(); $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id)); // Mark course as complete and get triggered event. $sink = $this->redirectEvents(); $ccompletion->mark_complete(); $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\\core\\event\\course_completed', $event); $this->assertEquals($this->course->id, $event->get_record_snapshot('course_completions', $event->objectid)->course); $this->assertEquals($this->course->id, $event->courseid); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->user->id, $event->relateduserid); $this->assertEquals(context_course::instance($this->course->id), $event->get_context()); $this->assertInstanceOf('moodle_url', $event->get_url()); $data = $ccompletion->get_record_data(); $this->assertEventLegacyData($data, $event); }
/** * Review this criteria and decide if the user has completed * * @param completion_completion $completion The user's completion record * @param bool $mark Optionally set false to not save changes to database * @param bool $is_complete Set to false if the criteria has been completed just now. * @return bool */ public function review($completion, $mark = true, $is_complete = false) { // If we are marking this as complete if ($is_complete && $mark) { $completion->completedself = 1; $completion->mark_complete(); return true; } return $completion->is_complete(); }
/** * Aggregate each user's criteria completions * * @return void */ function completion_cron_completions() { global $DB; if (debugging()) { mtrace('Aggregating completions'); } // Save time started $timestarted = time(); // Grab all criteria and their associated criteria completions $sql = ' SELECT DISTINCT c.id AS course, cr.id AS criteriaid, crc.userid AS userid, cr.criteriatype AS criteriatype, cc.timecompleted AS timecompleted FROM {course_completion_criteria} cr INNER JOIN {course} c ON cr.course = c.id INNER JOIN {course_completions} crc ON crc.course = c.id LEFT JOIN {course_completion_crit_compl} cc ON cc.criteriaid = cr.id AND crc.userid = cc.userid WHERE c.enablecompletion = 1 AND crc.timecompleted IS NULL AND crc.reaggregate > 0 AND crc.reaggregate < :timestarted ORDER BY course, userid '; // Check if result is empty if (!($rs = $DB->get_recordset_sql($sql, array('timestarted' => $timestarted)))) { return; } $current_user = null; $current_course = null; $completions = array(); while (1) { // Grab records for current user/course foreach ($rs as $record) { // If we are still grabbing the same users completions if ($record->userid === $current_user && $record->course === $current_course) { $completions[$record->criteriaid] = $record; } else { break; } } // Aggregate if (!empty($completions)) { if (debugging()) { mtrace('Aggregating completions for user ' . $current_user . ' in course ' . $current_course); } // Get course info object $info = new completion_info((object) array('id' => $current_course)); // Setup aggregation $overall = $info->get_aggregation_method(); $activity = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY); $prerequisite = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE); $role = $info->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE); $overall_status = null; $activity_status = null; $prerequisite_status = null; $role_status = null; // Get latest timecompleted $timecompleted = null; // Check each of the criteria foreach ($completions as $params) { $timecompleted = max($timecompleted, $params->timecompleted); $completion = new completion_criteria_completion($params, false); // Handle aggregation special cases if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { completion_cron_aggregate($activity, $completion->is_complete(), $activity_status); } else { if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) { completion_cron_aggregate($prerequisite, $completion->is_complete(), $prerequisite_status); } else { if ($params->criteriatype == COMPLETION_CRITERIA_TYPE_ROLE) { completion_cron_aggregate($role, $completion->is_complete(), $role_status); } else { completion_cron_aggregate($overall, $completion->is_complete(), $overall_status); } } } } // Include role criteria aggregation in overall aggregation if ($role_status !== null) { completion_cron_aggregate($overall, $role_status, $overall_status); } // Include activity criteria aggregation in overall aggregation if ($activity_status !== null) { completion_cron_aggregate($overall, $activity_status, $overall_status); } // Include prerequisite criteria aggregation in overall aggregation if ($prerequisite_status !== null) { completion_cron_aggregate($overall, $prerequisite_status, $overall_status); } // If aggregation status is true, mark course complete for user if ($overall_status) { if (debugging()) { mtrace('Marking complete'); } $ccompletion = new completion_completion(array('course' => $params->course, 'userid' => $params->userid)); $ccompletion->mark_complete($timecompleted); } } // If this is the end of the recordset, break the loop if (!$rs->valid()) { $rs->close(); break; } // New/next user, update user details, reset completions $current_user = $record->userid; $current_course = $record->course; $completions = array(); $completions[$record->criteriaid] = $record; } // Mark all users as aggregated $sql = "\n UPDATE\n {course_completions}\n SET\n reaggregate = 0\n WHERE\n reaggregate < {$timestarted}\n "; $DB->execute($sql); }
/** * Add a record to the facetoface submissions table and sends out an * email confirmation * * @param class $session record from the facetoface_sessions table * @param class $facetoface record from the facetoface table * @param class $course record from the course table * @param string $discountcode code entered by the user * @param integer $notificationtype type of notifications to send to user * @see {{MDL_F2F_INVITE}} * @param integer $statuscode Status code to set * @param integer $userid user to signup * @param bool $notifyuser whether or not to send an email confirmation * @param bool $displayerrors whether or not to return an error page on errors */ function facetoface_user_signup($session, $facetoface, $course, $discountcode, $notificationtype, $statuscode, $userid = false, $notifyuser = true) { global $CFG, $DB; // Get user ID. if (!$userid) { global $USER; $userid = $USER->id; } $return = false; $timenow = time(); // Check to see if a signup already exists. if ($existingsignup = $DB->get_record('facetoface_signups', array('sessionid' => $session->id, 'userid' => $userid))) { $usersignup = $existingsignup; } else { // Otherwise, prepare a signup object. $usersignup = new stdclass(); $usersignup->sessionid = $session->id; $usersignup->userid = $userid; } $usersignup->mailedreminder = 0; $usersignup->notificationtype = $notificationtype; $usersignup->discountcode = trim(strtoupper($discountcode)); if (empty($usersignup->discountcode)) { $usersignup->discountcode = null; } // Update/insert the signup record. if (!empty($usersignup->id)) { $success = $DB->update_record('facetoface_signups', $usersignup); } else { $usersignup->id = $DB->insert_record('facetoface_signups', $usersignup); $success = (bool) $usersignup->id; } if (!$success) { print_error('error:couldnotupdatef2frecord', 'facetoface'); return false; } // Work out which status to use. // If approval not required. if (!$facetoface->approvalreqd) { $newstatus = $statuscode; } else { // If approval required. // Get current status (if any). $currentstatus = $DB->get_field('facetoface_signups_status', 'statuscode', array('signupid' => $usersignup->id, 'superceded' => 0)); // If approved, then no problem. if ($currentstatus == MDL_F2F_STATUS_APPROVED) { $newstatus = $statuscode; } else { if ($session->datetimeknown) { // Otherwise, send manager request. $newstatus = MDL_F2F_STATUS_REQUESTED; } else { $newstatus = MDL_F2F_STATUS_WAITLISTED; } } } // Update status. if (!facetoface_update_signup_status($usersignup->id, $newstatus, $userid)) { print_error('error:f2ffailedupdatestatus', 'facetoface'); return false; } // Add to user calendar -- if facetoface usercalentry is set to true. if ($facetoface->usercalentry) { if (in_array($newstatus, array(MDL_F2F_STATUS_BOOKED, MDL_F2F_STATUS_WAITLISTED))) { facetoface_add_session_to_calendar($session, $facetoface, 'user', $userid, 'booking'); } } // Course completion. if (in_array($newstatus, array(MDL_F2F_STATUS_BOOKED, MDL_F2F_STATUS_WAITLISTED))) { $completion = new completion_info($course); if ($completion->is_enabled()) { $ccdetails = array('course' => $course->id, 'userid' => $userid); $cc = new completion_completion($ccdetails); $cc->mark_inprogress($timenow); } } // If session has already started, do not send a notification. if (facetoface_has_session_started($session, $timenow)) { $notifyuser = false; } // Send notification. if ($notifyuser) { // If booked/waitlisted. switch ($newstatus) { case MDL_F2F_STATUS_BOOKED: $error = facetoface_send_confirmation_notice($facetoface, $session, $userid, $notificationtype, false); break; case MDL_F2F_STATUS_WAITLISTED: $error = facetoface_send_confirmation_notice($facetoface, $session, $userid, $notificationtype, true); break; case MDL_F2F_STATUS_REQUESTED: $error = facetoface_send_request_notice($facetoface, $session, $userid); break; } if (!empty($error)) { print_error($error, 'facetoface'); return false; } if (!$DB->update_record('facetoface_signups', $usersignup)) { print_error('error:couldnotupdatef2frecord', 'facetoface'); return false; } } return true; }
/** * Mark this criteria complete for the associated user * * This method creates a course_completion_crit_compl record */ public function mark_complete() { // Create record $this->timecompleted = time(); // Save record if ($this->id) { $this->update(); } else { $this->insert(); } // Mark course completion record as started (if not already) $cc = array('course' => $this->course, 'userid' => $this->userid); $ccompletion = new completion_completion($cc); $ccompletion->mark_inprogress($this->timecompleted); }
/** * Test badges observer when course_completed event is fired. */ public function test_badges_observer_course_criteria_review() { $this->preventResetByRollback(); // Messaging is not compatible with transactions. $badge = new badge($this->coursebadge); $this->assertFalse($badge->is_issued($this->user->id)); $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id)); $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY)); $criteria_overall1 = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_COURSE, 'badgeid' => $badge->id)); $criteria_overall1->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'course_' . $this->course->id => $this->course->id)); $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id)); // Mark course as complete. $sink = $this->redirectEmails(); $ccompletion->mark_complete(); $this->assertCount(1, $sink->get_messages()); $sink->close(); // Check if badge is awarded. $this->assertDebuggingCalled('Error baking badge image!'); $this->assertTrue($badge->is_issued($this->user->id)); }
/** * Return criteria status text for display in reports * * @param completion_completion $completion The user's completion record * @return string */ public function get_status($completion) { return $completion->is_complete() ? get_string('yes') : userdate($this->timeend, '%d-%h-%y'); }
/** * Review this criteria and decide if the user has completed * * @param completion_completion $completion The user's completion record * @param bool $mark Optionally set false to not save changes to database * @return bool */ public function review($completion, $mark = true) { global $DB; $course = $DB->get_record('course', array('id' => $this->courseinstance)); $info = new completion_info($course); // If the course is complete if ($info->is_course_complete($completion->userid)) { if ($mark) { $completion->mark_complete(); } return true; } return false; }
/** * Forces synchronisation of all enrolments with external database. * * @param progress_trace $trace * @param null|int $onecourse limit sync to one course only (used primarily in restore) * @return int 0 means success, 1 db connect failure, 2 db read failure */ public function sync_enrolments(progress_trace $trace, $onecourse = null) { 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('remoteenroltable') or !$this->get_config('remotecoursefield') or !$this->get_config('remoteuserfield')) { $trace->output('User enrolment synchronisation skipped.'); $trace->finished(); return 0; } $trace->output('Starting user enrolment synchronisation...'); if (!($extdb = $this->db_init())) { $trace->output('Error while communicating with external enrolment database'); $trace->finished(); return 1; } // We may need a lot of memory here. core_php_time_limit::raise(); raise_memory_limit(MEMORY_HUGE); $table = $this->get_config('remoteenroltable'); $coursefield = trim($this->get_config('remotecoursefield')); $userfield = trim($this->get_config('remoteuserfield')); $rolefield = trim($this->get_config('remoterolefield')); $otheruserfield = trim($this->get_config('remoteotheruserfield')); $coursestatusfield = trim($this->get_config('remotecoursestatusfield')); $coursestatuscurrentfield = trim($this->get_config('remotecoursestatuscurrentfield')); $coursestatuscompletedfield = trim($this->get_config('remotecoursestatuscompletedfield')); $coursegradefield = trim($this->get_config('remotecoursegradefield')); $courseenroldatefield = trim($this->get_config('remotecourseenroldatefield')); $coursecompletiondatefield = trim($this->get_config('remotecoursecompletiondatefield')); // Lowercased versions - necessary because we normalise the resultset with array_change_key_case(). $coursefield_l = strtolower($coursefield); $userfield_l = strtolower($userfield); $rolefield_l = strtolower($rolefield); $otheruserfieldlower = strtolower($otheruserfield); $coursestatusfield_l = strtolower($coursestatusfield); $coursestatuscurrentfield_l = strtolower($coursestatuscurrentfield); $coursestatuscompletedfield_l = strtolower($coursestatuscompletedfield); $coursegradefield_l = strtolower($coursegradefield); $courseenroldatefield_l = strtolower($courseenroldatefield); $coursecompletiondatefield_l = strtolower($coursecompletiondatefield); $localrolefield = $this->get_config('localrolefield'); $localuserfield = $this->get_config('localuserfield'); $localcoursefield = $this->get_config('localcoursefield'); $unenrolaction = $this->get_config('unenrolaction'); $defaultrole = $this->get_config('defaultrole'); // Create roles mapping. $allroles = get_all_roles(); if (!isset($allroles[$defaultrole])) { $defaultrole = 0; } $roles = array(); foreach ($allroles as $role) { $roles[$role->{$localrolefield}] = $role->id; } if ($onecourse) { $sql = "SELECT c.id, c.visible, c.{$localcoursefield} AS mapping, c.shortname, e.id AS enrolid\n FROM {course} c\n LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'self')\n WHERE c.id = :id"; if (!($course = $DB->get_record_sql($sql, array('id' => $onecourse)))) { // Course does not exist, nothing to sync. return 0; } if (empty($course->mapping)) { // We can not map to this course, sorry. return 0; } if (empty($course->enrolid)) { $course->enrolid = $this->add_instance($course); } $existing = array($course->mapping => $course); // Feel free to unenrol everybody, no safety tricks here. $preventfullunenrol = false; // Course being restored are always hidden, we have to ignore the setting here. $ignorehidden = false; } else { // Get a list of courses to be synced that are in external table. $externalcourses = array(); $sql = $this->db_get_sql($table, array(), array($coursefield), true); if ($rs = $extdb->Execute($sql)) { if (!$rs->EOF) { while ($mapping = $rs->FetchRow()) { $mapping = reset($mapping); $mapping = $this->db_decode($mapping); if (empty($mapping)) { // invalid mapping continue; } $externalcourses[$mapping] = true; } } $rs->Close(); } else { $trace->output('Error reading data from the external enrolment table'); $extdb->Close(); return 2; } $preventfullunenrol = empty($externalcourses); if ($preventfullunenrol and $unenrolaction == ENROL_EXT_REMOVED_UNENROL) { $trace->output('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.', 1); } // First find all existing courses with enrol instance. $existing = array(); $sql = "SELECT c.id, c.visible, c.{$localcoursefield} AS mapping, e.id AS enrolid, c.shortname\n FROM {course} c\n JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'self')"; $rs = $DB->get_recordset_sql($sql); // Watch out for idnumber duplicates. foreach ($rs as $course) { if (empty($course->mapping)) { continue; } $existing[$course->mapping] = $course; unset($externalcourses[$course->mapping]); } $rs->close(); // Add necessary enrol instances that are not present yet. $params = array(); $localnotempty = ""; if ($localcoursefield !== 'id') { $localnotempty = "AND c.{$localcoursefield} <> :lcfe"; $params['lcfe'] = ''; } $sql = "SELECT c.id, c.visible, c.{$localcoursefield} AS mapping, c.shortname\n FROM {course} c\n LEFT JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'self')\n WHERE e.id IS NULL {$localnotempty}"; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $course) { if (empty($course->mapping)) { continue; } if (!isset($externalcourses[$course->mapping])) { // Course not synced or duplicate. continue; } $course->enrolid = $this->add_instance($course); $existing[$course->mapping] = $course; unset($externalcourses[$course->mapping]); } $rs->close(); // Print list of missing courses. if ($externalcourses) { $list = implode(', ', array_keys($externalcourses)); $trace->output("error: following courses do not exist - {$list}", 1); unset($list); } // Free memory. unset($externalcourses); $ignorehidden = $this->get_config('ignorehiddencourses'); } // Sync user enrolments. $sqlfields = array($userfield); if ($rolefield) { $sqlfields[] = $rolefield; } if ($otheruserfield) { $sqlfields[] = $otheruserfield; } if ($coursestatusfield) { $sqlfields[] = $coursestatusfield; } if ($coursegradefield) { $sqlfields[] = $coursegradefield; } if ($courseenroldatefield) { $sqlfields[] = $courseenroldatefield; } if ($coursecompletiondatefield) { $sqlfields[] = $coursecompletiondatefield; } foreach ($existing as $course) { if ($ignorehidden and !$course->visible) { continue; } if (!($instance = $DB->get_record('enrol', array('id' => $course->enrolid)))) { continue; // Weird! } $context = context_course::instance($course->id); // Get current list of enrolled users with their roles. $currentroles = array(); $currentenrols = array(); $currentstatus = array(); $usermapping = array(); $completioninfo = array(); $sql = "SELECT u.{$localuserfield} AS mapping, u.id AS userid, ue.status, ra.roleid\n FROM {user} u\n JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = '' AND ra.itemid = :enrolid)\n LEFT JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)\n WHERE u.deleted = 0"; $params = array('enrolid' => $instance->id); if ($localuserfield === 'username') { $sql .= " AND u.mnethostid = :mnethostid"; $params['mnethostid'] = $CFG->mnet_localhost_id; } $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { $currentroles[$ue->userid][$ue->roleid] = $ue->roleid; $usermapping[$ue->mapping] = $ue->userid; if (isset($ue->status)) { $currentenrols[$ue->userid][$ue->roleid] = $ue->roleid; $currentstatus[$ue->userid] = $ue->status; } } $rs->close(); // Get list of users that need to be enrolled and their roles. $requestedroles = array(); $requestedenrols = array(); $sql = $this->db_get_sql($table, array($coursefield => $course->mapping), $sqlfields); if ($rs = $extdb->Execute($sql)) { if (!$rs->EOF) { $usersearch = array('deleted' => 0); if ($localuserfield === 'username') { $usersearch['mnethostid'] = $CFG->mnet_localhost_id; } while ($fields = $rs->FetchRow()) { $fields = array_change_key_case($fields, CASE_LOWER); if (empty($fields[$userfield_l])) { $trace->output("error: skipping user without mandatory {$localuserfield} in course '{$course->mapping}'", 1); continue; } $mapping = $fields[$userfield_l]; if (!isset($usermapping[$mapping])) { $usersearch[$localuserfield] = $mapping; if (!($user = $DB->get_record('user', $usersearch, 'id', IGNORE_MULTIPLE))) { $trace->output("error: skipping unknown user {$localuserfield} '{$mapping}' in course '{$course->mapping}'", 1); continue; } $usermapping[$mapping] = $user->id; $userid = $user->id; } else { $userid = $usermapping[$mapping]; } if (empty($fields[$rolefield_l]) or !isset($roles[$fields[$rolefield_l]])) { if (!$defaultrole) { $trace->output("error: skipping user '{$userid}' in course '{$course->mapping}' - missing course and default role", 1); continue; } $roleid = $defaultrole; } else { $roleid = $roles[$fields[$rolefield_l]]; } $requestedroles[$userid][$roleid] = $roleid; if (empty($fields[$otheruserfieldlower])) { $requestedenrols[$userid][$roleid] = $roleid; } if (!empty($coursestatusfield_l)) { // Get status, grade, and completion date info only if the status field is defined. if (empty($fields[$coursestatusfield_l])) { // Assume that if the status field is empty, the course is still in progress. $completioninfo[$userid]['status'] = $coursestatuscurrentfield_l; } else { $completioninfo[$userid]['status'] = $fields[$coursestatusfield_l]; } $completioninfo[$userid]['courseid'] = $course->id; if (!empty($fields[$coursegradefield_l])) { $completioninfo[$userid]['grade'] = $fields[$coursegradefield_l]; } if (!empty($fields[$courseenroldatefield_l])) { $completioninfo[$userid]['enroldate'] = $fields[$courseenroldatefield_l]; } if (!empty($fields[$coursecompletiondatefield_l])) { $completioninfo[$userid]['completiondate'] = $fields[$coursecompletiondatefield_l]; } } } } $rs->Close(); } else { $trace->output("error: skipping course '{$course->mapping}' - could not match with external database", 1); continue; } unset($usermapping); // Enrol all users and sync roles. foreach ($requestedenrols as $userid => $userroles) { foreach ($userroles as $roleid) { if (empty($currentenrols[$userid])) { $this->enrol_user($instance, $userid, $roleid, 0, 0, ENROL_USER_ACTIVE); $currentroles[$userid][$roleid] = $roleid; $currentenrols[$userid][$roleid] = $roleid; $currentstatus[$userid] = ENROL_USER_ACTIVE; $trace->output("enrolling: {$userid} ==> {$course->shortname} as " . $allroles[$roleid]->shortname, 1); } } // Reenable enrolment when previously disable enrolment refreshed. if ($currentstatus[$userid] == ENROL_USER_SUSPENDED) { $this->update_user_enrol($instance, $userid, ENROL_USER_ACTIVE); $trace->output("unsuspending: {$userid} ==> {$course->shortname}", 1); } } foreach ($requestedroles as $userid => $userroles) { // Assign extra roles. foreach ($userroles as $roleid) { if (empty($currentroles[$userid][$roleid])) { role_assign($roleid, $userid, $context->id, ''); $currentroles[$userid][$roleid] = $roleid; $trace->output("assigning roles: {$userid} ==> {$course->shortname} as " . $allroles[$roleid]->shortname, 1); } } // Unassign removed roles. foreach ($currentroles[$userid] as $cr) { if (empty($userroles[$cr])) { role_unassign($cr, $userid, $context->id, ''); unset($currentroles[$userid][$cr]); $trace->output("unsassigning roles: {$userid} ==> {$course->shortname}", 1); } } unset($currentroles[$userid]); } foreach ($currentroles as $userid => $userroles) { // These are roles that exist only in Moodle, not the external database // so make sure the unenrol actions will handle them by setting status. $currentstatus += array($userid => ENROL_USER_ACTIVE); } // Handle course completions and final exam grades. foreach ($completioninfo as $userid => $cinfo) { if ($cinfo['status'] == $coursestatuscompletedfield_l) { // Update/create final exam grade then create course completion if course is flagged as complete for the user. require_once "{$CFG->libdir}/gradelib.php"; require_once $CFG->dirroot . '/completion/completion_completion.php'; //Get course shortname (needed to find final exam grade item id) if ($cm = $DB->get_record('course', array('id' => $cinfo['courseid']))) { $courseshortname = trim($cm->shortname); } else { $trace->output('Error: Unable to find course shortname or record for courseid ' . $cinfo['courseid'] . " for userid " . $userid . ". Course completion will be ignored."); continue; } $finalexamname = $courseshortname . ": Final Exam"; if ($gi = $DB->get_record('grade_items', array('itemname' => $finalexamname, 'courseid' => $cinfo['courseid']))) { // Get the grade_item record for the course final exam. $currentgrade = ""; //Now get the current final exam grade for user if present. $grading_info = grade_get_grades($cinfo['courseid'], $gi->itemtype, $gi->itemmodule, $gi->iteminstance, $userid); if (!empty($grading_info->items)) { $item = $grading_info->items[0]; if (isset($item->grades[$userid]->grade)) { $currentgrade = $item->grades[$userid]->grade + 0; $currentgrade = $currentgrade * 10; } } $trace->output('Old grade for courseid ' . $cinfo['courseid'] . " and userid " . $userid . " is " . $currentgrade . "."); } else { $trace->output('Error: Unable to get final exam record for courseid ' . $cinfo['courseid'] . " and userid " . $userid . ". Course completion will be ignored."); continue; } if (isset($cinfo['grade'])) { if ($cinfo['grade'] > $currentgrade || empty($currentgrade)) { // If imported grade is larger update the final exam grade $grade = array(); $grade['userid'] = $userid; $grade['rawgrade'] = $cinfo['grade'] / 10; //learn.saylor.org is currently using rawmaxgrade of 10.0000 grade_update('mod/quiz', $cinfo['courseid'], $gi->itemtype, $gi->itemmodule, $gi->iteminstance, $gi->itemnumber, $grade); $trace->output('Updating grade for courseid ' . $cinfo['courseid'] . " and userid " . $userid . " to " . $grade['rawgrade'] . "."); } else { if (!empty($currentgrade) && $currentgrade >= $cinfo['grade']) { $trace->output("Current grade for final exam for courseid " . $cinfo['courseid'] . " and userid " . $userid . " is larger or equal to the imported grade. Not updating grade."); continue; } else { debugging("Unable to determine if there is a current final exam grade for courseid " . $cinfo['courseid'] . " and userid " . $userid . " or whether it is less than the imported grade."); continue; } } //Mark course as complete. Create completion_completion object to handle completion info for that user and course. $cparams = array('userid' => $userid, 'course' => $cinfo['courseid']); $cc = new completion_completion($cparams); if ($cc->is_complete()) { continue; //Skip adding completion info for this course if the user has already completed this course. Possibility that his grade gets bumped up. } if (isset($cinfo['completiondate'])) { $completeddatestamp = strtotime($cinfo['completiondate']); //Convert the date string to a unix time stamp. } else { $completeddatestamp = time(); //If not set, just use the current date. } if (isset($cinfo['enroldate'])) { $enroldatestamp = strtotime($cinfo['enroldate']); //Convert the date string to a unix time stamp. } else { $enroldatestamp = $completeddatestamp; } $cc->mark_enrolled($enroldatestamp); $cc->mark_inprogress($enroldatestamp); $cc->mark_complete($completeddatestamp); $trace->output('Setting completion data for userid ' . $userid . ' and courseid ' . $cinfo['courseid'] . "."); } else { if (!isset($cinfo['grade'])) { $trace->output("Error: No grade info in external db for completed course " . $cinfo['courseid'] . " for user " . $userid . "."); } } } } // Deal with enrolments removed from external table. if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) { if (!$preventfullunenrol) { // Unenrol. foreach ($currentstatus as $userid => $status) { if (isset($requestedenrols[$userid])) { continue; } $this->unenrol_user($instance, $userid); $trace->output("unenrolling: {$userid} ==> {$course->shortname}", 1); } } } else { if ($unenrolaction == ENROL_EXT_REMOVED_KEEP) { // Keep - only adding enrolments. } else { if ($unenrolaction == ENROL_EXT_REMOVED_SUSPEND or $unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) { // Suspend enrolments. foreach ($currentstatus as $userid => $status) { if (isset($requestedenrols[$userid])) { continue; } if ($status != ENROL_USER_SUSPENDED) { $this->update_user_enrol($instance, $userid, ENROL_USER_SUSPENDED); $trace->output("suspending: {$userid} ==> {$course->shortname}", 1); } if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) { if (isset($requestedroles[$userid])) { // We want this "other user" to keep their roles. continue; } role_unassign_all(array('contextid' => $context->id, 'userid' => $userid, 'component' => '', 'itemid' => $instance->id)); $trace->output("unsassigning all roles: {$userid} ==> {$course->shortname}", 1); } } } } } } // Close db connection. $extdb->Close(); $trace->output('...user enrolment synchronisation finished.'); $trace->finished(); return 0; }