[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 = $CFG->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; 934 935 if ($context->contextlevel != CONTEXT_MODULE) { 936 return false; 937 } 938 939 require_login($course, true, $cm); 940 941 $lifetime = null; 942 943 if ($filearea === 'content') { 944 $revision = (int)array_shift($args); // Prevents caching problems - ignored here. 945 $relativepath = implode('/', $args); 946 $fullpath = "/$context->id/mod_scorm/content/0/$relativepath"; 947 // TODO: add any other access restrictions here if needed! 948 949 } else if ($filearea === 'package') { 950 if (!has_capability('moodle/course:manageactivities', $context)) { 951 return false; 952 } 953 $relativepath = implode('/', $args); 954 $fullpath = "/$context->id/mod_scorm/package/0/$relativepath"; 955 $lifetime = 0; // No caching here. 956 957 } else if ($filearea === 'imsmanifest') { // This isn't a real filearea, it's a url parameter for this type of package. 958 $revision = (int)array_shift($args); // Prevents caching problems - ignored here. 959 $relativepath = implode('/', $args); 960 961 // Get imsmanifest file. 962 $fs = get_file_storage(); 963 $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false); 964 $file = reset($files); 965 966 // Check that the package file is an imsmanifest.xml file - if not then this method is not allowed. 967 $packagefilename = $file->get_filename(); 968 if (strtolower($packagefilename) !== 'imsmanifest.xml') { 969 return false; 970 } 971 972 $file->send_relative_file($relativepath); 973 } else { 974 return false; 975 } 976 977 $fs = get_file_storage(); 978 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 979 if ($filearea === 'content') { // Return file not found straight away to improve performance. 980 send_header_404(); 981 die; 982 } 983 return false; 984 } 985 986 // Finally send the file. 987 send_stored_file($file, $lifetime, 0, false, $options); 988 } 989 990 /** 991 * @uses FEATURE_GROUPS 992 * @uses FEATURE_GROUPINGS 993 * @uses FEATURE_MOD_INTRO 994 * @uses FEATURE_COMPLETION_TRACKS_VIEWS 995 * @uses FEATURE_COMPLETION_HAS_RULES 996 * @uses FEATURE_GRADE_HAS_GRADE 997 * @uses FEATURE_GRADE_OUTCOMES 998 * @param string $feature FEATURE_xx constant for requested feature 999 * @return mixed True if module supports feature, false if not, null if doesn't know 1000 */ 1001 function scorm_supports($feature) { 1002 switch($feature) { 1003 case FEATURE_GROUPS: return false; 1004 case FEATURE_GROUPINGS: return false; 1005 case FEATURE_MOD_INTRO: return true; 1006 case FEATURE_COMPLETION_TRACKS_VIEWS: return true; 1007 case FEATURE_COMPLETION_HAS_RULES: return true; 1008 case FEATURE_GRADE_HAS_GRADE: return true; 1009 case FEATURE_GRADE_OUTCOMES: return true; 1010 case FEATURE_BACKUP_MOODLE2: return true; 1011 case FEATURE_SHOW_DESCRIPTION: return true; 1012 1013 default: return null; 1014 } 1015 } 1016 1017 /** 1018 * Get the filename for a temp log file 1019 * 1020 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1021 * @param integer $scoid - scoid of object this log entry is for 1022 * @return string The filename as an absolute path 1023 */ 1024 function scorm_debug_log_filename($type, $scoid) { 1025 global $CFG, $USER; 1026 1027 $logpath = $CFG->tempdir.'/scormlogs'; 1028 $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log'; 1029 return $logfile; 1030 } 1031 1032 /** 1033 * writes log output to a temp log file 1034 * 1035 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1036 * @param string $text - text to be written to file. 1037 * @param integer $scoid - scoid of object this log entry is for. 1038 */ 1039 function scorm_debug_log_write($type, $text, $scoid) { 1040 global $CFG; 1041 1042 $debugenablelog = get_config('scorm', 'allowapidebug'); 1043 if (!$debugenablelog || empty($text)) { 1044 return; 1045 } 1046 if (make_temp_directory('scormlogs/')) { 1047 $logfile = scorm_debug_log_filename($type, $scoid); 1048 @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND); 1049 @chmod($logfile, $CFG->filepermissions); 1050 } 1051 } 1052 1053 /** 1054 * Remove debug log file 1055 * 1056 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1057 * @param integer $scoid - scoid of object this log entry is for 1058 * @return boolean True if the file is successfully deleted, false otherwise 1059 */ 1060 function scorm_debug_log_remove($type, $scoid) { 1061 1062 $debugenablelog = get_config('scorm', 'allowapidebug'); 1063 $logfile = scorm_debug_log_filename($type, $scoid); 1064 if (!$debugenablelog || !file_exists($logfile)) { 1065 return false; 1066 } 1067 1068 return @unlink($logfile); 1069 } 1070 1071 /** 1072 * writes overview info for course_overview block - displays upcoming scorm objects that have a due date 1073 * 1074 * @param object $type - type of log(aicc,scorm12,scorm13) used as prefix for filename 1075 * @param array $htmlarray 1076 * @return mixed 1077 */ 1078 function scorm_print_overview($courses, &$htmlarray) { 1079 global $USER, $CFG; 1080 1081 if (empty($courses) || !is_array($courses) || count($courses) == 0) { 1082 return array(); 1083 } 1084 1085 if (!$scorms = get_all_instances_in_courses('scorm', $courses)) { 1086 return; 1087 } 1088 1089 $strscorm = get_string('modulename', 'scorm'); 1090 $strduedate = get_string('duedate', 'scorm'); 1091 1092 foreach ($scorms as $scorm) { 1093 $time = time(); 1094 $showattemptstatus = false; 1095 if ($scorm->timeopen) { 1096 $isopen = ($scorm->timeopen <= $time && $time <= $scorm->timeclose); 1097 } 1098 if ($scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ALL || 1099 $scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_MY) { 1100 $showattemptstatus = true; 1101 } 1102 if ($showattemptstatus || !empty($isopen) || !empty($scorm->timeclose)) { 1103 $str = html_writer::start_div('scorm overview').html_writer::div($strscorm. ': '. 1104 html_writer::link($CFG->wwwroot.'/mod/scorm/view.php?id='.$scorm->coursemodule, $scorm->name, 1105 array('title' => $strscorm, 'class' => $scorm->visible ? '' : 'dimmed')), 'name'); 1106 if ($scorm->timeclose) { 1107 $str .= html_writer::div($strduedate.': '.userdate($scorm->timeclose), 'info'); 1108 } 1109 if ($showattemptstatus) { 1110 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 1111 $str .= html_writer::div(scorm_get_attempt_status($USER, $scorm), 'details'); 1112 } 1113 $str .= html_writer::end_div(); 1114 if (empty($htmlarray[$scorm->course]['scorm'])) { 1115 $htmlarray[$scorm->course]['scorm'] = $str; 1116 } else { 1117 $htmlarray[$scorm->course]['scorm'] .= $str; 1118 } 1119 } 1120 } 1121 } 1122 1123 /** 1124 * Return a list of page types 1125 * @param string $pagetype current page type 1126 * @param stdClass $parentcontext Block's parent context 1127 * @param stdClass $currentcontext Current context of block 1128 */ 1129 function scorm_page_type_list($pagetype, $parentcontext, $currentcontext) { 1130 $modulepagetype = array('mod-scorm-*' => get_string('page-mod-scorm-x', 'scorm')); 1131 return $modulepagetype; 1132 } 1133 1134 /** 1135 * Returns the SCORM version used. 1136 * @param string $scormversion comes from $scorm->version 1137 * @param string $version one of the defined vars SCORM_12, SCORM_13, SCORM_AICC (or empty) 1138 * @return Scorm version. 1139 */ 1140 function scorm_version_check($scormversion, $version='') { 1141 $scormversion = trim(strtolower($scormversion)); 1142 if (empty($version) || $version == SCORM_12) { 1143 if ($scormversion == 'scorm_12' || $scormversion == 'scorm_1.2') { 1144 return SCORM_12; 1145 } 1146 if (!empty($version)) { 1147 return false; 1148 } 1149 } 1150 if (empty($version) || $version == SCORM_13) { 1151 if ($scormversion == 'scorm_13' || $scormversion == 'scorm_1.3') { 1152 return SCORM_13; 1153 } 1154 if (!empty($version)) { 1155 return false; 1156 } 1157 } 1158 if (empty($version) || $version == SCORM_AICC) { 1159 if (strpos($scormversion, 'aicc')) { 1160 return SCORM_AICC; 1161 } 1162 if (!empty($version)) { 1163 return false; 1164 } 1165 } 1166 return false; 1167 } 1168 1169 /** 1170 * Obtains the automatic completion state for this scorm based on any conditions 1171 * in scorm settings. 1172 * 1173 * @param object $course Course 1174 * @param object $cm Course-module 1175 * @param int $userid User ID 1176 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) 1177 * @return bool True if completed, false if not. (If no conditions, then return 1178 * value depends on comparison type) 1179 */ 1180 function scorm_get_completion_state($course, $cm, $userid, $type) { 1181 global $DB; 1182 1183 $result = $type; 1184 1185 // Get scorm. 1186 if (!$scorm = $DB->get_record('scorm', array('id' => $cm->instance))) { 1187 print_error('cannotfindscorm'); 1188 } 1189 // Only check for existence of tracks and return false if completionstatusrequired or completionscorerequired 1190 // this means that if only view is required we don't end up with a false state. 1191 if ($scorm->completionstatusrequired !== null || 1192 $scorm->completionscorerequired !== null) { 1193 // Get user's tracks data. 1194 $tracks = $DB->get_records_sql( 1195 " 1196 SELECT 1197 id, 1198 element, 1199 value 1200 FROM 1201 {scorm_scoes_track} 1202 WHERE 1203 scormid = ? 1204 AND userid = ? 1205 AND element IN 1206 ( 1207 'cmi.core.lesson_status', 1208 'cmi.completion_status', 1209 'cmi.success_status', 1210 'cmi.core.score.raw', 1211 'cmi.score.raw' 1212 ) 1213 ", 1214 array($scorm->id, $userid) 1215 ); 1216 1217 if (!$tracks) { 1218 return completion_info::aggregate_completion_states($type, $result, false); 1219 } 1220 } 1221 1222 // Check for status. 1223 if ($scorm->completionstatusrequired !== null) { 1224 1225 // Get status. 1226 $statuses = array_flip(scorm_status_options()); 1227 $nstatus = 0; 1228 1229 foreach ($tracks as $track) { 1230 if (!in_array($track->element, array('cmi.core.lesson_status', 'cmi.completion_status', 'cmi.success_status'))) { 1231 continue; 1232 } 1233 1234 if (array_key_exists($track->value, $statuses)) { 1235 $nstatus |= $statuses[$track->value]; 1236 } 1237 } 1238 1239 if ($scorm->completionstatusrequired & $nstatus) { 1240 return completion_info::aggregate_completion_states($type, $result, true); 1241 } else { 1242 return completion_info::aggregate_completion_states($type, $result, false); 1243 } 1244 1245 } 1246 1247 // Check for score. 1248 if ($scorm->completionscorerequired !== null) { 1249 $maxscore = -1; 1250 1251 foreach ($tracks as $track) { 1252 if (!in_array($track->element, array('cmi.core.score.raw', 'cmi.score.raw'))) { 1253 continue; 1254 } 1255 1256 if (strlen($track->value) && floatval($track->value) >= $maxscore) { 1257 $maxscore = floatval($track->value); 1258 } 1259 } 1260 1261 if ($scorm->completionscorerequired <= $maxscore) { 1262 return completion_info::aggregate_completion_states($type, $result, true); 1263 } else { 1264 return completion_info::aggregate_completion_states($type, $result, false); 1265 } 1266 } 1267 1268 return $result; 1269 } 1270 1271 /** 1272 * Register the ability to handle drag and drop file uploads 1273 * @return array containing details of the files / types the mod can handle 1274 */ 1275 function scorm_dndupload_register() { 1276 return array('files' => array( 1277 array('extension' => 'zip', 'message' => get_string('dnduploadscorm', 'scorm')) 1278 )); 1279 } 1280 1281 /** 1282 * Handle a file that has been uploaded 1283 * @param object $uploadinfo details of the file / content that has been uploaded 1284 * @return int instance id of the newly created mod 1285 */ 1286 function scorm_dndupload_handle($uploadinfo) { 1287 1288 $context = context_module::instance($uploadinfo->coursemodule); 1289 file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_scorm', 'package', 0); 1290 $fs = get_file_storage(); 1291 $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, 'sortorder, itemid, filepath, filename', false); 1292 $file = reset($files); 1293 1294 // Validate the file, make sure it's a valid SCORM package! 1295 $errors = scorm_validate_package($file); 1296 if (!empty($errors)) { 1297 return false; 1298 } 1299 // Create a default scorm object to pass to scorm_add_instance()! 1300 $scorm = get_config('scorm'); 1301 $scorm->course = $uploadinfo->course->id; 1302 $scorm->coursemodule = $uploadinfo->coursemodule; 1303 $scorm->cmidnumber = ''; 1304 $scorm->name = $uploadinfo->displayname; 1305 $scorm->scormtype = SCORM_TYPE_LOCAL; 1306 $scorm->reference = $file->get_filename(); 1307 $scorm->intro = ''; 1308 $scorm->width = $scorm->framewidth; 1309 $scorm->height = $scorm->frameheight; 1310 1311 return scorm_add_instance($scorm, null); 1312 } 1313 1314 /** 1315 * Sets activity completion state 1316 * 1317 * @param object $scorm object 1318 * @param int $userid User ID 1319 * @param int $completionstate Completion state 1320 * @param array $grades grades array of users with grades - used when $userid = 0 1321 */ 1322 function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COMPLETE, $grades = array()) { 1323 $course = new stdClass(); 1324 $course->id = $scorm->course; 1325 $completion = new completion_info($course); 1326 1327 // Check if completion is enabled site-wide, or for the course. 1328 if (!$completion->is_enabled()) { 1329 return; 1330 } 1331 1332 $cm = get_coursemodule_from_instance('scorm', $scorm->id, $scorm->course); 1333 if (empty($cm) || !$completion->is_enabled($cm)) { 1334 return; 1335 } 1336 1337 if (empty($userid)) { // We need to get all the relevant users from $grades param. 1338 foreach ($grades as $grade) { 1339 $completion->update_state($cm, $completionstate, $grade->userid); 1340 } 1341 } else { 1342 $completion->update_state($cm, $completionstate, $userid); 1343 } 1344 } 1345 1346 /** 1347 * Check that a Zip file contains a valid SCORM package 1348 * 1349 * @param $file stored_file a Zip file. 1350 * @return array empty if no issue is found. Array of error message otherwise 1351 */ 1352 function scorm_validate_package($file) { 1353 $packer = get_file_packer('application/zip'); 1354 $errors = array(); 1355 if ($file->is_external_file()) { // Get zip file so we can check it is correct. 1356 $file->import_external_file_contents(); 1357 } 1358 $filelist = $file->list_files($packer); 1359 1360 if (!is_array($filelist)) { 1361 $errors['packagefile'] = get_string('badarchive', 'scorm'); 1362 } else { 1363 $aiccfound = false; 1364 $badmanifestpresent = false; 1365 foreach ($filelist as $info) { 1366 if ($info->pathname == 'imsmanifest.xml') { 1367 return array(); 1368 } else if (strpos($info->pathname, 'imsmanifest.xml') !== false) { 1369 // This package has an imsmanifest file inside a folder of the package. 1370 $badmanifestpresent = true; 1371 } 1372 if (preg_match('/\.cst$/', $info->pathname)) { 1373 return array(); 1374 } 1375 } 1376 if (!$aiccfound) { 1377 if ($badmanifestpresent) { 1378 $errors['packagefile'] = get_string('badimsmanifestlocation', 'scorm'); 1379 } else { 1380 $errors['packagefile'] = get_string('nomanifest', 'scorm'); 1381 } 1382 } 1383 } 1384 return $errors; 1385 } 1386 1387 /** 1388 * Check and set the correct mode and attempt when entering a SCORM package. 1389 * 1390 * @param object $scorm object 1391 * @param string $newattempt should a new attempt be generated here. 1392 * @param int $attempt the attempt number this is for. 1393 * @param int $userid the userid of the user. 1394 * @param string $mode the current mode that has been selected. 1395 */ 1396 function scorm_check_mode($scorm, &$newattempt, &$attempt, $userid, &$mode) { 1397 global $DB; 1398 1399 if (($mode == 'browse')) { 1400 if ($scorm->hidebrowse == 1) { 1401 // Prevent Browse mode if hidebrowse is set. 1402 $mode = 'normal'; 1403 } else { 1404 // We don't need to check attempts as browse mode is set. 1405 return; 1406 } 1407 } 1408 // Check if the scorm module is incomplete (used to validate user request to start a new attempt). 1409 $incomplete = true; 1410 $tracks = $DB->get_recordset('scorm_scoes_track', array('scormid' => $scorm->id, 'userid' => $userid, 1411 'attempt' => $attempt, 'element' => 'cmi.core.lesson_status')); 1412 foreach ($tracks as $track) { 1413 if (($track->value == 'completed') || ($track->value == 'passed') || ($track->value == 'failed')) { 1414 $incomplete = false; 1415 } else { 1416 $incomplete = true; 1417 break; // Found an incomplete sco, so the result as a whole is incomplete. 1418 } 1419 } 1420 $tracks->close(); 1421 1422 // Validate user request to start a new attempt. 1423 if ($incomplete === true) { 1424 // The option to start a new attempt should never have been presented. Force false. 1425 $newattempt = 'off'; 1426 } else if (!empty($scorm->forcenewattempt)) { 1427 // A new attempt should be forced for already completed attempts. 1428 $newattempt = 'on'; 1429 } 1430 1431 if (($newattempt == 'on') && (($attempt < $scorm->maxattempt) || ($scorm->maxattempt == 0))) { 1432 $attempt++; 1433 $mode = 'normal'; 1434 } else { // Check if review mode should be set. 1435 if ($incomplete === true) { 1436 $mode = 'normal'; 1437 } else { 1438 $mode = 'review'; 1439 } 1440 } 1441 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |