MDL-61576 mod_lti: remove allowinstructorcustom in lib/locallib.php
[moodle.git] / mod / lti / locallib.php
CommitLineData
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//
b9b2e7bb
CS
17// This file is part of BasicLTI4Moodle
18//
19// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
20// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
21// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
22// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
23// are already supporting or going to support BasicLTI. This project Implements the consumer
24// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
25// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
26// at the GESSI research group at UPC.
27// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
28// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
29// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
30//
31// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
32// of the Universitat Politecnica de Catalunya http://www.upc.edu
e3f69b58 33// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
b9b2e7bb
CS
34
35/**
61eb12d4 36 * This file contains the library of functions and constants for the lti module
b9b2e7bb 37 *
2b17ec3d 38 * @package mod_lti
61eb12d4 39 * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
b9b2e7bb 40 * marc.alier@upc.edu
61eb12d4
CS
41 * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
42 * @author Marc Alier
43 * @author Jordi Piguillem
44 * @author Nikolas Galanis
8f45215d 45 * @author Chris Scribner
01f38dd0 46 * @copyright 2015 Vital Source Technologies http://vitalsource.com
47 * @author Stephen Vickers
61eb12d4 48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
b9b2e7bb
CS
49 */
50
51defined('MOODLE_INTERNAL') || die;
52
e3f69b58 53// TODO: Switch to core oauthlib once implemented - MDL-30149.
795dff01
CS
54use moodle\mod\lti as lti;
55
b9b2e7bb 56require_once($CFG->dirroot.'/mod/lti/OAuth.php');
7204d77b 57require_once($CFG->libdir.'/weblib.php');
c1fae2b9
JP
58require_once($CFG->dirroot . '/course/modlib.php');
59require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
b9b2e7bb
CS
60
61define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i');
62
63define('LTI_LAUNCH_CONTAINER_DEFAULT', 1);
64define('LTI_LAUNCH_CONTAINER_EMBED', 2);
65define('LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS', 3);
66define('LTI_LAUNCH_CONTAINER_WINDOW', 4);
cca9d3f7 67define('LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW', 5);
b9b2e7bb
CS
68
69define('LTI_TOOL_STATE_ANY', 0);
70define('LTI_TOOL_STATE_CONFIGURED', 1);
71define('LTI_TOOL_STATE_PENDING', 2);
72define('LTI_TOOL_STATE_REJECTED', 3);
e3f69b58 73define('LTI_TOOL_PROXY_TAB', 4);
74
75define('LTI_TOOL_PROXY_STATE_CONFIGURED', 1);
76define('LTI_TOOL_PROXY_STATE_PENDING', 2);
77define('LTI_TOOL_PROXY_STATE_ACCEPTED', 3);
78define('LTI_TOOL_PROXY_STATE_REJECTED', 4);
b9b2e7bb
CS
79
80define('LTI_SETTING_NEVER', 0);
81define('LTI_SETTING_ALWAYS', 1);
5e078d62 82define('LTI_SETTING_DELEGATE', 2);
b9b2e7bb 83
bff0a288
MG
84define('LTI_COURSEVISIBLE_NO', 0);
85define('LTI_COURSEVISIBLE_PRECONFIGURED', 1);
86define('LTI_COURSEVISIBLE_ACTIVITYCHOOSER', 2);
87
c1fae2b9
JP
88define('LTI_VERSION_1', 'LTI-1p0');
89define('LTI_VERSION_2', 'LTI-2p0');
90
b9b2e7bb 91/**
ae67efa8 92 * Return the launch data required for opening the external tool.
b9b2e7bb 93 *
ae67efa8
JL
94 * @param stdClass $instance the external tool activity settings
95 * @return array the endpoint URL and parameters (including the signature)
96 * @since Moodle 3.0
b9b2e7bb 97 */
ae67efa8 98function lti_get_launch_data($instance) {
b9b2e7bb
CS
99 global $PAGE, $CFG;
100
ea04a9f9 101 if (empty($instance->typeid)) {
606ab1a1 102 $tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course);
ea04a9f9 103 if ($tool) {
b9b2e7bb
CS
104 $typeid = $tool->id;
105 } else {
18ae59be
VC
106 $tool = lti_get_tool_by_url_match($instance->securetoolurl, $instance->course);
107 if ($tool) {
108 $typeid = $tool->id;
109 } else {
110 $typeid = null;
111 }
b9b2e7bb
CS
112 }
113 } else {
114 $typeid = $instance->typeid;
e3f69b58 115 $tool = lti_get_type($typeid);
b9b2e7bb 116 }
e27cb316 117
ea04a9f9 118 if ($typeid) {
b9b2e7bb
CS
119 $typeconfig = lti_get_type_config($typeid);
120 } else {
e3f69b58 121 // There is no admin configuration for this tool. Use configuration in the lti instance record plus some defaults.
b9b2e7bb 122 $typeconfig = (array)$instance;
e27cb316 123
b9b2e7bb
CS
124 $typeconfig['sendname'] = $instance->instructorchoicesendname;
125 $typeconfig['sendemailaddr'] = $instance->instructorchoicesendemailaddr;
126 $typeconfig['customparameters'] = $instance->instructorcustomparameters;
f4f711d7
CS
127 $typeconfig['acceptgrades'] = $instance->instructorchoiceacceptgrades;
128 $typeconfig['allowroster'] = $instance->instructorchoiceallowroster;
d8d04121 129 $typeconfig['forcessl'] = '0';
b9b2e7bb 130 }
e27cb316 131
e3f69b58 132 // Default the organizationid if not specified.
ea04a9f9 133 if (empty($typeconfig['organizationid'])) {
b9b2e7bb 134 $urlparts = parse_url($CFG->wwwroot);
e27cb316 135
b9b2e7bb
CS
136 $typeconfig['organizationid'] = $urlparts['host'];
137 }
e27cb316 138
e3f69b58 139 if (isset($tool->toolproxyid)) {
140 $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
141 $key = $toolproxy->guid;
142 $secret = $toolproxy->secret;
3dd9ca24 143 } else {
e3f69b58 144 $toolproxy = null;
145 if (!empty($instance->resourcekey)) {
146 $key = $instance->resourcekey;
147 } else if (!empty($typeconfig['resourcekey'])) {
148 $key = $typeconfig['resourcekey'];
149 } else {
150 $key = '';
151 }
152 if (!empty($instance->password)) {
153 $secret = $instance->password;
154 } else if (!empty($typeconfig['password'])) {
155 $secret = $typeconfig['password'];
156 } else {
157 $secret = '';
158 }
3dd9ca24 159 }
e27cb316 160
b9b2e7bb 161 $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl'];
d8d04121 162 $endpoint = trim($endpoint);
e27cb316 163
e3f69b58 164 // If the current request is using SSL and a secure tool URL is specified, use it.
ea04a9f9 165 if (lti_request_is_using_ssl() && !empty($instance->securetoolurl)) {
d8d04121
CS
166 $endpoint = trim($instance->securetoolurl);
167 }
e27cb316 168
e3f69b58 169 // If SSL is forced, use the secure tool url if specified. Otherwise, make sure https is on the normal launch URL.
170 if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
ea04a9f9 171 if (!empty($instance->securetoolurl)) {
d8d04121
CS
172 $endpoint = trim($instance->securetoolurl);
173 }
e27cb316 174
d8d04121
CS
175 $endpoint = lti_ensure_url_is_https($endpoint);
176 } else {
ea04a9f9 177 if (!strstr($endpoint, '://')) {
d8d04121
CS
178 $endpoint = 'http://' . $endpoint;
179 }
f17f4959 180 }
e27cb316 181
d8d04121
CS
182 $orgid = $typeconfig['organizationid'];
183
b9b2e7bb 184 $course = $PAGE->course;
e3f69b58 185 $islti2 = isset($tool->toolproxyid);
186 $allparams = lti_build_request($instance, $typeconfig, $course, $typeid, $islti2);
187 if ($islti2) {
188 $requestparams = lti_build_request_lti2($tool, $allparams);
8fa50fdd 189 } else {
e3f69b58 190 $requestparams = $allparams;
8fa50fdd 191 }
e3f69b58 192 $requestparams = array_merge($requestparams, lti_build_standard_request($instance, $orgid, $islti2));
193 $customstr = '';
194 if (isset($typeconfig['customparameters'])) {
195 $customstr = $typeconfig['customparameters'];
1706e83b 196 }
e3f69b58 197 $requestparams = array_merge($requestparams, lti_build_custom_parameters($toolproxy, $tool, $instance, $allparams, $customstr,
198 $instance->instructorcustomparameters, $islti2));
199
200 $launchcontainer = lti_get_launch_container($instance, $typeconfig);
edc89dfe
DW
201 $returnurlparams = array('course' => $course->id,
202 'launch_container' => $launchcontainer,
203 'instanceid' => $instance->id,
204 'sesskey' => sesskey());
6d6bd5f1
EL
205
206 // Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
e3f69b58 207 $url = new \moodle_url('/mod/lti/return.php', $returnurlparams);
1706e83b 208 $returnurl = $url->out(false);
e27cb316 209
e3f69b58 210 if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
1706e83b 211 $returnurl = lti_ensure_url_is_https($returnurl);
9d57ad17 212 }
6d6bd5f1 213
e3f69b58 214 $target = '';
8fa50fdd
MN
215 switch($launchcontainer) {
216 case LTI_LAUNCH_CONTAINER_EMBED:
217 case LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS:
218 $target = 'iframe';
219 break;
220 case LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW:
221 $target = 'frame';
222 break;
223 case LTI_LAUNCH_CONTAINER_WINDOW:
224 $target = 'window';
225 break;
226 }
e3f69b58 227 if (!empty($target)) {
8fa50fdd
MN
228 $requestparams['launch_presentation_document_target'] = $target;
229 }
230
1706e83b 231 $requestparams['launch_presentation_return_url'] = $returnurl;
e27cb316 232
d3496e3f
MN
233 // Allow request params to be updated by sub-plugins.
234 $plugins = core_component::get_plugin_list('ltisource');
235 foreach (array_keys($plugins) as $plugin) {
236 $pluginparams = component_callback('ltisource_'.$plugin, 'before_launch',
237 array($instance, $endpoint, $requestparams), array());
238
239 if (!empty($pluginparams) && is_array($pluginparams)) {
240 $requestparams = array_merge($requestparams, $pluginparams);
241 }
242 }
8fa50fdd 243
ea04a9f9 244 if (!empty($key) && !empty($secret)) {
3dd9ca24 245 $parms = lti_sign_parameters($requestparams, $endpoint, "POST", $key, $secret);
533c42ea 246
e3f69b58 247 $endpointurl = new \moodle_url($endpoint);
533c42ea
MN
248 $endpointparams = $endpointurl->params();
249
250 // Strip querystring params in endpoint url from $parms to avoid duplication.
251 if (!empty($endpointparams) && !empty($parms)) {
252 foreach (array_keys($endpointparams) as $paramname) {
253 if (isset($parms[$paramname])) {
254 unset($parms[$paramname]);
255 }
256 }
257 }
258
3dd9ca24 259 } else {
e3f69b58 260 // If no key and secret, do the launch unsigned.
261 $returnurlparams['unsigned'] = '1';
3dd9ca24 262 $parms = $requestparams;
3dd9ca24 263 }
e27cb316 264
ae67efa8
JL
265 return array($endpoint, $parms);
266}
267
268/**
c4c838ae 269 * Launch an external tool activity.
ae67efa8
JL
270 *
271 * @param stdClass $instance the external tool activity settings
c4c838ae 272 * @return string The HTML code containing the javascript code for the launch
ae67efa8 273 */
c4c838ae 274function lti_launch_tool($instance) {
ae67efa8
JL
275
276 list($endpoint, $parms) = lti_get_launch_data($instance);
b9b2e7bb 277 $debuglaunch = ( $instance->debuglaunch == 1 );
e27cb316 278
dbb0fec9 279 $content = lti_post_launch_html($parms, $endpoint, $debuglaunch);
e27cb316 280
b9b2e7bb
CS
281 echo $content;
282}
283
e3f69b58 284/**
285 * Prepares an LTI registration request message
286 *
287 * $param object $instance Tool Proxy instance object
288 */
289function lti_register($toolproxy) {
cc193e0d
RW
290 $endpoint = $toolproxy->regurl;
291
292 // Change the status to pending.
293 $toolproxy->state = LTI_TOOL_PROXY_STATE_PENDING;
294 lti_update_tool_proxy($toolproxy);
295
af9d3a92 296 $requestparams = lti_build_registration_request($toolproxy);
cc193e0d
RW
297
298 $content = lti_post_launch_html($requestparams, $endpoint, false);
299
300 echo $content;
301}
e3f69b58 302
af9d3a92
JO
303
304/**
305 * Gets the parameters for the regirstration request
306 *
307 * @param object $toolproxy Tool Proxy instance object
308 * @return array Registration request parameters
309 */
310function lti_build_registration_request($toolproxy) {
e3f69b58 311 $key = $toolproxy->guid;
312 $secret = $toolproxy->secret;
e3f69b58 313
314 $requestparams = array();
315 $requestparams['lti_message_type'] = 'ToolProxyRegistrationRequest';
316 $requestparams['lti_version'] = 'LTI-2p0';
317 $requestparams['reg_key'] = $key;
318 $requestparams['reg_password'] = $secret;
811d9ff9 319 $requestparams['reg_url'] = $toolproxy->regurl;
e3f69b58 320
e3f69b58 321 // Add the profile URL.
322 $profileservice = lti_get_service_by_name('profile');
323 $profileservice->set_tool_proxy($toolproxy);
324 $requestparams['tc_profile_url'] = $profileservice->parse_value('$ToolConsumerProfile.url');
325
326 // Add the return URL.
01f38dd0 327 $returnurlparams = array('id' => $toolproxy->id, 'sesskey' => sesskey());
cc193e0d 328 $url = new \moodle_url('/mod/lti/externalregistrationreturn.php', $returnurlparams);
e3f69b58 329 $returnurl = $url->out(false);
330
331 $requestparams['launch_presentation_return_url'] = $returnurl;
e3f69b58 332
cc193e0d 333 return $requestparams;
e3f69b58 334}
335
8fa50fdd
MN
336/**
337 * Build source ID
338 *
339 * @param int $instanceid
340 * @param int $userid
341 * @param string $servicesalt
342 * @param null|int $typeid
343 * @param null|int $launchid
344 * @return stdClass
345 */
346function lti_build_sourcedid($instanceid, $userid, $servicesalt, $typeid = null, $launchid = null) {
e3f69b58 347 $data = new \stdClass();
e27cb316 348
996b0fd9
CS
349 $data->instanceid = $instanceid;
350 $data->userid = $userid;
8fa50fdd 351 $data->typeid = $typeid;
ea04a9f9 352 if (!empty($launchid)) {
f4f711d7
CS
353 $data->launchid = $launchid;
354 } else {
355 $data->launchid = mt_rand();
356 }
996b0fd9
CS
357
358 $json = json_encode($data);
359
360 $hash = hash('sha256', $json . $servicesalt, false);
361
e3f69b58 362 $container = new \stdClass();
996b0fd9
CS
363 $container->data = $data;
364 $container->hash = $hash;
365
366 return $container;
367}
368
b9b2e7bb
CS
369/**
370 * This function builds the request that must be sent to the tool producer
371 *
372 * @param object $instance Basic LTI instance object
ff9d3d81 373 * @param array $typeconfig Basic LTI tool configuration
b9b2e7bb 374 * @param object $course Course object
8fa50fdd 375 * @param int|null $typeid Basic LTI tool ID
e3f69b58 376 * @param boolean $islti2 True if an LTI 2 tool is being launched
b9b2e7bb 377 *
e3f69b58 378 * @return array Request details
b9b2e7bb 379 */
e3f69b58 380function lti_build_request($instance, $typeconfig, $course, $typeid = null, $islti2 = false) {
b9b2e7bb
CS
381 global $USER, $CFG;
382
ea04a9f9 383 if (empty($instance->cmid)) {
16cac566
CS
384 $instance->cmid = 0;
385 }
e27cb316 386
e3f69b58 387 $role = lti_get_ims_role($USER, $instance->cmid, $instance->course, $islti2);
b9b2e7bb 388
b9b2e7bb 389 $requestparams = array(
34eb0501 390 'user_id' => $USER->id,
e3f69b58 391 'lis_person_sourcedid' => $USER->idnumber,
34eb0501
CS
392 'roles' => $role,
393 'context_id' => $course->id,
1bd212e8
SV
394 'context_label' => trim(html_to_text($course->shortname, 0)),
395 'context_title' => trim(html_to_text($course->fullname, 0)),
b9b2e7bb 396 );
d8f9109a 397 if (!empty($instance->name)) {
1bd212e8 398 $requestparams['resource_link_title'] = trim(html_to_text($instance->name, 0));
d8f9109a 399 }
400 if (!empty($instance->cmid)) {
401 $intro = format_module_intro('lti', $instance, $instance->cmid);
1768b85f 402 $intro = trim(html_to_text($intro, 0, false));
d8f9109a 403
404 // This may look weird, but this is required for new lines
405 // so we generate the same OAuth signature as the tool provider.
406 $intro = str_replace("\n", "\r\n", $intro);
407 $requestparams['resource_link_description'] = $intro;
408 }
219f956a
CB
409 if (!empty($instance->id)) {
410 $requestparams['resource_link_id'] = $instance->id;
411 }
412 if (!empty($instance->resource_link_id)) {
413 $requestparams['resource_link_id'] = $instance->resource_link_id;
414 }
e3f69b58 415 if ($course->format == 'site') {
416 $requestparams['context_type'] = 'Group';
417 } else {
418 $requestparams['context_type'] = 'CourseSection';
419 $requestparams['lis_course_section_sourcedid'] = $course->idnumber;
8fa50fdd 420 }
e27cb316 421
c1fae2b9
JP
422 if (!empty($instance->id) && !empty($instance->servicesalt) && ($islti2 ||
423 $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS ||
424 ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))
425 ) {
426 $placementsecret = $instance->servicesalt;
058cd1c1 427 $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret, $typeid));
428 $requestparams['lis_result_sourcedid'] = $sourcedid;
429
e3f69b58 430 // Add outcome service URL.
431 $serviceurl = new \moodle_url('/mod/lti/service.php');
34eb0501 432 $serviceurl = $serviceurl->out();
feeb5294 433
8fa50fdd
MN
434 $forcessl = false;
435 if (!empty($CFG->mod_lti_forcessl)) {
436 $forcessl = true;
437 }
438
e3f69b58 439 if ((isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) or $forcessl) {
d8d04121
CS
440 $serviceurl = lti_ensure_url_is_https($serviceurl);
441 }
feeb5294 442
34eb0501 443 $requestparams['lis_outcome_service_url'] = $serviceurl;
b9b2e7bb
CS
444 }
445
e3f69b58 446 // Send user's name and email data if appropriate.
447 if ($islti2 || $typeconfig['sendname'] == LTI_SETTING_ALWAYS ||
c1fae2b9
JP
448 ($typeconfig['sendname'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendname)
449 && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS)
450 ) {
e3f69b58 451 $requestparams['lis_person_name_given'] = $USER->firstname;
452 $requestparams['lis_person_name_family'] = $USER->lastname;
453 $requestparams['lis_person_name_full'] = $USER->firstname . ' ' . $USER->lastname;
435c709c 454 $requestparams['ext_user_username'] = $USER->username;
b9b2e7bb
CS
455 }
456
e3f69b58 457 if ($islti2 || $typeconfig['sendemailaddr'] == LTI_SETTING_ALWAYS ||
c1fae2b9
JP
458 ($typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendemailaddr)
459 && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS)
460 ) {
34eb0501 461 $requestparams['lis_person_contact_email_primary'] = $USER->email;
b9b2e7bb
CS
462 }
463
e3f69b58 464 return $requestparams;
465}
466
467/**
468 * This function builds the request that must be sent to an LTI 2 tool provider
469 *
470 * @param object $tool Basic LTI tool object
471 * @param array $params Custom launch parameters
472 *
473 * @return array Request details
474 */
475function lti_build_request_lti2($tool, $params) {
476
477 $requestparams = array();
478
479 $capabilities = lti_get_capabilities();
480 $enabledcapabilities = explode("\n", $tool->enabledcapability);
481 foreach ($enabledcapabilities as $capability) {
482 if (array_key_exists($capability, $capabilities)) {
483 $val = $capabilities[$capability];
484 if ($val && (substr($val, 0, 1) != '$')) {
485 if (isset($params[$val])) {
486 $requestparams[$capabilities[$capability]] = $params[$capabilities[$capability]];
487 }
b9b2e7bb
CS
488 }
489 }
b9b2e7bb
CS
490 }
491
e3f69b58 492 return $requestparams;
493
494}
495
496/**
497 * This function builds the standard parameters for an LTI 1 or 2 request that must be sent to the tool producer
498 *
c1fae2b9 499 * @param stdClass $instance Basic LTI instance object
e3f69b58 500 * @param string $orgid Organisation ID
501 * @param boolean $islti2 True if an LTI 2 tool is being launched
c1fae2b9 502 * @param string $messagetype The request message type. Defaults to basic-lti-launch-request if empty.
e3f69b58 503 *
504 * @return array Request details
505 */
c1fae2b9 506function lti_build_standard_request($instance, $orgid, $islti2, $messagetype = 'basic-lti-launch-request') {
e3f69b58 507 global $CFG;
508
509 $requestparams = array();
510
d8f9109a 511 if ($instance) {
512 $requestparams['resource_link_id'] = $instance->id;
513 if (property_exists($instance, 'resource_link_id') and !empty($instance->resource_link_id)) {
514 $requestparams['resource_link_id'] = $instance->resource_link_id;
515 }
e3f69b58 516 }
517
518 $requestparams['launch_presentation_locale'] = current_language();
519
520 // Make sure we let the tool know what LMS they are being called from.
521 $requestparams['ext_lms'] = 'moodle-2';
34eb0501
CS
522 $requestparams['tool_consumer_info_product_family_code'] = 'moodle';
523 $requestparams['tool_consumer_info_version'] = strval($CFG->version);
3dd9ca24 524
e3f69b58 525 // Add oauth_callback to be compliant with the 1.0A spec.
34eb0501 526 $requestparams['oauth_callback'] = 'about:blank';
3dd9ca24 527
e3f69b58 528 if (!$islti2) {
529 $requestparams['lti_version'] = 'LTI-1p0';
530 } else {
531 $requestparams['lti_version'] = 'LTI-2p0';
532 }
c1fae2b9 533 $requestparams['lti_message_type'] = $messagetype;
e27cb316 534
e3f69b58 535 if ($orgid) {
536 $requestparams["tool_consumer_instance_guid"] = $orgid;
537 }
538 if (!empty($CFG->mod_lti_institution_name)) {
1bd212e8 539 $requestparams['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0));
e3f69b58 540 } else {
2a5bb939 541 $requestparams['tool_consumer_instance_name'] = get_site()->shortname;
e3f69b58 542 }
1bd212e8 543 $requestparams['tool_consumer_instance_description'] = trim(html_to_text(get_site()->fullname, 0));
e3f69b58 544
b9b2e7bb
CS
545 return $requestparams;
546}
547
e3f69b58 548/**
549 * This function builds the custom parameters
550 *
551 * @param object $toolproxy Tool proxy instance object
552 * @param object $tool Tool instance object
553 * @param object $instance Tool placement instance object
554 * @param array $params LTI launch parameters
555 * @param string $customstr Custom parameters defined for tool
556 * @param string $instructorcustomstr Custom parameters defined for this placement
557 * @param boolean $islti2 True if an LTI 2 tool is being launched
558 *
559 * @return array Custom parameters
560 */
561function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $customstr, $instructorcustomstr, $islti2) {
562
563 // Concatenate the custom parameters from the administrator and the instructor
564 // Instructor parameters are only taken into consideration if the administrator
565 // has given permission.
566 $custom = array();
567 if ($customstr) {
568 $custom = lti_split_custom_parameters($toolproxy, $tool, $params, $customstr, $islti2);
569 }
adbf7c5a 570 if ($instructorcustomstr) {
571 $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
572 $instructorcustomstr, $islti2), $custom);
e3f69b58 573 }
574 if ($islti2) {
01f38dd0 575 $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
576 $tool->parameter, true), $custom);
e3f69b58 577 $settings = lti_get_tool_settings($tool->toolproxyid);
578 $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
d8f9109a 579 if (!empty($instance->course)) {
580 $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course);
581 $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
582 if (!empty($instance->id)) {
583 $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course, $instance->id);
584 $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
585 }
586 }
e3f69b58 587 }
588
589 return $custom;
590}
591
c1fae2b9
JP
592/**
593 * Builds a standard LTI Content-Item selection request.
594 *
595 * @param int $id The tool type ID.
596 * @param stdClass $course The course object.
597 * @param moodle_url $returnurl The return URL in the tool consumer (TC) that the tool provider (TP)
598 * will use to return the Content-Item message.
599 * @param string $title The tool's title, if available.
600 * @param string $text The text to display to represent the content item. This value may be a long description of the content item.
601 * @param array $mediatypes Array of MIME types types supported by the TC. If empty, the TC will support ltilink by default.
602 * @param array $presentationtargets Array of ways in which the selected content item(s) can be requested to be opened
603 * (via the presentationDocumentTarget element for a returned content item).
604 * If empty, "frame", "iframe", and "window" will be supported by default.
605 * @param bool $autocreate Indicates whether any content items returned by the TP would be automatically persisted without
606 * @param bool $multiple Indicates whether the user should be permitted to select more than one item. False by default.
607 * any option for the user to cancel the operation. False by default.
608 * @param bool $unsigned Indicates whether the TC is willing to accept an unsigned return message, or not.
609 * A signed message should always be required when the content item is being created automatically in the
610 * TC without further interaction from the user. False by default.
611 * @param bool $canconfirm Flag for can_confirm parameter. False by default.
612 * @param bool $copyadvice Indicates whether the TC is able and willing to make a local copy of a content item. False by default.
613 * @return stdClass The object containing the signed request parameters and the URL to the TP's Content-Item selection interface.
614 * @throws moodle_exception When the LTI tool type does not exist.`
615 * @throws coding_exception For invalid media type and presentation target parameters.
616 */
617function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [],
618 $presentationtargets = [], $autocreate = false, $multiple = false,
619 $unsigned = false, $canconfirm = false, $copyadvice = false) {
620 $tool = lti_get_type($id);
621 // Validate parameters.
622 if (!$tool) {
623 throw new moodle_exception('errortooltypenotfound', 'mod_lti');
624 }
625 if (!is_array($mediatypes)) {
626 throw new coding_exception('The list of accepted media types should be in an array');
627 }
628 if (!is_array($presentationtargets)) {
629 throw new coding_exception('The list of accepted presentation targets should be in an array');
630 }
631
632 // Check title. If empty, use the tool's name.
633 if (empty($title)) {
634 $title = $tool->name;
635 }
636
637 $typeconfig = lti_get_type_config($id);
638 $key = '';
639 $secret = '';
640 $islti2 = false;
641 if (isset($tool->toolproxyid)) {
642 $islti2 = true;
643 $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
644 $key = $toolproxy->guid;
645 $secret = $toolproxy->secret;
646 } else {
647 $toolproxy = null;
648 if (!empty($typeconfig['resourcekey'])) {
649 $key = $typeconfig['resourcekey'];
650 }
651 if (!empty($typeconfig['password'])) {
652 $secret = $typeconfig['password'];
653 }
654 }
655 $tool->enabledcapability = '';
656 if (!empty($typeconfig['enabledcapability_ContentItemSelectionRequest'])) {
657 $tool->enabledcapability = $typeconfig['enabledcapability_ContentItemSelectionRequest'];
658 }
659
660 $tool->parameter = '';
661 if (!empty($typeconfig['parameter_ContentItemSelectionRequest'])) {
662 $tool->parameter = $typeconfig['parameter_ContentItemSelectionRequest'];
663 }
664
665 // Set the tool URL.
666 if (!empty($typeconfig['toolurl_ContentItemSelectionRequest'])) {
667 $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']);
668 } else {
669 $toolurl = new moodle_url($typeconfig['toolurl']);
670 }
671
672 // Check if SSL is forced.
673 if (!empty($typeconfig['forcessl'])) {
674 // Make sure the tool URL is set to https.
675 if (strtolower($toolurl->get_scheme()) === 'http') {
676 $toolurl->set_scheme('https');
677 }
678 // Make sure the return URL is set to https.
679 if (strtolower($returnurl->get_scheme()) === 'http') {
680 $returnurl->set_scheme('https');
681 }
682 }
683 $toolurlout = $toolurl->out(false);
684
685 // Get base request parameters.
686 $instance = new stdClass();
687 $instance->course = $course->id;
688 $requestparams = lti_build_request($instance, $typeconfig, $course, $id, $islti2);
689
690 // Get LTI2-specific request parameters and merge to the request parameters if applicable.
691 if ($islti2) {
692 $lti2params = lti_build_request_lti2($tool, $requestparams);
693 $requestparams = array_merge($requestparams, $lti2params);
694 }
695
696 // Get standard request parameters and merge to the request parameters.
697 $orgid = !empty($typeconfig['organizationid']) ? $typeconfig['organizationid'] : '';
698 $standardparams = lti_build_standard_request(null, $orgid, $islti2, 'ContentItemSelectionRequest');
699 $requestparams = array_merge($requestparams, $standardparams);
700
701 // Get custom request parameters and merge to the request parameters.
702 $customstr = '';
703 if (!empty($typeconfig['customparameters'])) {
704 $customstr = $typeconfig['customparameters'];
705 }
706 $customparams = lti_build_custom_parameters($toolproxy, $tool, $instance, $requestparams, $customstr, '', $islti2);
707 $requestparams = array_merge($requestparams, $customparams);
708
709 // Allow request params to be updated by sub-plugins.
710 $plugins = core_component::get_plugin_list('ltisource');
711 foreach (array_keys($plugins) as $plugin) {
712 $pluginparams = component_callback('ltisource_' . $plugin, 'before_launch', [$instance, $toolurlout, $requestparams], []);
713
714 if (!empty($pluginparams) && is_array($pluginparams)) {
715 $requestparams = array_merge($requestparams, $pluginparams);
716 }
717 }
718
719 // Media types. Set to ltilink by default if empty.
720 if (empty($mediatypes)) {
721 $mediatypes = [
722 'application/vnd.ims.lti.v1.ltilink',
723 ];
724 }
725 $requestparams['accept_media_types'] = implode(',', $mediatypes);
726
727 // Presentation targets. Supports frame, iframe, window by default if empty.
728 if (empty($presentationtargets)) {
729 $presentationtargets = [
730 'frame',
731 'iframe',
732 'window',
733 ];
734 }
735 $requestparams['accept_presentation_document_targets'] = implode(',', $presentationtargets);
736
737 // Other request parameters.
738 $requestparams['accept_copy_advice'] = $copyadvice === true ? 'true' : 'false';
739 $requestparams['accept_multiple'] = $multiple === true ? 'true' : 'false';
740 $requestparams['accept_unsigned'] = $unsigned === true ? 'true' : 'false';
741 $requestparams['auto_create'] = $autocreate === true ? 'true' : 'false';
742 $requestparams['can_confirm'] = $canconfirm === true ? 'true' : 'false';
743 $requestparams['content_item_return_url'] = $returnurl->out(false);
744 $requestparams['title'] = $title;
745 $requestparams['text'] = $text;
746 $signedparams = lti_sign_parameters($requestparams, $toolurlout, 'POST', $key, $secret);
747 $toolurlparams = $toolurl->params();
748
749 // Strip querystring params in endpoint url from $signedparams to avoid duplication.
750 if (!empty($toolurlparams) && !empty($signedparams)) {
751 foreach (array_keys($toolurlparams) as $paramname) {
752 if (isset($signedparams[$paramname])) {
753 unset($signedparams[$paramname]);
754 }
755 }
756 }
757
758 // Check for params that should not be passed. Unset if they are set.
759 $unwantedparams = [
760 'resource_link_id',
761 'resource_link_title',
762 'resource_link_description',
763 'launch_presentation_return_url',
764 'lis_result_sourcedid',
765 ];
766 foreach ($unwantedparams as $param) {
767 if (isset($signedparams[$param])) {
768 unset($signedparams[$param]);
769 }
770 }
771
772 // Prepare result object.
773 $result = new stdClass();
774 $result->params = $signedparams;
775 $result->url = $toolurlout;
776
777 return $result;
778}
779
780/**
781 * Processes the tool provider's response to the ContentItemSelectionRequest and builds the configuration data from the
782 * selected content item. This configuration data can be then used when adding a tool into the course.
783 *
784 * @param int $typeid The tool type ID.
785 * @param string $messagetype The value for the lti_message_type parameter.
786 * @param string $ltiversion The value for the lti_version parameter.
787 * @param string $consumerkey The consumer key.
788 * @param string $contentitemsjson The JSON string for the content_items parameter.
789 * @return stdClass The array of module information objects.
790 * @throws moodle_exception
791 * @throws lti\OAuthException
792 */
793function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiversion, $consumerkey, $contentitemsjson) {
794 $tool = lti_get_type($typeid);
795 // Validate parameters.
796 if (!$tool) {
797 throw new moodle_exception('errortooltypenotfound', 'mod_lti');
798 }
799 // Check lti_message_type. Show debugging if it's not set to ContentItemSelection.
800 // No need to throw exceptions for now since lti_message_type does not seem to be used in this processing at the moment.
801 if ($messagetype !== 'ContentItemSelection') {
802 debugging("lti_message_type is invalid: {$messagetype}. It should be set to 'ContentItemSelection'.",
803 DEBUG_DEVELOPER);
804 }
805
806 $typeconfig = lti_get_type_config($typeid);
807
808 if (isset($tool->toolproxyid)) {
809 $islti2 = true;
810 $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
811 $key = $toolproxy->guid;
812 $secret = $toolproxy->secret;
813 } else {
814 $islti2 = false;
815 $toolproxy = null;
816 if (!empty($typeconfig['resourcekey'])) {
817 $key = $typeconfig['resourcekey'];
818 } else {
819 $key = '';
820 }
821 if (!empty($typeconfig['password'])) {
822 $secret = $typeconfig['password'];
823 } else {
824 $secret = '';
825 }
826 }
827
828 // Check LTI versions from our side and the response's side. Show debugging if they don't match.
829 // No need to throw exceptions for now since LTI version does not seem to be used in this processing at the moment.
830 $expectedversion = LTI_VERSION_1;
831 if ($islti2) {
832 $expectedversion = LTI_VERSION_2;
833 }
834 if ($ltiversion !== $expectedversion) {
835 debugging("lti_version from response does not match the tool's configuration. Tool: {$expectedversion}," .
836 " Response: {$ltiversion}", DEBUG_DEVELOPER);
837 }
838
839 if ($consumerkey !== $key) {
840 throw new moodle_exception('errorincorrectconsumerkey', 'mod_lti');
841 }
842
843 $store = new lti\TrivialOAuthDataStore();
844 $store->add_consumer($key, $secret);
845 $server = new lti\OAuthServer($store);
846 $method = new lti\OAuthSignatureMethod_HMAC_SHA1();
847 $server->add_signature_method($method);
848 $request = lti\OAuthRequest::from_request();
849 try {
850 $server->verify_request($request);
851 } catch (lti\OAuthException $e) {
852 throw new lti\OAuthException("OAuth signature failed: " . $e->getMessage());
853 }
854
855 $items = json_decode($contentitemsjson);
856 if (empty($items)) {
857 throw new moodle_exception('errorinvaliddata', 'mod_lti', '', $contentitemsjson);
858 }
859 if ($items->{'@context'} !== 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem') {
860 throw new moodle_exception('errorinvalidmediatype', 'mod_lti', '', $items->{'@context'});
861 }
862 if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'}) || (count($items->{'@graph'}) > 1)) {
863 throw new moodle_exception('errorinvalidresponseformat', 'mod_lti');
864 }
865
866 $config = null;
867 if (!empty($items->{'@graph'})) {
868 $item = $items->{'@graph'}[0];
869
870 $config = new stdClass();
871 $config->name = '';
872 if (isset($item->title)) {
873 $config->name = $item->title;
874 }
875 if (empty($config->name)) {
876 $config->name = $tool->name;
877 }
878 if (isset($item->text)) {
879 $config->introeditor = [
880 'text' => $item->text,
881 'format' => FORMAT_PLAIN
882 ];
883 }
884 if (isset($item->icon->{'@id'})) {
885 $iconurl = new moodle_url($item->icon->{'@id'});
886 // Assign item's icon URL to secureicon or icon depending on its scheme.
887 if (strtolower($iconurl->get_scheme()) === 'https') {
888 $config->secureicon = $iconurl->out(false);
889 } else {
890 $config->icon = $iconurl->out(false);
891 }
892 }
893 if (isset($item->url)) {
894 $url = new moodle_url($item->url);
4325a4d2 895 $config->toolurl = $url->out(false);
c1fae2b9
JP
896 $config->typeid = 0;
897 } else {
898 $config->typeid = $typeid;
899 }
900 $config->instructorchoicesendname = LTI_SETTING_NEVER;
901 $config->instructorchoicesendemailaddr = LTI_SETTING_NEVER;
902 $config->instructorchoiceacceptgrades = LTI_SETTING_NEVER;
903 $config->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT;
904 if (isset($item->placementAdvice->presentationDocumentTarget)) {
905 if ($item->placementAdvice->presentationDocumentTarget === 'window') {
906 $config->launchcontainer = LTI_LAUNCH_CONTAINER_WINDOW;
907 } else if ($item->placementAdvice->presentationDocumentTarget === 'frame') {
908 $config->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
909 } else if ($item->placementAdvice->presentationDocumentTarget === 'iframe') {
910 $config->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED;
911 }
912 }
913 if (isset($item->custom)) {
914 $customparameters = [];
915 foreach ($item->custom as $key => $value) {
916 $customparameters[] = "{$key}={$value}";
917 }
918 $config->instructorcustomparameters = implode("\n", $customparameters);
919 }
920 }
921 return $config;
922}
923
ea04a9f9 924function lti_get_tool_table($tools, $id) {
99938034 925 global $CFG, $OUTPUT, $USER;
795dff01 926 $html = '';
e27cb316 927
795dff01
CS
928 $typename = get_string('typename', 'lti');
929 $baseurl = get_string('baseurl', 'lti');
930 $action = get_string('action', 'lti');
931 $createdon = get_string('createdon', 'lti');
e27cb316 932
795dff01 933 if (!empty($tools)) {
5de15b83 934 $html .= "
e3f69b58 935 <div id=\"{$id}_tools_container\" style=\"margin-top:.5em;margin-bottom:.5em\">
5de15b83 936 <table id=\"{$id}_tools\">
795dff01
CS
937 <thead>
938 <tr>
939 <th>$typename</th>
940 <th>$baseurl</th>
941 <th>$createdon</th>
942 <th>$action</th>
943 </tr>
944 </thead>
5de15b83 945 ";
e27cb316 946
795dff01 947 foreach ($tools as $type) {
101ec5fd 948 $date = userdate($type->timecreated, get_string('strftimedatefullshort', 'core_langconfig'));
795dff01
CS
949 $accept = get_string('accept', 'lti');
950 $update = get_string('update', 'lti');
951 $delete = get_string('delete', 'lti');
e27cb316 952
e3f69b58 953 if (empty($type->toolproxyid)) {
954 $baseurl = new \moodle_url('/mod/lti/typessettings.php', array(
955 'action' => 'accept',
956 'id' => $type->id,
957 'sesskey' => sesskey(),
958 'tab' => $id
959 ));
960 $ref = $type->baseurl;
961 } else {
962 $baseurl = new \moodle_url('/mod/lti/toolssettings.php', array(
963 'action' => 'accept',
964 'id' => $type->id,
965 'sesskey' => sesskey(),
966 'tab' => $id
967 ));
968 $ref = $type->tpname;
969 }
baf41e4b
FM
970
971 $accepthtml = $OUTPUT->action_icon($baseurl,
e3f69b58 972 new \pix_icon('t/check', $accept, '', array('class' => 'iconsmall')), null,
baf41e4b 973 array('title' => $accept, 'class' => 'editing_accept'));
795dff01
CS
974
975 $deleteaction = 'delete';
e27cb316 976
ea04a9f9 977 if ($type->state == LTI_TOOL_STATE_CONFIGURED) {
795dff01
CS
978 $accepthtml = '';
979 }
e27cb316 980
ea04a9f9 981 if ($type->state != LTI_TOOL_STATE_REJECTED) {
795dff01
CS
982 $deleteaction = 'reject';
983 $delete = get_string('reject', 'lti');
984 }
e27cb316 985
baf41e4b
FM
986 $updateurl = clone($baseurl);
987 $updateurl->param('action', 'update');
988 $updatehtml = $OUTPUT->action_icon($updateurl,
e3f69b58 989 new \pix_icon('t/edit', $update, '', array('class' => 'iconsmall')), null,
baf41e4b
FM
990 array('title' => $update, 'class' => 'editing_update'));
991
e3f69b58 992 if (($type->state != LTI_TOOL_STATE_REJECTED) || empty($type->toolproxyid)) {
993 $deleteurl = clone($baseurl);
994 $deleteurl->param('action', $deleteaction);
995 $deletehtml = $OUTPUT->action_icon($deleteurl,
996 new \pix_icon('t/delete', $delete, '', array('class' => 'iconsmall')), null,
997 array('title' => $delete, 'class' => 'editing_delete'));
998 } else {
999 $deletehtml = '';
1000 }
5de15b83 1001 $html .= "
795dff01
CS
1002 <tr>
1003 <td>
1004 {$type->name}
1005 </td>
1006 <td>
e3f69b58 1007 {$ref}
795dff01
CS
1008 </td>
1009 <td>
1010 {$date}
1011 </td>
5de15b83 1012 <td align=\"center\">
baf41e4b 1013 {$accepthtml}{$updatehtml}{$deletehtml}
795dff01
CS
1014 </td>
1015 </tr>
5de15b83 1016 ";
795dff01
CS
1017 }
1018 $html .= '</table></div>';
1019 } else {
1020 $html .= get_string('no_' . $id, 'lti');
1021 }
e27cb316 1022
795dff01
CS
1023 return $html;
1024}
1025
e3f69b58 1026/**
1027 * This function builds the tab for a category of tool proxies
1028 *
1029 * @param object $toolproxies Tool proxy instance objects
1030 * @param string $id Category ID
1031 *
1032 * @return string HTML for tab
1033 */
1034function lti_get_tool_proxy_table($toolproxies, $id) {
1035 global $OUTPUT;
1036
1037 if (!empty($toolproxies)) {
1038 $typename = get_string('typename', 'lti');
1039 $url = get_string('registrationurl', 'lti');
1040 $action = get_string('action', 'lti');
1041 $createdon = get_string('createdon', 'lti');
1042
1043 $html = <<< EOD
1044 <div id="{$id}_tool_proxies_container" style="margin-top: 0.5em; margin-bottom: 0.5em">
1045 <table id="{$id}_tool_proxies">
1046 <thead>
1047 <tr>
1048 <th>{$typename}</th>
1049 <th>{$url}</th>
1050 <th>{$createdon}</th>
1051 <th>{$action}</th>
1052 </tr>
1053 </thead>
1054EOD;
1055 foreach ($toolproxies as $toolproxy) {
1056 $date = userdate($toolproxy->timecreated, get_string('strftimedatefullshort', 'core_langconfig'));
1057 $accept = get_string('register', 'lti');
1058 $update = get_string('update', 'lti');
1059 $delete = get_string('delete', 'lti');
1060
1061 $baseurl = new \moodle_url('/mod/lti/registersettings.php', array(
1062 'action' => 'accept',
1063 'id' => $toolproxy->id,
1064 'sesskey' => sesskey(),
1065 'tab' => $id
1066 ));
1067
1068 $registerurl = new \moodle_url('/mod/lti/register.php', array(
1069 'id' => $toolproxy->id,
1070 'sesskey' => sesskey(),
1071 'tab' => 'tool_proxy'
1072 ));
1073
1074 $accepthtml = $OUTPUT->action_icon($registerurl,
1075 new \pix_icon('t/check', $accept, '', array('class' => 'iconsmall')), null,
1076 array('title' => $accept, 'class' => 'editing_accept'));
1077
1078 $deleteaction = 'delete';
1079
1080 if ($toolproxy->state != LTI_TOOL_PROXY_STATE_CONFIGURED) {
1081 $accepthtml = '';
1082 }
1083
1084 if (($toolproxy->state == LTI_TOOL_PROXY_STATE_CONFIGURED) || ($toolproxy->state == LTI_TOOL_PROXY_STATE_PENDING)) {
1085 $delete = get_string('cancel', 'lti');
1086 }
1087
1088 $updateurl = clone($baseurl);
1089 $updateurl->param('action', 'update');
1090 $updatehtml = $OUTPUT->action_icon($updateurl,
1091 new \pix_icon('t/edit', $update, '', array('class' => 'iconsmall')), null,
1092 array('title' => $update, 'class' => 'editing_update'));
1093
1094 $deleteurl = clone($baseurl);
1095 $deleteurl->param('action', $deleteaction);
1096 $deletehtml = $OUTPUT->action_icon($deleteurl,
1097 new \pix_icon('t/delete', $delete, '', array('class' => 'iconsmall')), null,
1098 array('title' => $delete, 'class' => 'editing_delete'));
1099 $html .= <<< EOD
1100 <tr>
1101 <td>
1102 {$toolproxy->name}
1103 </td>
1104 <td>
1105 {$toolproxy->regurl}
1106 </td>
1107 <td>
1108 {$date}
1109 </td>
1110 <td align="center">
1111 {$accepthtml}{$updatehtml}{$deletehtml}
1112 </td>
1113 </tr>
1114EOD;
1115 }
1116 $html .= '</table></div>';
1117 } else {
1118 $html = get_string('no_' . $id, 'lti');
1119 }
1120
1121 return $html;
1122}
1123
01f38dd0 1124/**
1125 * Extracts the enabled capabilities into an array, including those implicitly declared in a parameter
1126 *
1127 * @param object $tool Tool instance object
1128 *
1129 * @return Array of enabled capabilities
1130 */
1131function lti_get_enabled_capabilities($tool) {
a2701db4
DK
1132 if (!isset($tool)) {
1133 return array();
1134 }
01f38dd0 1135 if (!empty($tool->enabledcapability)) {
1136 $enabledcapabilities = explode("\n", $tool->enabledcapability);
1137 } else {
1138 $enabledcapabilities = array();
1139 }
1140 $paramstr = str_replace("\r\n", "\n", $tool->parameter);
1141 $paramstr = str_replace("\n\r", "\n", $paramstr);
1142 $paramstr = str_replace("\r", "\n", $paramstr);
1143 $params = explode("\n", $paramstr);
1144 foreach ($params as $param) {
1145 $pos = strpos($param, '=');
1146 if (($pos === false) || ($pos < 1)) {
1147 continue;
1148 }
1149 $value = trim(core_text::substr($param, $pos + 1, strlen($param)));
1150 if (substr($value, 0, 1) == '$') {
1151 $value = substr($value, 1);
1152 if (!in_array($value, $enabledcapabilities)) {
1153 $enabledcapabilities[] = $value;
1154 }
1155 }
1156 }
1157 return $enabledcapabilities;
1158}
1159
b9b2e7bb
CS
1160/**
1161 * Splits the custom parameters field to the various parameters
1162 *
e3f69b58 1163 * @param object $toolproxy Tool proxy instance object
1164 * @param object $tool Tool instance object
1165 * @param array $params LTI launch parameters
1166 * @param string $customstr String containing the parameters
1167 * @param boolean $islti2 True if an LTI 2 tool is being launched
b9b2e7bb 1168 *
e4f7cfe1 1169 * @return array of custom parameters
b9b2e7bb 1170 */
e3f69b58 1171function lti_split_custom_parameters($toolproxy, $tool, $params, $customstr, $islti2 = false) {
1172 $customstr = str_replace("\r\n", "\n", $customstr);
1173 $customstr = str_replace("\n\r", "\n", $customstr);
1174 $customstr = str_replace("\r", "\n", $customstr);
1175 $lines = explode("\n", $customstr); // Or should this split on "/[\n;]/"?
b9b2e7bb
CS
1176 $retval = array();
1177 foreach ($lines as $line) {
01f38dd0 1178 $pos = strpos($line, '=');
b9b2e7bb
CS
1179 if ( $pos === false || $pos < 1 ) {
1180 continue;
1181 }
2f1e464a 1182 $key = trim(core_text::substr($line, 0, $pos));
e4f7cfe1 1183 $key = lti_map_keyname($key, false);
e3f69b58 1184 $val = trim(core_text::substr($line, $pos + 1, strlen($line)));
1185 $val = lti_parse_custom_parameter($toolproxy, $tool, $params, $val, $islti2);
1186 $key2 = lti_map_keyname($key);
1187 $retval['custom_'.$key2] = $val;
e4f7cfe1 1188 if ($key != $key2) {
e3f69b58 1189 $retval['custom_'.$key] = $val;
1190 }
1191 }
1192 return $retval;
1193}
1194
1195/**
1196 * Adds the custom parameters to an array
1197 *
1198 * @param object $toolproxy Tool proxy instance object
1199 * @param object $tool Tool instance object
1200 * @param array $params LTI launch parameters
1201 * @param array $parameters Array containing the parameters
1202 *
1203 * @return array Array of custom parameters
1204 */
1205function lti_get_custom_parameters($toolproxy, $tool, $params, $parameters) {
1206 $retval = array();
1207 foreach ($parameters as $key => $val) {
1208 $key2 = lti_map_keyname($key);
1209 $val = lti_parse_custom_parameter($toolproxy, $tool, $params, $val, true);
1210 $retval['custom_'.$key2] = $val;
1211 if ($key != $key2) {
1212 $retval['custom_'.$key] = $val;
1213 }
b9b2e7bb
CS
1214 }
1215 return $retval;
1216}
1217
e3f69b58 1218/**
1219 * Parse a custom parameter to replace any substitution variables
1220 *
1221 * @param object $toolproxy Tool proxy instance object
1222 * @param object $tool Tool instance object
1223 * @param array $params LTI launch parameters
1224 * @param string $value Custom parameter value
1225 * @param boolean $islti2 True if an LTI 2 tool is being launched
1226 *
1227 * @return Parsed value of custom parameter
1228 */
1229function lti_parse_custom_parameter($toolproxy, $tool, $params, $value, $islti2) {
1230 global $USER, $COURSE;
1231
1232 if ($value) {
1233 if (substr($value, 0, 1) == '\\') {
1234 $value = substr($value, 1);
1235 } else if (substr($value, 0, 1) == '$') {
1236 $value1 = substr($value, 1);
01f38dd0 1237 $enabledcapabilities = lti_get_enabled_capabilities($tool);
e3f69b58 1238 if (!$islti2 || in_array($value1, $enabledcapabilities)) {
1239 $capabilities = lti_get_capabilities();
1240 if (array_key_exists($value1, $capabilities)) {
1241 $val = $capabilities[$value1];
1242 if ($val) {
1243 if (substr($val, 0, 1) != '$') {
1244 $value = $params[$val];
1245 } else {
1246 $valarr = explode('->', substr($val, 1), 2);
ccfd168a 1247 $value = "{${$valarr[0]}->{$valarr[1]}}";
e3f69b58 1248 $value = str_replace('<br />' , ' ', $value);
1249 $value = str_replace('<br>' , ' ', $value);
1250 $value = format_string($value);
1251 }
3b3535c7
DK
1252 } else {
1253 $value = lti_calculate_custom_parameter($value1);
e3f69b58 1254 }
1255 } else if ($islti2) {
1256 $val = $value;
1257 $services = lti_get_services();
1258 foreach ($services as $service) {
1259 $service->set_tool_proxy($toolproxy);
1260 $value = $service->parse_value($val);
1261 if ($val != $value) {
1262 break;
1263 }
1264 }
1265 }
1266 }
1267 }
1268 }
1269 return $value;
1270}
1271
3b3535c7
DK
1272/**
1273 * Calculates the value of a custom parameter that has not been specified earlier
1274 *
1275 * @param string $value Custom parameter value
1276 *
1277 * @return string Calculated value of custom parameter
1278 */
1279function lti_calculate_custom_parameter($value) {
1280 global $USER, $COURSE;
1281
1282 switch ($value) {
1283 case 'Moodle.Person.userGroupIds':
1284 return implode(",", groups_get_user_groups($COURSE->id, $USER->id)[0]);
1285 }
1286 return null;
1287}
1288
b9b2e7bb
CS
1289/**
1290 * Used for building the names of the different custom parameters
1291 *
1292 * @param string $key Parameter name
e4f7cfe1 1293 * @param bool $tolower Do we want to convert the key into lower case?
b9b2e7bb
CS
1294 * @return string Processed name
1295 */
e4f7cfe1 1296function lti_map_keyname($key, $tolower = true) {
b9b2e7bb 1297 $newkey = "";
e4f7cfe1
AA
1298 if ($tolower) {
1299 $key = core_text::strtolower(trim($key));
1300 }
b9b2e7bb 1301 foreach (str_split($key) as $ch) {
e4f7cfe1 1302 if ( ($ch >= 'a' && $ch <= 'z') || ($ch >= '0' && $ch <= '9') || (!$tolower && ($ch >= 'A' && $ch <= 'Z'))) {
b9b2e7bb
CS
1303 $newkey .= $ch;
1304 } else {
1305 $newkey .= '_';
1306 }
1307 }
1308 return $newkey;
1309}
1310
1311/**
16cac566 1312 * Gets the IMS role string for the specified user and LTI course module.
e27cb316 1313 *
e3f69b58 1314 * @param mixed $user User object or user id
1315 * @param int $cmid The course module id of the LTI activity
1316 * @param int $courseid The course id of the LTI activity
1317 * @param boolean $islti2 True if an LTI 2 tool is being launched
1318 *
16cac566 1319 * @return string A role string suitable for passing with an LTI launch
b9b2e7bb 1320 */
e3f69b58 1321function lti_get_ims_role($user, $cmid, $courseid, $islti2) {
16cac566 1322 $roles = array();
e27cb316 1323
ea04a9f9 1324 if (empty($cmid)) {
e3f69b58 1325 // If no cmid is passed, check if the user is a teacher in the course
1326 // This allows other modules to programmatically "fake" a launch without
1327 // a real LTI instance.
bef9a719 1328 $context = context_course::instance($courseid);
e27cb316 1329
bef9a719 1330 if (has_capability('moodle/course:manageactivities', $context, $user)) {
1d4f052e
CS
1331 array_push($roles, 'Instructor');
1332 } else {
1333 array_push($roles, 'Learner');
1334 }
16cac566 1335 } else {
c288a3db 1336 $context = context_module::instance($cmid);
1d4f052e 1337
ea04a9f9 1338 if (has_capability('mod/lti:manage', $context)) {
1d4f052e
CS
1339 array_push($roles, 'Instructor');
1340 } else {
1341 array_push($roles, 'Learner');
1342 }
b9b2e7bb 1343 }
1d4f052e 1344
1aa11df7
BH
1345 if (is_siteadmin($user) || has_capability('mod/lti:admin', $context)) {
1346 // Make sure admins do not have the Learner role, then set admin role.
1347 $roles = array_diff($roles, array('Learner'));
e3f69b58 1348 if (!$islti2) {
1349 array_push($roles, 'urn:lti:sysrole:ims/lis/Administrator', 'urn:lti:instrole:ims/lis/Administrator');
1350 } else {
1351 array_push($roles, 'http://purl.imsglobal.org/vocab/lis/v2/person#Administrator');
1352 }
b9b2e7bb 1353 }
e27cb316 1354
16cac566 1355 return join(',', $roles);
b9b2e7bb
CS
1356}
1357
1358/**
1359 * Returns configuration details for the tool
1360 *
1361 * @param int $typeid Basic LTI tool typeid
1362 *
1363 * @return array Tool Configuration
1364 */
1365function lti_get_type_config($typeid) {
1366 global $DB;
1367
5f136255
EL
1368 $query = "SELECT name, value
1369 FROM {lti_types_config}
1370 WHERE typeid = :typeid1
1371 UNION ALL
6c43d831 1372 SELECT 'toolurl' AS name, baseurl AS value
5f136255 1373 FROM {lti_types}
3f358828 1374 WHERE id = :typeid2
1375 UNION ALL
6c43d831 1376 SELECT 'icon' AS name, icon AS value
3f358828 1377 FROM {lti_types}
1378 WHERE id = :typeid3
1379 UNION ALL
6c43d831 1380 SELECT 'secureicon' AS name, secureicon AS value
3f358828 1381 FROM {lti_types}
1382 WHERE id = :typeid4";
e27cb316 1383
b9b2e7bb 1384 $typeconfig = array();
3f358828 1385 $configs = $DB->get_records_sql($query,
1386 array('typeid1' => $typeid, 'typeid2' => $typeid, 'typeid3' => $typeid, 'typeid4' => $typeid));
e27cb316 1387
b9b2e7bb
CS
1388 if (!empty($configs)) {
1389 foreach ($configs as $config) {
1390 $typeconfig[$config->name] = $config->value;
1391 }
1392 }
e27cb316 1393
b9b2e7bb
CS
1394 return $typeconfig;
1395}
1396
ea04a9f9 1397function lti_get_tools_by_url($url, $state, $courseid = null) {
b9b2e7bb 1398 $domain = lti_get_domain_from_url($url);
e27cb316 1399
996b0fd9 1400 return lti_get_tools_by_domain($domain, $state, $courseid);
b9b2e7bb
CS
1401}
1402
ea04a9f9 1403function lti_get_tools_by_domain($domain, $state = null, $courseid = null) {
b9b2e7bb 1404 global $DB, $SITE;
e27cb316 1405
b9b2e7bb 1406 $filters = array('tooldomain' => $domain);
e27cb316 1407
b9b2e7bb
CS
1408 $statefilter = '';
1409 $coursefilter = '';
e27cb316 1410
ea04a9f9 1411 if ($state) {
b9b2e7bb
CS
1412 $statefilter = 'AND state = :state';
1413 }
e27cb316 1414
ea04a9f9 1415 if ($courseid && $courseid != $SITE->id) {
b9b2e7bb
CS
1416 $coursefilter = 'OR course = :courseid';
1417 }
e27cb316 1418
5f136255
EL
1419 $query = "SELECT *
1420 FROM {lti_types}
1421 WHERE tooldomain = :tooldomain
1422 AND (course = :siteid $coursefilter)
1423 $statefilter";
e27cb316 1424
b9b2e7bb 1425 return $DB->get_records_sql($query, array(
e27cb316
CS
1426 'courseid' => $courseid,
1427 'siteid' => $SITE->id,
1428 'tooldomain' => $domain,
b9b2e7bb
CS
1429 'state' => $state
1430 ));
1431}
1432
1433/**
1434 * Returns all basicLTI tools configured by the administrator
1435 *
1436 */
6831c7cd 1437function lti_filter_get_types($course) {
b9b2e7bb
CS
1438 global $DB;
1439
ea04a9f9 1440 if (!empty($course)) {
e3f69b58 1441 $where = "WHERE t.course = :course";
1442 $params = array('course' => $course);
6831c7cd 1443 } else {
e3f69b58 1444 $where = '';
1445 $params = array();
6831c7cd 1446 }
e3f69b58 1447 $query = "SELECT t.id, t.name, t.baseurl, t.state, t.toolproxyid, t.timecreated, tp.name tpname
1448 FROM {lti_types} t LEFT OUTER JOIN {lti_tool_proxies} tp ON t.toolproxyid = tp.id
1449 {$where}";
1450 return $DB->get_records_sql($query, $params);
b9b2e7bb
CS
1451}
1452
d81a603e
MN
1453/**
1454 * Given an array of tools, filter them based on their state
1455 *
1456 * @param array $tools An array of lti_types records
1457 * @param int $state One of the LTI_TOOL_STATE_* constants
1458 * @return array
1459 */
1460function lti_filter_tool_types(array $tools, $state) {
1461 $return = array();
1462 foreach ($tools as $key => $tool) {
1463 if ($tool->state == $state) {
1464 $return[$key] = $tool;
1465 }
1466 }
1467 return $return;
1468}
1469
01e8bfd7
JO
1470/**
1471 * Returns all lti types visible in this course
1472 *
1473 * @param int $courseid The id of the course to retieve types for
bff0a288
MG
1474 * @param array $coursevisible options for 'coursevisible' field,
1475 * default [LTI_COURSEVISIBLE_PRECONFIGURED, LTI_COURSEVISIBLE_ACTIVITYCHOOSER]
2348c137 1476 * @return stdClass[] All the lti types visible in the given course
01e8bfd7 1477 */
bff0a288 1478function lti_get_lti_types_by_course($courseid, $coursevisible = null) {
01e8bfd7 1479 global $DB, $SITE;
e27cb316 1480
bff0a288
MG
1481 if ($coursevisible === null) {
1482 $coursevisible = [LTI_COURSEVISIBLE_PRECONFIGURED, LTI_COURSEVISIBLE_ACTIVITYCHOOSER];
1483 }
1484
1485 list($coursevisiblesql, $coursevisparams) = $DB->get_in_or_equal($coursevisible, SQL_PARAMS_NAMED, 'coursevisible');
5f136255
EL
1486 $query = "SELECT *
1487 FROM {lti_types}
bff0a288 1488 WHERE coursevisible $coursevisiblesql
5f136255
EL
1489 AND (course = :siteid OR course = :courseid)
1490 AND state = :active";
e27cb316 1491
01e8bfd7 1492 return $DB->get_records_sql($query,
bff0a288 1493 array('siteid' => $SITE->id, 'courseid' => $courseid, 'active' => LTI_TOOL_STATE_CONFIGURED) + $coursevisparams);
01e8bfd7
JO
1494}
1495
1496/**
1497 * Returns tool types for lti add instance and edit page
1498 *
1499 * @return array Array of lti types
1500 */
1501function lti_get_types_for_add_instance() {
1502 global $COURSE;
1503 $admintypes = lti_get_lti_types_by_course($COURSE->id);
e27cb316 1504
b9b2e7bb 1505 $types = array();
e3f69b58 1506 $types[0] = (object)array('name' => get_string('automatic', 'lti'), 'course' => 0, 'toolproxyid' => null);
e27cb316 1507
ea04a9f9 1508 foreach ($admintypes as $type) {
16e8f130 1509 $types[$type->id] = $type;
b9b2e7bb 1510 }
e27cb316 1511
b9b2e7bb
CS
1512 return $types;
1513}
1514
01e8bfd7
JO
1515/**
1516 * Returns a list of configured types in the given course
1517 *
1518 * @param int $courseid The id of the course to retieve types for
2348c137 1519 * @param int $sectionreturn section to return to for forming the URLs
f418d899 1520 * @return array Array of lti types. Each element is object with properties: name, title, icon, help, helplink, link
01e8bfd7 1521 */
2348c137 1522function lti_get_configured_types($courseid, $sectionreturn = 0) {
01e8bfd7
JO
1523 global $OUTPUT;
1524 $types = array();
bff0a288 1525 $admintypes = lti_get_lti_types_by_course($courseid, [LTI_COURSEVISIBLE_ACTIVITYCHOOSER]);
01e8bfd7
JO
1526
1527 foreach ($admintypes as $ltitype) {
1528 $type = new stdClass();
1529 $type->modclass = MOD_CLASS_ACTIVITY;
1530 $type->name = 'lti_type_' . $ltitype->id;
5c1b749d
JP
1531 // Clean the name. We don't want tags here.
1532 $type->title = clean_param($ltitype->name, PARAM_NOTAGS);
f418d899
JD
1533 $trimmeddescription = trim($ltitype->description);
1534 if ($trimmeddescription != '') {
5c1b749d
JP
1535 // Clean the description. We don't want tags here.
1536 $type->help = clean_param($trimmeddescription, PARAM_NOTAGS);
f418d899
JD
1537 $type->helplink = get_string('modulename_shortcut_link', 'lti');
1538 }
01e8bfd7
JO
1539 if (empty($ltitype->icon)) {
1540 $type->icon = $OUTPUT->pix_icon('icon', '', 'lti', array('class' => 'icon'));
1541 } else {
1542 $type->icon = html_writer::empty_tag('img', array('src' => $ltitype->icon, 'alt' => $ltitype->name, 'class' => 'icon'));
1543 }
2348c137
MG
1544 $type->link = new moodle_url('/course/modedit.php', array('add' => 'lti', 'return' => 0, 'course' => $courseid,
1545 'sr' => $sectionreturn, 'typeid' => $ltitype->id));
01e8bfd7
JO
1546 $types[] = $type;
1547 }
1548 return $types;
1549}
1550
ea04a9f9 1551function lti_get_domain_from_url($url) {
b9b2e7bb 1552 $matches = array();
e27cb316 1553
ea04a9f9 1554 if (preg_match(LTI_URL_DOMAIN_REGEX, $url, $matches)) {
b9b2e7bb
CS
1555 return $matches[1];
1556 }
1557}
1558
ea04a9f9 1559function lti_get_tool_by_url_match($url, $courseid = null, $state = LTI_TOOL_STATE_CONFIGURED) {
b69dc429 1560 $possibletools = lti_get_tools_by_url($url, $state, $courseid);
e27cb316 1561
7023b65e 1562 return lti_get_best_tool_by_url($url, $possibletools, $courseid);
b9b2e7bb
CS
1563}
1564
ea04a9f9 1565function lti_get_url_thumbprint($url) {
8fa50fdd
MN
1566 // Parse URL requires a schema otherwise everything goes into 'path'. Fixed 5.4.7 or later.
1567 if (preg_match('/https?:\/\//', $url) !== 1) {
1568 $url = 'http://'.$url;
1569 }
b9b2e7bb 1570 $urlparts = parse_url(strtolower($url));
ea04a9f9 1571 if (!isset($urlparts['path'])) {
b9b2e7bb
CS
1572 $urlparts['path'] = '';
1573 }
e27cb316 1574
6b7e523b
FM
1575 if (!isset($urlparts['query'])) {
1576 $urlparts['query'] = '';
1577 }
1578
ea04a9f9 1579 if (!isset($urlparts['host'])) {
d8d04121
CS
1580 $urlparts['host'] = '';
1581 }
e27cb316 1582
ea04a9f9 1583 if (substr($urlparts['host'], 0, 4) === 'www.') {
d8d04121 1584 $urlparts['host'] = substr($urlparts['host'], 4);
b9b2e7bb 1585 }
e27cb316 1586
6b7e523b
FM
1587 $urllower = $urlparts['host'] . '/' . $urlparts['path'];
1588
1589 if ($urlparts['query'] != '') {
1590 $urllower .= '?' . $urlparts['query'];
1591 }
1592
1593 return $urllower;
b9b2e7bb
CS
1594}
1595
ea04a9f9
EL
1596function lti_get_best_tool_by_url($url, $tools, $courseid = null) {
1597 if (count($tools) === 0) {
b9b2e7bb
CS
1598 return null;
1599 }
e27cb316 1600
b9b2e7bb 1601 $urllower = lti_get_url_thumbprint($url);
e27cb316 1602
ea04a9f9 1603 foreach ($tools as $tool) {
b9b2e7bb 1604 $tool->_matchscore = 0;
e27cb316 1605
b9b2e7bb 1606 $toolbaseurllower = lti_get_url_thumbprint($tool->baseurl);
e27cb316 1607
ea04a9f9 1608 if ($urllower === $toolbaseurllower) {
e3f69b58 1609 // 100 points for exact thumbprint match.
b9b2e7bb 1610 $tool->_matchscore += 100;
ea04a9f9 1611 } else if (substr($urllower, 0, strlen($toolbaseurllower)) === $toolbaseurllower) {
e3f69b58 1612 // 50 points if tool thumbprint starts with the base URL thumbprint.
b9b2e7bb
CS
1613 $tool->_matchscore += 50;
1614 }
e27cb316 1615
e3f69b58 1616 // Prefer course tools over site tools.
ea04a9f9 1617 if (!empty($courseid)) {
e3f69b58 1618 // Minus 10 points for not matching the course id (global tools).
ea04a9f9 1619 if ($tool->course != $courseid) {
7023b65e
CS
1620 $tool->_matchscore -= 10;
1621 }
1622 }
b9b2e7bb 1623 }
e27cb316 1624
ea04a9f9
EL
1625 $bestmatch = array_reduce($tools, function($value, $tool) {
1626 if ($tool->_matchscore > $value->_matchscore) {
b9b2e7bb
CS
1627 return $tool;
1628 } else {
1629 return $value;
1630 }
e27cb316 1631
b9b2e7bb 1632 }, (object)array('_matchscore' => -1));
e27cb316 1633
e3f69b58 1634 // None of the tools are suitable for this URL.
ea04a9f9 1635 if ($bestmatch->_matchscore <= 0) {
b9b2e7bb
CS
1636 return null;
1637 }
e27cb316 1638
b9b2e7bb
CS
1639 return $bestmatch;
1640}
1641
ea04a9f9 1642function lti_get_shared_secrets_by_key($key) {
020eea1b 1643 global $DB;
e27cb316 1644
e3f69b58 1645 // Look up the shared secret for the specified key in both the types_config table (for configured tools)
1646 // And in the lti resource table for ad-hoc tools.
5f136255
EL
1647 $query = "SELECT t2.value
1648 FROM {lti_types_config} t1
1649 JOIN {lti_types_config} t2 ON t1.typeid = t2.typeid
1650 JOIN {lti_types} type ON t2.typeid = type.id
1651 WHERE t1.name = 'resourcekey'
1652 AND t1.value = :key1
1653 AND t2.name = 'password'
e3f69b58 1654 AND type.state = :configured1
1655 UNION
1656 SELECT tp.secret AS value
1657 FROM {lti_tool_proxies} tp
1658 JOIN {lti_types} t ON tp.id = t.toolproxyid
1659 WHERE tp.guid = :key2
1660 AND t.state = :configured2
5f136255
EL
1661 UNION
1662 SELECT password AS value
1663 FROM {lti}
e3f69b58 1664 WHERE resourcekey = :key3";
e27cb316 1665
e3f69b58 1666 $sharedsecrets = $DB->get_records_sql($query, array('configured1' => LTI_TOOL_STATE_CONFIGURED,
1667 'configured2' => LTI_TOOL_STATE_CONFIGURED, 'key1' => $key, 'key2' => $key, 'key3' => $key));
e27cb316 1668
ea04a9f9 1669 $values = array_map(function($item) {
020eea1b
CS
1670 return $item->value;
1671 }, $sharedsecrets);
e27cb316 1672
e3f69b58 1673 // There should really only be one shared secret per key. But, we can't prevent
1674 // more than one getting entered. For instance, if the same key is used for two tool providers.
020eea1b
CS
1675 return $values;
1676}
1677
b9b2e7bb
CS
1678/**
1679 * Delete a Basic LTI configuration
1680 *
1681 * @param int $id Configuration id
1682 */
1683function lti_delete_type($id) {
1684 global $DB;
1685
e3f69b58 1686 // We should probably just copy the launch URL to the tool instances in this case... using a single query.
b9b2e7bb
CS
1687 /*
1688 $instances = $DB->get_records('lti', array('typeid' => $id));
1689 foreach ($instances as $instance) {
1690 $instance->typeid = 0;
1691 $DB->update_record('lti', $instance);
1692 }*/
1693
1694 $DB->delete_records('lti_types', array('id' => $id));
1695 $DB->delete_records('lti_types_config', array('typeid' => $id));
1696}
1697
ea04a9f9 1698function lti_set_state_for_type($id, $state) {
b9b2e7bb 1699 global $DB;
e27cb316 1700
b9b2e7bb
CS
1701 $DB->update_record('lti_types', array('id' => $id, 'state' => $state));
1702}
1703
1704/**
1705 * Transforms a basic LTI object to an array
1706 *
1707 * @param object $ltiobject Basic LTI object
1708 *
1709 * @return array Basic LTI configuration details
1710 */
1711function lti_get_config($ltiobject) {
1712 $typeconfig = array();
1713 $typeconfig = (array)$ltiobject;
1714 $additionalconfig = lti_get_type_config($ltiobject->typeid);
1715 $typeconfig = array_merge($typeconfig, $additionalconfig);
1716 return $typeconfig;
1717}
1718
1719/**
1720 *
1721 * Generates some of the tool configuration based on the instance details
1722 *
1723 * @param int $id
1724 *
1725 * @return Instance configuration
1726 *
1727 */
1728function lti_get_type_config_from_instance($id) {
1729 global $DB;
1730
1731 $instance = $DB->get_record('lti', array('id' => $id));
1732 $config = lti_get_config($instance);
1733
e3f69b58 1734 $type = new \stdClass();
b9b2e7bb
CS
1735 $type->lti_fix = $id;
1736 if (isset($config['toolurl'])) {
1737 $type->lti_toolurl = $config['toolurl'];
1738 }
1739 if (isset($config['instructorchoicesendname'])) {
1740 $type->lti_sendname = $config['instructorchoicesendname'];
1741 }
1742 if (isset($config['instructorchoicesendemailaddr'])) {
1743 $type->lti_sendemailaddr = $config['instructorchoicesendemailaddr'];
1744 }
1745 if (isset($config['instructorchoiceacceptgrades'])) {
1746 $type->lti_acceptgrades = $config['instructorchoiceacceptgrades'];
1747 }
1748 if (isset($config['instructorchoiceallowroster'])) {
1749 $type->lti_allowroster = $config['instructorchoiceallowroster'];
1750 }
1751
1752 if (isset($config['instructorcustomparameters'])) {
1753 $type->lti_allowsetting = $config['instructorcustomparameters'];
1754 }
1755 return $type;
1756}
1757
1758/**
1759 * Generates some of the tool configuration based on the admin configuration details
1760 *
1761 * @param int $id
1762 *
1763 * @return Configuration details
1764 */
1765function lti_get_type_type_config($id) {
1766 global $DB;
1767
1768 $basicltitype = $DB->get_record('lti_types', array('id' => $id));
1769 $config = lti_get_type_config($id);
1770
e3f69b58 1771 $type = new \stdClass();
5f72d102 1772
b9b2e7bb 1773 $type->lti_typename = $basicltitype->name;
e27cb316 1774
6831c7cd 1775 $type->typeid = $basicltitype->id;
e27cb316 1776
e3f69b58 1777 $type->toolproxyid = $basicltitype->toolproxyid;
1778
b9b2e7bb 1779 $type->lti_toolurl = $basicltitype->baseurl;
e27cb316 1780
cc193e0d
RW
1781 $type->lti_description = $basicltitype->description;
1782
e3f69b58 1783 $type->lti_parameters = $basicltitype->parameter;
1784
3f358828 1785 $type->lti_icon = $basicltitype->icon;
1786
1787 $type->lti_secureicon = $basicltitype->secureicon;
1788
b9b2e7bb
CS
1789 if (isset($config['resourcekey'])) {
1790 $type->lti_resourcekey = $config['resourcekey'];
1791 }
1792 if (isset($config['password'])) {
1793 $type->lti_password = $config['password'];
1794 }
1795
1796 if (isset($config['sendname'])) {
1797 $type->lti_sendname = $config['sendname'];
1798 }
ea04a9f9 1799 if (isset($config['instructorchoicesendname'])) {
b9b2e7bb
CS
1800 $type->lti_instructorchoicesendname = $config['instructorchoicesendname'];
1801 }
ea04a9f9 1802 if (isset($config['sendemailaddr'])) {
b9b2e7bb
CS
1803 $type->lti_sendemailaddr = $config['sendemailaddr'];
1804 }
ea04a9f9 1805 if (isset($config['instructorchoicesendemailaddr'])) {
b9b2e7bb
CS
1806 $type->lti_instructorchoicesendemailaddr = $config['instructorchoicesendemailaddr'];
1807 }
ea04a9f9 1808 if (isset($config['acceptgrades'])) {
b9b2e7bb
CS
1809 $type->lti_acceptgrades = $config['acceptgrades'];
1810 }
ea04a9f9 1811 if (isset($config['instructorchoiceacceptgrades'])) {
b9b2e7bb
CS
1812 $type->lti_instructorchoiceacceptgrades = $config['instructorchoiceacceptgrades'];
1813 }
ea04a9f9 1814 if (isset($config['allowroster'])) {
b9b2e7bb
CS
1815 $type->lti_allowroster = $config['allowroster'];
1816 }
ea04a9f9 1817 if (isset($config['instructorchoiceallowroster'])) {
b9b2e7bb
CS
1818 $type->lti_instructorchoiceallowroster = $config['instructorchoiceallowroster'];
1819 }
1820
1821 if (isset($config['customparameters'])) {
1822 $type->lti_customparameters = $config['customparameters'];
1823 }
1824
ea04a9f9 1825 if (isset($config['forcessl'])) {
d8d04121
CS
1826 $type->lti_forcessl = $config['forcessl'];
1827 }
e27cb316 1828
b9b2e7bb
CS
1829 if (isset($config['organizationid'])) {
1830 $type->lti_organizationid = $config['organizationid'];
1831 }
1832 if (isset($config['organizationurl'])) {
1833 $type->lti_organizationurl = $config['organizationurl'];
1834 }
1835 if (isset($config['organizationdescr'])) {
1836 $type->lti_organizationdescr = $config['organizationdescr'];
1837 }
1838 if (isset($config['launchcontainer'])) {
1839 $type->lti_launchcontainer = $config['launchcontainer'];
1840 }
e27cb316 1841
b9b2e7bb
CS
1842 if (isset($config['coursevisible'])) {
1843 $type->lti_coursevisible = $config['coursevisible'];
1844 }
e27cb316 1845
d8f9109a 1846 if (isset($config['contentitem'])) {
1847 $type->lti_contentitem = $config['contentitem'];
1848 }
1849
b9b2e7bb
CS
1850 if (isset($config['debuglaunch'])) {
1851 $type->lti_debuglaunch = $config['debuglaunch'];
1852 }
e27cb316 1853
b9b2e7bb 1854 if (isset($config['module_class_type'])) {
036e84c3 1855 $type->lti_module_class_type = $config['module_class_type'];
b9b2e7bb
CS
1856 }
1857
1858 return $type;
1859}
1860
ea04a9f9 1861function lti_prepare_type_for_save($type, $config) {
e3f69b58 1862 if (isset($config->lti_toolurl)) {
1863 $type->baseurl = $config->lti_toolurl;
1864 $type->tooldomain = lti_get_domain_from_url($config->lti_toolurl);
1865 }
cc193e0d
RW
1866 if (isset($config->lti_description)) {
1867 $type->description = $config->lti_description;
1868 }
e3f69b58 1869 if (isset($config->lti_typename)) {
1870 $type->name = $config->lti_typename;
1871 }
af9d3a92 1872 if (isset($config->lti_coursevisible)) {
692b5348 1873 $type->coursevisible = $config->lti_coursevisible;
af9d3a92 1874 }
e27cb316 1875
3f358828 1876 if (isset($config->lti_icon)) {
1877 $type->icon = $config->lti_icon;
1878 }
1879 if (isset($config->lti_secureicon)) {
1880 $type->secureicon = $config->lti_secureicon;
1881 }
1882
0c9c4669
SC
1883 $type->forcessl = !empty($config->lti_forcessl) ? $config->lti_forcessl : 0;
1884 $config->lti_forcessl = $type->forcessl;
d8f9109a 1885 if (isset($config->lti_contentitem)) {
1886 $type->contentitem = !empty($config->lti_contentitem) ? $config->lti_contentitem : 0;
1887 $config->lti_contentitem = $type->contentitem;
1888 }
e27cb316 1889
b9b2e7bb 1890 $type->timemodified = time();
e27cb316 1891
b9b2e7bb
CS
1892 unset ($config->lti_typename);
1893 unset ($config->lti_toolurl);
cc193e0d 1894 unset ($config->lti_description);
3f358828 1895 unset ($config->lti_icon);
1896 unset ($config->lti_secureicon);
b9b2e7bb
CS
1897}
1898
ea04a9f9 1899function lti_update_type($type, $config) {
3f358828 1900 global $DB, $CFG;
e27cb316 1901
b9b2e7bb 1902 lti_prepare_type_for_save($type, $config);
e27cb316 1903
3f358828 1904 $clearcache = false;
1905 if (lti_request_is_using_ssl() && !empty($type->secureicon)) {
1906 $clearcache = !isset($config->oldicon) || ($config->oldicon !== $type->secureicon);
1907 } else {
1908 $clearcache = isset($type->icon) && (!isset($config->oldicon) || ($config->oldicon !== $type->icon));
1909 }
1910 unset($config->oldicon);
1911
b9b2e7bb
CS
1912 if ($DB->update_record('lti_types', $type)) {
1913 foreach ($config as $key => $value) {
e3f69b58 1914 if (substr($key, 0, 4) == 'lti_' && !is_null($value)) {
1915 $record = new \StdClass();
b9b2e7bb
CS
1916 $record->typeid = $type->id;
1917 $record->name = substr($key, 4);
1918 $record->value = $value;
b9b2e7bb
CS
1919 lti_update_config($record);
1920 }
1921 }
3f358828 1922 require_once($CFG->libdir.'/modinfolib.php');
1923 if ($clearcache) {
99a5377b
NH
1924 $sql = "SELECT DISTINCT course
1925 FROM {lti}
1926 WHERE typeid = ?";
1927
1928 $courses = $DB->get_fieldset_sql($sql, array($type->id));
1929
1930 foreach ($courses as $courseid) {
1931 rebuild_course_cache($courseid, true);
1932 }
3f358828 1933 }
b9b2e7bb
CS
1934 }
1935}
1936
ea04a9f9 1937function lti_add_type($type, $config) {
b9b2e7bb 1938 global $USER, $SITE, $DB;
e27cb316 1939
b9b2e7bb 1940 lti_prepare_type_for_save($type, $config);
e27cb316 1941
ea04a9f9 1942 if (!isset($type->state)) {
b9b2e7bb
CS
1943 $type->state = LTI_TOOL_STATE_PENDING;
1944 }
e27cb316 1945
ea04a9f9 1946 if (!isset($type->timecreated)) {
b9b2e7bb
CS
1947 $type->timecreated = time();
1948 }
e27cb316 1949
ea04a9f9 1950 if (!isset($type->createdby)) {
b9b2e7bb
CS
1951 $type->createdby = $USER->id;
1952 }
e27cb316 1953
ea04a9f9 1954 if (!isset($type->course)) {
b9b2e7bb
CS
1955 $type->course = $SITE->id;
1956 }
e27cb316 1957
e3f69b58 1958 // Create a salt value to be used for signing passed data to extension services
1959 // The outcome service uses the service salt on the instance. This can be used
1960 // for communication with services not related to a specific LTI instance.
b9b2e7bb
CS
1961 $config->lti_servicesalt = uniqid('', true);
1962
1963 $id = $DB->insert_record('lti_types', $type);
1964
1965 if ($id) {
1966 foreach ($config as $key => $value) {
e3f69b58 1967 if (substr($key, 0, 4) == 'lti_' && !is_null($value)) {
1968 $record = new \StdClass();
b9b2e7bb
CS
1969 $record->typeid = $id;
1970 $record->name = substr($key, 4);
1971 $record->value = $value;
1972
1973 lti_add_config($record);
1974 }
1975 }
1976 }
e27cb316 1977
996b0fd9 1978 return $id;
b9b2e7bb
CS
1979}
1980
e3f69b58 1981/**
1982 * Given an array of tool proxies, filter them based on their state
1983 *
1984 * @param array $toolproxies An array of lti_tool_proxies records
1985 * @param int $state One of the LTI_TOOL_PROXY_STATE_* constants
1986 *
1987 * @return array
1988 */
1989function lti_filter_tool_proxy_types(array $toolproxies, $state) {
1990 $return = array();
1991 foreach ($toolproxies as $key => $toolproxy) {
1992 if ($toolproxy->state == $state) {
1993 $return[$key] = $toolproxy;
1994 }
1995 }
1996 return $return;
1997}
1998
1999/**
2000 * Get the tool proxy instance given its GUID
2001 *
2002 * @param string $toolproxyguid Tool proxy GUID value
2003 *
2004 * @return object
2005 */
2006function lti_get_tool_proxy_from_guid($toolproxyguid) {
2007 global $DB;
2008
2009 $toolproxy = $DB->get_record('lti_tool_proxies', array('guid' => $toolproxyguid));
2010
2011 return $toolproxy;
2012}
2013
cc193e0d
RW
2014/**
2015 * Get the tool proxy instance given its registration URL
2016 *
2017 * @param string $regurl Tool proxy registration URL
2018 *
af9d3a92 2019 * @return array The record of the tool proxy with this url
cc193e0d
RW
2020 */
2021function lti_get_tool_proxies_from_registration_url($regurl) {
2022 global $DB;
2023
2024 return $DB->get_records_sql(
2025 'SELECT * FROM {lti_tool_proxies}
2026 WHERE '.$DB->sql_compare_text('regurl', 256).' = :regurl',
2027 array('regurl' => $regurl)
2028 );
2029}
2030
e3f69b58 2031/**
2032 * Generates some of the tool proxy configuration based on the admin configuration details
2033 *
2034 * @param int $id
2035 *
2036 * @return Tool Proxy details
2037 */
2038function lti_get_tool_proxy($id) {
2039 global $DB;
2040
2041 $toolproxy = $DB->get_record('lti_tool_proxies', array('id' => $id));
2042 return $toolproxy;
2043}
2044
811d9ff9
JO
2045/**
2046 * Returns lti tool proxies.
2047 *
2048 * @param bool $orphanedonly Only retrieves tool proxies that have no type associated with them
2049 * @return array of basicLTI types
2050 */
2051function lti_get_tool_proxies($orphanedonly) {
2052 global $DB;
2053
2054 if ($orphanedonly) {
2055 $tools = $DB->get_records('lti_types');
2056 $usedproxyids = array_values($DB->get_fieldset_select('lti_types', 'toolproxyid', 'toolproxyid IS NOT NULL'));
2057 $proxies = $DB->get_records('lti_tool_proxies', null, 'state DESC, timemodified DESC');
2058 foreach ($proxies as $key => $value) {
2059 if (in_array($value->id, $usedproxyids)) {
2060 unset($proxies[$key]);
2061 }
2062 }
2063 return $proxies;
2064 } else {
2065 return $DB->get_records('lti_tool_proxies', null, 'state DESC, timemodified DESC');
2066 }
2067}
2068
e3f69b58 2069/**
2070 * Generates some of the tool proxy configuration based on the admin configuration details
2071 *
2072 * @param int $id
2073 *
2074 * @return Tool Proxy details
2075 */
2076function lti_get_tool_proxy_config($id) {
2077 $toolproxy = lti_get_tool_proxy($id);
2078
2079 $tp = new \stdClass();
2080 $tp->lti_registrationname = $toolproxy->name;
2081 $tp->toolproxyid = $toolproxy->id;
2082 $tp->state = $toolproxy->state;
2083 $tp->lti_registrationurl = $toolproxy->regurl;
2084 $tp->lti_capabilities = explode("\n", $toolproxy->capabilityoffered);
2085 $tp->lti_services = explode("\n", $toolproxy->serviceoffered);
2086
2087 return $tp;
2088}
2089
2090/**
2091 * Update the database with a tool proxy instance
2092 *
2093 * @param object $config Tool proxy definition
2094 *
2095 * @return int Record id number
2096 */
2097function lti_add_tool_proxy($config) {
2098 global $USER, $DB;
2099
2100 $toolproxy = new \stdClass();
2101 if (isset($config->lti_registrationname)) {
2102 $toolproxy->name = trim($config->lti_registrationname);
2103 }
2104 if (isset($config->lti_registrationurl)) {
2105 $toolproxy->regurl = trim($config->lti_registrationurl);
2106 }
2107 if (isset($config->lti_capabilities)) {
2108 $toolproxy->capabilityoffered = implode("\n", $config->lti_capabilities);
cc193e0d
RW
2109 } else {
2110 $toolproxy->capabilityoffered = implode("\n", array_keys(lti_get_capabilities()));
e3f69b58 2111 }
2112 if (isset($config->lti_services)) {
2113 $toolproxy->serviceoffered = implode("\n", $config->lti_services);
cc193e0d 2114 } else {
af9d3a92
JO
2115 $func = function($s) {
2116 return $s->get_id();
2117 };
cc193e0d
RW
2118 $servicenames = array_map($func, lti_get_services());
2119 $toolproxy->serviceoffered = implode("\n", $servicenames);
e3f69b58 2120 }
2121 if (isset($config->toolproxyid) && !empty($config->toolproxyid)) {
2122 $toolproxy->id = $config->toolproxyid;
2123 if (!isset($toolproxy->state) || ($toolproxy->state != LTI_TOOL_PROXY_STATE_ACCEPTED)) {
2124 $toolproxy->state = LTI_TOOL_PROXY_STATE_CONFIGURED;
2125 $toolproxy->guid = random_string();
2126 $toolproxy->secret = random_string();
2127 }
2128 $id = lti_update_tool_proxy($toolproxy);
2129 } else {
2130 $toolproxy->state = LTI_TOOL_PROXY_STATE_CONFIGURED;
2131 $toolproxy->timemodified = time();
2132 $toolproxy->timecreated = $toolproxy->timemodified;
2133 if (!isset($toolproxy->createdby)) {
2134 $toolproxy->createdby = $USER->id;
2135 }
2136 $toolproxy->guid = random_string();
2137 $toolproxy->secret = random_string();
2138 $id = $DB->insert_record('lti_tool_proxies', $toolproxy);
2139 }
2140
2141 return $id;
2142}
2143
2144/**
2145 * Updates a tool proxy in the database
2146 *
2147 * @param object $toolproxy Tool proxy
2148 *
2149 * @return int Record id number
2150 */
2151function lti_update_tool_proxy($toolproxy) {
2152 global $DB;
2153
2154 $toolproxy->timemodified = time();
2155 $id = $DB->update_record('lti_tool_proxies', $toolproxy);
2156
2157 return $id;
2158}
2159
2160/**
2161 * Delete a Tool Proxy
2162 *
2163 * @param int $id Tool Proxy id
2164 */
2165function lti_delete_tool_proxy($id) {
2166 global $DB;
2167 $DB->delete_records('lti_tool_settings', array('toolproxyid' => $id));
2168 $tools = $DB->get_records('lti_types', array('toolproxyid' => $id));
2169 foreach ($tools as $tool) {
2170 lti_delete_type($tool->id);
2171 }
2172 $DB->delete_records('lti_tool_proxies', array('id' => $id));
2173}
2174
b9b2e7bb
CS
2175/**
2176 * Add a tool configuration in the database
2177 *
e3f69b58 2178 * @param object $config Tool configuration
b9b2e7bb
CS
2179 *
2180 * @return int Record id number
2181 */
2182function lti_add_config($config) {
2183 global $DB;
2184
2185 return $DB->insert_record('lti_types_config', $config);
2186}
2187
2188/**
2189 * Updates a tool configuration in the database
2190 *
e3f69b58 2191 * @param object $config Tool configuration
b9b2e7bb
CS
2192 *
2193 * @return Record id number
2194 */
2195function lti_update_config($config) {
2196 global $DB;
2197
2198 $return = true;
2199 $old = $DB->get_record('lti_types_config', array('typeid' => $config->typeid, 'name' => $config->name));
e27cb316 2200
b9b2e7bb
CS
2201 if ($old) {
2202 $config->id = $old->id;
2203 $return = $DB->update_record('lti_types_config', $config);
2204 } else {
2205 $return = $DB->insert_record('lti_types_config', $config);
2206 }
2207 return $return;
2208}
2209
e3f69b58 2210/**
2211 * Gets the tool settings
2212 *
2213 * @param int $toolproxyid Id of tool proxy record
2214 * @param int $courseid Id of course (null if system settings)
2215 * @param int $instanceid Id of course module (null if system or context settings)
2216 *
2217 * @return array Array settings
2218 */
2219function lti_get_tool_settings($toolproxyid, $courseid = null, $instanceid = null) {
2220 global $DB;
2221
2222 $settings = array();
2223 $settingsstr = $DB->get_field('lti_tool_settings', 'settings', array('toolproxyid' => $toolproxyid,
2224 'course' => $courseid, 'coursemoduleid' => $instanceid));
2225 if ($settingsstr !== false) {
2226 $settings = json_decode($settingsstr, true);
2227 }
2228 return $settings;
2229}
2230
2231/**
2232 * Sets the tool settings (
2233 *
2234 * @param array $settings Array of settings
2235 * @param int $toolproxyid Id of tool proxy record
2236 * @param int $courseid Id of course (null if system settings)
2237 * @param int $instanceid Id of course module (null if system or context settings)
2238 */
2239function lti_set_tool_settings($settings, $toolproxyid, $courseid = null, $instanceid = null) {
2240 global $DB;
2241
2242 $json = json_encode($settings);
2243 $record = $DB->get_record('lti_tool_settings', array('toolproxyid' => $toolproxyid,
2244 'course' => $courseid, 'coursemoduleid' => $instanceid));
2245 if ($record !== false) {
2246 $DB->update_record('lti_tool_settings', array('id' => $record->id, 'settings' => $json, 'timemodified' => time()));
2247 } else {
2248 $record = new \stdClass();
2249 $record->toolproxyid = $toolproxyid;
2250 $record->course = $courseid;
2251 $record->coursemoduleid = $instanceid;
2252 $record->settings = $json;
2253 $record->timecreated = time();
2254 $record->timemodified = $record->timecreated;
2255 $DB->insert_record('lti_tool_settings', $record);
2256 }
2257}
2258
b9b2e7bb
CS
2259/**
2260 * Signs the petition to launch the external tool using OAuth
2261 *
2262 * @param $oldparms Parameters to be passed for signing
2263 * @param $endpoint url of the external tool
2264 * @param $method Method for sending the parameters (e.g. POST)
2265 * @param $oauth_consumoer_key Key
2266 * @param $oauth_consumoer_secret Secret
b9b2e7bb 2267 */
3dd9ca24 2268function lti_sign_parameters($oldparms, $endpoint, $method, $oauthconsumerkey, $oauthconsumersecret) {
e3f69b58 2269
b9b2e7bb 2270 $parms = $oldparms;
b9b2e7bb
CS
2271
2272 $testtoken = '';
e27cb316 2273
e3f69b58 2274 // TODO: Switch to core oauthlib once implemented - MDL-30149.
795dff01
CS
2275 $hmacmethod = new lti\OAuthSignatureMethod_HMAC_SHA1();
2276 $testconsumer = new lti\OAuthConsumer($oauthconsumerkey, $oauthconsumersecret, null);
795dff01 2277 $accreq = lti\OAuthRequest::from_consumer_and_token($testconsumer, $testtoken, $method, $endpoint, $parms);
b9b2e7bb
CS
2278 $accreq->sign_request($hmacmethod, $testconsumer, $testtoken);
2279
b9b2e7bb
CS
2280 $newparms = $accreq->get_parameters();
2281
2282 return $newparms;
2283}
2284
2285/**
2286 * Posts the launch petition HTML
2287 *
2288 * @param $newparms Signed parameters
2289 * @param $endpoint URL of the external tool
2290 * @param $debug Debug (true/false)
2291 */
dbb0fec9 2292function lti_post_launch_html($newparms, $endpoint, $debug=false) {
e3f69b58 2293 $r = "<form action=\"" . $endpoint .
2294 "\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" encType=\"application/x-www-form-urlencoded\">\n";
e27cb316 2295
e3f69b58 2296 // Contruct html for the launch parameters.
b9b2e7bb
CS
2297 foreach ($newparms as $key => $value) {
2298 $key = htmlspecialchars($key);
2299 $value = htmlspecialchars($value);
2300 if ( $key == "ext_submit" ) {
e3f69b58 2301 $r .= "<input type=\"submit\"";
b9b2e7bb 2302 } else {
e3f69b58 2303 $r .= "<input type=\"hidden\" name=\"{$key}\"";
b9b2e7bb 2304 }
e3f69b58 2305 $r .= " value=\"";
b9b2e7bb
CS
2306 $r .= $value;
2307 $r .= "\"/>\n";
2308 }
2309
2310 if ( $debug ) {
2311 $r .= "<script language=\"javascript\"> \n";
2312 $r .= " //<![CDATA[ \n";
2313 $r .= "function basicltiDebugToggle() {\n";
2314 $r .= " var ele = document.getElementById(\"basicltiDebug\");\n";
ea04a9f9 2315 $r .= " if (ele.style.display == \"block\") {\n";
b9b2e7bb
CS
2316 $r .= " ele.style.display = \"none\";\n";
2317 $r .= " }\n";
2318 $r .= " else {\n";
2319 $r .= " ele.style.display = \"block\";\n";
2320 $r .= " }\n";
2321 $r .= "} \n";
2322 $r .= " //]]> \n";
2323 $r .= "</script>\n";
2324 $r .= "<a id=\"displayText\" href=\"javascript:basicltiDebugToggle();\">";
2325 $r .= get_string("toggle_debug_data", "lti")."</a>\n";
2326 $r .= "<div id=\"basicltiDebug\" style=\"display:none\">\n";
e3f69b58 2327 $r .= "<b>".get_string("basiclti_endpoint", "lti")."</b><br/>\n";
b9b2e7bb 2328 $r .= $endpoint . "<br/>\n&nbsp;<br/>\n";
e3f69b58 2329 $r .= "<b>".get_string("basiclti_parameters", "lti")."</b><br/>\n";
b9b2e7bb
CS
2330 foreach ($newparms as $key => $value) {
2331 $key = htmlspecialchars($key);
2332 $value = htmlspecialchars($value);
2333 $r .= "$key = $value<br/>\n";
2334 }
2335 $r .= "&nbsp;<br/>\n";
b9b2e7bb
CS
2336 $r .= "</div>\n";
2337 }
2338 $r .= "</form>\n";
2339
2340 if ( ! $debug ) {
b9b2e7bb
CS
2341 $r .= " <script type=\"text/javascript\"> \n" .
2342 " //<![CDATA[ \n" .
b9b2e7bb
CS
2343 " document.ltiLaunchForm.submit(); \n" .
2344 " //]]> \n" .
2345 " </script> \n";
2346 }
2347 return $r;
2348}
2349
ea04a9f9 2350function lti_get_type($typeid) {
996b0fd9 2351 global $DB;
e27cb316 2352
996b0fd9 2353 return $DB->get_record('lti_types', array('id' => $typeid));
57d1dffd
CS
2354}
2355
ea04a9f9
EL
2356function lti_get_launch_container($lti, $toolconfig) {
2357 if (empty($lti->launchcontainer)) {
e27cb316
CS
2358 $lti->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT;
2359 }
2360
ea04a9f9
EL
2361 if ($lti->launchcontainer == LTI_LAUNCH_CONTAINER_DEFAULT) {
2362 if (isset($toolconfig['launchcontainer'])) {
c4d80efe
CS
2363 $launchcontainer = $toolconfig['launchcontainer'];
2364 }
2365 } else {
2366 $launchcontainer = $lti->launchcontainer;
2367 }
e27cb316 2368
ea04a9f9 2369 if (empty($launchcontainer) || $launchcontainer == LTI_LAUNCH_CONTAINER_DEFAULT) {
c4d80efe
CS
2370 $launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
2371 }
57d1dffd 2372
c3d2fbf9 2373 $devicetype = core_useragent::get_device_type();
57d1dffd 2374
e3f69b58 2375 // Scrolling within the object element doesn't work on iOS or Android
2376 // Opening the popup window also had some issues in testing
2377 // For mobile devices, always take up the entire screen to ensure the best experience.
c3d2fbf9 2378 if ($devicetype === core_useragent::DEVICETYPE_MOBILE || $devicetype === core_useragent::DEVICETYPE_TABLET ) {
57d1dffd
CS
2379 $launchcontainer = LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW;
2380 }
e27cb316 2381
57d1dffd 2382 return $launchcontainer;
d8d04121
CS
2383}
2384
2385function lti_request_is_using_ssl() {
33dca156 2386 global $CFG;
672f4836 2387 return (stripos($CFG->wwwroot, 'https://') === 0);
d8d04121
CS
2388}
2389
ea04a9f9
EL
2390function lti_ensure_url_is_https($url) {
2391 if (!strstr($url, '://')) {
d8d04121
CS
2392 $url = 'https://' . $url;
2393 } else {
e3f69b58 2394 // If the URL starts with http, replace with https.
ea04a9f9 2395 if (stripos($url, 'http://') === 0) {
adc23cc9 2396 $url = 'https://' . substr($url, 7);
d8d04121
CS
2397 }
2398 }
e27cb316 2399
d8d04121 2400 return $url;
61eb12d4 2401}
8fa50fdd
MN
2402
2403/**
2404 * Determines if we should try to log the request
2405 *
2406 * @param string $rawbody
2407 * @return bool
2408 */
2409function lti_should_log_request($rawbody) {
2410 global $CFG;
2411
2412 if (empty($CFG->mod_lti_log_users)) {
2413 return false;
2414 }
2415
2416 $logusers = explode(',', $CFG->mod_lti_log_users);
2417 if (empty($logusers)) {
2418 return false;
2419 }
2420
2421 try {
e3f69b58 2422 $xml = new \SimpleXMLElement($rawbody);
8fa50fdd
MN
2423 $ns = $xml->getNamespaces();
2424 $ns = array_shift($ns);
2425 $xml->registerXPathNamespace('lti', $ns);
2426 $requestuserid = '';
2427 if ($node = $xml->xpath('//lti:userId')) {
2428 $node = $node[0];
2429 $requestuserid = clean_param((string) $node, PARAM_INT);
2430 } else if ($node = $xml->xpath('//lti:sourcedId')) {
2431 $node = $node[0];
2432 $resultjson = json_decode((string) $node);
2433 $requestuserid = clean_param($resultjson->data->userid, PARAM_INT);
2434 }
2435 } catch (Exception $e) {
2436 return false;
2437 }
2438
2439 if (empty($requestuserid) or !in_array($requestuserid, $logusers)) {
2440 return false;
2441 }
2442
2443 return true;
2444}
2445
2446/**
233b677b 2447 * Logs the request to a file in temp dir.
8fa50fdd
MN
2448 *
2449 * @param string $rawbody
2450 */
2451function lti_log_request($rawbody) {
2452 if ($tempdir = make_temp_directory('mod_lti', false)) {
2453 if ($tempfile = tempnam($tempdir, 'mod_lti_request'.date('YmdHis'))) {
00e27060
MN
2454 $content = "Request Headers:\n";
2455 foreach (moodle\mod\lti\OAuthUtil::get_headers() as $header => $value) {
2456 $content .= "$header: $value\n";
2457 }
2458 $content .= "Request Body:\n";
2459 $content .= $rawbody;
2460
2461 file_put_contents($tempfile, $content);
2462 chmod($tempfile, 0644);
2463 }
2464 }
2465}
2466
2467/**
233b677b 2468 * Log an LTI response.
00e27060
MN
2469 *
2470 * @param string $responsexml The response XML
2471 * @param Exception $e If there was an exception, pass that too
2472 */
2473function lti_log_response($responsexml, $e = null) {
2474 if ($tempdir = make_temp_directory('mod_lti', false)) {
2475 if ($tempfile = tempnam($tempdir, 'mod_lti_response'.date('YmdHis'))) {
2476 $content = '';
2477 if ($e instanceof Exception) {
2478 $info = get_exception_info($e);
2479
2480 $content .= "Exception:\n";
2481 $content .= "Message: $info->message\n";
2482 $content .= "Debug info: $info->debuginfo\n";
2483 $content .= "Backtrace:\n";
2484 $content .= format_backtrace($info->backtrace, true);
2485 $content .= "\n";
2486 }
2487 $content .= "Response XML:\n";
2488 $content .= $responsexml;
2489
2490 file_put_contents($tempfile, $content);
8fa50fdd
MN
2491 chmod($tempfile, 0644);
2492 }
2493 }
2494}
2495
2496/**
2497 * Fetches LTI type configuration for an LTI instance
2498 *
2499 * @param stdClass $instance
2500 * @return array Can be empty if no type is found
2501 */
2502function lti_get_type_config_by_instance($instance) {
2503 $typeid = null;
2504 if (empty($instance->typeid)) {
2505 $tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course);
2506 if ($tool) {
2507 $typeid = $tool->id;
2508 }
2509 } else {
2510 $typeid = $instance->typeid;
2511 }
2512 if (!empty($typeid)) {
2513 return lti_get_type_config($typeid);
2514 }
2515 return array();
2516}
2517
2518/**
2519 * Enforce type config settings onto the LTI instance
2520 *
2521 * @param stdClass $instance
2522 * @param array $typeconfig
2523 */
2524function lti_force_type_config_settings($instance, array $typeconfig) {
2525 $forced = array(
2526 'instructorchoicesendname' => 'sendname',
2527 'instructorchoicesendemailaddr' => 'sendemailaddr',
2528 'instructorchoiceacceptgrades' => 'acceptgrades',
2529 );
2530
2531 foreach ($forced as $instanceparam => $typeconfigparam) {
2532 if (array_key_exists($typeconfigparam, $typeconfig) && $typeconfig[$typeconfigparam] != LTI_SETTING_DELEGATE) {
2533 $instance->$instanceparam = $typeconfig[$typeconfigparam];
2534 }
2535 }
2536}
2537
e3f69b58 2538/**
2539 * Initializes an array with the capabilities supported by the LTI module
2540 *
2541 * @return array List of capability names (without a dollar sign prefix)
2542 */
2543function lti_get_capabilities() {
2544
2545 $capabilities = array(
2546 'basic-lti-launch-request' => '',
d8f9109a 2547 'ContentItemSelectionRequest' => '',
d7d8db95 2548 'ToolProxyRegistrationRequest' => '',
e3f69b58 2549 'Context.id' => 'context_id',
d7d8db95
SV
2550 'Context.title' => 'context_title',
2551 'Context.label' => 'context_label',
2552 'Context.sourcedId' => 'lis_course_section_sourcedid',
2553 'Context.longDescription' => '$COURSE->summary',
2554 'Context.timeFrame.begin' => '$COURSE->startdate',
e3f69b58 2555 'CourseSection.title' => 'context_title',
2556 'CourseSection.label' => 'context_label',
2557 'CourseSection.sourcedId' => 'lis_course_section_sourcedid',
2558 'CourseSection.longDescription' => '$COURSE->summary',
2559 'CourseSection.timeFrame.begin' => '$COURSE->startdate',
2560 'ResourceLink.id' => 'resource_link_id',
2561 'ResourceLink.title' => 'resource_link_title',
2562 'ResourceLink.description' => 'resource_link_description',
2563 'User.id' => 'user_id',
2564 'User.username' => '$USER->username',
2565 'Person.name.full' => 'lis_person_name_full',
2566 'Person.name.given' => 'lis_person_name_given',
2567 'Person.name.family' => 'lis_person_name_family',
2568 'Person.email.primary' => 'lis_person_contact_email_primary',
2569 'Person.sourcedId' => 'lis_person_sourcedid',
2570 'Person.name.middle' => '$USER->middlename',
2571 'Person.address.street1' => '$USER->address',
2572 'Person.address.locality' => '$USER->city',
2573 'Person.address.country' => '$USER->country',
2574 'Person.address.timezone' => '$USER->timezone',
2575 'Person.phone.primary' => '$USER->phone1',
2576 'Person.phone.mobile' => '$USER->phone2',
2577 'Person.webaddress' => '$USER->url',
2578 'Membership.role' => 'roles',
2579 'Result.sourcedId' => 'lis_result_sourcedid',
3729ff0f 2580 'Result.autocreate' => 'lis_outcome_service_url',
3b3535c7 2581 'Moodle.Person.userGroupIds' => null);
e3f69b58 2582
2583 return $capabilities;
2584
2585}
2586
2587/**
2588 * Initializes an array with the services supported by the LTI module
2589 *
2590 * @return array List of services
2591 */
2592function lti_get_services() {
2593
2594 $services = array();
2595 $definedservices = core_component::get_plugin_list('ltiservice');
2596 foreach ($definedservices as $name => $location) {
2597 $classname = "\\ltiservice_{$name}\\local\\service\\{$name}";
2598 $services[] = new $classname();
2599 }
2600
2601 return $services;
2602
2603}
2604
2605/**
2606 * Initializes an instance of the named service
2607 *
2608 * @param string $servicename Name of service
2609 *
2610 * @return mod_lti\local\ltiservice\service_base Service
2611 */
2612function lti_get_service_by_name($servicename) {
2613
2614 $service = false;
2615 $classname = "\\ltiservice_{$servicename}\\local\\service\\{$servicename}";
2616 if (class_exists($classname)) {
2617 $service = new $classname();
2618 }
2619
2620 return $service;
2621
2622}
2623
2624/**
2625 * Finds a service by id
2626 *
2627 * @param array $services Array of services
2628 * @param string $resourceid ID of resource
2629 *
2630 * @return mod_lti\local\ltiservice\service_base Service
2631 */
2632function lti_get_service_by_resource_id($services, $resourceid) {
2633
2634 $service = false;
2635 foreach ($services as $aservice) {
2636 foreach ($aservice->get_resources() as $resource) {
2637 if ($resource->get_id() === $resourceid) {
2638 $service = $aservice;
2639 break 2;
2640 }
2641 }
2642 }
2643
2644 return $service;
2645
2646}
2647
2648/**
2649 * Extracts the named contexts from a tool proxy
2650 *
2651 * @param object $json
2652 *
2653 * @return array Contexts
2654 */
2655function lti_get_contexts($json) {
2656
2657 $contexts = array();
2658 if (isset($json->{'@context'})) {
2659 foreach ($json->{'@context'} as $context) {
2660 if (is_object($context)) {
2661 $contexts = array_merge(get_object_vars($context), $contexts);
2662 }
2663 }
2664 }
2665
2666 return $contexts;
2667
2668}
2669
2670/**
2671 * Converts an ID to a fully-qualified ID
2672 *
2673 * @param array $contexts
2674 * @param string $id
2675 *
2676 * @return string Fully-qualified ID
2677 */
2678function lti_get_fqid($contexts, $id) {
2679
2680 $parts = explode(':', $id, 2);
2681 if (count($parts) > 1) {
2682 if (array_key_exists($parts[0], $contexts)) {
2683 $id = $contexts[$parts[0]] . $parts[1];
2684 }
2685 }
2686
2687 return $id;
2688
2689}
cc193e0d 2690
af9d3a92
JO
2691/**
2692 * Returns the icon for the given tool type
2693 *
2694 * @param stdClass $type The tool type
2695 *
2696 * @return string The url to the tool type's corresponding icon
2697 */
cc193e0d
RW
2698function get_tool_type_icon_url(stdClass $type) {
2699 global $OUTPUT;
2700
2701 $iconurl = $type->secureicon;
2702
2703 if (empty($iconurl)) {
2704 $iconurl = $type->icon;
2705 }
2706
2707 if (empty($iconurl)) {
663640f5 2708 $iconurl = $OUTPUT->image_url('icon', 'lti')->out();
cc193e0d
RW
2709 }
2710
2711 return $iconurl;
2712}
2713
af9d3a92
JO
2714/**
2715 * Returns the edit url for the given tool type
2716 *
2717 * @param stdClass $type The tool type
2718 *
2719 * @return string The url to edit the tool type
2720 */
cc193e0d 2721function get_tool_type_edit_url(stdClass $type) {
af9d3a92
JO
2722 $url = new moodle_url('/mod/lti/typessettings.php',
2723 array('action' => 'update', 'id' => $type->id, 'sesskey' => sesskey(), 'returnto' => 'toolconfigure'));
cc193e0d
RW
2724 return $url->out();
2725}
2726
811d9ff9
JO
2727/**
2728 * Returns the edit url for the given tool proxy.
2729 *
2730 * @param stdClass $proxy The tool proxy
2731 *
2732 * @return string The url to edit the tool type
2733 */
2734function get_tool_proxy_edit_url(stdClass $proxy) {
2735 $url = new moodle_url('/mod/lti/registersettings.php',
2736 array('action' => 'update', 'id' => $proxy->id, 'sesskey' => sesskey(), 'returnto' => 'toolconfigure'));
2737 return $url->out();
2738}
2739
af9d3a92
JO
2740/**
2741 * Returns the course url for the given tool type
2742 *
2743 * @param stdClass $type The tool type
2744 *
2745 * @return string|void The url to the course of the tool type, void if it is a site wide type
2746 */
cc193e0d
RW
2747function get_tool_type_course_url(stdClass $type) {
2748 if ($type->course == 1) {
2749 return;
2750 } else {
2751 $url = new moodle_url('/course/view.php', array('id' => $type->course));
2752 return $url->out();
2753 }
2754}
2755
af9d3a92
JO
2756/**
2757 * Returns the icon and edit urls for the tool type and the course url if it is a course type.
2758 *
2759 * @param stdClass $type The tool type
2760 *
811d9ff9 2761 * @return string The urls of the tool type
af9d3a92 2762 */
cc193e0d
RW
2763function get_tool_type_urls(stdClass $type) {
2764 $courseurl = get_tool_type_course_url($type);
2765
2766 $urls = array(
2767 'icon' => get_tool_type_icon_url($type),
2768 'edit' => get_tool_type_edit_url($type),
2769 );
2770
2771 if ($courseurl) {
2772 $urls['course'] = $courseurl;
2773 }
2774
2775 return $urls;
2776}
2777
811d9ff9
JO
2778/**
2779 * Returns the icon and edit urls for the tool proxy.
2780 *
2781 * @param stdClass $proxy The tool proxy
2782 *
2783 * @return string The urls of the tool proxy
2784 */
2785function get_tool_proxy_urls(stdClass $proxy) {
2786 global $OUTPUT;
2787
2788 $urls = array(
663640f5 2789 'icon' => $OUTPUT->image_url('icon', 'lti')->out(),
811d9ff9
JO
2790 'edit' => get_tool_proxy_edit_url($proxy),
2791 );
2792
2793 return $urls;
2794}
2795
af9d3a92
JO
2796/**
2797 * Returns information on the current state of the tool type
2798 *
2799 * @param stdClass $type The tool type
2800 *
2801 * @return array An array with a text description of the state, and boolean for whether it is in each state:
2802 * pending, configured, rejected, unknown
2803 */
cc193e0d 2804function get_tool_type_state_info(stdClass $type) {
cc193e0d
RW
2805 $state = '';
2806 $isconfigured = false;
2807 $ispending = false;
cc193e0d
RW
2808 $isrejected = false;
2809 $isunknown = false;
2810 switch ($type->state) {
cc193e0d 2811 case LTI_TOOL_STATE_CONFIGURED:
af9d3a92 2812 $state = get_string('active', 'mod_lti');
cc193e0d
RW
2813 $isconfigured = true;
2814 break;
2815 case LTI_TOOL_STATE_PENDING:
af9d3a92 2816 $state = get_string('pending', 'mod_lti');
cc193e0d
RW
2817 $ispending = true;
2818 break;
2819 case LTI_TOOL_STATE_REJECTED:
af9d3a92 2820 $state = get_string('rejected', 'mod_lti');
cc193e0d
RW
2821 $isrejected = true;
2822 break;
2823 default:
af9d3a92 2824 $state = get_string('unknownstate', 'mod_lti');
cc193e0d
RW
2825 $isunknown = true;
2826 break;
2827 }
2828
2829 return array(
2830 'text' => $state,
2831 'pending' => $ispending,
2832 'configured' => $isconfigured,
2833 'rejected' => $isrejected,
cc193e0d
RW
2834 'unknown' => $isunknown
2835 );
2836}
2837
af9d3a92
JO
2838/**
2839 * Returns a summary of each LTI capability this tool type requires in plain language
2840 *
2841 * @param stdClass $type The tool type
2842 *
2843 * @return array An array of text descriptions of each of the capabilities this tool type requires
2844 */
cc193e0d
RW
2845function get_tool_type_capability_groups($type) {
2846 $capabilities = lti_get_enabled_capabilities($type);
2847 $groups = array();
af9d3a92
JO
2848 $hascourse = false;
2849 $hasactivities = false;
2850 $hasuseraccount = false;
2851 $hasuserpersonal = false;
cc193e0d 2852
cc193e0d
RW
2853 foreach ($capabilities as $capability) {
2854 // Bail out early if we've already found all groups.
2855 if (count($groups) >= 4) {
2856 continue;
2857 }
2858
af9d3a92
JO
2859 if (!$hascourse && preg_match('/^CourseSection/', $capability)) {
2860 $hascourse = true;
2861 $groups[] = get_string('courseinformation', 'mod_lti');
2862 } else if (!$hasactivities && preg_match('/^ResourceLink/', $capability)) {
2863 $hasactivities = true;
2864 $groups[] = get_string('courseactivitiesorresources', 'mod_lti');
2865 } else if (!$hasuseraccount && preg_match('/^User/', $capability) || preg_match('/^Membership/', $capability)) {
2866 $hasuseraccount = true;
2867 $groups[] = get_string('useraccountinformation', 'mod_lti');
2868 } else if (!$hasuserpersonal && preg_match('/^Person/', $capability)) {
2869 $hasuserpersonal = true;
2870 $groups[] = get_string('userpersonalinformation', 'mod_lti');
cc193e0d
RW
2871 }
2872 }
2873
2874 return $groups;
2875}
2876
af9d3a92
JO
2877
2878/**
2879 * Returns the ids of each instance of this tool type
2880 *
2881 * @param stdClass $type The tool type
2882 *
2883 * @return array An array of ids of the instances of this tool type
2884 */
cc193e0d
RW
2885function get_tool_type_instance_ids($type) {
2886 global $DB;
2887
af9d3a92 2888 return array_keys($DB->get_fieldset_select('lti', 'id', 'typeid = ?', array($type->id)));
cc193e0d
RW
2889}
2890
af9d3a92
JO
2891/**
2892 * Serialises this tool type
2893 *
2894 * @param stdClass $type The tool type
2895 *
2896 * @return array An array of values representing this type
2897 */
cc193e0d
RW
2898function serialise_tool_type(stdClass $type) {
2899 $capabilitygroups = get_tool_type_capability_groups($type);
2900 $instanceids = get_tool_type_instance_ids($type);
5c1b749d
JP
2901 // Clean the name. We don't want tags here.
2902 $name = clean_param($type->name, PARAM_NOTAGS);
2903 if (!empty($type->description)) {
2904 // Clean the description. We don't want tags here.
2905 $description = clean_param($type->description, PARAM_NOTAGS);
2906 } else {
2907 $description = get_string('editdescription', 'mod_lti');
2908 }
cc193e0d
RW
2909 return array(
2910 'id' => $type->id,
5c1b749d
JP
2911 'name' => $name,
2912 'description' => $description,
cc193e0d
RW
2913 'urls' => get_tool_type_urls($type),
2914 'state' => get_tool_type_state_info($type),
2915 'hascapabilitygroups' => !empty($capabilitygroups),
2916 'capabilitygroups' => $capabilitygroups,
2917 // Course ID of 1 means it's not linked to a course.
2918 'courseid' => $type->course == 1 ? 0 : $type->course,
2919 'instanceids' => $instanceids,
2920 'instancecount' => count($instanceids)
2921 );
2922}
af9d3a92 2923
811d9ff9
JO
2924/**
2925 * Serialises this tool proxy.
2926 *
2927 * @param stdClass $proxy The tool proxy
2928 *
2929 * @return array An array of values representing this type
2930 */
2931function serialise_tool_proxy(stdClass $proxy) {
2932 return array(
2933 'id' => $proxy->id,
2934 'name' => $proxy->name,
b1c8a860 2935 'description' => get_string('activatetoadddescription', 'mod_lti'),
811d9ff9
JO
2936 'urls' => get_tool_proxy_urls($proxy),
2937 'state' => array(
2938 'text' => get_string('pending', 'mod_lti'),
2939 'pending' => true,
2940 'configured' => false,
2941 'rejected' => false,
2942 'unknown' => false
2943 ),
2944 'hascapabilitygroups' => true,
2945 'capabilitygroups' => array(),
2946 'courseid' => 0,
2947 'instanceids' => array(),
2948 'instancecount' => 0
2949 );
2950}
2951
af9d3a92
JO
2952/**
2953 * Loads the cartridge information into the tool type, if the launch url is for a cartridge file
2954 *
2955 * @param stdClass $type The tool type object to be filled in
2956 * @since Moodle 3.1
2957 */
2958function lti_load_type_if_cartridge($type) {
2959 if (!empty($type->lti_toolurl) && lti_is_cartridge($type->lti_toolurl)) {
2960 lti_load_type_from_cartridge($type->lti_toolurl, $type);
2961 }
2962}
2963
2964/**
2965 * Loads the cartridge information into the new tool, if the launch url is for a cartridge file
2966 *
2967 * @param stdClass $lti The tools config
2968 * @since Moodle 3.1
2969 */
2970function lti_load_tool_if_cartridge($lti) {
2971 if (!empty($lti->toolurl) && lti_is_cartridge($lti->toolurl)) {
2972 lti_load_tool_from_cartridge($lti->toolurl, $lti);
2973 }
2974}
2975
2976/**
2977 * Determines if the given url is for a IMS basic cartridge
2978 *
2979 * @param string $url The url to be checked
2980 * @return True if the url is for a cartridge
2981 * @since Moodle 3.1
2982 */
2983function lti_is_cartridge($url) {
2984 // If it is empty, it's not a cartridge.
2985 if (empty($url)) {
2986 return false;
2987 }
2988 // If it has xml at the end of the url, it's a cartridge.
2989 if (preg_match('/\.xml$/', $url)) {
2990 return true;
2991 }
2992 // Even if it doesn't have .xml, load the url to check if it's a cartridge..
2993 try {
2994 $toolinfo = lti_load_cartridge($url,
2995 array(
2996 "launch_url" => "launchurl"
2997 )
2998 );
2999 if (!empty($toolinfo['launchurl'])) {
3000 return true;
3001 }
3002 } catch (moodle_exception $e) {
3003 return false; // Error loading the xml, so it's not a cartridge.
3004 }
3005 return false;
3006}
3007
3008/**
3009 * Allows you to load settings for an external tool type from an IMS cartridge.
3010 *
3011 * @param string $url The URL to the cartridge
3012 * @param stdClass $type The tool type object to be filled in
3013 * @throws moodle_exception if the cartridge could not be loaded correctly
3014 * @since Moodle 3.1
3015 */
3016function lti_load_type_from_cartridge($url, $type) {
3017 $toolinfo = lti_load_cartridge($url,
3018 array(
3019 "title" => "lti_typename",
3020 "launch_url" => "lti_toolurl",
c724c988
JD
3021 "description" => "lti_description",
3022 "icon" => "lti_icon",
3023 "secure_icon" => "lti_secureicon"
af9d3a92
JO
3024 ),
3025 array(
c724c988
JD
3026 "icon_url" => "lti_extension_icon",
3027 "secure_icon_url" => "lti_extension_secureicon"
af9d3a92
JO
3028 )
3029 );
4741c316
JD
3030 // If an activity name exists, unset the cartridge name so we don't override it.
3031 if (isset($type->lti_typename)) {
3032 unset($toolinfo['lti_typename']);
3033 }
c724c988
JD
3034
3035 // Always prefer cartridge core icons first, then, if none are found, look at the extension icons.
3036 if (empty($toolinfo['lti_icon']) && !empty($toolinfo['lti_extension_icon'])) {
3037 $toolinfo['lti_icon'] = $toolinfo['lti_extension_icon'];
3038 }
3039 unset($toolinfo['lti_extension_icon']);
3040
3041 if (empty($toolinfo['lti_secureicon']) && !empty($toolinfo['lti_extension_secureicon'])) {
3042 $toolinfo['lti_secureicon'] = $toolinfo['lti_extension_secureicon'];
3043 }
3044 unset($toolinfo['lti_extension_secureicon']);
3045
a33be61a
TL
3046 // Ensure Custom icons aren't overridden by cartridge params.
3047 if (!empty($type->lti_icon)) {
3048 unset($toolinfo['lti_icon']);
3049 }
3050
3051 if (!empty($type->lti_secureicon)) {
3052 unset($toolinfo['lti_secureicon']);
3053 }
3054
af9d3a92
JO
3055 foreach ($toolinfo as $property => $value) {
3056 $type->$property = $value;
3057 }
3058}
3059
3060/**
3061 * Allows you to load in the configuration for an external tool from an IMS cartridge.
3062 *
3063 * @param string $url The URL to the cartridge
3064 * @param stdClass $lti LTI object
3065 * @throws moodle_exception if the cartridge could not be loaded correctly
3066 * @since Moodle 3.1
3067 */
3068function lti_load_tool_from_cartridge($url, $lti) {
3069 $toolinfo = lti_load_cartridge($url,
3070 array(
3071 "title" => "name",
3072 "launch_url" => "toolurl",
3073 "secure_launch_url" => "securetoolurl",
c724c988
JD
3074 "description" => "intro",
3075 "icon" => "icon",
3076 "secure_icon" => "secureicon"
af9d3a92
JO
3077 ),
3078 array(
c724c988
JD
3079 "icon_url" => "extension_icon",
3080 "secure_icon_url" => "extension_secureicon"
af9d3a92
JO
3081 )
3082 );
4741c316
JD
3083 // If an activity name exists, unset the cartridge name so we don't override it.
3084 if (isset($lti->name)) {
3085 unset($toolinfo['name']);
3086 }
c724c988
JD
3087
3088 // Always prefer cartridge core icons first, then, if none are found, look at the extension icons.
3089 if (empty($toolinfo['icon']) && !empty($toolinfo['extension_icon'])) {
3090 $toolinfo['icon'] = $toolinfo['extension_icon'];
3091 }
3092 unset($toolinfo['extension_icon']);
3093
3094 if (empty($toolinfo['secureicon']) && !empty($toolinfo['extension_secureicon'])) {
3095 $toolinfo['secureicon'] = $toolinfo['extension_secureicon'];
3096 }
3097 unset($toolinfo['extension_secureicon']);
3098
af9d3a92
JO
3099 foreach ($toolinfo as $property => $value) {
3100 $lti->$property = $value;
3101 }
3102}
3103
3104/**
3105 * Search for a tag within an XML DOMDocument
3106 *
3107 * @param string $url The url of the cartridge to be loaded
3108 * @param array $map The map of tags to keys in the return array
3109 * @param array $propertiesmap The map of properties to keys in the return array
3110 * @return array An associative array with the given keys and their values from the cartridge
3111 * @throws moodle_exception if the cartridge could not be loaded correctly
3112 * @since Moodle 3.1
3113 */
3114function lti_load_cartridge($url, $map, $propertiesmap = array()) {
3115 global $CFG;
3116 require_once($CFG->libdir. "/filelib.php");
af9d3a92
JO
3117
3118 $curl = new curl();
3119 $response = $curl->get($url);
3120
123cc912
MN
3121 // TODO MDL-46023 Replace this code with a call to the new library.
3122 $origerrors = libxml_use_internal_errors(true);
3123 $origentity = libxml_disable_entity_loader(true);
3124 libxml_clear_errors();
3125
af9d3a92
JO
3126 $document = new DOMDocument();
3127 @$document->loadXML($response, LIBXML_DTDLOAD | LIBXML_DTDATTR);
3128
3129 $cartridge = new DomXpath($document);
3130
3131 $errors = libxml_get_errors();
123cc912
MN
3132
3133 libxml_clear_errors();
3134 libxml_use_internal_errors($origerrors);
3135 libxml_disable_entity_loader($origentity);
3136
af9d3a92
JO
3137 if (count($errors) > 0) {
3138 $message = 'Failed to load cartridge.';
3139 foreach ($errors as $error) {
3140 $message .= "\n" . trim($error->message, "\n\r\t .") . " at line " . $error->line;
3141 }
123cc912 3142 throw new moodle_exception('errorreadingfile', '', '', $url, $message);
af9d3a92
JO
3143 }
3144
3145 $toolinfo = array();
3146 foreach ($map as $tag => $key) {
3147 $value = get_tag($tag, $cartridge);
3148 if ($value) {
3149 $toolinfo[$key] = $value;
3150 }
3151 }
3152 if (!empty($propertiesmap)) {
3153 foreach ($propertiesmap as $property => $key) {
3154 $value = get_tag("property", $cartridge, $property);
3155 if ($value) {
3156 $toolinfo[$key] = $value;
3157 }
3158 }
3159 }
123cc912 3160
af9d3a92
JO
3161 return $toolinfo;
3162}
3163
3164/**
3165 * Search for a tag within an XML DOMDocument
3166 *
3167 * @param stdClass $tagname The name of the tag to search for
3168 * @param XPath $xpath The XML to find the tag in
3169 * @param XPath $attribute The attribute to search for (if we should search for a child node with the given
3170 * value for the name attribute
3171 * @since Moodle 3.1
3172 */
3173function get_tag($tagname, $xpath, $attribute = null) {
3174 if ($attribute) {
3175 $result = $xpath->query('//*[local-name() = \'' . $tagname . '\'][@name="' . $attribute . '"]');
3176 } else {
3177 $result = $xpath->query('//*[local-name() = \'' . $tagname . '\']');
3178 }
3179 if ($result->length > 0) {
3180 return $result->item(0)->nodeValue;
3181 }
3182 return null;
3183}