Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace enrol_lti\local\ltiadvantage\task;

use core\task\scheduled_task;
use enrol_lti\helper;
< use enrol_lti\local\ltiadvantage\lib\http_client; < use enrol_lti\local\ltiadvantage\lib\issuer_database; < use enrol_lti\local\ltiadvantage\lib\launch_cache_session; < use enrol_lti\local\ltiadvantage\repository\application_registration_repository; < use enrol_lti\local\ltiadvantage\repository\deployment_repository; < use enrol_lti\local\ltiadvantage\repository\resource_link_repository; < use enrol_lti\local\ltiadvantage\repository\user_repository; < use Packback\Lti1p3\LtiAssignmentsGradesService; < use Packback\Lti1p3\LtiGrade; < use Packback\Lti1p3\LtiLineitem; < use Packback\Lti1p3\LtiRegistration; < use Packback\Lti1p3\LtiServiceConnector;
/** * LTI Advantage task responsible for pushing grades to tool platforms. * * @package enrol_lti * @copyright 2021 Jake Dallimore <jrhdallimore@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class sync_grades extends scheduled_task { /** * Get a descriptive name for this task. * * @return string */ public function get_name() { return get_string('tasksyncgrades', 'enrol_lti'); } /**
< * Sync grades to the platform using the Assignment and Grade Services. < * < * @param \stdClass $resource the enrol_lti_tools data record for the shared resource. < * @return array an array containing the < */ < protected function sync_grades_for_resource($resource): array { < $usercount = 0; < $sendcount = 0; < $userrepo = new user_repository(); < $resourcelinkrepo = new resource_link_repository(); < $appregistrationrepo = new application_registration_repository(); < $issuerdb = new issuer_database($appregistrationrepo, new deployment_repository()); < < if ($users = $userrepo->find_by_resource($resource->id)) { < $completion = new \completion_info(get_course($resource->courseid)); < $syncedusergrades = []; // Keep track of those users who have had their grade synced during this run. < foreach ($users as $user) { < $mtracecontent = "for the user '{$user->get_localid()}', for the resource '$resource->id' and the course " . < "'$resource->courseid'"; < $usercount++; < < // Check if we do not have a grade service endpoint in either of the resource links. < // Remember, not all launches need to support grade services. < $userresourcelinks = $resourcelinkrepo->find_by_resource_and_user($resource->id, $user->get_id()); < $userlastgrade = $user->get_lastgrade(); < mtrace("Found ".count($userresourcelinks)." resource link(s) $mtracecontent. Attempting to sync grades for all."); < < foreach ($userresourcelinks as $userresourcelink) { < mtrace("Processing resource link '{$userresourcelink->get_resourcelinkid()}'."); < if (!$gradeservice = $userresourcelink->get_grade_service()) { < mtrace("Skipping - No grade service found $mtracecontent."); < continue; < } < < if (!$context = \context::instance_by_id($resource->contextid, IGNORE_MISSING)) { < mtrace("Failed - Invalid contextid '$resource->contextid' for the resource '$resource->id'."); < continue; < } < < $grade = false; < $dategraded = false; < if ($context->contextlevel == CONTEXT_COURSE) { < if ($resource->gradesynccompletion && !$completion->is_course_complete($user->get_localid())) { < mtrace("Skipping - Course not completed $mtracecontent."); < continue; < } < < // Get the grade. < if ($grade = grade_get_course_grade($user->get_localid(), $resource->courseid)) { < $grademax = floatval($grade->item->grademax); < $dategraded = $grade->dategraded; < $grade = $grade->grade; < } < } else if ($context->contextlevel == CONTEXT_MODULE) { < $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); < < if ($resource->gradesynccompletion) { < $data = $completion->get_data($cm, false, $user->get_localid()); < if (!in_array($data->completionstate, [COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE])) { < mtrace("Skipping - Activity not completed $mtracecontent."); < continue; < } < } < < $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, < $user->get_localid()); < 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); < } < $dategraded = $grade->dategraded; < $grade = $grade->grade; < } < } < < if ($grade === false || $grade === null || strlen($grade) < 1) { < mtrace("Skipping - Invalid grade $mtracecontent."); < continue; < } < < if (empty($grademax)) { < mtrace("Skipping - Invalid grademax $mtracecontent."); < continue; < } < < if (!grade_floats_different($grade, $userlastgrade)) { < mtrace("Not sent - The grade $mtracecontent was not sent as the grades are the same."); < continue; < } < $floatgrade = $grade / $grademax; < < try { < // Get an AGS instance for the corresponding application registration and service data. < $appregistration = $appregistrationrepo->find_by_deployment( < $userresourcelink->get_deploymentid() < ); < $registration = $issuerdb->findRegistrationByIssuer( < $appregistration->get_platformid()->out(false), < $appregistration->get_clientid() < ); < global $CFG; < require_once($CFG->libdir . '/filelib.php'); < $sc = new LtiServiceConnector(new launch_cache_session(), new http_client(new \curl())); < < $lineitemurl = $gradeservice->get_lineitemurl(); < $lineitemsurl = $gradeservice->get_lineitemsurl(); < $servicedata = [ < 'lineitems' => $lineitemsurl ? $lineitemsurl->out(false) : null, < 'lineitem' => $lineitemurl ? $lineitemurl->out(false) : null, < 'scope' => $gradeservice->get_scopes(), < ]; < < $ags = $this->get_ags($sc, $registration, $servicedata); < $ltigrade = LtiGrade::new() < ->setScoreGiven($grade) < ->setScoreMaximum($grademax) < ->setUserId($user->get_sourceid()) < ->setTimestamp(date(\DateTimeInterface::ISO8601, $dategraded)) < ->setActivityProgress('Completed') < ->setGradingProgress('FullyGraded'); < < if (empty($servicedata['lineitem'])) { < // The launch did not include a couple lineitem, so find or create the line item for grading. < $lineitem = $ags->findOrCreateLineitem(new LtiLineitem([ < 'label' => $this->get_line_item_label($resource, $context), < 'scoreMaximum' => $grademax, < 'tag' => 'grade', < 'resourceId' => $userresourcelink->get_resourceid(), < 'resourceLinkId' => $userresourcelink->get_resourcelinkid() < ])); < $response = $ags->putGrade($ltigrade, $lineitem); < } else { < // Let AGS find the coupled line item. < $response = $ags->putGrade($ltigrade); < } < < } catch (\Exception $e) { < mtrace("Failed - The grade '$floatgrade' $mtracecontent failed to send."); < mtrace($e->getMessage()); < continue; < } < < if ($response['status'] == 200) { < $user->set_lastgrade(grade_floatval($grade)); < $syncedusergrades[$user->get_id()] = $user; < mtrace("Success - The grade '$floatgrade' $mtracecontent was sent."); < } else { < mtrace("Failed - The grade '$floatgrade' $mtracecontent failed to send."); < mtrace("Header: {$response['headers']['httpstatus']}"); < } < } < } < // Update the lastgrade value for any users who had a grade synced. Allows skipping on future runs if not changed. < // Update the count of total users having their grades synced, not the total number of grade sync calls made. < foreach ($syncedusergrades as $ltiuser) { < $userrepo->save($ltiuser); < $sendcount = $sendcount + 1; < } < } < return [$usercount, $sendcount]; < } < < /** < * Get the string label for the line item associated with the resource, based on the course or module name. < * < * @param \stdClass $resource the enrol_lti_tools record. < * @param \context $context the context of the resource - either course or module. < * @return string the label to use in the line item. < */ < protected function get_line_item_label(\stdClass $resource, \context $context): string { < $resourcename = 'default'; < if ($context->contextlevel == CONTEXT_COURSE) { < global $DB; < $coursenamesql = "SELECT c.fullname < FROM {enrol_lti_tools} t < JOIN {enrol} e < ON (e.id = t.enrolid) < JOIN {course} c < ON {c.id} = e.courseid < WHERE t.id = :resourceid"; < $coursename = $DB->get_field_sql($coursenamesql, ['resourceid' => $resource->id]); < $resourcename = format_string($coursename, true, ['context' => $context->id]); < } else if ($context->contextlevel == CONTEXT_MODULE) { < foreach (get_fast_modinfo($resource->courseid)->get_cms() as $mod) { < if ($mod->context->id == $context->id) { < $resourcename = $mod->name; < } < } < } < return $resourcename; < } < < /** < * Get an ags instance to make the call to the platform. < * < * @param LtiServiceConnector $sc a service connector instance. < * @param LtiRegistration $registration the registration instance. < * @param array $sd the service data. < * @return LtiAssignmentsGradesService < */ < protected function get_ags(LtiServiceConnector $sc, LtiRegistration $registration, array $sd): LtiAssignmentsGradesService { < return new LtiAssignmentsGradesService($sc, $registration, $sd); < } < < /** < * Performs the synchronisation of grades from the tool to any registered platforms.
> * Creates adhoc tasks (one per resource) to synchronize grades from the tool to any registered platforms.
* * @return bool|void */ public function execute() {
< global $CFG; < < require_once($CFG->dirroot . '/lib/completionlib.php'); < require_once($CFG->libdir . '/gradelib.php'); < require_once($CFG->dirroot . '/grade/querylib.php');
if (!is_enabled_auth('lti')) { mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti'))); return true; } if (!enrol_is_enabled('lti')) { mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti')); return true; } $resources = helper::get_lti_tools([ 'status' => ENROL_INSTANCE_ENABLED, 'gradesync' => 1, 'ltiversion' => 'LTI-1p3' ]); if (empty($resources)) { mtrace('Skipping task - There are no resources with grade sync enabled.'); return true; } foreach ($resources as $resource) {
< mtrace("Starting - LTI Advantage grade sync for shared resource '$resource->id' in course '$resource->courseid'."); < < [$usercount, $sendcount] = $this->sync_grades_for_resource($resource); < < mtrace("Completed - Synced grades for tool '$resource->id' in the course '$resource->courseid'. " . < "Processed $usercount users; sent $sendcount grades."); < mtrace("");
> $task = new \enrol_lti\local\ltiadvantage\task\sync_tool_grades(); > $task->set_custom_data($resource); > $task->set_component('enrol_lti'); > \core\task\manager::queue_adhoc_task($task, true);
}
> } > mtrace('Spawned ' . count($resources) . ' adhoc tasks to sync grades.');
}