Merge branch 'MDL-68384-fix-spec-violations-310' of https://github.com/cengage/moodle...
[moodle.git] / mod / lti / tests / locallib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 //
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
33 // Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
35 /**
36  * This file contains unit tests for (some of) lti/locallib.php
37  *
38  * @package    mod_lti
39  * @category   phpunit
40  * @copyright  2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
41  * @copyright  2009 Universitat Politecnica de Catalunya http://www.upc.edu
42  * @author     Charles Severance csev@unmich.edu
43  * @author     Marc Alier (marc.alier@upc.edu)
44  * @author     Jordi Piguillem
45  * @author     Nikolas Galanis
46  * @author     Chris Scribner
47  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48  */
50 defined('MOODLE_INTERNAL') || die;
52 global $CFG;
53 require_once($CFG->dirroot . '/mod/lti/locallib.php');
54 require_once($CFG->dirroot . '/mod/lti/servicelib.php');
56 /**
57  * Local library tests
58  *
59  * @package    mod_lti
60  * @copyright  Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
61  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
62  */
63 class mod_lti_locallib_testcase extends advanced_testcase {
65     public function test_split_custom_parameters() {
66         $this->resetAfterTest();
68         $tool = new stdClass();
69         $tool->enabledcapability = '';
70         $tool->parameter = '';
71         $tool->ltiversion = 'LTI-1p0';
72         $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "x=1\ny=2", false),
73             array('custom_x' => '1', 'custom_y' => '2'));
75         // Check params with caps.
76         $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "X=1", true),
77             array('custom_x' => '1', 'custom_X' => '1'));
79         // Removed repeat of previous test with a semicolon separator.
81         $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), 'Review:Chapter=1.2.56', true),
82             array(
83                 'custom_review_chapter' => '1.2.56',
84                 'custom_Review:Chapter' => '1.2.56'));
86         $this->assertEquals(lti_split_custom_parameters(null, $tool, array(),
87             'Complex!@#$^*(){}[]KEY=Complex!@#$^*;(){}[]½Value', true),
88             array(
89                 'custom_complex____________key' => 'Complex!@#$^*;(){}[]½Value',
90                 'custom_Complex!@#$^*(){}[]KEY' => 'Complex!@#$^*;(){}[]½Value'));
92         // Test custom parameter that returns $USER property.
93         $user = $this->getDataGenerator()->create_user(array('middlename' => 'SOMETHING'));
94         $this->setUser($user);
95         $this->assertEquals(array('custom_x' => '1', 'custom_y' => 'SOMETHING'),
96             lti_split_custom_parameters(null, $tool, array(), "x=1\ny=\$Person.name.middle", false));
97     }
99     /**
100      * This test has been disabled because the test-tool is
101      * being moved and probably it won't work anymore for this.
102      * We should be testing here local stuff only and leave
103      * outside-checks to the conformance tests. MDL-30347
104      */
105     public function disabled_test_sign_parameters() {
106         $correct = array ( 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing',
107             'ext_submit' => 'Click Me', 'lti_message_type' => 'basic-lti-launch-request', 'lti_version' => 'LTI-1p0',
108             'oauth_consumer_key' => 'lmsng.school.edu', 'oauth_nonce' => '47458148e33a8f9dafb888c3684cf476',
109             'oauth_signature' => 'qWgaBIezihCbeHgcwUy14tZcyDQ=', 'oauth_signature_method' => 'HMAC-SHA1',
110             'oauth_timestamp' => '1307141660', 'oauth_version' => '1.0', 'resource_link_id' => '123',
111             'resource_link_title' => 'Weekly Blog', 'roles' => 'Learner', 'tool_consumer_instance_guid' => 'lmsng.school.edu',
112             'user_id' => '789');
114         $requestparams = array('resource_link_id' => '123', 'resource_link_title' => 'Weekly Blog', 'user_id' => '789',
115             'roles' => 'Learner', 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing');
117         $parms = lti_sign_parameters($requestparams, 'http://www.imsglobal.org/developer/LTI/tool.php', 'POST',
118             'lmsng.school.edu', 'secret', 'Click Me', 'lmsng.school.edu' /*, $org_desc*/);
119         $this->assertTrue(isset($parms['oauth_nonce']));
120         $this->assertTrue(isset($parms['oauth_signature']));
121         $this->assertTrue(isset($parms['oauth_timestamp']));
123         // Those things that are hard to mock.
124         $correct['oauth_nonce'] = $parms['oauth_nonce'];
125         $correct['oauth_signature'] = $parms['oauth_signature'];
126         $correct['oauth_timestamp'] = $parms['oauth_timestamp'];
127         ksort($parms);
128         ksort($correct);
129         $this->assertEquals($parms, $correct);
130     }
132     /**
133      * This test has been disabled because, since its creation,
134      * the sourceId generation has changed and surely this is outdated.
135      * Some day these should be replaced by proper tests, but until then
136      * conformance tests say this is working. MDL-30347
137      */
138     public function disabled_test_parse_grade_replace_message() {
139         $message = '
140             <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
141               <imsx_POXHeader>
142                 <imsx_POXRequestHeaderInfo>
143                   <imsx_version>V1.0</imsx_version>
144                   <imsx_messageIdentifier>999998123</imsx_messageIdentifier>
145                 </imsx_POXRequestHeaderInfo>
146               </imsx_POXHeader>
147               <imsx_POXBody>
148                 <replaceResultRequest>
149                   <resultRecord>
150                     <sourcedGUID>
151                       <sourcedId>' .
152             '{&quot;data&quot;:{&quot;instanceid&quot;:&quot;2&quot;,&quot;userid&quot;:&quot;2&quot;},&quot;hash&quot;:' .
153             '&quot;0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b&quot;}' .
154                       '</sourcedId>
155                     </sourcedGUID>
156                     <result>
157                       <resultScore>
158                         <language>en-us</language>
159                         <textString>0.92</textString>
160                       </resultScore>
161                     </result>
162                   </resultRecord>
163                 </replaceResultRequest>
164               </imsx_POXBody>
165             </imsx_POXEnvelopeRequest>
166 ';
168         $parsed = lti_parse_grade_replace_message(new SimpleXMLElement($message));
170         $this->assertEquals($parsed->userid, '2');
171         $this->assertEquals($parsed->instanceid, '2');
172         $this->assertEquals($parsed->sourcedidhash, '0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b');
174         $ltiinstance = (object)array('servicesalt' => '4e5fcc06de1d58.44963230');
176         lti_verify_sourcedid($ltiinstance, $parsed);
177     }
179     public function test_lti_ensure_url_is_https() {
180         $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('http://moodle.org'));
181         $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('moodle.org'));
182         $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('https://moodle.org'));
183     }
185     /**
186      * Test lti_get_url_thumbprint against various URLs
187      */
188     public function test_lti_get_url_thumbprint() {
189         // Note: trailing and double slash are expected right now.  Must evaluate if it must be removed at some point.
190         $this->assertEquals('moodle.org/', lti_get_url_thumbprint('http://MOODLE.ORG'));
191         $this->assertEquals('moodle.org/', lti_get_url_thumbprint('http://www.moodle.org'));
192         $this->assertEquals('moodle.org/', lti_get_url_thumbprint('https://www.moodle.org'));
193         $this->assertEquals('moodle.org/', lti_get_url_thumbprint('moodle.org'));
194         $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('http://moodle.org/this/is/moodle'));
195         $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('https://moodle.org/this/is/moodle'));
196         $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('moodle.org/this/is/moodle'));
197         $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('moodle.org/this/is/moodle?'));
198         $this->assertEquals('moodle.org//this/is/moodle?foo=bar', lti_get_url_thumbprint('moodle.org/this/is/moodle?foo=bar'));
199     }
201     /*
202      * Verify that lti_build_request does handle resource_link_id as expected
203      */
204     public function test_lti_buid_request_resource_link_id() {
205         $this->resetAfterTest();
207         self::setUser($this->getDataGenerator()->create_user());
208         $course   = $this->getDataGenerator()->create_course();
209         $instance = $this->getDataGenerator()->create_module('lti', array(
210             'intro'       => "<p>This</p>\nhas\r\n<p>some</p>\nnew\n\rlines",
211             'introformat' => FORMAT_HTML,
212             'course'      => $course->id,
213         ));
215         $typeconfig = array(
216             'acceptgrades'     => 1,
217             'forcessl'         => 0,
218             'sendname'         => 2,
219             'sendemailaddr'    => 2,
220             'customparameters' => '',
221         );
223         // Normal call, we expect $instance->id to be used as resource_link_id.
224         $params = lti_build_request($instance, $typeconfig, $course, null);
225         $this->assertSame($instance->id, $params['resource_link_id']);
227         // If there is a resource_link_id set, it gets precedence.
228         $instance->resource_link_id = $instance->id + 99;
229         $params = lti_build_request($instance, $typeconfig, $course, null);
230         $this->assertSame($instance->resource_link_id, $params['resource_link_id']);
232         // With none set, resource_link_id is not set either.
233         unset($instance->id);
234         unset($instance->resource_link_id);
235         $params = lti_build_request($instance, $typeconfig, $course, null);
236         $this->assertArrayNotHasKey('resource_link_id', $params);
237     }
239     /**
240      * Test lti_build_request's resource_link_description and ensure
241      * that the newlines in the description are correct.
242      */
243     public function test_lti_build_request_description() {
244         $this->resetAfterTest();
246         self::setUser($this->getDataGenerator()->create_user());
247         $course   = $this->getDataGenerator()->create_course();
248         $instance = $this->getDataGenerator()->create_module('lti', array(
249             'intro'       => "<p>This</p>\nhas\r\n<p>some</p>\nnew\n\rlines",
250             'introformat' => FORMAT_HTML,
251             'course'      => $course->id,
252         ));
254         $typeconfig = array(
255             'acceptgrades'     => 1,
256             'forcessl'         => 0,
257             'sendname'         => 2,
258             'sendemailaddr'    => 2,
259             'customparameters' => '',
260         );
262         $params = lti_build_request($instance, $typeconfig, $course, null);
264         $ncount = substr_count($params['resource_link_description'], "\n");
265         $this->assertGreaterThan(0, $ncount);
267         $rcount = substr_count($params['resource_link_description'], "\r");
268         $this->assertGreaterThan(0, $rcount);
270         $this->assertEquals($ncount, $rcount, 'The number of \n characters should be the same as the number of \r characters');
272         $rncount = substr_count($params['resource_link_description'], "\r\n");
273         $this->assertGreaterThan(0, $rncount);
275         $this->assertEquals($ncount, $rncount, 'All newline characters should be a combination of \r\n');
276     }
278     /**
279      * Tests lti_prepare_type_for_save's handling of the "Force SSL" configuration.
280      */
281     public function test_lti_prepare_type_for_save_forcessl() {
282         $type = new stdClass();
283         $config = new stdClass();
285         // Try when the forcessl config property is not set.
286         lti_prepare_type_for_save($type, $config);
287         $this->assertObjectHasAttribute('lti_forcessl', $config);
288         $this->assertEquals(0, $config->lti_forcessl);
289         $this->assertEquals(0, $type->forcessl);
291         // Try when forcessl config property is set.
292         $config->lti_forcessl = 1;
293         lti_prepare_type_for_save($type, $config);
294         $this->assertObjectHasAttribute('lti_forcessl', $config);
295         $this->assertEquals(1, $config->lti_forcessl);
296         $this->assertEquals(1, $type->forcessl);
298         // Try when forcessl config property is set to 0.
299         $config->lti_forcessl = 0;
300         lti_prepare_type_for_save($type, $config);
301         $this->assertObjectHasAttribute('lti_forcessl', $config);
302         $this->assertEquals(0, $config->lti_forcessl);
303         $this->assertEquals(0, $type->forcessl);
304     }
306     /**
307      * Tests lti_load_type_from_cartridge and lti_load_type_if_cartridge
308      */
309     public function test_lti_load_type_from_cartridge() {
310         $type = new stdClass();
311         $type->lti_toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml');
313         lti_load_type_if_cartridge($type);
315         $this->assertEquals('Example tool', $type->lti_typename);
316         $this->assertEquals('Example tool description', $type->lti_description);
317         $this->assertEquals('http://www.example.com/lti/provider.php', $type->lti_toolurl);
318         $this->assertEquals('http://download.moodle.org/unittest/test.jpg', $type->lti_icon);
319         $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type->lti_secureicon);
320     }
322     /**
323      * Tests lti_load_tool_from_cartridge and lti_load_tool_if_cartridge
324      */
325     public function test_lti_load_tool_from_cartridge() {
326         $lti = new stdClass();
327         $lti->toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml');
329         lti_load_tool_if_cartridge($lti);
331         $this->assertEquals('Example tool', $lti->name);
332         $this->assertEquals('Example tool description', $lti->intro);
333         $this->assertEquals('http://www.example.com/lti/provider.php', $lti->toolurl);
334         $this->assertEquals('https://www.example.com/lti/provider.php', $lti->securetoolurl);
335         $this->assertEquals('http://download.moodle.org/unittest/test.jpg', $lti->icon);
336         $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $lti->secureicon);
337     }
339     /**
340      * Tests for lti_build_content_item_selection_request().
341      */
342     public function test_lti_build_content_item_selection_request() {
343         $this->resetAfterTest();
345         $this->setAdminUser();
346         // Create a tool proxy.
347         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
349         // Create a tool type, associated with that proxy.
350         $type = new stdClass();
351         $data = new stdClass();
352         $data->lti_contentitem = true;
353         $type->state = LTI_TOOL_STATE_CONFIGURED;
354         $type->name = "Test tool";
355         $type->description = "Example description";
356         $type->toolproxyid = $proxy->id;
357         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
359         $typeid = lti_add_type($type, $data);
361         $typeconfig = lti_get_type_config($typeid);
363         $course = $this->getDataGenerator()->create_course();
364         $returnurl = new moodle_url('/');
366         // Default parameters.
367         $result = lti_build_content_item_selection_request($typeid, $course, $returnurl);
368         $this->assertNotEmpty($result);
369         $this->assertNotEmpty($result->params);
370         $this->assertNotEmpty($result->url);
371         $params = $result->params;
372         $url = $result->url;
373         $this->assertEquals($typeconfig['toolurl'], $url);
374         $this->assertEquals('ContentItemSelectionRequest', $params['lti_message_type']);
375         $this->assertEquals(LTI_VERSION_1, $params['lti_version']);
376         $this->assertEquals('application/vnd.ims.lti.v1.ltilink', $params['accept_media_types']);
377         $this->assertEquals('frame,iframe,window', $params['accept_presentation_document_targets']);
378         $this->assertEquals($returnurl->out(false), $params['content_item_return_url']);
379         $this->assertEquals('false', $params['accept_unsigned']);
380         $this->assertEquals('true', $params['accept_multiple']);
381         $this->assertEquals('false', $params['accept_copy_advice']);
382         $this->assertEquals('false', $params['auto_create']);
383         $this->assertEquals($type->name, $params['title']);
384         $this->assertFalse(isset($params['resource_link_id']));
385         $this->assertFalse(isset($params['resource_link_title']));
386         $this->assertFalse(isset($params['resource_link_description']));
387         $this->assertFalse(isset($params['launch_presentation_return_url']));
388         $this->assertFalse(isset($params['lis_result_sourcedid']));
389         $this->assertEquals($params['tool_consumer_instance_guid'], 'www.example.com');
391         // Custom parameters.
392         $title = 'My custom title';
393         $text = 'This is the tool description';
394         $mediatypes = ['image/*', 'video/*'];
395         $targets = ['embed', 'iframe'];
396         $result = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text, $mediatypes, $targets,
397             true, true, true, true, true);
398         $this->assertNotEmpty($result);
399         $this->assertNotEmpty($result->params);
400         $this->assertNotEmpty($result->url);
401         $params = $result->params;
402         $this->assertEquals(implode(',', $mediatypes), $params['accept_media_types']);
403         $this->assertEquals(implode(',', $targets), $params['accept_presentation_document_targets']);
404         $this->assertEquals('true', $params['accept_unsigned']);
405         $this->assertEquals('true', $params['accept_multiple']);
406         $this->assertEquals('true', $params['accept_copy_advice']);
407         $this->assertEquals('true', $params['auto_create']);
408         $this->assertEquals($title, $params['title']);
409         $this->assertEquals($text, $params['text']);
411         // Invalid flag values.
412         $result = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text, $mediatypes, $targets,
413             'aa', -1, 0, 1, 0xabc);
414         $this->assertNotEmpty($result);
415         $this->assertNotEmpty($result->params);
416         $this->assertNotEmpty($result->url);
417         $params = $result->params;
418         $this->assertEquals(implode(',', $mediatypes), $params['accept_media_types']);
419         $this->assertEquals(implode(',', $targets), $params['accept_presentation_document_targets']);
420         $this->assertEquals('false', $params['accept_unsigned']);
421         $this->assertEquals('false', $params['accept_multiple']);
422         $this->assertEquals('false', $params['accept_copy_advice']);
423         $this->assertEquals('false', $params['auto_create']);
424         $this->assertEquals($title, $params['title']);
425         $this->assertEquals($text, $params['text']);
426     }
428     /**
429      * Test for lti_build_content_item_selection_request() with nonexistent tool type ID parameter.
430      */
431     public function test_lti_build_content_item_selection_request_invalid_tooltype() {
432         $this->resetAfterTest();
434         $this->setAdminUser();
435         $course = $this->getDataGenerator()->create_course();
436         $returnurl = new moodle_url('/');
438         // Should throw Exception on non-existent tool type.
439         $this->expectException('moodle_exception');
440         lti_build_content_item_selection_request(1, $course, $returnurl);
441     }
443     /**
444      * Test for lti_build_content_item_selection_request() with invalid media types parameter.
445      */
446     public function test_lti_build_content_item_selection_request_invalid_mediatypes() {
447         $this->resetAfterTest();
449         $this->setAdminUser();
451         // Create a tool type, associated with that proxy.
452         $type = new stdClass();
453         $data = new stdClass();
454         $data->lti_contentitem = true;
455         $type->state = LTI_TOOL_STATE_CONFIGURED;
456         $type->name = "Test tool";
457         $type->description = "Example description";
458         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
460         $typeid = lti_add_type($type, $data);
461         $course = $this->getDataGenerator()->create_course();
462         $returnurl = new moodle_url('/');
464         // Should throw coding_exception on non-array media types.
465         $mediatypes = 'image/*,video/*';
466         $this->expectException('coding_exception');
467         lti_build_content_item_selection_request($typeid, $course, $returnurl, '', '', $mediatypes);
468     }
470     /**
471      * Test for lti_build_content_item_selection_request() with invalid presentation targets parameter.
472      */
473     public function test_lti_build_content_item_selection_request_invalid_presentationtargets() {
474         $this->resetAfterTest();
476         $this->setAdminUser();
478         // Create a tool type, associated with that proxy.
479         $type = new stdClass();
480         $data = new stdClass();
481         $data->lti_contentitem = true;
482         $type->state = LTI_TOOL_STATE_CONFIGURED;
483         $type->name = "Test tool";
484         $type->description = "Example description";
485         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
487         $typeid = lti_add_type($type, $data);
488         $course = $this->getDataGenerator()->create_course();
489         $returnurl = new moodle_url('/');
491         // Should throw coding_exception on non-array presentation targets.
492         $targets = 'frame,iframe';
493         $this->expectException('coding_exception');
494         lti_build_content_item_selection_request($typeid, $course, $returnurl, '', '', [], $targets);
495     }
497     /**
498      * Provider for test_lti_get_best_tool_by_url.
499      *
500      * @return array of [urlToTest, expectedTool, allTools]
501      */
502     public function lti_get_best_tool_by_url_provider() {
503         $tools = [
504             (object) [
505                 'name' => 'Here',
506                 'baseurl' => 'https://example.com/i/am/?where=here',
507                 'tooldomain' => 'example.com',
508                 'state' => LTI_TOOL_STATE_CONFIGURED,
509                 'course' => SITEID
510             ],
511             (object) [
512                 'name' => 'There',
513                 'baseurl' => 'https://example.com/i/am/?where=there',
514                 'tooldomain' => 'example.com',
515                 'state' => LTI_TOOL_STATE_CONFIGURED,
516                 'course' => SITEID
517             ],
518             (object) [
519                 'name' => 'Not here',
520                 'baseurl' => 'https://example.com/i/am/?where=not/here',
521                 'tooldomain' => 'example.com',
522                 'state' => LTI_TOOL_STATE_CONFIGURED,
523                 'course' => SITEID
524             ],
525             (object) [
526                 'name' => 'Here',
527                 'baseurl' => 'https://example.com/i/am/',
528                 'tooldomain' => 'example.com',
529                 'state' => LTI_TOOL_STATE_CONFIGURED,
530                 'course' => SITEID
531             ],
532             (object) [
533                 'name' => 'Here',
534                 'baseurl' => 'https://example.com/i/was',
535                 'tooldomain' => 'example.com',
536                 'state' => LTI_TOOL_STATE_CONFIGURED,
537                 'course' => SITEID
538             ],
539             (object) [
540                 'name' => 'Here',
541                 'baseurl' => 'https://badexample.com/i/am/?where=here',
542                 'tooldomain' => 'badexample.com',
543                 'state' => LTI_TOOL_STATE_CONFIGURED,
544                 'course' => SITEID
545             ],
546         ];
548         $data = [
549             [
550                 'url' => $tools[0]->baseurl,
551                 'expected' => $tools[0],
552             ],
553             [
554                 'url' => $tools[1]->baseurl,
555                 'expected' => $tools[1],
556             ],
557             [
558                 'url' => $tools[2]->baseurl,
559                 'expected' => $tools[2],
560             ],
561             [
562                 'url' => $tools[3]->baseurl,
563                 'expected' => $tools[3],
564             ],
565             [
566                 'url' => $tools[4]->baseurl,
567                 'expected' => $tools[4],
568             ],
569             [
570                 'url' => $tools[5]->baseurl,
571                 'expected' => $tools[5],
572             ],
573             [
574                 'url' => 'https://nomatch.com/i/am/',
575                 'expected' => null
576             ],
577             [
578                 'url' => 'https://example.com',
579                 'expected' => null
580             ],
581             [
582                 'url' => 'https://example.com/i/am/?where=unknown',
583                 'expected' => $tools[3]
584             ]
585         ];
587         // Construct the final array as required by the provider API. Each row
588         // of the array contains the URL to test, the expected tool, and
589         // the complete list of tools.
590         return array_map(function($data) use ($tools) {
591             return [$data['url'], $data['expected'], $tools];
592         }, $data);
593     }
595     /**
596      * Test lti_get_best_tool_by_url.
597      *
598      * @dataProvider lti_get_best_tool_by_url_provider
599      * @param string $url The URL to test.
600      * @param object $expected The expected tool matching the URL.
601      * @param array $tools The pool of tools to match the URL with.
602      */
603     public function test_lti_get_best_tool_by_url($url, $expected, $tools) {
604         $actual = lti_get_best_tool_by_url($url, $tools, null);
605         $this->assertSame($expected, $actual);
606     }
608     /**
609      * Test lti_get_jwt_message_type_mapping().
610      */
611     public function test_lti_get_jwt_message_type_mapping() {
612         $mapping = [
613             'basic-lti-launch-request' => 'LtiResourceLinkRequest',
614             'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest',
615             'LtiDeepLinkingResponse' => 'ContentItemSelection',
616         ];
618         $this->assertEquals($mapping, lti_get_jwt_message_type_mapping());
619     }
621     /**
622      * Test lti_get_jwt_claim_mapping()
623      */
624     public function test_lti_get_jwt_claim_mapping() {
625         $mapping = [
626             'accept_copy_advice' => [
627                 'suffix' => 'dl',
628                 'group' => 'deep_linking_settings',
629                 'claim' => 'accept_copy_advice',
630                 'isarray' => false,
631                 'type' => 'boolean'
632             ],
633             'accept_media_types' => [
634                 'suffix' => 'dl',
635                 'group' => 'deep_linking_settings',
636                 'claim' => 'accept_media_types',
637                 'isarray' => true
638             ],
639             'accept_multiple' => [
640                 'suffix' => 'dl',
641                 'group' => 'deep_linking_settings',
642                 'claim' => 'accept_multiple',
643                 'isarray' => false,
644                 'type' => 'boolean'
645             ],
646             'accept_presentation_document_targets' => [
647                 'suffix' => 'dl',
648                 'group' => 'deep_linking_settings',
649                 'claim' => 'accept_presentation_document_targets',
650                 'isarray' => true
651             ],
652             'accept_types' => [
653                 'suffix' => 'dl',
654                 'group' => 'deep_linking_settings',
655                 'claim' => 'accept_types',
656                 'isarray' => true
657             ],
658             'accept_unsigned' => [
659                 'suffix' => 'dl',
660                 'group' => 'deep_linking_settings',
661                 'claim' => 'accept_unsigned',
662                 'isarray' => false,
663                 'type' => 'boolean'
664             ],
665             'auto_create' => [
666                 'suffix' => 'dl',
667                 'group' => 'deep_linking_settings',
668                 'claim' => 'auto_create',
669                 'isarray' => false,
670                 'type' => 'boolean'
671             ],
672             'can_confirm' => [
673                 'suffix' => 'dl',
674                 'group' => 'deep_linking_settings',
675                 'claim' => 'can_confirm',
676                 'isarray' => false,
677                 'type' => 'boolean'
678             ],
679             'content_item_return_url' => [
680                 'suffix' => 'dl',
681                 'group' => 'deep_linking_settings',
682                 'claim' => 'deep_link_return_url',
683                 'isarray' => false
684             ],
685             'content_items' => [
686                 'suffix' => 'dl',
687                 'group' => '',
688                 'claim' => 'content_items',
689                 'isarray' => true
690             ],
691             'data' => [
692                 'suffix' => 'dl',
693                 'group' => 'deep_linking_settings',
694                 'claim' => 'data',
695                 'isarray' => false
696             ],
697             'text' => [
698                 'suffix' => 'dl',
699                 'group' => 'deep_linking_settings',
700                 'claim' => 'text',
701                 'isarray' => false
702             ],
703             'title' => [
704                 'suffix' => 'dl',
705                 'group' => 'deep_linking_settings',
706                 'claim' => 'title',
707                 'isarray' => false
708             ],
709             'lti_msg' => [
710                 'suffix' => 'dl',
711                 'group' => '',
712                 'claim' => 'msg',
713                 'isarray' => false
714             ],
715             'lti_log' => [
716                 'suffix' => 'dl',
717                 'group' => '',
718                 'claim' => 'log',
719                 'isarray' => false
720             ],
721             'lti_errormsg' => [
722                 'suffix' => 'dl',
723                 'group' => '',
724                 'claim' => 'errormsg',
725                 'isarray' => false
726             ],
727             'lti_errorlog' => [
728                 'suffix' => 'dl',
729                 'group' => '',
730                 'claim' => 'errorlog',
731                 'isarray' => false
732             ],
733             'context_id' => [
734                 'suffix' => '',
735                 'group' => 'context',
736                 'claim' => 'id',
737                 'isarray' => false
738             ],
739             'context_label' => [
740                 'suffix' => '',
741                 'group' => 'context',
742                 'claim' => 'label',
743                 'isarray' => false
744             ],
745             'context_title' => [
746                 'suffix' => '',
747                 'group' => 'context',
748                 'claim' => 'title',
749                 'isarray' => false
750             ],
751             'context_type' => [
752                 'suffix' => '',
753                 'group' => 'context',
754                 'claim' => 'type',
755                 'isarray' => true
756             ],
757             'lis_course_offering_sourcedid' => [
758                 'suffix' => '',
759                 'group' => 'lis',
760                 'claim' => 'course_offering_sourcedid',
761                 'isarray' => false
762             ],
763             'lis_course_section_sourcedid' => [
764                 'suffix' => '',
765                 'group' => 'lis',
766                 'claim' => 'course_section_sourcedid',
767                 'isarray' => false
768             ],
769             'launch_presentation_css_url' => [
770                 'suffix' => '',
771                 'group' => 'launch_presentation',
772                 'claim' => 'css_url',
773                 'isarray' => false
774             ],
775             'launch_presentation_document_target' => [
776                 'suffix' => '',
777                 'group' => 'launch_presentation',
778                 'claim' => 'document_target',
779                 'isarray' => false
780             ],
781             'launch_presentation_height' => [
782                 'suffix' => '',
783                 'group' => 'launch_presentation',
784                 'claim' => 'height',
785                 'isarray' => false
786             ],
787             'launch_presentation_locale' => [
788                 'suffix' => '',
789                 'group' => 'launch_presentation',
790                 'claim' => 'locale',
791                 'isarray' => false
792             ],
793             'launch_presentation_return_url' => [
794                 'suffix' => '',
795                 'group' => 'launch_presentation',
796                 'claim' => 'return_url',
797                 'isarray' => false
798             ],
799             'launch_presentation_width' => [
800                 'suffix' => '',
801                 'group' => 'launch_presentation',
802                 'claim' => 'width',
803                 'isarray' => false
804             ],
805             'lis_person_contact_email_primary' => [
806                 'suffix' => '',
807                 'group' => null,
808                 'claim' => 'email',
809                 'isarray' => false
810             ],
811             'lis_person_name_family' => [
812                 'suffix' => '',
813                 'group' => null,
814                 'claim' => 'family_name',
815                 'isarray' => false
816             ],
817             'lis_person_name_full' => [
818                 'suffix' => '',
819                 'group' => null,
820                 'claim' => 'name',
821                 'isarray' => false
822             ],
823             'lis_person_name_given' => [
824                 'suffix' => '',
825                 'group' => null,
826                 'claim' => 'given_name',
827                 'isarray' => false
828             ],
829             'lis_person_sourcedid' => [
830                 'suffix' => '',
831                 'group' => 'lis',
832                 'claim' => 'person_sourcedid',
833                 'isarray' => false
834             ],
835             'user_id' => [
836                 'suffix' => '',
837                 'group' => null,
838                 'claim' => 'sub',
839                 'isarray' => false
840             ],
841             'user_image' => [
842                 'suffix' => '',
843                 'group' => null,
844                 'claim' => 'picture',
845                 'isarray' => false
846             ],
847             'roles' => [
848                 'suffix' => '',
849                 'group' => '',
850                 'claim' => 'roles',
851                 'isarray' => true
852             ],
853             'role_scope_mentor' => [
854                 'suffix' => '',
855                 'group' => '',
856                 'claim' => 'role_scope_mentor',
857                 'isarray' => false
858             ],
859             'deployment_id' => [
860                 'suffix' => '',
861                 'group' => '',
862                 'claim' => 'deployment_id',
863                 'isarray' => false
864             ],
865             'lti_message_type' => [
866                 'suffix' => '',
867                 'group' => '',
868                 'claim' => 'message_type',
869                 'isarray' => false
870             ],
871             'lti_version' => [
872                 'suffix' => '',
873                 'group' => '',
874                 'claim' => 'version',
875                 'isarray' => false
876             ],
877             'resource_link_description' => [
878                 'suffix' => '',
879                 'group' => 'resource_link',
880                 'claim' => 'description',
881                 'isarray' => false
882             ],
883             'resource_link_id' => [
884                 'suffix' => '',
885                 'group' => 'resource_link',
886                 'claim' => 'id',
887                 'isarray' => false
888             ],
889             'resource_link_title' => [
890                 'suffix' => '',
891                 'group' => 'resource_link',
892                 'claim' => 'title',
893                 'isarray' => false
894             ],
895             'tool_consumer_info_product_family_code' => [
896                 'suffix' => '',
897                 'group' => 'tool_platform',
898                 'claim' => 'product_family_code',
899                 'isarray' => false
900             ],
901             'tool_consumer_info_version' => [
902                 'suffix' => '',
903                 'group' => 'tool_platform',
904                 'claim' => 'version',
905                 'isarray' => false
906             ],
907             'tool_consumer_instance_contact_email' => [
908                 'suffix' => '',
909                 'group' => 'tool_platform',
910                 'claim' => 'contact_email',
911                 'isarray' => false
912             ],
913             'tool_consumer_instance_description' => [
914                 'suffix' => '',
915                 'group' => 'tool_platform',
916                 'claim' => 'description',
917                 'isarray' => false
918             ],
919             'tool_consumer_instance_guid' => [
920                 'suffix' => '',
921                 'group' => 'tool_platform',
922                 'claim' => 'guid',
923                 'isarray' => false
924             ],
925             'tool_consumer_instance_name' => [
926                 'suffix' => '',
927                 'group' => 'tool_platform',
928                 'claim' => 'name',
929                 'isarray' => false
930             ],
931             'tool_consumer_instance_url' => [
932                 'suffix' => '',
933                 'group' => 'tool_platform',
934                 'claim' => 'url',
935                 'isarray' => false
936             ],
937             'custom_context_memberships_url' => [
938                 'suffix' => 'nrps',
939                 'group' => 'namesroleservice',
940                 'claim' => 'context_memberships_url',
941                 'isarray' => false
942             ],
943             'custom_context_memberships_versions' => [
944                 'suffix' => 'nrps',
945                 'group' => 'namesroleservice',
946                 'claim' => 'service_versions',
947                 'isarray' => true
948             ],
949             'custom_gradebookservices_scope' => [
950                 'suffix' => 'ags',
951                 'group' => 'endpoint',
952                 'claim' => 'scope',
953                 'isarray' => true
954             ],
955             'custom_lineitems_url' => [
956                 'suffix' => 'ags',
957                 'group' => 'endpoint',
958                 'claim' => 'lineitems',
959                 'isarray' => false
960             ],
961             'custom_lineitem_url' => [
962                 'suffix' => 'ags',
963                 'group' => 'endpoint',
964                 'claim' => 'lineitem',
965                 'isarray' => false
966             ],
967             'custom_results_url' => [
968                 'suffix' => 'ags',
969                 'group' => 'endpoint',
970                 'claim' => 'results',
971                 'isarray' => false
972             ],
973             'custom_result_url' => [
974                 'suffix' => 'ags',
975                 'group' => 'endpoint',
976                 'claim' => 'result',
977                 'isarray' => false
978             ],
979             'custom_scores_url' => [
980                 'suffix' => 'ags',
981                 'group' => 'endpoint',
982                 'claim' => 'scores',
983                 'isarray' => false
984             ],
985             'custom_score_url' => [
986                 'suffix' => 'ags',
987                 'group' => 'endpoint',
988                 'claim' => 'score',
989                 'isarray' => false
990             ],
991             'lis_outcome_service_url' => [
992                 'suffix' => 'bo',
993                 'group' => 'basicoutcome',
994                 'claim' => 'lis_outcome_service_url',
995                 'isarray' => false
996             ],
997             'lis_result_sourcedid' => [
998                 'suffix' => 'bo',
999                 'group' => 'basicoutcome',
1000                 'claim' => 'lis_result_sourcedid',
1001                 'isarray' => false
1002             ],
1003         ];
1005         $this->assertEquals($mapping, lti_get_jwt_claim_mapping());
1006     }
1008     /**
1009      * Test lti_build_standard_message().
1010      */
1011     public function test_lti_build_standard_message_institution_name_set() {
1012         global $CFG;
1014         $this->resetAfterTest();
1016         $CFG->mod_lti_institution_name = 'some institution name lols';
1018         $course   = $this->getDataGenerator()->create_course();
1019         $instance = $this->getDataGenerator()->create_module('lti',
1020             [
1021                 'course' => $course->id,
1022             ]
1023         );
1025         $message = lti_build_standard_message($instance, '2', LTI_VERSION_1);
1027         $this->assertEquals('moodle-2', $message['ext_lms']);
1028         $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']);
1029         $this->assertEquals(LTI_VERSION_1, $message['lti_version']);
1030         $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']);
1031         $this->assertEquals('2', $message['tool_consumer_instance_guid']);
1032         $this->assertEquals('some institution name lols', $message['tool_consumer_instance_name']);
1033         $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']);
1034     }
1036     /**
1037      * Test lti_build_standard_message().
1038      */
1039     public function test_lti_build_standard_message_institution_name_not_set() {
1040         $this->resetAfterTest();
1042         $course   = $this->getDataGenerator()->create_course();
1043         $instance = $this->getDataGenerator()->create_module('lti',
1044             [
1045                 'course' => $course->id,
1046             ]
1047         );
1049         $message = lti_build_standard_message($instance, '2', LTI_VERSION_2);
1051         $this->assertEquals('moodle-2', $message['ext_lms']);
1052         $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']);
1053         $this->assertEquals(LTI_VERSION_2, $message['lti_version']);
1054         $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']);
1055         $this->assertEquals('2', $message['tool_consumer_instance_guid']);
1056         $this->assertEquals('phpunit', $message['tool_consumer_instance_name']);
1057         $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']);
1058     }
1060     /**
1061      * Test lti_verify_jwt_signature().
1062      */
1063     public function test_lti_verify_jwt_signature() {
1064         $this->resetAfterTest();
1066         $this->setAdminUser();
1068         // Create a tool type, associated with that proxy.
1069         $type = new stdClass();
1070         $type->state = LTI_TOOL_STATE_CONFIGURED;
1071         $type->name = "Test tool";
1072         $type->description = "Example description";
1073         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1075         $config = new stdClass();
1076         $config->lti_publickey = '-----BEGIN PUBLIC KEY-----
1077 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
1078 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
1079 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
1080 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
1081 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
1082 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
1083 MwIDAQAB
1084 -----END PUBLIC KEY-----';
1086         $config->lti_keytype = LTI_RSA_KEY;
1088         $typeid = lti_add_type($type, $config);
1090         lti_verify_jwt_signature($typeid, '', 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g' .
1091             'RG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOs' .
1092             'S_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMgu' .
1093             'EIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iY' .
1094             'v7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA');
1095     }
1097     /**
1098      * Test lti_verify_jwt_signature_jwk().
1099      */
1100     public function test_lti_verify_jwt_signature_jwk() {
1101         $this->resetAfterTest();
1103         $this->setAdminUser();
1105         // Create a tool type, associated with that proxy.
1106         $type = new stdClass();
1107         $type->state = LTI_TOOL_STATE_CONFIGURED;
1108         $type->name = "Test tool";
1109         $type->description = "Example description";
1110         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1112         $config = new stdClass();
1113         $config->lti_publickeyset = dirname(__FILE__) . '/fixtures/test_keyset';
1115         $config->lti_keytype = LTI_JWK_KEYSET;
1117         $typeid = lti_add_type($type, $config);
1119         $jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU3YzExNzdkMmQ1M2EwMjFjNzM';
1120         $jwt .= '3NTY0OTFjMTM3YjE3In0.eyJpc3MiOiJnclJvbkd3RTd1WjRwZ28iLCJzdWIiOiJnclJvb';
1121         $jwt .= 'kd3RTd1WjRwZ28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0L21vb2RsZS9tb2QvbHRpL3R';
1122         $jwt .= 'va2VuLnBocCIsImp0aSI6IjFlMUJPVEczVFJjbFdUem00dERsMGc9PSIsImlhdCI6MTU4M';
1123         $jwt .= 'Dg1NTUwNX0.Lowhc9ovNAXRb2rkAnv1oozDXlRD54Mz2JS1i8Zx4yGWQzmXzam-La19_g0';
1124         $jwt .= 'CTnwlKM6gxaInnRKFRAcwhJVcWec389liLAjMbna6d6iTWYTZr7q_4BIe3CT_oTMWASGta';
1125         $jwt .= 'Paaq53ch1rO4YdueEtmtd1K47ibo4Lhu1jmP_icc3lxjfnqiv4vIYdy7W2JQEzpk1ImuQr';
1126         $jwt .= 'AlO1xR3fZ6bgcJhVIaw5xoaZD3ZgEjuZOQXMkywv1bL-mL17RX336CzHd8rYZg82QXrBzb';
1127         $jwt .= 'NWzAlaZxv9VSug8t6mORvM6TkYYWjqEBKemgkD5rNh1BHrPcjWP7vy2Jz7YMjLsmuvDuLK';
1128         $jwt .= '_PHYIKL--s4gcXWoYmOu1vj-SgoPczTJPoiBD35hAKqVHy5ggHaYHBy95_bbcFd8H1smHw';
1129         $jwt .= 'pejrAFj1QAwGyTISLzUm08oq7Ak0tSxRKKXw4lpZAka1MmYxO3tJ_3-MXw6Bwz12bNgitJ';
1130         $jwt .= 'lQd6n3kkGLCJAmANeRkPsH6eZVwF0n2cjh2O1JAwyNcMD2vs4I8ftM1EqqoE2M3r6kt3AC';
1131         $jwt .= 'EscmqzizI3j80USBCLUUb1UTsfJb2g7oyApJAp-13Q3InR3QyvWO8unG5VraFE7IL5I28h';
1132         $jwt .= 'MkQAHuCI90DFmXB4leflAu7wNlIK_U8xkGl8X8Mnv6MWgg94Ki8jgIq_kA85JAqI';
1134         lti_verify_jwt_signature($typeid, '', $jwt);
1135     }
1137     /**
1138      * Test lti_verify_jwt_signature().
1139      */
1140     public function test_lti_verify_jwt_signature_with_lti2() {
1141         $this->resetAfterTest();
1143         $this->setAdminUser();
1145         // Create a tool proxy.
1146         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
1148         // Create a tool type, associated with that proxy.
1149         $type = new stdClass();
1150         $type->state = LTI_TOOL_STATE_CONFIGURED;
1151         $type->name = "Test tool";
1152         $type->description = "Example description";
1153         $type->toolproxyid = $proxy->id;
1154         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1156         $data = new stdClass();
1157         $data->lti_contentitem = true;
1159         $typeid = lti_add_type($type, $data);
1161         $this->expectExceptionMessage('JWT security not supported with LTI 2');
1162         lti_verify_jwt_signature($typeid, '', '');
1163     }
1165     /**
1166      * Test lti_verify_jwt_signature().
1167      */
1168     public function test_lti_verify_jwt_signature_no_consumer_key() {
1169         $this->resetAfterTest();
1171         $this->setAdminUser();
1173         // Create a tool type, associated with that proxy.
1174         $type = new stdClass();
1175         $type->state = LTI_TOOL_STATE_CONFIGURED;
1176         $type->name = "Test tool";
1177         $type->description = "Example description";
1178         $type->clientid = 'consumerkey';
1179         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1181         $config = new stdClass();
1182         $typeid = lti_add_type($type, $config);
1184         $this->expectExceptionMessage(get_string('errorincorrectconsumerkey', 'mod_lti'));
1185         lti_verify_jwt_signature($typeid, '', '');
1186     }
1188     /**
1189      * Test lti_verify_jwt_signature().
1190      */
1191     public function test_lti_verify_jwt_signature_no_public_key() {
1192         $this->resetAfterTest();
1193         $this->setAdminUser();
1195         // Create a tool type, associated with that proxy.
1196         $type = new stdClass();
1197         $type->state = LTI_TOOL_STATE_CONFIGURED;
1198         $type->name = "Test tool";
1199         $type->description = "Example description";
1200         $type->clientid = 'consumerkey';
1201         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1203         $config = new stdClass();
1204         $config->lti_keytype = LTI_RSA_KEY;
1205         $typeid = lti_add_type($type, $config);
1207         $this->expectExceptionMessage('No public key configured');
1208         lti_verify_jwt_signature($typeid, 'consumerkey', '');
1209     }
1211     /**
1212      * Test lti_convert_content_items().
1213      */
1214     public function test_lti_convert_content_items() {
1215         $contentitems = [];
1216         $contentitems[] = [
1217             'type' => 'ltiResourceLink',
1218             'url' => 'http://example.com/messages/launch',
1219             'title' => 'Test title',
1220             'text' => 'Test text',
1221             'iframe' => []
1222         ];
1223         $contentitems[] = [
1224             'type' => 'ltiResourceLink',
1225             'url' => 'http://example.com/messages/launch2',
1226             'title' => 'Test title2',
1227             'text' => 'Test text2',
1228             'iframe' => [
1229                 'height' => 200,
1230                 'width' => 300
1231             ],
1232             'window' => []
1233         ];
1234         $contentitems[] = [
1235             'type' => 'ltiResourceLink',
1236             'url' => 'http://example.com/messages/launch3',
1237             'title' => 'Test title3',
1238             'text' => 'Test text3',
1239             'window' => [
1240                 'targetName' => 'test-win',
1241                 'height' => 400
1242             ]
1243         ];
1245         $contentitems = json_encode($contentitems);
1247         $json = lti_convert_content_items($contentitems);
1249         $jsondecode = json_decode($json);
1251         $strcontext = '@context';
1252         $strgraph = '@graph';
1253         $strtype = '@type';
1255         $objgraph = new stdClass();
1256         $objgraph->url = 'http://example.com/messages/launch';
1257         $objgraph->title = 'Test title';
1258         $objgraph->text = 'Test text';
1259         $objgraph->placementAdvice = new stdClass();
1260         $objgraph->placementAdvice->presentationDocumentTarget = 'iframe';
1261         $objgraph->{$strtype} = 'LtiLinkItem';
1262         $objgraph->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1264         $objgraph2 = new stdClass();
1265         $objgraph2->url = 'http://example.com/messages/launch2';
1266         $objgraph2->title = 'Test title2';
1267         $objgraph2->text = 'Test text2';
1268         $objgraph2->placementAdvice = new stdClass();
1269         $objgraph2->placementAdvice->presentationDocumentTarget = 'iframe';
1270         $objgraph2->placementAdvice->displayHeight = 200;
1271         $objgraph2->placementAdvice->displayWidth = 300;
1272         $objgraph2->{$strtype} = 'LtiLinkItem';
1273         $objgraph2->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1275         $objgraph3 = new stdClass();
1276         $objgraph3->url = 'http://example.com/messages/launch3';
1277         $objgraph3->title = 'Test title3';
1278         $objgraph3->text = 'Test text3';
1279         $objgraph3->placementAdvice = new stdClass();
1280         $objgraph3->placementAdvice->presentationDocumentTarget = 'window';
1281         $objgraph3->placementAdvice->displayHeight = 400;
1282         $objgraph3->placementAdvice->windowTarget = 'test-win';
1283         $objgraph3->{$strtype} = 'LtiLinkItem';
1284         $objgraph3->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1286         $expected = new stdClass();
1287         $expected->{$strcontext} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
1288         $expected->{$strgraph} = [];
1289         $expected->{$strgraph}[] = $objgraph;
1290         $expected->{$strgraph}[] = $objgraph2;
1291         $expected->{$strgraph}[] = $objgraph3;
1293         $this->assertEquals($expected, $jsondecode);
1294     }
1296     /**
1297      * Test adding a single gradable item through content item.
1298      */
1299     public function test_lti_tool_configuration_from_content_item_single_gradable() {
1300         $this->resetAfterTest();
1301         $this->setAdminUser();
1303         $type = new stdClass();
1304         $type->name = "Test tool";
1305         $type->baseurl = "http://example.com";
1306         $config = new stdClass();
1307         $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
1308         $typeid = lti_add_type($type, $config);
1310         $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1311         $contentitems = [];
1312         $contentitems[] = [
1313             'type' => 'ltiResourceLink',
1314             'url' => 'http://example.com/messages/launch',
1315             'title' => 'Test title',
1316             'lineItem' => [
1317                 'resourceId' => 'r12345',
1318                 'tag' => 'final',
1319                 'scoreMaximum' => 10.0
1320             ],
1321             'frame' => []
1322         ];
1323         $contentitemsjson13 = json_encode($contentitems);
1324         $json11 = lti_convert_content_items($contentitemsjson13);
1326         $config = lti_tool_configuration_from_content_item($typeid,
1327                                                            'ContentItemSelection',
1328                                                            $type->ltiversion,
1329                                                            'ConsumerKey',
1330                                                            $json11);
1332         $this->assertEquals($contentitems[0]['url'], $config->toolurl);
1333         $this->assertEquals(LTI_SETTING_ALWAYS, $config->instructorchoiceacceptgrades);
1334         $this->assertEquals($contentitems[0]['lineItem']['tag'], $config->lineitemtag);
1335         $this->assertEquals($contentitems[0]['lineItem']['resourceId'], $config->lineitemresourceid);
1336         $this->assertEquals($contentitems[0]['lineItem']['scoreMaximum'], $config->grade_modgrade_point);
1337     }
1339     /**
1340      * Test adding multiple gradable items through content item.
1341      */
1342     public function test_lti_tool_configuration_from_content_item_multiple() {
1343         $this->resetAfterTest();
1344         $this->setAdminUser();
1346         $type = new stdClass();
1347         $type->name = "Test tool";
1348         $type->baseurl = "http://example.com";
1349         $config = new stdClass();
1350         $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
1351         $typeid = lti_add_type($type, $config);
1353         $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1354         $contentitems = [];
1355         $contentitems[] = [
1356             'type' => 'ltiResourceLink',
1357             'url' => 'http://example.com/messages/launch',
1358             'title' => 'Test title',
1359             'text' => 'Test text',
1360             'icon' => [
1361                 'url' => 'http://lti.example.com/image.jpg',
1362                 'width' => 100
1363             ],
1364             'frame' => []
1365         ];
1366         $contentitems[] = [
1367             'type' => 'ltiResourceLink',
1368             'url' => 'http://example.com/messages/launchgraded',
1369             'title' => 'Test Graded',
1370             'lineItem' => [
1371                 'resourceId' => 'r12345',
1372                 'tag' => 'final',
1373                 'scoreMaximum' => 10.0
1374             ],
1375             'frame' => []
1376         ];
1377         $contentitemsjson13 = json_encode($contentitems);
1378         $json11 = lti_convert_content_items($contentitemsjson13);
1380         $config = lti_tool_configuration_from_content_item($typeid,
1381                                                            'ContentItemSelection',
1382                                                            $type->ltiversion,
1383                                                            'ConsumerKey',
1384                                                            $json11);
1385         $this->assertNotNull($config->multiple);
1386         $this->assertEquals(2, count( $config->multiple ));
1387         $this->assertEquals($contentitems[0]['title'], $config->multiple[0]->name);
1388         $this->assertEquals($contentitems[0]['url'], $config->multiple[0]->toolurl);
1389         $this->assertEquals(LTI_SETTING_NEVER, $config->multiple[0]->instructorchoiceacceptgrades);
1390         $this->assertEquals($contentitems[1]['url'], $config->multiple[1]->toolurl);
1391         $this->assertEquals(LTI_SETTING_ALWAYS, $config->multiple[1]->instructorchoiceacceptgrades);
1392         $this->assertEquals($contentitems[1]['lineItem']['tag'], $config->multiple[1]->lineitemtag);
1393         $this->assertEquals($contentitems[1]['lineItem']['resourceId'], $config->multiple[1]->lineitemresourceid);
1394         $this->assertEquals($contentitems[1]['lineItem']['scoreMaximum'], $config->multiple[1]->grade_modgrade_point);
1395     }
1397     /**
1398      * Test adding a single non gradable item through content item.
1399      */
1400     public function test_lti_tool_configuration_from_content_item_single() {
1401         $this->resetAfterTest();
1402         $this->setAdminUser();
1404         $type = new stdClass();
1405         $type->name = "Test tool";
1406         $type->baseurl = "http://example.com";
1407         $config = new stdClass();
1408         $typeid = lti_add_type($type, $config);
1410         $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1411         $contentitems = [];
1412         $contentitems[] = [
1413             'type' => 'ltiResourceLink',
1414             'url' => 'http://example.com/messages/launch',
1415             'title' => 'Test title',
1416             'text' => 'Test text',
1417             'icon' => [
1418                 'url' => 'http://lti.example.com/image.jpg',
1419                 'width' => 100
1420             ],
1421             'frame' => []
1422         ];
1423         $contentitemsjson13 = json_encode($contentitems);
1424         $json11 = lti_convert_content_items($contentitemsjson13);
1426         $config = lti_tool_configuration_from_content_item($typeid,
1427                                                            'ContentItemSelection',
1428                                                            $type->ltiversion,
1429                                                            'ConsumerKey',
1430                                                            $json11);
1431         $this->assertEquals($contentitems[0]['title'], $config->name);
1432         $this->assertEquals($contentitems[0]['text'], $config->introeditor['text']);
1433         $this->assertEquals($contentitems[0]['url'], $config->toolurl);
1434         $this->assertEquals($contentitems[0]['icon']['url'], $config->icon);
1435         $this->assertEquals(LTI_SETTING_NEVER, $config->instructorchoiceacceptgrades);
1437     }
1439     /**
1440      * Test lti_sign_jwt().
1441      */
1442     public function test_lti_sign_jwt() {
1443         $this->resetAfterTest();
1445         $this->setAdminUser();
1447         // Create a tool type, associated with that proxy.
1448         $type = new stdClass();
1449         $type->state = LTI_TOOL_STATE_CONFIGURED;
1450         $type->name = "Test tool";
1451         $type->description = "Example description";
1452         $type->clientid = 'consumerkey';
1453         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1455         $config = new stdClass();
1456         $typeid = lti_add_type($type, $config);
1458         $params = [];
1459         $params['roles'] = 'urn:lti:role:ims/lis/testrole,' .
1460             'urn:lti:instrole:ims/lis/testinstrole,' .
1461             'urn:lti:sysrole:ims/lis/testsysrole,' .
1462             'hi';
1463         $params['accept_copy_advice'] = [
1464             'suffix' => 'dl',
1465             'group' => 'deep_linking_settings',
1466             'claim' => 'accept_copy_advice',
1467             'isarray' => false
1468         ];
1469         $params['lis_result_sourcedid'] = [
1470             'suffix' => 'bos',
1471             'group' => 'basicoutcomesservice',
1472             'claim' => 'lis_result_sourcedid',
1473             'isarray' => false
1474         ];
1475         $endpoint = 'https://www.example.com/moodle';
1476         $oauthconsumerkey = 'consumerkey';
1477         $nonce = '';
1479         $jwt = lti_sign_jwt($params, $endpoint, $oauthconsumerkey, $typeid, $nonce);
1481         $this->assertArrayHasKey('id_token', $jwt);
1482         $this->assertNotEmpty($jwt['id_token']);
1483     }
1485     /**
1486      * Test lti_convert_from_jwt()
1487      */
1488     public function test_lti_convert_from_jwt() {
1489         $this->resetAfterTest();
1491         $this->setAdminUser();
1493         // Create a tool type, associated with that proxy.
1494         $type = new stdClass();
1495         $type->state = LTI_TOOL_STATE_CONFIGURED;
1496         $type->name = "Test tool";
1497         $type->description = "Example description";
1498         $type->clientid = 'sso.example.com';
1499         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1501         $config = new stdClass();
1502         $config->lti_publickey = '-----BEGIN PUBLIC KEY-----
1503 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
1504 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
1505 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
1506 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
1507 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
1508 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
1509 MwIDAQAB
1510 -----END PUBLIC KEY-----';
1511         $config->lti_keytype = LTI_RSA_KEY;
1513         $typeid = lti_add_type($type, $config);
1515         $params = lti_convert_from_jwt($typeid, 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib' .
1516             'mFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoic3NvLmV4YW1wbGUuY29tIn0.XURVvEb5ueAvFsn-S9EB' .
1517             'BSfKbsgUzfRQqmJ6evlrYdx7sXWoZXw1nYjaLTg-mawvBr7MVvrdG9qh6oN8OfkQ7bfMwiz4tjBMJ4B4q_sig5BDYIKwMNjZL5GGCBs89FQrgqZBhxw' .
1518             '3exTjPBEn69__w40o0AhCsBohPMh0ZsAyHug5dhm8vIuOP667repUJzM8uKCD6L4bEL6vQE8EwU6WQOmfJ2SDmRs-1pFkiaFd6hmPn6AVX7ETtzQmlT' .
1519             'X-nXe9weQjU1lH4AQG2Yfnn-7lS94bt6E76Zt-XndP3IY7W48EpnRfUK9Ff1fZlomT4MPahdNP1eP8gT2iMz7vYpCfmA');
1521         $this->assertEquals('sso.example.com', $params['oauth_consumer_key']);
1522         $this->assertEquals('John Doe', $params['lis_person_name_full']);
1523     }
1525     /**
1526      * Test lti_get_permitted_service_scopes().
1527      */
1528     public function test_lti_get_permitted_service_scopes() {
1529         $this->resetAfterTest();
1531         $this->setAdminUser();
1533         // Create a tool type, associated with that proxy.
1534         $type = new stdClass();
1535         $type->state = LTI_TOOL_STATE_CONFIGURED;
1536         $type->name = "Test tool";
1537         $type->description = "Example description";
1538         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1540         $typeconfig = new stdClass();
1541         $typeconfig->lti_acceptgrades = true;
1543         $typeid = lti_add_type($type, $typeconfig);
1545         $tool = lti_get_type($typeid);
1547         $config = lti_get_type_config($typeid);
1548         $permittedscopes = lti_get_permitted_service_scopes($tool, $config);
1550         $expected = [
1551             'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome'
1552         ];
1553         $this->assertEquals($expected, $permittedscopes);
1554     }
1556     /**
1557      * Test get_tool_type_config().
1558      */
1559     public function test_get_tool_type_config() {
1560         $this->resetAfterTest();
1562         $this->setAdminUser();
1564         // Create a tool type, associated with that proxy.
1565         $type = new stdClass();
1566         $type->state = LTI_TOOL_STATE_CONFIGURED;
1567         $type->name = "Test tool";
1568         $type->description = "Example description";
1569         $type->clientid = "Test client ID";
1570         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1572         $config = new stdClass();
1574         $typeid = lti_add_type($type, $config);
1576         $type = lti_get_type($typeid);
1578         $typeconfig = get_tool_type_config($type);
1580         $this->assertEquals('https://www.example.com/moodle', $typeconfig['platformid']);
1581         $this->assertEquals($type->clientid, $typeconfig['clientid']);
1582         $this->assertEquals($typeid, $typeconfig['deploymentid']);
1583         $this->assertEquals('https://www.example.com/moodle/mod/lti/certs.php', $typeconfig['publickeyseturl']);
1584         $this->assertEquals('https://www.example.com/moodle/mod/lti/token.php', $typeconfig['accesstokenurl']);
1585         $this->assertEquals('https://www.example.com/moodle/mod/lti/auth.php', $typeconfig['authrequesturl']);
1586     }
1588     /**
1589      * Test lti_new_access_token().
1590      */
1591     public function test_lti_new_access_token() {
1592         global $DB;
1594         $this->resetAfterTest();
1596         $this->setAdminUser();
1598         // Create a tool type, associated with that proxy.
1599         $type = new stdClass();
1600         $type->state = LTI_TOOL_STATE_CONFIGURED;
1601         $type->name = "Test tool";
1602         $type->description = "Example description";
1603         $type->clientid = "Test client ID";
1604         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1606         $config = new stdClass();
1608         $typeid = lti_add_type($type, $config);
1610         $scopes = ['lti_some_scope', 'lti_another_scope'];
1612         lti_new_access_token($typeid, $scopes);
1614         $token = $DB->get_records('lti_access_tokens');
1615         $this->assertEquals(1, count($token));
1617         $token = reset($token);
1619         $this->assertEquals($typeid, $token->typeid);
1620         $this->assertEquals(json_encode(array_values($scopes)), $token->scope);
1621         $this->assertEquals($token->timecreated + LTI_ACCESS_TOKEN_LIFE, $token->validuntil);
1622         $this->assertNull($token->lastaccess);
1623     }
1625     /**
1626      * Test lti_build_login_request().
1627      */
1628     public function test_lti_build_login_request() {
1629         global $USER, $CFG;
1631         $this->resetAfterTest();
1633         $USER->id = 123456789;
1635         $course   = $this->getDataGenerator()->create_course();
1636         $instance = $this->getDataGenerator()->create_module('lti',
1637             [
1638                 'course' => $course->id,
1639             ]
1640         );
1642         $config = new stdClass();
1643         $config->lti_clientid = 'some-client-id';
1644         $config->typeid = 'some-type-id';
1645         $config->lti_toolurl = 'some-lti-tool-url';
1647         $request = lti_build_login_request($course->id, $instance->id, $instance, $config, 'basic-lti-launch-request');
1649         $this->assertEquals($CFG->wwwroot, $request['iss']);
1650         $this->assertEquals('http://some-lti-tool-url', $request['target_link_uri']);
1651         $this->assertEquals(123456789, $request['login_hint']);
1652         $this->assertEquals($instance->id, $request['lti_message_hint']);
1653         $this->assertEquals('some-client-id', $request['client_id']);
1654         $this->assertEquals('some-type-id', $request['lti_deployment_id']);
1655     }
1657     /**
1658      * Test default orgid is host if not specified in config (tool installed in earlier version of Moodle).
1659      */
1660     public function test_lti_get_launch_data_default_organizationid_unset_usehost() {
1661         global $DB;
1662         $this->resetAfterTest();
1663         $this->setAdminUser();
1664         $config = new stdClass();
1665         $config->lti_organizationid = '';
1666         $course = $this->getDataGenerator()->create_course();
1667         $type = $this->create_type($config);
1668         $link = $this->create_instance($type, $course);
1669         $launchdata = lti_get_launch_data($link);
1670         $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
1671     }
1673     /**
1674      * Test default org id is set to host when config is usehost.
1675      */
1676     public function test_lti_get_launch_data_default_organizationid_set_usehost() {
1677         global $DB;
1678         $this->resetAfterTest();
1679         $this->setAdminUser();
1680         $config = new stdClass();
1681         $config->lti_organizationid = '';
1682         $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST;
1683         $course = $this->getDataGenerator()->create_course();
1684         $type = $this->create_type($config);
1685         $link = $this->create_instance($type, $course);
1686         $launchdata = lti_get_launch_data($link);
1687         $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
1688     }
1690     /**
1691      * Test default org id is set to site id when config is usesiteid.
1692      */
1693     public function test_lti_get_launch_data_default_organizationid_set_usesiteid() {
1694         global $DB;
1695         $this->resetAfterTest();
1696         $this->setAdminUser();
1697         $config = new stdClass();
1698         $config->lti_organizationid = '';
1699         $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
1700         $course = $this->getDataGenerator()->create_course();
1701         $type = $this->create_type($config);
1702         $link = $this->create_instance($type, $course);
1703         $launchdata = lti_get_launch_data($link);
1704         $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], md5(get_site_identifier()));
1705     }
1707     /**
1708      * Test orgid can be overridden in which case default is ignored.
1709      */
1710     public function test_lti_get_launch_data_default_organizationid_orgid_override() {
1711         global $DB;
1712         $this->resetAfterTest();
1713         $this->setAdminUser();
1714         $config = new stdClass();
1715         $config->lti_organizationid = 'overridden!';
1716         $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
1717         $course = $this->getDataGenerator()->create_course();
1718         $type = $this->create_type($config);
1719         $link = $this->create_instance($type, $course);
1720         $launchdata = lti_get_launch_data($link);
1721         $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'overridden!');
1722     }
1724     public function test_get_course_history() {
1725         global $DB;
1726         $this->resetAfterTest();
1727         $this->setAdminUser();
1728         $parentparentcourse = $this->getDataGenerator()->create_course();
1729         $parentcourse = $this->getDataGenerator()->create_course();
1730         $parentcourse->originalcourseid = $parentparentcourse->id;
1731         $DB->update_record('course', $parentcourse);
1732         $course = $this->getDataGenerator()->create_course();
1733         $course->originalcourseid = $parentcourse->id;
1734         $DB->update_record('course', $course);
1735         $this->assertEquals(get_course_history($parentparentcourse), []);
1736         $this->assertEquals(get_course_history($parentcourse), [$parentparentcourse->id]);
1737         $this->assertEquals(get_course_history($course), [$parentcourse->id, $parentparentcourse->id]);
1738         $course->originalcourseid = 38903;
1739         $DB->update_record('course', $course);
1740         $this->assertEquals(get_course_history($course), [38903]);
1741     }
1743     /**
1744      * Create an LTI Tool.
1745      *
1746      * @param object $config tool config.
1747      *
1748      * @return object tool.
1749      */
1750     private function create_type(object $config) {
1751         $type = new stdClass();
1752         $type->state = LTI_TOOL_STATE_CONFIGURED;
1753         $type->name = "Test tool";
1754         $type->description = "Example description";
1755         $type->clientid = "Test client ID";
1756         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1758         $configbase = new stdClass();
1759         $configbase->lti_acceptgrades = LTI_SETTING_NEVER;
1760         $configbase->lti_sendname = LTI_SETTING_NEVER;
1761         $configbase->lti_sendemailaddr = LTI_SETTING_NEVER;
1762         $mergedconfig = (object) array_merge( (array) $configbase, (array) $config);
1763         $typeid = lti_add_type($type, $mergedconfig);
1764         return lti_get_type($typeid);
1765     }
1767     /**
1768      * Create an LTI Instance for the tool in a given course.
1769      *
1770      * @param object $type tool for which an instance should be added.
1771      * @param object $course course where the instance should be added.
1772      *
1773      * @return object instance.
1774      */
1775     private function create_instance(object $type, object $course) {
1776         $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1777         return $generator->create_instance(array('course' => $course->id,
1778                   'toolurl' => $type->baseurl,
1779                   'typeid' => $type->id
1780                   ), array());
1781     }