Commit | Line | Data |
---|---|---|
b9b2e7bb | 1 | <?php |
61eb12d4 CS |
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 | /** | |
8f45215d | 18 | * LTI web service endpoints |
61eb12d4 | 19 | * |
2b17ec3d | 20 | * @package mod_lti |
8f45215d | 21 | * @copyright Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com) |
61eb12d4 | 22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later |
8f45215d | 23 | * @author Chris Scribner |
61eb12d4 CS |
24 | */ |
25 | ||
4d7a2c9e | 26 | define('NO_DEBUG_DISPLAY', true); |
e3f69b58 | 27 | define('NO_MOODLE_COOKIES', true); |
4d7a2c9e | 28 | |
1fcf0ca8 | 29 | require_once(__DIR__ . "/../../config.php"); |
b9b2e7bb | 30 | require_once($CFG->dirroot.'/mod/lti/locallib.php'); |
996b0fd9 | 31 | require_once($CFG->dirroot.'/mod/lti/servicelib.php'); |
b9b2e7bb | 32 | |
e3f69b58 | 33 | // TODO: Switch to core oauthlib once implemented - MDL-30149. |
00e27060 | 34 | use mod_lti\service_exception_handler; |
020eea1b | 35 | use moodle\mod\lti as lti; |
27cbb596 | 36 | use ltiservice_basicoutcomes\local\service\basicoutcomes; |
020eea1b | 37 | |
996b0fd9 | 38 | $rawbody = file_get_contents("php://input"); |
020eea1b | 39 | |
00e27060 MN |
40 | $logrequests = lti_should_log_request($rawbody); |
41 | $errorhandler = new service_exception_handler($logrequests); | |
42 | ||
43 | // Register our own error handler so we can always send valid XML response. | |
44 | set_exception_handler(array($errorhandler, 'handle')); | |
45 | ||
46 | if ($logrequests) { | |
8fa50fdd MN |
47 | lti_log_request($rawbody); |
48 | } | |
49 | ||
27cbb596 SV |
50 | $ok = true; |
51 | $type = null; | |
52 | $toolproxy = false; | |
e27cb316 | 53 | |
27cbb596 SV |
54 | $consumerkey = lti\get_oauth_key_from_headers(null, array(basicoutcomes::SCOPE_BASIC_OUTCOMES)); |
55 | if ($consumerkey === false) { | |
56 | throw new Exception('Missing or invalid consumer key or access token.'); | |
57 | } else if (is_string($consumerkey)) { | |
58 | $toolproxy = lti_get_tool_proxy_from_guid($consumerkey); | |
59 | if ($toolproxy !== false) { | |
60 | $secrets = array($toolproxy->secret); | |
61 | } else if (!empty($tool)) { | |
62 | $secrets = array($typeconfig['password']); | |
63 | } else { | |
64 | $secrets = lti_get_shared_secrets_by_key($consumerkey); | |
65 | } | |
66 | $sharedsecret = lti_verify_message($consumerkey, lti_get_shared_secrets_by_key($consumerkey), $rawbody); | |
67 | if ($sharedsecret === false) { | |
68 | throw new Exception('Message signature not valid'); | |
020eea1b | 69 | } |
020eea1b CS |
70 | } |
71 | ||
78ed99ec | 72 | // TODO MDL-46023 Replace this code with a call to the new library. |
1e8375d8 | 73 | $origentity = lti_libxml_disable_entity_loader(true); |
78ed99ec FM |
74 | $xml = simplexml_load_string($rawbody); |
75 | if (!$xml) { | |
1e8375d8 | 76 | lti_libxml_disable_entity_loader($origentity); |
78ed99ec FM |
77 | throw new Exception('Invalid XML content'); |
78 | } | |
1e8375d8 | 79 | lti_libxml_disable_entity_loader($origentity); |
b9b2e7bb CS |
80 | |
81 | $body = $xml->imsx_POXBody; | |
ea04a9f9 | 82 | foreach ($body->children() as $child) { |
b9b2e7bb CS |
83 | $messagetype = $child->getName(); |
84 | } | |
85 | ||
00e27060 MN |
86 | // We know more about the message, update error handler to send better errors. |
87 | $errorhandler->set_message_id(lti_parse_message_id($xml)); | |
88 | $errorhandler->set_message_type($messagetype); | |
89 | ||
ea04a9f9 | 90 | switch ($messagetype) { |
b9b2e7bb | 91 | case 'replaceResultRequest': |
00e27060 | 92 | $parsed = lti_parse_grade_replace_message($xml); |
e27cb316 | 93 | |
b9b2e7bb | 94 | $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid)); |
e27cb316 | 95 | |
8fa50fdd MN |
96 | if (!lti_accepts_grades($ltiinstance)) { |
97 | throw new Exception('Tool does not accept grades'); | |
98 | } | |
99 | ||
996b0fd9 | 100 | lti_verify_sourcedid($ltiinstance, $parsed); |
8fa50fdd | 101 | lti_set_session_user($parsed->userid); |
e27cb316 | 102 | |
f4f711d7 | 103 | $gradestatus = lti_update_grade($ltiinstance, $parsed->userid, $parsed->launchid, $parsed->gradeval); |
e27cb316 | 104 | |
00e27060 MN |
105 | if (!$gradestatus) { |
106 | throw new Exception('Grade replace response'); | |
107 | } | |
108 | ||
b9b2e7bb | 109 | $responsexml = lti_get_response_xml( |
00e27060 | 110 | 'success', |
b9b2e7bb CS |
111 | 'Grade replace response', |
112 | $parsed->messageid, | |
113 | 'replaceResultResponse' | |
114 | ); | |
e27cb316 | 115 | |
b9b2e7bb | 116 | echo $responsexml->asXML(); |
e27cb316 | 117 | |
b9b2e7bb | 118 | break; |
e27cb316 | 119 | |
b9b2e7bb CS |
120 | case 'readResultRequest': |
121 | $parsed = lti_parse_grade_read_message($xml); | |
e27cb316 | 122 | |
b9b2e7bb | 123 | $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid)); |
e27cb316 | 124 | |
8fa50fdd MN |
125 | if (!lti_accepts_grades($ltiinstance)) { |
126 | throw new Exception('Tool does not accept grades'); | |
127 | } | |
128 | ||
e3f69b58 | 129 | // Getting the grade requires the context is set. |
c288a3db | 130 | $context = context_course::instance($ltiinstance->course); |
60bd82f6 | 131 | $PAGE->set_context($context); |
e27cb316 | 132 | |
996b0fd9 | 133 | lti_verify_sourcedid($ltiinstance, $parsed); |
e27cb316 | 134 | |
b9b2e7bb | 135 | $grade = lti_read_grade($ltiinstance, $parsed->userid); |
e27cb316 | 136 | |
b9b2e7bb | 137 | $responsexml = lti_get_response_xml( |
e3f69b58 | 138 | 'success', // Empty grade is also 'success'. |
b9b2e7bb CS |
139 | 'Result read', |
140 | $parsed->messageid, | |
141 | 'readResultResponse' | |
142 | ); | |
e27cb316 | 143 | |
b9b2e7bb | 144 | $node = $responsexml->imsx_POXBody->readResultResponse; |
ddcfda87 CS |
145 | $node = $node->addChild('result')->addChild('resultScore'); |
146 | $node->addChild('language', 'en'); | |
147 | $node->addChild('textString', isset($grade) ? $grade : ''); | |
e27cb316 | 148 | |
b9b2e7bb | 149 | echo $responsexml->asXML(); |
e27cb316 | 150 | |
b9b2e7bb | 151 | break; |
e27cb316 | 152 | |
b9b2e7bb CS |
153 | case 'deleteResultRequest': |
154 | $parsed = lti_parse_grade_delete_message($xml); | |
e27cb316 | 155 | |
b9b2e7bb | 156 | $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid)); |
e27cb316 | 157 | |
8fa50fdd MN |
158 | if (!lti_accepts_grades($ltiinstance)) { |
159 | throw new Exception('Tool does not accept grades'); | |
160 | } | |
161 | ||
996b0fd9 | 162 | lti_verify_sourcedid($ltiinstance, $parsed); |
8fa50fdd | 163 | lti_set_session_user($parsed->userid); |
e27cb316 | 164 | |
b9b2e7bb | 165 | $gradestatus = lti_delete_grade($ltiinstance, $parsed->userid); |
e27cb316 | 166 | |
00e27060 MN |
167 | if (!$gradestatus) { |
168 | throw new Exception('Grade delete request'); | |
169 | } | |
170 | ||
b9b2e7bb | 171 | $responsexml = lti_get_response_xml( |
00e27060 | 172 | 'success', |
e27cb316 CS |
173 | 'Grade delete request', |
174 | $parsed->messageid, | |
b9b2e7bb CS |
175 | 'deleteResultResponse' |
176 | ); | |
e27cb316 | 177 | |
b9b2e7bb | 178 | echo $responsexml->asXML(); |
e27cb316 | 179 | |
020eea1b | 180 | break; |
e27cb316 | 181 | |
020eea1b | 182 | default: |
e3f69b58 | 183 | // Fire an event if we get a web service request which we don't support directly. |
184 | // This will allow others to extend the LTI services, which I expect to be a common | |
185 | // use case, at least until the spec matures. | |
d5bc7b66 MG |
186 | $data = new stdClass(); |
187 | $data->body = $rawbody; | |
188 | $data->xml = $xml; | |
8a8fb3a6 | 189 | $data->messageid = lti_parse_message_id($xml); |
d5bc7b66 MG |
190 | $data->messagetype = $messagetype; |
191 | $data->consumerkey = $consumerkey; | |
192 | $data->sharedsecret = $sharedsecret; | |
b97d94ff AG |
193 | $eventdata = array(); |
194 | $eventdata['other'] = array(); | |
8a8fb3a6 | 195 | $eventdata['other']['messageid'] = $data->messageid; |
b97d94ff AG |
196 | $eventdata['other']['messagetype'] = $messagetype; |
197 | $eventdata['other']['consumerkey'] = $consumerkey; | |
e27cb316 | 198 | |
976b5bca | 199 | // Before firing the event, allow subplugins a chance to handle. |
8a8fb3a6 | 200 | if (lti_extend_lti_services($data)) { |
976b5bca CS |
201 | break; |
202 | } | |
203 | ||
e3f69b58 | 204 | // If an event handler handles the web service, it should set this global to true |
205 | // So this code knows whether to send an "operation not supported" or not. | |
206 | global $ltiwebservicehandled; | |
207 | $ltiwebservicehandled = false; | |
e27cb316 | 208 | |
ba0f89e1 CS |
209 | try { |
210 | $event = \mod_lti\event\unknown_service_api_called::create($eventdata); | |
d5bc7b66 | 211 | $event->set_message_data($data); |
ba0f89e1 CS |
212 | $event->trigger(); |
213 | } catch (Exception $e) { | |
e3f69b58 | 214 | $ltiwebservicehandled = false; |
ba0f89e1 | 215 | } |
e27cb316 | 216 | |
e3f69b58 | 217 | if (!$ltiwebservicehandled) { |
a0ba4ec6 | 218 | $responsexml = lti_get_response_xml( |
e27cb316 CS |
219 | 'unsupported', |
220 | 'unsupported', | |
a0ba4ec6 CS |
221 | lti_parse_message_id($xml), |
222 | $messagetype | |
223 | ); | |
224 | ||
225 | echo $responsexml->asXML(); | |
226 | } | |
e27cb316 | 227 | |
b9b2e7bb CS |
228 | break; |
229 | } |