See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 namespace mod_scorm; 18 19 use externallib_advanced_testcase; 20 use mod_scorm_external; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 global $CFG; 25 26 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 27 require_once($CFG->dirroot . '/mod/scorm/lib.php'); 28 29 /** 30 * SCORM module external functions tests 31 * 32 * @package mod_scorm 33 * @category external 34 * @copyright 2015 Juan Leyva <juan@moodle.com> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 * @since Moodle 3.0 37 */ 38 class externallib_test extends externallib_advanced_testcase { 39 40 /** 41 * Set up for every test 42 */ 43 public function setUp(): void { 44 global $DB, $CFG; 45 $this->resetAfterTest(); 46 $this->setAdminUser(); 47 48 $CFG->enablecompletion = 1; 49 // Setup test data. 50 $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1)); 51 $this->scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $this->course->id), 52 array('completion' => 2, 'completionview' => 1)); 53 $this->context = \context_module::instance($this->scorm->cmid); 54 $this->cm = get_coursemodule_from_instance('scorm', $this->scorm->id); 55 56 // Create users. 57 $this->student = self::getDataGenerator()->create_user(); 58 $this->teacher = self::getDataGenerator()->create_user(); 59 60 // Users enrolments. 61 $this->studentrole = $DB->get_record('role', array('shortname' => 'student')); 62 $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 63 $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual'); 64 $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual'); 65 } 66 67 /** 68 * Test view_scorm 69 */ 70 public function test_view_scorm() { 71 global $DB; 72 73 // Test invalid instance id. 74 try { 75 mod_scorm_external::view_scorm(0); 76 $this->fail('Exception expected due to invalid mod_scorm instance id.'); 77 } catch (\moodle_exception $e) { 78 $this->assertEquals('invalidrecord', $e->errorcode); 79 } 80 81 // Test not-enrolled user. 82 $user = self::getDataGenerator()->create_user(); 83 $this->setUser($user); 84 try { 85 mod_scorm_external::view_scorm($this->scorm->id); 86 $this->fail('Exception expected due to not enrolled user.'); 87 } catch (\moodle_exception $e) { 88 $this->assertEquals('requireloginerror', $e->errorcode); 89 } 90 91 // Test user with full capabilities. 92 $this->studentrole = $DB->get_record('role', array('shortname' => 'student')); 93 $this->getDataGenerator()->enrol_user($user->id, $this->course->id, $this->studentrole->id); 94 95 // Trigger and capture the event. 96 $sink = $this->redirectEvents(); 97 98 $result = mod_scorm_external::view_scorm($this->scorm->id); 99 $result = \external_api::clean_returnvalue(mod_scorm_external::view_scorm_returns(), $result); 100 101 $events = $sink->get_events(); 102 $this->assertCount(1, $events); 103 $event = array_shift($events); 104 105 // Checking that the event contains the expected values. 106 $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event); 107 $this->assertEquals($this->context, $event->get_context()); 108 $moodleurl = new \moodle_url('/mod/scorm/view.php', array('id' => $this->cm->id)); 109 $this->assertEquals($moodleurl, $event->get_url()); 110 $this->assertEventContextNotUsed($event); 111 $this->assertNotEmpty($event->get_name()); 112 } 113 114 /** 115 * Test get scorm attempt count 116 */ 117 public function test_mod_scorm_get_scorm_attempt_count_own_empty() { 118 // Set to the student user. 119 self::setUser($this->student); 120 121 // Retrieve my attempts (should be 0). 122 $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id); 123 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result); 124 $this->assertEquals(0, $result['attemptscount']); 125 } 126 127 public function test_mod_scorm_get_scorm_attempt_count_own_with_complete() { 128 // Set to the student user. 129 self::setUser($this->student); 130 131 // Create attempts. 132 $scoes = scorm_get_scoes($this->scorm->id); 133 $sco = array_shift($scoes); 134 scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed'); 135 scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed'); 136 137 $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id); 138 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result); 139 $this->assertEquals(2, $result['attemptscount']); 140 } 141 142 public function test_mod_scorm_get_scorm_attempt_count_own_incomplete() { 143 // Set to the student user. 144 self::setUser($this->student); 145 146 // Create a complete attempt, and an incomplete attempt. 147 $scoes = scorm_get_scoes($this->scorm->id); 148 $sco = array_shift($scoes); 149 scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed'); 150 scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 2, 'cmi.core.credit', '0'); 151 152 $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id, true); 153 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result); 154 $this->assertEquals(1, $result['attemptscount']); 155 } 156 157 public function test_mod_scorm_get_scorm_attempt_count_others_as_teacher() { 158 // As a teacher. 159 self::setUser($this->teacher); 160 161 // Create a completed attempt for student. 162 $scoes = scorm_get_scoes($this->scorm->id); 163 $sco = array_shift($scoes); 164 scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed'); 165 166 // I should be able to view the attempts for my students. 167 $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id); 168 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result); 169 $this->assertEquals(1, $result['attemptscount']); 170 } 171 172 public function test_mod_scorm_get_scorm_attempt_count_others_as_student() { 173 // Create a second student. 174 $student2 = self::getDataGenerator()->create_user(); 175 $this->getDataGenerator()->enrol_user($student2->id, $this->course->id, $this->studentrole->id, 'manual'); 176 177 // As a student. 178 self::setUser($student2); 179 180 // I should not be able to view the attempts of another student. 181 $this->expectException(\required_capability_exception::class); 182 mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id); 183 } 184 185 public function test_mod_scorm_get_scorm_attempt_count_invalid_instanceid() { 186 // As student. 187 self::setUser($this->student); 188 189 // Test invalid instance id. 190 $this->expectException(\moodle_exception::class); 191 mod_scorm_external::get_scorm_attempt_count(0, $this->student->id); 192 } 193 194 public function test_mod_scorm_get_scorm_attempt_count_invalid_userid() { 195 // As student. 196 self::setUser($this->student); 197 198 $this->expectException(\moodle_exception::class); 199 mod_scorm_external::get_scorm_attempt_count($this->scorm->id, -1); 200 } 201 202 /** 203 * Test get scorm scoes 204 */ 205 public function test_mod_scorm_get_scorm_scoes() { 206 global $DB; 207 208 $this->resetAfterTest(true); 209 210 // Create users. 211 $student = self::getDataGenerator()->create_user(); 212 $teacher = self::getDataGenerator()->create_user(); 213 214 // Create courses to add the modules. 215 $course = self::getDataGenerator()->create_course(); 216 217 // First scorm, dates restriction. 218 $record = new \stdClass(); 219 $record->course = $course->id; 220 $record->timeopen = time() + DAYSECS; 221 $record->timeclose = $record->timeopen + DAYSECS; 222 $scorm = self::getDataGenerator()->create_module('scorm', $record); 223 224 // Set to the student user. 225 self::setUser($student); 226 227 // Users enrolments. 228 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 229 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 230 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 231 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual'); 232 233 // Retrieve my scoes, warning!. 234 try { 235 mod_scorm_external::get_scorm_scoes($scorm->id); 236 $this->fail('Exception expected due to invalid dates.'); 237 } catch (\moodle_exception $e) { 238 $this->assertEquals('notopenyet', $e->errorcode); 239 } 240 241 $scorm->timeopen = time() - DAYSECS; 242 $scorm->timeclose = time() - HOURSECS; 243 $DB->update_record('scorm', $scorm); 244 245 try { 246 mod_scorm_external::get_scorm_scoes($scorm->id); 247 $this->fail('Exception expected due to invalid dates.'); 248 } catch (\moodle_exception $e) { 249 $this->assertEquals('expired', $e->errorcode); 250 } 251 252 // Retrieve my scoes, user with permission. 253 self::setUser($teacher); 254 $result = mod_scorm_external::get_scorm_scoes($scorm->id); 255 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result); 256 $this->assertCount(2, $result['scoes']); 257 $this->assertCount(0, $result['warnings']); 258 259 $scoes = scorm_get_scoes($scorm->id); 260 $sco = array_shift($scoes); 261 $sco->extradata = array(); 262 $this->assertEquals((array) $sco, $result['scoes'][0]); 263 264 $sco = array_shift($scoes); 265 $sco->extradata = array(); 266 $sco->extradata[] = array( 267 'element' => 'isvisible', 268 'value' => $sco->isvisible 269 ); 270 $sco->extradata[] = array( 271 'element' => 'parameters', 272 'value' => $sco->parameters 273 ); 274 unset($sco->isvisible); 275 unset($sco->parameters); 276 277 // Sort the array (if we don't sort tests will fails for Postgres). 278 usort($result['scoes'][1]['extradata'], function($a, $b) { 279 return strcmp($a['element'], $b['element']); 280 }); 281 282 $this->assertEquals((array) $sco, $result['scoes'][1]); 283 284 // Use organization. 285 $organization = 'golf_sample_default_org'; 286 $result = mod_scorm_external::get_scorm_scoes($scorm->id, $organization); 287 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result); 288 $this->assertCount(1, $result['scoes']); 289 $this->assertEquals($organization, $result['scoes'][0]['organization']); 290 $this->assertCount(0, $result['warnings']); 291 292 // Test invalid instance id. 293 try { 294 mod_scorm_external::get_scorm_scoes(0); 295 $this->fail('Exception expected due to invalid instance id.'); 296 } catch (\moodle_exception $e) { 297 $this->assertEquals('invalidrecord', $e->errorcode); 298 } 299 300 } 301 302 /** 303 * Test get scorm scoes (with a complex SCORM package) 304 */ 305 public function test_mod_scorm_get_scorm_scoes_complex_package() { 306 global $CFG; 307 308 // As student. 309 self::setUser($this->student); 310 311 $record = new \stdClass(); 312 $record->course = $this->course->id; 313 $record->packagefilepath = $CFG->dirroot.'/mod/scorm/tests/packages/complexscorm.zip'; 314 $scorm = self::getDataGenerator()->create_module('scorm', $record); 315 316 $result = mod_scorm_external::get_scorm_scoes($scorm->id); 317 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result); 318 $this->assertCount(9, $result['scoes']); 319 $this->assertCount(0, $result['warnings']); 320 321 $expectedscoes = array(); 322 $scoreturnstructure = mod_scorm_external::get_scorm_scoes_returns(); 323 $scoes = scorm_get_scoes($scorm->id); 324 foreach ($scoes as $sco) { 325 $sco->extradata = array(); 326 foreach ($sco as $element => $value) { 327 // Add the extra data to the extradata array and remove the object element. 328 if (!isset($scoreturnstructure->keys['scoes']->content->keys[$element])) { 329 $sco->extradata[] = array( 330 'element' => $element, 331 'value' => $value 332 ); 333 unset($sco->{$element}); 334 } 335 } 336 $expectedscoes[] = (array) $sco; 337 } 338 339 $this->assertEquals($expectedscoes, $result['scoes']); 340 } 341 342 /* 343 * Test get scorm user data 344 */ 345 public function test_mod_scorm_get_scorm_user_data() { 346 global $DB; 347 348 $this->resetAfterTest(true); 349 350 // Create users. 351 $student1 = self::getDataGenerator()->create_user(); 352 $teacher = self::getDataGenerator()->create_user(); 353 354 // Set to the student user. 355 self::setUser($student1); 356 357 // Create courses to add the modules. 358 $course = self::getDataGenerator()->create_course(); 359 360 // First scorm. 361 $record = new \stdClass(); 362 $record->course = $course->id; 363 $scorm = self::getDataGenerator()->create_module('scorm', $record); 364 365 // Users enrolments. 366 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 367 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 368 $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id, 'manual'); 369 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual'); 370 371 // Create attempts. 372 $scoes = scorm_get_scoes($scorm->id); 373 $sco = array_shift($scoes); 374 scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed'); 375 scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80'); 376 scorm_insert_track($student1->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed'); 377 378 $result = mod_scorm_external::get_scorm_user_data($scorm->id, 1); 379 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_user_data_returns(), $result); 380 $this->assertCount(2, $result['data']); 381 // Find our tracking data. 382 $found = 0; 383 foreach ($result['data'] as $scodata) { 384 foreach ($scodata['userdata'] as $userdata) { 385 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') { 386 $found++; 387 } 388 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') { 389 $found++; 390 } 391 } 392 } 393 $this->assertEquals(2, $found); 394 395 // Test invalid instance id. 396 try { 397 mod_scorm_external::get_scorm_user_data(0, 1); 398 $this->fail('Exception expected due to invalid instance id.'); 399 } catch (\moodle_exception $e) { 400 $this->assertEquals('invalidrecord', $e->errorcode); 401 } 402 } 403 404 /** 405 * Test insert scorm tracks 406 */ 407 public function test_mod_scorm_insert_scorm_tracks() { 408 global $DB; 409 410 $this->resetAfterTest(true); 411 412 // Create users. 413 $student = self::getDataGenerator()->create_user(); 414 415 // Create courses to add the modules. 416 $course = self::getDataGenerator()->create_course(); 417 418 // First scorm, dates restriction. 419 $record = new \stdClass(); 420 $record->course = $course->id; 421 $record->timeopen = time() + DAYSECS; 422 $record->timeclose = $record->timeopen + DAYSECS; 423 $scorm = self::getDataGenerator()->create_module('scorm', $record); 424 425 // Get a SCO. 426 $scoes = scorm_get_scoes($scorm->id); 427 $sco = array_shift($scoes); 428 429 // Tracks. 430 $tracks = array(); 431 $tracks[] = array( 432 'element' => 'cmi.core.lesson_status', 433 'value' => 'completed' 434 ); 435 $tracks[] = array( 436 'element' => 'cmi.core.score.raw', 437 'value' => '80' 438 ); 439 440 // Set to the student user. 441 self::setUser($student); 442 443 // Users enrolments. 444 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 445 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 446 447 // Exceptions first. 448 try { 449 mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks); 450 $this->fail('Exception expected due to dates'); 451 } catch (\moodle_exception $e) { 452 $this->assertEquals('notopenyet', $e->errorcode); 453 } 454 455 $scorm->timeopen = time() - DAYSECS; 456 $scorm->timeclose = time() - HOURSECS; 457 $DB->update_record('scorm', $scorm); 458 459 try { 460 mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks); 461 $this->fail('Exception expected due to dates'); 462 } catch (\moodle_exception $e) { 463 $this->assertEquals('expired', $e->errorcode); 464 } 465 466 // Test invalid instance id. 467 try { 468 mod_scorm_external::insert_scorm_tracks(0, 1, $tracks); 469 $this->fail('Exception expected due to invalid sco id.'); 470 } catch (\moodle_exception $e) { 471 $this->assertEquals('cannotfindsco', $e->errorcode); 472 } 473 474 $scorm->timeopen = 0; 475 $scorm->timeclose = 0; 476 $DB->update_record('scorm', $scorm); 477 478 // Retrieve my tracks. 479 $result = mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks); 480 $result = \external_api::clean_returnvalue(mod_scorm_external::insert_scorm_tracks_returns(), $result); 481 $this->assertCount(0, $result['warnings']); 482 483 $trackids = $DB->get_records('scorm_scoes_track', array('userid' => $student->id, 'scoid' => $sco->id, 484 'scormid' => $scorm->id, 'attempt' => 1)); 485 // We use asort here to prevent problems with ids ordering. 486 $expectedkeys = array_keys($trackids); 487 $this->assertEquals(asort($expectedkeys), asort($result['trackids'])); 488 } 489 490 /** 491 * Test get scorm sco tracks 492 */ 493 public function test_mod_scorm_get_scorm_sco_tracks() { 494 global $DB; 495 496 $this->resetAfterTest(true); 497 498 // Create users. 499 $student = self::getDataGenerator()->create_user(); 500 $otherstudent = self::getDataGenerator()->create_user(); 501 $teacher = self::getDataGenerator()->create_user(); 502 503 // Set to the student user. 504 self::setUser($student); 505 506 // Create courses to add the modules. 507 $course = self::getDataGenerator()->create_course(); 508 509 // First scorm. 510 $record = new \stdClass(); 511 $record->course = $course->id; 512 $scorm = self::getDataGenerator()->create_module('scorm', $record); 513 514 // Users enrolments. 515 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 516 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 517 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 518 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual'); 519 520 // Create attempts. 521 $scoes = scorm_get_scoes($scorm->id); 522 $sco = array_shift($scoes); 523 scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed'); 524 scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80'); 525 scorm_insert_track($student->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed'); 526 527 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 1); 528 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result); 529 // 7 default elements + 2 custom ones. 530 $this->assertCount(9, $result['data']['tracks']); 531 $this->assertEquals(1, $result['data']['attempt']); 532 $this->assertCount(0, $result['warnings']); 533 // Find our tracking data. 534 $found = 0; 535 foreach ($result['data']['tracks'] as $userdata) { 536 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') { 537 $found++; 538 } 539 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') { 540 $found++; 541 } 542 } 543 $this->assertEquals(2, $found); 544 545 // Try invalid attempt. 546 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 10); 547 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result); 548 $this->assertCount(0, $result['data']['tracks']); 549 $this->assertEquals(10, $result['data']['attempt']); 550 $this->assertCount(1, $result['warnings']); 551 $this->assertEquals('notattempted', $result['warnings'][0]['warningcode']); 552 553 // Capabilities check. 554 try { 555 mod_scorm_external::get_scorm_sco_tracks($sco->id, $otherstudent->id); 556 $this->fail('Exception expected due to invalid instance id.'); 557 } catch (\required_capability_exception $e) { 558 $this->assertEquals('nopermissions', $e->errorcode); 559 } 560 561 self::setUser($teacher); 562 // Ommit the attempt parameter, the function should calculate the last attempt. 563 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id); 564 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result); 565 // 7 default elements + 1 custom one. 566 $this->assertCount(8, $result['data']['tracks']); 567 $this->assertEquals(2, $result['data']['attempt']); 568 569 // Test invalid instance id. 570 try { 571 mod_scorm_external::get_scorm_sco_tracks(0, 1); 572 $this->fail('Exception expected due to invalid instance id.'); 573 } catch (\moodle_exception $e) { 574 $this->assertEquals('cannotfindsco', $e->errorcode); 575 } 576 // Invalid user. 577 try { 578 mod_scorm_external::get_scorm_sco_tracks($sco->id, 0); 579 $this->fail('Exception expected due to invalid instance id.'); 580 } catch (\moodle_exception $e) { 581 $this->assertEquals('invaliduser', $e->errorcode); 582 } 583 } 584 585 /* 586 * Test get scorms by courses 587 */ 588 public function test_mod_scorm_get_scorms_by_courses() { 589 global $DB; 590 591 $this->resetAfterTest(true); 592 593 // Create users. 594 $student = self::getDataGenerator()->create_user(); 595 $teacher = self::getDataGenerator()->create_user(); 596 597 // Set to the student user. 598 self::setUser($student); 599 600 // Create courses to add the modules. 601 $course1 = self::getDataGenerator()->create_course(); 602 $course2 = self::getDataGenerator()->create_course(); 603 604 // First scorm. 605 $record = new \stdClass(); 606 $record->introformat = FORMAT_HTML; 607 $record->course = $course1->id; 608 $record->hidetoc = 2; 609 $record->displayattemptstatus = 2; 610 $record->skipview = 2; 611 $scorm1 = self::getDataGenerator()->create_module('scorm', $record); 612 613 // Second scorm. 614 $record = new \stdClass(); 615 $record->introformat = FORMAT_HTML; 616 $record->course = $course2->id; 617 $scorm2 = self::getDataGenerator()->create_module('scorm', $record); 618 619 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 620 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 621 622 // Users enrolments. 623 $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual'); 624 $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id, 'manual'); 625 626 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later. 627 $enrol = enrol_get_plugin('manual'); 628 $enrolinstances = enrol_get_instances($course2->id, true); 629 foreach ($enrolinstances as $courseenrolinstance) { 630 if ($courseenrolinstance->enrol == "manual") { 631 $instance2 = $courseenrolinstance; 632 break; 633 } 634 } 635 $enrol->enrol_user($instance2, $student->id, $studentrole->id); 636 637 $returndescription = mod_scorm_external::get_scorms_by_courses_returns(); 638 639 // Test open/close dates. 640 641 $timenow = time(); 642 $scorm1->timeopen = $timenow - DAYSECS; 643 $scorm1->timeclose = $timenow - HOURSECS; 644 $DB->update_record('scorm', $scorm1); 645 646 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id)); 647 $result = \external_api::clean_returnvalue($returndescription, $result); 648 $this->assertCount(1, $result['warnings']); 649 // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'. 650 $this->assertCount(8, $result['scorms'][0]); 651 $this->assertEquals('expired', $result['warnings'][0]['warningcode']); 652 653 $scorm1->timeopen = $timenow + DAYSECS; 654 $scorm1->timeclose = $scorm1->timeopen + DAYSECS; 655 $DB->update_record('scorm', $scorm1); 656 657 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id)); 658 $result = \external_api::clean_returnvalue($returndescription, $result); 659 $this->assertCount(1, $result['warnings']); 660 // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'. 661 $this->assertCount(8, $result['scorms'][0]); 662 $this->assertEquals('notopenyet', $result['warnings'][0]['warningcode']); 663 664 // Reset times. 665 $scorm1->timeopen = 0; 666 $scorm1->timeclose = 0; 667 $DB->update_record('scorm', $scorm1); 668 669 // Create what we expect to be returned when querying the two courses. 670 // First for the student user. 671 $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'lang', 'version', 'maxgrade', 672 'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted', 'forcenewattempt', 'lastattemptlock', 673 'displayattemptstatus', 'displaycoursestructure', 'sha1hash', 'md5hash', 'revision', 'launch', 674 'skipview', 'hidebrowse', 'hidetoc', 'nav', 'navpositionleft', 'navpositiontop', 'auto', 675 'popup', 'width', 'height', 'timeopen', 'timeclose', 'packagesize', 676 'packageurl', 'scormtype', 'reference'); 677 678 // Add expected coursemodule and data. 679 $scorm1->coursemodule = $scorm1->cmid; 680 $scorm1->section = 0; 681 $scorm1->visible = true; 682 $scorm1->groupmode = 0; 683 $scorm1->groupingid = 0; 684 $scorm1->lang = ''; 685 686 $scorm2->coursemodule = $scorm2->cmid; 687 $scorm2->section = 0; 688 $scorm2->visible = true; 689 $scorm2->groupmode = 0; 690 $scorm2->groupingid = 0; 691 $scorm2->lang = ''; 692 693 // SCORM size. The same package is used in both SCORMs. 694 $scormcontext1 = \context_module::instance($scorm1->cmid); 695 $scormcontext2 = \context_module::instance($scorm2->cmid); 696 $fs = get_file_storage(); 697 $packagefile = $fs->get_file($scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference); 698 $packagesize = $packagefile->get_filesize(); 699 700 $packageurl1 = \moodle_url::make_webservice_pluginfile_url( 701 $scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference)->out(false); 702 $packageurl2 = \moodle_url::make_webservice_pluginfile_url( 703 $scormcontext2->id, 'mod_scorm', 'package', 0, '/', $scorm2->reference)->out(false); 704 705 $scorm1->packagesize = $packagesize; 706 $scorm1->packageurl = $packageurl1; 707 $scorm2->packagesize = $packagesize; 708 $scorm2->packageurl = $packageurl2; 709 710 // Forced to boolean as it is returned as PARAM_BOOL. 711 $protectpackages = (bool)get_config('scorm', 'protectpackagedownloads'); 712 $expected1 = array('protectpackagedownloads' => $protectpackages); 713 $expected2 = array('protectpackagedownloads' => $protectpackages); 714 foreach ($expectedfields as $field) { 715 716 // Since we return the fields used as boolean as PARAM_BOOL instead PARAM_INT we need to force casting here. 717 // From the returned fields definition we obtain the type expected for the field. 718 if (empty($returndescription->keys['scorms']->content->keys[$field]->type)) { 719 continue; 720 } 721 $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type; 722 if ($fieldtype == PARAM_BOOL) { 723 $expected1[$field] = (bool) $scorm1->{$field}; 724 $expected2[$field] = (bool) $scorm2->{$field}; 725 } else { 726 $expected1[$field] = $scorm1->{$field}; 727 $expected2[$field] = $scorm2->{$field}; 728 } 729 } 730 $expected1['introfiles'] = []; 731 $expected2['introfiles'] = []; 732 733 $expectedscorms = array(); 734 $expectedscorms[] = $expected2; 735 $expectedscorms[] = $expected1; 736 737 // Call the external function passing course ids. 738 $result = mod_scorm_external::get_scorms_by_courses(array($course2->id, $course1->id)); 739 $result = \external_api::clean_returnvalue($returndescription, $result); 740 $this->assertEquals($expectedscorms, $result['scorms']); 741 742 // Call the external function without passing course id. 743 $result = mod_scorm_external::get_scorms_by_courses(); 744 $result = \external_api::clean_returnvalue($returndescription, $result); 745 $this->assertEquals($expectedscorms, $result['scorms']); 746 747 // Unenrol user from second course and alter expected scorms. 748 $enrol->unenrol_user($instance2, $student->id); 749 array_shift($expectedscorms); 750 751 // Call the external function without passing course id. 752 $result = mod_scorm_external::get_scorms_by_courses(); 753 $result = \external_api::clean_returnvalue($returndescription, $result); 754 $this->assertEquals($expectedscorms, $result['scorms']); 755 756 // Call for the second course we unenrolled the user from, expected warning. 757 $result = mod_scorm_external::get_scorms_by_courses(array($course2->id)); 758 $this->assertCount(1, $result['warnings']); 759 $this->assertEquals('1', $result['warnings'][0]['warningcode']); 760 $this->assertEquals($course2->id, $result['warnings'][0]['itemid']); 761 762 // Now, try as a teacher for getting all the additional fields. 763 self::setUser($teacher); 764 765 $additionalfields = array('updatefreq', 'timemodified', 'options', 766 'completionstatusrequired', 'completionscorerequired', 'completionstatusallscos', 767 'autocommit', 'section', 'visible', 'groupmode', 'groupingid'); 768 769 foreach ($additionalfields as $field) { 770 $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type; 771 772 if ($fieldtype == PARAM_BOOL) { 773 $expectedscorms[0][$field] = (bool) $scorm1->{$field}; 774 } else { 775 $expectedscorms[0][$field] = $scorm1->{$field}; 776 } 777 } 778 779 $result = mod_scorm_external::get_scorms_by_courses(); 780 $result = \external_api::clean_returnvalue($returndescription, $result); 781 $this->assertEquals($expectedscorms, $result['scorms']); 782 783 // Even with the SCORM closed in time teacher should retrieve the info. 784 $scorm1->timeopen = $timenow - DAYSECS; 785 $scorm1->timeclose = $timenow - HOURSECS; 786 $DB->update_record('scorm', $scorm1); 787 788 $expectedscorms[0]['timeopen'] = $scorm1->timeopen; 789 $expectedscorms[0]['timeclose'] = $scorm1->timeclose; 790 791 $result = mod_scorm_external::get_scorms_by_courses(); 792 $result = \external_api::clean_returnvalue($returndescription, $result); 793 $this->assertEquals($expectedscorms, $result['scorms']); 794 795 // Admin also should get all the information. 796 self::setAdminUser(); 797 798 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id)); 799 $result = \external_api::clean_returnvalue($returndescription, $result); 800 $this->assertEquals($expectedscorms, $result['scorms']); 801 } 802 803 /** 804 * Test launch_sco 805 */ 806 public function test_launch_sco() { 807 global $DB; 808 809 // Test invalid instance id. 810 try { 811 mod_scorm_external::launch_sco(0); 812 $this->fail('Exception expected due to invalid mod_scorm instance id.'); 813 } catch (\moodle_exception $e) { 814 $this->assertEquals('invalidrecord', $e->errorcode); 815 } 816 817 // Test not-enrolled user. 818 $user = self::getDataGenerator()->create_user(); 819 $this->setUser($user); 820 try { 821 mod_scorm_external::launch_sco($this->scorm->id); 822 $this->fail('Exception expected due to not enrolled user.'); 823 } catch (\moodle_exception $e) { 824 $this->assertEquals('requireloginerror', $e->errorcode); 825 } 826 827 // Test user with full capabilities. 828 $this->setUser($this->student); 829 830 // Trigger and capture the event. 831 $sink = $this->redirectEvents(); 832 833 $scoes = scorm_get_scoes($this->scorm->id); 834 foreach ($scoes as $sco) { 835 // Find launchable SCO. 836 if ($sco->launch != '') { 837 break; 838 } 839 } 840 841 $result = mod_scorm_external::launch_sco($this->scorm->id, $sco->id); 842 $result = \external_api::clean_returnvalue(mod_scorm_external::launch_sco_returns(), $result); 843 844 $events = $sink->get_events(); 845 $this->assertCount(3, $events); 846 $event = array_pop($events); 847 848 // Checking that the event contains the expected values. 849 $this->assertInstanceOf('\mod_scorm\event\sco_launched', $event); 850 $this->assertEquals($this->context, $event->get_context()); 851 $moodleurl = new \moodle_url('/mod/scorm/player.php', array('cm' => $this->cm->id, 'scoid' => $sco->id)); 852 $this->assertEquals($moodleurl, $event->get_url()); 853 $this->assertEventContextNotUsed($event); 854 $this->assertNotEmpty($event->get_name()); 855 856 $event = array_shift($events); 857 $this->assertInstanceOf('\core\event\course_module_completion_updated', $event); 858 859 // Check completion status. 860 $completion = new \completion_info($this->course); 861 $completiondata = $completion->get_data($this->cm); 862 $this->assertEquals(COMPLETION_VIEWED, $completiondata->completionstate); 863 864 // Invalid SCO. 865 try { 866 mod_scorm_external::launch_sco($this->scorm->id, -1); 867 $this->fail('Exception expected due to invalid SCO id.'); 868 } catch (\moodle_exception $e) { 869 $this->assertEquals('cannotfindsco', $e->errorcode); 870 } 871 } 872 873 /** 874 * Test mod_scorm_get_scorm_access_information. 875 */ 876 public function test_mod_scorm_get_scorm_access_information() { 877 global $DB; 878 879 $this->resetAfterTest(true); 880 881 $student = self::getDataGenerator()->create_user(); 882 $course = self::getDataGenerator()->create_course(); 883 // Create the scorm. 884 $record = new \stdClass(); 885 $record->course = $course->id; 886 $scorm = self::getDataGenerator()->create_module('scorm', $record); 887 $context = \context_module::instance($scorm->cmid); 888 889 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 890 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 891 892 self::setUser($student); 893 $result = mod_scorm_external::get_scorm_access_information($scorm->id); 894 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_access_information_returns(), $result); 895 896 // Check default values for capabilities. 897 $enabledcaps = array('canskipview', 'cansavetrack', 'canviewscores'); 898 899 unset($result['warnings']); 900 foreach ($result as $capname => $capvalue) { 901 if (in_array($capname, $enabledcaps)) { 902 $this->assertTrue($capvalue); 903 } else { 904 $this->assertFalse($capvalue); 905 } 906 } 907 // Now, unassign one capability. 908 unassign_capability('mod/scorm:viewscores', $studentrole->id); 909 array_pop($enabledcaps); 910 accesslib_clear_all_caches_for_unit_testing(); 911 912 $result = mod_scorm_external::get_scorm_access_information($scorm->id); 913 $result = \external_api::clean_returnvalue(mod_scorm_external::get_scorm_access_information_returns(), $result); 914 unset($result['warnings']); 915 foreach ($result as $capname => $capvalue) { 916 if (in_array($capname, $enabledcaps)) { 917 $this->assertTrue($capvalue); 918 } else { 919 $this->assertFalse($capvalue); 920 } 921 } 922 } 923 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body