Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [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 508 $trackids = $DB->get_records('scorm_scoes_track', array('userid' => $student->id, 'scoid' => $sco->id, 509 'scormid' => $scorm->id, 'attempt' => 1)); 510 // We use asort here to prevent problems with ids ordering. 511 $expectedkeys = array_keys($trackids); 512 $this->assertEquals(asort($expectedkeys), asort($result['trackids'])); 513 } 514 515 /** 516 * Test get scorm sco tracks 517 */ 518 public function test_mod_scorm_get_scorm_sco_tracks() { 519 global $DB; 520 521 $this->resetAfterTest(true); 522 523 // Create users. 524 $student = self::getDataGenerator()->create_user(); 525 $otherstudent = self::getDataGenerator()->create_user(); 526 $teacher = self::getDataGenerator()->create_user(); 527 528 // Set to the student user. 529 self::setUser($student); 530 531 // Create courses to add the modules. 532 $course = self::getDataGenerator()->create_course(); 533 534 // First scorm. 535 $record = new \stdClass(); 536 $record->course = $course->id; 537 $scorm = self::getDataGenerator()->create_module('scorm', $record); 538 539 // Users enrolments. 540 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 541 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher')); 542 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 543 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual'); 544 545 // Create attempts. 546 $scoes = scorm_get_scoes($scorm->id); 547 $sco = array_shift($scoes); 548 scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed'); 549 scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80'); 550 scorm_insert_track($student->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed'); 551 552 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 1); 553 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result); 554 // 7 default elements + 2 custom ones. 555 $this->assertCount(9, $result['data']['tracks']); 556 $this->assertEquals(1, $result['data']['attempt']); 557 $this->assertCount(0, $result['warnings']); 558 // Find our tracking data. 559 $found = 0; 560 foreach ($result['data']['tracks'] as $userdata) { 561 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') { 562 $found++; 563 } 564 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') { 565 $found++; 566 } 567 } 568 $this->assertEquals(2, $found); 569 570 // Try invalid attempt. 571 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 10); 572 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result); 573 $this->assertCount(0, $result['data']['tracks']); 574 $this->assertEquals(10, $result['data']['attempt']); 575 $this->assertCount(1, $result['warnings']); 576 $this->assertEquals('notattempted', $result['warnings'][0]['warningcode']); 577 578 // Capabilities check. 579 try { 580 mod_scorm_external::get_scorm_sco_tracks($sco->id, $otherstudent->id); 581 $this->fail('Exception expected due to invalid instance id.'); 582 } catch (\required_capability_exception $e) { 583 $this->assertEquals('nopermissions', $e->errorcode); 584 } 585 586 self::setUser($teacher); 587 // Ommit the attempt parameter, the function should calculate the last attempt. 588 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id); 589 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result); 590 // 7 default elements + 1 custom one. 591 $this->assertCount(8, $result['data']['tracks']); 592 $this->assertEquals(2, $result['data']['attempt']); 593 594 // Test invalid instance id. 595 try { 596 mod_scorm_external::get_scorm_sco_tracks(0, 1); 597 $this->fail('Exception expected due to invalid instance id.'); 598 } catch (\moodle_exception $e) { 599 $this->assertEquals('cannotfindsco', $e->errorcode); 600 } 601 // Invalid user. 602 try { 603 mod_scorm_external::get_scorm_sco_tracks($sco->id, 0); 604 $this->fail('Exception expected due to invalid instance id.'); 605 } catch (\moodle_exception $e) { 606 $this->assertEquals('invaliduser', $e->errorcode); 607 } 608 } 609 610 /* 611 * Test get scorms by courses 612 */ 613 public function test_mod_scorm_get_scorms_by_courses() { 614 global $DB; 615 616 $this->resetAfterTest(true); 617 618 // Create users. 619 $student = self::getDataGenerator()->create_user(); 620 $teacher = self::getDataGenerator()->create_user(); 621 622 // Set to the student user. 623 self::setUser($student); 624 625 // Create courses to add the modules. 626 $course1 = self::getDataGenerator()->create_course(); 627 $course2 = self::getDataGenerator()->create_course(); 628 629 // First scorm. 630 $record = new \stdClass(); 631 $record->introformat = FORMAT_HTML; 632 $record->course = $course1->id; 633 $record->hidetoc = 2; 634 $record->displayattemptstatus = 2; 635 $record->skipview = 2; 636 $scorm1 = self::getDataGenerator()->create_module('scorm', $record); 637 638 // Second scorm. 639 $record = new \stdClass(); 640 $record->introformat = FORMAT_HTML; 641 $record->course = $course2->id; 642 $scorm2 = self::getDataGenerator()->create_module('scorm', $record); 643 644 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 645 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 646 647 // Users enrolments. 648 $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual'); 649 $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id, 'manual'); 650 651 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later. 652 $enrol = enrol_get_plugin('manual'); 653 $enrolinstances = enrol_get_instances($course2->id, true); 654 foreach ($enrolinstances as $courseenrolinstance) { 655 if ($courseenrolinstance->enrol == "manual") { 656 $instance2 = $courseenrolinstance; 657 break; 658 } 659 } 660 $enrol->enrol_user($instance2, $student->id, $studentrole->id); 661 662 $returndescription = mod_scorm_external::get_scorms_by_courses_returns(); 663 664 // Test open/close dates. 665 666 $timenow = time(); 667 $scorm1->timeopen = $timenow - DAYSECS; 668 $scorm1->timeclose = $timenow - HOURSECS; 669 $DB->update_record('scorm', $scorm1); 670 671 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id)); 672 $result = external_api::clean_returnvalue($returndescription, $result); 673 $this->assertCount(1, $result['warnings']); 674 // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'. 675 $this->assertCount(8, $result['scorms'][0]); 676 $this->assertEquals('expired', $result['warnings'][0]['warningcode']); 677 678 $scorm1->timeopen = $timenow + DAYSECS; 679 $scorm1->timeclose = $scorm1->timeopen + DAYSECS; 680 $DB->update_record('scorm', $scorm1); 681 682 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id)); 683 $result = external_api::clean_returnvalue($returndescription, $result); 684 $this->assertCount(1, $result['warnings']); 685 // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'. 686 $this->assertCount(8, $result['scorms'][0]); 687 $this->assertEquals('notopenyet', $result['warnings'][0]['warningcode']); 688 689 // Reset times. 690 $scorm1->timeopen = 0; 691 $scorm1->timeclose = 0; 692 $DB->update_record('scorm', $scorm1); 693 694 // Create what we expect to be returned when querying the two courses. 695 // First for the student user. 696 $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'lang', 'version', 'maxgrade', 697 'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted', 'forcenewattempt', 'lastattemptlock', 698 'displayattemptstatus', 'displaycoursestructure', 'sha1hash', 'md5hash', 'revision', 'launch', 699 'skipview', 'hidebrowse', 'hidetoc', 'nav', 'navpositionleft', 'navpositiontop', 'auto', 700 'popup', 'width', 'height', 'timeopen', 'timeclose', 'packagesize', 701 'packageurl', 'scormtype', 'reference'); 702 703 // Add expected coursemodule and data. 704 $scorm1->coursemodule = $scorm1->cmid; 705 $scorm1->section = 0; 706 $scorm1->visible = true; 707 $scorm1->groupmode = 0; 708 $scorm1->groupingid = 0; 709 $scorm1->lang = ''; 710 711 $scorm2->coursemodule = $scorm2->cmid; 712 $scorm2->section = 0; 713 $scorm2->visible = true; 714 $scorm2->groupmode = 0; 715 $scorm2->groupingid = 0; 716 $scorm2->lang = ''; 717 718 // SCORM size. The same package is used in both SCORMs. 719 $scormcontext1 = \context_module::instance($scorm1->cmid); 720 $scormcontext2 = \context_module::instance($scorm2->cmid); 721 $fs = get_file_storage(); 722 $packagefile = $fs->get_file($scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference); 723 $packagesize = $packagefile->get_filesize(); 724 725 $packageurl1 = \moodle_url::make_webservice_pluginfile_url( 726 $scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference)->out(false); 727 $packageurl2 = \moodle_url::make_webservice_pluginfile_url( 728 $scormcontext2->id, 'mod_scorm', 'package', 0, '/', $scorm2->reference)->out(false); 729 730 $scorm1->packagesize = $packagesize; 731 $scorm1->packageurl = $packageurl1; 732 $scorm2->packagesize = $packagesize; 733 $scorm2->packageurl = $packageurl2; 734 735 // Forced to boolean as it is returned as PARAM_BOOL. 736 $protectpackages = (bool)get_config('scorm', 'protectpackagedownloads'); 737 $expected1 = array('protectpackagedownloads' => $protectpackages); 738 $expected2 = array('protectpackagedownloads' => $protectpackages); 739 foreach ($expectedfields as $field) { 740 741 // Since we return the fields used as boolean as PARAM_BOOL instead PARAM_INT we need to force casting here. 742 // From the returned fields definition we obtain the type expected for the field. 743 if (empty($returndescription->keys['scorms']->content->keys[$field]->type)) { 744 continue; 745 } 746 $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type; 747 if ($fieldtype == PARAM_BOOL) { 748 $expected1[$field] = (bool) $scorm1->{$field}; 749 $expected2[$field] = (bool) $scorm2->{$field}; 750 } else { 751 $expected1[$field] = $scorm1->{$field}; 752 $expected2[$field] = $scorm2->{$field}; 753 } 754 } 755 $expected1['introfiles'] = []; 756 $expected2['introfiles'] = []; 757 758 $expectedscorms = array(); 759 $expectedscorms[] = $expected2; 760 $expectedscorms[] = $expected1; 761 762 // Call the external function passing course ids. 763 $result = mod_scorm_external::get_scorms_by_courses(array($course2->id, $course1->id)); 764 $result = external_api::clean_returnvalue($returndescription, $result); 765 $this->assertEquals($expectedscorms, $result['scorms']); 766 767 // Call the external function without passing course id. 768 $result = mod_scorm_external::get_scorms_by_courses(); 769 $result = external_api::clean_returnvalue($returndescription, $result); 770 $this->assertEquals($expectedscorms, $result['scorms']); 771 772 // Unenrol user from second course and alter expected scorms. 773 $enrol->unenrol_user($instance2, $student->id); 774 array_shift($expectedscorms); 775 776 // Call the external function without passing course id. 777 $result = mod_scorm_external::get_scorms_by_courses(); 778 $result = external_api::clean_returnvalue($returndescription, $result); 779 $this->assertEquals($expectedscorms, $result['scorms']); 780 781 // Call for the second course we unenrolled the user from, expected warning. 782 $result = mod_scorm_external::get_scorms_by_courses(array($course2->id)); 783 $this->assertCount(1, $result['warnings']); 784 $this->assertEquals('1', $result['warnings'][0]['warningcode']); 785 $this->assertEquals($course2->id, $result['warnings'][0]['itemid']); 786 787 // Now, try as a teacher for getting all the additional fields. 788 self::setUser($teacher); 789 790 $additionalfields = array('updatefreq', 'timemodified', 'options', 791 'completionstatusrequired', 'completionscorerequired', 'completionstatusallscos', 792 'autocommit', 'section', 'visible', 'groupmode', 'groupingid'); 793 794 foreach ($additionalfields as $field) { 795 $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type; 796 797 if ($fieldtype == PARAM_BOOL) { 798 $expectedscorms[0][$field] = (bool) $scorm1->{$field}; 799 } else { 800 $expectedscorms[0][$field] = $scorm1->{$field}; 801 } 802 } 803 804 $result = mod_scorm_external::get_scorms_by_courses(); 805 $result = external_api::clean_returnvalue($returndescription, $result); 806 $this->assertEquals($expectedscorms, $result['scorms']); 807 808 // Even with the SCORM closed in time teacher should retrieve the info. 809 $scorm1->timeopen = $timenow - DAYSECS; 810 $scorm1->timeclose = $timenow - HOURSECS; 811 $DB->update_record('scorm', $scorm1); 812 813 $expectedscorms[0]['timeopen'] = $scorm1->timeopen; 814 $expectedscorms[0]['timeclose'] = $scorm1->timeclose; 815 816 $result = mod_scorm_external::get_scorms_by_courses(); 817 $result = external_api::clean_returnvalue($returndescription, $result); 818 $this->assertEquals($expectedscorms, $result['scorms']); 819 820 // Admin also should get all the information. 821 self::setAdminUser(); 822 823 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id)); 824 $result = external_api::clean_returnvalue($returndescription, $result); 825 $this->assertEquals($expectedscorms, $result['scorms']); 826 } 827 828 /** 829 * Test launch_sco 830 */ 831 public function test_launch_sco() { 832 global $DB; 833 834 // Test invalid instance id. 835 try { 836 mod_scorm_external::launch_sco(0); 837 $this->fail('Exception expected due to invalid mod_scorm instance id.'); 838 } catch (\moodle_exception $e) { 839 $this->assertEquals('invalidrecord', $e->errorcode); 840 } 841 842 // Test not-enrolled user. 843 $user = self::getDataGenerator()->create_user(); 844 $this->setUser($user); 845 try { 846 mod_scorm_external::launch_sco($this->scorm->id); 847 $this->fail('Exception expected due to not enrolled user.'); 848 } catch (\moodle_exception $e) { 849 $this->assertEquals('requireloginerror', $e->errorcode); 850 } 851 852 // Test user with full capabilities. 853 $this->setUser($this->student); 854 855 // Trigger and capture the event. 856 $sink = $this->redirectEvents(); 857 858 $scoes = scorm_get_scoes($this->scorm->id); 859 foreach ($scoes as $sco) { 860 // Find launchable SCO. 861 if ($sco->launch != '') { 862 break; 863 } 864 } 865 866 $result = mod_scorm_external::launch_sco($this->scorm->id, $sco->id); 867 $result = external_api::clean_returnvalue(mod_scorm_external::launch_sco_returns(), $result); 868 869 $events = $sink->get_events(); 870 $this->assertCount(3, $events); 871 $event = array_pop($events); 872 873 // Checking that the event contains the expected values. 874 $this->assertInstanceOf('\mod_scorm\event\sco_launched', $event); 875 $this->assertEquals($this->context, $event->get_context()); 876 $moodleurl = new \moodle_url('/mod/scorm/player.php', array('cm' => $this->cm->id, 'scoid' => $sco->id)); 877 $this->assertEquals($moodleurl, $event->get_url()); 878 $this->assertEventContextNotUsed($event); 879 $this->assertNotEmpty($event->get_name()); 880 881 $event = array_shift($events); 882 $this->assertInstanceOf('\core\event\course_module_completion_updated', $event); 883 884 // Check completion status. 885 $completion = new \completion_info($this->course); 886 $completiondata = $completion->get_data($this->cm); 887 $this->assertEquals(COMPLETION_VIEWED, $completiondata->completionstate); 888 889 // Invalid SCO. 890 try { 891 mod_scorm_external::launch_sco($this->scorm->id, -1); 892 $this->fail('Exception expected due to invalid SCO id.'); 893 } catch (\moodle_exception $e) { 894 $this->assertEquals('cannotfindsco', $e->errorcode); 895 } 896 } 897 898 /** 899 * Test mod_scorm_get_scorm_access_information. 900 */ 901 public function test_mod_scorm_get_scorm_access_information() { 902 global $DB; 903 904 $this->resetAfterTest(true); 905 906 $student = self::getDataGenerator()->create_user(); 907 $course = self::getDataGenerator()->create_course(); 908 // Create the scorm. 909 $record = new \stdClass(); 910 $record->course = $course->id; 911 $scorm = self::getDataGenerator()->create_module('scorm', $record); 912 $context = \context_module::instance($scorm->cmid); 913 914 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 915 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 916 917 self::setUser($student); 918 $result = mod_scorm_external::get_scorm_access_information($scorm->id); 919 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_access_information_returns(), $result); 920 921 // Check default values for capabilities. 922 $enabledcaps = array('canskipview', 'cansavetrack', 'canviewscores'); 923 924 unset($result['warnings']); 925 foreach ($result as $capname => $capvalue) { 926 if (in_array($capname, $enabledcaps)) { 927 $this->assertTrue($capvalue); 928 } else { 929 $this->assertFalse($capvalue); 930 } 931 } 932 // Now, unassign one capability. 933 unassign_capability('mod/scorm:viewscores', $studentrole->id); 934 array_pop($enabledcaps); 935 accesslib_clear_all_caches_for_unit_testing(); 936 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 unset($result['warnings']); 940 foreach ($result as $capname => $capvalue) { 941 if (in_array($capname, $enabledcaps)) { 942 $this->assertTrue($capvalue); 943 } else { 944 $this->assertFalse($capvalue); 945 } 946 } 947 } 948 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body