[ 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 * Library of internal classes and functions for module SCORM 19 * 20 * @package mod_scorm 21 * @copyright 1999 onwards Roberto Pinna 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 require_once("$CFG->dirroot/mod/scorm/lib.php"); 26 require_once("$CFG->libdir/filelib.php"); 27 28 // Constants and settings for module scorm. 29 define('SCORM_UPDATE_NEVER', '0'); 30 define('SCORM_UPDATE_EVERYDAY', '2'); 31 define('SCORM_UPDATE_EVERYTIME', '3'); 32 33 define('SCORM_SKIPVIEW_NEVER', '0'); 34 define('SCORM_SKIPVIEW_FIRST', '1'); 35 define('SCORM_SKIPVIEW_ALWAYS', '2'); 36 37 define('SCO_ALL', 0); 38 define('SCO_DATA', 1); 39 define('SCO_ONLY', 2); 40 41 define('GRADESCOES', '0'); 42 define('GRADEHIGHEST', '1'); 43 define('GRADEAVERAGE', '2'); 44 define('GRADESUM', '3'); 45 46 define('HIGHESTATTEMPT', '0'); 47 define('AVERAGEATTEMPT', '1'); 48 define('FIRSTATTEMPT', '2'); 49 define('LASTATTEMPT', '3'); 50 51 define('TOCJSLINK', 1); 52 define('TOCFULLURL', 2); 53 54 // Local Library of functions for module scorm. 55 56 /** 57 * @package mod_scorm 58 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 59 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 60 */ 61 class scorm_package_file_info extends file_info_stored { 62 public function get_parent() { 63 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { 64 return $this->browser->get_file_info($this->context); 65 } 66 return parent::get_parent(); 67 } 68 public function get_visible_name() { 69 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { 70 return $this->topvisiblename; 71 } 72 return parent::get_visible_name(); 73 } 74 } 75 76 /** 77 * Returns an array of the popup options for SCORM and each options default value 78 * 79 * @return array an array of popup options as the key and their defaults as the value 80 */ 81 function scorm_get_popup_options_array() { 82 $cfgscorm = get_config('scorm'); 83 84 return array('scrollbars' => isset($cfgscorm->scrollbars) ? $cfgscorm->scrollbars : 0, 85 'directories' => isset($cfgscorm->directories) ? $cfgscorm->directories : 0, 86 'location' => isset($cfgscorm->location) ? $cfgscorm->location : 0, 87 'menubar' => isset($cfgscorm->menubar) ? $cfgscorm->menubar : 0, 88 'toolbar' => isset($cfgscorm->toolbar) ? $cfgscorm->toolbar : 0, 89 'status' => isset($cfgscorm->status) ? $cfgscorm->status : 0); 90 } 91 92 /** 93 * Returns an array of the array of what grade options 94 * 95 * @return array an array of what grade options 96 */ 97 function scorm_get_grade_method_array() { 98 return array (GRADESCOES => get_string('gradescoes', 'scorm'), 99 GRADEHIGHEST => get_string('gradehighest', 'scorm'), 100 GRADEAVERAGE => get_string('gradeaverage', 'scorm'), 101 GRADESUM => get_string('gradesum', 'scorm')); 102 } 103 104 /** 105 * Returns an array of the array of what grade options 106 * 107 * @return array an array of what grade options 108 */ 109 function scorm_get_what_grade_array() { 110 return array (HIGHESTATTEMPT => get_string('highestattempt', 'scorm'), 111 AVERAGEATTEMPT => get_string('averageattempt', 'scorm'), 112 FIRSTATTEMPT => get_string('firstattempt', 'scorm'), 113 LASTATTEMPT => get_string('lastattempt', 'scorm')); 114 } 115 116 /** 117 * Returns an array of the array of skip view options 118 * 119 * @return array an array of skip view options 120 */ 121 function scorm_get_skip_view_array() { 122 return array(SCORM_SKIPVIEW_NEVER => get_string('never'), 123 SCORM_SKIPVIEW_FIRST => get_string('firstaccess', 'scorm'), 124 SCORM_SKIPVIEW_ALWAYS => get_string('always')); 125 } 126 127 /** 128 * Returns an array of the array of hide table of contents options 129 * 130 * @return array an array of hide table of contents options 131 */ 132 function scorm_get_hidetoc_array() { 133 return array(SCORM_TOC_SIDE => get_string('sided', 'scorm'), 134 SCORM_TOC_HIDDEN => get_string('hidden', 'scorm'), 135 SCORM_TOC_POPUP => get_string('popupmenu', 'scorm'), 136 SCORM_TOC_DISABLED => get_string('disabled', 'scorm')); 137 } 138 139 /** 140 * Returns an array of the array of update frequency options 141 * 142 * @return array an array of update frequency options 143 */ 144 function scorm_get_updatefreq_array() { 145 return array(SCORM_UPDATE_NEVER => get_string('never'), 146 SCORM_UPDATE_EVERYDAY => get_string('everyday', 'scorm'), 147 SCORM_UPDATE_EVERYTIME => get_string('everytime', 'scorm')); 148 } 149 150 /** 151 * Returns an array of the array of popup display options 152 * 153 * @return array an array of popup display options 154 */ 155 function scorm_get_popup_display_array() { 156 return array(0 => get_string('currentwindow', 'scorm'), 157 1 => get_string('popup', 'scorm')); 158 } 159 160 /** 161 * Returns an array of the array of navigation buttons display options 162 * 163 * @return array an array of navigation buttons display options 164 */ 165 function scorm_get_navigation_display_array() { 166 return array(SCORM_NAV_DISABLED => get_string('no'), 167 SCORM_NAV_UNDER_CONTENT => get_string('undercontent', 'scorm'), 168 SCORM_NAV_FLOATING => get_string('floating', 'scorm')); 169 } 170 171 /** 172 * Returns an array of the array of attempt options 173 * 174 * @return array an array of attempt options 175 */ 176 function scorm_get_attempts_array() { 177 $attempts = array(0 => get_string('nolimit', 'scorm'), 178 1 => get_string('attempt1', 'scorm')); 179 180 for ($i = 2; $i <= 6; $i++) { 181 $attempts[$i] = get_string('attemptsx', 'scorm', $i); 182 } 183 184 return $attempts; 185 } 186 187 /** 188 * Returns an array of the attempt status options 189 * 190 * @return array an array of attempt status options 191 */ 192 function scorm_get_attemptstatus_array() { 193 return array(SCORM_DISPLAY_ATTEMPTSTATUS_NO => get_string('no'), 194 SCORM_DISPLAY_ATTEMPTSTATUS_ALL => get_string('attemptstatusall', 'scorm'), 195 SCORM_DISPLAY_ATTEMPTSTATUS_MY => get_string('attemptstatusmy', 'scorm'), 196 SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY => get_string('attemptstatusentry', 'scorm')); 197 } 198 199 /** 200 * Extracts scrom package, sets up all variables. 201 * Called whenever scorm changes 202 * @param object $scorm instance - fields are updated and changes saved into database 203 * @param bool $full force full update if true 204 * @return void 205 */ 206 function scorm_parse($scorm, $full) { 207 global $CFG, $DB; 208 $cfgscorm = get_config('scorm'); 209 210 if (!isset($scorm->cmid)) { 211 $cm = get_coursemodule_from_instance('scorm', $scorm->id); 212 $scorm->cmid = $cm->id; 213 } 214 $context = context_module::instance($scorm->cmid); 215 $newhash = $scorm->sha1hash; 216 217 if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) { 218 219 $fs = get_file_storage(); 220 $packagefile = false; 221 $packagefileimsmanifest = false; 222 223 if ($scorm->scormtype === SCORM_TYPE_LOCAL) { 224 if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) { 225 if ($packagefile->is_external_file()) { // Get zip file so we can check it is correct. 226 $packagefile->import_external_file_contents(); 227 } 228 $newhash = $packagefile->get_contenthash(); 229 if (strtolower($packagefile->get_filename()) == 'imsmanifest.xml') { 230 $packagefileimsmanifest = true; 231 } 232 } else { 233 $newhash = null; 234 } 235 } else { 236 if (!$cfgscorm->allowtypelocalsync) { 237 // Sorry - localsync disabled. 238 return; 239 } 240 if ($scorm->reference !== '' and (!$full or $scorm->sha1hash !== sha1($scorm->reference))) { 241 $fs->delete_area_files($context->id, 'mod_scorm', 'package'); 242 $filerecord = array('contextid' => $context->id, 'component' => 'mod_scorm', 'filearea' => 'package', 243 'itemid' => 0, 'filepath' => '/'); 244 if ($packagefile = $fs->create_file_from_url($filerecord, $scorm->reference, array('calctimeout' => true))) { 245 $newhash = sha1($scorm->reference); 246 } else { 247 $newhash = null; 248 } 249 } 250 } 251 252 if ($packagefile) { 253 if (!$full and $packagefile and $scorm->sha1hash === $newhash) { 254 if (strpos($scorm->version, 'SCORM') !== false) { 255 if ($packagefileimsmanifest || $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) { 256 // No need to update. 257 return; 258 } 259 } else if (strpos($scorm->version, 'AICC') !== false) { 260 // TODO: add more sanity checks - something really exists in scorm_content area. 261 return; 262 } 263 } 264 if (!$packagefileimsmanifest) { 265 // Now extract files. 266 $fs->delete_area_files($context->id, 'mod_scorm', 'content'); 267 268 $packer = get_file_packer('application/zip'); 269 $packagefile->extract_to_storage($packer, $context->id, 'mod_scorm', 'content', 0, '/'); 270 } 271 272 } else if (!$full) { 273 return; 274 } 275 if ($packagefileimsmanifest) { 276 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); 277 // Direct link to imsmanifest.xml file. 278 if (!scorm_parse_scorm($scorm, $packagefile)) { 279 $scorm->version = 'ERROR'; 280 } 281 282 } else if ($manifest = $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) { 283 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); 284 // SCORM. 285 if (!scorm_parse_scorm($scorm, $manifest)) { 286 $scorm->version = 'ERROR'; 287 } 288 } else { 289 require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php"); 290 // AICC. 291 $result = scorm_parse_aicc($scorm); 292 if (!$result) { 293 $scorm->version = 'ERROR'; 294 } else { 295 $scorm->version = 'AICC'; 296 } 297 } 298 299 } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL and $cfgscorm->allowtypeexternal) { 300 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); 301 // SCORM only, AICC can not be external. 302 if (!scorm_parse_scorm($scorm, $scorm->reference)) { 303 $scorm->version = 'ERROR'; 304 } 305 $newhash = sha1($scorm->reference); 306 307 } else if ($scorm->scormtype === SCORM_TYPE_AICCURL and $cfgscorm->allowtypeexternalaicc) { 308 require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php"); 309 // AICC. 310 $result = scorm_parse_aicc($scorm); 311 if (!$result) { 312 $scorm->version = 'ERROR'; 313 } else { 314 $scorm->version = 'AICC'; 315 } 316 317 } else { 318 // Sorry, disabled type. 319 return; 320 } 321 322 $scorm->revision++; 323 $scorm->sha1hash = $newhash; 324 $DB->update_record('scorm', $scorm); 325 } 326 327 328 function scorm_array_search($item, $needle, $haystacks, $strict=false) { 329 if (!empty($haystacks)) { 330 foreach ($haystacks as $key => $element) { 331 if ($strict) { 332 if ($element->{$item} === $needle) { 333 return $key; 334 } 335 } else { 336 if ($element->{$item} == $needle) { 337 return $key; 338 } 339 } 340 } 341 } 342 return false; 343 } 344 345 function scorm_repeater($what, $times) { 346 if ($times <= 0) { 347 return null; 348 } 349 $return = ''; 350 for ($i = 0; $i < $times; $i++) { 351 $return .= $what; 352 } 353 return $return; 354 } 355 356 function scorm_external_link($link) { 357 // Check if a link is external. 358 $result = false; 359 $link = strtolower($link); 360 if (substr($link, 0, 7) == 'http://') { 361 $result = true; 362 } else if (substr($link, 0, 8) == 'https://') { 363 $result = true; 364 } else if (substr($link, 0, 4) == 'www.') { 365 $result = true; 366 } 367 return $result; 368 } 369 370 /** 371 * Returns an object containing all datas relative to the given sco ID 372 * 373 * @param integer $id The sco ID 374 * @return mixed (false if sco id does not exists) 375 */ 376 function scorm_get_sco($id, $what=SCO_ALL) { 377 global $DB; 378 379 if ($sco = $DB->get_record('scorm_scoes', array('id' => $id))) { 380 $sco = ($what == SCO_DATA) ? new stdClass() : $sco; 381 if (($what != SCO_ONLY) && ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid' => $id)))) { 382 foreach ($scodatas as $scodata) { 383 $sco->{$scodata->name} = $scodata->value; 384 } 385 } else if (($what != SCO_ONLY) && (!($scodatas = $DB->get_records('scorm_scoes_data', array('scoid' => $id))))) { 386 $sco->parameters = ''; 387 } 388 return $sco; 389 } else { 390 return false; 391 } 392 } 393 394 /** 395 * Returns an object (array) containing all the scoes data related to the given sco ID 396 * 397 * @param integer $id The sco ID 398 * @param integer $organisation an organisation ID - defaults to false if not required 399 * @return mixed (false if there are no scoes or an array) 400 */ 401 function scorm_get_scoes($id, $organisation=false) { 402 global $DB; 403 404 $queryarray = array('scorm' => $id); 405 if (!empty($organisation)) { 406 $queryarray['organization'] = $organisation; 407 } 408 if ($scoes = $DB->get_records('scorm_scoes', $queryarray, 'sortorder, id')) { 409 // Drop keys so that it is a simple array as expected. 410 $scoes = array_values($scoes); 411 foreach ($scoes as $sco) { 412 if ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid' => $sco->id))) { 413 foreach ($scodatas as $scodata) { 414 $sco->{$scodata->name} = $scodata->value; 415 } 416 } 417 } 418 return $scoes; 419 } else { 420 return false; 421 } 422 } 423 424 function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $value, $forcecompleted=false, $trackdata = null) { 425 global $DB, $CFG; 426 427 $id = null; 428 429 if ($forcecompleted) { 430 // TODO - this could be broadened to encompass SCORM 2004 in future. 431 if (($element == 'cmi.core.lesson_status') && ($value == 'incomplete')) { 432 if ($track = $DB->get_record_select('scorm_scoes_track', 433 'userid=? AND scormid=? AND scoid=? AND attempt=? '. 434 'AND element=\'cmi.core.score.raw\'', 435 array($userid, $scormid, $scoid, $attempt))) { 436 $value = 'completed'; 437 } 438 } 439 if ($element == 'cmi.core.score.raw') { 440 if ($tracktest = $DB->get_record_select('scorm_scoes_track', 441 'userid=? AND scormid=? AND scoid=? AND attempt=? '. 442 'AND element=\'cmi.core.lesson_status\'', 443 array($userid, $scormid, $scoid, $attempt))) { 444 if ($tracktest->value == "incomplete") { 445 $tracktest->value = "completed"; 446 $DB->update_record('scorm_scoes_track', $tracktest); 447 } 448 } 449 } 450 if (($element == 'cmi.success_status') && ($value == 'passed' || $value == 'failed')) { 451 if ($DB->get_record('scorm_scoes_data', array('scoid' => $scoid, 'name' => 'objectivesetbycontent'))) { 452 $objectiveprogressstatus = true; 453 $objectivesatisfiedstatus = false; 454 if ($value == 'passed') { 455 $objectivesatisfiedstatus = true; 456 } 457 458 if ($track = $DB->get_record('scorm_scoes_track', array('userid' => $userid, 459 'scormid' => $scormid, 460 'scoid' => $scoid, 461 'attempt' => $attempt, 462 'element' => 'objectiveprogressstatus'))) { 463 $track->value = $objectiveprogressstatus; 464 $track->timemodified = time(); 465 $DB->update_record('scorm_scoes_track', $track); 466 $id = $track->id; 467 } else { 468 $track = new stdClass(); 469 $track->userid = $userid; 470 $track->scormid = $scormid; 471 $track->scoid = $scoid; 472 $track->attempt = $attempt; 473 $track->element = 'objectiveprogressstatus'; 474 $track->value = $objectiveprogressstatus; 475 $track->timemodified = time(); 476 $id = $DB->insert_record('scorm_scoes_track', $track); 477 } 478 if ($objectivesatisfiedstatus) { 479 if ($track = $DB->get_record('scorm_scoes_track', array('userid' => $userid, 480 'scormid' => $scormid, 481 'scoid' => $scoid, 482 'attempt' => $attempt, 483 'element' => 'objectivesatisfiedstatus'))) { 484 $track->value = $objectivesatisfiedstatus; 485 $track->timemodified = time(); 486 $DB->update_record('scorm_scoes_track', $track); 487 $id = $track->id; 488 } else { 489 $track = new stdClass(); 490 $track->userid = $userid; 491 $track->scormid = $scormid; 492 $track->scoid = $scoid; 493 $track->attempt = $attempt; 494 $track->element = 'objectivesatisfiedstatus'; 495 $track->value = $objectivesatisfiedstatus; 496 $track->timemodified = time(); 497 $id = $DB->insert_record('scorm_scoes_track', $track); 498 ob_start(); 499 $filepath = $CFG->dataroot."\\temp\\tempfile.txt"; 500 $fh = fopen($filepath, "a+"); 501 var_dump($track); 502 $string = ob_get_clean(); 503 fwrite($fh, $string); 504 fclose($fh); 505 } 506 } 507 } 508 } 509 510 } 511 512 $track = null; 513 if ($trackdata !== null) { 514 if (isset($trackdata[$element])) { 515 $track = $trackdata[$element]; 516 } 517 } else { 518 $track = $DB->get_record('scorm_scoes_track', array('userid' => $userid, 519 'scormid' => $scormid, 520 'scoid' => $scoid, 521 'attempt' => $attempt, 522 'element' => $element)); 523 } 524 if ($track) { 525 if ($element != 'x.start.time' ) { // Don't update x.start.time - keep the original value. 526 if ($track->value != $value) { 527 $track->value = $value; 528 $track->timemodified = time(); 529 $DB->update_record('scorm_scoes_track', $track); 530 } 531 $id = $track->id; 532 } 533 } else { 534 $track = new stdClass(); 535 $track->userid = $userid; 536 $track->scormid = $scormid; 537 $track->scoid = $scoid; 538 $track->attempt = $attempt; 539 $track->element = $element; 540 $track->value = $value; 541 $track->timemodified = time(); 542 $id = $DB->insert_record('scorm_scoes_track', $track); 543 } 544 545 if (strstr($element, '.score.raw') || 546 (in_array($element, array('cmi.completion_status', 'cmi.core.lesson_status', 'cmi.success_status')) 547 && in_array($track->value, array('completed', 'passed')))) { 548 $scorm = $DB->get_record('scorm', array('id' => $scormid)); 549 include_once($CFG->dirroot.'/mod/scorm/lib.php'); 550 scorm_update_grades($scorm, $userid); 551 } 552 553 return $id; 554 } 555 556 /** 557 * simple quick function to return true/false if this user has tracks in this scorm 558 * 559 * @param integer $scormid The scorm ID 560 * @param integer $userid the users id 561 * @return boolean (false if there are no tracks) 562 */ 563 function scorm_has_tracks($scormid, $userid) { 564 global $DB; 565 return $DB->record_exists('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scormid)); 566 } 567 568 function scorm_get_tracks($scoid, $userid, $attempt='') { 569 // Gets all tracks of specified sco and user. 570 global $DB; 571 572 if (empty($attempt)) { 573 if ($scormid = $DB->get_field('scorm_scoes', 'scorm', array('id' => $scoid))) { 574 $attempt = scorm_get_last_attempt($scormid, $userid); 575 } else { 576 $attempt = 1; 577 } 578 } 579 if ($tracks = $DB->get_records('scorm_scoes_track', array('userid' => $userid, 'scoid' => $scoid, 580 'attempt' => $attempt), 'element ASC')) { 581 $usertrack = scorm_format_interactions($tracks); 582 $usertrack->userid = $userid; 583 $usertrack->scoid = $scoid; 584 585 return $usertrack; 586 } else { 587 return false; 588 } 589 } 590 /** 591 * helper function to return a formatted list of interactions for reports. 592 * 593 * @param array $trackdata the records from scorm_scoes_track table 594 * @return object formatted list of interactions 595 */ 596 function scorm_format_interactions($trackdata) { 597 $usertrack = new stdClass(); 598 599 // Defined in order to unify scorm1.2 and scorm2004. 600 $usertrack->score_raw = ''; 601 $usertrack->status = ''; 602 $usertrack->total_time = '00:00:00'; 603 $usertrack->session_time = '00:00:00'; 604 $usertrack->timemodified = 0; 605 606 foreach ($trackdata as $track) { 607 $element = $track->element; 608 $usertrack->{$element} = $track->value; 609 switch ($element) { 610 case 'cmi.core.lesson_status': 611 case 'cmi.completion_status': 612 if ($track->value == 'not attempted') { 613 $track->value = 'notattempted'; 614 } 615 $usertrack->status = $track->value; 616 break; 617 case 'cmi.core.score.raw': 618 case 'cmi.score.raw': 619 $usertrack->score_raw = (float) sprintf('%2.2f', $track->value); 620 break; 621 case 'cmi.core.session_time': 622 case 'cmi.session_time': 623 $usertrack->session_time = $track->value; 624 break; 625 case 'cmi.core.total_time': 626 case 'cmi.total_time': 627 $usertrack->total_time = $track->value; 628 break; 629 } 630 if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) { 631 $usertrack->timemodified = $track->timemodified; 632 } 633 } 634 635 return $usertrack; 636 } 637 /* Find the start and finsh time for a a given SCO attempt 638 * 639 * @param int $scormid SCORM Id 640 * @param int $scoid SCO Id 641 * @param int $userid User Id 642 * @param int $attemt Attempt Id 643 * 644 * @return object start and finsh time EPOC secods 645 * 646 */ 647 function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) { 648 global $DB; 649 650 $timedata = new stdClass(); 651 $params = array('userid' => $userid, 'scormid' => $scormid, 'attempt' => $attempt); 652 if (!empty($scoid)) { 653 $params['scoid'] = $scoid; 654 } 655 $tracks = $DB->get_records('scorm_scoes_track', $params, "timemodified ASC"); 656 if ($tracks) { 657 $tracks = array_values($tracks); 658 } 659 660 if ($tracks) { 661 $timedata->start = $tracks[0]->timemodified; 662 } else { 663 $timedata->start = false; 664 } 665 if ($tracks && $track = array_pop($tracks)) { 666 $timedata->finish = $track->timemodified; 667 } else { 668 $timedata->finish = $timedata->start; 669 } 670 return $timedata; 671 } 672 673 function scorm_grade_user_attempt($scorm, $userid, $attempt=1) { 674 global $DB; 675 $attemptscore = new stdClass(); 676 $attemptscore->scoes = 0; 677 $attemptscore->values = 0; 678 $attemptscore->max = 0; 679 $attemptscore->sum = 0; 680 $attemptscore->lastmodify = 0; 681 682 if (!$scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) { 683 return null; 684 } 685 686 foreach ($scoes as $sco) { 687 if ($userdata = scorm_get_tracks($sco->id, $userid, $attempt)) { 688 if (($userdata->status == 'completed') || ($userdata->status == 'passed')) { 689 $attemptscore->scoes++; 690 } 691 if (!empty($userdata->score_raw) || (isset($scorm->type) && $scorm->type == 'sco' && isset($userdata->score_raw))) { 692 $attemptscore->values++; 693 $attemptscore->sum += $userdata->score_raw; 694 $attemptscore->max = ($userdata->score_raw > $attemptscore->max) ? $userdata->score_raw : $attemptscore->max; 695 if (isset($userdata->timemodified) && ($userdata->timemodified > $attemptscore->lastmodify)) { 696 $attemptscore->lastmodify = $userdata->timemodified; 697 } else { 698 $attemptscore->lastmodify = 0; 699 } 700 } 701 } 702 } 703 switch ($scorm->grademethod) { 704 case GRADEHIGHEST: 705 $score = (float) $attemptscore->max; 706 break; 707 case GRADEAVERAGE: 708 if ($attemptscore->values > 0) { 709 $score = $attemptscore->sum / $attemptscore->values; 710 } else { 711 $score = 0; 712 } 713 break; 714 case GRADESUM: 715 $score = $attemptscore->sum; 716 break; 717 case GRADESCOES: 718 $score = $attemptscore->scoes; 719 break; 720 default: 721 $score = $attemptscore->max; // Remote Learner GRADEHIGHEST is default. 722 } 723 724 return $score; 725 } 726 727 function scorm_grade_user($scorm, $userid) { 728 729 // Ensure we dont grade user beyond $scorm->maxattempt settings. 730 $lastattempt = scorm_get_last_attempt($scorm->id, $userid); 731 if ($scorm->maxattempt != 0 && $lastattempt >= $scorm->maxattempt) { 732 $lastattempt = $scorm->maxattempt; 733 } 734 735 switch ($scorm->whatgrade) { 736 case FIRSTATTEMPT: 737 return scorm_grade_user_attempt($scorm, $userid, 1); 738 break; 739 case LASTATTEMPT: 740 return scorm_grade_user_attempt($scorm, $userid, scorm_get_last_completed_attempt($scorm->id, $userid)); 741 break; 742 case HIGHESTATTEMPT: 743 $maxscore = 0; 744 for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { 745 $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt); 746 $maxscore = $attemptscore > $maxscore ? $attemptscore : $maxscore; 747 } 748 return $maxscore; 749 750 break; 751 case AVERAGEATTEMPT: 752 $attemptcount = scorm_get_attempt_count($userid, $scorm, true, true); 753 if (empty($attemptcount)) { 754 return 0; 755 } else { 756 $attemptcount = count($attemptcount); 757 } 758 $lastattempt = scorm_get_last_attempt($scorm->id, $userid); 759 $sumscore = 0; 760 for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { 761 $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt); 762 $sumscore += $attemptscore; 763 } 764 765 return round($sumscore / $attemptcount); 766 break; 767 } 768 } 769 770 function scorm_count_launchable($scormid, $organization='') { 771 global $DB; 772 773 $sqlorganization = ''; 774 $params = array($scormid); 775 if (!empty($organization)) { 776 $sqlorganization = " AND organization=?"; 777 $params[] = $organization; 778 } 779 return $DB->count_records_select('scorm_scoes', "scorm = ? $sqlorganization AND ". 780 $DB->sql_isnotempty('scorm_scoes', 'launch', false, true), 781 $params); 782 } 783 784 /** 785 * Returns the last attempt used - if no attempts yet, returns 1 for first attempt 786 * 787 * @param int $scormid the id of the scorm. 788 * @param int $userid the id of the user. 789 * 790 * @return int The attempt number to use. 791 */ 792 function scorm_get_last_attempt($scormid, $userid) { 793 global $DB; 794 795 // Find the last attempt number for the given user id and scorm id. 796 $sql = "SELECT MAX(attempt) 797 FROM {scorm_scoes_track} 798 WHERE userid = ? AND scormid = ?"; 799 $lastattempt = $DB->get_field_sql($sql, array($userid, $scormid)); 800 if (empty($lastattempt)) { 801 return '1'; 802 } else { 803 return $lastattempt; 804 } 805 } 806 807 /** 808 * Returns the last completed attempt used - if no completed attempts yet, returns 1 for first attempt 809 * 810 * @param int $scormid the id of the scorm. 811 * @param int $userid the id of the user. 812 * 813 * @return int The attempt number to use. 814 */ 815 function scorm_get_last_completed_attempt($scormid, $userid) { 816 global $DB; 817 818 // Find the last completed attempt number for the given user id and scorm id. 819 $sql = "SELECT MAX(attempt) 820 FROM {scorm_scoes_track} 821 WHERE userid = ? AND scormid = ? 822 AND (value='completed' OR value='passed')"; 823 $lastattempt = $DB->get_field_sql($sql, array($userid, $scormid)); 824 if (empty($lastattempt)) { 825 return '1'; 826 } else { 827 return $lastattempt; 828 } 829 } 830 831 /** 832 * Returns the full list of attempts a user has made. 833 * 834 * @param int $scormid the id of the scorm. 835 * @param int $userid the id of the user. 836 * 837 * @return array array of attemptids 838 */ 839 function scorm_get_all_attempts($scormid, $userid) { 840 global $DB; 841 $attemptids = array(); 842 $sql = "SELECT DISTINCT attempt FROM {scorm_scoes_track} WHERE userid = ? AND scormid = ? ORDER BY attempt"; 843 $attempts = $DB->get_records_sql($sql, array($userid, $scormid)); 844 foreach ($attempts as $attempt) { 845 $attemptids[] = $attempt->attempt; 846 } 847 return $attemptids; 848 } 849 850 function scorm_view_display ($user, $scorm, $action, $cm) { 851 global $CFG, $DB, $PAGE, $OUTPUT, $COURSE; 852 853 if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) { 854 scorm_parse($scorm, false); 855 } 856 857 $organization = optional_param('organization', '', PARAM_INT); 858 859 if ($scorm->displaycoursestructure == 1) { 860 echo $OUTPUT->box_start('generalbox boxaligncenter toc', 'toc'); 861 echo html_writer::div(get_string('contents', 'scorm'), 'structurehead'); 862 } 863 if (empty($organization)) { 864 $organization = $scorm->launch; 865 } 866 if ($orgs = $DB->get_records_select_menu('scorm_scoes', 'scorm = ? AND '. 867 $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '. 868 $DB->sql_isempty('scorm_scoes', 'organization', false, false), 869 array($scorm->id), 'sortorder, id', 'id,title')) { 870 if (count($orgs) > 1) { 871 $select = new single_select(new moodle_url($action), 'organization', $orgs, $organization, null); 872 $select->label = get_string('organizations', 'scorm'); 873 $select->class = 'scorm-center'; 874 echo $OUTPUT->render($select); 875 } 876 } 877 $orgidentifier = ''; 878 if ($sco = scorm_get_sco($organization, SCO_ONLY)) { 879 if (($sco->organization == '') && ($sco->launch == '')) { 880 $orgidentifier = $sco->identifier; 881 } else { 882 $orgidentifier = $sco->organization; 883 } 884 } 885 886 $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe. 887 if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) { 888 $scorm->version = 'scorm_12'; 889 } 890 require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php'); 891 892 $result = scorm_get_toc($user, $scorm, $cm->id, TOCFULLURL, $orgidentifier); 893 $incomplete = $result->incomplete; 894 895 // Do we want the TOC to be displayed? 896 if ($scorm->displaycoursestructure == 1) { 897 echo $result->toc; 898 echo $OUTPUT->box_end(); 899 } 900 901 // Is this the first attempt ? 902 $attemptcount = scorm_get_attempt_count($user->id, $scorm); 903 904 // Do not give the player launch FORM if the SCORM object is locked after the final attempt. 905 if ($scorm->lastattemptlock == 0 || $result->attemptleft > 0) { 906 echo html_writer::start_div('scorm-center'); 907 echo html_writer::start_tag('form', array('id' => 'scormviewform', 908 'method' => 'post', 909 'action' => $CFG->wwwroot.'/mod/scorm/player.php')); 910 if ($scorm->hidebrowse == 0) { 911 print_string('mode', 'scorm'); 912 echo ': '.html_writer::empty_tag('input', array('type' => 'radio', 'id' => 'b', 'name' => 'mode', 'value' => 'browse')). 913 html_writer::label(get_string('browse', 'scorm'), 'b'); 914 echo html_writer::empty_tag('input', array('type' => 'radio', 915 'id' => 'n', 'name' => 'mode', 916 'value' => 'normal', 'checked' => 'checked')). 917 html_writer::label(get_string('normal', 'scorm'), 'n'); 918 919 } else { 920 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'mode', 'value' => 'normal')); 921 } 922 if ($scorm->forcenewattempt == 1) { 923 if ($incomplete === false) { 924 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'newattempt', 'value' => 'on')); 925 } 926 } else if (!empty($attemptcount) && ($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) { 927 echo html_writer::empty_tag('br'); 928 echo html_writer::checkbox('newattempt', 'on', false, '', array('id' => 'a')); 929 echo html_writer::label(get_string('newattempt', 'scorm'), 'a'); 930 } 931 if (!empty($scorm->popup)) { 932 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'display', 'value' => 'popup')); 933 } 934 935 echo html_writer::empty_tag('br'); 936 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'scoid', 'value' => $scorm->launch)); 937 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'cm', 'value' => $cm->id)); 938 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentorg', 'value' => $orgidentifier)); 939 echo html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('enter', 'scorm'))); 940 echo html_writer::end_tag('form'); 941 echo html_writer::end_div(); 942 } 943 } 944 945 function scorm_simple_play($scorm, $user, $context, $cmid) { 946 global $DB; 947 948 $result = false; 949 950 if (has_capability('mod/scorm:viewreport', $context)) { 951 // If this user can view reports, don't skipview so they can see links to reports. 952 return $result; 953 } 954 955 if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) { 956 scorm_parse($scorm, false); 957 } 958 $scoes = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '. 959 $DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id), 'sortorder, id', 'id'); 960 961 if ($scoes) { 962 $orgidentifier = ''; 963 if ($sco = scorm_get_sco($scorm->launch, SCO_ONLY)) { 964 if (($sco->organization == '') && ($sco->launch == '')) { 965 $orgidentifier = $sco->identifier; 966 } else { 967 $orgidentifier = $sco->organization; 968 } 969 } 970 if ($scorm->skipview >= SCORM_SKIPVIEW_FIRST) { 971 $sco = current($scoes); 972 $url = new moodle_url('/mod/scorm/player.php', array('a' => $scorm->id, 973 'currentorg' => $orgidentifier, 974 'scoid' => $sco->id)); 975 if ($scorm->skipview == SCORM_SKIPVIEW_ALWAYS || !scorm_has_tracks($scorm->id, $user->id)) { 976 if (!empty($scorm->forcenewattempt)) { 977 $result = scorm_get_toc($user, $scorm, $cmid, TOCFULLURL, $orgidentifier); 978 if ($result->incomplete === false) { 979 $url->param('newattempt', 'on'); 980 } 981 } 982 redirect($url); 983 } 984 } 985 } 986 return $result; 987 } 988 989 function scorm_get_count_users($scormid, $groupingid=null) { 990 global $CFG, $DB; 991 992 if (!empty($groupingid)) { 993 $sql = "SELECT COUNT(DISTINCT st.userid) 994 FROM {scorm_scoes_track} st 995 INNER JOIN {groups_members} gm ON st.userid = gm.userid 996 INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid 997 WHERE st.scormid = ? AND gg.groupingid = ? 998 "; 999 $params = array($scormid, $groupingid); 1000 } else { 1001 $sql = "SELECT COUNT(DISTINCT st.userid) 1002 FROM {scorm_scoes_track} st 1003 WHERE st.scormid = ? 1004 "; 1005 $params = array($scormid); 1006 } 1007 1008 return ($DB->count_records_sql($sql, $params)); 1009 } 1010 1011 /** 1012 * Build up the JavaScript representation of an array element 1013 * 1014 * @param string $sversion SCORM API version 1015 * @param array $userdata User track data 1016 * @param string $elementname Name of array element to get values for 1017 * @param array $children list of sub elements of this array element that also need instantiating 1018 * @return Javascript array elements 1019 */ 1020 function scorm_reconstitute_array_element($sversion, $userdata, $elementname, $children) { 1021 // Reconstitute comments_from_learner and comments_from_lms. 1022 $current = ''; 1023 $currentsubelement = ''; 1024 $currentsub = ''; 1025 $count = 0; 1026 $countsub = 0; 1027 $scormseperator = '_'; 1028 $return = ''; 1029 if (scorm_version_check($sversion, SCORM_13)) { // Scorm 1.3 elements use a . instead of an _ . 1030 $scormseperator = '.'; 1031 } 1032 // Filter out the ones we want. 1033 $elementlist = array(); 1034 foreach ($userdata as $element => $value) { 1035 if (substr($element, 0, strlen($elementname)) == $elementname) { 1036 $elementlist[$element] = $value; 1037 } 1038 } 1039 1040 // Sort elements in .n array order. 1041 uksort($elementlist, "scorm_element_cmp"); 1042 1043 // Generate JavaScript. 1044 foreach ($elementlist as $element => $value) { 1045 if (scorm_version_check($sversion, SCORM_13)) { 1046 $element = preg_replace('/\.(\d+)\./', ".N\$1.", $element); 1047 preg_match('/\.(N\d+)\./', $element, $matches); 1048 } else { 1049 $element = preg_replace('/\.(\d+)\./', "_\$1.", $element); 1050 preg_match('/\_(\d+)\./', $element, $matches); 1051 } 1052 if (count($matches) > 0 && $current != $matches[1]) { 1053 if ($countsub > 0) { 1054 $return .= ' '.$elementname.$scormseperator.$current.'.'.$currentsubelement.'._count = '.$countsub.";\n"; 1055 } 1056 $current = $matches[1]; 1057 $count++; 1058 $currentsubelement = ''; 1059 $currentsub = ''; 1060 $countsub = 0; 1061 $end = strpos($element, $matches[1]) + strlen($matches[1]); 1062 $subelement = substr($element, 0, $end); 1063 $return .= ' '.$subelement." = new Object();\n"; 1064 // Now add the children. 1065 foreach ($children as $child) { 1066 $return .= ' '.$subelement.".".$child." = new Object();\n"; 1067 $return .= ' '.$subelement.".".$child."._children = ".$child."_children;\n"; 1068 } 1069 } 1070 1071 // Now - flesh out the second level elements if there are any. 1072 if (scorm_version_check($sversion, SCORM_13)) { 1073 $element = preg_replace('/(.*?\.N\d+\..*?)\.(\d+)\./', "\$1.N\$2.", $element); 1074 preg_match('/.*?\.N\d+\.(.*?)\.(N\d+)\./', $element, $matches); 1075 } else { 1076 $element = preg_replace('/(.*?\_\d+\..*?)\.(\d+)\./', "\$1_\$2.", $element); 1077 preg_match('/.*?\_\d+\.(.*?)\_(\d+)\./', $element, $matches); 1078 } 1079 1080 // Check the sub element type. 1081 if (count($matches) > 0 && $currentsubelement != $matches[1]) { 1082 if ($countsub > 0) { 1083 $return .= ' '.$elementname.$scormseperator.$current.'.'.$currentsubelement.'._count = '.$countsub.";\n"; 1084 } 1085 $currentsubelement = $matches[1]; 1086 $currentsub = ''; 1087 $countsub = 0; 1088 $end = strpos($element, $matches[1]) + strlen($matches[1]); 1089 $subelement = substr($element, 0, $end); 1090 $return .= ' '.$subelement." = new Object();\n"; 1091 } 1092 1093 // Now check the subelement subscript. 1094 if (count($matches) > 0 && $currentsub != $matches[2]) { 1095 $currentsub = $matches[2]; 1096 $countsub++; 1097 $end = strrpos($element, $matches[2]) + strlen($matches[2]); 1098 $subelement = substr($element, 0, $end); 1099 $return .= ' '.$subelement." = new Object();\n"; 1100 } 1101 1102 $return .= ' '.$element.' = \''.$value."';\n"; 1103 } 1104 if ($countsub > 0) { 1105 $return .= ' '.$elementname.$scormseperator.$current.'.'.$currentsubelement.'._count = '.$countsub.";\n"; 1106 } 1107 if ($count > 0) { 1108 $return .= ' '.$elementname.'._count = '.$count.";\n"; 1109 } 1110 return $return; 1111 } 1112 1113 /** 1114 * Build up the JavaScript representation of an array element 1115 * 1116 * @param string $a left array element 1117 * @param string $b right array element 1118 * @return comparator - 0,1,-1 1119 */ 1120 function scorm_element_cmp($a, $b) { 1121 preg_match('/.*?(\d+)\./', $a, $matches); 1122 $left = intval($matches[1]); 1123 preg_match('/.?(\d+)\./', $b, $matches); 1124 $right = intval($matches[1]); 1125 if ($left < $right) { 1126 return -1; // Smaller. 1127 } else if ($left > $right) { 1128 return 1; // Bigger. 1129 } else { 1130 // Look for a second level qualifier eg cmi.interactions_0.correct_responses_0.pattern. 1131 if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $a, $matches)) { 1132 $leftterm = intval($matches[2]); 1133 $left = intval($matches[3]); 1134 if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $b, $matches)) { 1135 $rightterm = intval($matches[2]); 1136 $right = intval($matches[3]); 1137 if ($leftterm < $rightterm) { 1138 return -1; // Smaller. 1139 } else if ($leftterm > $rightterm) { 1140 return 1; // Bigger. 1141 } else { 1142 if ($left < $right) { 1143 return -1; // Smaller. 1144 } else if ($left > $right) { 1145 return 1; // Bigger. 1146 } 1147 } 1148 } 1149 } 1150 // Fall back for no second level matches or second level matches are equal. 1151 return 0; // Equal to. 1152 } 1153 } 1154 1155 /** 1156 * Generate the user attempt status string 1157 * 1158 * @param object $user Current context user 1159 * @param object $scorm a moodle scrom object - mdl_scorm 1160 * @return string - Attempt status string 1161 */ 1162 function scorm_get_attempt_status($user, $scorm, $cm='') { 1163 global $DB, $PAGE, $OUTPUT; 1164 1165 $attempts = scorm_get_attempt_count($user->id, $scorm, true); 1166 if (empty($attempts)) { 1167 $attemptcount = 0; 1168 } else { 1169 $attemptcount = count($attempts); 1170 } 1171 1172 $result = html_writer::start_tag('p').get_string('noattemptsallowed', 'scorm').': '; 1173 if ($scorm->maxattempt > 0) { 1174 $result .= $scorm->maxattempt . html_writer::empty_tag('br'); 1175 } else { 1176 $result .= get_string('unlimited').html_writer::empty_tag('br'); 1177 } 1178 $result .= get_string('noattemptsmade', 'scorm').': ' . $attemptcount . html_writer::empty_tag('br'); 1179 1180 if ($scorm->maxattempt == 1) { 1181 switch ($scorm->grademethod) { 1182 case GRADEHIGHEST: 1183 $grademethod = get_string('gradehighest', 'scorm'); 1184 break; 1185 case GRADEAVERAGE: 1186 $grademethod = get_string('gradeaverage', 'scorm'); 1187 break; 1188 case GRADESUM: 1189 $grademethod = get_string('gradesum', 'scorm'); 1190 break; 1191 case GRADESCOES: 1192 $grademethod = get_string('gradescoes', 'scorm'); 1193 break; 1194 } 1195 } else { 1196 switch ($scorm->whatgrade) { 1197 case HIGHESTATTEMPT: 1198 $grademethod = get_string('highestattempt', 'scorm'); 1199 break; 1200 case AVERAGEATTEMPT: 1201 $grademethod = get_string('averageattempt', 'scorm'); 1202 break; 1203 case FIRSTATTEMPT: 1204 $grademethod = get_string('firstattempt', 'scorm'); 1205 break; 1206 case LASTATTEMPT: 1207 $grademethod = get_string('lastattempt', 'scorm'); 1208 break; 1209 } 1210 } 1211 1212 if (!empty($attempts)) { 1213 $i = 1; 1214 foreach ($attempts as $attempt) { 1215 $gradereported = scorm_grade_user_attempt($scorm, $user->id, $attempt->attemptnumber); 1216 if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) { 1217 $gradereported = $gradereported / $scorm->maxgrade; 1218 $gradereported = number_format($gradereported * 100, 0) .'%'; 1219 } 1220 $result .= get_string('gradeforattempt', 'scorm').' ' . $i . ': ' . $gradereported .html_writer::empty_tag('br'); 1221 $i++; 1222 } 1223 } 1224 $calculatedgrade = scorm_grade_user($scorm, $user->id); 1225 if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) { 1226 $calculatedgrade = $calculatedgrade / $scorm->maxgrade; 1227 $calculatedgrade = number_format($calculatedgrade * 100, 0) .'%'; 1228 } 1229 $result .= get_string('grademethod', 'scorm'). ': ' . $grademethod; 1230 if (empty($attempts)) { 1231 $result .= html_writer::empty_tag('br').get_string('gradereported', 'scorm'). 1232 ': '.get_string('none').html_writer::empty_tag('br'); 1233 } else { 1234 $result .= html_writer::empty_tag('br').get_string('gradereported', 'scorm'). 1235 ': '.$calculatedgrade.html_writer::empty_tag('br'); 1236 } 1237 $result .= html_writer::end_tag('p'); 1238 if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) { 1239 $result .= html_writer::tag('p', get_string('exceededmaxattempts', 'scorm'), array('class' => 'exceededmaxattempts')); 1240 } 1241 if (!empty($cm)) { 1242 $context = context_module::instance($cm->id); 1243 if (has_capability('mod/scorm:deleteownresponses', $context) && 1244 $DB->record_exists('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id))) { 1245 // Check to see if any data is stored for this user. 1246 $deleteurl = new moodle_url($PAGE->url, array('action' => 'delete', 'sesskey' => sesskey())); 1247 $result .= $OUTPUT->single_button($deleteurl, get_string('deleteallattempts', 'scorm')); 1248 } 1249 } 1250 1251 return $result; 1252 } 1253 1254 /** 1255 * Get SCORM attempt count 1256 * 1257 * @param object $user Current context user 1258 * @param object $scorm a moodle scrom object - mdl_scorm 1259 * @param bool $returnobjects if true returns a object with attempts, if false returns count of attempts. 1260 * @param bool $ignoremissingcompletion - ignores attempts that haven't reported a grade/completion. 1261 * @return int - no. of attempts so far 1262 */ 1263 function scorm_get_attempt_count($userid, $scorm, $returnobjects = false, $ignoremissingcompletion = false) { 1264 global $DB; 1265 1266 // Historically attempts that don't report these elements haven't been included in the average attempts grading method 1267 // we may want to change this in future, but to avoid unexpected grade decreases we're leaving this in. MDL-43222 . 1268 if (scorm_version_check($scorm->version, SCORM_13)) { 1269 $element = 'cmi.score.raw'; 1270 } else if ($scorm->grademethod == GRADESCOES) { 1271 $element = 'cmi.core.lesson_status'; 1272 } else { 1273 $element = 'cmi.core.score.raw'; 1274 } 1275 1276 if ($returnobjects) { 1277 $params = array('userid' => $userid, 'scormid' => $scorm->id); 1278 if ($ignoremissingcompletion) { // Exclude attempts that don't have the completion element requested. 1279 $params['element'] = $element; 1280 } 1281 $attempts = $DB->get_records('scorm_scoes_track', $params, 'attempt', 'DISTINCT attempt AS attemptnumber'); 1282 return $attempts; 1283 } else { 1284 $params = array($userid, $scorm->id); 1285 $sql = "SELECT COUNT(DISTINCT attempt) 1286 FROM {scorm_scoes_track} 1287 WHERE userid = ? AND scormid = ?"; 1288 if ($ignoremissingcompletion) { // Exclude attempts that don't have the completion element requested. 1289 $sql .= ' AND element = ?'; 1290 $params[] = $element; 1291 } 1292 1293 $attemptscount = $DB->count_records_sql($sql, $params); 1294 return $attemptscount; 1295 } 1296 } 1297 1298 /** 1299 * Figure out with this is a debug situation 1300 * 1301 * @param object $scorm a moodle scrom object - mdl_scorm 1302 * @return boolean - debugging true/false 1303 */ 1304 function scorm_debugging($scorm) { 1305 global $CFG, $USER; 1306 $cfgscorm = get_config('scorm'); 1307 1308 if (!$cfgscorm->allowapidebug) { 1309 return false; 1310 } 1311 $identifier = $USER->username.':'.$scorm->name; 1312 $test = $cfgscorm->apidebugmask; 1313 // Check the regex is only a short list of safe characters. 1314 if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) { 1315 return false; 1316 } 1317 $res = false; 1318 eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;'); 1319 return $res; 1320 } 1321 1322 /** 1323 * Delete Scorm tracks for selected users 1324 * 1325 * @param array $attemptids list of attempts that need to be deleted 1326 * @param stdClass $scorm instance 1327 * 1328 * @return bool true deleted all responses, false failed deleting an attempt - stopped here 1329 */ 1330 function scorm_delete_responses($attemptids, $scorm) { 1331 if (!is_array($attemptids) || empty($attemptids)) { 1332 return false; 1333 } 1334 1335 foreach ($attemptids as $num => $attemptid) { 1336 if (empty($attemptid)) { 1337 unset($attemptids[$num]); 1338 } 1339 } 1340 1341 foreach ($attemptids as $attempt) { 1342 $keys = explode(':', $attempt); 1343 if (count($keys) == 2) { 1344 $userid = clean_param($keys[0], PARAM_INT); 1345 $attemptid = clean_param($keys[1], PARAM_INT); 1346 if (!$userid || !$attemptid || !scorm_delete_attempt($userid, $scorm, $attemptid)) { 1347 return false; 1348 } 1349 } else { 1350 return false; 1351 } 1352 } 1353 return true; 1354 } 1355 1356 /** 1357 * Delete Scorm tracks for selected users 1358 * 1359 * @param int $userid ID of User 1360 * @param stdClass $scorm Scorm object 1361 * @param int $attemptid user attempt that need to be deleted 1362 * 1363 * @return bool true suceeded 1364 */ 1365 function scorm_delete_attempt($userid, $scorm, $attemptid) { 1366 global $DB; 1367 1368 $DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attemptid)); 1369 $cm = get_coursemodule_from_instance('scorm', $scorm->id); 1370 1371 // Trigger instances list viewed event. 1372 $event = \mod_scorm\event\attempt_deleted::create(array( 1373 'other' => array('attemptid' => $attemptid), 1374 'context' => context_module::instance($cm->id), 1375 'relateduserid' => $userid 1376 )); 1377 $event->add_record_snapshot('course_modules', $cm); 1378 $event->add_record_snapshot('scorm', $scorm); 1379 $event->trigger(); 1380 1381 include_once ('lib.php'); 1382 scorm_update_grades($scorm, $userid, true); 1383 return true; 1384 } 1385 1386 /** 1387 * Converts SCORM duration notation to human-readable format 1388 * The function works with both SCORM 1.2 and SCORM 2004 time formats 1389 * @param $duration string SCORM duration 1390 * @return string human-readable date/time 1391 */ 1392 function scorm_format_duration($duration) { 1393 // Fetch date/time strings. 1394 $stryears = get_string('years'); 1395 $strmonths = get_string('nummonths'); 1396 $strdays = get_string('days'); 1397 $strhours = get_string('hours'); 1398 $strminutes = get_string('minutes'); 1399 $strseconds = get_string('seconds'); 1400 1401 if ($duration[0] == 'P') { 1402 // If timestamp starts with 'P' - it's a SCORM 2004 format 1403 // this regexp discards empty sections, takes Month/Minute ambiguity into consideration, 1404 // and outputs filled sections, discarding leading zeroes and any format literals 1405 // also saves the only zero before seconds decimals (if there are any) and discards decimals if they are zero. 1406 $pattern = array( '#([A-Z])0+Y#', '#([A-Z])0+M#', '#([A-Z])0+D#', '#P(|\d+Y)0*(\d+)M#', 1407 '#0*(\d+)Y#', '#0*(\d+)D#', '#P#', '#([A-Z])0+H#', '#([A-Z])[0.]+S#', 1408 '#\.0+S#', '#T(|\d+H)0*(\d+)M#', '#0*(\d+)H#', '#0+\.(\d+)S#', 1409 '#0*([\d.]+)S#', '#T#' ); 1410 $replace = array( '$1', '$1', '$1', '$1$2 '.$strmonths.' ', '$1 '.$stryears.' ', '$1 '.$strdays.' ', 1411 '', '$1', '$1', 'S', '$1$2 '.$strminutes.' ', '$1 '.$strhours.' ', 1412 '0.$1 '.$strseconds, '$1 '.$strseconds, ''); 1413 } else { 1414 // Else we have SCORM 1.2 format there 1415 // first convert the timestamp to some SCORM 2004-like format for conveniency. 1416 $duration = preg_replace('#^(\d+):(\d+):([\d.]+)$#', 'T$1H$2M$3S', $duration); 1417 // Then convert in the same way as SCORM 2004. 1418 $pattern = array( '#T0+H#', '#([A-Z])0+M#', '#([A-Z])[0.]+S#', '#\.0+S#', '#0*(\d+)H#', 1419 '#0*(\d+)M#', '#0+\.(\d+)S#', '#0*([\d.]+)S#', '#T#' ); 1420 $replace = array( 'T', '$1', '$1', 'S', '$1 '.$strhours.' ', '$1 '.$strminutes.' ', 1421 '0.$1 '.$strseconds, '$1 '.$strseconds, '' ); 1422 } 1423 1424 $result = preg_replace($pattern, $replace, $duration); 1425 1426 return $result; 1427 } 1428 1429 function scorm_get_toc_object($user, $scorm, $currentorg='', $scoid='', $mode='normal', $attempt='', 1430 $play=false, $organizationsco=null) { 1431 global $CFG, $DB, $PAGE, $OUTPUT; 1432 1433 // Always pass the mode even if empty as that is what is done elsewhere and the urls have to match. 1434 $modestr = '&mode='; 1435 if ($mode != 'normal') { 1436 $modestr = '&mode='.$mode; 1437 } 1438 1439 $result = array(); 1440 $incomplete = false; 1441 1442 if (!empty($organizationsco)) { 1443 $result[0] = $organizationsco; 1444 $result[0]->isvisible = true; 1445 $result[0]->statusicon = ''; 1446 $result[0]->url = ''; 1447 } 1448 1449 if ($scoes = scorm_get_scoes($scorm->id, $currentorg)) { 1450 // Retrieve user tracking data for each learning object. 1451 $usertracks = array(); 1452 foreach ($scoes as $sco) { 1453 if (!empty($sco->launch)) { 1454 if ($usertrack = scorm_get_tracks($sco->id, $user->id, $attempt)) { 1455 if ($usertrack->status == '') { 1456 $usertrack->status = 'notattempted'; 1457 } 1458 $usertracks[$sco->identifier] = $usertrack; 1459 } 1460 } 1461 } 1462 foreach ($scoes as $sco) { 1463 if (!isset($sco->isvisible)) { 1464 $sco->isvisible = true; 1465 } 1466 1467 if (empty($sco->title)) { 1468 $sco->title = $sco->identifier; 1469 } 1470 1471 if (scorm_version_check($scorm->version, SCORM_13)) { 1472 $sco->prereq = true; 1473 } else { 1474 $sco->prereq = empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites, $usertracks); 1475 } 1476 1477 if ($sco->isvisible) { 1478 if (!empty($sco->launch)) { 1479 if (empty($scoid) && ($mode != 'normal')) { 1480 $scoid = $sco->id; 1481 } 1482 1483 if (isset($usertracks[$sco->identifier])) { 1484 $usertrack = $usertracks[$sco->identifier]; 1485 $strstatus = get_string($usertrack->status, 'scorm'); 1486 1487 if ($sco->scormtype == 'sco') { 1488 $statusicon = html_writer::img($OUTPUT->pix_url($usertrack->status, 'scorm'), $strstatus, 1489 array('title' => $strstatus)); 1490 } else { 1491 $statusicon = html_writer::img($OUTPUT->pix_url('asset', 'scorm'), get_string('assetlaunched', 'scorm'), 1492 array('title' => get_string('assetlaunched', 'scorm'))); 1493 } 1494 1495 if (($usertrack->status == 'notattempted') || 1496 ($usertrack->status == 'incomplete') || 1497 ($usertrack->status == 'browsed')) { 1498 $incomplete = true; 1499 if ($play && empty($scoid)) { 1500 $scoid = $sco->id; 1501 } 1502 } 1503 1504 $strsuspended = get_string('suspended', 'scorm'); 1505 1506 $exitvar = 'cmi.core.exit'; 1507 1508 if (scorm_version_check($scorm->version, SCORM_13)) { 1509 $exitvar = 'cmi.exit'; 1510 } 1511 1512 if ($incomplete && isset($usertrack->{$exitvar}) && ($usertrack->{$exitvar} == 'suspend')) { 1513 $statusicon = html_writer::img($OUTPUT->pix_url('suspend', 'scorm'), $strstatus.' - '.$strsuspended, 1514 array('title' => $strstatus.' - '.$strsuspended)); 1515 } 1516 1517 } else { 1518 if ($play && empty($scoid)) { 1519 $scoid = $sco->id; 1520 } 1521 1522 $incomplete = true; 1523 1524 if ($sco->scormtype == 'sco') { 1525 $statusicon = html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'), 1526 get_string('notattempted', 'scorm'), 1527 array('title' => get_string('notattempted', 'scorm'))); 1528 } else { 1529 $statusicon = html_writer::img($OUTPUT->pix_url('asset', 'scorm'), get_string('asset', 'scorm'), 1530 array('title' => get_string('asset', 'scorm'))); 1531 } 1532 } 1533 } 1534 } 1535 1536 if (empty($statusicon)) { 1537 $sco->statusicon = html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'), get_string('notattempted', 'scorm'), 1538 array('title' => get_string('notattempted', 'scorm'))); 1539 } else { 1540 $sco->statusicon = $statusicon; 1541 } 1542 1543 $sco->url = 'a='.$scorm->id.'&scoid='.$sco->id.'¤torg='.$currentorg.$modestr.'&attempt='.$attempt; 1544 $sco->incomplete = $incomplete; 1545 1546 if (!in_array($sco->id, array_keys($result))) { 1547 $result[$sco->id] = $sco; 1548 } 1549 } 1550 } 1551 1552 // Get the parent scoes! 1553 $result = scorm_get_toc_get_parent_child($result, $currentorg); 1554 1555 // Be safe, prevent warnings from showing up while returning array. 1556 if (!isset($scoid)) { 1557 $scoid = ''; 1558 } 1559 1560 return array('scoes' => $result, 'usertracks' => $usertracks, 'scoid' => $scoid); 1561 } 1562 1563 function scorm_get_toc_get_parent_child(&$result, $currentorg) { 1564 $final = array(); 1565 $level = 0; 1566 // Organization is always the root, prevparent. 1567 if (!empty($currentorg)) { 1568 $prevparent = $currentorg; 1569 } else { 1570 $prevparent = '/'; 1571 } 1572 1573 foreach ($result as $sco) { 1574 if ($sco->parent == '/') { 1575 $final[$level][$sco->identifier] = $sco; 1576 $prevparent = $sco->identifier; 1577 unset($result[$sco->id]); 1578 } else { 1579 if ($sco->parent == $prevparent) { 1580 $final[$level][$sco->identifier] = $sco; 1581 $prevparent = $sco->identifier; 1582 unset($result[$sco->id]); 1583 } else { 1584 if (!empty($final[$level])) { 1585 $found = false; 1586 foreach ($final[$level] as $fin) { 1587 if ($sco->parent == $fin->identifier) { 1588 $found = true; 1589 } 1590 } 1591 1592 if ($found) { 1593 $final[$level][$sco->identifier] = $sco; 1594 unset($result[$sco->id]); 1595 $found = false; 1596 } else { 1597 $level++; 1598 $final[$level][$sco->identifier] = $sco; 1599 unset($result[$sco->id]); 1600 } 1601 } 1602 } 1603 } 1604 } 1605 1606 for ($i = 0; $i <= $level; $i++) { 1607 $prevparent = ''; 1608 foreach ($final[$i] as $ident => $sco) { 1609 if (empty($prevparent)) { 1610 $prevparent = $ident; 1611 } 1612 if (!isset($final[$i][$prevparent]->children)) { 1613 $final[$i][$prevparent]->children = array(); 1614 } 1615 if ($sco->parent == $prevparent) { 1616 $final[$i][$prevparent]->children[] = $sco; 1617 $prevparent = $ident; 1618 } else { 1619 $parent = false; 1620 foreach ($final[$i] as $identifier => $scoobj) { 1621 if ($identifier == $sco->parent) { 1622 $parent = $identifier; 1623 } 1624 } 1625 1626 if ($parent !== false) { 1627 $final[$i][$parent]->children[] = $sco; 1628 } 1629 } 1630 } 1631 } 1632 1633 $results = array(); 1634 for ($i = 0; $i <= $level; $i++) { 1635 $keys = array_keys($final[$i]); 1636 $results[] = $final[$i][$keys[0]]; 1637 } 1638 1639 return $results; 1640 } 1641 1642 function scorm_format_toc_for_treeview($user, $scorm, $scoes, $usertracks, $cmid, $toclink=TOCJSLINK, $currentorg='', 1643 $attempt='', $play=false, $organizationsco=null, $children=false) { 1644 global $CFG; 1645 1646 $result = new stdClass(); 1647 $result->prerequisites = true; 1648 $result->incomplete = true; 1649 $result->toc = ''; 1650 1651 if (!$children) { 1652 $attemptsmade = scorm_get_attempt_count($user->id, $scorm); 1653 $result->attemptleft = $scorm->maxattempt == 0 ? 1 : $scorm->maxattempt - $attemptsmade; 1654 } 1655 1656 if (!$children) { 1657 $result->toc = html_writer::start_tag('ul'); 1658 1659 if (!$play && !empty($organizationsco)) { 1660 $result->toc .= html_writer::start_tag('li').$organizationsco->title.html_writer::end_tag('li'); 1661 } 1662 } 1663 1664 $prevsco = ''; 1665 if (!empty($scoes)) { 1666 foreach ($scoes as $sco) { 1667 $result->toc .= html_writer::start_tag('li'); 1668 $scoid = $sco->id; 1669 1670 $sco->isvisible = true; 1671 1672 if ($sco->isvisible) { 1673 $score = ''; 1674 1675 if (isset($usertracks[$sco->identifier])) { 1676 $viewscore = has_capability('mod/scorm:viewscores', context_module::instance($cmid)); 1677 if (isset($usertracks[$sco->identifier]->score_raw) && $viewscore) { 1678 if ($usertracks[$sco->identifier]->score_raw != '') { 1679 $score = '('.get_string('score', 'scorm').': '.$usertracks[$sco->identifier]->score_raw.')'; 1680 } 1681 } 1682 } 1683 1684 if (!empty($sco->prereq)) { 1685 if ($sco->id == $scoid) { 1686 $result->prerequisites = true; 1687 } 1688 1689 if (!empty($prevsco) && scorm_version_check($scorm->version, SCORM_13) && !empty($prevsco->hidecontinue)) { 1690 if ($sco->scormtype == 'sco') { 1691 $result->toc .= html_writer::span($sco->statusicon.' '.format_string($sco->title)); 1692 } else { 1693 $result->toc .= html_writer::span(' '.format_string($sco->title)); 1694 } 1695 } else if ($toclink == TOCFULLURL) { 1696 $url = $CFG->wwwroot.'/mod/scorm/player.php?'.$sco->url; 1697 if (!empty($sco->launch)) { 1698 if ($sco->scormtype == 'sco') { 1699 $result->toc .= $sco->statusicon.' '; 1700 $result->toc .= html_writer::link($url, format_string($sco->title)).$score; 1701 } else { 1702 $result->toc .= ' '.html_writer::link($url, format_string($sco->title), 1703 array('data-scoid' => $sco->id)).$score; 1704 } 1705 } else { 1706 if ($sco->scormtype == 'sco') { 1707 $result->toc .= $sco->statusicon.' '.format_string($sco->title).$score; 1708 } else { 1709 $result->toc .= ' '.format_string($sco->title).$score; 1710 } 1711 } 1712 } else { 1713 if (!empty($sco->launch)) { 1714 if ($sco->scormtype == 'sco') { 1715 $result->toc .= html_writer::tag('a', $sco->statusicon.' '. 1716 format_string($sco->title).' '.$score, 1717 array('data-scoid' => $sco->id, 'title' => $sco->url)); 1718 } else { 1719 $result->toc .= html_writer::tag('a', ' '.format_string($sco->title).' '.$score, 1720 array('data-scoid' => $sco->id, 'title' => $sco->url)); 1721 } 1722 } else { 1723 if ($sco->scormtype == 'sco') { 1724 $result->toc .= html_writer::span($sco->statusicon.' '.format_string($sco->title)); 1725 } else { 1726 $result->toc .= html_writer::span(' '.format_string($sco->title)); 1727 } 1728 } 1729 } 1730 1731 } else { 1732 if ($play) { 1733 if ($sco->scormtype == 'sco') { 1734 $result->toc .= html_writer::span($sco->statusicon.' '.format_string($sco->title)); 1735 } else { 1736 $result->toc .= ' '.format_string($sco->title).html_writer::end_span(); 1737 } 1738 } else { 1739 if ($sco->scormtype == 'sco') { 1740 $result->toc .= $sco->statusicon.' '.format_string($sco->title); 1741 } else { 1742 $result->toc .= ' '.format_string($sco->title); 1743 } 1744 } 1745 } 1746 1747 } else { 1748 $result->toc .= " ".format_string($sco->title); 1749 } 1750 1751 if (!empty($sco->children)) { 1752 $result->toc .= html_writer::start_tag('ul'); 1753 $childresult = scorm_format_toc_for_treeview($user, $scorm, $sco->children, $usertracks, $cmid, 1754 $toclink, $currentorg, $attempt, $play, $organizationsco, true); 1755 $result->toc .= $childresult->toc; 1756 $result->toc .= html_writer::end_tag('ul'); 1757 $result->toc .= html_writer::end_tag('li'); 1758 } else { 1759 $result->toc .= html_writer::end_tag('li'); 1760 } 1761 $prevsco = $sco; 1762 } 1763 $result->incomplete = $sco->incomplete; 1764 } 1765 1766 if (!$children) { 1767 $result->toc .= html_writer::end_tag('ul'); 1768 } 1769 1770 return $result; 1771 } 1772 1773 function scorm_format_toc_for_droplist($scorm, $scoes, $usertracks, $currentorg='', $organizationsco=null, 1774 $children=false, $level=0, $tocmenus=array()) { 1775 if (!empty($scoes)) { 1776 if (!empty($organizationsco) && !$children) { 1777 $tocmenus[$organizationsco->id] = $organizationsco->title; 1778 } 1779 1780 $parents[$level] = '/'; 1781 foreach ($scoes as $sco) { 1782 if ($parents[$level] != $sco->parent) { 1783 if ($newlevel = array_search($sco->parent, $parents)) { 1784 $level = $newlevel; 1785 } else { 1786 $i = $level; 1787 while (($i > 0) && ($parents[$level] != $sco->parent)) { 1788 $i--; 1789 } 1790 1791 if (($i == 0) && ($sco->parent != $currentorg)) { 1792 $level++; 1793 } else { 1794 $level = $i; 1795 } 1796 1797 $parents[$level] = $sco->parent; 1798 } 1799 } 1800 1801 if ($sco->prereq) { 1802 if ($sco->scormtype == 'sco') { 1803 $tocmenus[$sco->id] = scorm_repeater('−', $level) . '>' . format_string($sco->title); 1804 } 1805 } else { 1806 if ($sco->scormtype == 'sco') { 1807 $tocmenus[$sco->id] = scorm_repeater('−', $level) . '>' . format_string($sco->title); 1808 } 1809 } 1810 1811 if (!empty($sco->children)) { 1812 $tocmenus = scorm_format_toc_for_droplist($scorm, $sco->children, $usertracks, $currentorg, 1813 $organizationsco, true, $level, $tocmenus); 1814 } 1815 } 1816 } 1817 1818 return $tocmenus; 1819 } 1820 1821 function scorm_get_toc($user, $scorm, $cmid, $toclink=TOCJSLINK, $currentorg='', $scoid='', $mode='normal', 1822 $attempt='', $play=false, $tocheader=false) { 1823 global $CFG, $DB, $OUTPUT; 1824 1825 if (empty($attempt)) { 1826 $attempt = scorm_get_last_attempt($scorm->id, $user->id); 1827 } 1828 1829 $result = new stdClass(); 1830 $organizationsco = null; 1831 1832 if ($tocheader) { 1833 $result->toc = html_writer::start_div('yui3-g-r', array('id' => 'scorm_layout')); 1834 $result->toc .= html_writer::start_div('yui3-u-1-5', array('id' => 'scorm_toc')); 1835 $result->toc .= html_writer::div('', '', array('id' => 'scorm_toc_title')); 1836 $result->toc .= html_writer::start_div('', array('id' => 'scorm_tree')); 1837 } 1838 1839 if (!empty($currentorg)) { 1840 $organizationsco = $DB->get_record('scorm_scoes', array('scorm' => $scorm->id, 'identifier' => $currentorg)); 1841 if (!empty($organizationsco->title)) { 1842 if ($play) { 1843 $result->toctitle = $organizationsco->title; 1844 } 1845 } 1846 } 1847 1848 $scoes = scorm_get_toc_object($user, $scorm, $currentorg, $scoid, $mode, $attempt, $play, $organizationsco); 1849 1850 $treeview = scorm_format_toc_for_treeview($user, $scorm, $scoes['scoes'][0]->children, $scoes['usertracks'], $cmid, 1851 $toclink, $currentorg, $attempt, $play, $organizationsco, false); 1852 1853 if ($tocheader) { 1854 $result->toc .= $treeview->toc; 1855 } else { 1856 $result->toc = $treeview->toc; 1857 } 1858 1859 if (!empty($scoes['scoid'])) { 1860 $scoid = $scoes['scoid']; 1861 } 1862 1863 if (empty($scoid)) { 1864 // If this is a normal package with an org sco and child scos get the first child. 1865 if (!empty($scoes['scoes'][0]->children)) { 1866 $result->sco = $scoes['scoes'][0]->children[0]; 1867 } else { // This package only has one sco - it may be a simple external AICC package. 1868 $result->sco = $scoes['scoes'][0]; 1869 } 1870 1871 } else { 1872 $result->sco = scorm_get_sco($scoid); 1873 } 1874 1875 if ($scorm->hidetoc == SCORM_TOC_POPUP) { 1876 $tocmenu = scorm_format_toc_for_droplist($scorm, $scoes['scoes'][0]->children, $scoes['usertracks'], 1877 $currentorg, $organizationsco); 1878 1879 $modestr = ''; 1880 if ($mode != 'normal') { 1881 $modestr = '&mode='.$mode; 1882 } 1883 1884 $url = new moodle_url('/mod/scorm/player.php?a='.$scorm->id.'¤torg='.$currentorg.$modestr); 1885 $result->tocmenu = $OUTPUT->single_select($url, 'scoid', $tocmenu, $result->sco->id, null, "tocmenu"); 1886 } 1887 1888 $result->prerequisites = $treeview->prerequisites; 1889 $result->incomplete = $treeview->incomplete; 1890 $result->attemptleft = $treeview->attemptleft; 1891 1892 if ($tocheader) { 1893 $result->toc .= html_writer::end_div().html_writer::end_div(); 1894 $result->toc .= html_writer::start_div('', array('id' => 'scorm_toc_toggle')); 1895 $result->toc .= html_writer::tag('button', '', array('id' => 'scorm_toc_toggle_btn')).html_writer::end_div(); 1896 $result->toc .= html_writer::start_div('', array('id' => 'scorm_content')); 1897 $result->toc .= html_writer::div('', '', array('id' => 'scorm_navpanel')); 1898 $result->toc .= html_writer::end_div().html_writer::end_div(); 1899 } 1900 1901 return $result; 1902 } 1903 1904 function scorm_get_adlnav_json ($scoes, &$adlnav = array(), $parentscoid = null) { 1905 if (is_object($scoes)) { 1906 $sco = $scoes; 1907 if (isset($sco->url)) { 1908 $adlnav[$sco->id]['identifier'] = $sco->identifier; 1909 $adlnav[$sco->id]['launch'] = $sco->launch; 1910 $adlnav[$sco->id]['title'] = $sco->title; 1911 $adlnav[$sco->id]['url'] = $sco->url; 1912 $adlnav[$sco->id]['parent'] = $sco->parent; 1913 if (isset($sco->choice)) { 1914 $adlnav[$sco->id]['choice'] = $sco->choice; 1915 } 1916 if (isset($sco->flow)) { 1917 $adlnav[$sco->id]['flow'] = $sco->flow; 1918 } else if (isset($parentscoid) && isset($adlnav[$parentscoid]['flow'])) { 1919 $adlnav[$sco->id]['flow'] = $adlnav[$parentscoid]['flow']; 1920 } 1921 if (isset($sco->isvisible)) { 1922 $adlnav[$sco->id]['isvisible'] = $sco->isvisible; 1923 } 1924 if (isset($sco->parameters)) { 1925 $adlnav[$sco->id]['parameters'] = $sco->parameters; 1926 } 1927 if (isset($sco->hidecontinue)) { 1928 $adlnav[$sco->id]['hidecontinue'] = $sco->hidecontinue; 1929 } 1930 if (isset($sco->hideprevious)) { 1931 $adlnav[$sco->id]['hideprevious'] = $sco->hideprevious; 1932 } 1933 if (isset($sco->hidesuspendall)) { 1934 $adlnav[$sco->id]['hidesuspendall'] = $sco->hidesuspendall; 1935 } 1936 if (!empty($parentscoid)) { 1937 $adlnav[$sco->id]['parentscoid'] = $parentscoid; 1938 } 1939 if (isset($adlnav['prevscoid'])) { 1940 $adlnav[$sco->id]['prevscoid'] = $adlnav['prevscoid']; 1941 $adlnav[$adlnav['prevscoid']]['nextscoid'] = $sco->id; 1942 if (isset($adlnav['prevparent']) && $adlnav['prevparent'] == $sco->parent) { 1943 $adlnav[$sco->id]['prevsibling'] = $adlnav['prevscoid']; 1944 $adlnav[$adlnav['prevscoid']]['nextsibling'] = $sco->id; 1945 } 1946 } 1947 $adlnav['prevscoid'] = $sco->id; 1948 $adlnav['prevparent'] = $sco->parent; 1949 } 1950 if (isset($sco->children)) { 1951 foreach ($sco->children as $children) { 1952 scorm_get_adlnav_json($children, $adlnav, $sco->id); 1953 } 1954 } 1955 } else { 1956 foreach ($scoes as $sco) { 1957 scorm_get_adlnav_json ($sco, $adlnav); 1958 } 1959 unset($adlnav['prevscoid']); 1960 unset($adlnav['prevparent']); 1961 } 1962 return json_encode($adlnav); 1963 } 1964 1965 /** 1966 * Check for the availability of a resource by URL. 1967 * 1968 * Check is performed using an HTTP HEAD call. 1969 * 1970 * @param $url string A valid URL 1971 * @return bool|string True if no issue is found. The error string message, otherwise 1972 */ 1973 function scorm_check_url($url) { 1974 $curl = new curl; 1975 // Same options as in {@link download_file_content()}, used in {@link scorm_parse_scorm()}. 1976 $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => true, 'CURLOPT_MAXREDIRS' => 5)); 1977 $cmsg = $curl->head($url); 1978 $info = $curl->get_info(); 1979 if (empty($info['http_code']) || $info['http_code'] != 200) { 1980 return get_string('invalidurlhttpcheck', 'scorm', array('cmsg' => $cmsg)); 1981 } 1982 1983 return true; 1984 } 1985 1986 /** 1987 * Check for a parameter in userdata and return it if it's set 1988 * or return the value from $ifempty if its empty 1989 * 1990 * @param stdClass $userdata Contains user's data 1991 * @param string $param parameter that should be checked 1992 * @param string $ifempty value to be replaced with if $param is not set 1993 * @return string value from $userdata->$param if its not empty, or $ifempty 1994 */ 1995 function scorm_isset($userdata, $param, $ifempty = '') { 1996 if (isset($userdata->$param)) { 1997 return $userdata->$param; 1998 } else { 1999 return $ifempty; 2000 } 2001 }
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 |