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