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