See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 * Adds form elements for gradebook sync add/edit page. 117 * 118 * @param \MoodleQuickForm $mform Moodle quickform object definition 119 */ 120 public function get_configuration_options(&$mform) { 121 122 $selectelementname = 'ltiservice_gradesynchronization'; 123 $identifier = 'grade_synchronization'; 124 $options = [ 125 get_string('nevergs', $this->get_component_id()), 126 get_string('partialgs', $this->get_component_id()), 127 get_string('alwaysgs', $this->get_component_id()) 128 ]; 129 130 $mform->addElement('select', $selectelementname, get_string($identifier, $this->get_component_id()), $options); 131 $mform->setType($selectelementname, 'int'); 132 $mform->setDefault($selectelementname, 0); 133 $mform->addHelpButton($selectelementname, $identifier, $this->get_component_id()); 134 } 135 136 /** 137 * Return an array of key/values to add to the launch parameters. 138 * 139 * @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'. 140 * @param string $courseid the course id. 141 * @param object $user The user id. 142 * @param string $typeid The tool lti type id. 143 * @param string $modlti The id of the lti activity. 144 * 145 * The type is passed to check the configuration 146 * and not return parameters for services not used. 147 * 148 * @return array of key/value pairs to add as launch parameters. 149 */ 150 public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) { 151 global $DB; 152 $launchparameters = array(); 153 $this->set_type(lti_get_type($typeid)); 154 $this->set_typeconfig(lti_get_type_config($typeid)); 155 // Only inject parameters if the service is enabled for this tool. 156 if (isset($this->get_typeconfig()['ltiservice_gradesynchronization'])) { 157 if ($this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_READ || 158 $this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_FULL) { 159 // Check for used in context is only needed because there is no explicit site tool - course relation. 160 if ($this->is_allowed_in_context($typeid, $courseid)) { 161 $id = null; 162 if (!is_null($modlti)) { 163 $conditions = array('courseid' => $courseid, 'itemtype' => 'mod', 164 'itemmodule' => 'lti', 'iteminstance' => $modlti); 165 166 $coupledlineitems = $DB->get_records('grade_items', $conditions); 167 $conditionsgbs = array('courseid' => $courseid, 'ltilinkid' => $modlti); 168 $lineitemsgbs = $DB->get_records('ltiservice_gradebookservices', $conditionsgbs); 169 // If a link has more that one attached grade items, per spec we do not populate line item url. 170 if (count($lineitemsgbs) == 1) { 171 $id = reset($lineitemsgbs)->gradeitemid; 172 } 173 if (count($lineitemsgbs) < 2 && count($coupledlineitems) == 1) { 174 $coupledid = reset($coupledlineitems)->id; 175 if (!is_null($id) && $id != $coupledid) { 176 $id = null; 177 } else { 178 $id = $coupledid; 179 } 180 } 181 } 182 $launchparameters['gradebookservices_scope'] = implode(',', $this->get_permitted_scopes()); 183 $launchparameters['lineitems_url'] = '$LineItems.url'; 184 if (!is_null($id)) { 185 $launchparameters['lineitem_url'] = '$LineItem.url'; 186 } 187 } 188 } 189 } 190 return $launchparameters; 191 } 192 193 /** 194 * Fetch the lineitem instances. 195 * 196 * @param string $courseid ID of course 197 * @param string $resourceid Resource identifier used for filtering, may be null 198 * @param string $ltilinkid Resource Link identifier used for filtering, may be null 199 * @param string $tag 200 * @param int $limitfrom Offset for the first line item to include in a paged set 201 * @param int $limitnum Maximum number of line items to include in the paged set 202 * @param string $typeid 203 * 204 * @return array 205 * @throws \Exception 206 */ 207 public function get_lineitems($courseid, $resourceid, $ltilinkid, $tag, $limitfrom, $limitnum, $typeid) { 208 global $DB; 209 210 // Select all lti potential linetiems in site. 211 $params = array('courseid' => $courseid); 212 213 $sql = "SELECT i.* 214 FROM {grade_items} i 215 WHERE (i.courseid = :courseid) 216 ORDER BY i.id"; 217 $lineitems = $DB->get_records_sql($sql, $params); 218 219 // For each one, check the gbs id, and check that toolproxy matches. If so, add the 220 // tag to the result and add it to a final results array. 221 $lineitemstoreturn = array(); 222 $lineitemsandtotalcount = array(); 223 if ($lineitems) { 224 foreach ($lineitems as $lineitem) { 225 $gbs = $this->find_ltiservice_gradebookservice_for_lineitem($lineitem->id); 226 if ($gbs && (!isset($tag) || (isset($tag) && $gbs->tag == $tag)) 227 && (!isset($ltilinkid) || (isset($ltilinkid) && $gbs->ltilinkid == $ltilinkid)) 228 && (!isset($resourceid) || (isset($resourceid) && $gbs->resourceid == $resourceid))) { 229 if (is_null($typeid)) { 230 if ($this->get_tool_proxy()->id == $gbs->toolproxyid) { 231 array_push($lineitemstoreturn, $lineitem); 232 } 233 } else { 234 if ($typeid == $gbs->typeid) { 235 array_push($lineitemstoreturn, $lineitem); 236 } 237 } 238 } else if (($lineitem->itemtype == 'mod' && $lineitem->itemmodule == 'lti' 239 && !isset($resourceid) && !isset($tag) 240 && (!isset($ltilinkid) || (isset($ltilinkid) 241 && $lineitem->iteminstance == $ltilinkid)))) { 242 // We will need to check if the activity related belongs to our tool proxy. 243 $ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance)); 244 if (($ltiactivity) && (isset($ltiactivity->typeid))) { 245 if ($ltiactivity->typeid != 0) { 246 $tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid)); 247 } else { 248 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid); 249 if (!$tool) { 250 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid); 251 } 252 } 253 if (is_null($typeid)) { 254 if (($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid)) { 255 array_push($lineitemstoreturn, $lineitem); 256 } 257 } else { 258 if (($tool) && ($tool->id == $typeid)) { 259 array_push($lineitemstoreturn, $lineitem); 260 } 261 } 262 } 263 } 264 } 265 $lineitemsandtotalcount = array(); 266 array_push($lineitemsandtotalcount, count($lineitemstoreturn)); 267 // Return the right array based in the paging parameters limit and from. 268 if (($limitnum) && ($limitnum > 0)) { 269 $lineitemstoreturn = array_slice($lineitemstoreturn, $limitfrom, $limitnum); 270 } 271 array_push($lineitemsandtotalcount, $lineitemstoreturn); 272 } 273 return $lineitemsandtotalcount; 274 } 275 276 /** 277 * Fetch a lineitem instance. 278 * 279 * Returns the lineitem instance if found, otherwise false. 280 * 281 * @param string $courseid ID of course 282 * @param string $itemid ID of lineitem 283 * @param string $typeid 284 * 285 * @return \ltiservice_gradebookservices\local\resources\lineitem|bool 286 */ 287 public function get_lineitem($courseid, $itemid, $typeid) { 288 global $DB, $CFG; 289 290 require_once($CFG->libdir . '/gradelib.php'); 291 $lineitem = \grade_item::fetch(array('id' => $itemid)); 292 if ($lineitem) { 293 $gbs = $this->find_ltiservice_gradebookservice_for_lineitem($itemid); 294 if (!$gbs) { 295 // We will need to check if the activity related belongs to our tool proxy. 296 $ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance)); 297 if (($ltiactivity) && (isset($ltiactivity->typeid))) { 298 if ($ltiactivity->typeid != 0) { 299 $tool = $DB->get_record('lti_types', array('id' => $ltiactivity->typeid)); 300 } else { 301 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $courseid); 302 if (!$tool) { 303 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $courseid); 304 } 305 } 306 if (is_null($typeid)) { 307 if (!(($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid))) { 308 return false; 309 } 310 } else { 311 if (!(($tool) && ($tool->id == $typeid))) { 312 return false; 313 } 314 } 315 } else { 316 return false; 317 } 318 } 319 } 320 return $lineitem; 321 } 322 323 /** 324 * Adds a decoupled (standalone) line item. 325 * Decoupled line items are not directly attached to 326 * an lti instance activity. They are recorded in 327 * the gradebook as manual activities and the 328 * gradebookservices is used to associate that manual column 329 * with the tool in addition to storing the LTI related 330 * metadata (resource id, tag). 331 * 332 * @param string $courseid ID of course 333 * @param string $label label of lineitem 334 * @param float $maximumscore maximum score of lineitem 335 * @param string $baseurl 336 * @param int|null $ltilinkid id of lti instance this line item is associated with 337 * @param string|null $resourceid resource id of lineitem 338 * @param string|null $tag tag of lineitem 339 * @param int $typeid lti type to which this line item is associated with 340 * @param int|null $toolproxyid lti2 tool proxy to which this lineitem is associated to 341 * 342 * @return int id of the created gradeitem 343 */ 344 public function add_standalone_lineitem(string $courseid, string $label, float $maximumscore, 345 string $baseurl, ?int $ltilinkid, ?string $resourceid, ?string $tag, int $typeid, 346 int $toolproxyid = null) : int { 347 global $DB; 348 $params = array(); 349 $params['itemname'] = $label; 350 $params['gradetype'] = GRADE_TYPE_VALUE; 351 $params['grademax'] = $maximumscore; 352 $params['grademin'] = 0; 353 $item = new \grade_item(array('id' => 0, 'courseid' => $courseid)); 354 \grade_item::set_properties($item, $params); 355 $item->itemtype = 'manual'; 356 $item->grademax = $maximumscore; 357 $id = $item->insert('mod/ltiservice_gradebookservices'); 358 $DB->insert_record('ltiservice_gradebookservices', (object)array( 359 'gradeitemid' => $id, 360 'courseid' => $courseid, 361 'toolproxyid' => $toolproxyid, 362 'typeid' => $typeid, 363 'baseurl' => $baseurl, 364 'ltilinkid' => $ltilinkid, 365 'resourceid' => $resourceid, 366 'tag' => $tag 367 )); 368 return $id; 369 } 370 371 /** 372 * Set a grade item. 373 * 374 * @param object $gradeitem Grade Item record 375 * @param object $score Result object 376 * @param int $userid User ID 377 * 378 * @throws \Exception 379 * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more. 380 * @see gradebookservices::save_grade_item($gradeitem, $score, $userid) 381 */ 382 public static function save_score($gradeitem, $score, $userid) { 383 $service = new gradebookservices(); 384 $service->save_grade_item($gradeitem, $score, $userid); 385 } 386 387 /** 388 * Saves a score received from the LTI tool. 389 * 390 * @param object $gradeitem Grade Item record 391 * @param object $score Result object 392 * @param int $userid User ID 393 * 394 * @throws \Exception 395 */ 396 public function save_grade_item($gradeitem, $score, $userid) { 397 global $DB, $CFG; 398 $source = 'mod' . $this->get_component_id(); 399 if ($DB->get_record('user', array('id' => $userid)) === false) { 400 throw new \Exception(null, 400); 401 } 402 require_once($CFG->libdir . '/gradelib.php'); 403 $finalgrade = null; 404 $timemodified = null; 405 if (isset($score->scoreGiven)) { 406 $finalgrade = grade_floatval($score->scoreGiven); 407 $max = 1; 408 if (isset($score->scoreMaximum)) { 409 $max = $score->scoreMaximum; 410 } 411 if (!is_null($max) && grade_floats_different($max, $gradeitem->grademax) && grade_floats_different($max, 0.0)) { 412 // Rescale to match the grade item maximum. 413 $finalgrade = grade_floatval($finalgrade * $gradeitem->grademax / $max); 414 } 415 if (isset($score->timestamp)) { 416 $timemodified = strtotime($score->timestamp); 417 } else { 418 $timemodified = time(); 419 } 420 } 421 $feedbackformat = FORMAT_MOODLE; 422 $feedback = null; 423 if (!empty($score->comment)) { 424 $feedback = $score->comment; 425 $feedbackformat = FORMAT_PLAIN; 426 } 427 428 if ($gradeitem->is_manual_item()) { 429 $result = $gradeitem->update_final_grade($userid, $finalgrade, null, $feedback, FORMAT_PLAIN, null, $timemodified); 430 } else { 431 if (!$grade = \grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $userid))) { 432 $grade = new \grade_grade(); 433 $grade->userid = $userid; 434 $grade->itemid = $gradeitem->id; 435 } 436 $grade->rawgrademax = $score->scoreMaximum; 437 $grade->timemodified = $timemodified; 438 $grade->feedbackformat = $feedbackformat; 439 $grade->feedback = $feedback; 440 $grade->rawgrade = $finalgrade; 441 $status = grade_update($source, $gradeitem->courseid, 442 $gradeitem->itemtype, $gradeitem->itemmodule, 443 $gradeitem->iteminstance, $gradeitem->itemnumber, $grade); 444 445 $result = ($status == GRADE_UPDATE_OK); 446 } 447 if (!$result) { 448 debugging("failed to save score for item ".$gradeitem->id." and user ".$grade->userid); 449 throw new \Exception(null, 500); 450 } 451 452 } 453 454 /** 455 * Get the json object representation of the grade item 456 * 457 * @param object $item Grade Item record 458 * @param string $endpoint Endpoint for lineitems container request 459 * @param string $typeid 460 * 461 * @return object 462 */ 463 public static function item_for_json($item, $endpoint, $typeid) { 464 465 $lineitem = new \stdClass(); 466 if (is_null($typeid)) { 467 $typeidstring = ""; 468 } else { 469 $typeidstring = "?type_id={$typeid}"; 470 } 471 $lineitem->id = "{$endpoint}/{$item->id}/lineitem" . $typeidstring; 472 $lineitem->label = $item->itemname; 473 $lineitem->scoreMaximum = floatval($item->grademax); 474 $gbs = self::find_ltiservice_gradebookservice_for_lineitem($item->id); 475 if ($gbs) { 476 $lineitem->resourceId = (!empty($gbs->resourceid)) ? $gbs->resourceid : ''; 477 $lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : ''; 478 if (isset($gbs->ltilinkid)) { 479 $lineitem->resourceLinkId = strval($gbs->ltilinkid); 480 $lineitem->ltiLinkId = strval($gbs->ltilinkid); 481 } 482 } else { 483 $lineitem->tag = ''; 484 if (isset($item->iteminstance)) { 485 $lineitem->resourceLinkId = strval($item->iteminstance); 486 $lineitem->ltiLinkId = strval($item->iteminstance); 487 } 488 } 489 490 return $lineitem; 491 492 } 493 494 /** 495 * Get the object matching the JSON representation of the result. 496 * 497 * @param object $grade Grade record 498 * @param string $endpoint Endpoint for lineitem 499 * @param int $typeid The id of the type to include in the result url. 500 * 501 * @return object 502 */ 503 public static function result_for_json($grade, $endpoint, $typeid) { 504 505 if (is_null($typeid)) { 506 $id = "{$endpoint}/results?user_id={$grade->userid}"; 507 } else { 508 $id = "{$endpoint}/results?type_id={$typeid}&user_id={$grade->userid}"; 509 } 510 $result = new \stdClass(); 511 $result->id = $id; 512 $result->userId = $grade->userid; 513 if (!empty($grade->finalgrade)) { 514 $result->resultScore = floatval($grade->finalgrade); 515 $result->resultMaximum = floatval($grade->rawgrademax); 516 if (!empty($grade->feedback)) { 517 $result->comment = $grade->feedback; 518 } 519 if (is_null($typeid)) { 520 $result->scoreOf = $endpoint; 521 } else { 522 $result->scoreOf = "{$endpoint}?type_id={$typeid}"; 523 } 524 $result->timestamp = date('c', $grade->timemodified); 525 } 526 return $result; 527 } 528 529 /** 530 * Check if an LTI id is valid. 531 * 532 * @param string $linkid The lti id 533 * @param string $course The course 534 * @param string $toolproxy The tool proxy id 535 * 536 * @return boolean 537 */ 538 public static function check_lti_id($linkid, $course, $toolproxy) { 539 global $DB; 540 // Check if lti type is zero or not (comes from a backup). 541 $sqlparams1 = array(); 542 $sqlparams1['linkid'] = $linkid; 543 $sqlparams1['course'] = $course; 544 $ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course)); 545 if ($ltiactivity->typeid == 0) { 546 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course); 547 if (!$tool) { 548 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course); 549 } 550 return (($tool) && ($toolproxy == $tool->toolproxyid)); 551 } else { 552 $sqlparams2 = array(); 553 $sqlparams2['linkid'] = $linkid; 554 $sqlparams2['course'] = $course; 555 $sqlparams2['toolproxy'] = $toolproxy; 556 $sql = 'SELECT lti.* 557 FROM {lti} lti 558 INNER JOIN {lti_types} typ ON lti.typeid = typ.id 559 WHERE lti.id = ? 560 AND lti.course = ? 561 AND typ.toolproxyid = ?'; 562 return $DB->record_exists_sql($sql, $sqlparams2); 563 } 564 } 565 566 /** 567 * Check if an LTI id is valid when we are in a LTI 1.x case 568 * 569 * @param string $linkid The lti id 570 * @param string $course The course 571 * @param string $typeid The lti type id 572 * 573 * @return boolean 574 */ 575 public static function check_lti_1x_id($linkid, $course, $typeid) { 576 global $DB; 577 // Check if lti type is zero or not (comes from a backup). 578 $sqlparams1 = array(); 579 $sqlparams1['linkid'] = $linkid; 580 $sqlparams1['course'] = $course; 581 $ltiactivity = $DB->get_record('lti', array('id' => $linkid, 'course' => $course)); 582 if ($ltiactivity) { 583 if ($ltiactivity->typeid == 0) { 584 $tool = lti_get_tool_by_url_match($ltiactivity->toolurl, $course); 585 if (!$tool) { 586 $tool = lti_get_tool_by_url_match($ltiactivity->securetoolurl, $course); 587 } 588 return (($tool) && ($typeid == $tool->id)); 589 } else { 590 $sqlparams2 = array(); 591 $sqlparams2['linkid'] = $linkid; 592 $sqlparams2['course'] = $course; 593 $sqlparams2['typeid'] = $typeid; 594 $sql = 'SELECT lti.* 595 FROM {lti} lti 596 INNER JOIN {lti_types} typ ON lti.typeid = typ.id 597 WHERE lti.id = ? 598 AND lti.course = ? 599 AND typ.id = ?'; 600 return $DB->record_exists_sql($sql, $sqlparams2); 601 } 602 } else { 603 return false; 604 } 605 } 606 607 /** 608 * Updates the tag and resourceid values for a grade item coupled to an lti link instance. 609 * 610 * @param object $ltiinstance The lti instance to which the grade item is coupled to 611 * @param string|null $resourceid The resourceid to apply to the lineitem. If empty string which will be stored as null. 612 * @param string|null $tag The tag to apply to the lineitem. If empty string which will be stored as null. 613 * 614 */ 615 public static function update_coupled_gradebookservices(object $ltiinstance, ?string $resourceid, ?string $tag) : void { 616 global $DB; 617 618 if ($ltiinstance && $ltiinstance->typeid) { 619 $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $ltiinstance->id)); 620 if ($gradeitem) { 621 $resourceid = (isset($resourceid) && empty(trim($resourceid))) ? null : $resourceid; 622 $tag = (isset($tag) && empty(trim($tag))) ? null : $tag; 623 $gbs = self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); 624 if ($gbs) { 625 $gbs->resourceid = $resourceid; 626 $gbs->tag = $tag; 627 $DB->update_record('ltiservice_gradebookservices', $gbs); 628 } else { 629 $baseurl = lti_get_type_type_config($ltiinstance->typeid)->lti_toolurl; 630 $DB->insert_record('ltiservice_gradebookservices', (object)array( 631 'gradeitemid' => $gradeitem->id, 632 'courseid' => $gradeitem->courseid, 633 'typeid' => $ltiinstance->typeid, 634 'baseurl' => $baseurl, 635 'ltilinkid' => $ltiinstance->id, 636 'resourceid' => $resourceid, 637 'tag' => $tag 638 )); 639 } 640 } 641 } 642 } 643 644 /** 645 * Called when a new LTI Instance is added. 646 * 647 * @param object $lti LTI Instance. 648 */ 649 public function instance_added(object $lti): void { 650 self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null); 651 } 652 653 /** 654 * Called when a new LTI Instance is updated. 655 * 656 * @param object $lti LTI Instance. 657 */ 658 public function instance_updated(object $lti): void { 659 self::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? null, $lti->lineitemtag ?? null); 660 } 661 662 /** 663 * Set the form data when displaying the LTI Instance form. 664 * 665 * @param object $defaultvalues Default form values. 666 */ 667 public function set_instance_form_values(object $defaultvalues): void { 668 $defaultvalues->lineitemresourceid = ''; 669 $defaultvalues->lineitemtag = ''; 670 if (is_object($defaultvalues) && $defaultvalues->instance) { 671 $gbs = self::find_ltiservice_gradebookservice_for_lti($defaultvalues->instance); 672 if ($gbs) { 673 $defaultvalues->lineitemresourceid = $gbs->resourceid; 674 $defaultvalues->lineitemtag = $gbs->tag; 675 } 676 } 677 } 678 679 /** 680 * Deletes orphaned rows from the 'ltiservice_gradebookservices' table. 681 * 682 * Sometimes, if a gradebook entry is deleted and it was a lineitem 683 * the row in the table ltiservice_gradebookservices can become an orphan 684 * This method will clean these orphans. It will happens based on a task 685 * because it is not urgent and we don't want to slow the service 686 */ 687 public static function delete_orphans_ltiservice_gradebookservices_rows() { 688 global $DB; 689 690 $sql = "DELETE 691 FROM {ltiservice_gradebookservices} 692 WHERE gradeitemid NOT IN (SELECT id 693 FROM {grade_items} gi)"; 694 $DB->execute($sql); 695 } 696 697 /** 698 * Check if a user can be graded in a course 699 * 700 * @param int $courseid The course 701 * @param int $userid The user 702 * @return bool 703 */ 704 public static function is_user_gradable_in_course($courseid, $userid) { 705 global $CFG; 706 707 $gradableuser = false; 708 $coursecontext = \context_course::instance($courseid); 709 if (is_enrolled($coursecontext, $userid, '', false)) { 710 $roles = get_user_roles($coursecontext, $userid); 711 $gradebookroles = explode(',', $CFG->gradebookroles); 712 foreach ($roles as $role) { 713 foreach ($gradebookroles as $gradebookrole) { 714 if ($role->roleid = $gradebookrole) { 715 $gradableuser = true; 716 } 717 } 718 } 719 } 720 721 return $gradableuser; 722 } 723 724 /** 725 * Find the right element in the ltiservice_gradebookservice table for an lti instance 726 * 727 * @param string $instanceid The LTI module instance id 728 * @return object gradebookservice for this line item 729 */ 730 public static function find_ltiservice_gradebookservice_for_lti($instanceid) { 731 global $DB; 732 733 if ($instanceid) { 734 $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $instanceid)); 735 if ($gradeitem) { 736 return self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id); 737 } 738 } 739 } 740 741 /** 742 * Find the right element in the ltiservice_gradebookservice table for a lineitem 743 * 744 * @param string $lineitemid The lineitem (gradeitem) id 745 * @return object gradebookservice if it exists 746 */ 747 public static function find_ltiservice_gradebookservice_for_lineitem($lineitemid) { 748 global $DB; 749 if ($lineitemid) { 750 return $DB->get_record('ltiservice_gradebookservices', 751 array('gradeitemid' => $lineitemid)); 752 } 753 } 754 755 /** 756 * Validates specific ISO 8601 format of the timestamps. 757 * 758 * @param string $date The timestamp to check. 759 * @return boolean true or false if the date matches the format. 760 * 761 */ 762 public static function validate_iso8601_date($date) { 763 if (preg_match('/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])' . 764 '(\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])))' . 765 '([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)' . 766 '?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/', $date) > 0) { 767 return true; 768 } else { 769 return false; 770 } 771 } 772 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body