function execute($data, $row, $user, $courseid, $starttime = 0, $endtime = 0) { global $DB, $USER, $CFG; $courseid = $row->id; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; if ($grade = grade_get_course_grade($user->id, $courseid)) { return $grade->grade; } return ''; }
/** * Returns the aggregated or calculated course grade for the given user(s). * @public * @param int $userid * @param int $courseid optional id of course or array of ids, empty means all uses courses (returns array if not present) * @return mixed grade info or grades array including item info, false if error */ function grade_get_course_grade($userid, $courseid_or_ids = null) { if (!is_array($courseid_or_ids)) { if (empty($courseid_or_ids)) { if (!($courses = enrol_get_users_courses($userid))) { return false; } $courseids = array_keys($courses); return grade_get_course_grade($userid, $courseids); } if (!is_numeric($courseid_or_ids)) { return false; } if (!($grades = grade_get_course_grade($userid, array($courseid_or_ids)))) { return false; } else { // only one grade - not array $grade = reset($grades); return $grade; } } foreach ($courseid_or_ids as $courseid) { $grade_item = grade_item::fetch_course_item($courseid); $course_items[$grade_item->courseid] = $grade_item; } $grades = array(); foreach ($course_items as $grade_item) { if ($grade_item->needsupdate) { grade_regrade_final_grades($courseid); } $item = new stdClass(); $item->scaleid = $grade_item->scaleid; $item->name = $grade_item->get_name(); $item->grademin = $grade_item->grademin; $item->grademax = $grade_item->grademax; $item->gradepass = $grade_item->gradepass; $item->locked = $grade_item->is_locked(); $item->hidden = $grade_item->is_hidden(); switch ($grade_item->gradetype) { case GRADE_TYPE_NONE: continue; case GRADE_TYPE_VALUE: $item->scaleid = 0; break; case GRADE_TYPE_TEXT: $item->scaleid = 0; $item->grademin = 0; $item->grademax = 0; $item->gradepass = 0; break; } $grade_grade = new grade_grade(array('userid' => $userid, 'itemid' => $grade_item->id)); $grade_grade->grade_item =& $grade_item; $grade = new stdClass(); $grade->grade = $grade_grade->finalgrade; $grade->locked = $grade_grade->is_locked(); $grade->hidden = $grade_grade->is_hidden(); $grade->overridden = $grade_grade->overridden; $grade->feedback = $grade_grade->feedback; $grade->feedbackformat = $grade_grade->feedbackformat; $grade->usermodified = $grade_grade->usermodified; $grade->dategraded = $grade_grade->get_dategraded(); $grade->item = $item; // create text representation of grade if ($grade_item->needsupdate) { $grade->grade = false; $grade->str_grade = get_string('error'); $grade->str_long_grade = $grade->str_grade; } else { if (is_null($grade->grade)) { $grade->str_grade = '-'; $grade->str_long_grade = $grade->str_grade; } else { $grade->str_grade = grade_format_gradevalue($grade->grade, $grade_item); if ($grade_item->gradetype == GRADE_TYPE_SCALE or $grade_item->get_displaytype() != GRADE_DISPLAY_TYPE_REAL) { $grade->str_long_grade = $grade->str_grade; } else { $a = new stdClass(); $a->grade = $grade->str_grade; $a->max = grade_format_gradevalue($grade_item->grademax, $grade_item); $grade->str_long_grade = get_string('gradelong', 'grades', $a); } } } // create html representation of feedback if (is_null($grade->feedback)) { $grade->str_feedback = ''; } else { $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat); } $grades[$grade_item->courseid] = $grade; } return $grades; }
/** * Performs the synchronisation of grades. * * @return bool|void */ public function execute() { global $DB, $CFG; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php'; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php'; require_once $CFG->dirroot . '/lib/completionlib.php'; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; // Check if the authentication plugin is disabled. if (!is_enabled_auth('lti')) { mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti'))); return true; } // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong. if (!enrol_is_enabled('lti')) { mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti')); return true; } // Get all the enabled tools. if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'gradesync' => 1))) { foreach ($tools as $tool) { mtrace("Starting - Grade sync for shared tool '{$tool->id}' for the course '{$tool->courseid}'."); // Variables to keep track of information to display later. $usercount = 0; $sendcount = 0; // We check for all the users - users can access the same tool from different consumers. if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) { $completion = new \completion_info(get_course($tool->courseid)); foreach ($ltiusers as $ltiuser) { $mtracecontent = "for the user '{$ltiuser->userid}' in the tool '{$tool->id}' for the course " . "'{$tool->courseid}'"; $usercount = $usercount + 1; // Check if we do not have a serviceurl - this can happen if the sync process has an unexpected error. if (empty($ltiuser->serviceurl)) { mtrace("Skipping - Empty serviceurl {$mtracecontent}."); continue; } // Check if we do not have a sourceid - this can happen if the sync process has an unexpected error. if (empty($ltiuser->sourceid)) { mtrace("Skipping - Empty sourceid {$mtracecontent}."); continue; } // Need a valid context to continue. if (!($context = \context::instance_by_id($tool->contextid))) { mtrace("Failed - Invalid contextid '{$tool->contextid}' for the tool '{$tool->id}'."); continue; } // Ok, let's get the grade. $grade = false; if ($context->contextlevel == CONTEXT_COURSE) { // Check if the user did not completed the course when it was required. if ($tool->gradesynccompletion && !$completion->is_course_complete($ltiuser->userid)) { mtrace("Skipping - Course not completed {$mtracecontent}."); continue; } // Get the grade. if ($grade = grade_get_course_grade($ltiuser->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->gradesynccompletion) { $data = $completion->get_data($cm, false, $ltiuser->userid); if ($data->completionstate != COMPLETION_COMPLETE_PASS && $data->completionstate != COMPLETION_COMPLETE) { mtrace("Skipping - Activity not completed {$mtracecontent}."); continue; } } $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $ltiuser->userid); if (!empty($grades->items[0]->grades)) { $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("Skipping - Invalid grade {$mtracecontent}."); continue; } // No need to be dividing by zero. if (empty($grademax)) { mtrace("Skipping - Invalid grade {$mtracecontent}."); continue; } // This can happen if the sync process has an unexpected error. if ($grade == $ltiuser->lastgrade) { mtrace("Not sent - The grade {$mtracecontent} was not sent as the grades are the same."); continue; } // Sync with the external system. $floatgrade = $grade / $grademax; $body = \enrol_lti\helper::create_service_body($ltiuser->sourceid, $floatgrade); try { $response = sendOAuthBodyPOST('POST', $ltiuser->serviceurl, $ltiuser->consumerkey, $ltiuser->consumersecret, 'application/xml', $body); } catch (\Exception $e) { mtrace("Failed - The grade '{$floatgrade}' {$mtracecontent} failed to send."); mtrace($e->getMessage()); continue; } if (strpos(strtolower($response), 'success') !== false) { $DB->set_field('enrol_lti_users', 'lastgrade', intval($grade), array('id' => $ltiuser->id)); mtrace("Success - The grade '{$floatgrade}' {$mtracecontent} was sent."); $sendcount = $sendcount + 1; } else { mtrace("Failed - The grade '{$floatgrade}' {$mtracecontent} failed to send."); } } } mtrace("Completed - Synced grades for tool '{$tool->id}' in the course '{$tool->courseid}'. " . "Processed {$usercount} users; sent {$sendcount} grades."); mtrace(""); } } }
/** * This will provide summary info about the user's grade in the subcourse below the link on * the course/view.php page * * @param cm_info $cm * @return void */ function mod_subcourse_cm_info_view(cm_info $cm) { global $USER, $CFG; $html = ''; require_once $CFG->dirroot . '/grade/querylib.php'; $currentgrade = grade_get_course_grade($USER->id, $cm->course); $html .= html_writer::empty_tag('br'); $html .= html_writer::start_tag('span'); $html .= get_string('currentgrade', 'subcourse') . ' ' . $currentgrade->str_grade; $html .= html_writer::end_tag('span'); $cm->set_after_link($html); }
/** * Review this criteria and decide if it has been completed * * @return bool Whether criteria is complete */ public function review($userid) { global $DB; foreach ($this->params as $param) { $course = $DB->get_record('course', array('id' => $param['course'])); $info = new completion_info($course); $check_grade = true; $check_date = true; if (isset($param['grade'])) { $grade = grade_get_course_grade($userid, $course->id); $check_grade = $grade->grade >= $param['grade']; } if (isset($param['bydate'])) { $cparams = array('userid' => $userid, 'course' => $course->id); $completion = new completion_completion($cparams); $date = $completion->timecompleted; $check_date = $date <= $param['bydate']; } $overall = false; if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) { if ($info->is_course_complete($userid) && $check_grade && $check_date) { $overall = true; continue; } else { return false; } } else { if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) { if ($info->is_course_complete($userid) && $check_grade && $check_date) { return true; } else { $overall = false; continue; } } } } return $overall; }
if (strlen($user->serviceurl) < 1) { mtrace(" Empty serviceurl"); continue; } if (strlen($user->sourceid) < 1) { mtrace(" Empty sourceid"); 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)) {
/** * Retrieves an array with grade information including grade obtained by a user for a course in Moodle, maximum grade for that course and min grade. Returns null if the module is not gradable * or if the user has not been graded. * @author elever * @param int $id_course : native id of the course * @param int $userid : moodle identifier of the user * @return array $grade_info | null */ function block_intuitel_get_grade_info_course($id_course, $userid) { global $CFG; require_once $CFG->dirroot . '/lib/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; $grade_info = \grade_get_course_grade($userid, $id_course); $grade = array(); $grade['grade'] = $grade_info->grade; $grade['grademax'] = $grade_info->item->grademax; $grade['grademin'] = $grade_info->item->grademin; //TODO check result if user has not been graded in the course return $grade; }
/** * Review this criteria and decide if it has been completed * * @param int $userid User whose criteria completion needs to be reviewed. * @param bool $filtered An additional parameter indicating that user list * has been reduced and some expensive checks can be skipped. * * @return bool Whether criteria is complete */ public function review($userid, $filtered = false) { $course = new stdClass(); $course->id = $this->courseid; if ($this->coursestartdate > time()) { return false; } $info = new completion_info($course); foreach ($this->params as $param) { $check_grade = true; $check_date = true; if (isset($param['grade'])) { $grade = grade_get_course_grade($userid, $course->id); $check_grade = $grade->grade >= $param['grade']; } if (!$filtered && isset($param['bydate'])) { $cparams = array('userid' => $userid, 'course' => $course->id); $completion = new completion_completion($cparams); $date = $completion->timecompleted; $check_date = $date <= $param['bydate']; } if ($info->is_course_complete($userid) && $check_grade && $check_date) { return true; } } return false; }
/** * Get user's course grade in this course * * @param completion_completion $completion an instance of completion_completion class * @return float */ private function get_grade($completion) { $grade = grade_get_course_grade($completion->userid, $this->course); return $grade->grade; }
/** * Return the users custom profile field * * @param int $userid * @param int $courseid * @param str $format the format of the date * @return object */ function jemena_get_course_completion_stats($userid, $courseid, $format = '') { global $CFG, $DB; require_once $CFG->dirroot . '/lib/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; $completionstats = new stdClass(); // Get the score for the course $objgrade = grade_get_course_grade($userid, $courseid); $completionstats->score = round($objgrade->grade, 2); $sql = "SELECT * " . "FROM {course_completions} cc " . "WHERE userid = '{$userid}' " . "AND course = '{$courseid}' " . "AND timecompleted IS NOT NULL"; if ($stats = $DB->get_record_sql($sql)) { $completionstats->completed = get_string('completed', 'report_jemena'); $completionstats->datecompleted = userdate($stats->timecompleted, $format); } else { $completionstats->completed = get_string('notcompleted', 'report_jemena'); $completionstats->datecompleted = ""; } return $completionstats; }
/** * Review this criteria and decide if it has been completed * * @param int $userid User whose criteria completion needs to be reviewed. * @return bool Whether criteria is complete */ public function review($userid) { global $DB; foreach ($this->params as $param) { $course = $DB->get_record('course', array('id' => $param['course'])); if ($course->startdate > time()) { return false; } $info = new completion_info($course); $check_grade = true; $check_date = true; if (isset($param['grade'])) { $grade = grade_get_course_grade($userid, $course->id); $check_grade = $grade->grade >= $param['grade']; } if (isset($param['bydate'])) { $cparams = array('userid' => $userid, 'course' => $course->id); $completion = new completion_completion($cparams); $date = $completion->timecompleted; $check_date = $date <= $param['bydate']; } if ($info->is_course_complete($userid) && $check_grade && $check_date) { return true; } } return false; }
/** * 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"); }
/** * Load the course completion info * * @param object $user User object from database * @param object $cinfo Course object from database */ public static final function load_course_completioninfo($user, $cinfo) { global $DB, $CFG; static $cstatus, $completioninfo = array(); require_once $CFG->dirroot . '/lib/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; require_once $CFG->dirroot . '/lib/completionlib.php'; // Completion status 'cache' values (speed up, lass!). if ($cstatus === null) { $cstatus = array(); $cstatus['started'] = get_string('report:status_started', 'block_coupon'); $cstatus['notstarted'] = get_string('report:status_not_started', 'block_coupon'); $cstatus['complete'] = get_string('report:status_completed', 'block_coupon'); } // Completion info 'cache' (speed up, lass!). if (!isset($completioninfo[$cinfo->id])) { $completioninfo[$cinfo->id] = new \completion_info($cinfo); } $ci = new \stdClass(); $ci->complete = false; $ci->str_status = $cstatus['notstarted']; $ci->date_started = '-'; $ci->date_complete = '-'; $ci->str_grade = '-'; $ci->gradeinfo = null; // Ok, fill out real data according to completion status/info. $com = $completioninfo[$cinfo->id]; if ($com->is_tracked_user($user->id)) { // Do we have an enrolment for the course for this user. $sql = 'SELECT ue.* FROM {user_enrolments} ue JOIN {enrol} e ON ue.enrolid=e.id WHERE ue.userid = ? AND e.courseid = ? ORDER BY timestart ASC, timecreated ASC'; $records = $DB->get_records_sql($sql, array($user->id, $cinfo->id)); if (count($records) === 1) { $record = array_shift($records); $ci->time_started = $record->timestart > 0 ? $record->timestart : $record->timecreated; $ci->date_started = date('d-m-Y H:i:s', $ci->time_started); } else { $started = 0; $created = 0; foreach ($records as $record) { if ($record->timestart > 0) { $started = $started == 0 ? $record->timestart : min($record->timestart, $started); } $created = $created == 0 ? $record->timecreated : min($record->timecreated, $created); } $ci->time_started = $started > 0 ? $started : $created; $ci->date_started = date('d-m-Y H:i:s', $started > 0 ? $started : $created); } if ($com->is_course_complete($user->id)) { // Fetch details for course completion. $ci->complete = true; $comcom = new \completion_completion(array('userid' => $user->id, 'course' => $cinfo->id)); $ci->date_complete = date('d-m-Y H:i:s', $comcom->timecompleted); $ci->gradeinfo = grade_get_course_grade($user->id, $cinfo->id); if ($ci->gradeinfo !== false) { $ci->str_grade = $ci->gradeinfo->str_grade; } $ci->str_status = $cstatus['complete']; } else { $ci->str_status = $cstatus['started']; } } return $ci; }
/** * Cron function for sync grades * @return void */ function local_ltiprovider_cron() { global $DB, $CFG; 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'); 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; } $grade = false; if ($context = $DB->get_record('context', array('id' => $tool->contextid))) { if ($context->contextlevel == CONTEXT_COURSE) { 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); $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); $grademax = floatval($grade->item->grademax); $grade = $grade->grade; } } if ($grade === false || $grade === NULL || strlen($grade) < 1) 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) 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 = 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: " . $response); $error_count = $error_count + 1; } } else { mtrace(" User grade 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)); } } } }
/** * Retorna a nota do aluno em um curso informado. * * Significado dos valores negativos: * -1 Nota não informada * -9999 a grade não existe * -8888 a grade retornou null * * @param int $userid * @param int $courseid * @return int Nota do aluno no curso. */ public static function get_final_grade_by_user_id_and_course_id($userid, $courseid) { // no MOODLE não temos factories // obtemos os acessores diretamente via variaveis globais global $CFG; require_once $CFG->dirroot . '/grade/lib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; require_once $CFG->dirroot . "/user/lib.php"; // para trabalhar com as grades de notas precisa-se ter // as seguintes habilidades: // * moodle/grade:export // * gradeexprt/txt:view // primeiro pego o contexto do sistema com base no usuário corrente $context = get_context_instance(CONTEXT_SYSTEM); // então verifico as abilidades uma a uma require_capability('moodle/grade:export', $context); require_capability('gradeexport/txt:view', $context); // neste ponto se verifica os parametros informados estão corretos // e dentro do exigido pela função // observe que solicitei os parametros como sendo do tipo inteiro // diretamente sem que sejam algum tipo de estrutura. Isto facilita // o acesso aos parametros, $funcParams = self::validate_parameters(self::get_final_grade_by_user_id_and_course_id_parameters(), array('userId' => $userid, 'courseId' => $courseid)); // OK, agora que está tudo ok, consulto no banco de dados a nota // do usuário conforme o curso $grade = grade_get_course_grade($funcParams['userId'], $funcParams['courseId']); /* * $grade é um objeto com detalhes da nota dada ao usuário * como neste caso informei apenas um curso retorna uma grade, se * informar mais de um curso ou nenhum curso retorna um array com * todas as notas disponiveis para o curso informado ou para os * cursos matriculados respectivamente. * * $grade->grade // Nota, obrigatorio * $grade->locked // * $grade->hidden // * $grade->overridden // * $grade->feedback // * $grade->feedbackformat // * $grade->usermodified // * $grade->dategraded // * $grade->item // * * formato de $grade->item * $item->name // Nome do Item (Por exemplo: Total do Curso) * $item->grademin // Valor Minimo da Nota * $item->grademax // Valor Máximo * $item->gradepass // * $item->locked * $item->hidden * */ // TODO, estudar melhorias neste retorno. $result = array(); if ($grade === false) { $result['grade'] = grade_floatval(-9999); } else { if ($grade === null) { $result['grade'] = grade_floatval(-8888); } else { $result['grade'] = grade_floatval($grade->grade); } } return $result; }
function local_ltiprovider_send_grade($tool, $user, $timenow) { // I've set this up this way to potentially use the Event API to trigger a grade send automatically. // TODO: Probably don't need all of these... global $DB, $CFG, $PAGE; 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'; $PAGE->set_context(context_system::instance()); $grade = false; if ($context = $DB->get_record('context', array('id' => $tool->contextid))) { if ($context->contextlevel == CONTEXT_COURSE) { 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); $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) { 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 // TODO: toggle this. There are places where replacing the grade // every time is desirable. This should probably be on the tool // level. //if ( intval($grade) == $user->lastgrade ) 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; } // Checking the return value and properly coping with it. $xml = simplexml_load_string(str_replace('xmlns', 'ns', $response)); $imsx_codeMajor = $xml->xpath('/imsx_POXEnvelopeResponse/imsx_POXHeader' . '/imsx_POXResponseHeaderInfo/imsx_statusInfo/imsx_codeMajor'); $imsx_description = $xml->xpath('/imsx_POXEnvelopeResponse/imsx_POXHeader' . '/imsx_POXResponseHeaderInfo/imsx_statusInfo/imsx_description'); mtrace(" Remote system description was " . $imsx_description[0]); if (strtolower($imsx_codeMajor[0]) === 'success') { $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}"); return array(true, "Successfully set grade"); } else { mtrace(" User grade send failed: " . $response); $error_count = $error_count + 1; } } else { mtrace(" User grade out of range: grade = " . $grade); $error_count = $error_count + 1; } } else { mtrace(" Invalid context: contextid = " . $tool->contextid); } }