|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00017 require_once("$CFG->dirroot/mod/scorm/lib.php"); 00018 require_once("$CFG->libdir/filelib.php"); 00019 00021 define('SCORM_UPDATE_NEVER', '0'); 00022 define('SCORM_UPDATE_EVERYDAY', '2'); 00023 define('SCORM_UPDATE_EVERYTIME', '3'); 00024 00025 define('SCO_ALL', 0); 00026 define('SCO_DATA', 1); 00027 define('SCO_ONLY', 2); 00028 00029 define('GRADESCOES', '0'); 00030 define('GRADEHIGHEST', '1'); 00031 define('GRADEAVERAGE', '2'); 00032 define('GRADESUM', '3'); 00033 00034 define('HIGHESTATTEMPT', '0'); 00035 define('AVERAGEATTEMPT', '1'); 00036 define('FIRSTATTEMPT', '2'); 00037 define('LASTATTEMPT', '3'); 00038 00039 define('TOCJSLINK', 1); 00040 define('TOCFULLURL', 2); 00041 00043 00049 class scorm_package_file_info extends file_info_stored { 00050 public function get_parent() { 00051 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { 00052 return $this->browser->get_file_info($this->context); 00053 } 00054 return parent::get_parent(); 00055 } 00056 public function get_visible_name() { 00057 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { 00058 return $this->topvisiblename; 00059 } 00060 return parent::get_visible_name(); 00061 } 00062 } 00063 00069 function scorm_get_popup_options_array() { 00070 global $CFG; 00071 $cfg_scorm = get_config('scorm'); 00072 00073 return array('resizable'=> isset($cfg_scorm->resizable) ? $cfg_scorm->resizable : 0, 00074 'scrollbars'=> isset($cfg_scorm->scrollbars) ? $cfg_scorm->scrollbars : 0, 00075 'directories'=> isset($cfg_scorm->directories) ? $cfg_scorm->directories : 0, 00076 'location'=> isset($cfg_scorm->location) ? $cfg_scorm->location : 0, 00077 'menubar'=> isset($cfg_scorm->menubar) ? $cfg_scorm->menubar : 0, 00078 'toolbar'=> isset($cfg_scorm->toolbar) ? $cfg_scorm->toolbar : 0, 00079 'status'=> isset($cfg_scorm->status) ? $cfg_scorm->status : 0); 00080 } 00081 00087 function scorm_get_grade_method_array() { 00088 return array (GRADESCOES => get_string('gradescoes', 'scorm'), 00089 GRADEHIGHEST => get_string('gradehighest', 'scorm'), 00090 GRADEAVERAGE => get_string('gradeaverage', 'scorm'), 00091 GRADESUM => get_string('gradesum', 'scorm')); 00092 } 00093 00099 function scorm_get_what_grade_array() { 00100 return array (HIGHESTATTEMPT => get_string('highestattempt', 'scorm'), 00101 AVERAGEATTEMPT => get_string('averageattempt', 'scorm'), 00102 FIRSTATTEMPT => get_string('firstattempt', 'scorm'), 00103 LASTATTEMPT => get_string('lastattempt', 'scorm')); 00104 } 00105 00111 function scorm_get_skip_view_array() { 00112 return array(0 => get_string('never'), 00113 1 => get_string('firstaccess', 'scorm'), 00114 2 => get_string('always')); 00115 } 00116 00122 function scorm_get_hidetoc_array() { 00123 return array(SCORM_TOC_SIDE => get_string('sided', 'scorm'), 00124 SCORM_TOC_HIDDEN => get_string('hidden', 'scorm'), 00125 SCORM_TOC_POPUP => get_string('popupmenu', 'scorm'), 00126 SCORM_TOC_DISABLED => get_string('disabled', 'scorm')); 00127 } 00128 00134 function scorm_get_updatefreq_array() { 00135 return array(SCORM_UPDATE_NEVER => get_string('never'), 00136 SCORM_UPDATE_EVERYDAY => get_string('everyday', 'scorm'), 00137 SCORM_UPDATE_EVERYTIME => get_string('everytime', 'scorm')); 00138 } 00139 00145 function scorm_get_popup_display_array() { 00146 return array(0 => get_string('currentwindow', 'scorm'), 00147 1 => get_string('popup', 'scorm')); 00148 } 00149 00155 function scorm_get_attempts_array() { 00156 $attempts = array(0 => get_string('nolimit', 'scorm'), 00157 1 => get_string('attempt1', 'scorm')); 00158 00159 for ($i=2; $i<=6; $i++) { 00160 $attempts[$i] = get_string('attemptsx', 'scorm', $i); 00161 } 00162 00163 return $attempts; 00164 } 00172 function scorm_parse($scorm, $full) { 00173 global $CFG, $DB; 00174 $cfg_scorm = get_config('scorm'); 00175 00176 if (!isset($scorm->cmid)) { 00177 $cm = get_coursemodule_from_instance('scorm', $scorm->id); 00178 $scorm->cmid = $cm->id; 00179 } 00180 $context = get_context_instance(CONTEXT_MODULE, $scorm->cmid); 00181 $newhash = $scorm->sha1hash; 00182 00183 if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) { 00184 00185 $fs = get_file_storage(); 00186 $packagefile = false; 00187 00188 if ($scorm->scormtype === SCORM_TYPE_LOCAL) { 00189 if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) { 00190 $newhash = $packagefile->get_contenthash(); 00191 } else { 00192 $newhash = null; 00193 } 00194 } else { 00195 if (!$cfg_scorm->allowtypelocalsync) { 00196 // sorry - localsync disabled 00197 return; 00198 } 00199 if ($scorm->reference !== '' and (!$full or $scorm->sha1hash !== sha1($scorm->reference))) { 00200 $fs->delete_area_files($context->id, 'mod_scorm', 'package'); 00201 $file_record = array('contextid'=>$context->id, 'component'=>'mod_scorm', 'filearea'=>'package', 'itemid'=>0, 'filepath'=>'/'); 00202 if ($packagefile = $fs->create_file_from_url($file_record, $scorm->reference, array('calctimeout' => true))) { 00203 $newhash = sha1($scorm->reference); 00204 } else { 00205 $newhash = null; 00206 } 00207 } 00208 } 00209 00210 if ($packagefile) { 00211 if (!$full and $packagefile and $scorm->sha1hash === $newhash) { 00212 if (strpos($scorm->version, 'SCORM') !== false) { 00213 if ($fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) { 00214 // no need to update 00215 return; 00216 } 00217 } else if (strpos($scorm->version, 'AICC') !== false) { 00218 // TODO: add more sanity checks - something really exists in scorm_content area 00219 return; 00220 } 00221 } 00222 00223 // now extract files 00224 $fs->delete_area_files($context->id, 'mod_scorm', 'content'); 00225 00226 $packer = get_file_packer('application/zip'); 00227 $packagefile->extract_to_storage($packer, $context->id, 'mod_scorm', 'content', 0, '/'); 00228 00229 } else if (!$full) { 00230 return; 00231 } 00232 00233 if ($manifest = $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) { 00234 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); 00235 // SCORM 00236 if (!scorm_parse_scorm($scorm, $manifest)) { 00237 $scorm->version = 'ERROR'; 00238 } 00239 } else { 00240 require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php"); 00241 // AICC 00242 if (!scorm_parse_aicc($scorm)) { 00243 $scorm->version = 'ERROR'; 00244 } 00245 $scorm->version = 'AICC'; 00246 } 00247 00248 } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL and $cfg_scorm->allowtypeexternal) { 00249 if (!$full and $scorm->sha1hash === sha1($scorm->reference)) { 00250 return; 00251 } 00252 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); 00253 // SCORM only, AICC can not be external 00254 if (!scorm_parse_scorm($scorm, $scorm->reference)) { 00255 $scorm->version = 'ERROR'; 00256 } 00257 $newhash = sha1($scorm->reference); 00258 00259 } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY and !empty($CFG->repositoryactivate) and $cfg_scorm->allowtypeimsrepository) { 00260 if (!$full and $scorm->sha1hash === sha1($scorm->reference)) { 00261 return; 00262 } 00263 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); 00264 if (!scorm_parse_scorm($scorm, $CFG->repository.substr($scorm->reference, 1).'/imsmanifest.xml')) { 00265 $scorm->version = 'ERROR'; 00266 } 00267 $newhash = sha1($scorm->reference); 00268 } else if ($scorm->scormtype === SCORM_TYPE_AICCURL and $cfg_scorm->allowtypeexternalaicc) { 00269 require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php"); 00270 // AICC 00271 if (!scorm_parse_aicc($scorm)) { 00272 $scorm->version = 'ERROR'; 00273 } 00274 $scorm->version = 'AICC'; 00275 } else { 00276 // sorry, disabled type 00277 return; 00278 } 00279 00280 $scorm->revision++; 00281 $scorm->sha1hash = $newhash; 00282 $DB->update_record('scorm', $scorm); 00283 } 00284 00285 00286 function scorm_array_search($item, $needle, $haystacks, $strict=false) { 00287 if (!empty($haystacks)) { 00288 foreach ($haystacks as $key => $element) { 00289 if ($strict) { 00290 if ($element->{$item} === $needle) { 00291 return $key; 00292 } 00293 } else { 00294 if ($element->{$item} == $needle) { 00295 return $key; 00296 } 00297 } 00298 } 00299 } 00300 return false; 00301 } 00302 00303 function scorm_repeater($what, $times) { 00304 if ($times <= 0) { 00305 return null; 00306 } 00307 $return = ''; 00308 for ($i=0; $i<$times; $i++) { 00309 $return .= $what; 00310 } 00311 return $return; 00312 } 00313 00314 function scorm_external_link($link) { 00315 // check if a link is external 00316 $result = false; 00317 $link = strtolower($link); 00318 if (substr($link, 0, 7) == 'http://') { 00319 $result = true; 00320 } else if (substr($link, 0, 8) == 'https://') { 00321 $result = true; 00322 } else if (substr($link, 0, 4) == 'www.') { 00323 $result = true; 00324 } 00325 return $result; 00326 } 00327 00334 function scorm_get_sco($id, $what=SCO_ALL) { 00335 global $DB; 00336 00337 if ($sco = $DB->get_record('scorm_scoes', array('id'=>$id))) { 00338 $sco = ($what == SCO_DATA) ? new stdClass() : $sco; 00339 if (($what != SCO_ONLY) && ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$id)))) { 00340 foreach ($scodatas as $scodata) { 00341 $sco->{$scodata->name} = $scodata->value; 00342 } 00343 } else if (($what != SCO_ONLY) && (!($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$id))))) { 00344 $sco->parameters = ''; 00345 } 00346 return $sco; 00347 } else { 00348 return false; 00349 } 00350 } 00351 00359 function scorm_get_scoes($id, $organisation=false) { 00360 global $DB; 00361 00362 $organizationsql = ''; 00363 $queryarray = array('scorm'=>$id); 00364 if (!empty($organisation)) { 00365 $queryarray['organization'] = $organisation; 00366 } 00367 if ($scoes = $DB->get_records('scorm_scoes', $queryarray, 'id ASC')) { 00368 // drop keys so that it is a simple array as expected 00369 $scoes = array_values($scoes); 00370 foreach ($scoes as $sco) { 00371 if ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$sco->id))) { 00372 foreach ($scodatas as $scodata) { 00373 $sco->{$scodata->name} = $scodata->value; 00374 } 00375 } 00376 } 00377 return $scoes; 00378 } else { 00379 return false; 00380 } 00381 } 00382 00383 function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $value, $forcecompleted=false) { 00384 global $DB, $CFG; 00385 00386 $id = null; 00387 00388 if ($forcecompleted) { 00389 //TODO - this could be broadened to encompass SCORM 2004 in future 00390 if (($element == 'cmi.core.lesson_status') && ($value == 'incomplete')) { 00391 if ($track = $DB->get_record_select('scorm_scoes_track', 'userid=? AND scormid=? AND scoid=? AND attempt=? AND element=\'cmi.core.score.raw\'', array($userid, $scormid, $scoid, $attempt))) { 00392 $value = 'completed'; 00393 } 00394 } 00395 if ($element == 'cmi.core.score.raw') { 00396 if ($tracktest = $DB->get_record_select('scorm_scoes_track', 'userid=? AND scormid=? AND scoid=? AND attempt=? AND element=\'cmi.core.lesson_status\'', array($userid, $scormid, $scoid, $attempt))) { 00397 if ($tracktest->value == "incomplete") { 00398 $tracktest->value = "completed"; 00399 $DB->update_record('scorm_scoes_track', $tracktest); 00400 } 00401 } 00402 } 00403 } 00404 00405 if ($track = $DB->get_record('scorm_scoes_track', array('userid'=>$userid, 'scormid'=>$scormid, 'scoid'=>$scoid, 'attempt'=>$attempt, 'element'=>$element))) { 00406 if ($element != 'x.start.time' ) { //don't update x.start.time - keep the original value. 00407 $track->value = $value; 00408 $track->timemodified = time(); 00409 $DB->update_record('scorm_scoes_track', $track); 00410 $id = $track->id; 00411 } 00412 } else { 00413 $track->userid = $userid; 00414 $track->scormid = $scormid; 00415 $track->scoid = $scoid; 00416 $track->attempt = $attempt; 00417 $track->element = $element; 00418 $track->value = $value; 00419 $track->timemodified = time(); 00420 $id = $DB->insert_record('scorm_scoes_track', $track); 00421 } 00422 00423 if (strstr($element, '.score.raw') || 00424 (($element == 'cmi.core.lesson_status' || $element == 'cmi.completion_status') && ($track->value == 'completed' || $track->value == 'passed'))) { 00425 $scorm = $DB->get_record('scorm', array('id' => $scormid)); 00426 include_once($CFG->dirroot.'/mod/scorm/lib.php'); 00427 scorm_update_grades($scorm, $userid); 00428 } 00429 00430 return $id; 00431 } 00432 00433 function scorm_get_tracks($scoid, $userid, $attempt='') { 00435 global $CFG, $DB; 00436 00437 if (empty($attempt)) { 00438 if ($scormid = $DB->get_field('scorm_scoes', 'scorm', array('id'=>$scoid))) { 00439 $attempt = scorm_get_last_attempt($scormid, $userid); 00440 } else { 00441 $attempt = 1; 00442 } 00443 } 00444 if ($tracks = $DB->get_records('scorm_scoes_track', array('userid'=>$userid, 'scoid'=>$scoid, 'attempt'=>$attempt), 'element ASC')) { 00445 $usertrack = new stdClass(); 00446 $usertrack->userid = $userid; 00447 $usertrack->scoid = $scoid; 00448 // Defined in order to unify scorm1.2 and scorm2004 00449 $usertrack->score_raw = ''; 00450 $usertrack->status = ''; 00451 $usertrack->total_time = '00:00:00'; 00452 $usertrack->session_time = '00:00:00'; 00453 $usertrack->timemodified = 0; 00454 foreach ($tracks as $track) { 00455 $element = $track->element; 00456 $usertrack->{$element} = $track->value; 00457 switch ($element) { 00458 case 'cmi.core.lesson_status': 00459 case 'cmi.completion_status': 00460 if ($track->value == 'not attempted') { 00461 $track->value = 'notattempted'; 00462 } 00463 $usertrack->status = $track->value; 00464 break; 00465 case 'cmi.core.score.raw': 00466 case 'cmi.score.raw': 00467 $usertrack->score_raw = (float) sprintf('%2.2f', $track->value); 00468 break; 00469 case 'cmi.core.session_time': 00470 case 'cmi.session_time': 00471 $usertrack->session_time = $track->value; 00472 break; 00473 case 'cmi.core.total_time': 00474 case 'cmi.total_time': 00475 $usertrack->total_time = $track->value; 00476 break; 00477 } 00478 if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) { 00479 $usertrack->timemodified = $track->timemodified; 00480 } 00481 } 00482 if (is_array($usertrack)) { 00483 ksort($usertrack); 00484 } 00485 return $usertrack; 00486 } else { 00487 return false; 00488 } 00489 } 00490 00491 00492 /* Find the start and finsh time for a a given SCO attempt 00493 * 00494 * @param int $scormid SCORM Id 00495 * @param int $scoid SCO Id 00496 * @param int $userid User Id 00497 * @param int $attemt Attempt Id 00498 * 00499 * @return object start and finsh time EPOC secods 00500 * 00501 */ 00502 function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) { 00503 global $DB; 00504 00505 $timedata = new stdClass(); 00506 $sql = !empty($scoid) ? "userid=$userid AND scormid=$scormid AND scoid=$scoid AND attempt=$attempt" : "userid=$userid AND scormid=$scormid AND attempt=$attempt"; 00507 $tracks = $DB->get_records_select('scorm_scoes_track', "$sql ORDER BY timemodified ASC"); 00508 if ($tracks) { 00509 $tracks = array_values($tracks); 00510 } 00511 00512 if ($tracks) { 00513 $timedata->start = $tracks[0]->timemodified; 00514 } else { 00515 $timedata->start = false; 00516 } 00517 if ($tracks && $track = array_pop($tracks)) { 00518 $timedata->finish = $track->timemodified; 00519 } else { 00520 $timedata->finish = $timedata->start; 00521 } 00522 return $timedata; 00523 } 00524 00525 00526 function scorm_get_user_data($userid) { 00527 global $DB; 00530 00531 return $DB->get_record('user', array('id'=>$userid), user_picture::fields()); 00532 } 00533 00534 function scorm_grade_user_attempt($scorm, $userid, $attempt=1) { 00535 global $DB; 00536 $attemptscore = null; 00537 $attemptscore->scoes = 0; 00538 $attemptscore->values = 0; 00539 $attemptscore->max = 0; 00540 $attemptscore->sum = 0; 00541 $attemptscore->lastmodify = 0; 00542 00543 if (!$scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) { 00544 return null; 00545 } 00546 00547 foreach ($scoes as $sco) { 00548 if ($userdata=scorm_get_tracks($sco->id, $userid, $attempt)) { 00549 if (($userdata->status == 'completed') || ($userdata->status == 'passed')) { 00550 $attemptscore->scoes++; 00551 } 00552 if (!empty($userdata->score_raw) || (isset($scorm->type) && $scorm->type=='sco' && isset($userdata->score_raw))) { 00553 $attemptscore->values++; 00554 $attemptscore->sum += $userdata->score_raw; 00555 $attemptscore->max = ($userdata->score_raw > $attemptscore->max)?$userdata->score_raw:$attemptscore->max; 00556 if (isset($userdata->timemodified) && ($userdata->timemodified > $attemptscore->lastmodify)) { 00557 $attemptscore->lastmodify = $userdata->timemodified; 00558 } else { 00559 $attemptscore->lastmodify = 0; 00560 } 00561 } 00562 } 00563 } 00564 switch ($scorm->grademethod) { 00565 case GRADEHIGHEST: 00566 $score = (float) $attemptscore->max; 00567 break; 00568 case GRADEAVERAGE: 00569 if ($attemptscore->values > 0) { 00570 $score = $attemptscore->sum/$attemptscore->values; 00571 } else { 00572 $score = 0; 00573 } 00574 break; 00575 case GRADESUM: 00576 $score = $attemptscore->sum; 00577 break; 00578 case GRADESCOES: 00579 $score = $attemptscore->scoes; 00580 break; 00581 default: 00582 $score = $attemptscore->max; // Remote Learner GRADEHIGHEST is default 00583 } 00584 00585 return $score; 00586 } 00587 00588 function scorm_grade_user($scorm, $userid) { 00589 00590 // ensure we dont grade user beyond $scorm->maxattempt settings 00591 $lastattempt = scorm_get_last_attempt($scorm->id, $userid); 00592 if ($scorm->maxattempt != 0 && $lastattempt >= $scorm->maxattempt) { 00593 $lastattempt = $scorm->maxattempt; 00594 } 00595 00596 switch ($scorm->whatgrade) { 00597 case FIRSTATTEMPT: 00598 return scorm_grade_user_attempt($scorm, $userid, 1); 00599 break; 00600 case LASTATTEMPT: 00601 return scorm_grade_user_attempt($scorm, $userid, scorm_get_last_completed_attempt($scorm->id, $userid)); 00602 break; 00603 case HIGHESTATTEMPT: 00604 $maxscore = 0; 00605 for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { 00606 $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt); 00607 $maxscore = $attemptscore > $maxscore ? $attemptscore: $maxscore; 00608 } 00609 return $maxscore; 00610 00611 break; 00612 case AVERAGEATTEMPT: 00613 $attemptcount = scorm_get_attempt_count($userid, $scorm, true); 00614 if (empty($attemptcount)) { 00615 return 0; 00616 } else { 00617 $attemptcount = count($attemptcount); 00618 } 00619 $lastattempt = scorm_get_last_attempt($scorm->id, $userid); 00620 $sumscore = 0; 00621 for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { 00622 $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt); 00623 $sumscore += $attemptscore; 00624 } 00625 00626 return round($sumscore / $attemptcount); 00627 break; 00628 } 00629 } 00630 00631 function scorm_count_launchable($scormid, $organization='') { 00632 global $DB; 00633 00634 $sqlorganization = ''; 00635 $params = array($scormid); 00636 if (!empty($organization)) { 00637 $sqlorganization = " AND organization=?"; 00638 $params[] = $organization; 00639 } 00640 return $DB->count_records_select('scorm_scoes', "scorm = ? $sqlorganization AND ".$DB->sql_isnotempty('scorm_scoes', 'launch', false, true), $params); 00641 } 00642 00643 function scorm_get_last_attempt($scormid, $userid) { 00644 global $DB; 00645 00647 if ($lastattempt = $DB->get_record('scorm_scoes_track', array('userid'=>$userid, 'scormid'=>$scormid), 'max(attempt) as a')) { 00648 if (empty($lastattempt->a)) { 00649 return '1'; 00650 } else { 00651 return $lastattempt->a; 00652 } 00653 } else { 00654 return false; 00655 } 00656 } 00657 00658 function scorm_get_last_completed_attempt($scormid, $userid) { 00659 global $DB; 00660 00662 if ($lastattempt = $DB->get_record_select('scorm_scoes_track', "userid = ? AND scormid = ? AND (value='completed' OR value='passed')", array($userid, $scormid), 'max(attempt) as a')) { 00663 if (empty($lastattempt->a)) { 00664 return '1'; 00665 } else { 00666 return $lastattempt->a; 00667 } 00668 } else { 00669 return false; 00670 } 00671 } 00672 00673 function scorm_course_format_display($user, $course) { 00674 global $CFG, $DB, $PAGE, $OUTPUT; 00675 00676 $strupdate = get_string('update'); 00677 $context = get_context_instance(CONTEXT_COURSE, $course->id); 00678 00679 echo '<div class="mod-scorm">'; 00680 if ($scorms = get_all_instances_in_course('scorm', $course)) { 00681 // The module SCORM activity with the least id is the course 00682 $scorm = current($scorms); 00683 if (! $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id)) { 00684 print_error('invalidcoursemodule'); 00685 } 00686 $contextmodule = get_context_instance(CONTEXT_MODULE, $cm->id); 00687 if ((has_capability('mod/scorm:skipview', $contextmodule))) { 00688 scorm_simple_play($scorm, $user, $contextmodule, $cm->id); 00689 } 00690 $colspan = ''; 00691 $headertext = '<table width="100%"><tr><td class="title">'.get_string('name').': <b>'.format_string($scorm->name).'</b>'; 00692 if (has_capability('moodle/course:manageactivities', $context)) { 00693 if ($PAGE->user_is_editing()) { 00694 // Display update icon 00695 $path = $CFG->wwwroot.'/course'; 00696 $headertext .= '<span class="commands">'. 00697 '<a title="'.$strupdate.'" href="'.$path.'/mod.php?update='.$cm->id.'&sesskey='.sesskey().'">'. 00698 '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="'.$strupdate.'" /></a></span>'; 00699 } 00700 $headertext .= '</td>'; 00701 // Display report link 00702 $trackedusers = $DB->get_record('scorm_scoes_track', array('scormid'=>$scorm->id), 'count(distinct(userid)) as c'); 00703 if ($trackedusers->c > 0) { 00704 $headertext .= '<td class="reportlink">'. 00705 '<a href="'.$CFG->wwwroot.'/mod/scorm/report.php?id='.$cm->id.'">'. 00706 get_string('viewallreports', 'scorm', $trackedusers->c).'</a>'; 00707 } else { 00708 $headertext .= '<td class="reportlink">'.get_string('noreports', 'scorm'); 00709 } 00710 $colspan = ' colspan="2"'; 00711 } 00712 $headertext .= '</td></tr><tr><td'.$colspan.'>'.get_string('summary').':<br />'.format_module_intro('scorm', $scorm, $scorm->coursemodule).'</td></tr></table>'; 00713 echo $OUTPUT->box($headertext, 'generalbox boxwidthwide'); 00714 scorm_view_display($user, $scorm, 'view.php?id='.$course->id, $cm); 00715 } else { 00716 if (has_capability('moodle/course:update', $context)) { 00717 // Create a new activity 00718 $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'section'=>'0', 'sesskey'=>sesskey(),'add'=>'scorm')); 00719 redirect($url); 00720 } else { 00721 echo $OUTPUT->notification('Could not find a scorm course here'); 00722 } 00723 } 00724 echo '</div>'; 00725 } 00726 00727 function scorm_view_display ($user, $scorm, $action, $cm) { 00728 global $CFG, $DB, $PAGE, $OUTPUT; 00729 00730 if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) { 00731 scorm_parse($scorm, false); 00732 } 00733 00734 $organization = optional_param('organization', '', PARAM_INT); 00735 00736 if ($scorm->displaycoursestructure == 1) { 00737 echo $OUTPUT->box_start('generalbox boxaligncenter toc'); 00738 ?> 00739 <div class="structurehead"><?php print_string('contents', 'scorm') ?></div> 00740 <?php 00741 } 00742 if (empty($organization)) { 00743 $organization = $scorm->launch; 00744 } 00745 if ($orgs = $DB->get_records_select_menu('scorm_scoes', 'scorm = ? AND '. 00746 $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '. 00747 $DB->sql_isempty('scorm_scoes', 'organization', false, false), 00748 array($scorm->id), 'id', 'id,title')) { 00749 if (count($orgs) > 1) { 00750 $select = new single_select(new moodle_url($action), 'organization', $orgs, $organization, null); 00751 $select->label = get_string('organizations', 'scorm'); 00752 $select->class = 'scorm-center'; 00753 echo $OUTPUT->render($select); 00754 } 00755 } 00756 $orgidentifier = ''; 00757 if ($sco = scorm_get_sco($organization, SCO_ONLY)) { 00758 if (($sco->organization == '') && ($sco->launch == '')) { 00759 $orgidentifier = $sco->identifier; 00760 } else { 00761 $orgidentifier = $sco->organization; 00762 } 00763 } 00764 00765 $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe 00766 if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) { 00767 $scorm->version = 'scorm_12'; 00768 } 00769 require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php'); 00770 00771 $result = scorm_get_toc($user, $scorm, $cm->id, TOCFULLURL, $orgidentifier); 00772 $incomplete = $result->incomplete; 00773 00774 // do we want the TOC to be displayed? 00775 if ($scorm->displaycoursestructure == 1) { 00776 echo $result->toc; 00777 echo $OUTPUT->box_end(); 00778 } 00779 00780 // is this the first attempt ? 00781 $attemptcount = scorm_get_attempt_count($user->id, $scorm); 00782 00783 // do not give the player launch FORM if the SCORM object is locked after the final attempt 00784 if ($scorm->lastattemptlock == 0 || $result->attemptleft > 0) { 00785 ?> 00786 <div class="scorm-center"> 00787 <form id="theform" method="post" action="<?php echo $CFG->wwwroot ?>/mod/scorm/player.php"> 00788 <?php 00789 if ($scorm->hidebrowse == 0) { 00790 print_string('mode', 'scorm'); 00791 echo ': <input type="radio" id="b" name="mode" value="browse" /><label for="b">'.get_string('browse', 'scorm').'</label>'."\n"; 00792 echo '<input type="radio" id="n" name="mode" value="normal" checked="checked" /><label for="n">'.get_string('normal', 'scorm')."</label>\n"; 00793 } else { 00794 echo '<input type="hidden" name="mode" value="normal" />'."\n"; 00795 } 00796 if ($scorm->forcenewattempt == 1) { 00797 if ($incomplete === false) { 00798 echo '<input type="hidden" name="newattempt" value="on" />'."\n"; 00799 } 00800 } else if (!empty($attemptcount) && ($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) { 00801 ?> 00802 <br /> 00803 <input type="checkbox" id="a" name="newattempt" /> 00804 <label for="a"><?php print_string('newattempt', 'scorm') ?></label> 00805 <?php 00806 } 00807 ?> 00808 <br /> 00809 <input type="hidden" name="scoid"/> 00810 <input type="hidden" name="cm" value="<?php echo $cm->id ?>"/> 00811 <input type="hidden" name="currentorg" value="<?php echo $orgidentifier ?>" /> 00812 <input type="submit" value="<?php print_string('enter', 'scorm') ?>" /> 00813 </form> 00814 </div> 00815 <?php 00816 } 00817 } 00818 00819 function scorm_simple_play($scorm, $user, $context, $cmid) { 00820 global $DB; 00821 00822 $result = false; 00823 00824 if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) { 00825 scorm_parse($scorm, false); 00826 } 00827 if (has_capability('mod/scorm:viewreport', $context)) { //if this user can view reports, don't skipview so they can see links to reports. 00828 return $result; 00829 } 00830 00831 $scoes = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id), 'id', 'id'); 00832 00833 if ($scoes) { 00834 $orgidentifier = ''; 00835 if ($sco = scorm_get_sco($scorm->launch, SCO_ONLY)) { 00836 if (($sco->organization == '') && ($sco->launch == '')) { 00837 $orgidentifier = $sco->identifier; 00838 } else { 00839 $orgidentifier = $sco->organization; 00840 } 00841 } 00842 if ($scorm->skipview >= 1) { 00843 $sco = current($scoes); 00844 $url = new moodle_url('/mod/scorm/player.php', array('a' => $scorm->id, 00845 'currentorg'=>$orgidentifier, 00846 'scoid'=>$sco->id)); 00847 if ($scorm->skipview == 2 || scorm_get_tracks($sco->id, $user->id) === false) { 00848 if (!empty($scorm->forcenewattempt)) { 00849 $result = scorm_get_toc($user, $scorm, $cmid, TOCFULLURL, $orgidentifier); 00850 if ($result->incomplete === false) { 00851 $url->param('newattempt','on'); 00852 } 00853 } 00854 redirect($url); 00855 } 00856 } 00857 } 00858 return $result; 00859 } 00860 00861 function scorm_get_count_users($scormid, $groupingid=null) { 00862 global $CFG, $DB; 00863 00864 if (!empty($groupingid)) { 00865 $sql = "SELECT COUNT(DISTINCT st.userid) 00866 FROM {scorm_scoes_track} st 00867 INNER JOIN {groups_members} gm ON st.userid = gm.userid 00868 INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid 00869 WHERE st.scormid = ? AND gg.groupingid = ? 00870 "; 00871 $params = array($scormid, $groupingid); 00872 } else { 00873 $sql = "SELECT COUNT(DISTINCT st.userid) 00874 FROM {scorm_scoes_track} st 00875 WHERE st.scormid = ? 00876 "; 00877 $params = array($scormid); 00878 } 00879 00880 return ($DB->count_records_sql($sql, $params)); 00881 } 00882 00892 function scorm_reconstitute_array_element($sversion, $userdata, $element_name, $children) { 00893 // reconstitute comments_from_learner and comments_from_lms 00894 $current = ''; 00895 $current_subelement = ''; 00896 $current_sub = ''; 00897 $count = 0; 00898 $count_sub = 0; 00899 $scormseperator = '_'; 00900 if (scorm_version_check($sversion, SCORM_13)) { //scorm 1.3 elements use a . instead of an _ 00901 $scormseperator = '.'; 00902 } 00903 // filter out the ones we want 00904 $element_list = array(); 00905 foreach ($userdata as $element => $value) { 00906 if (substr($element, 0, strlen($element_name)) == $element_name) { 00907 $element_list[$element] = $value; 00908 } 00909 } 00910 00911 // sort elements in .n array order 00912 uksort($element_list, "scorm_element_cmp"); 00913 00914 // generate JavaScript 00915 foreach ($element_list as $element => $value) { 00916 if (scorm_version_check($sversion, SCORM_13)) { 00917 $element = preg_replace('/\.(\d+)\./', ".N\$1.", $element); 00918 preg_match('/\.(N\d+)\./', $element, $matches); 00919 } else { 00920 $element = preg_replace('/\.(\d+)\./', "_\$1.", $element); 00921 preg_match('/\_(\d+)\./', $element, $matches); 00922 } 00923 if (count($matches) > 0 && $current != $matches[1]) { 00924 if ($count_sub > 0) { 00925 echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; 00926 } 00927 $current = $matches[1]; 00928 $count++; 00929 $current_subelement = ''; 00930 $current_sub = ''; 00931 $count_sub = 0; 00932 $end = strpos($element, $matches[1])+strlen($matches[1]); 00933 $subelement = substr($element, 0, $end); 00934 echo ' '.$subelement." = new Object();\n"; 00935 // now add the children 00936 foreach ($children as $child) { 00937 echo ' '.$subelement.".".$child." = new Object();\n"; 00938 echo ' '.$subelement.".".$child."._children = ".$child."_children;\n"; 00939 } 00940 } 00941 00942 // now - flesh out the second level elements if there are any 00943 if (scorm_version_check($sversion, SCORM_13)) { 00944 $element = preg_replace('/(.*?\.N\d+\..*?)\.(\d+)\./', "\$1.N\$2.", $element); 00945 preg_match('/.*?\.N\d+\.(.*?)\.(N\d+)\./', $element, $matches); 00946 } else { 00947 $element = preg_replace('/(.*?\_\d+\..*?)\.(\d+)\./', "\$1_\$2.", $element); 00948 preg_match('/.*?\_\d+\.(.*?)\_(\d+)\./', $element, $matches); 00949 } 00950 00951 // check the sub element type 00952 if (count($matches) > 0 && $current_subelement != $matches[1]) { 00953 if ($count_sub > 0) { 00954 echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; 00955 } 00956 $current_subelement = $matches[1]; 00957 $current_sub = ''; 00958 $count_sub = 0; 00959 $end = strpos($element, $matches[1])+strlen($matches[1]); 00960 $subelement = substr($element, 0, $end); 00961 echo ' '.$subelement." = new Object();\n"; 00962 } 00963 00964 // now check the subelement subscript 00965 if (count($matches) > 0 && $current_sub != $matches[2]) { 00966 $current_sub = $matches[2]; 00967 $count_sub++; 00968 $end = strrpos($element, $matches[2])+strlen($matches[2]); 00969 $subelement = substr($element, 0, $end); 00970 echo ' '.$subelement." = new Object();\n"; 00971 } 00972 00973 echo ' '.$element.' = \''.$value."';\n"; 00974 } 00975 if ($count_sub > 0) { 00976 echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; 00977 } 00978 if ($count > 0) { 00979 echo ' '.$element_name.'._count = '.$count.";\n"; 00980 } 00981 } 00982 00990 function scorm_element_cmp($a, $b) { 00991 preg_match('/.*?(\d+)\./', $a, $matches); 00992 $left = intval($matches[1]); 00993 preg_match('/.?(\d+)\./', $b, $matches); 00994 $right = intval($matches[1]); 00995 if ($left < $right) { 00996 return -1; // smaller 00997 } else if ($left > $right) { 00998 return 1; // bigger 00999 } else { 01000 // look for a second level qualifier eg cmi.interactions_0.correct_responses_0.pattern 01001 if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $a, $matches)) { 01002 $leftterm = intval($matches[2]); 01003 $left = intval($matches[3]); 01004 if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $b, $matches)) { 01005 $rightterm = intval($matches[2]); 01006 $right = intval($matches[3]); 01007 if ($leftterm < $rightterm) { 01008 return -1; // smaller 01009 } else if ($leftterm > $rightterm) { 01010 return 1; // bigger 01011 } else { 01012 if ($left < $right) { 01013 return -1; // smaller 01014 } else if ($left > $right) { 01015 return 1; // bigger 01016 } 01017 } 01018 } 01019 } 01020 // fall back for no second level matches or second level matches are equal 01021 return 0; // equal to 01022 } 01023 } 01024 01032 function scorm_get_attempt_status($user, $scorm, $cm='') { 01033 global $DB, $PAGE, $OUTPUT; 01034 01035 $attempts = scorm_get_attempt_count($user->id, $scorm, true); 01036 if (empty($attempts)) { 01037 $attemptcount = 0; 01038 } else { 01039 $attemptcount = count($attempts); 01040 } 01041 01042 $result = '<p>'.get_string('noattemptsallowed', 'scorm').': '; 01043 if ($scorm->maxattempt > 0) { 01044 $result .= $scorm->maxattempt . '<br />'; 01045 } else { 01046 $result .= get_string('unlimited').'<br />'; 01047 } 01048 $result .= get_string('noattemptsmade', 'scorm').': ' . $attemptcount . '<br />'; 01049 01050 if ($scorm->maxattempt == 1) { 01051 switch ($scorm->grademethod) { 01052 case GRADEHIGHEST: 01053 $grademethod = get_string('gradehighest', 'scorm'); 01054 break; 01055 case GRADEAVERAGE: 01056 $grademethod = get_string('gradeaverage', 'scorm'); 01057 break; 01058 case GRADESUM: 01059 $grademethod = get_string('gradesum', 'scorm'); 01060 break; 01061 case GRADESCOES: 01062 $grademethod = get_string('gradescoes', 'scorm'); 01063 break; 01064 } 01065 } else { 01066 switch ($scorm->whatgrade) { 01067 case HIGHESTATTEMPT: 01068 $grademethod = get_string('highestattempt', 'scorm'); 01069 break; 01070 case AVERAGEATTEMPT: 01071 $grademethod = get_string('averageattempt', 'scorm'); 01072 break; 01073 case FIRSTATTEMPT: 01074 $grademethod = get_string('firstattempt', 'scorm'); 01075 break; 01076 case LASTATTEMPT: 01077 $grademethod = get_string('lastattempt', 'scorm'); 01078 break; 01079 } 01080 } 01081 01082 if (!empty($attempts)) { 01083 $i = 1; 01084 foreach ($attempts as $attempt) { 01085 $gradereported = scorm_grade_user_attempt($scorm, $user->id, $attempt->attemptnumber); 01086 if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) { 01087 $gradereported = $gradereported/$scorm->maxgrade; 01088 $gradereported = number_format($gradereported*100, 0) .'%'; 01089 } 01090 $result .= get_string('gradeforattempt', 'scorm').' ' . $i . ': ' . $gradereported .'<br />'; 01091 $i++; 01092 } 01093 } 01094 $calculatedgrade = scorm_grade_user($scorm, $user->id); 01095 if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) { 01096 $calculatedgrade = $calculatedgrade/$scorm->maxgrade; 01097 $calculatedgrade = number_format($calculatedgrade*100, 0) .'%'; 01098 } 01099 $result .= get_string('grademethod', 'scorm'). ': ' . $grademethod; 01100 if (empty($attempts)) { 01101 $result .= '<br />' . get_string('gradereported', 'scorm') . ': ' . get_string('none') . '<br />'; 01102 } else { 01103 $result .= '<br />' . get_string('gradereported', 'scorm') . ': ' . $calculatedgrade . '<br />'; 01104 } 01105 $result .= '</p>'; 01106 if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) { 01107 $result .= '<p><font color="#cc0000">'.get_string('exceededmaxattempts', 'scorm').'</font></p>'; 01108 } 01109 if (!empty($cm)) { 01110 $context = context_module::instance($cm->id); 01111 if (has_capability('mod/scorm:deleteownresponses', $context) && 01112 $DB->record_exists('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id))) { 01113 //check to see if any data is stored for this user: 01114 $deleteurl = new moodle_url($PAGE->url, array('action'=>'delete', 'sesskey' => sesskey())); 01115 $result .= $OUTPUT->single_button($deleteurl, get_string('deleteallattempts', 'scorm')); 01116 } 01117 } 01118 01119 01120 return $result; 01121 } 01122 01131 function scorm_get_attempt_count($userid, $scorm, $attempts_only=false) { 01132 global $DB; 01133 $attemptcount = 0; 01134 $element = 'cmi.core.score.raw'; 01135 if ($scorm->grademethod == GRADESCOES) { 01136 $element = 'cmi.core.lesson_status'; 01137 } 01138 if (scorm_version_check($scorm->version, SCORM_13)) { 01139 $element = 'cmi.score.raw'; 01140 } 01141 $attempts = $DB->get_records_select('scorm_scoes_track', "element=? AND userid=? AND scormid=?", array($element, $userid, $scorm->id), 'attempt', 'DISTINCT attempt AS attemptnumber'); 01142 if ($attempts_only) { 01143 return $attempts; 01144 } 01145 if (!empty($attempts)) { 01146 $attemptcount = count($attempts); 01147 } 01148 return $attemptcount; 01149 } 01150 01157 function scorm_debugging($scorm) { 01158 global $CFG, $USER; 01159 $cfg_scorm = get_config('scorm'); 01160 01161 if (!$cfg_scorm->allowapidebug) { 01162 return false; 01163 } 01164 $identifier = $USER->username.':'.$scorm->name; 01165 $test = $cfg_scorm->apidebugmask; 01166 // check the regex is only a short list of safe characters 01167 if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) { 01168 return false; 01169 } 01170 $res = false; 01171 eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;'); 01172 return $res; 01173 } 01174 01183 function scorm_delete_responses($attemptids, $scorm) { 01184 if (!is_array($attemptids) || empty($attemptids)) { 01185 return false; 01186 } 01187 01188 foreach ($attemptids as $num => $attemptid) { 01189 if (empty($attemptid)) { 01190 unset($attemptids[$num]); 01191 } 01192 } 01193 01194 foreach ($attemptids as $attempt) { 01195 $keys = explode(':', $attempt); 01196 if (count($keys) == 2) { 01197 $userid = clean_param($keys[0], PARAM_INT); 01198 $attemptid = clean_param($keys[1], PARAM_INT); 01199 if (!$userid || !$attemptid || !scorm_delete_attempt($userid, $scorm, $attemptid)) { 01200 return false; 01201 } 01202 } else { 01203 return false; 01204 } 01205 } 01206 return true; 01207 } 01208 01218 function scorm_delete_attempt($userid, $scorm, $attemptid) { 01219 global $DB; 01220 01221 $DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attemptid)); 01222 include_once('lib.php'); 01223 scorm_update_grades($scorm, $userid, true); 01224 return true; 01225 } 01226 01233 function scorm_format_duration($duration) { 01234 // fetch date/time strings 01235 $stryears = get_string('years'); 01236 $strmonths = get_string('nummonths'); 01237 $strdays = get_string('days'); 01238 $strhours = get_string('hours'); 01239 $strminutes = get_string('minutes'); 01240 $strseconds = get_string('seconds'); 01241 01242 if ($duration[0] == 'P') { 01243 // if timestamp starts with 'P' - it's a SCORM 2004 format 01244 // this regexp discards empty sections, takes Month/Minute ambiguity into consideration, 01245 // and outputs filled sections, discarding leading zeroes and any format literals 01246 // also saves the only zero before seconds decimals (if there are any) and discards decimals if they are zero 01247 $pattern = array( '#([A-Z])0+Y#', '#([A-Z])0+M#', '#([A-Z])0+D#', '#P(|\d+Y)0*(\d+)M#', '#0*(\d+)Y#', '#0*(\d+)D#', '#P#', 01248 '#([A-Z])0+H#', '#([A-Z])[0.]+S#', '#\.0+S#', '#T(|\d+H)0*(\d+)M#', '#0*(\d+)H#', '#0+\.(\d+)S#', '#0*([\d.]+)S#', '#T#' ); 01249 $replace = array( '$1', '$1', '$1', '$1$2 '.$strmonths.' ', '$1 '.$stryears.' ', '$1 '.$strdays.' ', '', 01250 '$1', '$1', 'S', '$1$2 '.$strminutes.' ', '$1 '.$strhours.' ', '0.$1 '.$strseconds, '$1 '.$strseconds, ''); 01251 } else { 01252 // else we have SCORM 1.2 format there 01253 // first convert the timestamp to some SCORM 2004-like format for conveniency 01254 $duration = preg_replace('#^(\d+):(\d+):([\d.]+)$#', 'T$1H$2M$3S', $duration); 01255 // then convert in the same way as SCORM 2004 01256 $pattern = array( '#T0+H#', '#([A-Z])0+M#', '#([A-Z])[0.]+S#', '#\.0+S#', '#0*(\d+)H#', '#0*(\d+)M#', '#0+\.(\d+)S#', '#0*([\d.]+)S#', '#T#' ); 01257 $replace = array( 'T', '$1', '$1', 'S', '$1 '.$strhours.' ', '$1 '.$strminutes.' ', '0.$1 '.$strseconds, '$1 '.$strseconds, '' ); 01258 } 01259 01260 $result = preg_replace($pattern, $replace, $duration); 01261 01262 return $result; 01263 } 01264 01265 function scorm_get_toc($user,$scorm,$cmid,$toclink=TOCJSLINK,$currentorg='',$scoid='',$mode='normal',$attempt='',$play=false, $tocheader=false) { 01266 global $CFG, $DB, $PAGE, $OUTPUT; 01267 01268 $modestr = ''; 01269 if ($mode == 'browse') { 01270 $modestr = '&mode='.$mode; 01271 } 01272 01273 $result = new stdClass(); 01274 if ($tocheader) { 01275 $result->toc = '<div id="scorm_layout">'; 01276 $result->toc .= '<div id="scorm_toc">'; 01277 $result->toc .= '<div id="scorm_tree">'; 01278 } 01279 $result->toc .= '<ul>'; 01280 $tocmenus = array(); 01281 $result->prerequisites = true; 01282 $incomplete = false; 01283 01284 // 01285 // Get the current organization infos 01286 // 01287 if (!empty($currentorg)) { 01288 if (($organizationtitle = $DB->get_field('scorm_scoes','title', array('scorm'=>$scorm->id,'identifier'=>$currentorg))) != '') { 01289 if ($play) { 01290 $result->toctitle = "$organizationtitle"; 01291 } 01292 else { 01293 $result->toc .= "\t<li>$organizationtitle</li>\n"; 01294 } 01295 $tocmenus[] = $organizationtitle; 01296 } 01297 } 01298 01299 // 01300 // If not specified retrieve the last attempt number 01301 // 01302 if (empty($attempt)) { 01303 $attempt = scorm_get_attempt_count($user->id, $scorm); 01304 } 01305 $result->attemptleft = $scorm->maxattempt == 0 ? 1 : $scorm->maxattempt - $attempt; 01306 if ($scoes = scorm_get_scoes($scorm->id, $currentorg)){ 01307 // 01308 // Retrieve user tracking data for each learning object 01309 // 01310 $usertracks = array(); 01311 foreach ($scoes as $sco) { 01312 if (!empty($sco->launch)) { 01313 if ($usertrack = scorm_get_tracks($sco->id,$user->id,$attempt)) { 01314 if ($usertrack->status == '') { 01315 $usertrack->status = 'notattempted'; 01316 } 01317 $usertracks[$sco->identifier] = $usertrack; 01318 } 01319 } 01320 } 01321 01322 $level=0; 01323 $sublist=1; 01324 $previd = 0; 01325 $nextid = 0; 01326 $findnext = false; 01327 $parents[$level]='/'; 01328 $prevsco = ''; 01329 foreach ($scoes as $pos => $sco) { 01330 $isvisible = false; 01331 $sco->title = $sco->title; 01332 if (!isset($sco->isvisible) || (isset($sco->isvisible) && ($sco->isvisible == 'true'))) { 01333 $isvisible = true; 01334 } 01335 if ($parents[$level] != $sco->parent) { 01336 if ($newlevel = array_search($sco->parent,$parents)) { 01337 for ($i=0; $i<($level-$newlevel); $i++) { 01338 $result->toc .= "\t\t</li></ul></li>\n"; 01339 } 01340 $level = $newlevel; 01341 } else { 01342 $i = $level; 01343 $closelist = ''; 01344 while (($i > 0) && ($parents[$level] != $sco->parent)) { 01345 if ($i === 1 && $level > 1) { 01346 $closelist .= "\t\t</ul></li>\n"; 01347 } else { 01348 $closelist .= "\t</li></ul></li>\n"; 01349 } 01350 $i--; 01351 } 01352 if (($i == 0) && ($sco->parent != $currentorg)) { 01353 $result->toc .= "\n\t<ul>\n"; 01354 $level++; 01355 } else { 01356 $result->toc .= $closelist; 01357 $level = $i; 01358 } 01359 $parents[$level] = $sco->parent; 01360 } 01361 } 01362 if ($isvisible) { 01363 $result->toc .= "<li>"; 01364 } 01365 if (isset($scoes[$pos+1])) { 01366 $nextsco = $scoes[$pos+1]; 01367 } else { 01368 $nextsco = false; 01369 } 01370 $nextisvisible = false; 01371 if (($nextsco !== false) && (!isset($nextsco->isvisible) || (isset($nextsco->isvisible) && ($nextsco->isvisible == 'true')))) { 01372 $nextisvisible = true; 01373 } 01374 if ($nextisvisible && ($nextsco !== false) && ($sco->parent != $nextsco->parent) && 01375 (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) { 01376 $sublist++; 01377 } 01378 if (empty($sco->title)) { 01379 $sco->title = $sco->identifier; 01380 } 01381 if ($isvisible) { 01382 if (!empty($sco->launch)) { 01383 $score = ''; 01384 if (empty($scoid) && ($mode != 'normal')) { 01385 $scoid = $sco->id; 01386 } 01387 if (isset($usertracks[$sco->identifier])) { 01388 $usertrack = $usertracks[$sco->identifier]; 01389 $strstatus = get_string($usertrack->status,'scorm'); 01390 if ($sco->scormtype == 'sco') { 01391 $statusicon = '<img src="'.$OUTPUT->pix_url($usertrack->status, 'scorm').'" alt="'.$strstatus.'" title="'.$strstatus.'" />'; 01392 } else { 01393 $statusicon = '<img src="'.$OUTPUT->pix_url('assetc', 'scorm').'" alt="'.get_string('assetlaunched','scorm').'" title="'.get_string('assetlaunched','scorm').'" />'; 01394 } 01395 01396 if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) { 01397 $incomplete = true; 01398 if ($play && empty($scoid)) { 01399 $scoid = $sco->id; 01400 } 01401 } 01402 if ($usertrack->score_raw != '' && has_capability('mod/scorm:viewscores', get_context_instance(CONTEXT_MODULE,$cmid))) { 01403 $score = '('.get_string('score','scorm').': '.$usertrack->score_raw.')'; 01404 } 01405 $strsuspended = get_string('suspended','scorm'); 01406 $exitvar = 'cmi.core.exit'; 01407 if (scorm_version_check($scorm->version, SCORM_13)) { 01408 $exitvar = 'cmi.exit'; 01409 } 01410 if ($incomplete && isset($usertrack->{$exitvar}) && ($usertrack->{$exitvar} == 'suspend')) { 01411 $statusicon = '<img src="'.$OUTPUT->pix_url('suspend', 'scorm').'" alt="'.$strstatus.' - '.$strsuspended.'" title="'.$strstatus.' - '.$strsuspended.'" />'; 01412 } 01413 } else { 01414 if ($play && empty($scoid)) { 01415 $scoid = $sco->id; 01416 } 01417 $incomplete = true; 01418 if ($sco->scormtype == 'sco') { 01419 $statusicon = '<img src="'.$OUTPUT->pix_url('notattempted', 'scorm').'" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />'; 01420 } else { 01421 $statusicon = '<img src="'.$OUTPUT->pix_url('asset', 'scorm').'" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />'; 01422 } 01423 } 01424 if ($sco->id == $scoid) { 01425 $findnext = true; 01426 } 01427 01428 if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1) && ($nextsco!==false) && (!$findnext)) { 01429 if (!empty($sco->launch)) { 01430 $previd = $sco->id; 01431 } 01432 } 01433 if (scorm_version_check($scorm->version, SCORM_13)) { 01434 require_once($CFG->dirroot.'/mod/scorm/datamodels/sequencinglib.php'); 01435 $prereq = scorm_seq_evaluate($sco->id,$usertracks); 01436 } else { 01437 //TODO: split check for sco->prerequisites only for AICC as I think that's the only case it's set. 01438 $prereq = empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites,$usertracks); 01439 } 01440 if ($prereq) { 01441 if ($sco->id == $scoid) { 01442 $result->prerequisites = true; 01443 } 01444 if (!empty($prevsco) && scorm_version_check($scorm->version, SCORM_13) && !empty($prevsco->hidecontinue)) { 01445 $result->toc .= '<span>'.$statusicon.' '.format_string($sco->title).'</span>'; 01446 } else if ($toclink == TOCFULLURL) { //display toc with urls for structure page 01447 $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&currentorg='.$currentorg.$modestr.'&scoid='.$sco->id; 01448 $result->toc .= $statusicon.' <a href="'.$url.'">'.format_string($sco->title).'</a>'.$score."\n"; 01449 } else { //display toc for inside scorm player 01450 if ($sco->launch) { 01451 $link = 'a='.$scorm->id.'&scoid='.$sco->id.'¤torg='.$currentorg.$modestr.'&attempt='.$attempt; 01452 $result->toc .= '<a title="'.$link.'">'.$statusicon.' '.format_string($sco->title).' '.$score.'</a>'; 01453 } else { 01454 $result->toc .= '<span>'.$statusicon.' '.format_string($sco->title).'</span>'; 01455 } 01456 } 01457 $tocmenus[$sco->id] = scorm_repeater('−',$level) . '>' . format_string($sco->title); 01458 } else { 01459 if ($sco->id == $scoid) { 01460 $result->prerequisites = false; 01461 } 01462 if ($play) { 01463 // should be disabled 01464 $result->toc .= '<span>'.$statusicon.' '.format_string($sco->title).'</span>'; 01465 } else { 01466 $result->toc .= $statusicon.' '.format_string($sco->title)."\n"; 01467 } 01468 } 01469 } else { 01470 $result->toc .= ' '.format_string($sco->title); 01471 } 01472 if (($nextsco === false) || $nextsco->parent == $sco->parent) { 01473 $result->toc .= "</li>\n"; 01474 } 01475 } 01476 if (($nextsco !== false) && ($nextid == 0) && ($findnext)) { 01477 if (!empty($nextsco->launch)) { 01478 $nextid = $nextsco->id; 01479 } 01480 } 01481 $prevsco = $sco; 01482 } 01483 for ($i=0;$i<$level;$i++) { 01484 $result->toc .= "\t\t</ul></li>\n"; 01485 } 01486 01487 if ($play) { 01488 // it is possible that $scoid is still not set, in this case we don't want an empty object 01489 if ($scoid) { 01490 $sco = scorm_get_sco($scoid); 01491 } 01492 $sco->previd = $previd; 01493 $sco->nextid = $nextid; 01494 $result->sco = $sco; 01495 $result->incomplete = $incomplete; 01496 } else { 01497 $result->incomplete = $incomplete; 01498 } 01499 } 01500 $result->toc .= '</ul>'; 01501 01502 // NEW IMS TOC 01503 if ($tocheader) { 01504 $result->toc .= '</div></div></div>'; 01505 $result->toc .= '<div id="scorm_navpanel"></div>'; 01506 } 01507 01508 01509 if ($scorm->hidetoc == 0) { 01510 $PAGE->requires->data_for_js('scormdata', array( 01511 'plusicon' => $OUTPUT->pix_url('plus', 'scorm'), 01512 'minusicon' => $OUTPUT->pix_url('minus', 'scorm'))); 01513 $PAGE->requires->js('/lib/cookies.js'); 01514 $PAGE->requires->js('/mod/scorm/datamodels/scorm_datamodels.js'); 01515 } 01516 01517 $url = new moodle_url('/mod/scorm/player.php?a='.$scorm->id.'¤torg='.$currentorg.$modestr); 01518 $result->tocmenu = $OUTPUT->single_select($url, 'scoid', $tocmenus, $sco->id, null, "tocmenu"); 01519 01520 return $result; 01521 }