Commit | Line | Data |
---|---|---|
b9b2e7bb CS |
1 | <?php |
2 | // This file is part of BasicLTI4Moodle | |
3 | // | |
4 | // BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability) | |
5 | // consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web | |
6 | // based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI | |
7 | // specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS | |
8 | // are already supporting or going to support BasicLTI. This project Implements the consumer | |
9 | // for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas. | |
10 | // BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem | |
11 | // at the GESSI research group at UPC. | |
12 | // SimpleLTI consumer for Moodle is an implementation of the early specification of LTI | |
13 | // by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a | |
14 | // Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier. | |
15 | // | |
16 | // BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis | |
17 | // of the Universitat Politecnica de Catalunya http://www.upc.edu | |
18 | // Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu | |
19 | // | |
20 | // Moodle is free software: you can redistribute it and/or modify | |
21 | // it under the terms of the GNU General Public License as published by | |
22 | // the Free Software Foundation, either version 3 of the License, or | |
23 | // (at your option) any later version. | |
24 | // | |
25 | // Moodle is distributed in the hope that it will be useful, | |
26 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
27 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
28 | // GNU General Public License for more details. | |
29 | // | |
30 | // You should have received a copy of the GNU General Public License | |
31 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
32 | ||
33 | /** | |
34 | * This file contains the library of functions and constants for the basiclti module | |
35 | * | |
36 | * @package lti | |
37 | * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis | |
38 | * marc.alier@upc.edu | |
39 | * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu | |
40 | * | |
41 | * @author Marc Alier | |
42 | * @author Jordi Piguillem | |
43 | * @author Nikolas Galanis | |
44 | * | |
45 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
46 | */ | |
47 | ||
48 | defined('MOODLE_INTERNAL') || die; | |
49 | ||
50 | require_once($CFG->dirroot.'/mod/lti/OAuth.php'); | |
51 | ||
52 | define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i'); | |
53 | ||
54 | define('LTI_LAUNCH_CONTAINER_DEFAULT', 1); | |
55 | define('LTI_LAUNCH_CONTAINER_EMBED', 2); | |
56 | define('LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS', 3); | |
57 | define('LTI_LAUNCH_CONTAINER_WINDOW', 4); | |
58 | ||
59 | define('LTI_TOOL_STATE_ANY', 0); | |
60 | define('LTI_TOOL_STATE_CONFIGURED', 1); | |
61 | define('LTI_TOOL_STATE_PENDING', 2); | |
62 | define('LTI_TOOL_STATE_REJECTED', 3); | |
63 | ||
64 | define('LTI_SETTING_NEVER', 0); | |
65 | define('LTI_SETTING_ALWAYS', 1); | |
66 | define('LTI_SETTING_DEFAULT', 2); | |
67 | ||
68 | /** | |
69 | * Prints a Basic LTI activity | |
70 | * | |
71 | * $param int $basicltiid Basic LTI activity id | |
72 | */ | |
73 | function lti_view($instance, $makeobject=false) { | |
74 | global $PAGE, $CFG; | |
75 | ||
76 | if(empty($instance->typeid)){ | |
606ab1a1 | 77 | $tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course); |
b9b2e7bb CS |
78 | if($tool){ |
79 | $typeid = $tool->id; | |
80 | } else { | |
81 | $typeid = null; | |
82 | } | |
83 | } else { | |
84 | $typeid = $instance->typeid; | |
85 | } | |
86 | ||
87 | if($typeid){ | |
88 | $typeconfig = lti_get_type_config($typeid); | |
89 | } else { | |
90 | //There is no admin configuration for this tool. Use configuration in the lti instance record plus some defaults. | |
91 | $typeconfig = (array)$instance; | |
92 | ||
93 | $typeconfig['sendname'] = $instance->instructorchoicesendname; | |
94 | $typeconfig['sendemailaddr'] = $instance->instructorchoicesendemailaddr; | |
95 | $typeconfig['customparameters'] = $instance->instructorcustomparameters; | |
96 | } | |
97 | ||
98 | //Default the organizationid if not specified | |
99 | if(empty($typeconfig['organizationid'])){ | |
100 | $urlparts = parse_url($CFG->wwwroot); | |
101 | ||
102 | $typeconfig['organizationid'] = $urlparts['host']; | |
103 | } | |
104 | ||
105 | $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl']; | |
106 | $key = !empty($instance->resourcekey) ? $instance->resourcekey : $typeconfig['resourcekey']; | |
107 | $secret = !empty($instance->password) ? $instance->password : $typeconfig['password']; | |
108 | $orgid = $typeconfig['organizationid']; | |
109 | /* Suppress this for now - Chuck | |
110 | $orgdesc = $typeconfig['organizationdescr']; | |
111 | */ | |
112 | ||
113 | $course = $PAGE->course; | |
114 | $requestparams = lti_build_request($instance, $typeconfig, $course); | |
115 | ||
116 | // Make sure we let the tool know what LMS they are being called from | |
117 | $requestparams["ext_lms"] = "moodle-2"; | |
118 | ||
119 | // Add oauth_callback to be compliant with the 1.0A spec | |
120 | $requestparams["oauth_callback"] = "about:blank"; | |
121 | ||
122 | $submittext = get_string('press_to_submit', 'lti'); | |
dbb0fec9 | 123 | $parms = lti_sign_parameters($requestparams, $endpoint, "POST", $key, $secret, $submittext, $orgid /*, $orgdesc*/); |
b9b2e7bb CS |
124 | |
125 | $debuglaunch = ( $instance->debuglaunch == 1 ); | |
126 | ||
dbb0fec9 | 127 | $content = lti_post_launch_html($parms, $endpoint, $debuglaunch); |
b9b2e7bb CS |
128 | |
129 | echo $content; | |
130 | } | |
131 | ||
996b0fd9 CS |
132 | function lti_build_sourcedid($instanceid, $userid, $servicesalt){ |
133 | $data = new stdClass(); | |
134 | ||
135 | $data->instanceid = $instanceid; | |
136 | $data->userid = $userid; | |
137 | ||
138 | $json = json_encode($data); | |
139 | ||
140 | $hash = hash('sha256', $json . $servicesalt, false); | |
141 | ||
142 | $container = new stdClass(); | |
143 | $container->data = $data; | |
144 | $container->hash = $hash; | |
145 | ||
146 | return $container; | |
147 | } | |
148 | ||
b9b2e7bb CS |
149 | /** |
150 | * This function builds the request that must be sent to the tool producer | |
151 | * | |
152 | * @param object $instance Basic LTI instance object | |
153 | * @param object $typeconfig Basic LTI tool configuration | |
154 | * @param object $course Course object | |
155 | * | |
156 | * @return array $request Request details | |
157 | */ | |
158 | function lti_build_request($instance, $typeconfig, $course) { | |
159 | global $USER, $CFG; | |
160 | ||
161 | $context = get_context_instance(CONTEXT_COURSE, $course->id); | |
162 | $role = lti_get_ims_role($USER, $context); | |
163 | ||
164 | $locale = $course->lang; | |
165 | if ( strlen($locale) < 1 ) { | |
166 | $locale = $CFG->lang; | |
167 | } | |
168 | ||
169 | $requestparams = array( | |
170 | "resource_link_id" => $instance->id, | |
171 | "resource_link_title" => $instance->name, | |
172 | "resource_link_description" => $instance->intro, | |
173 | "user_id" => $USER->id, | |
174 | "roles" => $role, | |
175 | "context_id" => $course->id, | |
176 | "context_label" => $course->shortname, | |
177 | "context_title" => $course->fullname, | |
178 | "launch_presentation_locale" => $locale, | |
179 | ); | |
180 | ||
b9b2e7bb CS |
181 | $placementsecret = $instance->servicesalt; |
182 | ||
183 | if ( isset($placementsecret) ) { | |
996b0fd9 | 184 | $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret)); |
b9b2e7bb CS |
185 | } |
186 | ||
187 | if ( isset($placementsecret) && | |
188 | ( $typeconfig['acceptgrades'] == 1 || | |
189 | ( $typeconfig['acceptgrades'] == 2 && $instance->instructorchoiceacceptgrades == 1 ) ) ) { | |
190 | $requestparams["lis_result_sourcedid"] = $sourcedid; | |
191 | $requestparams["ext_ims_lis_basic_outcome_url"] = $CFG->wwwroot.'/mod/lti/service.php'; | |
192 | } | |
193 | ||
194 | if ( isset($placementsecret) && | |
195 | ( $typeconfig['allowroster'] == 1 || | |
196 | ( $typeconfig['allowroster'] == 2 && $instance->instructorchoiceallowroster == 1 ) ) ) { | |
197 | $requestparams["ext_ims_lis_memberships_id"] = $sourcedid; | |
198 | $requestparams["ext_ims_lis_memberships_url"] = $CFG->wwwroot.'/mod/lti/service.php'; | |
199 | } | |
200 | ||
201 | // Send user's name and email data if appropriate | |
202 | if ( $typeconfig['sendname'] == 1 || | |
203 | ( $typeconfig['sendname'] == 2 && $instance->instructorchoicesendname == 1 ) ) { | |
204 | $requestparams["lis_person_name_given"] = $USER->firstname; | |
205 | $requestparams["lis_person_name_family"] = $USER->lastname; | |
206 | $requestparams["lis_person_name_full"] = $USER->firstname." ".$USER->lastname; | |
207 | } | |
208 | ||
209 | if ( $typeconfig['sendemailaddr'] == 1 || | |
210 | ( $typeconfig['sendemailaddr'] == 2 && $instance->instructorchoicesendemailaddr == 1 ) ) { | |
211 | $requestparams["lis_person_contact_email_primary"] = $USER->email; | |
212 | } | |
213 | ||
214 | // Concatenate the custom parameters from the administrator and the instructor | |
215 | // Instructor parameters are only taken into consideration if the administrator | |
216 | // has giver permission | |
217 | $customstr = $typeconfig['customparameters']; | |
218 | $instructorcustomstr = $instance->instructorcustomparameters; | |
219 | $custom = array(); | |
220 | $instructorcustom = array(); | |
221 | if ($customstr) { | |
dbb0fec9 | 222 | $custom = lti_split_custom_parameters($customstr); |
b9b2e7bb CS |
223 | } |
224 | if (!isset($typeconfig['allowinstructorcustom']) || $typeconfig['allowinstructorcustom'] == 0) { | |
225 | $requestparams = array_merge($custom, $requestparams); | |
226 | } else { | |
227 | if ($instructorcustomstr) { | |
dbb0fec9 | 228 | $instructorcustom = lti_split_custom_parameters($instructorcustomstr); |
b9b2e7bb CS |
229 | } |
230 | foreach ($instructorcustom as $key => $val) { | |
231 | if (array_key_exists($key, $custom)) { | |
232 | // Ignore the instructor's parameter | |
233 | } else { | |
234 | $custom[$key] = $val; | |
235 | } | |
236 | } | |
237 | $requestparams = array_merge($custom, $requestparams); | |
238 | } | |
239 | ||
240 | return $requestparams; | |
241 | } | |
242 | ||
243 | /** | |
244 | * Splits the custom parameters field to the various parameters | |
245 | * | |
246 | * @param string $customstr String containing the parameters | |
247 | * | |
248 | * @return Array of custom parameters | |
249 | */ | |
dbb0fec9 | 250 | function lti_split_custom_parameters($customstr) { |
b9b2e7bb CS |
251 | $textlib = textlib_get_instance(); |
252 | ||
253 | $lines = preg_split("/[\n;]/", $customstr); | |
254 | $retval = array(); | |
255 | foreach ($lines as $line) { | |
256 | $pos = strpos($line, "="); | |
257 | if ( $pos === false || $pos < 1 ) { | |
258 | continue; | |
259 | } | |
260 | $key = trim($textlib->substr($line, 0, $pos)); | |
261 | $val = trim($textlib->substr($line, $pos+1)); | |
dbb0fec9 | 262 | $key = lti_map_keyname($key); |
b9b2e7bb CS |
263 | $retval['custom_'.$key] = $val; |
264 | } | |
265 | return $retval; | |
266 | } | |
267 | ||
268 | /** | |
269 | * Used for building the names of the different custom parameters | |
270 | * | |
271 | * @param string $key Parameter name | |
272 | * | |
273 | * @return string Processed name | |
274 | */ | |
dbb0fec9 | 275 | function lti_map_keyname($key) { |
b9b2e7bb CS |
276 | $textlib = textlib_get_instance(); |
277 | ||
278 | $newkey = ""; | |
279 | $key = $textlib->strtolower(trim($key)); | |
280 | foreach (str_split($key) as $ch) { | |
281 | if ( ($ch >= 'a' && $ch <= 'z') || ($ch >= '0' && $ch <= '9') ) { | |
282 | $newkey .= $ch; | |
283 | } else { | |
284 | $newkey .= '_'; | |
285 | } | |
286 | } | |
287 | return $newkey; | |
288 | } | |
289 | ||
290 | /** | |
291 | * Returns the IMS user role in a given context | |
292 | * | |
293 | * This function queries Moodle for an user role and | |
294 | * returns the correspondant IMS role | |
295 | * | |
296 | * @param StdClass $user Moodle user instance | |
297 | * @param StdClass $context Moodle context | |
298 | * | |
299 | * @return string IMS Role | |
300 | * | |
301 | */ | |
302 | function lti_get_ims_role($user, $context) { | |
303 | ||
304 | $roles = get_user_roles($context, $user->id); | |
305 | $rolesname = array(); | |
306 | foreach ($roles as $role) { | |
307 | $rolesname[] = $role->shortname; | |
308 | } | |
309 | ||
310 | if (in_array('admin', $rolesname) || in_array('coursecreator', $rolesname)) { | |
311 | return get_string('imsroleadmin', 'lti'); | |
312 | } | |
313 | ||
314 | if (in_array('editingteacher', $rolesname) || in_array('teacher', $rolesname)) { | |
315 | return get_string('imsroleinstructor', 'lti'); | |
316 | } | |
317 | ||
318 | return get_string('imsrolelearner', 'lti'); | |
319 | } | |
320 | ||
321 | /** | |
322 | * Returns configuration details for the tool | |
323 | * | |
324 | * @param int $typeid Basic LTI tool typeid | |
325 | * | |
326 | * @return array Tool Configuration | |
327 | */ | |
328 | function lti_get_type_config($typeid) { | |
329 | global $DB; | |
330 | ||
331 | $typeconfig = array(); | |
332 | $configs = $DB->get_records('lti_types_config', array('typeid' => $typeid)); | |
333 | if (!empty($configs)) { | |
334 | foreach ($configs as $config) { | |
335 | $typeconfig[$config->name] = $config->value; | |
336 | } | |
337 | } | |
338 | return $typeconfig; | |
339 | } | |
340 | ||
996b0fd9 | 341 | function lti_get_tools_by_url($url, $state, $courseid = null){ |
b9b2e7bb CS |
342 | $domain = lti_get_domain_from_url($url); |
343 | ||
996b0fd9 | 344 | return lti_get_tools_by_domain($domain, $state, $courseid); |
b9b2e7bb CS |
345 | } |
346 | ||
347 | function lti_get_tools_by_domain($domain, $state = null, $courseid = null){ | |
348 | global $DB, $SITE; | |
349 | ||
350 | $filters = array('tooldomain' => $domain); | |
351 | ||
352 | $statefilter = ''; | |
353 | $coursefilter = ''; | |
354 | ||
355 | if($state){ | |
356 | $statefilter = 'AND state = :state'; | |
357 | } | |
358 | ||
359 | if($courseid && $courseid != $SITE->id){ | |
360 | $coursefilter = 'OR course = :courseid'; | |
361 | } | |
362 | ||
363 | $query = <<<QUERY | |
364 | SELECT * FROM {lti_types} | |
365 | WHERE | |
366 | tooldomain = :tooldomain | |
367 | AND (course = :siteid $coursefilter) | |
368 | $statefilter | |
369 | QUERY; | |
370 | ||
371 | return $DB->get_records_sql($query, array( | |
372 | 'courseid' => $courseid, | |
373 | 'siteid' => $SITE->id, | |
374 | 'tooldomain' => $domain, | |
375 | 'state' => $state | |
376 | )); | |
377 | } | |
378 | ||
379 | /** | |
380 | * Returns all basicLTI tools configured by the administrator | |
381 | * | |
382 | */ | |
6831c7cd | 383 | function lti_filter_get_types($course) { |
b9b2e7bb CS |
384 | global $DB; |
385 | ||
6831c7cd CS |
386 | if(!empty($course)){ |
387 | $filter = array('course' => $course); | |
388 | } else { | |
389 | $filter = array(); | |
390 | } | |
391 | ||
392 | return $DB->get_records('lti_types', $filter); | |
b9b2e7bb CS |
393 | } |
394 | ||
395 | function lti_get_types_for_add_instance(){ | |
996b0fd9 CS |
396 | global $DB, $SITE, $COURSE; |
397 | ||
398 | $query = <<<QUERY | |
399 | SELECT * | |
400 | FROM {lti_types} | |
401 | WHERE | |
402 | coursevisible = 1 | |
403 | AND (course = :siteid OR course = :courseid) | |
606ab1a1 | 404 | AND state = :active |
996b0fd9 CS |
405 | QUERY; |
406 | ||
606ab1a1 | 407 | $admintypes = $DB->get_records_sql($query, array('siteid' => $SITE->id, 'courseid' => $COURSE->id, 'active' => LTI_TOOL_STATE_CONFIGURED)); |
b9b2e7bb CS |
408 | |
409 | $types = array(); | |
996b0fd9 | 410 | $types[0] = (object)array('name' => get_string('automatic', 'lti'), 'course' => $SITE->id); |
b9b2e7bb CS |
411 | |
412 | foreach($admintypes as $type) { | |
16e8f130 | 413 | $types[$type->id] = $type; |
b9b2e7bb CS |
414 | } |
415 | ||
416 | return $types; | |
417 | } | |
418 | ||
419 | function lti_get_domain_from_url($url){ | |
420 | $matches = array(); | |
421 | ||
422 | if(preg_match(LTI_URL_DOMAIN_REGEX, $url, $matches)){ | |
423 | return $matches[1]; | |
424 | } | |
425 | } | |
426 | ||
427 | function lti_get_tool_by_url_match($url, $courseid = null, $state = LTI_TOOL_STATE_CONFIGURED){ | |
b69dc429 | 428 | $possibletools = lti_get_tools_by_url($url, $state, $courseid); |
b9b2e7bb | 429 | |
7023b65e | 430 | return lti_get_best_tool_by_url($url, $possibletools, $courseid); |
b9b2e7bb CS |
431 | } |
432 | ||
433 | function lti_get_url_thumbprint($url){ | |
434 | $urlparts = parse_url(strtolower($url)); | |
435 | if(!isset($urlparts['path'])){ | |
436 | $urlparts['path'] = ''; | |
437 | } | |
438 | ||
439 | if(substr($urlparts['host'], 0, 3) === 'www'){ | |
440 | $urllparts['host'] = substr(3); | |
441 | } | |
442 | ||
443 | return $urllower = $urlparts['host'] . '/' . $urlparts['path']; | |
444 | } | |
445 | ||
7023b65e | 446 | function lti_get_best_tool_by_url($url, $tools, $courseid = null){ |
b9b2e7bb CS |
447 | if(count($tools) === 0){ |
448 | return null; | |
449 | } | |
450 | ||
451 | $urllower = lti_get_url_thumbprint($url); | |
452 | ||
453 | foreach($tools as $tool){ | |
454 | $tool->_matchscore = 0; | |
455 | ||
456 | $toolbaseurllower = lti_get_url_thumbprint($tool->baseurl); | |
457 | ||
458 | if($urllower === $toolbaseurllower){ | |
7023b65e | 459 | //100 points for exact thumbprint match |
b9b2e7bb CS |
460 | $tool->_matchscore += 100; |
461 | } else if(substr($urllower, 0, strlen($toolbaseurllower)) === $toolbaseurllower){ | |
7023b65e | 462 | //50 points if tool thumbprint starts with the base URL thumbprint |
b9b2e7bb CS |
463 | $tool->_matchscore += 50; |
464 | } | |
7023b65e CS |
465 | |
466 | //Prefer course tools over site tools | |
467 | if(!empty($courseid)){ | |
468 | //Minus 25 points for not matching the course id (global tools) | |
469 | if($tool->course != $courseid){ | |
470 | $tool->_matchscore -= 10; | |
471 | } | |
472 | } | |
b9b2e7bb CS |
473 | } |
474 | ||
475 | $bestmatch = array_reduce($tools, function($value, $tool){ | |
476 | if($tool->_matchscore > $value->_matchscore){ | |
477 | return $tool; | |
478 | } else { | |
479 | return $value; | |
480 | } | |
481 | ||
482 | }, (object)array('_matchscore' => -1)); | |
483 | ||
484 | //None of the tools are suitable for this URL | |
485 | if($bestmatch->_matchscore <= 0){ | |
486 | return null; | |
487 | } | |
488 | ||
489 | return $bestmatch; | |
490 | } | |
491 | ||
492 | /** | |
493 | * Prints the various configured tool types | |
494 | * | |
495 | */ | |
496 | function lti_filter_print_types() { | |
497 | global $CFG; | |
498 | ||
499 | $types = lti_filter_get_types(); | |
500 | if (!empty($types)) { | |
501 | echo '<ul>'; | |
502 | foreach ($types as $type) { | |
503 | echo '<li>'. | |
504 | $type->name. | |
505 | '<span class="commands">'. | |
506 | '<a class="editing_update" href="typessettings.php?action=update&id='.$type->id.'&sesskey='.sesskey().'" title="Update">'. | |
507 | '<img class="iconsmall" alt="Update" src="'.$CFG->wwwroot.'/pix/t/edit.gif"/>'. | |
508 | '</a>'. | |
509 | '<a class="editing_delete" href="typessettings.php?action=delete&id='.$type->id.'&sesskey='.sesskey().'" title="Delete">'. | |
510 | '<img class="iconsmall" alt="Delete" src="'.$CFG->wwwroot.'/pix/t/delete.gif"/>'. | |
511 | '</a>'. | |
512 | '</span>'. | |
513 | '</li>'; | |
514 | ||
515 | } | |
516 | echo '</ul>'; | |
517 | } else { | |
518 | echo '<div class="message">'; | |
519 | echo get_string('notypes', 'lti'); | |
520 | echo '</div>'; | |
521 | } | |
522 | } | |
523 | ||
524 | /** | |
525 | * Delete a Basic LTI configuration | |
526 | * | |
527 | * @param int $id Configuration id | |
528 | */ | |
529 | function lti_delete_type($id) { | |
530 | global $DB; | |
531 | ||
532 | //We should probably just copy the launch URL to the tool instances in this case... using a single query | |
533 | /* | |
534 | $instances = $DB->get_records('lti', array('typeid' => $id)); | |
535 | foreach ($instances as $instance) { | |
536 | $instance->typeid = 0; | |
537 | $DB->update_record('lti', $instance); | |
538 | }*/ | |
539 | ||
540 | $DB->delete_records('lti_types', array('id' => $id)); | |
541 | $DB->delete_records('lti_types_config', array('typeid' => $id)); | |
542 | } | |
543 | ||
544 | function lti_set_state_for_type($id, $state){ | |
545 | global $DB; | |
546 | ||
547 | $DB->update_record('lti_types', array('id' => $id, 'state' => $state)); | |
548 | } | |
549 | ||
550 | /** | |
551 | * Transforms a basic LTI object to an array | |
552 | * | |
553 | * @param object $ltiobject Basic LTI object | |
554 | * | |
555 | * @return array Basic LTI configuration details | |
556 | */ | |
557 | function lti_get_config($ltiobject) { | |
558 | $typeconfig = array(); | |
559 | $typeconfig = (array)$ltiobject; | |
560 | $additionalconfig = lti_get_type_config($ltiobject->typeid); | |
561 | $typeconfig = array_merge($typeconfig, $additionalconfig); | |
562 | return $typeconfig; | |
563 | } | |
564 | ||
565 | /** | |
566 | * | |
567 | * Generates some of the tool configuration based on the instance details | |
568 | * | |
569 | * @param int $id | |
570 | * | |
571 | * @return Instance configuration | |
572 | * | |
573 | */ | |
574 | function lti_get_type_config_from_instance($id) { | |
575 | global $DB; | |
576 | ||
577 | $instance = $DB->get_record('lti', array('id' => $id)); | |
578 | $config = lti_get_config($instance); | |
579 | ||
580 | $type = new stdClass(); | |
581 | $type->lti_fix = $id; | |
582 | if (isset($config['toolurl'])) { | |
583 | $type->lti_toolurl = $config['toolurl']; | |
584 | } | |
585 | if (isset($config['instructorchoicesendname'])) { | |
586 | $type->lti_sendname = $config['instructorchoicesendname']; | |
587 | } | |
588 | if (isset($config['instructorchoicesendemailaddr'])) { | |
589 | $type->lti_sendemailaddr = $config['instructorchoicesendemailaddr']; | |
590 | } | |
591 | if (isset($config['instructorchoiceacceptgrades'])) { | |
592 | $type->lti_acceptgrades = $config['instructorchoiceacceptgrades']; | |
593 | } | |
594 | if (isset($config['instructorchoiceallowroster'])) { | |
595 | $type->lti_allowroster = $config['instructorchoiceallowroster']; | |
596 | } | |
597 | ||
598 | if (isset($config['instructorcustomparameters'])) { | |
599 | $type->lti_allowsetting = $config['instructorcustomparameters']; | |
600 | } | |
601 | return $type; | |
602 | } | |
603 | ||
604 | /** | |
605 | * Generates some of the tool configuration based on the admin configuration details | |
606 | * | |
607 | * @param int $id | |
608 | * | |
609 | * @return Configuration details | |
610 | */ | |
611 | function lti_get_type_type_config($id) { | |
612 | global $DB; | |
613 | ||
614 | $basicltitype = $DB->get_record('lti_types', array('id' => $id)); | |
615 | $config = lti_get_type_config($id); | |
616 | ||
617 | $type->lti_typename = $basicltitype->name; | |
618 | ||
6831c7cd CS |
619 | $type->typeid = $basicltitype->id; |
620 | ||
b9b2e7bb CS |
621 | $type->lti_toolurl = $basicltitype->baseurl; |
622 | ||
623 | if (isset($config['resourcekey'])) { | |
624 | $type->lti_resourcekey = $config['resourcekey']; | |
625 | } | |
626 | if (isset($config['password'])) { | |
627 | $type->lti_password = $config['password']; | |
628 | } | |
629 | ||
630 | if (isset($config['sendname'])) { | |
631 | $type->lti_sendname = $config['sendname']; | |
632 | } | |
633 | if (isset($config['instructorchoicesendname'])){ | |
634 | $type->lti_instructorchoicesendname = $config['instructorchoicesendname']; | |
635 | } | |
636 | if (isset($config['sendemailaddr'])){ | |
637 | $type->lti_sendemailaddr = $config['sendemailaddr']; | |
638 | } | |
639 | if (isset($config['instructorchoicesendemailaddr'])){ | |
640 | $type->lti_instructorchoicesendemailaddr = $config['instructorchoicesendemailaddr']; | |
641 | } | |
642 | if (isset($config['acceptgrades'])){ | |
643 | $type->lti_acceptgrades = $config['acceptgrades']; | |
644 | } | |
645 | if (isset($config['instructorchoiceacceptgrades'])){ | |
646 | $type->lti_instructorchoiceacceptgrades = $config['instructorchoiceacceptgrades']; | |
647 | } | |
648 | if (isset($config['allowroster'])){ | |
649 | $type->lti_allowroster = $config['allowroster']; | |
650 | } | |
651 | if (isset($config['instructorchoiceallowroster'])){ | |
652 | $type->lti_instructorchoiceallowroster = $config['instructorchoiceallowroster']; | |
653 | } | |
654 | ||
655 | if (isset($config['customparameters'])) { | |
656 | $type->lti_customparameters = $config['customparameters']; | |
657 | } | |
658 | ||
659 | if (isset($config['organizationid'])) { | |
660 | $type->lti_organizationid = $config['organizationid']; | |
661 | } | |
662 | if (isset($config['organizationurl'])) { | |
663 | $type->lti_organizationurl = $config['organizationurl']; | |
664 | } | |
665 | if (isset($config['organizationdescr'])) { | |
666 | $type->lti_organizationdescr = $config['organizationdescr']; | |
667 | } | |
668 | if (isset($config['launchcontainer'])) { | |
669 | $type->lti_launchcontainer = $config['launchcontainer']; | |
670 | } | |
671 | ||
672 | if (isset($config['coursevisible'])) { | |
673 | $type->lti_coursevisible = $config['coursevisible']; | |
674 | } | |
675 | ||
676 | if (isset($config['debuglaunch'])) { | |
677 | $type->lti_debuglaunch = $config['debuglaunch']; | |
678 | } | |
679 | ||
680 | if (isset($config['module_class_type'])) { | |
681 | $type->lti_module_class_type = $config['module_class_type']; | |
682 | } | |
683 | ||
684 | return $type; | |
685 | } | |
686 | ||
687 | function lti_prepare_type_for_save($type, $config){ | |
688 | $type->baseurl = $config->lti_toolurl; | |
689 | $type->tooldomain = lti_get_domain_from_url($config->lti_toolurl); | |
690 | $type->name = $config->lti_typename; | |
691 | ||
692 | $type->coursevisible = !empty($config->lti_coursevisible) ? $config->lti_coursevisible : 0; | |
693 | $config->lti_coursevisible = $type->coursevisible; | |
694 | ||
695 | $type->timemodified = time(); | |
696 | ||
697 | unset ($config->lti_typename); | |
698 | unset ($config->lti_toolurl); | |
699 | } | |
700 | ||
701 | function lti_update_type($type, $config){ | |
702 | global $DB; | |
703 | ||
704 | lti_prepare_type_for_save($type, $config); | |
705 | ||
706 | if ($DB->update_record('lti_types', $type)) { | |
707 | foreach ($config as $key => $value) { | |
708 | if (substr($key, 0, 4)=='lti_' && !is_null($value)) { | |
709 | $record = new StdClass(); | |
710 | $record->typeid = $type->id; | |
711 | $record->name = substr($key, 4); | |
712 | $record->value = $value; | |
713 | ||
714 | lti_update_config($record); | |
715 | } | |
716 | } | |
717 | } | |
718 | } | |
719 | ||
720 | function lti_add_type($type, $config){ | |
721 | global $USER, $SITE, $DB; | |
722 | ||
723 | lti_prepare_type_for_save($type, $config); | |
724 | ||
725 | if(!isset($type->state)){ | |
726 | $type->state = LTI_TOOL_STATE_PENDING; | |
727 | } | |
728 | ||
729 | if(!isset($type->timecreated)){ | |
730 | $type->timecreated = time(); | |
731 | } | |
732 | ||
733 | if(!isset($type->createdby)){ | |
734 | $type->createdby = $USER->id; | |
735 | } | |
736 | ||
737 | if(!isset($type->course)){ | |
738 | $type->course = $SITE->id; | |
739 | } | |
740 | ||
741 | //Create a salt value to be used for signing passed data to extension services | |
6831c7cd CS |
742 | //The outcome service uses the service salt on the instance. This can be used |
743 | //for communication with services not related to a specific LTI instance. | |
b9b2e7bb CS |
744 | $config->lti_servicesalt = uniqid('', true); |
745 | ||
746 | $id = $DB->insert_record('lti_types', $type); | |
747 | ||
748 | if ($id) { | |
749 | foreach ($config as $key => $value) { | |
750 | if (substr($key, 0, 4)=='lti_' && !is_null($value)) { | |
751 | $record = new StdClass(); | |
752 | $record->typeid = $id; | |
753 | $record->name = substr($key, 4); | |
754 | $record->value = $value; | |
755 | ||
756 | lti_add_config($record); | |
757 | } | |
758 | } | |
759 | } | |
996b0fd9 CS |
760 | |
761 | return $id; | |
b9b2e7bb CS |
762 | } |
763 | ||
764 | /** | |
765 | * Add a tool configuration in the database | |
766 | * | |
767 | * @param $config Tool configuration | |
768 | * | |
769 | * @return int Record id number | |
770 | */ | |
771 | function lti_add_config($config) { | |
772 | global $DB; | |
773 | ||
774 | return $DB->insert_record('lti_types_config', $config); | |
775 | } | |
776 | ||
777 | /** | |
778 | * Updates a tool configuration in the database | |
779 | * | |
780 | * @param $config Tool configuration | |
781 | * | |
782 | * @return Record id number | |
783 | */ | |
784 | function lti_update_config($config) { | |
785 | global $DB; | |
786 | ||
787 | $return = true; | |
788 | $old = $DB->get_record('lti_types_config', array('typeid' => $config->typeid, 'name' => $config->name)); | |
789 | ||
790 | if ($old) { | |
791 | $config->id = $old->id; | |
792 | $return = $DB->update_record('lti_types_config', $config); | |
793 | } else { | |
794 | $return = $DB->insert_record('lti_types_config', $config); | |
795 | } | |
796 | return $return; | |
797 | } | |
798 | ||
799 | /** | |
800 | * Signs the petition to launch the external tool using OAuth | |
801 | * | |
802 | * @param $oldparms Parameters to be passed for signing | |
803 | * @param $endpoint url of the external tool | |
804 | * @param $method Method for sending the parameters (e.g. POST) | |
805 | * @param $oauth_consumoer_key Key | |
806 | * @param $oauth_consumoer_secret Secret | |
807 | * @param $submittext The text for the submit button | |
808 | * @param $orgid LMS name | |
809 | * @param $orgdesc LMS key | |
810 | */ | |
dbb0fec9 | 811 | function lti_sign_parameters($oldparms, $endpoint, $method, $oauthconsumerkey, $oauthconsumersecret, $submittext, $orgid /*, $orgdesc*/) { |
b9b2e7bb CS |
812 | global $lastbasestring; |
813 | $parms = $oldparms; | |
814 | $parms["lti_version"] = "LTI-1p0"; | |
815 | $parms["lti_message_type"] = "basic-lti-launch-request"; | |
816 | if ( $orgid ) { | |
817 | $parms["tool_consumer_instance_guid"] = $orgid; | |
818 | } | |
819 | /* Suppress this for now - Chuck | |
820 | if ( $orgdesc ) $parms["tool_consumer_instance_description"] = $orgdesc; | |
821 | */ | |
822 | $parms["ext_submit"] = $submittext; | |
823 | ||
824 | $testtoken = ''; | |
825 | ||
826 | $hmacmethod = new OAuthSignatureMethod_HMAC_SHA1(); | |
827 | $testconsumer = new OAuthConsumer($oauthconsumerkey, $oauthconsumersecret, null); | |
828 | ||
829 | $accreq = OAuthRequest::from_consumer_and_token($testconsumer, $testtoken, $method, $endpoint, $parms); | |
830 | $accreq->sign_request($hmacmethod, $testconsumer, $testtoken); | |
831 | ||
832 | // Pass this back up "out of band" for debugging | |
833 | $lastbasestring = $accreq->get_signature_base_string(); | |
834 | ||
835 | $newparms = $accreq->get_parameters(); | |
836 | ||
837 | return $newparms; | |
838 | } | |
839 | ||
840 | /** | |
841 | * Posts the launch petition HTML | |
842 | * | |
843 | * @param $newparms Signed parameters | |
844 | * @param $endpoint URL of the external tool | |
845 | * @param $debug Debug (true/false) | |
846 | */ | |
dbb0fec9 | 847 | function lti_post_launch_html($newparms, $endpoint, $debug=false) { |
b9b2e7bb CS |
848 | global $lastbasestring; |
849 | ||
850 | $r = "<form action=\"".$endpoint."\" name=\"ltiLaunchForm\" id=\"ltiLaunchForm\" method=\"post\" encType=\"application/x-www-form-urlencoded\">\n"; | |
851 | ||
852 | $submittext = $newparms['ext_submit']; | |
853 | ||
854 | // Contruct html for the launch parameters | |
855 | foreach ($newparms as $key => $value) { | |
856 | $key = htmlspecialchars($key); | |
857 | $value = htmlspecialchars($value); | |
858 | if ( $key == "ext_submit" ) { | |
859 | $r .= "<input type=\"submit\" name=\""; | |
860 | } else { | |
861 | $r .= "<input type=\"hidden\" name=\""; | |
862 | } | |
863 | $r .= $key; | |
864 | $r .= "\" value=\""; | |
865 | $r .= $value; | |
866 | $r .= "\"/>\n"; | |
867 | } | |
868 | ||
869 | if ( $debug ) { | |
870 | $r .= "<script language=\"javascript\"> \n"; | |
871 | $r .= " //<![CDATA[ \n"; | |
872 | $r .= "function basicltiDebugToggle() {\n"; | |
873 | $r .= " var ele = document.getElementById(\"basicltiDebug\");\n"; | |
874 | $r .= " if(ele.style.display == \"block\") {\n"; | |
875 | $r .= " ele.style.display = \"none\";\n"; | |
876 | $r .= " }\n"; | |
877 | $r .= " else {\n"; | |
878 | $r .= " ele.style.display = \"block\";\n"; | |
879 | $r .= " }\n"; | |
880 | $r .= "} \n"; | |
881 | $r .= " //]]> \n"; | |
882 | $r .= "</script>\n"; | |
883 | $r .= "<a id=\"displayText\" href=\"javascript:basicltiDebugToggle();\">"; | |
884 | $r .= get_string("toggle_debug_data", "lti")."</a>\n"; | |
885 | $r .= "<div id=\"basicltiDebug\" style=\"display:none\">\n"; | |
886 | $r .= "<b>".get_string("basiclti_endpoint", "lti")."</b><br/>\n"; | |
887 | $r .= $endpoint . "<br/>\n <br/>\n"; | |
888 | $r .= "<b>".get_string("basiclti_parameters", "lti")."</b><br/>\n"; | |
889 | foreach ($newparms as $key => $value) { | |
890 | $key = htmlspecialchars($key); | |
891 | $value = htmlspecialchars($value); | |
892 | $r .= "$key = $value<br/>\n"; | |
893 | } | |
894 | $r .= " <br/>\n"; | |
895 | $r .= "<p><b>".get_string("basiclti_base_string", "lti")."</b><br/>\n".$lastbasestring."</p>\n"; | |
896 | $r .= "</div>\n"; | |
897 | } | |
898 | $r .= "</form>\n"; | |
899 | ||
900 | if ( ! $debug ) { | |
901 | $ext_submit = "ext_submit"; | |
902 | $ext_submit_text = $submittext; | |
903 | $r .= " <script type=\"text/javascript\"> \n" . | |
904 | " //<![CDATA[ \n" . | |
905 | " document.getElementById(\"ltiLaunchForm\").style.display = \"none\";\n" . | |
906 | " nei = document.createElement('input');\n" . | |
907 | " nei.setAttribute('type', 'hidden');\n" . | |
908 | " nei.setAttribute('name', '".$ext_submit."');\n" . | |
909 | " nei.setAttribute('value', '".$ext_submit_text."');\n" . | |
910 | " document.getElementById(\"ltiLaunchForm\").appendChild(nei);\n" . | |
911 | " document.ltiLaunchForm.submit(); \n" . | |
912 | " //]]> \n" . | |
913 | " </script> \n"; | |
914 | } | |
915 | return $r; | |
916 | } | |
917 | ||
918 | /** | |
919 | * Returns a link with info about the state of the basiclti submissions | |
920 | * | |
921 | * This is used by view_header to put this link at the top right of the page. | |
922 | * For teachers it gives the number of submitted assignments with a link | |
923 | * For students it gives the time of their submission. | |
924 | * This will be suitable for most assignment types. | |
925 | * | |
926 | * @global object | |
927 | * @global object | |
928 | * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php | |
929 | * @return string | |
930 | */ | |
dbb0fec9 | 931 | function lti_submittedlink($cm, $allgroups=false) { |
b9b2e7bb CS |
932 | global $CFG; |
933 | ||
934 | $submitted = ''; | |
935 | $urlbase = "{$CFG->wwwroot}/mod/lti/"; | |
936 | ||
937 | $context = get_context_instance(CONTEXT_MODULE, $cm->id); | |
938 | if (has_capability('mod/lti:grade', $context)) { | |
939 | if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) { | |
940 | $group = 0; | |
941 | } else { | |
942 | $group = groups_get_activity_group($cm); | |
943 | } | |
944 | ||
945 | $submitted = '<a href="'.$urlbase.'submissions.php?id='.$cm->id.'">'. | |
946 | get_string('viewsubmissions', 'lti').'</a>'; | |
947 | } else { | |
948 | if (isloggedin()) { | |
949 | // TODO Insert code for students if needed | |
950 | } | |
951 | } | |
952 | ||
953 | return $submitted; | |
954 | } | |
955 | ||
996b0fd9 CS |
956 | function lti_get_type($typeid){ |
957 | global $DB; | |
958 | ||
959 | return $DB->get_record('lti_types', array('id' => $typeid)); | |
960 | } |