b6d4e469601a10f03854d27bab2836818ab0fc81
[moodle.git] / mod / lti / servicelib.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  * Utility code for LTI service handling.
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 defined('MOODLE_INTERNAL') || die;
28 require_once($CFG->dirroot.'/mod/lti/OAuthBody.php');
30 // TODO: Switch to core oauthlib once implemented - MDL-30149.
31 use moodle\mod\lti as lti;
33 define('LTI_ITEM_TYPE', 'mod');
34 define('LTI_ITEM_MODULE', 'lti');
35 define('LTI_SOURCE', 'mod/lti');
37 function lti_get_response_xml($codemajor, $description, $messageref, $messagetype) {
38     $xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><imsx_POXEnvelopeResponse />');
39     $xml->addAttribute('xmlns', 'http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0');
41     $headerinfo = $xml->addChild('imsx_POXHeader')->addChild('imsx_POXResponseHeaderInfo');
43     $headerinfo->addChild('imsx_version', 'V1.0');
44     $headerinfo->addChild('imsx_messageIdentifier', (string)mt_rand());
46     $statusinfo = $headerinfo->addChild('imsx_statusInfo');
47     $statusinfo->addchild('imsx_codeMajor', $codemajor);
48     $statusinfo->addChild('imsx_severity', 'status');
49     $statusinfo->addChild('imsx_description', $description);
50     $statusinfo->addChild('imsx_messageRefIdentifier', $messageref);
51     $incomingtype = str_replace('Response', 'Request', $messagetype);
52     $statusinfo->addChild('imsx_operationRefIdentifier', $incomingtype);
54     $xml->addChild('imsx_POXBody')->addChild($messagetype);
56     return $xml;
57 }
59 function lti_parse_message_id($xml) {
60     if (empty($xml->imsx_POXHeader)) {
61         return '';
62     }
64     $node = $xml->imsx_POXHeader->imsx_POXRequestHeaderInfo->imsx_messageIdentifier;
65     $messageid = (string)$node;
67     return $messageid;
68 }
70 function lti_parse_grade_replace_message($xml) {
71     $node = $xml->imsx_POXBody->replaceResultRequest->resultRecord->sourcedGUID->sourcedId;
72     $resultjson = json_decode((string)$node);
74     $node = $xml->imsx_POXBody->replaceResultRequest->resultRecord->result->resultScore->textString;
76     $score = (string) $node;
77     if ( ! is_numeric($score) ) {
78         throw new Exception('Score must be numeric');
79     }
80     $grade = floatval($score);
81     if ( $grade < 0.0 || $grade > 1.0 ) {
82         throw new Exception('Score not between 0.0 and 1.0');
83     }
85     $parsed = new stdClass();
86     $parsed->gradeval = $grade;
88     $parsed->instanceid = $resultjson->data->instanceid;
89     $parsed->userid = $resultjson->data->userid;
90     $parsed->launchid = $resultjson->data->launchid;
91     $parsed->typeid = $resultjson->data->typeid;
92     $parsed->sourcedidhash = $resultjson->hash;
94     $parsed->messageid = lti_parse_message_id($xml);
96     return $parsed;
97 }
99 function lti_parse_grade_read_message($xml) {
100     $node = $xml->imsx_POXBody->readResultRequest->resultRecord->sourcedGUID->sourcedId;
101     $resultjson = json_decode((string)$node);
103     $parsed = new stdClass();
104     $parsed->instanceid = $resultjson->data->instanceid;
105     $parsed->userid = $resultjson->data->userid;
106     $parsed->launchid = $resultjson->data->launchid;
107     $parsed->typeid = $resultjson->data->typeid;
108     $parsed->sourcedidhash = $resultjson->hash;
110     $parsed->messageid = lti_parse_message_id($xml);
112     return $parsed;
115 function lti_parse_grade_delete_message($xml) {
116     $node = $xml->imsx_POXBody->deleteResultRequest->resultRecord->sourcedGUID->sourcedId;
117     $resultjson = json_decode((string)$node);
119     $parsed = new stdClass();
120     $parsed->instanceid = $resultjson->data->instanceid;
121     $parsed->userid = $resultjson->data->userid;
122     $parsed->launchid = $resultjson->data->launchid;
123     $parsed->typeid = $resultjson->data->typeid;
124     $parsed->sourcedidhash = $resultjson->hash;
126     $parsed->messageid = lti_parse_message_id($xml);
128     return $parsed;
131 function lti_accepts_grades($ltiinstance) {
132     global $DB;
134     $acceptsgrades = true;
135     $ltitype = $DB->get_record('lti_types', array('id' => $ltiinstance->typeid));
137     if (empty($ltitype->toolproxyid)) {
138         $typeconfig = lti_get_config($ltiinstance);
140         $typeacceptgrades = isset($typeconfig['acceptgrades']) ? $typeconfig['acceptgrades'] : LTI_SETTING_DELEGATE;
142         if (!($typeacceptgrades == LTI_SETTING_ALWAYS ||
143             ($typeacceptgrades == LTI_SETTING_DELEGATE && $ltiinstance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))) {
144             $acceptsgrades = false;
145         }
146     } else {
147         $enabledcapabilities = explode("\n", $ltitype->enabledcapability);
148         $acceptsgrades = in_array('Result.autocreate', $enabledcapabilities);
149     }
151     return $acceptsgrades;
154 /**
155  * Set the passed user ID to the session user.
156  *
157  * @param int $userid
158  */
159 function lti_set_session_user($userid) {
160     global $DB;
162     if ($user = $DB->get_record('user', array('id' => $userid))) {
163         \core\session\manager::set_user($user);
164     }
167 function lti_update_grade($ltiinstance, $userid, $launchid, $gradeval) {
168     global $CFG, $DB;
169     require_once($CFG->libdir . '/gradelib.php');
171     $params = array();
172     $params['itemname'] = $ltiinstance->name;
174     $gradeval = $gradeval * floatval($ltiinstance->grade);
176     $grade = new stdClass();
177     $grade->userid   = $userid;
178     $grade->rawgrade = $gradeval;
180     $status = grade_update(LTI_SOURCE, $ltiinstance->course, LTI_ITEM_TYPE, LTI_ITEM_MODULE, $ltiinstance->id, 0, $grade, $params);
182     $record = $DB->get_record('lti_submission', array('ltiid' => $ltiinstance->id, 'userid' => $userid,
183         'launchid' => $launchid), 'id');
184     if ($record) {
185         $id = $record->id;
186     } else {
187         $id = null;
188     }
190     if (!empty($id)) {
191         $DB->update_record('lti_submission', array(
192             'id' => $id,
193             'dateupdated' => time(),
194             'gradepercent' => $gradeval,
195             'state' => 2
196         ));
197     } else {
198         $DB->insert_record('lti_submission', array(
199             'ltiid' => $ltiinstance->id,
200             'userid' => $userid,
201             'datesubmitted' => time(),
202             'dateupdated' => time(),
203             'gradepercent' => $gradeval,
204             'originalgrade' => $gradeval,
205             'launchid' => $launchid,
206             'state' => 1
207         ));
208     }
210     return $status == GRADE_UPDATE_OK;
213 function lti_read_grade($ltiinstance, $userid) {
214     global $CFG;
215     require_once($CFG->libdir . '/gradelib.php');
217     $grades = grade_get_grades($ltiinstance->course, LTI_ITEM_TYPE, LTI_ITEM_MODULE, $ltiinstance->id, $userid);
219     $ltigrade = floatval($ltiinstance->grade);
221     if (!empty($ltigrade) && isset($grades) && isset($grades->items[0]) && is_array($grades->items[0]->grades)) {
222         foreach ($grades->items[0]->grades as $agrade) {
223             $grade = $agrade->grade;
224             if (isset($grade)) {
225                 return $grade / $ltigrade;
226             }
227         }
228     }
231 function lti_delete_grade($ltiinstance, $userid) {
232     global $CFG;
233     require_once($CFG->libdir . '/gradelib.php');
235     $grade = new stdClass();
236     $grade->userid   = $userid;
237     $grade->rawgrade = null;
239     $status = grade_update(LTI_SOURCE, $ltiinstance->course, LTI_ITEM_TYPE, LTI_ITEM_MODULE, $ltiinstance->id, 0, $grade);
241     return $status == GRADE_UPDATE_OK;
244 function lti_verify_message($key, $sharedsecrets, $body, $headers = null) {
245     foreach ($sharedsecrets as $secret) {
246         $signaturefailed = false;
248         try {
249             // TODO: Switch to core oauthlib once implemented - MDL-30149.
250             lti\handle_oauth_body_post($key, $secret, $body, $headers);
251         } catch (Exception $e) {
252             $signaturefailed = true;
253         }
255         if (!$signaturefailed) {
256             return $secret; // Return the secret used to sign the message).
257         }
258     }
260     return false;
263 /**
264  * Validate source ID from external request
265  *
266  * @param object $ltiinstance
267  * @param object $parsed
268  * @throws Exception
269  */
270 function lti_verify_sourcedid($ltiinstance, $parsed) {
271     $sourceid = lti_build_sourcedid($parsed->instanceid, $parsed->userid,
272         $ltiinstance->servicesalt, $parsed->typeid, $parsed->launchid);
274     if ($sourceid->hash != $parsed->sourcedidhash) {
275         throw new Exception('SourcedId hash not valid');
276     }
279 /**
280  * Extend the LTI services through the ltisource plugins
281  *
282  * @param stdClass $data LTI request data
283  * @return bool
284  * @throws coding_exception
285  */
286 function lti_extend_lti_services($data) {
287     $plugins = get_plugin_list_with_function('ltisource', $data->messagetype);
288     if (!empty($plugins)) {
289         // There can only be one.
290         if (count($plugins) > 1) {
291             throw new coding_exception('More than one ltisource plugin handler found');
292         }
293         $data->xml = new SimpleXMLElement($data->body);
294         $callback = current($plugins);
295         call_user_func($callback, $data);
297         return true;
298     }
299     return false;