Example #1
0
 /**
  * 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='******'&amp;course=' . $course->id . '&amp;rolec=' . $allow_marking_criteria . '&amp;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>';
Example #5
0
/**
 * 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();
 }
Example #8
0
/**
 * 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);
}
Example #9
0
/**
 * 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);
 }
Example #11
0
 /**
  * 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;
 }
Example #14
0
 /**
  * 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;
 }