Merge branch 'MDL-51571_ltiErrorHandler' of https://github.com/moodlerooms/moodle
[moodle.git] / mod / lti / service.php
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/>.
17 /**
18  * LTI web service endpoints
19  *
20  * @package mod_lti
21  * @copyright  Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com)
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  * @author     Chris Scribner
24  */
26 define('NO_DEBUG_DISPLAY', true);
27 define('NO_MOODLE_COOKIES', true);
29 require_once(dirname(__FILE__) . "/../../config.php");
30 require_once($CFG->dirroot.'/mod/lti/locallib.php');
31 require_once($CFG->dirroot.'/mod/lti/servicelib.php');
33 // TODO: Switch to core oauthlib once implemented - MDL-30149.
34 use mod_lti\service_exception_handler;
35 use moodle\mod\lti as lti;
37 $rawbody = file_get_contents("php://input");
39 $logrequests  = lti_should_log_request($rawbody);
40 $errorhandler = new service_exception_handler($logrequests);
42 // Register our own error handler so we can always send valid XML response.
43 set_exception_handler(array($errorhandler, 'handle'));
45 if ($logrequests) {
46     lti_log_request($rawbody);
47 }
49 foreach (lti\OAuthUtil::get_headers() as $name => $value) {
50     if ($name === 'Authorization') {
51         // TODO: Switch to core oauthlib once implemented - MDL-30149.
52         $oauthparams = lti\OAuthUtil::split_header($value);
54         $consumerkey = $oauthparams['oauth_consumer_key'];
55         break;
56     }
57 }
59 if (empty($consumerkey)) {
60     throw new Exception('Consumer key is missing.');
61 }
63 $sharedsecret = lti_verify_message($consumerkey, lti_get_shared_secrets_by_key($consumerkey), $rawbody);
65 if ($sharedsecret === false) {
66     throw new Exception('Message signature not valid');
67 }
69 // TODO MDL-46023 Replace this code with a call to the new library.
70 $origentity = libxml_disable_entity_loader(true);
71 $xml = simplexml_load_string($rawbody);
72 if (!$xml) {
73     libxml_disable_entity_loader($origentity);
74     throw new Exception('Invalid XML content');
75 }
76 libxml_disable_entity_loader($origentity);
78 $body = $xml->imsx_POXBody;
79 foreach ($body->children() as $child) {
80     $messagetype = $child->getName();
81 }
83 // We know more about the message, update error handler to send better errors.
84 $errorhandler->set_message_id(lti_parse_message_id($xml));
85 $errorhandler->set_message_type($messagetype);
87 switch ($messagetype) {
88     case 'replaceResultRequest':
89         $parsed = lti_parse_grade_replace_message($xml);
91         $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid));
93         if (!lti_accepts_grades($ltiinstance)) {
94             throw new Exception('Tool does not accept grades');
95         }
97         lti_verify_sourcedid($ltiinstance, $parsed);
98         lti_set_session_user($parsed->userid);
100         $gradestatus = lti_update_grade($ltiinstance, $parsed->userid, $parsed->launchid, $parsed->gradeval);
102         if (!$gradestatus) {
103             throw new Exception('Grade replace response');
104         }
106         $responsexml = lti_get_response_xml(
107                 'success',
108                 'Grade replace response',
109                 $parsed->messageid,
110                 'replaceResultResponse'
111         );
113         echo $responsexml->asXML();
115         break;
117     case 'readResultRequest':
118         $parsed = lti_parse_grade_read_message($xml);
120         $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid));
122         if (!lti_accepts_grades($ltiinstance)) {
123             throw new Exception('Tool does not accept grades');
124         }
126         // Getting the grade requires the context is set.
127         $context = context_course::instance($ltiinstance->course);
128         $PAGE->set_context($context);
130         lti_verify_sourcedid($ltiinstance, $parsed);
132         $grade = lti_read_grade($ltiinstance, $parsed->userid);
134         $responsexml = lti_get_response_xml(
135                 'success',  // Empty grade is also 'success'.
136                 'Result read',
137                 $parsed->messageid,
138                 'readResultResponse'
139         );
141         $node = $responsexml->imsx_POXBody->readResultResponse;
142         $node = $node->addChild('result')->addChild('resultScore');
143         $node->addChild('language', 'en');
144         $node->addChild('textString', isset($grade) ? $grade : '');
146         echo $responsexml->asXML();
148         break;
150     case 'deleteResultRequest':
151         $parsed = lti_parse_grade_delete_message($xml);
153         $ltiinstance = $DB->get_record('lti', array('id' => $parsed->instanceid));
155         if (!lti_accepts_grades($ltiinstance)) {
156             throw new Exception('Tool does not accept grades');
157         }
159         lti_verify_sourcedid($ltiinstance, $parsed);
160         lti_set_session_user($parsed->userid);
162         $gradestatus = lti_delete_grade($ltiinstance, $parsed->userid);
164         if (!$gradestatus) {
165             throw new Exception('Grade delete request');
166         }
168         $responsexml = lti_get_response_xml(
169                 'success',
170                 'Grade delete request',
171                 $parsed->messageid,
172                 'deleteResultResponse'
173         );
175         echo $responsexml->asXML();
177         break;
179     default:
180         // Fire an event if we get a web service request which we don't support directly.
181         // This will allow others to extend the LTI services, which I expect to be a common
182         // use case, at least until the spec matures.
183         $data = new stdClass();
184         $data->body = $rawbody;
185         $data->xml = $xml;
186         $data->messageid = lti_parse_message_id($xml);
187         $data->messagetype = $messagetype;
188         $data->consumerkey = $consumerkey;
189         $data->sharedsecret = $sharedsecret;
190         $eventdata = array();
191         $eventdata['other'] = array();
192         $eventdata['other']['messageid'] = $data->messageid;
193         $eventdata['other']['messagetype'] = $messagetype;
194         $eventdata['other']['consumerkey'] = $consumerkey;
196         // Before firing the event, allow subplugins a chance to handle.
197         if (lti_extend_lti_services($data)) {
198             break;
199         }
201         // If an event handler handles the web service, it should set this global to true
202         // So this code knows whether to send an "operation not supported" or not.
203         global $ltiwebservicehandled;
204         $ltiwebservicehandled = false;
206         try {
207             $event = \mod_lti\event\unknown_service_api_called::create($eventdata);
208             $event->set_message_data($data);
209             $event->trigger();
210         } catch (Exception $e) {
211             $ltiwebservicehandled = false;
212         }
214         if (!$ltiwebservicehandled) {
215             $responsexml = lti_get_response_xml(
216                 'unsupported',
217                 'unsupported',
218                  lti_parse_message_id($xml),
219                  $messagetype
220             );
222             echo $responsexml->asXML();
223         }
225         break;