Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file contains a class definition for the LTI Gradebook Services 19 * 20 * @package ltiservice_gradebookservices 21 * @copyright 2017 Cengage Learning http://www.cengage.com 22 * @author Dirk Singels, Diego del Blanco, Claude Vervoort 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace ltiservice_gradebookservices\local\service; 27 28 use ltiservice_gradebookservices\local\resources\lineitem; 29 use ltiservice_gradebookservices\local\resources\lineitems; 30 use ltiservice_gradebookservices\local\resources\results; 31 use ltiservice_gradebookservices\local\resources\scores; 32 use mod_lti\local\ltiservice\resource_base; 33 use mod_lti\local\ltiservice\service_base; 34 35 defined('MOODLE_INTERNAL') || die(); 36 37 /** 38 * A service implementing LTI Gradebook Services. 39 * 40 * @package ltiservice_gradebookservices 41 * @copyright 2017 Cengage Learning http://www.cengage.com 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class gradebookservices extends service_base { 45 46 /** Read-only access to Gradebook services */ 47 const GRADEBOOKSERVICES_READ = 1; 48 /** Full access to Gradebook services */ 49 const GRADEBOOKSERVICES_FULL = 2; 50 /** Scope for full access to Lineitem service */ 51 const SCOPE_GRADEBOOKSERVICES_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem'; 52 /** Scope for full access to Lineitem service */ 53 const SCOPE_GRADEBOOKSERVICES_LINEITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly'; 54 /** Scope for access to Result service */ 55 const SCOPE_GRADEBOOKSERVICES_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly'; 56 /** Scope for access to Score service */ 57 const SCOPE_GRADEBOOKSERVICES_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score'; 58 59 60 /** 61 * Class constructor. 62 */ 63 public function __construct() { 64 65 parent::__construct(); 66 $this->id = 'gradebookservices'; 67 $this->name = get_string($this->get_component_id(), $this->get_component_id()); 68 69 } 70 71 /** 72 * Get the resources for this service. 73 * 74 * @return resource_base[] 75 */ 76 public function get_resources() { 77 78 // The containers should be ordered in the array after their elements. 79 // Lineitems should be after lineitem. 80 if (empty($this->resources)) { 81 $this->resources = array(); 82 $this->resources[] = new lineitem($this); 83 $this->resources[] = new lineitems($this); 84 $this->resources[] = new results($this); 85 $this->resources[] = new scores($this); 86 } 87 88 return $this->resources; 89 } 90 91 /** 92 * Get the scope(s) permitted for this service. 93 * 94 * @return array 95 */ 96 public function get_permitted_scopes() { 97 98 $scopes = array(); 99 $ok = !empty($this->get_type()); 100 if ($ok && isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { 101 if (!empty($setting = $this->get_typeconfig()['ltiservice_gradesynchronization'])) { 102 $scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ; 103 $scopes[] = self::SCOPE_GRADEBOOKSERVICES_RESULT_READ; 104 $scopes[] = self::SCOPE_GRADEBOOKSERVICES_SCORE; 105 if ($setting == self::GRADEBOOKSERVICES_FULL) { 106 $scopes[] = self::SCOPE_GRADEBOOKSERVICES_LINEITEM; 107 } 108 } 109 } 110 111 return $scopes; 112 113 } 114 115 /** 116 * Get the scopes defined by this service. 117 * 118 * @return array 119 */ 120 public function get_scopes() { 121 return [self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ, self::SCOPE_GRADEBOOKSERVICES_RESULT_READ, 122 self::SCOPE_GRADEBOOKSERVICES_SCORE, self::SCOPE_GRADEBOOKSERVICES_LINEITEM]; 123 } 124 125 /** 126 * Adds form elements for gradebook sync add/edit page. 127 * 128 * @param \MoodleQuickForm $mform Moodle quickform object definition 129 */ 130 public function get_configuration_options(&$mform) { 131 132 $selectelementname = 'ltiservice_gradesynchronization'; 133 $identifier = 'grade_synchronization'; 134 $options = [ 135 get_string('nevergs', $this->get_component_id()), 136 get_string('partialgs', $this->get_component_id()), 137 get_string('alwaysgs', $this->get_component_id()) 138 ]; 139 140 $mform->addElement('select', $selectelementname, get_string($identifier, $this->get_component_id()), $options); 141 $mform->setType($selectelementname, 'int'); 142 $mform->setDefault($selectelementname, 0); 143 $mform->addHelpButton($selectelementname, $identifier, $this->get_component_id()); 144 } 145 146 /** 147 * Return an array of key/claim mapping allowing LTI 1.1 custom parameters 148 * to be transformed to LTI 1.3 claims. 149 * 150 * @return array Key/value pairs of params to claim mapping. 151 */ 152 public function get_jwt_claim_mappings(): array { 153 return [ 154 'custom_gradebookservices_scope' => [ 155 'suffix' => 'ags', 156 'group' => 'endpoint', 157 'claim' => 'scope', 158 'isarray' => true 159 ], 160 'custom_lineitems_url' => [ 161 'suffix' => 'ags', 162 'group' => 'endpoint', 163 'claim' => 'lineitems', 164 'isarray' => false 165 ], 166 'custom_lineitem_url' => [ 167 'suffix' => 'ags', 168 'group' => 'endpoint', 169 'claim' => 'lineitem', 170 'isarray' => false 171 ], 172 'custom_results_url' => [ 173 'suffix' => 'ags', 174 'group' => 'endpoint', 175 'claim' => 'results', 176 'isarray' => false 177 ], 178 'custom_result_url' => [ 179 'suffix' => 'ags', 180 'group' => 'endpoint', 181 'claim' => 'result', 182 'isarray' => false 183 ], 184 'custom_scores_url' => [ 185 'suffix' => 'ags', 186 'group' => 'endpoint', 187 'claim' => 'scores', 188 'isarray' => false 189 ], 190 'custom_score_url' => [ 191 'suffix' => 'ags', 192 'group' => 'endpoint', 193 'claim' => 'score', 194 'isarray' => false 195 ] 196 ]; 197 } 198 199 /** 200 * Return an array of key/values to add to the launch parameters. 201 * 202 * @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'. 203 * @param string $courseid the course id. 204 * @param object $user The user id. 205 * @param string $typeid The tool lti type id. 206 * @param string $modlti The id of the lti activity. 207 * 208 * The type is passed to check the configuration 209 * and not return parameters for services not used. 210 * 211 * @return array of key/value pairs to add as launch parameters. 212 */ 213 public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) { 214 global $DB; 215 $launchparameters = array(); 216 $this->set_type(lti_get_type($typeid)); 217 $this->set_typeconfig(lti_get_type_config($typeid)); 218 // Only inject parameters if the service is enabled for this tool. 219 if (isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { 220 if ($this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_READ || 221 $this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_FULL) { 222 // Check for used in context is only needed because there is no explicit site tool - course relation. 223 if ($this->is_allowed_in_context($typeid, $courseid)) { 224 $id = null; 225 if (!is_null($modlti)) { 226 $conditions = array('courseid' => $courseid, 'itemtype' => 'mod', 227 'itemmodule' => 'lti', 'iteminstance' => $modlti); 228 229 $coupledlineitems = $DB->get_records('grade_items', $conditions); 230 $conditionsgbs = array('courseid' => $courseid, 'ltilinkid' => $modlti); 231 $lineitemsgbs = $DB->get_records('ltiservice_gradebookservices', $conditionsgbs); 232 // If a link has more that one attached grade items, per spec we do not populate line item url. 233 if (count($lineitemsgbs) == 1) { 234 $id = reset($lineitemsgbs)->gradeitemid; 235 } 236 if (count($lineitemsgbs) < 2 && count($coupledlineitems) == 1) { 237 $coupledid = reset($coupledlineitems)->id; 238 if (!is_null($id) && $id != $coupledid) { 239 $id = null; 240 } else { 241 $id = $coupledid; 242 } 243 } 244 } 245 $launchparameters['gradebookservices_scope'] = implode(',', $this->get_permitted_scopes()); 246 $launchparameters['lineitems_url'] = '$LineItems.url'; 247 if (!is_null($id)) { 248 $launchparameters['lineitem_url'] = '$LineItem.url'; 249 } 250 } 251 } 252 } 253 return $launchparameters; 254 } 255 256 /** 257 * Fetch the lineitem instances. 258 * 259 * @param string $courseid ID of course 260 * @param string $resourceid Resource identifier used for filtering, may be null 261 * @param string $ltilinkid Resource Link identifier used for filtering, may be null 262 * @param string $tag 263 * @param int $limitfrom Offset for the first line item to include in a paged set 264 * @param int $limitnum Maximum number of line items to include in the paged set 265 * @param string $typeid 266 * 267 * @return array 268 * @throws \Exception 269 */ 270 public function get_lineitems($courseid, $resourceid, $ltilinkid, $tag, $limitfrom, $limitnum, $typeid) { 271 global $DB; 272 273 // Select all lti potential linetiems in site. 274 $params = array('courseid' => $courseid); 275 276 $sql = "SELECT i.* 277 FROM {grade_items} i 278 WHERE (i.courseid = :courseid) 279 ORDER BY i.id"; 280 $lineitems = $DB->get_records_sql($sql, $params); 281 282 // For each one, check the gbs id, and check that toolproxy matches. If so, add the 283 // tag to the result and add it to a final results array. 284 $lineitemstoreturn = array(); 285 $lineitemsandtotalcount = array(); 286 if ($lineitems) { 287 foreach ($lineitems as $lineitem) { 288 $gbs = $this->find_ltiservice_gradebookservice_for_lineitem($lineitem->id); 289 if ($gbs && (!isset($tag) || (isset($tag) && $gbs->tag == $tag)) 290 && (!isset($ltilinkid) || (isset($ltilinkid) && $gbs->ltilinkid == $ltilinkid)) 291 && (!isset($resourceid) || (isset($resourceid) && $gbs->resourceid == $resourceid))) { 292 if (is_null($typeid)) { 293 if ($this->get_tool_proxy()->id == $gbs->toolproxyid) { 294 array_push($lineitemstoreturn, $lineitem); 295 } 296 } else { 297 if ($typeid == $gbs->typeid) { 298 array_push($lineitemstoreturn, $lineitem); 299 } 300 } 301 } else if (($lineitem->itemtype == 'mod' && $lineitem->itemmodule == 'lti' 302 && !isset($resourceid) && !isset($tag) 303 && (!isset($ltilinkid) || (isset($ltilinkid) 304 && $lineitem->iteminstance == $ltilinkid)))) { 305 // We will need to check if the activity related belongs to our tool proxy. 306 $ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance)); 307 if (($ltiactivity) && (isset($ltiactivity->typeid))) { 308 if ($ltiactivity->typeid != 0) { 309 $tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid)); 310 } else { 311 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid); 312 if (!$tool) { 313 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid); 314 } 315 } 316 if (is_null($typeid)) { 317 if (($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid)) { 318 array_push($lineitemstoreturn, $lineitem); 319 } 320 } else { 321 if (($tool) && ($tool->id == $typeid)) { 322 array_push($lineitemstoreturn, $lineitem); 323 } 324 } 325 } 326 } 327 } 328 $lineitemsandtotalcount = array(); 329 array_push($lineitemsandtotalcount, count($lineitemstoreturn)); 330 // Return the right array based in the paging parameters limit and from. 331 if (($limitnum) && ($limitnum > 0)) { 332 $lineitemstoreturn = array_slice($lineitemstoreturn, $limitfrom, $limitnum); 333 } 334 array_push($lineitemsandtotalcount, $lineitemstoreturn); 335 } 336 return $lineitemsandtotalcount; 337 } 338 339 /** 340 * Fetch a lineitem instance. 341 * 342 * Returns the lineitem instance if found, otherwise false. 343 * 344 * @param string $courseid ID of course 345 * @param string $itemid ID of lineitem 346 * @param string $typeid 347 * 348 * @return \ltiservice_gradebookservices\local\resources\lineitem|bool 349 */ 350 public function get_lineitem($courseid, $itemid, $typeid) { 351 global $DB, $CFG; 352 353 require_once($CFG->libdir . '/gradelib.php'); 354 $lineitem = \grade_item::fetch(array('id' => $itemid)); 355 if ($lineitem) { 356 $gbs = $this->find_ltiservice_gradebookservice_for_lineitem($itemid); 357 if (!$gbs) { 358 // We will need to check if the activity related belongs to our tool proxy. 359 $ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance)); 360 if (($ltiactivity) && (isset($ltiactivity->typeid))) { 361 if ($ltiactivity->typeid != 0) { 362 $tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid)); 363 } else { 364 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid); 365 if (!$tool) { 366 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid); 367 } 368 } 369 if (is_null($typeid)) { 370 if (!(($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid))) { 371 return false; 372 } 373 } else { 374 if (!(($tool) && ($tool->id == $typeid))) { 375 return false; 376 } 377 } 378 } else { 379 return false; 380 } 381 } 382 } 383 return $lineitem; 384 } 385 386 /** 387 * Adds a decoupled (standalone) line item. 388 * Decoupled line items are not directly attached to 389 * an lti instance activity. They are recorded in 390 * the gradebook as manual activities and the 391 * gradebookservices is used to associate that manual column 392 * with the tool in addition to storing the LTI related 393 * metadata (resource id, tag). 394 * 395 * @param string $courseid ID of course 396 * @param string $label label of lineitem 397 * @param float $maximumscore maximum score of lineitem 398 * @param string $baseurl 399 * @param int|null $ltilinkid id of lti instance this line item is associated with 400 * @param string|null $resourceid resource id of lineitem 401 * @param string|null $tag tag of lineitem 402 * @param int $typeid lti type to which this line item is associated with 403 * @param int|null $toolproxyid lti2 tool proxy to which this lineitem is associated to 404 * 405 * @return int id of the created gradeitem 406 */ 407 public function add_standalone_lineitem(string $courseid, string $label, float $maximumscore, 408 string $baseurl, ?int $ltilinkid, ?string $resourceid, ?string $tag, int $typeid, 409 int $toolproxyid = null) : int { 410 global $DB; 411 $params = array(); 412 $params['itemname'] = $label; 413 $params['gradetype'] = GRADE_TYPE_VALUE; 414 $params['grademax'] = $maximumscore; 415 $params['grademin'] = 0; 416 $item = new \grade_item(array('id' => 0, 'courseid' => $courseid)); 417 \grade_item::set_properties($item, $params); 418 $item->itemtype = 'manual'; 419 $item->grademax = $maximumscore; 420 $id = $item->insert('mod/ltiservice_gradebookservices'); 421 $DB->insert_record('ltiservice_gradebookservices', (object)array( 422 'gradeitemid' => $id, 423 'courseid' => $courseid, 424 'toolproxyid' => $toolproxyid, 425 'typeid' => $typeid, 426 'baseurl' => $baseurl, 427 'ltilinkid' => $ltilinkid, 428 'resourceid' => $resourceid, 429 'tag' => $tag 430 )); 431 return $id; 432 } 433 434 /** 435 * Set a grade item. 436 * 437 * @param object $gradeitem Grade Item record 438 * @param object $score Result object 439 * @param int $userid User ID 440 * 441 * @throws \Exception 442 * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more. 443 * @see gradebookservices::save_grade_item($gradeitem, $score, $userid) 444 */ 445 public static function save_score($gradeitem, $score, $userid) { 446 $service = new gradebookservices(); 447 $service->save_grade_item($gradeitem, $score, $userid); 448 } 449 450 /** 451 * Saves a score received from the LTI tool. 452 * 453 * @param object $gradeitem Grade Item record 454 * @param object $score Result object 455 * @param int $userid User ID 456 * 457 * @throws \Exception 458 */ 459 public function save_grade_item($gradeitem, $score, $userid) { 460 global $DB, $CFG; 461 $source = 'mod' . $this->get_component_id(); 462 if ($DB->get_record('user', array('id' => $userid)) === false) { 463 throw new \Exception(null, 400); 464 } 465 require_once($CFG->libdir . '/gradelib.php'); 466 $finalgrade = null; 467 $timemodified = null; 468 if (isset($score->scoreGiven)) { 469 $finalgrade = grade_floatval($score->scoreGiven); 470 $max = 1; 471 if (isset($score->scoreMaximum)) { 472 $max = $score->scoreMaximum; 473 } 474 if (!is_null($max) && grade_floats_different($max, $gradeitem->grademax) && grade_floats_different($max, 0.0)) { 475 // Rescale to match the grade item maximum. 476 $finalgrade = grade_floatval($finalgrade * $gradeitem->grademax / $max); 477 } 478 if (isset($score->timestamp)) { 479 $timemodified = strtotime($score->timestamp); 480 } else { 481 $timemodified = time(); 482 } 483 } 484 $feedbackformat = FORMAT_MOODLE; 485 $feedback = null; 486 if (!empty($score->comment)) { 487 $feedback = $score->comment; 488 $feedbackformat = FORMAT_PLAIN; 489 } 490 491 if ($gradeitem->is_manual_item()) { 492 $result = $gradeitem->update_final_grade($userid, $finalgrade, null, $feedback, FORMAT_PLAIN, null, $timemodified); 493 } else { 494 if (!$grade = \grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $userid))) { 495 $grade = new \grade_grade(); 496 $grade->userid = $userid; 497 $grade->itemid = $gradeitem->id; 498 } 499 $grade->rawgrademax = $score->scoreMaximum; 500 $grade->timemodified = $timemodified; 501 $grade->feedbackformat = $feedbackformat; 502 $grade->feedback = $feedback; 503 $grade->rawgrade = $finalgrade; 504 $status = grade_update($source, $gradeitem->courseid, 505 $gradeitem->itemtype, $gradeitem->itemmodule, 506 $gradeitem->iteminstance, $gradeitem->itemnumber, $grade); 507 508 $result = ($status == GRADE_UPDATE_OK); 509 } 510 if (!$result) { 511 debugging("failed to save score for item ".$gradeitem->id." and user ".$grade->userid); 512 throw new \Exception(null, 500); 513 } 514 515 } 516 517 /** 518 * Get the json object representation of the grade item 519 * 520 * @param object $item Grade Item record 521 * @param string $endpoint Endpoint for lineitems container request 522 * @param string $typeid 523 * 524 * @return object 525 */ 526 public static function item_for_json($item, $endpoint, $typeid) { 527 528 $lineitem = new \stdClass(); 529 if (is_null($typeid)) { 530 $typeidstring = ""; 531 } else { 532 $typeidstring = "?type_id={$typeid}"; 533 } 534 $lineitem->id = "{$endpoint}/{$item->id}/lineitem" . $typeidstring; 535 $lineitem->label = $item->itemname; 536 $lineitem->scoreMaximum = floatval($item->grademax); 537 $gbs = self::find_ltiservice_gradebookservice_for_lineitem($item->id); 538 if ($gbs) { 539 $lineitem->resourceId = (!empty($gbs->resourceid)) ? $gbs->resourceid : ''; 540 $lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : ''; 541 if (isset($gbs->ltilinkid)) { 542 $lineitem->resourceLinkId = strval($gbs->ltilinkid); 543 $lineitem->ltiLinkId = strval($gbs->ltilinkid); 544 } 545 } else { 546 $lineitem->tag = ''; 547 if (isset($item->iteminstance)) { 548 $lineitem->resourceLinkId = strval($item->iteminstance); 549 $lineitem->ltiLinkId = strval($item->iteminstance); 550 } 551 } 552 553 return $lineitem; 554 555 } 556 557 /** 558 * Get the object matching the JSON representation of the result. 559 * 560 * @param object $grade Grade record 561 * @param string $endpoint Endpoint for lineitem 562 * @param int $typeid The id of the type to include in the result url. 563 * 564 * @return object 565 */ 566 public static function result_for_json($grade, $endpoint, $typeid) { 567 568 if (is_null($typeid)) { 569 $id = "{$endpoint}/results?user_id={$grade->userid}"; 570 } else { 571 $id = "{$endpoint}/results?type_id={$typeid}&user_id={$grade->userid}"; 572 } 573 $result = new \stdClass(); 574 $result->id = $id; 575 $result->userId = $grade->userid; 576 if (!empty($grade->finalgrade)) { 577 $result->resultScore = floatval($grade->finalgrade); 578 $result->resultMaximum = floatval($grade->rawgrademax); 579 if (!empty($grade->feedback)) { 580 $result->comment = $grade->feedback; 581 } 582 if (is_null($typeid)) { 583 $result->scoreOf = $endpoint; 584 } else { 585 $result->scoreOf = "{$endpoint}?type_id={$typeid}"; 586 } 587 $result->timestamp = date('c', $grade->timemodified); 588 } 589 return $result; 590 } 591 592 /** 593 * Check if an LTI id is valid. 594 * 595 * @param string $linkid The lti id 596 * @param string $course The course 597 * @param string $toolproxy The tool proxy id 598 * 599 * @return boolean 600 */ 601 public static function check_lti_id($linkid, $course, $toolproxy) { 602 global $DB; 603 // Check if lti type is zero or not (comes from a backup). 604 $sqlparams1 = array(); 605 $sqlparams1['linkid'] = $linkid; 606 $sqlparams1['course'] = $course; 607 $ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course)); 608 if ($ltiactivity->typeid == 0) { 609 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course); 610 if (!$tool) { 611 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course); 612 } 613 return (($tool) && ($toolproxy == $tool->toolproxyid)); 614 } else { 615 $sqlparams2 = array(); 616 $sqlparams2['linkid'] = $linkid; 617 $sqlparams2['course'] = $course; 618 $sqlparams2['toolproxy'] = $toolproxy; 619 $sql = 'SELECT lti.* 620 FROM {lti} lti 621 INNER JOIN {lti_types} typ ON lti.typeid = typ.id 622 WHERE lti.id = ? 623 AND lti.course = ? 624 AND typ.toolproxyid = ?'; 625 return $DB->record_exists_sql($sql, $sqlparams2); 626 } 627 } 628 629 /** 630 * Check if an LTI id is valid when we are in a LTI 1.x case 631 * 632 * @param string $linkid The lti id 633 * @param string $course The course 634 * @param string $typeid The lti type id 635 * 636 * @return boolean 637 */ 638 public static function check_lti_1x_id($linkid, $course, $typeid) { 639 global $DB; 640 // Check if lti type is zero or not (comes from a backup). 641 $sqlparams1 = array(); 642 $sqlparams1['linkid'] = $linkid; 643 $sqlparams1['course'] = $course; 644 $ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course)); 645 if ($ltiactivity) { 646 if ($ltiactivity->typeid == 0) { 647 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course); 648 if (!$tool) { 649 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course); 650 } 651 return (($tool) && ($typeid == $tool->id)); 652 } else { 653 $sqlparams2 = array(); 654 $sqlparams2['linkid'] = $linkid; 655 $sqlparams2['course'] = $course; 656 $sqlparams2['typeid'] = $typeid; 657 $sql = 'SELECT lti.* 658 FROM {lti} lti 659 INNER JOIN {lti_types} typ ON lti.typeid = typ.id 660 WHERE lti.id = ? 661 AND lti.course = ? 662 AND typ.id = ?'; 663 return $DB->record_exists_sql($sql, $sqlparams2); 664 } 665 } else { 666 return false; 667 } 668 } 669 670 /** 671 * Updates the tag and resourceid values for a grade item coupled to an lti link instance. 672 * 673 * @param object $ltiinstance The lti instance to which the grade item is coupled to 674 * @param string|null $resourceid The resourceid to apply to the lineitem. If empty string which will be stored as null. 675 * @param string|null $tag The tag to apply to the lineitem. If empty string which will be stored as null. 676 * 677 */ 678 public static function update_coupled_gradebookservices(object $ltiinstance, ?string $resourceid, ?string $tag) : void { 679 global $DB; 680 681 if ($ltiinstance && $ltiinstance->typeid) { 682 $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $ltiinstance->id)); 683 if ($gradeitem) { 684 $resourceid = (isset($resourceid) && empty(trim($resourceid))) ? null : $resourceid; 685 $tag = (isset($tag) && empty(trim($tag))) ? null : $tag; 686 $gbs = self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); 687 if ($gbs) { 688 $gbs->resourceid = $resourceid; 689 $gbs->tag = $tag; 690 $DB->update_record('ltiservice_gradebookservices', $gbs); 691 } else { 692 $baseurl = lti_get_type_type_config($ltiinstance->typeid)->lti_toolurl; 693 $DB->insert_record('ltiservice_gradebookservices', (object)array( 694 'gradeitemid' => $gradeitem->id, 695 'courseid' => $gradeitem->courseid, 696 'typeid' => $ltiinstance->typeid, 697 'baseurl' => $baseurl, 698 'ltilinkid' => $ltiinstance->id, 699 'resourceid' => $resourceid, 700 'tag' => $tag 701 )); 702 } 703 } 704 } 705 } 706 707 /** 708 * Called when a new LTI Instance is added. 709 * 710 * @param object $lti LTI Instance. 711 */ 712 public function instance_added(object $lti): void { 713 self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null); 714 } 715 716 /** 717 * Called when a new LTI Instance is updated. 718 * 719 * @param object $lti LTI Instance. 720 */ 721 public function instance_updated(object $lti): void { 722 self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null); 723 } 724 725 /** 726 * Set the form data when displaying the LTI Instance form. 727 * 728 * @param object $defaultvalues Default form values. 729 */ 730 public function set_instance_form_values(object $defaultvalues): void { 731 $defaultvalues->lineitemresourceid = ''; 732 $defaultvalues->lineitemtag = ''; 733 if (is_object($defaultvalues) && $defaultvalues->instance) { 734 $gbs = self::find_ltiservice_gradebookservice_for_lti($defaultvalues->instance); 735 if ($gbs) { 736 $defaultvalues->lineitemresourceid = $gbs->resourceid; 737 $defaultvalues->lineitemtag = $gbs->tag; 738 } 739 } 740 } 741 742 /** 743 * Deletes orphaned rows from the 'ltiservice_gradebookservices' table. 744 * 745 * Sometimes, if a gradebook entry is deleted and it was a lineitem 746 * the row in the table ltiservice_gradebookservices can become an orphan 747 * This method will clean these orphans. It will happens based on a task 748 * because it is not urgent and we don't want to slow the service 749 */ 750 public static function delete_orphans_ltiservice_gradebookservices_rows() { 751 global $DB; 752 753 $sql = "DELETE 754 FROM {ltiservice_gradebookservices} 755 WHERE gradeitemid NOT IN (SELECT id 756 FROM {grade_items} gi)"; 757 $DB->execute($sql); 758 } 759 760 /** 761 * Check if a user can be graded in a course 762 * 763 * @param int $courseid The course 764 * @param int $userid The user 765 * @return bool 766 */ 767 public static function is_user_gradable_in_course($courseid, $userid) { 768 global $CFG; 769 770 $gradableuser = false; 771 $coursecontext = \context_course::instance($courseid); 772 if (is_enrolled($coursecontext, $userid, '', false)) { 773 $roles = get_user_roles($coursecontext, $userid); 774 $gradebookroles = explode(',', $CFG->gradebookroles); 775 foreach ($roles as $role) { 776 foreach ($gradebookroles as $gradebookrole) { 777 if ($role->roleid === $gradebookrole) { 778 $gradableuser = true; 779 } 780 } 781 } 782 } 783 784 return $gradableuser; 785 } 786 787 /** 788 * Find the right element in the ltiservice_gradebookservice table for an lti instance 789 * 790 * @param string $instanceid The LTI module instance id 791 * @return object gradebookservice for this line item 792 */ 793 public static function find_ltiservice_gradebookservice_for_lti($instanceid) { 794 global $DB; 795 796 if ($instanceid) { 797 $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $instanceid)); 798 if ($gradeitem) { 799 return self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); 800 } 801 } 802 } 803 804 /** 805 * Find the right element in the ltiservice_gradebookservice table for a lineitem 806 * 807 * @param string $lineitemid The lineitem (gradeitem) id 808 * @return object gradebookservice if it exists 809 */ 810 public static function find_ltiservice_gradebookservice_for_lineitem($lineitemid) { 811 global $DB; 812 if ($lineitemid) { 813 return $DB->get_record('ltiservice_gradebookservices', 814 array('gradeitemid' => $lineitemid)); 815 } 816 } 817 818 /** 819 * Validates specific ISO 8601 format of the timestamps. 820 * 821 * @param string $date The timestamp to check. 822 * @return boolean true or false if the date matches the format. 823 * 824 */ 825 public static function validate_iso8601_date($date) { 826 if (preg_match('/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])' . 827 '(\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])))' . 828 '([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)' . 829 '?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/', $date) > 0) { 830 return true; 831 } else { 832 return false; 833 } 834 } 835 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body