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 LISResult container resource 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\resources; 27 28 use ltiservice_gradebookservices\local\service\gradebookservices; 29 use mod_lti\local\ltiservice\resource_base; 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 /** 34 * A resource implementing LISResult container. 35 * 36 * @package ltiservice_gradebookservices 37 * @copyright 2017 Cengage Learning http://www.cengage.com 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class scores extends resource_base { 41 42 /** 43 * Class constructor. 44 * 45 * @param \ltiservice_gradebookservices\local\service\gradebookservices $service Service instance 46 */ 47 public function __construct($service) { 48 49 parent::__construct($service); 50 $this->id = 'Score.collection'; 51 $this->template = '/{context_id}/lineitems/{item_id}/lineitem/scores'; 52 $this->variables[] = 'Scores.url'; 53 $this->formats[] = 'application/vnd.ims.lis.v1.scorecontainer+json'; 54 $this->formats[] = 'application/vnd.ims.lis.v1.score+json'; 55 $this->methods[] = 'POST'; 56 57 } 58 59 /** 60 * Execute the request for this resource. 61 * 62 * @param \mod_lti\local\ltiservice\response $response Response object for this request. 63 */ 64 public function execute($response) { 65 global $CFG, $DB; 66 67 $params = $this->parse_template(); 68 $contextid = $params['context_id']; 69 $itemid = $params['item_id']; 70 71 // GET is disabled by the moment, but we have the code ready 72 // for a future implementation. 73 74 $isget = $response->get_request_method() === 'GET'; 75 if ($isget) { 76 $contenttype = $response->get_accept(); 77 } else { 78 $contenttype = $response->get_content_type(); 79 } 80 $container = empty($contenttype) || ($contenttype === $this->formats[0]); 81 // We will receive typeid when working with LTI 1.x, if not the we are in LTI 2. 82 $typeid = optional_param('type_id', null, PARAM_ALPHANUM); 83 84 $scope = gradebookservices::SCOPE_GRADEBOOKSERVICES_SCORE; 85 86 try { 87 if (!$this->check_tool($typeid, $response->get_request_data(), array($scope))) { 88 throw new \Exception(null, 401); 89 } 90 $typeid = $this->get_service()->get_type()->id; 91 if (empty($contextid) || !($container ^ ($response->get_request_method() === self::HTTP_POST)) || 92 (!empty($contenttype) && !in_array($contenttype, $this->formats))) { 93 throw new \Exception('No context or unsupported content type', 400); 94 } 95 if (!($course = $DB->get_record('course', array('id' => $contextid), 'id', IGNORE_MISSING))) { 96 throw new \Exception("Not Found: Course {$contextid} doesn't exist", 404); 97 } 98 if (!$this->get_service()->is_allowed_in_context($typeid, $course->id)) { 99 throw new \Exception('Not allowed in context', 403); 100 } 101 if (!$DB->record_exists('grade_items', array('id' => $itemid))) { 102 throw new \Exception("Not Found: Grade item {$itemid} doesn't exist", 404); 103 } 104 $item = $this->get_service()->get_lineitem($contextid, $itemid, $typeid); 105 if ($item === false) { 106 throw new \Exception('Line item does not exist', 404); 107 } 108 $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lineitem($itemid); 109 $ltilinkid = null; 110 if (isset($item->iteminstance)) { 111 $ltilinkid = $item->iteminstance; 112 } else if ($gbs && isset($gbs->ltilinkid)) { 113 $ltilinkid = $gbs->ltilinkid; 114 } 115 if ($ltilinkid != null) { 116 if (is_null($typeid)) { 117 if (isset($item->iteminstance) && (!gradebookservices::check_lti_id($ltilinkid, $item->courseid, 118 $this->get_service()->get_tool_proxy()->id))) { 119 $response->set_code(403); 120 $response->set_reason("Invalid LTI id supplied."); 121 return; 122 } 123 } else { 124 if (isset($item->iteminstance) && (!gradebookservices::check_lti_1x_id($ltilinkid, $item->courseid, 125 $typeid))) { 126 $response->set_code(403); 127 $response->set_reason("Invalid LTI id supplied."); 128 return; 129 } 130 } 131 } 132 $json = '[]'; 133 require_once($CFG->libdir.'/gradelib.php'); 134 switch ($response->get_request_method()) { 135 case 'GET': 136 $response->set_code(405); 137 $response->set_reason("GET requests are not allowed."); 138 break; 139 case 'POST': 140 try { 141 $json = $this->get_json_for_post_request($response, $response->get_request_data(), $item, $contextid, 142 $typeid); 143 $response->set_content_type($this->formats[1]); 144 } catch (\Exception $e) { 145 $response->set_code($e->getCode()); 146 $response->set_reason($e->getMessage()); 147 } 148 break; 149 default: // Should not be possible. 150 $response->set_code(405); 151 $response->set_reason("Invalid request method specified."); 152 return; 153 } 154 $response->set_body($json); 155 } catch (\Exception $e) { 156 $response->set_code($e->getCode()); 157 $response->set_reason($e->getMessage()); 158 } 159 } 160 161 /** 162 * Generate the JSON for a POST request. 163 * 164 * @param \mod_lti\local\ltiservice\response $response Response object for this request. 165 * @param string $body POST body 166 * @param object $item Grade item instance 167 * @param string $contextid 168 * @param string $typeid 169 * 170 * @throws \Exception 171 */ 172 private function get_json_for_post_request($response, $body, $item, $contextid, $typeid) { 173 $score = json_decode($body); 174 if (empty($score) || 175 !isset($score->userId) || 176 !isset($score->timestamp) || 177 !isset($score->gradingProgress) || 178 !isset($score->activityProgress) || 179 !isset($score->timestamp) || 180 isset($score->timestamp) && !gradebookservices::validate_iso8601_date($score->timestamp) || 181 (isset($score->scoreGiven) && !is_numeric($score->scoreGiven)) || 182 (isset($score->scoreGiven) && !isset($score->scoreMaximum)) || 183 (isset($score->scoreMaximum) && !is_numeric($score->scoreMaximum)) || 184 (!gradebookservices::is_user_gradable_in_course($contextid, $score->userId)) 185 ) { 186 throw new \Exception('Incorrect score received' . $body, 400); 187 } 188 $score->timemodified = intval($score->timestamp); 189 190 if (!isset($score->scoreMaximum)) { 191 $score->scoreMaximum = 1; 192 } 193 $response->set_code(200); 194 $grade = \grade_grade::fetch(array('itemid' => $item->id, 'userid' => $score->userId)); 195 if ($grade && !empty($grade->timemodified)) { 196 if ($grade->timemodified >= strtotime($score->timestamp)) { 197 $exmsg = "Refusing score with an earlier timestamp for item " . $item->id . " and user " . $score->userId; 198 throw new \Exception($exmsg, 409); 199 } 200 } 201 if (isset($score->scoreGiven)) { 202 if ($score->gradingProgress != 'FullyGraded') { 203 $score->scoreGiven = null; 204 } 205 } 206 $this->get_service()->save_grade_item($item, $score, $score->userId); 207 } 208 209 /** 210 * Parse a value for custom parameter substitution variables. 211 * 212 * @param string $value String to be parsed 213 * 214 * @return string 215 */ 216 public function parse_value($value) { 217 global $COURSE, $CFG; 218 219 if (strpos($value, '$Scores.url') !== false) { 220 require_once($CFG->libdir . '/gradelib.php'); 221 222 $resolved = ''; 223 $this->params['context_id'] = $COURSE->id; 224 $id = optional_param('id', 0, PARAM_INT); // Course Module ID. 225 if (!empty($id)) { 226 $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST); 227 $id = $cm->instance; 228 $item = grade_get_grades($COURSE->id, 'mod', 'lti', $id); 229 if ($item && $item->items) { 230 $this->params['item_id'] = $item->items[0]->id; 231 $resolved = parent::get_endpoint(); 232 } 233 } 234 $value = str_replace('$Scores.url', $resolved, $value); 235 } 236 237 return $value; 238 } 239 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body