Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.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/>.

/**
 * This file contains a class definition for the LTI Gradebook Services
 *
 * @package    ltiservice_gradebookservices
 * @copyright  2017 Cengage Learning http://www.cengage.com
 * @author     Dirk Singels, Diego del Blanco, Claude Vervoort
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace ltiservice_gradebookservices\local\service;

use ltiservice_gradebookservices\local\resources\lineitem;
use ltiservice_gradebookservices\local\resources\lineitems;
use ltiservice_gradebookservices\local\resources\results;
use ltiservice_gradebookservices\local\resources\scores;
use mod_lti\local\ltiservice\resource_base;
use mod_lti\local\ltiservice\service_base;
> use moodle_url;
defined('MOODLE_INTERNAL') || die();
> global $CFG; /** > require_once($CFG->dirroot . '/mod/lti/locallib.php'); * A service implementing LTI Gradebook Services. >
* * @package ltiservice_gradebookservices * @copyright 2017 Cengage Learning http://www.cengage.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class gradebookservices extends service_base { /** Read-only access to Gradebook services */ const GRADEBOOKSERVICES_READ = 1; /** Full access to Gradebook services */ const GRADEBOOKSERVICES_FULL = 2; /** Scope for full access to Lineitem service */ const SCOPE_GRADEBOOKSERVICES_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'; /** Scope for full access to Lineitem service */ const SCOPE_GRADEBOOKSERVICES_LINEITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly'; /** Scope for access to Result service */ const SCOPE_GRADEBOOKSERVICES_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly'; /** Scope for access to Score service */ const SCOPE_GRADEBOOKSERVICES_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score'; /** * Class constructor. */ public function __construct() { parent::__construct(); $this->id = 'gradebookservices'; $this->name = get_string($this->get_component_id(), $this->get_component_id()); } /** * Get the resources for this service. * * @return resource_base[] */ public function get_resources() { // The containers should be ordered in the array after their elements. // Lineitems should be after lineitem. if (empty($this->resources)) { $this->resources = array(); $this->resources[] = new lineitem($this); $this->resources[] = new lineitems($this); $this->resources[] = new results($this); $this->resources[] = new scores($this); } return $this->resources; } /** * Get the scope(s) permitted for this service. * * @return array */ public function get_permitted_scopes() { $scopes = array(); $ok = !empty($this->get_type()); if ($ok && isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { if (!empty($setting = $this->get_typeconfig()['ltiservice_gradesynchronization'])) { $scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ; $scopes[] = self::SCOPE_GRADEBOOKSERVICES_RESULT_READ; $scopes[] = self::SCOPE_GRADEBOOKSERVICES_SCORE; if ($setting == self::GRADEBOOKSERVICES_FULL) { $scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM; } } } return $scopes; } /** * Get the scopes defined by this service. * * @return array */ public function get_scopes() { return [self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ, self::SCOPE_GRADEBOOKSERVICES_RESULT_READ, self::SCOPE_GRADEBOOKSERVICES_SCORE, self::SCOPE_GRADEBOOKSERVICES_LINEITEM]; } /** * Adds form elements for gradebook sync add/edit page. * * @param \MoodleQuickForm $mform Moodle quickform object definition */ public function get_configuration_options(&$mform) { $selectelementname = 'ltiservice_gradesynchronization'; $identifier = 'grade_synchronization'; $options = [ get_string('nevergs', $this->get_component_id()), get_string('partialgs', $this->get_component_id()), get_string('alwaysgs', $this->get_component_id()) ]; $mform->addElement('select', $selectelementname, get_string($identifier, $this->get_component_id()), $options); $mform->setType($selectelementname, 'int'); $mform->setDefault($selectelementname, 0); $mform->addHelpButton($selectelementname, $identifier, $this->get_component_id()); } /**
> * For submission review, if there is a dedicated URL, use it as the target link. * Return an array of key/values to add to the launch parameters. > * * > * @param string $messagetype message type for this launch * @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'. > * @param string $targetlinkuri current target link uri * @param string $courseid the course id. > * @param string|null $customstr concatenated list of custom parameters * @param object $user The user id. > * @param int $courseid * @param string $typeid The tool lti type id. > * @param null|object $lti LTI Instance. * @param string $modlti The id of the lti activity. > * * > * @return array containing the target link URL and the custom params string to use. * The type is passed to check the configuration > */ * and not return parameters for services not used. > public function override_endpoint(string $messagetype, string $targetlinkuri, ?string $customstr, int $courseid, * > ?object $lti = null): array { * @return array of key/value pairs to add as launch parameters. > global $DB; */ > if ($messagetype == 'LtiSubmissionReviewRequest' && isset($lti->id)) { public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) { > $conditions = array('courseid' => $courseid, 'ltilinkid' => $lti->id); global $DB; > $coupledlineitems = $DB->get_records('ltiservice_gradebookservices', $conditions); $launchparameters = array(); > if (count($coupledlineitems) == 1) { $this->set_type(lti_get_type($typeid)); > $item = reset($coupledlineitems); $this->set_typeconfig(lti_get_type_config($typeid)); > $url = $item->subreviewurl; // Only inject parameters if the service is enabled for this tool. > $subreviewparams = $item->subreviewparams; if (isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { > if (!empty($url) && $url != 'DEFAULT') { if ($this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_READ || > $targetlinkuri = $url; $this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_FULL) { > } // Check for used in context is only needed because there is no explicit site tool - course relation. > if (!empty($subreviewparams)) { if ($this->is_allowed_in_context($typeid, $courseid)) { > if (!empty($customstr)) { $id = null; > $customstr .= "\n{$subreviewparams}"; if (!is_null($modlti)) { > } else { $conditions = array('courseid' => $courseid, 'itemtype' => 'mod', > $customstr = $subreviewparams; 'itemmodule' => 'lti', 'iteminstance' => $modlti); > } > } $coupledlineitems = $DB->get_records('grade_items', $conditions); > } $conditionsgbs = array('courseid' => $courseid, 'ltilinkid' => $modlti); > } $lineitemsgbs = $DB->get_records('ltiservice_gradebookservices', $conditionsgbs); > return [$targetlinkuri, $customstr]; // If a link has more that one attached grade items, per spec we do not populate line item url. > } if (count($lineitemsgbs) == 1) { > $id = reset($lineitemsgbs)->gradeitemid; > /** } > * Return an array of key/claim mapping allowing LTI 1.1 custom parameters if (count($lineitemsgbs) < 2 && count($coupledlineitems) == 1) { > * to be transformed to LTI 1.3 claims. $coupledid = reset($coupledlineitems)->id; > * if (!is_null($id) && $id != $coupledid) { > * @return array Key/value pairs of params to claim mapping. $id = null; > */ } else { > public function get_jwt_claim_mappings(): array { $id = $coupledid; > return [ } > 'custom_gradebookservices_scope' => [ } > 'suffix' => 'ags', } > 'group' => 'endpoint', $launchparameters['gradebookservices_scope'] = implode(',', $this->get_permitted_scopes()); > 'claim' => 'scope', $launchparameters['lineitems_url'] = '$LineItems.url'; > 'isarray' => true if (!is_null($id)) { > ], $launchparameters['lineitem_url'] = '$LineItem.url'; > 'custom_lineitems_url' => [ } > 'suffix' => 'ags', } > 'group' => 'endpoint', } > 'claim' => 'lineitems', } > 'isarray' => false return $launchparameters; > ], } > 'custom_lineitem_url' => [ > 'suffix' => 'ags', /** > 'group' => 'endpoint', * Fetch the lineitem instances. > 'claim' => 'lineitem', * > 'isarray' => false * @param string $courseid ID of course > ], * @param string $resourceid Resource identifier used for filtering, may be null > 'custom_results_url' => [ * @param string $ltilinkid Resource Link identifier used for filtering, may be null > 'suffix' => 'ags', * @param string $tag > 'group' => 'endpoint', * @param int $limitfrom Offset for the first line item to include in a paged set > 'claim' => 'results', * @param int $limitnum Maximum number of line items to include in the paged set > 'isarray' => false * @param string $typeid > ], * > 'custom_result_url' => [ * @return array > 'suffix' => 'ags', * @throws \Exception > 'group' => 'endpoint', */ > 'claim' => 'result', public function get_lineitems($courseid, $resourceid, $ltilinkid, $tag, $limitfrom, $limitnum, $typeid) { > 'isarray' => false global $DB; > ], > 'custom_scores_url' => [ // Select all lti potential linetiems in site. > 'suffix' => 'ags', $params = array('courseid' => $courseid); > 'group' => 'endpoint', > 'claim' => 'scores', $sql = "SELECT i.* > 'isarray' => false FROM {grade_items} i > ], WHERE (i.courseid = :courseid) > 'custom_score_url' => [ ORDER BY i.id"; > 'suffix' => 'ags', $lineitems = $DB->get_records_sql($sql, $params); > 'group' => 'endpoint', > 'claim' => 'score', // For each one, check the gbs id, and check that toolproxy matches. If so, add the > 'isarray' => false // tag to the result and add it to a final results array. > ] $lineitemstoreturn = array(); > ]; $lineitemsandtotalcount = array(); > } if ($lineitems) { > foreach ($lineitems as $lineitem) { > /**
$gbs = $this->find_ltiservice_gradebookservice_for_lineitem($lineitem->id); if ($gbs && (!isset($tag) || (isset($tag) && $gbs->tag == $tag)) && (!isset($ltilinkid) || (isset($ltilinkid) && $gbs->ltilinkid == $ltilinkid)) && (!isset($resourceid) || (isset($resourceid) && $gbs->resourceid == $resourceid))) { if (is_null($typeid)) { if ($this->get_tool_proxy()->id == $gbs->toolproxyid) { array_push($lineitemstoreturn, $lineitem); } } else { if ($typeid == $gbs->typeid) { array_push($lineitemstoreturn, $lineitem); } } } else if (($lineitem->itemtype == 'mod' && $lineitem->itemmodule == 'lti' && !isset($resourceid) && !isset($tag) && (!isset($ltilinkid) || (isset($ltilinkid) && $lineitem->iteminstance == $ltilinkid)))) { // We will need to check if the activity related belongs to our tool proxy. $ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance)); if (($ltiactivity) && (isset($ltiactivity->typeid))) { if ($ltiactivity->typeid != 0) { $tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid)); } else { $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid); if (!$tool) { $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid); } } if (is_null($typeid)) { if (($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid)) { array_push($lineitemstoreturn, $lineitem); } } else { if (($tool) && ($tool->id == $typeid)) { array_push($lineitemstoreturn, $lineitem); } } } } } $lineitemsandtotalcount = array(); array_push($lineitemsandtotalcount, count($lineitemstoreturn)); // Return the right array based in the paging parameters limit and from. if (($limitnum) && ($limitnum > 0)) { $lineitemstoreturn = array_slice($lineitemstoreturn, $limitfrom, $limitnum); } array_push($lineitemsandtotalcount, $lineitemstoreturn); } return $lineitemsandtotalcount; } /** * Fetch a lineitem instance. * * Returns the lineitem instance if found, otherwise false. * * @param string $courseid ID of course * @param string $itemid ID of lineitem * @param string $typeid * * @return \ltiservice_gradebookservices\local\resources\lineitem|bool */ public function get_lineitem($courseid, $itemid, $typeid) { global $DB, $CFG; require_once($CFG->libdir . '/gradelib.php'); $lineitem = \grade_item::fetch(array('id' => $itemid)); if ($lineitem) { $gbs = $this->find_ltiservice_gradebookservice_for_lineitem($itemid); if (!$gbs) { // We will need to check if the activity related belongs to our tool proxy. $ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance)); if (($ltiactivity) && (isset($ltiactivity->typeid))) { if ($ltiactivity->typeid != 0) { $tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid)); } else { $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid); if (!$tool) { $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid); } } if (is_null($typeid)) { if (!(($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid))) { return false; } } else { if (!(($tool) && ($tool->id == $typeid))) { return false; } } } else { return false; } } } return $lineitem; } /** * Adds a decoupled (standalone) line item. * Decoupled line items are not directly attached to * an lti instance activity. They are recorded in * the gradebook as manual activities and the * gradebookservices is used to associate that manual column * with the tool in addition to storing the LTI related * metadata (resource id, tag). * * @param string $courseid ID of course * @param string $label label of lineitem * @param float $maximumscore maximum score of lineitem * @param string $baseurl * @param int|null $ltilinkid id of lti instance this line item is associated with * @param string|null $resourceid resource id of lineitem * @param string|null $tag tag of lineitem * @param int $typeid lti type to which this line item is associated with * @param int|null $toolproxyid lti2 tool proxy to which this lineitem is associated to * * @return int id of the created gradeitem */ public function add_standalone_lineitem(string $courseid, string $label, float $maximumscore, string $baseurl, ?int $ltilinkid, ?string $resourceid, ?string $tag, int $typeid, int $toolproxyid = null) : int { global $DB; $params = array(); $params['itemname'] = $label; $params['gradetype'] = GRADE_TYPE_VALUE; $params['grademax'] = $maximumscore; $params['grademin'] = 0; $item = new \grade_item(array('id' => 0, 'courseid' => $courseid)); \grade_item::set_properties($item, $params); $item->itemtype = 'manual'; $item->grademax = $maximumscore; $id = $item->insert('mod/ltiservice_gradebookservices'); $DB->insert_record('ltiservice_gradebookservices', (object)array( 'gradeitemid' => $id, 'courseid' => $courseid, 'toolproxyid' => $toolproxyid, 'typeid' => $typeid, 'baseurl' => $baseurl, 'ltilinkid' => $ltilinkid, 'resourceid' => $resourceid, 'tag' => $tag )); return $id; } /** * Set a grade item. * * @param object $gradeitem Grade Item record * @param object $score Result object * @param int $userid User ID * * @throws \Exception * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more. * @see gradebookservices::save_grade_item($gradeitem, $score, $userid) */ public static function save_score($gradeitem, $score, $userid) { $service = new gradebookservices(); $service->save_grade_item($gradeitem, $score, $userid); } /** * Saves a score received from the LTI tool. * * @param object $gradeitem Grade Item record * @param object $score Result object * @param int $userid User ID * * @throws \Exception */ public function save_grade_item($gradeitem, $score, $userid) { global $DB, $CFG; $source = 'mod' . $this->get_component_id(); if ($DB->get_record('user', array('id' => $userid)) === false) { throw new \Exception(null, 400); } require_once($CFG->libdir . '/gradelib.php'); $finalgrade = null; $timemodified = null; if (isset($score->scoreGiven)) { $finalgrade = grade_floatval($score->scoreGiven); $max = 1; if (isset($score->scoreMaximum)) { $max = $score->scoreMaximum; } if (!is_null($max) && grade_floats_different($max, $gradeitem->grademax) && grade_floats_different($max, 0.0)) { // Rescale to match the grade item maximum. $finalgrade = grade_floatval($finalgrade * $gradeitem->grademax / $max); } if (isset($score->timestamp)) { $timemodified = strtotime($score->timestamp); } else { $timemodified = time(); } } $feedbackformat = FORMAT_MOODLE; $feedback = null; if (!empty($score->comment)) { $feedback = $score->comment; $feedbackformat = FORMAT_PLAIN; } if ($gradeitem->is_manual_item()) { $result = $gradeitem->update_final_grade($userid, $finalgrade, null, $feedback, FORMAT_PLAIN, null, $timemodified); } else { if (!$grade = \grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $userid))) { $grade = new \grade_grade(); $grade->userid = $userid; $grade->itemid = $gradeitem->id; } $grade->rawgrademax = $score->scoreMaximum; $grade->timemodified = $timemodified; $grade->feedbackformat = $feedbackformat; $grade->feedback = $feedback; $grade->rawgrade = $finalgrade; $status = grade_update($source, $gradeitem->courseid, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, $gradeitem->itemnumber, $grade); $result = ($status == GRADE_UPDATE_OK); } if (!$result) { debugging("failed to save score for item ".$gradeitem->id." and user ".$grade->userid); throw new \Exception(null, 500); } } /** * Get the json object representation of the grade item * * @param object $item Grade Item record * @param string $endpoint Endpoint for lineitems container request * @param string $typeid * * @return object */ public static function item_for_json($item, $endpoint, $typeid) { $lineitem = new \stdClass(); if (is_null($typeid)) { $typeidstring = ""; } else { $typeidstring = "?type_id={$typeid}"; } $lineitem->id = "{$endpoint}/{$item->id}/lineitem" . $typeidstring; $lineitem->label = $item->itemname; $lineitem->scoreMaximum = floatval($item->grademax); $gbs = self::find_ltiservice_gradebookservice_for_lineitem($item->id); if ($gbs) { $lineitem->resourceId = (!empty($gbs->resourceid)) ? $gbs->resourceid : ''; $lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : ''; if (isset($gbs->ltilinkid)) { $lineitem->resourceLinkId = strval($gbs->ltilinkid); $lineitem->ltiLinkId = strval($gbs->ltilinkid); }
> if (!empty($gbs->subreviewurl)) { } else { > $submissionreview = new \stdClass(); $lineitem->tag = ''; > if ($gbs->subreviewurl != 'DEFAULT') { if (isset($item->iteminstance)) { > $submissionreview->url = $gbs->subreviewurl; $lineitem->resourceLinkId = strval($item->iteminstance); > } $lineitem->ltiLinkId = strval($item->iteminstance); > if (!empty($gbs->subreviewparams)) { } > $submissionreview->custom = lti_split_parameters($gbs->subreviewparams); } > } > $lineitem->submissionReview = $submissionreview; return $lineitem; > }
} /** * Get the object matching the JSON representation of the result. * * @param object $grade Grade record * @param string $endpoint Endpoint for lineitem * @param int $typeid The id of the type to include in the result url. * * @return object */ public static function result_for_json($grade, $endpoint, $typeid) { if (is_null($typeid)) { $id = "{$endpoint}/results?user_id={$grade->userid}"; } else { $id = "{$endpoint}/results?type_id={$typeid}&user_id={$grade->userid}"; } $result = new \stdClass(); $result->id = $id; $result->userId = $grade->userid; if (!empty($grade->finalgrade)) { $result->resultScore = floatval($grade->finalgrade); $result->resultMaximum = floatval($grade->rawgrademax); if (!empty($grade->feedback)) { $result->comment = $grade->feedback; } if (is_null($typeid)) { $result->scoreOf = $endpoint; } else { $result->scoreOf = "{$endpoint}?type_id={$typeid}"; } $result->timestamp = date('c', $grade->timemodified); } return $result; } /** * Check if an LTI id is valid. * * @param string $linkid The lti id * @param string $course The course * @param string $toolproxy The tool proxy id * * @return boolean */ public static function check_lti_id($linkid, $course, $toolproxy) { global $DB; // Check if lti type is zero or not (comes from a backup). $sqlparams1 = array(); $sqlparams1['linkid'] = $linkid; $sqlparams1['course'] = $course; $ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course)); if ($ltiactivity->typeid == 0) { $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course); if (!$tool) { $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course); } return (($tool) && ($toolproxy == $tool->toolproxyid)); } else { $sqlparams2 = array(); $sqlparams2['linkid'] = $linkid; $sqlparams2['course'] = $course; $sqlparams2['toolproxy'] = $toolproxy; $sql = 'SELECT lti.* FROM {lti} lti INNER JOIN {lti_types} typ ON lti.typeid = typ.id WHERE lti.id = ? AND lti.course = ? AND typ.toolproxyid = ?'; return $DB->record_exists_sql($sql, $sqlparams2); } } /** * Check if an LTI id is valid when we are in a LTI 1.x case * * @param string $linkid The lti id * @param string $course The course * @param string $typeid The lti type id * * @return boolean */ public static function check_lti_1x_id($linkid, $course, $typeid) { global $DB; // Check if lti type is zero or not (comes from a backup). $sqlparams1 = array(); $sqlparams1['linkid'] = $linkid; $sqlparams1['course'] = $course; $ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course)); if ($ltiactivity) { if ($ltiactivity->typeid == 0) { $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course); if (!$tool) { $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course); } return (($tool) && ($typeid == $tool->id)); } else { $sqlparams2 = array(); $sqlparams2['linkid'] = $linkid; $sqlparams2['course'] = $course; $sqlparams2['typeid'] = $typeid; $sql = 'SELECT lti.* FROM {lti} lti INNER JOIN {lti_types} typ ON lti.typeid = typ.id WHERE lti.id = ? AND lti.course = ? AND typ.id = ?'; return $DB->record_exists_sql($sql, $sqlparams2); } } else { return false; } } /**
< * Updates the tag and resourceid values for a grade item coupled to an lti link instance.
> * Updates the tag, resourceid and submission review values for a grade item coupled to an lti link instance.
* * @param object $ltiinstance The lti instance to which the grade item is coupled to * @param string|null $resourceid The resourceid to apply to the lineitem. If empty string which will be stored as null. * @param string|null $tag The tag to apply to the lineitem. If empty string which will be stored as null.
> * @param moodle_url|null $subreviewurl The submission review target link URL * > * @param string|null $subreviewparams The submission review custom parameters.
*/
< public static function update_coupled_gradebookservices(object $ltiinstance, ?string $resourceid, ?string $tag) : void {
> public static function update_coupled_gradebookservices(object $ltiinstance, > ?string $resourceid, ?string $tag, ?\moodle_url $subreviewurl, ?string $subreviewparams) : void {
global $DB; if ($ltiinstance && $ltiinstance->typeid) { $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $ltiinstance->id)); if ($gradeitem) { $resourceid = (isset($resourceid) && empty(trim($resourceid))) ? null : $resourceid;
> $subreviewurlstr = $subreviewurl ? $subreviewurl->out(false) : null;
$tag = (isset($tag) && empty(trim($tag))) ? null : $tag; $gbs = self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); if ($gbs) { $gbs->resourceid = $resourceid; $gbs->tag = $tag;
> $gbs->subreviewurl = $subreviewurlstr; $DB->update_record('ltiservice_gradebookservices', $gbs); > $gbs->subreviewparams = $subreviewparams;
} else { $baseurl = lti_get_type_type_config($ltiinstance->typeid)->lti_toolurl; $DB->insert_record('ltiservice_gradebookservices', (object)array( 'gradeitemid' => $gradeitem->id, 'courseid' => $gradeitem->courseid, 'typeid' => $ltiinstance->typeid, 'baseurl' => $baseurl, 'ltilinkid' => $ltiinstance->id, 'resourceid' => $resourceid,
< 'tag' => $tag
> 'tag' => $tag, > 'subreviewurl' => $subreviewurlstr, > 'subreviewparams' => $subreviewparams
)); } } } } /** * Called when a new LTI Instance is added. * * @param object $lti LTI Instance. */ public function instance_added(object $lti): void {
< self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null);
> self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null, > isset($lti->lineitemsubreviewurl) ? new moodle_url($lti->lineitemsubreviewurl) : null, > $lti->lineitemsubreviewparams ?? null);
} /** * Called when a new LTI Instance is updated. * * @param object $lti LTI Instance. */ public function instance_updated(object $lti): void {
< self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null);
> self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null, > isset($lti->lineitemsubreviewurl) ? new moodle_url($lti->lineitemsubreviewurl) : null, > $lti->lineitemsubreviewparams ?? null);
} /** * Set the form data when displaying the LTI Instance form. * * @param object $defaultvalues Default form values. */ public function set_instance_form_values(object $defaultvalues): void { $defaultvalues->lineitemresourceid = ''; $defaultvalues->lineitemtag = '';
> $defaultvalues->subreviewurl = ''; if (is_object($defaultvalues) && $defaultvalues->instance) { > $defaultvalues->subreviewparams = '';
$gbs = self::find_ltiservice_gradebookservice_for_lti($defaultvalues->instance); if ($gbs) { $defaultvalues->lineitemresourceid = $gbs->resourceid; $defaultvalues->lineitemtag = $gbs->tag;
> $defaultvalues->lineitemsubreviewurl = $gbs->subreviewurl; } > $defaultvalues->lineitemsubreviewparams = $gbs->subreviewparams;
} } /** * Deletes orphaned rows from the 'ltiservice_gradebookservices' table. * * Sometimes, if a gradebook entry is deleted and it was a lineitem * the row in the table ltiservice_gradebookservices can become an orphan * This method will clean these orphans. It will happens based on a task * because it is not urgent and we don't want to slow the service */ public static function delete_orphans_ltiservice_gradebookservices_rows() { global $DB; $sql = "DELETE FROM {ltiservice_gradebookservices} WHERE gradeitemid NOT IN (SELECT id FROM {grade_items} gi)"; $DB->execute($sql); } /** * Check if a user can be graded in a course * * @param int $courseid The course * @param int $userid The user * @return bool */ public static function is_user_gradable_in_course($courseid, $userid) { global $CFG; $gradableuser = false; $coursecontext = \context_course::instance($courseid); if (is_enrolled($coursecontext, $userid, '', false)) { $roles = get_user_roles($coursecontext, $userid); $gradebookroles = explode(',', $CFG->gradebookroles); foreach ($roles as $role) { foreach ($gradebookroles as $gradebookrole) { if ($role->roleid === $gradebookrole) { $gradableuser = true; } } } } return $gradableuser; } /** * Find the right element in the ltiservice_gradebookservice table for an lti instance * * @param string $instanceid The LTI module instance id * @return object gradebookservice for this line item */ public static function find_ltiservice_gradebookservice_for_lti($instanceid) { global $DB; if ($instanceid) { $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $instanceid)); if ($gradeitem) { return self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); } } } /** * Find the right element in the ltiservice_gradebookservice table for a lineitem * * @param string $lineitemid The lineitem (gradeitem) id * @return object gradebookservice if it exists */ public static function find_ltiservice_gradebookservice_for_lineitem($lineitemid) { global $DB; if ($lineitemid) { return $DB->get_record('ltiservice_gradebookservices', array('gradeitemid' => $lineitemid)); } } /** * Validates specific ISO 8601 format of the timestamps. * * @param string $date The timestamp to check. * @return boolean true or false if the date matches the format. * */ public static function validate_iso8601_date($date) { if (preg_match('/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])' . '(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))' . '([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)' . '?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/', $date) > 0) { return true; } else { return false; } } }