/**
 * Duplicate a course
 *
 * @param int $courseid
 * @param string $fullname Duplicated course fullname
 * @param int $newcourse Destination course
 * @param array $options List of backup options
 * @return stdClass New course info
 */
function local_ltiprovider_duplicate_course($courseid, $newcourse, $visible = 1, $options = array(), $useridcreating = null, $context)
{
    global $CFG, $USER, $DB;
    require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php';
    require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php';
    if (empty($USER)) {
        // Emulate session.
        cron_setup_user();
    }
    // Context validation.
    if (!($course = $DB->get_record('course', array('id' => $courseid)))) {
        throw new moodle_exception('invalidcourseid', 'error');
    }
    $removeoptions = array();
    $removeoptions['keep_roles_and_enrolments'] = true;
    $removeoptions['keep_groups_and_groupings'] = true;
    remove_course_contents($newcourse->id, false, $removeoptions);
    $backupdefaults = array('activities' => 1, 'blocks' => 1, 'filters' => 1, 'users' => 0, 'role_assignments' => 0, 'comments' => 0, 'userscompletion' => 0, 'logs' => 0, 'grade_histories' => 0);
    $backupsettings = array();
    // Check for backup and restore options.
    if (!empty($options)) {
        foreach ($options as $option) {
            // Strict check for a correct value (allways 1 or 0, true or false).
            $value = clean_param($option['value'], PARAM_INT);
            if ($value !== 0 and $value !== 1) {
                throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
            }
            if (!isset($backupdefaults[$option['name']])) {
                throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
            }
            $backupsettings[$option['name']] = $value;
        }
    }
    // Backup the course.
    $admin = get_admin();
    $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $admin->id);
    foreach ($backupsettings as $name => $value) {
        $bc->get_plan()->get_setting($name)->set_value($value);
    }
    $backupid = $bc->get_backupid();
    $backupbasepath = $bc->get_plan()->get_basepath();
    $bc->execute_plan();
    $results = $bc->get_results();
    $file = $results['backup_destination'];
    $bc->destroy();
    // Restore the backup immediately.
    // Check if we need to unzip the file because the backup temp dir does not contains backup files.
    if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
        $file->extract_to_pathname(get_file_packer(), $backupbasepath);
    }
    $rc = new restore_controller($backupid, $newcourse->id, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $admin->id, backup::TARGET_CURRENT_DELETING);
    foreach ($backupsettings as $name => $value) {
        $setting = $rc->get_plan()->get_setting($name);
        if ($setting->get_status() == backup_setting::NOT_LOCKED) {
            $setting->set_value($value);
        }
    }
    if (!$rc->execute_precheck()) {
        $precheckresults = $rc->get_precheck_results();
        if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
            if (empty($CFG->keeptempdirectoriesonbackup)) {
                fulldelete($backupbasepath);
            }
            $errorinfo = '';
            foreach ($precheckresults['errors'] as $error) {
                $errorinfo .= $error;
            }
            if (array_key_exists('warnings', $precheckresults)) {
                foreach ($precheckresults['warnings'] as $warning) {
                    $errorinfo .= $warning;
                }
            }
            throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
        }
    }
    $rc->execute_plan();
    $rc->destroy();
    $course = $DB->get_record('course', array('id' => $newcourse->id), '*', MUST_EXIST);
    $course->visible = $visible;
    $course->fullname = $newcourse->fullname;
    $course->shortname = $newcourse->shortname;
    $course->idnumber = $newcourse->idnumber;
    // Set shortname and fullname back.
    $DB->update_record('course', $course);
    if (empty($CFG->keeptempdirectoriesonbackup)) {
        fulldelete($backupbasepath);
    }
    // Delete the course backup file created by this WebService. Originally located in the course backups area.
    $file->delete();
    // We have to unenroll all the user except the one that create the course.
    if (get_config('local_ltiprovider', 'duplicatecourseswithoutusers') and $useridcreating) {
        require_once $CFG->dirroot . '/group/lib.php';
        // Previous to unenrol users, we assign some type of activities to the user that created the course.
        if ($user = $DB->get_record('user', array('id' => $useridcreating))) {
            if ($databases = $DB->get_records('data', array('course' => $course->id))) {
                foreach ($databases as $data) {
                    $DB->execute("UPDATE {data_records} SET userid = ? WHERE dataid = ?", array($user->id, $data->id));
                }
            }
            if ($glossaries = $DB->get_records('glossary', array('course' => $course->id))) {
                foreach ($glossaries as $glossary) {
                    $DB->execute("UPDATE {glossary_entries} SET userid = ? WHERE glossaryid = ?", array($user->id, $glossary->id));
                }
            }
            // Same for questions.
            $newcoursecontextid = context_course::instance($course->id);
            if ($qcategories = $DB->get_records('question_categories', array('contextid' => $newcoursecontextid->id))) {
                foreach ($qcategories as $qcategory) {
                    $DB->execute("UPDATE {question} SET createdby = ?, modifiedby = ? WHERE category = ?", array($user->id, $user->id, $qcategory->id));
                }
            }
            // Enrol the user.
            if ($tool = $DB->get_record('local_ltiprovider', array('contextid' => $newcoursecontextid->id))) {
                $roles = explode(',', strtolower($context->info['roles']));
                local_ltiprovider_enrol_user($tool, $user, $roles, true);
            }
            // Now, we unenrol all the users except the one who created the course.
            $plugins = enrol_get_plugins(true);
            $instances = enrol_get_instances($course->id, true);
            foreach ($instances as $key => $instance) {
                if (!isset($plugins[$instance->enrol])) {
                    unset($instances[$key]);
                    continue;
                }
            }
            $sql = "SELECT ue.*\n                          FROM {user_enrolments} ue\n                          JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)\n                          JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)";
            $params = array('courseid' => $course->id, 'courselevel' => CONTEXT_COURSE);
            $rs = $DB->get_recordset_sql($sql, $params);
            foreach ($rs as $ue) {
                if ($ue->userid == $user->id) {
                    continue;
                }
                if (!isset($instances[$ue->enrolid])) {
                    continue;
                }
                $instance = $instances[$ue->enrolid];
                $plugin = $plugins[$instance->enrol];
                if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
                    continue;
                }
                $plugin->unenrol_user($instance, $ue->userid);
            }
            $rs->close();
            groups_delete_group_members($course->id);
            groups_delete_groups($course->id, false);
            groups_delete_groupings_groups($course->id, false);
            groups_delete_groupings($course->id, false);
        }
    }
    return $course;
}
         }
     }
 } else {
     if ($moodlecontext->contextlevel == CONTEXT_MODULE) {
         $cmid = $moodlecontext->instanceid;
         $cm = get_coursemodule_from_id(false, $moodlecontext->instanceid, 0, false, MUST_EXIST);
         $courseid = $cm->course;
         $urltogo = $CFG->wwwroot . '/mod/' . $cm->modname . '/view.php?id=' . $cm->id;
     } else {
         print_error("invalidcontext");
     }
 }
 $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
 // Enrol the user in the course
 $isinstructor = $context->isInstructor();
 local_ltiprovider_enrol_user($tool, $user, $isinstructor);
 if ($moodlecontext->contextlevel == CONTEXT_MODULE) {
     $role = $isinstructor ? 'instructor' : 'learner';
     // Enrol the user in the activity
     if ($tool->aroleinst and $isinstructor or $tool->arolelearn and !$isinstructor) {
         $roleid = $isinstructor ? $tool->aroleinst : $tool->arolelearn;
         role_assign($roleid, $user->id, $tool->contextid);
     }
 }
 // Login user
 $sourceid = !empty($context->info['lis_result_sourcedid']) ? $context->info['lis_result_sourcedid'] : '';
 $serviceurl = !empty($context->info['lis_outcome_service_url']) ? $context->info['lis_outcome_service_url'] : '';
 if ($userlog = $DB->get_record('local_ltiprovider_user', array('toolid' => $tool->id, 'userid' => $user->id))) {
     if ($userlog->sourceid != $sourceid) {
         $DB->set_field('local_ltiprovider_user', 'sourceid', $sourceid, array('id' => $userlog->id));
     }
Beispiel #3
0
/**
 * Cron function for sync grades
 * @return void
 */
function local_ltiprovider_cron()
{
    global $DB, $CFG;
    require_once $CFG->dirroot . "/local/ltiprovider/locallib.php";
    require_once $CFG->dirroot . "/local/ltiprovider/ims-blti/OAuth.php";
    require_once $CFG->dirroot . "/local/ltiprovider/ims-blti/OAuthBody.php";
    require_once $CFG->libdir . '/gradelib.php';
    require_once $CFG->dirroot . '/grade/querylib.php';
    // TODO - Add a global setting for this
    $synctime = 60 * 60;
    // Every 1 hour grades are sync
    $timenow = time();
    mtrace('Running cron for ltiprovider');
    mtrace('Deleting LTI tools assigned to deleted courses');
    if ($tools = $DB->get_records('local_ltiprovider')) {
        foreach ($tools as $tool) {
            local_ltiprovider_check_missing_course($tool);
        }
    }
    // Grades service.
    if ($tools = $DB->get_records_select('local_ltiprovider', 'disabled = ? AND sendgrades = ?', array(0, 1))) {
        foreach ($tools as $tool) {
            if ($tool->lastsync + $synctime < $timenow) {
                mtrace(" Starting sync tool for grades id {$tool->id} course id {$tool->courseid}");
                if ($tool->requirecompletion) {
                    mtrace("  Grades require activity or course completion");
                }
                $user_count = 0;
                $send_count = 0;
                $error_count = 0;
                $completion = new completion_info(get_course($tool->courseid));
                if ($users = $DB->get_records('local_ltiprovider_user', array('toolid' => $tool->id))) {
                    foreach ($users as $user) {
                        $user_count = $user_count + 1;
                        // This can happen is the sync process has an unexpected error
                        if (strlen($user->serviceurl) < 1) {
                            mtrace("   Empty serviceurl");
                            continue;
                        }
                        if (strlen($user->sourceid) < 1) {
                            mtrace("   Empty sourceid");
                            continue;
                        }
                        if ($user->lastsync > $tool->lastsync) {
                            mtrace("   Skipping user {$user->id} due to recent sync");
                            continue;
                        }
                        $grade = false;
                        if ($context = $DB->get_record('context', array('id' => $tool->contextid))) {
                            if ($context->contextlevel == CONTEXT_COURSE) {
                                if ($tool->requirecompletion and !$completion->is_course_complete($user->userid)) {
                                    mtrace("   Skipping user {$user->userid} since he didn't complete the course");
                                    continue;
                                }
                                if ($grade = grade_get_course_grade($user->userid, $tool->courseid)) {
                                    $grademax = floatval($grade->item->grademax);
                                    $grade = $grade->grade;
                                }
                            } else {
                                if ($context->contextlevel == CONTEXT_MODULE) {
                                    $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST);
                                    if ($tool->requirecompletion) {
                                        $data = $completion->get_data($cm, false, $user->userid);
                                        if ($data->completionstate != COMPLETION_COMPLETE_PASS and $data->completionstate != COMPLETION_COMPLETE) {
                                            mtrace("   Skipping user {$user->userid} since he didn't complete the activity");
                                            continue;
                                        }
                                    }
                                    $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $user->userid);
                                    if (empty($grades->items[0]->grades)) {
                                        $grade = false;
                                    } else {
                                        $grade = reset($grades->items[0]->grades);
                                        if (!empty($grade->item)) {
                                            $grademax = floatval($grade->item->grademax);
                                        } else {
                                            $grademax = floatval($grades->items[0]->grademax);
                                        }
                                        $grade = $grade->grade;
                                    }
                                }
                            }
                            if ($grade === false || $grade === NULL || strlen($grade) < 1) {
                                mtrace("   Invalid grade {$grade}");
                                continue;
                            }
                            // No need to be dividing by zero
                            if ($grademax == 0.0) {
                                $grademax = 100.0;
                            }
                            // TODO: Make lastgrade should be float or string - but it is integer so we truncate
                            // TODO: Then remove those intval() calls
                            // Don't double send
                            if (intval($grade) == $user->lastgrade) {
                                mtrace("   Skipping, last grade send is equal to current grade");
                                continue;
                            }
                            // We sync with the external system only when the new grade differs with the previous one
                            // TODO - Global setting for check this
                            if ($grade >= 0 and $grade <= $grademax) {
                                $float_grade = $grade / $grademax;
                                $body = local_ltiprovider_create_service_body($user->sourceid, $float_grade);
                                try {
                                    $response = ltiprovider\sendOAuthBodyPOST('POST', $user->serviceurl, $user->consumerkey, $user->consumersecret, 'application/xml', $body);
                                } catch (Exception $e) {
                                    mtrace(" " . $e->getMessage());
                                    $error_count = $error_count + 1;
                                    continue;
                                }
                                // TODO - Check for errors in $retval in a correct way (parsing xml)
                                if (strpos(strtolower($response), 'success') !== false) {
                                    $DB->set_field('local_ltiprovider_user', 'lastsync', $timenow, array('id' => $user->id));
                                    $DB->set_field('local_ltiprovider_user', 'lastgrade', intval($grade), array('id' => $user->id));
                                    mtrace(" User grade sent to remote system. userid: {$user->userid} grade: {$float_grade}");
                                    $send_count = $send_count + 1;
                                } else {
                                    mtrace(" User grade send failed. userid: {$user->userid} grade: {$float_grade}: " . $response);
                                    $error_count = $error_count + 1;
                                }
                            } else {
                                mtrace(" User grade for user {$user->userid} out of range: grade = " . $grade);
                                $error_count = $error_count + 1;
                            }
                        } else {
                            mtrace(" Invalid context: contextid = " . $tool->contextid);
                        }
                    }
                }
                mtrace(" Completed sync tool id {$tool->id} course id {$tool->courseid} users={$user_count} sent={$send_count} errors={$error_count}");
                $DB->set_field('local_ltiprovider', 'lastsync', $timenow, array('id' => $tool->id));
            }
        }
    }
    $timenow = time();
    // Automatic course restaurations.
    if ($croncourses = get_config('local_ltiprovider', 'croncourses')) {
        $croncourses = unserialize($croncourses);
        if (is_array($croncourses)) {
            mtrace('Starting restauration of pending courses');
            foreach ($croncourses as $key => $course) {
                mtrace('Starting restoration of ' . $key);
                // We limit the backups to 1 hour, then retry.
                if ($course->restorestart and $timenow < $course->restorestart + 3600) {
                    mtrace('Skipping restoration in process for: ' . $key);
                    continue;
                }
                $course->restorestart = time();
                $croncourses[$key] = $course;
                $croncoursessafe = serialize($croncourses);
                set_config('croncourses', $croncoursessafe, 'local_ltiprovider');
                if ($destinationcourse = $DB->get_record('course', array('id' => $course->destinationid))) {
                    // Duplicate course + users.
                    local_ltiprovider_duplicate_course($course->id, $destinationcourse, 1, $options = array(array('name' => 'users', 'value' => 1)), $course->userrestoringid, $course->context);
                    mtrace('Restoration for ' . $key . ' finished');
                } else {
                    mtrace('Restoration for ' . $key . ' finished (destination course not exists)');
                }
                unset($croncourses[$key]);
                $croncoursessafe = serialize($croncourses);
                set_config('croncourses', $croncoursessafe, 'local_ltiprovider');
            }
        }
    }
    // Membership service.
    $timenow = time();
    $userphotos = array();
    if ($tools = $DB->get_records('local_ltiprovider', array('disabled' => 0, 'syncmembers' => 1))) {
        mtrace('Starting sync of member using the memberships service');
        $consumers = array();
        foreach ($tools as $tool) {
            $lastsync = get_config('local_ltiprovider', 'membershipslastsync-' . $tool->id);
            if (!$lastsync) {
                $lastsync = 0;
            }
            if ($lastsync + $tool->syncperiod < $timenow) {
                mtrace('Starting sync of tool: ' . $tool->id);
                // We check for all the users, notice that users can access the same tool from different consumers.
                if ($users = $DB->get_records('local_ltiprovider_user', array('toolid' => $tool->id), 'lastaccess DESC')) {
                    $response = "";
                    foreach ($users as $user) {
                        if (!$user->membershipsurl or !$user->membershipsid) {
                            continue;
                        }
                        $consumer = md5($user->membershipsurl . ':' . $user->membershipsid . ':' . $user->consumerkey . ':' . $user->consumersecret);
                        if (in_array($consumer, $consumers)) {
                            // We had syncrhonized with this consumer yet.
                            continue;
                        }
                        $consumers[] = $consumer;
                        $params = array('lti_message_type' => 'basic-lis-readmembershipsforcontext', 'id' => $user->membershipsid, 'lti_version' => 'LTI-1p0');
                        mtrace('Calling memberships url: ' . $user->membershipsurl . ' with body: ' . json_encode($params));
                        try {
                            $response = ltiprovider\sendOAuthParamsPOST('POST', $user->membershipsurl, $user->consumerkey, $user->consumersecret, 'application/x-www-form-urlencoded', $params);
                        } catch (Exception $e) {
                            mtrace("Exception: " . $e->getMessage());
                            $response = false;
                        }
                        if ($response) {
                            $data = new SimpleXMLElement($response);
                            if (!empty($data->statusinfo)) {
                                if (strpos(strtolower($data->statusinfo->codemajor), 'success') !== false) {
                                    $members = $data->memberships->member;
                                    mtrace(count($members) . ' members received');
                                    $currentusers = array();
                                    foreach ($members as $member) {
                                        $username = local_ltiprovider_create_username($user->consumerkey, $member->user_id);
                                        $userobj = $DB->get_record('user', array('username' => $username));
                                        if (!$userobj) {
                                            // Old format.
                                            $oldusername = '******' . md5($user->consumerkey . ':' . $member->user_id);
                                            $userobj = $DB->get_record('user', array('username' => $oldusername));
                                            if ($userobj) {
                                                $DB->set_field('user', 'username', $username, array('id' => $userobj->id));
                                            }
                                            $userobj = $DB->get_record('user', array('username' => $username));
                                        }
                                        if ($userobj) {
                                            $currentusers[] = $userobj->id;
                                            $userobj->firstname = clean_param($member->person_name_given, PARAM_TEXT);
                                            $userobj->lastname = clean_param($member->person_name_family, PARAM_TEXT);
                                            $userobj->email = clean_param($member->person_contact_email_primary, PARAM_EMAIL);
                                            $userobj->timemodified = time();
                                            $DB->update_record('user', $userobj);
                                            $userphotos[$userobj->id] = $member->user_image;
                                            // Trigger event.
                                            $event = \core\event\user_updated::create(array('objectid' => $userobj->id, 'relateduserid' => $userobj->id, 'context' => context_user::instance($userobj->id)));
                                            $event->trigger();
                                        } else {
                                            // New members.
                                            if ($tool->syncmode == 1 or $tool->syncmode == 2) {
                                                // We have to enrol new members so we have to create it.
                                                $userobj = new stdClass();
                                                // clean_param , email username text
                                                $auth = get_config('local_ltiprovider', 'defaultauthmethod');
                                                if ($auth) {
                                                    $userobj->auth = $auth;
                                                } else {
                                                    $userobj->auth = 'nologin';
                                                }
                                                $username = local_ltiprovider_create_username($user->consumerkey, $member->user_id);
                                                $userobj->username = $username;
                                                $userobj->password = md5(uniqid(rand(), 1));
                                                $userobj->firstname = clean_param($member->person_name_given, PARAM_TEXT);
                                                $userobj->lastname = clean_param($member->person_name_family, PARAM_TEXT);
                                                $userobj->email = clean_param($member->person_contact_email_primary, PARAM_EMAIL);
                                                $userobj->city = $tool->city;
                                                $userobj->country = $tool->country;
                                                $userobj->institution = $tool->institution;
                                                $userobj->timezone = $tool->timezone;
                                                $userobj->maildisplay = $tool->maildisplay;
                                                $userobj->mnethostid = $CFG->mnet_localhost_id;
                                                $userobj->confirmed = 1;
                                                $userobj->lang = $tool->lang;
                                                $userobj->timecreated = time();
                                                if (!$userobj->lang) {
                                                    // TODO: This should be changed for detect the course lang
                                                    $userobj->lang = current_language();
                                                }
                                                $userobj->id = $DB->insert_record('user', $userobj);
                                                // Reload full user
                                                $userobj = $DB->get_record('user', array('id' => $userobj->id));
                                                $userphotos[$userobj->id] = $member->user_image;
                                                // Trigger event.
                                                $event = \core\event\user_created::create(array('objectid' => $userobj->id, 'relateduserid' => $userobj->id, 'context' => context_user::instance($userobj->id)));
                                                $event->trigger();
                                                $currentusers[] = $userobj->id;
                                            }
                                        }
                                        // 1 -> Enrol and unenrol, 2 -> enrol
                                        if ($tool->syncmode == 1 or $tool->syncmode == 2) {
                                            // Enroll the user in the course. We don't know if it was previously unenrolled.
                                            $roles = explode(',', strtolower($member->roles));
                                            local_ltiprovider_enrol_user($tool, $userobj, $roles, true);
                                        }
                                    }
                                    // Now we check if we have to unenrol users for keep both systems sync.
                                    if ($tool->syncmode == 1 or $tool->syncmode == 3) {
                                        // Unenrol users also.
                                        $context = context_course::instance($tool->courseid);
                                        $eusers = get_enrolled_users($context);
                                        foreach ($eusers as $euser) {
                                            if (!in_array($euser->id, $currentusers)) {
                                                local_ltiprovider_unenrol_user($tool, $euser);
                                            }
                                        }
                                    }
                                } else {
                                    mtrace('Error recived from the remote system: ' . $data->statusinfo->codemajor . ' ' . $data->statusinfo->severity . ' ' . $data->statusinfo->codeminor);
                                }
                            } else {
                                mtrace('Error parsing the XML received' . substr($response, 0, 125) . '... (Displaying only 125 chars)');
                            }
                        } else {
                            mtrace('No response received from ' . $user->membershipsurl);
                        }
                    }
                }
                set_config('membershipslastsync-' . $tool->id, $timenow, 'local_ltiprovider');
            } else {
                $last = format_time(time() - $lastsync);
                mtrace("Tool {$tool->id} synchronized {$last} ago");
            }
            mtrace('Finished sync of member using the memberships service');
        }
    }
    // Sync of user photos.
    mtrace("Sync user profile images");
    $counter = 0;
    if ($userphotos) {
        foreach ($userphotos as $userid => $url) {
            if ($url) {
                $result = local_ltiprovider_update_user_profile_image($userid, $url);
                if ($result === true) {
                    $counter++;
                    mtrace("Profile image succesfully downloaded and created from {$url}");
                } else {
                    mtrace($result);
                }
            }
        }
    }
    mtrace("{$counter} profile images updated");
}
/**
 * Cron function for sync grades
 * @return void
 */
function local_ltiprovider_cron()
{
    global $DB, $CFG;
    require_once $CFG->dirroot . "/local/ltiprovider/locallib.php";
    require_once $CFG->dirroot . "/local/ltiprovider/ims-blti/OAuth.php";
    require_once $CFG->dirroot . "/local/ltiprovider/ims-blti/OAuthBody.php";
    require_once $CFG->libdir . '/gradelib.php';
    require_once $CFG->dirroot . '/grade/querylib.php';
    // TODO - Add a global setting for this
    $synctime = 60 * 60;
    // Every 1 hour grades are sync
    $timenow = time();
    mtrace('Running cron for ltiprovider');
    mtrace('Deleting LTI tools assigned to deleted courses');
    if ($tools = $DB->get_records('local_ltiprovider')) {
        foreach ($tools as $tool) {
            local_ltiprovider_check_missing_course($tool);
        }
    }
    // Grades service.
    if ($tools = $DB->get_records_select('local_ltiprovider', 'disabled = ? AND sendgrades = ?', array(0, 1))) {
        foreach ($tools as $tool) {
            if ($tool->lastsync + $synctime < $timenow) {
                mtrace(" Starting sync tool id {$tool->id} course id {$tool->courseid}");
                $user_count = 0;
                $send_count = 0;
                $error_count = 0;
                if ($users = $DB->get_records('local_ltiprovider_user', array('toolid' => $tool->id))) {
                    foreach ($users as $user) {
                        $user_count = $user_count + 1;
                        // This can happen is the sync process has an unexpected error
                        if (strlen($user->serviceurl) < 1) {
                            continue;
                        }
                        if (strlen($user->sourceid) < 1) {
                            continue;
                        }
                        if ($user->lastsync > $tool->lastsync) {
                            mtrace("Skipping user {$user->id}");
                            continue;
                        } else {
                            list($result, $message) = local_ltiprovider_send_grade($tool, $user, $timenow);
                            if ($result === true) {
                                $send_count++;
                            } else {
                                $error_count++;
                            }
                        }
                    }
                }
            }
        }
    }
    $timenow = time();
    // Automatic course restaurations.
    if ($croncourses = get_config('local_ltiprovider', 'croncourses')) {
        $croncourses = unserialize($croncourses);
        if (is_array($croncourses)) {
            mtrace('Starting restauration of pending courses');
            foreach ($croncourses as $key => $course) {
                mtrace('Starting restoration of ' . $key);
                // We limit the backups to 1 hour, then retry.
                if ($course->restorestart and $timenow < $course->restorestart + 3600) {
                    mtrace('Skipping restoration in process for: ' . $key);
                    continue;
                }
                $course->restorestart = time();
                $croncourses[$key] = $course;
                $croncoursessafe = serialize($croncourses);
                set_config('croncourses', $croncoursessafe, 'local_ltiprovider');
                if ($destinationcourse = $DB->get_record('course', array('id' => $course->destinationid))) {
                    // Duplicate course + users.
                    local_ltiprovider_duplicate_course($course->id, $destinationcourse, 1, $options = array(array('name' => 'users', 'value' => 1)), $course->userrestoringid, $course->context);
                    mtrace('Restoration for ' . $key . ' finished');
                } else {
                    mtrace('Restoration for ' . $key . ' finished (destination course not exists)');
                }
                unset($croncourses[$key]);
                $croncoursessafe = serialize($croncourses);
                set_config('croncourses', $croncoursessafe, 'local_ltiprovider');
            }
        }
    }
    // Membership service.
    $timenow = time();
    $userphotos = array();
    if ($tools = $DB->get_records('local_ltiprovider', array('disabled' => 0, 'syncmembers' => 1))) {
        mtrace('Starting sync of member using the memberships service');
        $consumers = array();
        foreach ($tools as $tool) {
            $lastsync = get_config('local_ltiprovider', 'membershipslastsync-' . $tool->id);
            if (!$lastsync) {
                $lastsync = 0;
            }
            if ($lastsync + $tool->syncperiod < $timenow) {
                mtrace('Starting sync of tool: ' . $tool->id);
                // We check for all the users, notice that users can access the same tool from different consumers.
                if ($users = $DB->get_records('local_ltiprovider_user', array('toolid' => $tool->id), 'lastaccess DESC')) {
                    $response = "";
                    foreach ($users as $user) {
                        if (!$user->membershipsurl or !$user->membershipsid) {
                            continue;
                        }
                        $consumer = md5($user->membershipsurl . ':' . $user->membershipsid . ':' . $user->consumerkey . ':' . $user->consumersecret);
                        if (in_array($consumer, $consumers)) {
                            // We had syncrhonized with this consumer yet.
                            continue;
                        }
                        $consumers[] = $consumer;
                        $params = array('lti_message_type' => 'basic-lis-readmembershipsforcontext', 'id' => $user->membershipsid, 'lti_version' => 'LTI-1p0');
                        mtrace('Calling memberships url: ' . $user->membershipsurl . ' with body: ' . json_encode($params));
                        try {
                            $response = ltiprovider\sendOAuthParamsPOST('POST', $user->membershipsurl, $user->consumerkey, $user->consumersecret, 'application/x-www-form-urlencoded', $params);
                        } catch (Exception $e) {
                            mtrace("Exception: " . $e->getMessage());
                            $response = false;
                        }
                        if ($response) {
                            $data = new SimpleXMLElement($response);
                            if (!empty($data->statusinfo)) {
                                if (strpos(strtolower($data->statusinfo->codemajor), 'success') !== false) {
                                    $members = $data->memberships->member;
                                    mtrace(count($members) . ' members received');
                                    $currentusers = array();
                                    foreach ($members as $member) {
                                        $username = local_ltiprovider_create_username($user->consumerkey, $member->user_id);
                                        $userobj = $DB->get_record('user', array('username' => $username));
                                        if (!$userobj) {
                                            // Old format.
                                            $oldusername = '******' . md5($user->consumerkey . ':' . $member->user_id);
                                            $userobj = $DB->get_record('user', array('username' => $oldusername));
                                            if ($userobj) {
                                                $DB->set_field('user', 'username', $username, array('id' => $userobj->id));
                                            }
                                            $userobj = $DB->get_record('user', array('username' => $username));
                                        }
                                        if ($userobj) {
                                            $currentusers[] = $userobj->id;
                                            $userobj->firstname = clean_param($member->person_name_given, PARAM_TEXT);
                                            $userobj->lastname = clean_param($member->person_name_family, PARAM_TEXT);
                                            $userobj->email = clean_param($member->person_contact_email_primary, PARAM_EMAIL);
                                            $userobj->timemodified = time();
                                            $DB->update_record('user', $userobj);
                                            $userphotos[$userobj->id] = $member->user_image;
                                            events_trigger('user_updated', $userobj);
                                        } else {
                                            // New members.
                                            if ($tool->syncmode == 1 or $tool->syncmode == 2) {
                                                // We have to enrol new members so we have to create it.
                                                $userobj = new stdClass();
                                                // clean_param , email username text
                                                $auth = get_config('local_ltiprovider', 'defaultauthmethod');
                                                if ($auth) {
                                                    $userobj->auth = $auth;
                                                } else {
                                                    $userobj->auth = 'nologin';
                                                }
                                                $username = local_ltiprovider_create_username($user->consumerkey, $member->user_id);
                                                $userobj->username = $username;
                                                $userobj->password = md5(uniqid(rand(), 1));
                                                $userobj->firstname = clean_param($member->person_name_given, PARAM_TEXT);
                                                $userobj->lastname = clean_param($member->person_name_family, PARAM_TEXT);
                                                $userobj->email = clean_param($member->person_contact_email_primary, PARAM_EMAIL);
                                                $userobj->city = $tool->city;
                                                $userobj->country = $tool->country;
                                                $userobj->institution = $tool->institution;
                                                $userobj->timezone = $tool->timezone;
                                                $userobj->maildisplay = $tool->maildisplay;
                                                $userobj->mnethostid = $CFG->mnet_localhost_id;
                                                $userobj->confirmed = 1;
                                                $userobj->lang = $tool->lang;
                                                $userobj->timecreated = time();
                                                if (!$userobj->lang) {
                                                    // TODO: This should be changed for detect the course lang
                                                    $userobj->lang = current_language();
                                                }
                                                $userobj->id = $DB->insert_record('user', $userobj);
                                                // Reload full user
                                                $userobj = $DB->get_record('user', array('id' => $userobj->id));
                                                $userphotos[$userobj->id] = $member->user_image;
                                                events_trigger('user_created', $userobj);
                                                $currentusers[] = $userobj->id;
                                            }
                                        }
                                        // 1 -> Enrol and unenrol, 2 -> enrol
                                        if ($tool->syncmode == 1 or $tool->syncmode == 2) {
                                            // Enroll the user in the course. We don't know if it was previously unenrolled.
                                            $roles = explode(',', strtolower($member->roles));
                                            local_ltiprovider_enrol_user($tool, $userobj, $roles, true);
                                        }
                                    }
                                    // Now we check if we have to unenrol users for keep both systems sync.
                                    if ($tool->syncmode == 1 or $tool->syncmode == 3) {
                                        // Unenrol users also.
                                        $context = context_course::instance($tool->courseid);
                                        $eusers = get_enrolled_users($context);
                                        foreach ($eusers as $euser) {
                                            if (!in_array($euser->id, $currentusers)) {
                                                local_ltiprovider_unenrol_user($tool, $euser);
                                            }
                                        }
                                    }
                                } else {
                                    mtrace('Error recived from the remote system: ' . $data->statusinfo->codemajor . ' ' . $data->statusinfo->severity . ' ' . $data->statusinfo->codeminor);
                                }
                            } else {
                                mtrace('Error parsing the XML received' . substr($response, 0, 125) . '... (Displaying only 125 chars)');
                            }
                        } else {
                            mtrace('No response received from ' . $user->membershipsurl);
                        }
                    }
                }
                set_config('membershipslastsync-' . $tool->id, $timenow, 'local_ltiprovider');
            } else {
                $last = format_time(time() - $lastsync);
                mtrace("Tool {$tool->id} synchronized {$last} ago");
            }
            mtrace('Finished sync of member using the memberships service');
        }
    }
    // Sync of user photos.
    mtrace("Sync user profile images");
    $counter = 0;
    if ($userphotos) {
        foreach ($userphotos as $userid => $url) {
            if ($url) {
                $result = local_ltiprovider_update_user_profile_image($userid, $url);
                if ($result === true) {
                    $counter++;
                    mtrace("Profile image succesfully downloaded and created from {$url}");
                } else {
                    mtrace($result);
                }
            }
        }
    }
    mtrace("{$counter} profile images updated");
}