[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * @package mod_scorm 19 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 20 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 21 */ 22 23 /** SCORM_TYPE_LOCAL = local */ 24 define('SCORM_TYPE_LOCAL', 'local'); 25 /** SCORM_TYPE_LOCALSYNC = localsync */ 26 define('SCORM_TYPE_LOCALSYNC', 'localsync'); 27 /** SCORM_TYPE_EXTERNAL = external */ 28 define('SCORM_TYPE_EXTERNAL', 'external'); 29 /** SCORM_TYPE_AICCURL = external AICC url */ 30 define('SCORM_TYPE_AICCURL', 'aiccurl'); 31 32 define('SCORM_TOC_SIDE', 0); 33 define('SCORM_TOC_HIDDEN', 1); 34 define('SCORM_TOC_POPUP', 2); 35 define('SCORM_TOC_DISABLED', 3); 36 37 // Used to show/hide navigation buttons and set their position. 38 define('SCORM_NAV_DISABLED', 0); 39 define('SCORM_NAV_UNDER_CONTENT', 1); 40 define('SCORM_NAV_FLOATING', 2); 41 42 // Used to check what SCORM version is being used. 43 define('SCORM_12', 1); 44 define('SCORM_13', 2); 45 define('SCORM_AICC', 3); 46 47 // List of possible attemptstatusdisplay options. 48 define('SCORM_DISPLAY_ATTEMPTSTATUS_NO', 0); 49 define('SCORM_DISPLAY_ATTEMPTSTATUS_ALL', 1); 50 define('SCORM_DISPLAY_ATTEMPTSTATUS_MY', 2); 51 define('SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY', 3); 52 53 /** 54 * Return an array of status options 55 * 56 * Optionally with translated strings 57 * 58 * @param bool $with_strings (optional) 59 * @return array 60 */ 61 function scorm_status_options($withstrings = false) { 62 // Id's are important as they are bits. 63 $options = array( 64 2 => 'passed', 65 4 => 'completed' 66 ); 67 68 if ($withstrings) { 69 foreach ($options as $key => $value) { 70 $options[$key] = get_string('completionstatus_'.$value, 'scorm'); 71 } 72 } 73 74 return $options; 75 } 76 77 78 /** 79 * Given an object containing all the necessary data, 80 * (defined by the form in mod_form.php) this function 81 * will create a new instance and return the id number 82 * of the new instance. 83 * 84 * @global stdClass 85 * @global object 86 * @uses CONTEXT_MODULE 87 * @uses SCORM_TYPE_LOCAL 88 * @uses SCORM_TYPE_LOCALSYNC 89 * @uses SCORM_TYPE_EXTERNAL 90 * @param object $scorm Form data 91 * @param object $mform 92 * @return int new instance id 93 */ 94 function scorm_add_instance($scorm, $mform=null) { 95 global $CFG, $DB; 96 97 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 98 99 if (empty($scorm->timeopen)) { 100 $scorm->timeopen = 0; 101 } 102 if (empty($scorm->timeclose)) { 103 $scorm->timeclose = 0; 104 } 105 $cmid = $scorm->coursemodule; 106 $cmidnumber = $scorm->cmidnumber; 107 $courseid = $scorm->course; 108 109 $context = context_module::instance($cmid); 110 111 $scorm = scorm_option2text($scorm); 112 $scorm->width = (int)str_replace('%', '', $scorm->width); 113 $scorm->height = (int)str_replace('%', '', $scorm->height); 114 115 if (!isset($scorm->whatgrade)) { 116 $scorm->whatgrade = 0; 117 } 118 119 $id = $DB->insert_record('scorm', $scorm); 120 121 // Update course module record - from now on this instance properly exists and all function may be used. 122 $DB->set_field('course_modules', 'instance', $id, array('id' => $cmid)); 123 124 // Reload scorm instance. 125 $record = $DB->get_record('scorm', array('id' => $id)); 126 127 // Store the package and verify. 128 if ($record->scormtype === SCORM_TYPE_LOCAL) { 129 if (!empty($scorm->packagefile)) { 130 $fs = get_file_storage(); 131 $fs->delete_area_files($context->id, 'mod_scorm', 'package'); 132 file_save_draft_area_files($scorm->packagefile, $context->id, 'mod_scorm', 'package', 133 0, array('subdirs' => 0, 'maxfiles' => 1)); 134 // Get filename of zip that was uploaded. 135 $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false); 136 $file = reset($files); 137 $filename = $file->get_filename(); 138 if ($filename !== false) { 139 $record->reference = $filename; 140 } 141 } 142 143 } else if ($record->scormtype === SCORM_TYPE_LOCALSYNC) { 144 $record->reference = $scorm->packageurl; 145 } else if ($record->scormtype === SCORM_TYPE_EXTERNAL) { 146 $record->reference = $scorm->packageurl; 147 } else if ($record->scormtype === SCORM_TYPE_AICCURL) { 148 $record->reference = $scorm->packageurl; 149 $record->hidetoc = SCORM_TOC_DISABLED; // TOC is useless for direct AICCURL so disable it. 150 } else { 151 return false; 152 } 153 154 // Save reference. 155 $DB->update_record('scorm', $record); 156 157 // Extra fields required in grade related functions. 158 $record->course = $courseid; 159 $record->cmidnumber = $cmidnumber; 160 $record->cmid = $cmid; 161 162 scorm_parse($record, true); 163 164 scorm_grade_item_update($record); 165 166 return $record->id; 167 } 168 169 /** 170 * Given an object containing all the necessary data, 171 * (defined by the form in mod_form.php) this function 172 * will update an existing instance with new data. 173 * 174 * @global stdClass 175 * @global object 176 * @uses CONTEXT_MODULE 177 * @uses SCORM_TYPE_LOCAL 178 * @uses SCORM_TYPE_LOCALSYNC 179 * @uses SCORM_TYPE_EXTERNAL 180 * @param object $scorm Form data 181 * @param object $mform 182 * @return bool 183 */ 184 function scorm_update_instance($scorm, $mform=null) { 185 global $CFG, $DB; 186 187 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 188 189 if (empty($scorm->timeopen)) { 190 $scorm->timeopen = 0; 191 } 192 if (empty($scorm->timeclose)) { 193 $scorm->timeclose = 0; 194 } 195 196 $cmid = $scorm->coursemodule; 197 $cmidnumber = $scorm->cmidnumber; 198 $courseid = $scorm->course; 199 200 $scorm->id = $scorm->instance; 201 202 $context = context_module::instance($cmid); 203 204 if ($scorm->scormtype === SCORM_TYPE_LOCAL) { 205 if (!empty($scorm->packagefile)) { 206 $fs = get_file_storage(); 207 $fs->delete_area_files($context->id, 'mod_scorm', 'package'); 208 file_save_draft_area_files($scorm->packagefile, $context->id, 'mod_scorm', 'package', 209 0, array('subdirs' => 0, 'maxfiles' => 1)); 210 // Get filename of zip that was uploaded. 211 $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false); 212 $file = reset($files); 213 $filename = $file->get_filename(); 214 if ($filename !== false) { 215 $scorm->reference = $filename; 216 } 217 } 218 219 } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) { 220 $scorm->reference = $scorm->packageurl; 221 } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) { 222 $scorm->reference = $scorm->packageurl; 223 } else if ($scorm->scormtype === SCORM_TYPE_AICCURL) { 224 $scorm->reference = $scorm->packageurl; 225 $scorm->hidetoc = SCORM_TOC_DISABLED; // TOC is useless for direct AICCURL so disable it. 226 } else { 227 return false; 228 } 229 230 $scorm = scorm_option2text($scorm); 231 $scorm->width = (int)str_replace('%', '', $scorm->width); 232 $scorm->height = (int)str_replace('%', '', $scorm->height); 233 $scorm->timemodified = time(); 234 235 if (!isset($scorm->whatgrade)) { 236 $scorm->whatgrade = 0; 237 } 238 239 $DB->update_record('scorm', $scorm); 240 241 $scorm = $DB->get_record('scorm', array('id' => $scorm->id)); 242 243 // Extra fields required in grade related functions. 244 $scorm->course = $courseid; 245 $scorm->idnumber = $cmidnumber; 246 $scorm->cmid = $cmid; 247 248 scorm_parse($scorm, (bool)$scorm->updatefreq); 249 250 scorm_grade_item_update($scorm); 251 scorm_update_grades($scorm); 252 253 return true; 254 } 255 256 /** 257 * Given an ID of an instance of this module, 258 * this function will permanently delete the instance 259 * and any data that depends on it. 260 * 261 * @global stdClass 262 * @global object 263 * @param int $id Scorm instance id 264 * @return boolean 265 */ 266 function scorm_delete_instance($id) { 267 global $CFG, $DB; 268 269 if (! $scorm = $DB->get_record('scorm', array('id' => $id))) { 270 return false; 271 } 272 273 $result = true; 274 275 // Delete any dependent records. 276 if (! $DB->delete_records('scorm_scoes_track', array('scormid' => $scorm->id))) { 277 $result = false; 278 } 279 if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id))) { 280 foreach ($scoes as $sco) { 281 if (! $DB->delete_records('scorm_scoes_data', array('scoid' => $sco->id))) { 282 $result = false; 283 } 284 } 285 $DB->delete_records('scorm_scoes', array('scorm' => $scorm->id)); 286 } 287 if (! $DB->delete_records('scorm', array('id' => $scorm->id))) { 288 $result = false; 289 } 290 291 /*if (! $DB->delete_records('scorm_sequencing_controlmode', array('scormid'=>$scorm->id))) { 292 $result = false; 293 } 294 if (! $DB->delete_records('scorm_sequencing_rolluprules', array('scormid'=>$scorm->id))) { 295 $result = false; 296 } 297 if (! $DB->delete_records('scorm_sequencing_rolluprule', array('scormid'=>$scorm->id))) { 298 $result = false; 299 } 300 if (! $DB->delete_records('scorm_sequencing_rollupruleconditions', array('scormid'=>$scorm->id))) { 301 $result = false; 302 } 303 if (! $DB->delete_records('scorm_sequencing_rolluprulecondition', array('scormid'=>$scorm->id))) { 304 $result = false; 305 } 306 if (! $DB->delete_records('scorm_sequencing_rulecondition', array('scormid'=>$scorm->id))) { 307 $result = false; 308 } 309 if (! $DB->delete_records('scorm_sequencing_ruleconditions', array('scormid'=>$scorm->id))) { 310 $result = false; 311 }*/ 312 313 scorm_grade_item_delete($scorm); 314 315 return $result; 316 } 317 318 /** 319 * Return a small object with summary information about what a 320 * user has done with a given particular instance of this module 321 * Used for user activity reports. 322 * 323 * @global stdClass 324 * @param int $course Course id 325 * @param int $user User id 326 * @param int $mod 327 * @param int $scorm The scorm id 328 * @return mixed 329 */ 330 function scorm_user_outline($course, $user, $mod, $scorm) { 331 global $CFG; 332 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 333 334 require_once("$CFG->libdir/gradelib.php"); 335 $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id); 336 if (!empty($grades->items[0]->grades)) { 337 $grade = reset($grades->items[0]->grades); 338 $result = new stdClass(); 339 $result->info = get_string('grade') . ': '. $grade->str_long_grade; 340 341 // Datesubmitted == time created. dategraded == time modified or time overridden 342 // if grade was last modified by the user themselves use date graded. Otherwise use date submitted. 343 // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704. 344 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) { 345 $result->time = $grade->dategraded; 346 } else { 347 $result->time = $grade->datesubmitted; 348 } 349 350 return $result; 351 } 352 return null; 353 } 354 355 /** 356 * Print a detailed representation of what a user has done with 357 * a given particular instance of this module, for user activity reports. 358 * 359 * @global stdClass 360 * @global object 361 * @param object $course 362 * @param object $user 363 * @param object $mod 364 * @param object $scorm 365 * @return boolean 366 */ 367 function scorm_user_complete($course, $user, $mod, $scorm) { 368 global $CFG, $DB, $OUTPUT; 369 require_once("$CFG->libdir/gradelib.php"); 370 371 $liststyle = 'structlist'; 372 $now = time(); 373 $firstmodify = $now; 374 $lastmodify = 0; 375 $sometoreport = false; 376 $report = ''; 377 378 // First Access and Last Access dates for SCOs. 379 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 380 $timetracks = scorm_get_sco_runtime($scorm->id, false, $user->id); 381 $firstmodify = $timetracks->start; 382 $lastmodify = $timetracks->finish; 383 384 $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id); 385 if (!empty($grades->items[0]->grades)) { 386 $grade = reset($grades->items[0]->grades); 387 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade); 388 if ($grade->str_feedback) { 389 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback); 390 } 391 } 392 393 if ($orgs = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '. 394 $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '. 395 $DB->sql_isempty('scorm_scoes', 'organization', false, false), 396 array($scorm->id), 'sortorder, id', 'id, identifier, title')) { 397 if (count($orgs) <= 1) { 398 unset($orgs); 399 $orgs = array(); 400 $org = new stdClass(); 401 $org->identifier = ''; 402 $orgs[] = $org; 403 } 404 $report .= html_writer::start_div('mod-scorm'); 405 foreach ($orgs as $org) { 406 $conditions = array(); 407 $currentorg = ''; 408 if (!empty($org->identifier)) { 409 $report .= html_writer::div($org->title, 'orgtitle'); 410 $currentorg = $org->identifier; 411 $conditions['organization'] = $currentorg; 412 } 413 $report .= html_writer::start_tag('ul', array('id' => '0', 'class' => $liststyle)); 414 $conditions['scorm'] = $scorm->id; 415 if ($scoes = $DB->get_records('scorm_scoes', $conditions, "sortorder, id")) { 416 // Drop keys so that we can access array sequentially. 417 $scoes = array_values($scoes); 418 $level = 0; 419 $sublist = 1; 420 $parents[$level] = '/'; 421 foreach ($scoes as $pos => $sco) { 422 if ($parents[$level] != $sco->parent) { 423 if ($level > 0 && $parents[$level - 1] == $sco->parent) { 424 $report .= html_writer::end_tag('ul').html_writer::end_tag('li'); 425 $level--; 426 } else { 427 $i = $level; 428 $closelist = ''; 429 while (($i > 0) && ($parents[$level] != $sco->parent)) { 430 $closelist .= html_writer::end_tag('ul').html_writer::end_tag('li'); 431 $i--; 432 } 433 if (($i == 0) && ($sco->parent != $currentorg)) { 434 $report .= html_writer::start_tag('li'); 435 $report .= html_writer::start_tag('ul', array('id' => $sublist, 'class' => $liststyle)); 436 $level++; 437 } else { 438 $report .= $closelist; 439 $level = $i; 440 } 441 $parents[$level] = $sco->parent; 442 } 443 } 444 $report .= html_writer::start_tag('li'); 445 if (isset($scoes[$pos + 1])) { 446 $nextsco = $scoes[$pos + 1]; 447 } else { 448 $nextsco = false; 449 } 450 if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && 451 (($level == 0) || (($level > 0) && ($nextsco->parent == $sco->identifier)))) { 452 $sublist++; 453 } else { 454 $report .= $OUTPUT->spacer(array("height" => "12", "width" => "13")); 455 } 456 457 if ($sco->launch) { 458 $score = ''; 459 $totaltime = ''; 460 if ($usertrack = scorm_get_tracks($sco->id, $user->id)) { 461 if ($usertrack->status == '') { 462 $usertrack->status = 'notattempted'; 463 } 464 $strstatus = get_string($usertrack->status, 'scorm'); 465 $report .= html_writer::img($OUTPUT->pix_url($usertrack->status, 'scorm'), 466 $strstatus, array('title' => $strstatus)); 467 } else { 468 if ($sco->scormtype == 'sco') { 469 $report .= html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'), 470 get_string('notattempted', 'scorm'), 471 array('title' => get_string('notattempted', 'scorm'))); 472 } else { 473 $report .= html_writer::img($OUTPUT->pix_url('asset', 'scorm'), get_string('asset', 'scorm'), 474 array('title' => get_string('asset', 'scorm'))); 475 } 476 } 477 $report .= " $sco->title $score$totaltime".html_writer::end_tag('li'); 478 if ($usertrack !== false) { 479 $sometoreport = true; 480 $report .= html_writer::start_tag('li').html_writer::start_tag('ul', array('class' => $liststyle)); 481 foreach ($usertrack as $element => $value) { 482 if (substr($element, 0, 3) == 'cmi') { 483 $report .= html_writer::tag('li', $element.' => '.s($value)); 484 } 485 } 486 $report .= html_writer::end_tag('ul').html_writer::end_tag('li'); 487 } 488 } else { 489 $report .= " $sco->title".html_writer::end_tag('li'); 490 } 491 } 492 for ($i = 0; $i < $level; $i++) { 493 $report .= html_writer::end_tag('ul').html_writer::end_tag('li'); 494 } 495 } 496 $report .= html_writer::end_tag('ul').html_writer::empty_tag('br'); 497 } 498 $report .= html_writer::end_div(); 499 } 500 if ($sometoreport) { 501 if ($firstmodify < $now) { 502 $timeago = format_time($now - $firstmodify); 503 echo get_string('firstaccess', 'scorm').': '.userdate($firstmodify).' ('.$timeago.")".html_writer::empty_tag('br'); 504 } 505 if ($lastmodify > 0) { 506 $timeago = format_time($now - $lastmodify); 507 echo get_string('lastaccess', 'scorm').': '.userdate($lastmodify).' ('.$timeago.")".html_writer::empty_tag('br'); 508 } 509 echo get_string('report', 'scorm').":".html_writer::empty_tag('br'); 510 echo $report; 511 } else { 512 print_string('noactivity', 'scorm'); 513 } 514 515 return true; 516 } 517 518 /** 519 * Function to be run periodically according to the moodle cron 520 * This function searches for things that need to be done, such 521 * as sending out mail, toggling flags etc ... 522 * 523 * @global stdClass 524 * @global object 525 * @return boolean 526 */ 527 function scorm_cron () { 528 global $CFG, $DB; 529 530 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 531 532 $sitetimezone = core_date::get_server_timezone(); 533 // Now see if there are any scorm updates to be done. 534 535 if (!isset($CFG->scorm_updatetimelast)) { // To catch the first time. 536 set_config('scorm_updatetimelast', 0); 537 } 538 539 $timenow = time(); 540 $updatetime = usergetmidnight($timenow, $sitetimezone); 541 542 if ($CFG->scorm_updatetimelast < $updatetime and $timenow > $updatetime) { 543 544 set_config('scorm_updatetimelast', $timenow); 545 546 mtrace('Updating scorm packages which require daily update');// We are updating. 547 548 $scormsupdate = $DB->get_records('scorm', array('updatefreq' => SCORM_UPDATE_EVERYDAY)); 549 foreach ($scormsupdate as $scormupdate) { 550 scorm_parse($scormupdate, true); 551 } 552 553 // Now clear out AICC session table with old session data. 554 $cfgscorm = get_config('scorm'); 555 if (!empty($cfgscorm->allowaicchacp)) { 556 $expiretime = time() - ($cfgscorm->aicchacpkeepsessiondata * 24 * 60 * 60); 557 $DB->delete_records_select('scorm_aicc_session', 'timemodified < ?', array($expiretime)); 558 } 559 } 560 561 return true; 562 } 563 564 /** 565 * Return grade for given user or all users. 566 * 567 * @global stdClass 568 * @global object 569 * @param int $scormid id of scorm 570 * @param int $userid optional user id, 0 means all users 571 * @return array array of grades, false if none 572 */ 573 function scorm_get_user_grades($scorm, $userid=0) { 574 global $CFG, $DB; 575 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 576 577 $grades = array(); 578 if (empty($userid)) { 579 $scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid", 580 array($scorm->id), "", "userid,null"); 581 if ($scousers) { 582 foreach ($scousers as $scouser) { 583 $grades[$scouser->userid] = new stdClass(); 584 $grades[$scouser->userid]->id = $scouser->userid; 585 $grades[$scouser->userid]->userid = $scouser->userid; 586 $grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid); 587 } 588 } else { 589 return false; 590 } 591 592 } else { 593 $preattempt = $DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid", 594 array($scorm->id, $userid), "", "userid,null"); 595 if (!$preattempt) { 596 return false; // No attempt yet. 597 } 598 $grades[$userid] = new stdClass(); 599 $grades[$userid]->id = $userid; 600 $grades[$userid]->userid = $userid; 601 $grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid); 602 } 603 604 return $grades; 605 } 606 607 /** 608 * Update grades in central gradebook 609 * 610 * @category grade 611 * @param object $scorm 612 * @param int $userid specific user only, 0 mean all 613 * @param bool $nullifnone 614 */ 615 function scorm_update_grades($scorm, $userid=0, $nullifnone=true) { 616 global $CFG; 617 require_once($CFG->libdir.'/gradelib.php'); 618 require_once($CFG->libdir.'/completionlib.php'); 619 620 if ($grades = scorm_get_user_grades($scorm, $userid)) { 621 scorm_grade_item_update($scorm, $grades); 622 // Set complete. 623 scorm_set_completion($scorm, $userid, COMPLETION_COMPLETE, $grades); 624 } else if ($userid and $nullifnone) { 625 $grade = new stdClass(); 626 $grade->userid = $userid; 627 $grade->rawgrade = null; 628 scorm_grade_item_update($scorm, $grade); 629 // Set incomplete. 630 scorm_set_completion($scorm, $userid, COMPLETION_INCOMPLETE); 631 } else { 632 scorm_grade_item_update($scorm); 633 } 634 } 635 636 /** 637 * Update/create grade item for given scorm 638 * 639 * @category grade 640 * @uses GRADE_TYPE_VALUE 641 * @uses GRADE_TYPE_NONE 642 * @param object $scorm object with extra cmidnumber 643 * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook 644 * @return object grade_item 645 */ 646 function scorm_grade_item_update($scorm, $grades=null) { 647 global $CFG, $DB; 648 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 649 if (!function_exists('grade_update')) { // Workaround for buggy PHP versions. 650 require_once($CFG->libdir.'/gradelib.php'); 651 } 652 653 $params = array('itemname' => $scorm->name); 654 if (isset($scorm->cmidnumber)) { 655 $params['idnumber'] = $scorm->cmidnumber; 656 } 657 658 if ($scorm->grademethod == GRADESCOES) { 659 $maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND '. 660 $DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id)); 661 if ($maxgrade) { 662 $params['gradetype'] = GRADE_TYPE_VALUE; 663 $params['grademax'] = $maxgrade; 664 $params['grademin'] = 0; 665 } else { 666 $params['gradetype'] = GRADE_TYPE_NONE; 667 } 668 } else { 669 $params['gradetype'] = GRADE_TYPE_VALUE; 670 $params['grademax'] = $scorm->maxgrade; 671 $params['grademin'] = 0; 672 } 673 674 if ($grades === 'reset') { 675 $params['reset'] = true; 676 $grades = null; 677 } 678 679 return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params); 680 } 681 682 /** 683 * Delete grade item for given scorm 684 * 685 * @category grade 686 * @param object $scorm object 687 * @return object grade_item 688 */ 689 function scorm_grade_item_delete($scorm) { 690 global $CFG; 691 require_once($CFG->libdir.'/gradelib.php'); 692 693 return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, null, array('deleted' => 1)); 694 } 695 696 /** 697 * List the actions that correspond to a view of this module. 698 * This is used by the participation report. 699 * 700 * Note: This is not used by new logging system. Event with 701 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 702 * be considered as view action. 703 * 704 * @return array 705 */ 706 function scorm_get_view_actions() { 707 return array('pre-view', 'view', 'view all', 'report'); 708 } 709 710 /** 711 * List the actions that correspond to a post of this module. 712 * This is used by the participation report. 713 * 714 * Note: This is not used by new logging system. Event with 715 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 716 * will be considered as post action. 717 * 718 * @return array 719 */ 720 function scorm_get_post_actions() { 721 return array(); 722 } 723 724 /** 725 * @param object $scorm 726 * @return object $scorm 727 */ 728 function scorm_option2text($scorm) { 729 $scormpopoupoptions = scorm_get_popup_options_array(); 730 731 if (isset($scorm->popup)) { 732 if ($scorm->popup == 1) { 733 $optionlist = array(); 734 foreach ($scormpopoupoptions as $name => $option) { 735 if (isset($scorm->$name)) { 736 $optionlist[] = $name.'='.$scorm->$name; 737 } else { 738 $optionlist[] = $name.'=0'; 739 } 740 } 741 $scorm->options = implode(',', $optionlist); 742 } else { 743 $scorm->options = ''; 744 } 745 } else { 746 $scorm->popup = 0; 747 $scorm->options = ''; 748 } 749 return $scorm; 750 } 751 752 /** 753 * Implementation of the function for printing the form elements that control 754 * whether the course reset functionality affects the scorm. 755 * 756 * @param object $mform form passed by reference 757 */ 758 function scorm_reset_course_form_definition(&$mform) { 759 $mform->addElement('header', 'scormheader', get_string('modulenameplural', 'scorm')); 760 $mform->addElement('advcheckbox', 'reset_scorm', get_string('deleteallattempts', 'scorm')); 761 } 762 763 /** 764 * Course reset form defaults. 765 * 766 * @return array 767 */ 768 function scorm_reset_course_form_defaults($course) { 769 return array('reset_scorm' => 1); 770 } 771 772 /** 773 * Removes all grades from gradebook 774 * 775 * @global stdClass 776 * @global object 777 * @param int $courseid 778 * @param string optional type 779 */ 780 function scorm_reset_gradebook($courseid, $type='') { 781 global $CFG, $DB; 782 783 $sql = "SELECT s.*, cm.idnumber as cmidnumber, s.course as courseid 784 FROM {scorm} s, {course_modules} cm, {modules} m 785 WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id AND s.course=?"; 786 787 if ($scorms = $DB->get_records_sql($sql, array($courseid))) { 788 foreach ($scorms as $scorm) { 789 scorm_grade_item_update($scorm, 'reset'); 790 } 791 } 792 } 793 794 /** 795 * Actual implementation of the reset course functionality, delete all the 796 * scorm attempts for course $data->courseid. 797 * 798 * @global stdClass 799 * @global object 800 * @param object $data the data submitted from the reset course. 801 * @return array status array 802 */ 803 function scorm_reset_userdata($data) { 804 global $CFG, $DB; 805 806 $componentstr = get_string('modulenameplural', 'scorm'); 807 $status = array(); 808 809 if (!empty($data->reset_scorm)) { 810 $scormssql = "SELECT s.id 811 FROM {scorm} s 812 WHERE s.course=?"; 813 814 $DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid)); 815 816 // Remove all grades from gradebook. 817 if (empty($data->reset_gradebook_grades)) { 818 scorm_reset_gradebook($data->courseid); 819 } 820 821 $status[] = array('component' => $componentstr, 'item' => get_string('deleteallattempts', 'scorm'), 'error' => false); 822 } 823 824 // No dates to shift here. 825 826 return $status; 827 } 828 829 /** 830 * Returns all other caps used in module 831 * 832 * @return array 833 */ 834 function scorm_get_extra_capabilities() { 835 return array('moodle/site:accessallgroups'); 836 } 837 838 /** 839 * Lists all file areas current user may browse 840 * 841 * @param object $course 842 * @param object $cm 843 * @param object $context 844 * @return array 845 */ 846 function scorm_get_file_areas($course, $cm, $context) { 847 $areas = array(); 848 $areas['content'] = get_string('areacontent', 'scorm'); 849 $areas['package'] = get_string('areapackage', 'scorm'); 850 return $areas; 851 } 852 853 /** 854 * File browsing support for SCORM file areas 855 * 856 * @package mod_scorm 857 * @category files 858 * @param file_browser $browser file browser instance 859 * @param array $areas file areas 860 * @param stdClass $course course object 861 * @param stdClass $cm course module object 862 * @param stdClass $context context object 863 * @param string $filearea file area 864 * @param int $itemid item ID 865 * @param string $filepath file path 866 * @param string $filename file name 867 * @return file_info instance or null if not found 868 */ 869 function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { 870 global $CFG; 871 872 if (!has_capability('moodle/course:managefiles', $context)) { 873 return null; 874 } 875 876 // No writing for now! 877 878 $fs = get_file_storage(); 879 880 if ($filearea === 'content') { 881 882 $filepath = is_null($filepath) ? '/' : $filepath; 883 $filename = is_null($filename) ? '.' : $filename; 884 885 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 886 if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'content', 0, $filepath, $filename)) { 887 if ($filepath === '/' and $filename === '.') { 888 $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'content', 0); 889 } else { 890 // Not found. 891 return null; 892 } 893 } 894 require_once("$CFG->dirroot/mod/scorm/locallib.php"); 895 return new scorm_package_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, false, false); 896 897 } else if ($filearea === 'package') { 898 $filepath = is_null($filepath) ? '/' : $filepath; 899 $filename = is_null($filename) ? '.' : $filename; 900 901 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 902 if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, $filepath, $filename)) { 903 if ($filepath === '/' and $filename === '.') { 904 $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'package', 0); 905 } else { 906 // Not found. 907 return null; 908 } 909 } 910 return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false); 911 } 912 913 // Scorm_intro handled in file_browser. 914 915 return false; 916 } 917 918 /** 919 * Serves scorm content, introduction images and packages. Implements needed access control ;-) 920 * 921 * @package mod_scorm 922 * @category files 923 * @param stdClass $course course object 924 * @param stdClass $cm course module object 925 * @param stdClass $context context object 926 * @param string $filearea file area 927 * @param array $args extra arguments 928 * @param bool $forcedownload whether or not force download 929 * @param array $options additional options affecting the file serving 930 * @return bool false if file not found, does not return if found - just send the file 931 */ 932 function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { 933 global $CFG, $DB; 934 935 if ($context->contextlevel != CONTEXT_MODULE) { 936 return false; 937 } 938 939 require_login($course, true, $cm); 940 941 $canmanageactivity = has_capability('moodle/course:manageactivities', $context); 942 $lifetime = null; 943 944 // Check SCORM availability. 945 if (!$canmanageactivity) { 946 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 947 948 $scorm = $DB->get_record('scorm', array('id' => $cm->instance), 'id, timeopen, timeclose', MUST_EXIST); 949 list($available, $warnings) = scorm_get_availability_status($scorm); 950 if (!$available) { 951 return false; 952 } 953 } 954 955 if ($filearea === 'content') { 956 $revision = (int)array_shift($args); // Prevents caching problems - ignored here. 957 $relativepath = implode('/', $args); 958 $fullpath = "/$context->id/mod_scorm/content/0/$relativepath"; 959 // TODO: add any other access restrictions here if needed! 960 961 } else if ($filearea === 'package') { 962 // Check if the global setting for disabling package downloads is enabled. 963 $protectpackagedownloads = get_config('scorm', 'protectpackagedownloads'); 964 if ($protectpackagedownloads and !$canmanageactivity) { 965 return false; 966 } 967 $revision = (int)array_shift($args); // Prevents caching problems - ignored here. 968 $relativepath = implode('/', $args); 969 $fullpath = "/$context->id/mod_scorm/package/0/$relativepath"; 970 $lifetime = 0; // No caching here. 971 972 } else if ($filearea === 'imsmanifest') { // This isn't a real filearea, it's a url parameter for this type of package. 973 $revision = (int)array_shift($args); // Prevents caching problems - ignored here. 974 $relativepath = implode('/', $args); 975 976 // Get imsmanifest file. 977 $fs = get_file_storage(); 978 $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false); 979 $file = reset($files); 980 981 // Check that the package file is an imsmanifest.xml file - if not then this method is not allowed. 982 $packagefilename = $file->get_filename(); 983 if (strtolower($packagefilename) !== 'imsmanifest.xml') { 984 return false; 985 } 986 987 $file->send_relative_file($relativepath); 988 } else { 989 return false; 990 } 991 992 $fs = get_file_storage(); 993 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 994 if ($filearea === 'content') { // Return file not found straight away to improve performance. 995 send_header_404(); 996 die; 997 } 998 return false; 999 } 1000 1001 // Finally send the file. 1002 send_stored_file($file, $lifetime, 0, false, $options); 1003 } 1004 1005 /** 1006 * @uses FEATURE_GROUPS 1007 * @uses FEATURE_GROUPINGS 1008 * @uses FEATURE_MOD_INTRO 1009 * @uses FEATURE_COMPLETION_TRACKS_VIEWS 1010 * @uses FEATURE_COMPLETION_HAS_RULES 1011 * @uses FEATURE_GRADE_HAS_GRADE 1012 * @uses FEATURE_GRADE_OUTCOMES 1013 * @param string $feature FEATURE_xx constant for requested feature 1014 * @return mixed True if module supports feature, false if not, null if doesn't know 1015 */ 1016 function scorm_supports($feature) { 1017 switch($feature) { 1018 case FEATURE_GROUPS: return true; 1019 case FEATURE_GROUPINGS: return true; 1020 case FEATURE_MOD_INTRO: return true; 1021 case FEATURE_COMPLETION_TRACKS_VIEWS: return true; 1022 case FEATURE_COMPLETION_HAS_RULES: return true; 1023 case FEATURE_GRADE_HAS_GRADE: return true; 1024 case FEATURE_GRADE_OUTCOMES: return true; 1025 case FEATURE_BACKUP_MOODLE2: return true; 1026 case FEATURE_SHOW_DESCRIPTION: return true; 1027 1028 default: return null; 1029 } 1030 } 1031 1032 /** 1033 * Get the filename for a temp log file 1034 * 1035 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1036 * @param integer $scoid - scoid of object this log entry is for 1037 * @return string The filename as an absolute path 1038 */ 1039 function scorm_debug_log_filename($type, $scoid) { 1040 global $CFG, $USER; 1041 1042 $logpath = $CFG->tempdir.'/scormlogs'; 1043 $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log'; 1044 return $logfile; 1045 } 1046 1047 /** 1048 * writes log output to a temp log file 1049 * 1050 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1051 * @param string $text - text to be written to file. 1052 * @param integer $scoid - scoid of object this log entry is for. 1053 */ 1054 function scorm_debug_log_write($type, $text, $scoid) { 1055 global $CFG; 1056 1057 $debugenablelog = get_config('scorm', 'allowapidebug'); 1058 if (!$debugenablelog || empty($text)) { 1059 return; 1060 } 1061 if (make_temp_directory('scormlogs/')) { 1062 $logfile = scorm_debug_log_filename($type, $scoid); 1063 @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND); 1064 @chmod($logfile, $CFG->filepermissions); 1065 } 1066 } 1067 1068 /** 1069 * Remove debug log file 1070 * 1071 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1072 * @param integer $scoid - scoid of object this log entry is for 1073 * @return boolean True if the file is successfully deleted, false otherwise 1074 */ 1075 function scorm_debug_log_remove($type, $scoid) { 1076 1077 $debugenablelog = get_config('scorm', 'allowapidebug'); 1078 $logfile = scorm_debug_log_filename($type, $scoid); 1079 if (!$debugenablelog || !file_exists($logfile)) { 1080 return false; 1081 } 1082 1083 return @unlink($logfile); 1084 } 1085 1086 /** 1087 * writes overview info for course_overview block - displays upcoming scorm objects that have a due date 1088 * 1089 * @param object $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1090 * @param array $htmlarray 1091 * @return mixed 1092 */ 1093 function scorm_print_overview($courses, &$htmlarray) { 1094 global $USER, $CFG; 1095 1096 if (empty($courses) || !is_array($courses) || count($courses) == 0) { 1097 return array(); 1098 } 1099 1100 if (!$scorms = get_all_instances_in_courses('scorm', $courses)) { 1101 return; 1102 } 1103 1104 $strscorm = get_string('modulename', 'scorm'); 1105 $strduedate = get_string('duedate', 'scorm'); 1106 1107 foreach ($scorms as $scorm) { 1108 $time = time(); 1109 $showattemptstatus = false; 1110 if ($scorm->timeopen) { 1111 $isopen = ($scorm->timeopen <= $time && $time <= $scorm->timeclose); 1112 } 1113 if ($scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ALL || 1114 $scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_MY) { 1115 $showattemptstatus = true; 1116 } 1117 if ($showattemptstatus || !empty($isopen) || !empty($scorm->timeclose)) { 1118 $str = html_writer::start_div('scorm overview').html_writer::div($strscorm. ': '. 1119 html_writer::link($CFG->wwwroot.'/mod/scorm/view.php?id='.$scorm->coursemodule, $scorm->name, 1120 array('title' => $strscorm, 'class' => $scorm->visible ? '' : 'dimmed')), 'name'); 1121 if ($scorm->timeclose) { 1122 $str .= html_writer::div($strduedate.': '.userdate($scorm->timeclose), 'info'); 1123 } 1124 if ($showattemptstatus) { 1125 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 1126 $str .= html_writer::div(scorm_get_attempt_status($USER, $scorm), 'details'); 1127 } 1128 $str .= html_writer::end_div(); 1129 if (empty($htmlarray[$scorm->course]['scorm'])) { 1130 $htmlarray[$scorm->course]['scorm'] = $str; 1131 } else { 1132 $htmlarray[$scorm->course]['scorm'] .= $str; 1133 } 1134 } 1135 } 1136 } 1137 1138 /** 1139 * Return a list of page types 1140 * @param string $pagetype current page type 1141 * @param stdClass $parentcontext Block's parent context 1142 * @param stdClass $currentcontext Current context of block 1143 */ 1144 function scorm_page_type_list($pagetype, $parentcontext, $currentcontext) { 1145 $modulepagetype = array('mod-scorm-*' => get_string('page-mod-scorm-x', 'scorm')); 1146 return $modulepagetype; 1147 } 1148 1149 /** 1150 * Returns the SCORM version used. 1151 * @param string $scormversion comes from $scorm->version 1152 * @param string $version one of the defined vars SCORM_12, SCORM_13, SCORM_AICC (or empty) 1153 * @return Scorm version. 1154 */ 1155 function scorm_version_check($scormversion, $version='') { 1156 $scormversion = trim(strtolower($scormversion)); 1157 if (empty($version) || $version == SCORM_12) { 1158 if ($scormversion == 'scorm_12' || $scormversion == 'scorm_1.2') { 1159 return SCORM_12; 1160 } 1161 if (!empty($version)) { 1162 return false; 1163 } 1164 } 1165 if (empty($version) || $version == SCORM_13) { 1166 if ($scormversion == 'scorm_13' || $scormversion == 'scorm_1.3') { 1167 return SCORM_13; 1168 } 1169 if (!empty($version)) { 1170 return false; 1171 } 1172 } 1173 if (empty($version) || $version == SCORM_AICC) { 1174 if (strpos($scormversion, 'aicc')) { 1175 return SCORM_AICC; 1176 } 1177 if (!empty($version)) { 1178 return false; 1179 } 1180 } 1181 return false; 1182 } 1183 1184 /** 1185 * Obtains the automatic completion state for this scorm based on any conditions 1186 * in scorm settings. 1187 * 1188 * @param object $course Course 1189 * @param object $cm Course-module 1190 * @param int $userid User ID 1191 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) 1192 * @return bool True if completed, false if not. (If no conditions, then return 1193 * value depends on comparison type) 1194 */ 1195 function scorm_get_completion_state($course, $cm, $userid, $type) { 1196 global $DB; 1197 1198 $result = $type; 1199 1200 // Get scorm. 1201 if (!$scorm = $DB->get_record('scorm', array('id' => $cm->instance))) { 1202 print_error('cannotfindscorm'); 1203 } 1204 // Only check for existence of tracks and return false if completionstatusrequired or completionscorerequired 1205 // this means that if only view is required we don't end up with a false state. 1206 if ($scorm->completionstatusrequired !== null || 1207 $scorm->completionscorerequired !== null) { 1208 // Get user's tracks data. 1209 $tracks = $DB->get_records_sql( 1210 " 1211 SELECT 1212 id, 1213 element, 1214 value 1215 FROM 1216 {scorm_scoes_track} 1217 WHERE 1218 scormid = ? 1219 AND userid = ? 1220 AND element IN 1221 ( 1222 'cmi.core.lesson_status', 1223 'cmi.completion_status', 1224 'cmi.success_status', 1225 'cmi.core.score.raw', 1226 'cmi.score.raw' 1227 ) 1228 ", 1229 array($scorm->id, $userid) 1230 ); 1231 1232 if (!$tracks) { 1233 return completion_info::aggregate_completion_states($type, $result, false); 1234 } 1235 } 1236 1237 // Check for status. 1238 if ($scorm->completionstatusrequired !== null) { 1239 1240 // Get status. 1241 $statuses = array_flip(scorm_status_options()); 1242 $nstatus = 0; 1243 1244 foreach ($tracks as $track) { 1245 if (!in_array($track->element, array('cmi.core.lesson_status', 'cmi.completion_status', 'cmi.success_status'))) { 1246 continue; 1247 } 1248 1249 if (array_key_exists($track->value, $statuses)) { 1250 $nstatus |= $statuses[$track->value]; 1251 } 1252 } 1253 1254 if ($scorm->completionstatusrequired & $nstatus) { 1255 return completion_info::aggregate_completion_states($type, $result, true); 1256 } else { 1257 return completion_info::aggregate_completion_states($type, $result, false); 1258 } 1259 1260 } 1261 1262 // Check for score. 1263 if ($scorm->completionscorerequired !== null) { 1264 $maxscore = -1; 1265 1266 foreach ($tracks as $track) { 1267 if (!in_array($track->element, array('cmi.core.score.raw', 'cmi.score.raw'))) { 1268 continue; 1269 } 1270 1271 if (strlen($track->value) && floatval($track->value) >= $maxscore) { 1272 $maxscore = floatval($track->value); 1273 } 1274 } 1275 1276 if ($scorm->completionscorerequired <= $maxscore) { 1277 return completion_info::aggregate_completion_states($type, $result, true); 1278 } else { 1279 return completion_info::aggregate_completion_states($type, $result, false); 1280 } 1281 } 1282 1283 return $result; 1284 } 1285 1286 /** 1287 * Register the ability to handle drag and drop file uploads 1288 * @return array containing details of the files / types the mod can handle 1289 */ 1290 function scorm_dndupload_register() { 1291 return array('files' => array( 1292 array('extension' => 'zip', 'message' => get_string('dnduploadscorm', 'scorm')) 1293 )); 1294 } 1295 1296 /** 1297 * Handle a file that has been uploaded 1298 * @param object $uploadinfo details of the file / content that has been uploaded 1299 * @return int instance id of the newly created mod 1300 */ 1301 function scorm_dndupload_handle($uploadinfo) { 1302 1303 $context = context_module::instance($uploadinfo->coursemodule); 1304 file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_scorm', 'package', 0); 1305 $fs = get_file_storage(); 1306 $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, 'sortorder, itemid, filepath, filename', false); 1307 $file = reset($files); 1308 1309 // Validate the file, make sure it's a valid SCORM package! 1310 $errors = scorm_validate_package($file); 1311 if (!empty($errors)) { 1312 return false; 1313 } 1314 // Create a default scorm object to pass to scorm_add_instance()! 1315 $scorm = get_config('scorm'); 1316 $scorm->course = $uploadinfo->course->id; 1317 $scorm->coursemodule = $uploadinfo->coursemodule; 1318 $scorm->cmidnumber = ''; 1319 $scorm->name = $uploadinfo->displayname; 1320 $scorm->scormtype = SCORM_TYPE_LOCAL; 1321 $scorm->reference = $file->get_filename(); 1322 $scorm->intro = ''; 1323 $scorm->width = $scorm->framewidth; 1324 $scorm->height = $scorm->frameheight; 1325 1326 return scorm_add_instance($scorm, null); 1327 } 1328 1329 /** 1330 * Sets activity completion state 1331 * 1332 * @param object $scorm object 1333 * @param int $userid User ID 1334 * @param int $completionstate Completion state 1335 * @param array $grades grades array of users with grades - used when $userid = 0 1336 */ 1337 function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COMPLETE, $grades = array()) { 1338 $course = new stdClass(); 1339 $course->id = $scorm->course; 1340 $completion = new completion_info($course); 1341 1342 // Check if completion is enabled site-wide, or for the course. 1343 if (!$completion->is_enabled()) { 1344 return; 1345 } 1346 1347 $cm = get_coursemodule_from_instance('scorm', $scorm->id, $scorm->course); 1348 if (empty($cm) || !$completion->is_enabled($cm)) { 1349 return; 1350 } 1351 1352 if (empty($userid)) { // We need to get all the relevant users from $grades param. 1353 foreach ($grades as $grade) { 1354 $completion->update_state($cm, $completionstate, $grade->userid); 1355 } 1356 } else { 1357 $completion->update_state($cm, $completionstate, $userid); 1358 } 1359 } 1360 1361 /** 1362 * Check that a Zip file contains a valid SCORM package 1363 * 1364 * @param $file stored_file a Zip file. 1365 * @return array empty if no issue is found. Array of error message otherwise 1366 */ 1367 function scorm_validate_package($file) { 1368 $packer = get_file_packer('application/zip'); 1369 $errors = array(); 1370 if ($file->is_external_file()) { // Get zip file so we can check it is correct. 1371 $file->import_external_file_contents(); 1372 } 1373 $filelist = $file->list_files($packer); 1374 1375 if (!is_array($filelist)) { 1376 $errors['packagefile'] = get_string('badarchive', 'scorm'); 1377 } else { 1378 $aiccfound = false; 1379 $badmanifestpresent = false; 1380 foreach ($filelist as $info) { 1381 if ($info->pathname == 'imsmanifest.xml') { 1382 return array(); 1383 } else if (strpos($info->pathname, 'imsmanifest.xml') !== false) { 1384 // This package has an imsmanifest file inside a folder of the package. 1385 $badmanifestpresent = true; 1386 } 1387 if (preg_match('/\.cst$/', $info->pathname)) { 1388 return array(); 1389 } 1390 } 1391 if (!$aiccfound) { 1392 if ($badmanifestpresent) { 1393 $errors['packagefile'] = get_string('badimsmanifestlocation', 'scorm'); 1394 } else { 1395 $errors['packagefile'] = get_string('nomanifest', 'scorm'); 1396 } 1397 } 1398 } 1399 return $errors; 1400 } 1401 1402 /** 1403 * Check and set the correct mode and attempt when entering a SCORM package. 1404 * 1405 * @param object $scorm object 1406 * @param string $newattempt should a new attempt be generated here. 1407 * @param int $attempt the attempt number this is for. 1408 * @param int $userid the userid of the user. 1409 * @param string $mode the current mode that has been selected. 1410 */ 1411 function scorm_check_mode($scorm, &$newattempt, &$attempt, $userid, &$mode) { 1412 global $DB; 1413 1414 if (($mode == 'browse')) { 1415 if ($scorm->hidebrowse == 1) { 1416 // Prevent Browse mode if hidebrowse is set. 1417 $mode = 'normal'; 1418 } else { 1419 // We don't need to check attempts as browse mode is set. 1420 return; 1421 } 1422 } 1423 // Check if the scorm module is incomplete (used to validate user request to start a new attempt). 1424 $incomplete = true; 1425 $tracks = $DB->get_recordset('scorm_scoes_track', array('scormid' => $scorm->id, 'userid' => $userid, 1426 'attempt' => $attempt, 'element' => 'cmi.core.lesson_status')); 1427 foreach ($tracks as $track) { 1428 if (($track->value == 'completed') || ($track->value == 'passed') || ($track->value == 'failed')) { 1429 $incomplete = false; 1430 } else { 1431 $incomplete = true; 1432 break; // Found an incomplete sco, so the result as a whole is incomplete. 1433 } 1434 } 1435 $tracks->close(); 1436 1437 // Validate user request to start a new attempt. 1438 if ($incomplete === true) { 1439 // The option to start a new attempt should never have been presented. Force false. 1440 $newattempt = 'off'; 1441 } else if (!empty($scorm->forcenewattempt)) { 1442 // A new attempt should be forced for already completed attempts. 1443 $newattempt = 'on'; 1444 } 1445 1446 if (($newattempt == 'on') && (($attempt < $scorm->maxattempt) || ($scorm->maxattempt == 0))) { 1447 $attempt++; 1448 $mode = 'normal'; 1449 } else { // Check if review mode should be set. 1450 if ($incomplete === true) { 1451 $mode = 'normal'; 1452 } else { 1453 $mode = 'review'; 1454 } 1455 } 1456 } 1457 1458 /** 1459 * Trigger the course_module_viewed event. 1460 * 1461 * @param stdClass $scorm scorm object 1462 * @param stdClass $course course object 1463 * @param stdClass $cm course module object 1464 * @param stdClass $context context object 1465 * @since Moodle 3.0 1466 */ 1467 function scorm_view($scorm, $course, $cm, $context) { 1468 1469 // Trigger course_module_viewed event. 1470 $params = array( 1471 'context' => $context, 1472 'objectid' => $scorm->id 1473 ); 1474 1475 $event = \mod_scorm\event\course_module_viewed::create($params); 1476 $event->add_record_snapshot('course_modules', $cm); 1477 $event->add_record_snapshot('course', $course); 1478 $event->add_record_snapshot('scorm', $scorm); 1479 $event->trigger(); 1480 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |