on-demand release 2.4dev
[moodle.git] / mod / scorm / locallib.php
CommitLineData
e5dd8e3b 1<?php
f7b5c6aa
DM
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/>.
f69db63e 16
9528568b 17require_once("$CFG->dirroot/mod/scorm/lib.php");
64f93798 18require_once("$CFG->libdir/filelib.php");
9528568b 19
a30b6819 20/// Constants and settings for module scorm
8aba9cda
DM
21define('SCORM_UPDATE_NEVER', '0');
22define('SCORM_UPDATE_EVERYDAY', '2');
23define('SCORM_UPDATE_EVERYTIME', '3');
a679d64d 24
bb5cc0e9
DM
25define('SCORM_SKIPVIEW_NEVER', '0');
26define('SCORM_SKIPVIEW_FIRST', '1');
27define('SCORM_SKIPVIEW_ALWAYS', '2');
28
b3659259 29define('SCO_ALL', 0);
30define('SCO_DATA', 1);
31define('SCO_ONLY', 2);
a30b6819 32
33define('GRADESCOES', '0');
34define('GRADEHIGHEST', '1');
35define('GRADEAVERAGE', '2');
36define('GRADESUM', '3');
a30b6819 37
38define('HIGHESTATTEMPT', '0');
39define('AVERAGEATTEMPT', '1');
40define('FIRSTATTEMPT', '2');
41define('LASTATTEMPT', '3');
a30b6819 42
7cc7cf58
DM
43define('TOCJSLINK', 1);
44define('TOCFULLURL', 2);
45
9528568b 46/// Local Library of functions for module scorm
a30b6819 47
64f93798
PS
48/**
49 * @package mod-scorm
50 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
51 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
52 */
53class scorm_package_file_info extends file_info_stored {
54 public function get_parent() {
55 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
56 return $this->browser->get_file_info($this->context);
57 }
58 return parent::get_parent();
59 }
60 public function get_visible_name() {
61 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
62 return $this->topvisiblename;
63 }
64 return parent::get_visible_name();
65 }
66}
67
30fc6e2d 68/**
69 * Returns an array of the popup options for SCORM and each options default value
7554f671 70 *
30fc6e2d 71 * @return array an array of popup options as the key and their defaults as the value
72 */
ca4eda13 73function scorm_get_popup_options_array() {
30fc6e2d 74 global $CFG;
75 $cfg_scorm = get_config('scorm');
7554f671 76
77 return array('resizable'=> isset($cfg_scorm->resizable) ? $cfg_scorm->resizable : 0,
78 'scrollbars'=> isset($cfg_scorm->scrollbars) ? $cfg_scorm->scrollbars : 0,
79 'directories'=> isset($cfg_scorm->directories) ? $cfg_scorm->directories : 0,
1adc77e6 80 'location'=> isset($cfg_scorm->location) ? $cfg_scorm->location : 0,
7554f671 81 'menubar'=> isset($cfg_scorm->menubar) ? $cfg_scorm->menubar : 0,
82 'toolbar'=> isset($cfg_scorm->toolbar) ? $cfg_scorm->toolbar : 0,
83 'status'=> isset($cfg_scorm->status) ? $cfg_scorm->status : 0);
30fc6e2d 84}
85
86/**
87 * Returns an array of the array of what grade options
7554f671 88 *
30fc6e2d 89 * @return array an array of what grade options
90 */
ca4eda13 91function scorm_get_grade_method_array() {
30fc6e2d 92 return array (GRADESCOES => get_string('gradescoes', 'scorm'),
93 GRADEHIGHEST => get_string('gradehighest', 'scorm'),
94 GRADEAVERAGE => get_string('gradeaverage', 'scorm'),
7554f671 95 GRADESUM => get_string('gradesum', 'scorm'));
30fc6e2d 96}
97
98/**
99 * Returns an array of the array of what grade options
7554f671 100 *
30fc6e2d 101 * @return array an array of what grade options
102 */
ca4eda13 103function scorm_get_what_grade_array() {
30fc6e2d 104 return array (HIGHESTATTEMPT => get_string('highestattempt', 'scorm'),
105 AVERAGEATTEMPT => get_string('averageattempt', 'scorm'),
106 FIRSTATTEMPT => get_string('firstattempt', 'scorm'),
107 LASTATTEMPT => get_string('lastattempt', 'scorm'));
108}
109
110/**
111 * Returns an array of the array of skip view options
7554f671 112 *
30fc6e2d 113 * @return array an array of skip view options
114 */
ca4eda13 115function scorm_get_skip_view_array() {
bb5cc0e9
DM
116 return array(SCORM_SKIPVIEW_NEVER => get_string('never'),
117 SCORM_SKIPVIEW_FIRST => get_string('firstaccess', 'scorm'),
118 SCORM_SKIPVIEW_ALWAYS => get_string('always'));
30fc6e2d 119}
120
121/**
122 * Returns an array of the array of hide table of contents options
7554f671 123 *
30fc6e2d 124 * @return array an array of hide table of contents options
125 */
ca4eda13 126function scorm_get_hidetoc_array() {
5f65d37c 127 return array(SCORM_TOC_SIDE => get_string('sided', 'scorm'),
99da7a95
DM
128 SCORM_TOC_HIDDEN => get_string('hidden', 'scorm'),
129 SCORM_TOC_POPUP => get_string('popupmenu', 'scorm'),
130 SCORM_TOC_DISABLED => get_string('disabled', 'scorm'));
30fc6e2d 131}
132
133/**
134 * Returns an array of the array of update frequency options
7554f671 135 *
30fc6e2d 136 * @return array an array of update frequency options
137 */
ca4eda13 138function scorm_get_updatefreq_array() {
8aba9cda
DM
139 return array(SCORM_UPDATE_NEVER => get_string('never'),
140 SCORM_UPDATE_EVERYDAY => get_string('everyday', 'scorm'),
141 SCORM_UPDATE_EVERYTIME => get_string('everytime', 'scorm'));
30fc6e2d 142}
143
144/**
145 * Returns an array of the array of popup display options
7554f671 146 *
30fc6e2d 147 * @return array an array of popup display options
148 */
ca4eda13 149function scorm_get_popup_display_array() {
d67eb434 150 return array(0 => get_string('currentwindow', 'scorm'),
30fc6e2d 151 1 => get_string('popup', 'scorm'));
152}
153
154/**
155 * Returns an array of the array of attempt options
7554f671 156 *
30fc6e2d 157 * @return array an array of attempt options
158 */
ca4eda13
DM
159function scorm_get_attempts_array() {
160 $attempts = array(0 => get_string('nolimit', 'scorm'),
161 1 => get_string('attempt1', 'scorm'));
7554f671 162
30fc6e2d 163 for ($i=2; $i<=6; $i++) {
ca4eda13 164 $attempts[$i] = get_string('attemptsx', 'scorm', $i);
30fc6e2d 165 }
7554f671 166
30fc6e2d 167 return $attempts;
168}
da92e3b0
DM
169
170/**
171 * Returns an array of the attempt status options
172 *
173 * @return array an array of attempt status options
174 */
175function scorm_get_attemptstatus_array() {
176 return array(SCORM_DISPLAY_ATTEMPTSTATUS_NO => get_string('no'),
177 SCORM_DISPLAY_ATTEMPTSTATUS_ALL => get_string('attemptstatusall', 'scorm'),
178 SCORM_DISPLAY_ATTEMPTSTATUS_MY => get_string('attemptstatusmy', 'scorm'),
179 SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY => get_string('attemptstatusentry', 'scorm'));
180}
181
9528568b 182/**
183 * Extracts scrom package, sets up all variables.
184 * Called whenever scorm changes
185 * @param object $scorm instance - fields are updated and changes saved into database
186 * @param bool $full force full update if true
187 * @return void
188 */
189function scorm_parse($scorm, $full) {
190 global $CFG, $DB;
30fc6e2d 191 $cfg_scorm = get_config('scorm');
8aee93f1 192
9528568b 193 if (!isset($scorm->cmid)) {
194 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
195 $scorm->cmid = $cm->id;
196 }
a3fc4b3a 197 $context = context_module::instance($scorm->cmid);
9528568b 198 $newhash = $scorm->sha1hash;
a30b6819 199
9528568b 200 if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
a30b6819 201
9528568b 202 $fs = get_file_storage();
203 $packagefile = false;
f69db63e 204
9528568b 205 if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
64f93798 206 if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) {
9528568b 207 $newhash = $packagefile->get_contenthash();
208 } else {
209 $newhash = null;
210 }
211 } else {
30fc6e2d 212 if (!$cfg_scorm->allowtypelocalsync) {
9528568b 213 // sorry - localsync disabled
214 return;
215 }
216 if ($scorm->reference !== '' and (!$full or $scorm->sha1hash !== sha1($scorm->reference))) {
64f93798
PS
217 $fs->delete_area_files($context->id, 'mod_scorm', 'package');
218 $file_record = array('contextid'=>$context->id, 'component'=>'mod_scorm', 'filearea'=>'package', 'itemid'=>0, 'filepath'=>'/');
60b5a2fe 219 if ($packagefile = $fs->create_file_from_url($file_record, $scorm->reference, array('calctimeout' => true))) {
9528568b 220 $newhash = sha1($scorm->reference);
5c1ac70c 221 } else {
9528568b 222 $newhash = null;
5c1ac70c 223 }
f69db63e 224 }
225 }
f69db63e 226
9528568b 227 if ($packagefile) {
228 if (!$full and $packagefile and $scorm->sha1hash === $newhash) {
229 if (strpos($scorm->version, 'SCORM') !== false) {
64f93798 230 if ($fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) {
9528568b 231 // no need to update
232 return;
e4aa175a 233 }
9528568b 234 } else if (strpos($scorm->version, 'AICC') !== false) {
235 // TODO: add more sanity checks - something really exists in scorm_content area
236 return;
237 }
238 }
239
240 // now extract files
64f93798 241 $fs->delete_area_files($context->id, 'mod_scorm', 'content');
9528568b 242
243 $packer = get_file_packer('application/zip');
64f93798 244 $packagefile->extract_to_storage($packer, $context->id, 'mod_scorm', 'content', 0, '/');
9528568b 245
246 } else if (!$full) {
247 return;
248 }
249
64f93798 250 if ($manifest = $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) {
9528568b 251 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php");
252 // SCORM
253 if (!scorm_parse_scorm($scorm, $manifest)) {
254 $scorm->version = 'ERROR';
255 }
256 } else {
257 require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php");
258 // AICC
259 if (!scorm_parse_aicc($scorm)) {
260 $scorm->version = 'ERROR';
261 }
4388bd45 262 $scorm->version = 'AICC';
9528568b 263 }
264
30fc6e2d 265 } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL and $cfg_scorm->allowtypeexternal) {
9528568b 266 if (!$full and $scorm->sha1hash === sha1($scorm->reference)) {
267 return;
268 }
269 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php");
270 // SCORM only, AICC can not be external
271 if (!scorm_parse_scorm($scorm, $scorm->reference)) {
272 $scorm->version = 'ERROR';
273 }
274 $newhash = sha1($scorm->reference);
275
30fc6e2d 276 } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY and !empty($CFG->repositoryactivate) and $cfg_scorm->allowtypeimsrepository) {
9528568b 277 if (!$full and $scorm->sha1hash === sha1($scorm->reference)) {
278 return;
279 }
280 require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php");
ca4eda13 281 if (!scorm_parse_scorm($scorm, $CFG->repository.substr($scorm->reference, 1).'/imsmanifest.xml')) {
9528568b 282 $scorm->version = 'ERROR';
283 }
284 $newhash = sha1($scorm->reference);
4388bd45
DM
285 } else if ($scorm->scormtype === SCORM_TYPE_AICCURL and $cfg_scorm->allowtypeexternalaicc) {
286 require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php");
287 // AICC
288 if (!scorm_parse_aicc($scorm)) {
289 $scorm->version = 'ERROR';
290 }
291 $scorm->version = 'AICC';
e4aa175a 292 } else {
9528568b 293 // sorry, disabled type
294 return;
e4aa175a 295 }
9528568b 296
297 $scorm->revision++;
298 $scorm->sha1hash = $newhash;
299 $DB->update_record('scorm', $scorm);
e4aa175a 300}
301
9528568b 302
2b3447c3 303function scorm_array_search($item, $needle, $haystacks, $strict=false) {
304 if (!empty($haystacks)) {
305 foreach ($haystacks as $key => $element) {
306 if ($strict) {
307 if ($element->{$item} === $needle) {
308 return $key;
309 }
310 } else {
311 if ($element->{$item} == $needle) {
312 return $key;
e4aa175a 313 }
314 }
e4aa175a 315 }
316 }
2b3447c3 317 return false;
e4aa175a 318}
319
2b3447c3 320function scorm_repeater($what, $times) {
321 if ($times <= 0) {
322 return null;
323 }
324 $return = '';
ca4eda13 325 for ($i=0; $i<$times; $i++) {
2b3447c3 326 $return .= $what;
327 }
328 return $return;
329}
e4aa175a 330
2b3447c3 331function scorm_external_link($link) {
ca4eda13 332 // check if a link is external
2b3447c3 333 $result = false;
334 $link = strtolower($link);
ca4eda13 335 if (substr($link, 0, 7) == 'http://') {
2b3447c3 336 $result = true;
ca4eda13 337 } else if (substr($link, 0, 8) == 'https://') {
2b3447c3 338 $result = true;
ca4eda13 339 } else if (substr($link, 0, 4) == 'www.') {
2b3447c3 340 $result = true;
341 }
342 return $result;
e4aa175a 343}
344
b3659259 345/**
ca4eda13
DM
346 * Returns an object containing all datas relative to the given sco ID
347 *
348 * @param integer $id The sco ID
349 * @return mixed (false if sco id does not exists)
350 */
351function scorm_get_sco($id, $what=SCO_ALL) {
bf347041 352 global $DB;
353
354 if ($sco = $DB->get_record('scorm_scoes', array('id'=>$id))) {
b3659259 355 $sco = ($what == SCO_DATA) ? new stdClass() : $sco;
bf347041 356 if (($what != SCO_ONLY) && ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$id)))) {
b3659259 357 foreach ($scodatas as $scodata) {
c31f631b 358 $sco->{$scodata->name} = $scodata->value;
b3659259 359 }
bf347041 360 } else if (($what != SCO_ONLY) && (!($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$id))))) {
9528568b 361 $sco->parameters = '';
b3659259 362 }
363 return $sco;
364 } else {
365 return false;
366 }
367}
82605bea 368
369/**
ca4eda13
DM
370 * Returns an object (array) containing all the scoes data related to the given sco ID
371 *
372 * @param integer $id The sco ID
373 * @param integer $organisation an organisation ID - defaults to false if not required
374 * @return mixed (false if there are no scoes or an array)
375 */
376function scorm_get_scoes($id, $organisation=false) {
b44c704f 377 global $DB;
378
82605bea 379 $organizationsql = '';
b44c704f 380 $queryarray = array('scorm'=>$id);
82605bea 381 if (!empty($organisation)) {
b44c704f 382 $queryarray['organization'] = $organisation;
82605bea 383 }
b44c704f 384 if ($scoes = $DB->get_records('scorm_scoes', $queryarray, 'id ASC')) {
82605bea 385 // drop keys so that it is a simple array as expected
386 $scoes = array_values($scoes);
387 foreach ($scoes as $sco) {
ca4eda13 388 if ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$sco->id))) {
82605bea 389 foreach ($scodatas as $scodata) {
2fd0e9fe 390 $sco->{$scodata->name} = $scodata->value;
82605bea 391 }
392 }
393 }
394 return $scoes;
395 } else {
396 return false;
397 }
398}
399
ca4eda13 400function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $value, $forcecompleted=false) {
86996ffe 401 global $DB, $CFG;
bf347041 402
e4aa175a 403 $id = null;
6381fa56 404
405 if ($forcecompleted) {
406 //TODO - this could be broadened to encompass SCORM 2004 in future
407 if (($element == 'cmi.core.lesson_status') && ($value == 'incomplete')) {
ca4eda13 408 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))) {
6381fa56 409 $value = 'completed';
410 }
411 }
412 if ($element == 'cmi.core.score.raw') {
ca4eda13 413 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))) {
6381fa56 414 if ($tracktest->value == "incomplete") {
415 $tracktest->value = "completed";
ca4eda13 416 $DB->update_record('scorm_scoes_track', $tracktest);
6381fa56 417 }
418 }
419 }
420 }
421
ca4eda13 422 if ($track = $DB->get_record('scorm_scoes_track', array('userid'=>$userid, 'scormid'=>$scormid, 'scoid'=>$scoid, 'attempt'=>$attempt, 'element'=>$element))) {
d1818fdc 423 if ($element != 'x.start.time' ) { //don't update x.start.time - keep the original value.
454f5aee 424 $track->value = $value;
d1818fdc 425 $track->timemodified = time();
ca4eda13 426 $DB->update_record('scorm_scoes_track', $track);
dd88de0e 427 $id = $track->id;
d1818fdc 428 }
e4aa175a 429 } else {
37ca1722 430 $track = new stdClass();
e4aa175a 431 $track->userid = $userid;
432 $track->scormid = $scormid;
433 $track->scoid = $scoid;
434 $track->attempt = $attempt;
435 $track->element = $element;
454f5aee 436 $track->value = $value;
e4aa175a 437 $track->timemodified = time();
ca4eda13 438 $id = $DB->insert_record('scorm_scoes_track', $track);
e4aa175a 439 }
9528568b 440
9528568b 441 if (strstr($element, '.score.raw') ||
94db2749
AB
442 (in_array($element, array('cmi.completion_status', 'cmi.core.lesson_status', 'cmi.success_status'))
443 && in_array($track->value, array('completed', 'passed')))) {
a0b36684 444 $scorm = $DB->get_record('scorm', array('id' => $scormid));
86996ffe 445 include_once($CFG->dirroot.'/mod/scorm/lib.php');
7f7946fd 446 scorm_update_grades($scorm, $userid);
d23121ab 447 }
9528568b 448
e4aa175a 449 return $id;
450}
451
410b7c9b
DM
452/**
453 * simple quick function to return true/false if this user has tracks in this scorm
454 *
455 * @param integer $scormid The scorm ID
456 * @param integer $userid the users id
457 * @return boolean (false if there are no tracks)
458 */
459function scorm_has_tracks($scormid, $userid) {
460 global $DB;
29b24454 461 return $DB->record_exists('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scormid));
410b7c9b
DM
462}
463
ca4eda13
DM
464function scorm_get_tracks($scoid, $userid, $attempt='') {
465 /// Gets all tracks of specified sco and user
bf347041 466 global $CFG, $DB;
e4aa175a 467
468 if (empty($attempt)) {
ca4eda13
DM
469 if ($scormid = $DB->get_field('scorm_scoes', 'scorm', array('id'=>$scoid))) {
470 $attempt = scorm_get_last_attempt($scormid, $userid);
e4aa175a 471 } else {
472 $attempt = 1;
473 }
474 }
ca4eda13 475 if ($tracks = $DB->get_records('scorm_scoes_track', array('userid'=>$userid, 'scoid'=>$scoid, 'attempt'=>$attempt), 'element ASC')) {
39790bd8 476 $usertrack = new stdClass();
e4aa175a 477 $usertrack->userid = $userid;
9528568b 478 $usertrack->scoid = $scoid;
a30b6819 479 // Defined in order to unify scorm1.2 and scorm2004
e4aa175a 480 $usertrack->score_raw = '';
e4aa175a 481 $usertrack->status = '';
57a32d3e 482 $usertrack->progress = '';
e4aa175a 483 $usertrack->total_time = '00:00:00';
484 $usertrack->session_time = '00:00:00';
485 $usertrack->timemodified = 0;
486 foreach ($tracks as $track) {
487 $element = $track->element;
488 $usertrack->{$element} = $track->value;
489 switch ($element) {
f69db63e 490 case 'cmi.core.lesson_status':
491 case 'cmi.completion_status':
492 if ($track->value == 'not attempted') {
493 $track->value = 'notattempted';
9528568b 494 }
f69db63e 495 $usertrack->status = $track->value;
57a32d3e
AA
496 break;
497 case 'cmi.success_status':
498 $usertrack->progress = $track->value;
499 break;
500 case 'cmi.progress_measure':
9381c280 501 if (!empty($track->value) && (empty($usertrack->progress) || $usertrack->progress == 'unknown') ) {
57a32d3e
AA
502 $usertrack->progress = $track->value;
503 }
504 break;
e4aa175a 505 case 'cmi.core.score.raw':
506 case 'cmi.score.raw':
0d55dd4d 507 $usertrack->score_raw = (float) sprintf('%2.2f', $track->value);
1c2b2132
AA
508 break;
509 case 'cmi.core.score.max':
510 case 'cmi.score.max':
511 $usertrack->score_max = (float) sprintf('%2.2f', $track->value);
512 break;
513 case 'cmi.core.score.min':
514 case 'cmi.score.min':
515 $usertrack->score_min = (float) sprintf('%2.2f', $track->value);
516 break;
e4aa175a 517 case 'cmi.core.session_time':
518 case 'cmi.session_time':
519 $usertrack->session_time = $track->value;
1c2b2132 520 break;
e4aa175a 521 case 'cmi.core.total_time':
522 case 'cmi.total_time':
523 $usertrack->total_time = $track->value;
1c2b2132 524 break;
9528568b 525 }
e4aa175a 526 if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) {
527 $usertrack->timemodified = $track->timemodified;
9528568b 528 }
3505e82b 529 }
9528568b 530 if (is_array($usertrack)) {
07b905ae 531 ksort($usertrack);
532 }
e4aa175a 533 return $usertrack;
534 } else {
535 return false;
536 }
537}
538
51c1c4e1 539
540/* Find the start and finsh time for a a given SCO attempt
541 *
542 * @param int $scormid SCORM Id
543 * @param int $scoid SCO Id
544 * @param int $userid User Id
545 * @param int $attemt Attempt Id
546 *
547 * @return object start and finsh time EPOC secods
548 *
549 */
550function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) {
4454447d 551 global $DB;
51c1c4e1 552
39790bd8 553 $timedata = new stdClass();
51c1c4e1 554 $sql = !empty($scoid) ? "userid=$userid AND scormid=$scormid AND scoid=$scoid AND attempt=$attempt" : "userid=$userid AND scormid=$scormid AND attempt=$attempt";
ca4eda13 555 $tracks = $DB->get_records_select('scorm_scoes_track', "$sql ORDER BY timemodified ASC");
51c1c4e1 556 if ($tracks) {
557 $tracks = array_values($tracks);
558 }
559
c86a91d5 560 if ($tracks) {
51c1c4e1 561 $timedata->start = $tracks[0]->timemodified;
ca4eda13 562 } else {
51c1c4e1 563 $timedata->start = false;
564 }
565 if ($tracks && $track = array_pop($tracks)) {
566 $timedata->finish = $track->timemodified;
ca4eda13 567 } else {
51c1c4e1 568 $timedata->finish = $timedata->start;
569 }
570 return $timedata;
571}
572
573
2b3447c3 574function scorm_get_user_data($userid) {
bf347041 575 global $DB;
ca4eda13
DM
576 /// Gets user info required to display the table of scorm results
577 /// for report.php
e4aa175a 578
629f5021 579 return $DB->get_record('user', array('id'=>$userid), user_picture::fields());
2b3447c3 580}
e4aa175a 581
c0f6b608 582function scorm_grade_user_attempt($scorm, $userid, $attempt=1) {
bf347041 583 global $DB;
37ca1722 584 $attemptscore = new stdClass();
a30b6819 585 $attemptscore->scoes = 0;
586 $attemptscore->values = 0;
587 $attemptscore->max = 0;
588 $attemptscore->sum = 0;
589 $attemptscore->lastmodify = 0;
9528568b 590
bf347041 591 if (!$scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) {
ca4eda13 592 return null;
e4aa175a 593 }
e4aa175a 594
9528568b 595 foreach ($scoes as $sco) {
ca4eda13 596 if ($userdata=scorm_get_tracks($sco->id, $userid, $attempt)) {
2b3447c3 597 if (($userdata->status == 'completed') || ($userdata->status == 'passed')) {
a30b6819 598 $attemptscore->scoes++;
9528568b 599 }
5c2aa157 600 if (!empty($userdata->score_raw) || (isset($scorm->type) && $scorm->type=='sco' && isset($userdata->score_raw))) {
a30b6819 601 $attemptscore->values++;
602 $attemptscore->sum += $userdata->score_raw;
603 $attemptscore->max = ($userdata->score_raw > $attemptscore->max)?$userdata->score_raw:$attemptscore->max;
604 if (isset($userdata->timemodified) && ($userdata->timemodified > $attemptscore->lastmodify)) {
605 $attemptscore->lastmodify = $userdata->timemodified;
606 } else {
607 $attemptscore->lastmodify = 0;
608 }
9528568b 609 }
610 }
e4aa175a 611 }
03751efe 612 switch ($scorm->grademethod) {
a30b6819 613 case GRADEHIGHEST:
0d55dd4d 614 $score = (float) $attemptscore->max;
9528568b 615 break;
a30b6819 616 case GRADEAVERAGE:
617 if ($attemptscore->values > 0) {
618 $score = $attemptscore->sum/$attemptscore->values;
5c1ac70c 619 } else {
a30b6819 620 $score = 0;
9528568b 621 }
622 break;
a30b6819 623 case GRADESUM:
624 $score = $attemptscore->sum;
9528568b 625 break;
a30b6819 626 case GRADESCOES:
627 $score = $attemptscore->scoes;
7ef16bf9 628 break;
629 default:
630 $score = $attemptscore->max; // Remote Learner GRADEHIGHEST is default
5c1ac70c 631 }
a30b6819 632
c0f6b608 633 return $score;
a30b6819 634}
635
8c5516dc 636function scorm_grade_user($scorm, $userid) {
a30b6819 637
03751efe 638 // ensure we dont grade user beyond $scorm->maxattempt settings
7ce6eb87 639 $lastattempt = scorm_get_last_attempt($scorm->id, $userid);
ca4eda13 640 if ($scorm->maxattempt != 0 && $lastattempt >= $scorm->maxattempt) {
7ce6eb87 641 $lastattempt = $scorm->maxattempt;
642 }
643
03751efe 644 switch ($scorm->whatgrade) {
a30b6819 645 case FIRSTATTEMPT:
8c5516dc 646 return scorm_grade_user_attempt($scorm, $userid, 1);
9528568b 647 break;
a30b6819 648 case LASTATTEMPT:
8c5516dc 649 return scorm_grade_user_attempt($scorm, $userid, scorm_get_last_completed_attempt($scorm->id, $userid));
a30b6819 650 break;
651 case HIGHESTATTEMPT:
a30b6819 652 $maxscore = 0;
a30b6819 653 for ($attempt = 1; $attempt <= $lastattempt; $attempt++) {
8c5516dc
DM
654 $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt);
655 $maxscore = $attemptscore > $maxscore ? $attemptscore: $maxscore;
a30b6819 656 }
8c5516dc
DM
657 return $maxscore;
658
a30b6819 659 break;
660 case AVERAGEATTEMPT:
b1ca5d31 661 $attemptcount = scorm_get_attempt_count($userid, $scorm, true);
b1ca5d31
DM
662 if (empty($attemptcount)) {
663 return 0;
664 } else {
665 $attemptcount = count($attemptcount);
666 }
a30b6819 667 $lastattempt = scorm_get_last_attempt($scorm->id, $userid);
668 $sumscore = 0;
669 for ($attempt = 1; $attempt <= $lastattempt; $attempt++) {
8c5516dc
DM
670 $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt);
671 $sumscore += $attemptscore;
a30b6819 672 }
673
b1ca5d31 674 return round($sumscore / $attemptcount);
a30b6819 675 break;
676 }
e4aa175a 677}
678
ca4eda13 679function scorm_count_launchable($scormid, $organization='') {
bf347041 680 global $DB;
681
682 $sqlorganization = '';
683 $params = array($scormid);
8e45ba45 684 if (!empty($organization)) {
bf347041 685 $sqlorganization = " AND organization=?";
686 $params[] = $organization;
8e45ba45 687 }
ca4eda13 688 return $DB->count_records_select('scorm_scoes', "scorm = ? $sqlorganization AND ".$DB->sql_isnotempty('scorm_scoes', 'launch', false, true), $params);
e4aa175a 689}
690
12287859
DM
691/**
692 * Returns the last attempt used - if no attempts yet, returns 1 for first attempt
693 *
694 * @param int $scormid the id of the scorm.
695 * @param int $userid the id of the user.
696 *
697 * @return int The attempt number to use.
698 */
2b3447c3 699function scorm_get_last_attempt($scormid, $userid) {
bf347041 700 global $DB;
701
ca4eda13 702 /// Find the last attempt number for the given user id and scorm id
12287859
DM
703 $sql = "SELECT MAX(attempt)
704 FROM {scorm_scoes_track}
705 WHERE userid = ? AND scormid = ?";
706 $lastattempt = $DB->get_field_sql($sql, array($userid, $scormid));
707 if (empty($lastattempt)) {
708 return '1';
6381fa56 709 } else {
12287859 710 return $lastattempt;
e4aa175a 711 }
e4aa175a 712}
713
12287859
DM
714/**
715 * Returns the last completed attempt used - if no completed attempts yet, returns 1 for first attempt
716 *
717 * @param int $scormid the id of the scorm.
718 * @param int $userid the id of the user.
719 *
720 * @return int The attempt number to use.
721 */
2276208a
DM
722function scorm_get_last_completed_attempt($scormid, $userid) {
723 global $DB;
724
12287859
DM
725 /// Find the last completed attempt number for the given user id and scorm id
726 $sql = "SELECT MAX(attempt)
727 FROM {scorm_scoes_track}
728 WHERE userid = ? AND scormid = ?
729 AND (value='completed' OR value='passed')";
730 $lastattempt = $DB->get_field_sql($sql, array($userid, $scormid));
731 if (empty($lastattempt)) {
732 return '1';
2276208a 733 } else {
12287859 734 return $lastattempt;
2276208a
DM
735 }
736}
737
ca4eda13 738function scorm_course_format_display($user, $course) {
f2a1963c 739 global $CFG, $DB, $PAGE, $OUTPUT;
e4aa175a 740
741 $strupdate = get_string('update');
a3fc4b3a 742 $context = context_course::instance($course->id);
e4aa175a 743
744 echo '<div class="mod-scorm">';
745 if ($scorms = get_all_instances_in_course('scorm', $course)) {
9528568b 746 // The module SCORM activity with the least id is the course
e4aa175a 747 $scorm = current($scorms);
748 if (! $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id)) {
08b56f93 749 print_error('invalidcoursemodule');
e4aa175a 750 }
a3fc4b3a 751 $contextmodule = context_module::instance($cm->id);
4201a746 752 if ((has_capability('mod/scorm:skipview', $contextmodule))) {
666efc14 753 scorm_simple_play($scorm, $user, $contextmodule, $cm->id);
aab23f98 754 }
e4aa175a 755 $colspan = '';
756 $headertext = '<table width="100%"><tr><td class="title">'.get_string('name').': <b>'.format_string($scorm->name).'</b>';
2b3447c3 757 if (has_capability('moodle/course:manageactivities', $context)) {
830dd6e9 758 if ($PAGE->user_is_editing()) {
e4aa175a 759 // Display update icon
760 $path = $CFG->wwwroot.'/course';
761 $headertext .= '<span class="commands">'.
762 '<a title="'.$strupdate.'" href="'.$path.'/mod.php?update='.$cm->id.'&amp;sesskey='.sesskey().'">'.
b5d0cafc 763 '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="'.$strupdate.'" /></a></span>';
e4aa175a 764 }
765 $headertext .= '</td>';
766 // Display report link
bf347041 767 $trackedusers = $DB->get_record('scorm_scoes_track', array('scormid'=>$scorm->id), 'count(distinct(userid)) as c');
e4aa175a 768 if ($trackedusers->c > 0) {
769 $headertext .= '<td class="reportlink">'.
4aea3cc7 770 '<a href="'.$CFG->wwwroot.'/mod/scorm/report.php?id='.$cm->id.'">'.
ca4eda13 771 get_string('viewallreports', 'scorm', $trackedusers->c).'</a>';
e4aa175a 772 } else {
ca4eda13 773 $headertext .= '<td class="reportlink">'.get_string('noreports', 'scorm');
e4aa175a 774 }
775 $colspan = ' colspan="2"';
9528568b 776 }
ac3668bf 777 $headertext .= '</td></tr><tr><td'.$colspan.'>'.get_string('summary').':<br />'.format_module_intro('scorm', $scorm, $scorm->coursemodule).'</td></tr></table>';
ca4eda13 778 echo $OUTPUT->box($headertext, 'generalbox boxwidthwide');
39340358 779 scorm_view_display($user, $scorm, 'view.php?id='.$course->id, $cm);
e4aa175a 780 } else {
0d699c24 781 if (has_capability('moodle/course:update', $context)) {
e4aa175a 782 // Create a new activity
444ff001
DM
783 $url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'section'=>'0', 'sesskey'=>sesskey(),'add'=>'scorm'));
784 redirect($url);
e4aa175a 785 } else {
6aff538a 786 echo $OUTPUT->notification('Could not find a scorm course here');
e4aa175a 787 }
788 }
789 echo '</div>';
790}
791
39340358 792function scorm_view_display ($user, $scorm, $action, $cm) {
4eeec9ac 793 global $CFG, $DB, $PAGE, $OUTPUT;
ab3b00e1 794
8aba9cda 795 if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
9528568b 796 scorm_parse($scorm, false);
ab3b00e1 797 }
798
e4aa175a 799 $organization = optional_param('organization', '', PARAM_INT);
800
ca4eda13 801 if ($scorm->displaycoursestructure == 1) {
30bce58e 802 echo $OUTPUT->box_start('generalbox boxaligncenter toc');
ca4eda13
DM
803 ?>
804 <div class="structurehead"><?php print_string('contents', 'scorm') ?></div>
805 <?php
6381fa56 806 }
e4aa175a 807 if (empty($organization)) {
808 $organization = $scorm->launch;
809 }
2e3c31bc 810 if ($orgs = $DB->get_records_select_menu('scorm_scoes', 'scorm = ? AND '.
84ab3289
DM
811 $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '.
812 $DB->sql_isempty('scorm_scoes', 'organization', false, false),
ca4eda13 813 array($scorm->id), 'id', 'id,title')) {
e4aa175a 814 if (count($orgs) > 1) {
edc28287 815 $select = new single_select(new moodle_url($action), 'organization', $orgs, $organization, null);
ca4eda13 816 $select->label = get_string('organizations', 'scorm');
edc28287
PS
817 $select->class = 'scorm-center';
818 echo $OUTPUT->render($select);
e4aa175a 819 }
820 }
821 $orgidentifier = '';
b3659259 822 if ($sco = scorm_get_sco($organization, SCO_ONLY)) {
823 if (($sco->organization == '') && ($sco->launch == '')) {
824 $orgidentifier = $sco->identifier;
e4aa175a 825 } else {
b3659259 826 $orgidentifier = $sco->organization;
e4aa175a 827 }
828 }
28ffbff3 829
2b3447c3 830 $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe
dbe7e6f6 831 if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) {
832 $scorm->version = 'scorm_12';
833 }
2b3447c3 834 require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php');
835
ca4eda13 836 $result = scorm_get_toc($user, $scorm, $cm->id, TOCFULLURL, $orgidentifier);
e4aa175a 837 $incomplete = $result->incomplete;
28ffbff3 838
6381fa56 839 // do we want the TOC to be displayed?
ca4eda13 840 if ($scorm->displaycoursestructure == 1) {
6381fa56 841 echo $result->toc;
6aff538a 842 echo $OUTPUT->box_end();
6381fa56 843 }
7554f671 844
6381fa56 845 // is this the first attempt ?
b1ca5d31 846 $attemptcount = scorm_get_attempt_count($user->id, $scorm);
7554f671 847
6381fa56 848 // do not give the player launch FORM if the SCORM object is locked after the final attempt
849 if ($scorm->lastattemptlock == 0 || $result->attemptleft > 0) {
ca4eda13 850 ?>
52a9a9b5 851 <div class="scorm-center">
d5159fb8 852 <form id="scormviewform" method="post" action="<?php echo $CFG->wwwroot ?>/mod/scorm/player.php">
ca4eda13
DM
853 <?php
854 if ($scorm->hidebrowse == 0) {
855 print_string('mode', 'scorm');
856 echo ': <input type="radio" id="b" name="mode" value="browse" /><label for="b">'.get_string('browse', 'scorm').'</label>'."\n";
857 echo '<input type="radio" id="n" name="mode" value="normal" checked="checked" /><label for="n">'.get_string('normal', 'scorm')."</label>\n";
858 } else {
859 echo '<input type="hidden" name="mode" value="normal" />'."\n";
860 }
861 if ($scorm->forcenewattempt == 1) {
862 if ($incomplete === false) {
863 echo '<input type="hidden" name="newattempt" value="on" />'."\n";
864 }
865 } else if (!empty($attemptcount) && ($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) {
866 ?>
6381fa56 867 <br />
868 <input type="checkbox" id="a" name="newattempt" />
ca4eda13
DM
869 <label for="a"><?php print_string('newattempt', 'scorm') ?></label>
870 <?php
871 }
d5159fb8
DM
872 if (!empty($scorm->popup)) {
873 echo '<input type="hidden" name="display" value="popup" />'."\n";
874 }
ca4eda13 875 ?>
e4aa175a 876 <br />
4675994a 877 <input type="hidden" name="scoid"/>
3d00d6ab 878 <input type="hidden" name="cm" value="<?php echo $cm->id ?>"/>
e4aa175a 879 <input type="hidden" name="currentorg" value="<?php echo $orgidentifier ?>" />
ca4eda13 880 <input type="submit" value="<?php print_string('enter', 'scorm') ?>" />
e4aa175a 881 </form>
882 </div>
ca4eda13 883 <?php
6381fa56 884 }
e4aa175a 885}
6280b17a 886
666efc14 887function scorm_simple_play($scorm, $user, $context, $cmid) {
bf347041 888 global $DB;
889
9316beca 890 $result = false;
891
8aba9cda 892 if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
9316beca 893 scorm_parse($scorm, false);
894 }
64f93798 895 if (has_capability('mod/scorm:viewreport', $context)) { //if this user can view reports, don't skipview so they can see links to reports.
489797d9
DM
896 return $result;
897 }
9316beca 898
84ab3289 899 $scoes = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id), 'id', 'id');
9316beca 900
7325b016 901 if ($scoes) {
c5803aaa
DM
902 $orgidentifier = '';
903 if ($sco = scorm_get_sco($scorm->launch, SCO_ONLY)) {
904 if (($sco->organization == '') && ($sco->launch == '')) {
905 $orgidentifier = $sco->identifier;
906 } else {
907 $orgidentifier = $sco->organization;
908 }
909 }
234415c4 910 if ($scorm->skipview >= SCORM_SKIPVIEW_FIRST) {
9316beca 911 $sco = current($scoes);
aab23f98
DM
912 $url = new moodle_url('/mod/scorm/player.php', array('a' => $scorm->id,
913 'currentorg'=>$orgidentifier,
914 'scoid'=>$sco->id));
410b7c9b 915 if ($scorm->skipview == SCORM_SKIPVIEW_ALWAYS || !scorm_has_tracks($scorm->id, $user->id)) {
666efc14
DM
916 if (!empty($scorm->forcenewattempt)) {
917 $result = scorm_get_toc($user, $scorm, $cmid, TOCFULLURL, $orgidentifier);
918 if ($result->incomplete === false) {
919 $url->param('newattempt','on');
920 }
921 }
aab23f98 922 redirect($url);
9316beca 923 }
924 }
925 }
926 return $result;
28ffbff3 927}
d8c9d8a1 928
929function scorm_get_count_users($scormid, $groupingid=null) {
bf347041 930 global $CFG, $DB;
9528568b 931
98da6021 932 if (!empty($groupingid)) {
d8c9d8a1 933 $sql = "SELECT COUNT(DISTINCT st.userid)
bf347041 934 FROM {scorm_scoes_track} st
935 INNER JOIN {groups_members} gm ON st.userid = gm.userid
9528568b 936 INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid
bf347041 937 WHERE st.scormid = ? AND gg.groupingid = ?
d8c9d8a1 938 ";
bf347041 939 $params = array($scormid, $groupingid);
d8c9d8a1 940 } else {
941 $sql = "SELECT COUNT(DISTINCT st.userid)
9528568b 942 FROM {scorm_scoes_track} st
bf347041 943 WHERE st.scormid = ?
d8c9d8a1 944 ";
bf347041 945 $params = array($scormid);
d8c9d8a1 946 }
9528568b 947
bf347041 948 return ($DB->count_records_sql($sql, $params));
d8c9d8a1 949}
950
527af457 951/**
ca4eda13
DM
952 * Build up the JavaScript representation of an array element
953 *
954 * @param string $sversion SCORM API version
955 * @param array $userdata User track data
956 * @param string $element_name Name of array element to get values for
957 * @param array $children list of sub elements of this array element that also need instantiating
958 * @return None
959 */
527af457 960function scorm_reconstitute_array_element($sversion, $userdata, $element_name, $children) {
961 // reconstitute comments_from_learner and comments_from_lms
962 $current = '';
bf34ac0f 963 $current_subelement = '';
964 $current_sub = '';
527af457 965 $count = 0;
bf34ac0f 966 $count_sub = 0;
6b635d44 967 $scormseperator = '_';
e6402b54 968 if (scorm_version_check($sversion, SCORM_13)) { //scorm 1.3 elements use a . instead of an _
ca4eda13 969 $scormseperator = '.';
6b635d44 970 }
527af457 971 // filter out the ones we want
972 $element_list = array();
ca4eda13
DM
973 foreach ($userdata as $element => $value) {
974 if (substr($element, 0, strlen($element_name)) == $element_name) {
527af457 975 $element_list[$element] = $value;
976 }
977 }
7554f671 978
527af457 979 // sort elements in .n array order
980 uksort($element_list, "scorm_element_cmp");
7554f671 981
527af457 982 // generate JavaScript
ca4eda13 983 foreach ($element_list as $element => $value) {
e6402b54 984 if (scorm_version_check($sversion, SCORM_13)) {
b6293233 985 $element = preg_replace('/\.(\d+)\./', ".N\$1.", $element);
527af457 986 preg_match('/\.(N\d+)\./', $element, $matches);
987 } else {
b6293233 988 $element = preg_replace('/\.(\d+)\./', "_\$1.", $element);
527af457 989 preg_match('/\_(\d+)\./', $element, $matches);
990 }
991 if (count($matches) > 0 && $current != $matches[1]) {
bf34ac0f 992 if ($count_sub > 0) {
6b635d44 993 echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n";
bf34ac0f 994 }
7554f671 995 $current = $matches[1];
527af457 996 $count++;
bf34ac0f 997 $current_subelement = '';
998 $current_sub = '';
999 $count_sub = 0;
ca4eda13
DM
1000 $end = strpos($element, $matches[1])+strlen($matches[1]);
1001 $subelement = substr($element, 0, $end);
527af457 1002 echo ' '.$subelement." = new Object();\n";
1003 // now add the children
1004 foreach ($children as $child) {
1005 echo ' '.$subelement.".".$child." = new Object();\n";
1006 echo ' '.$subelement.".".$child."._children = ".$child."_children;\n";
1007 }
1008 }
7554f671 1009
bf34ac0f 1010 // now - flesh out the second level elements if there are any
e6402b54 1011 if (scorm_version_check($sversion, SCORM_13)) {
b6293233 1012 $element = preg_replace('/(.*?\.N\d+\..*?)\.(\d+)\./', "\$1.N\$2.", $element);
bf34ac0f 1013 preg_match('/.*?\.N\d+\.(.*?)\.(N\d+)\./', $element, $matches);
1014 } else {
b6293233 1015 $element = preg_replace('/(.*?\_\d+\..*?)\.(\d+)\./', "\$1_\$2.", $element);
bf34ac0f 1016 preg_match('/.*?\_\d+\.(.*?)\_(\d+)\./', $element, $matches);
1017 }
7554f671 1018
bf34ac0f 1019 // check the sub element type
1020 if (count($matches) > 0 && $current_subelement != $matches[1]) {
1021 if ($count_sub > 0) {
03e9a122 1022 echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n";
bf34ac0f 1023 }
1024 $current_subelement = $matches[1];
1025 $current_sub = '';
1026 $count_sub = 0;
ca4eda13
DM
1027 $end = strpos($element, $matches[1])+strlen($matches[1]);
1028 $subelement = substr($element, 0, $end);
bf34ac0f 1029 echo ' '.$subelement." = new Object();\n";
1030 }
7554f671 1031
bf34ac0f 1032 // now check the subelement subscript
1033 if (count($matches) > 0 && $current_sub != $matches[2]) {
7554f671 1034 $current_sub = $matches[2];
bf34ac0f 1035 $count_sub++;
ca4eda13
DM
1036 $end = strrpos($element, $matches[2])+strlen($matches[2]);
1037 $subelement = substr($element, 0, $end);
bf34ac0f 1038 echo ' '.$subelement." = new Object();\n";
1039 }
7554f671 1040
527af457 1041 echo ' '.$element.' = \''.$value."';\n";
1042 }
bf34ac0f 1043 if ($count_sub > 0) {
6b635d44 1044 echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n";
bf34ac0f 1045 }
527af457 1046 if ($count > 0) {
1047 echo ' '.$element_name.'._count = '.$count.";\n";
1048 }
1049}
1050
1051/**
ca4eda13
DM
1052 * Build up the JavaScript representation of an array element
1053 *
1054 * @param string $a left array element
1055 * @param string $b right array element
1056 * @return comparator - 0,1,-1
1057 */
527af457 1058function scorm_element_cmp($a, $b) {
bf34ac0f 1059 preg_match('/.*?(\d+)\./', $a, $matches);
527af457 1060 $left = intval($matches[1]);
bf34ac0f 1061 preg_match('/.?(\d+)\./', $b, $matches);
527af457 1062 $right = intval($matches[1]);
1063 if ($left < $right) {
1064 return -1; // smaller
ca4eda13 1065 } else if ($left > $right) {
527af457 1066 return 1; // bigger
1067 } else {
bf34ac0f 1068 // look for a second level qualifier eg cmi.interactions_0.correct_responses_0.pattern
1069 if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $a, $matches)) {
1070 $leftterm = intval($matches[2]);
1071 $left = intval($matches[3]);
1072 if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $b, $matches)) {
1073 $rightterm = intval($matches[2]);
1074 $right = intval($matches[3]);
1075 if ($leftterm < $rightterm) {
1076 return -1; // smaller
ca4eda13 1077 } else if ($leftterm > $rightterm) {
bf34ac0f 1078 return 1; // bigger
1079 } else {
1080 if ($left < $right) {
1081 return -1; // smaller
ca4eda13 1082 } else if ($left > $right) {
bf34ac0f 1083 return 1; // bigger
1084 }
1085 }
1086 }
1087 }
1088 // fall back for no second level matches or second level matches are equal
527af457 1089 return 0; // equal to
1090 }
1091}
6381fa56 1092
1093/**
ca4eda13
DM
1094 * Generate the user attempt status string
1095 *
1096 * @param object $user Current context user
1097 * @param object $scorm a moodle scrom object - mdl_scorm
1098 * @return string - Attempt status string
1099 */
d32e353e
DM
1100function scorm_get_attempt_status($user, $scorm, $cm='') {
1101 global $DB, $PAGE, $OUTPUT;
7554f671 1102
b1ca5d31 1103 $attempts = scorm_get_attempt_count($user->id, $scorm, true);
ca4eda13 1104 if (empty($attempts)) {
6381fa56 1105 $attemptcount = 0;
1106 } else {
1107 $attemptcount = count($attempts);
1108 }
7554f671 1109
6381fa56 1110 $result = '<p>'.get_string('noattemptsallowed', 'scorm').': ';
1111 if ($scorm->maxattempt > 0) {
4f4a8acf 1112 $result .= $scorm->maxattempt . '<br />';
6381fa56 1113 } else {
4f4a8acf 1114 $result .= get_string('unlimited').'<br />';
6381fa56 1115 }
4f4a8acf 1116 $result .= get_string('noattemptsmade', 'scorm').': ' . $attemptcount . '<br />';
6381fa56 1117
b1ca5d31
DM
1118 if ($scorm->maxattempt == 1) {
1119 switch ($scorm->grademethod) {
1120 case GRADEHIGHEST:
1121 $grademethod = get_string('gradehighest', 'scorm');
1122 break;
1123 case GRADEAVERAGE:
1124 $grademethod = get_string('gradeaverage', 'scorm');
1125 break;
1126 case GRADESUM:
1127 $grademethod = get_string('gradesum', 'scorm');
1128 break;
1129 case GRADESCOES:
1130 $grademethod = get_string('gradescoes', 'scorm');
1131 break;
1132 }
ca4eda13
DM
1133 } else {
1134 switch ($scorm->whatgrade) {
b1ca5d31
DM
1135 case HIGHESTATTEMPT:
1136 $grademethod = get_string('highestattempt', 'scorm');
1137 break;
1138 case AVERAGEATTEMPT:
1139 $grademethod = get_string('averageattempt', 'scorm');
1140 break;
1141 case FIRSTATTEMPT:
1142 $grademethod = get_string('firstattempt', 'scorm');
1143 break;
1144 case LASTATTEMPT:
1145 $grademethod = get_string('lastattempt', 'scorm');
1146 break;
1147 }
ca4eda13 1148 }
7554f671 1149
ca4eda13 1150 if (!empty($attempts)) {
b1ca5d31 1151 $i = 1;
ca4eda13 1152 foreach ($attempts as $attempt) {
6381fa56 1153 $gradereported = scorm_grade_user_attempt($scorm, $user->id, $attempt->attemptnumber);
4f4a8acf
DM
1154 if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) {
1155 $gradereported = $gradereported/$scorm->maxgrade;
1156 $gradereported = number_format($gradereported*100, 0) .'%';
1157 }
1158 $result .= get_string('gradeforattempt', 'scorm').' ' . $i . ': ' . $gradereported .'<br />';
b1ca5d31 1159 $i++;
6381fa56 1160 }
1161 }
b1ca5d31 1162 $calculatedgrade = scorm_grade_user($scorm, $user->id);
4f4a8acf
DM
1163 if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) {
1164 $calculatedgrade = $calculatedgrade/$scorm->maxgrade;
1165 $calculatedgrade = number_format($calculatedgrade*100, 0) .'%';
1166 }
6381fa56 1167 $result .= get_string('grademethod', 'scorm'). ': ' . $grademethod;
ca4eda13
DM
1168 if (empty($attempts)) {
1169 $result .= '<br />' . get_string('gradereported', 'scorm') . ': ' . get_string('none') . '<br />';
6381fa56 1170 } else {
ca4eda13 1171 $result .= '<br />' . get_string('gradereported', 'scorm') . ': ' . $calculatedgrade . '<br />';
6381fa56 1172 }
1173 $result .= '</p>';
1174 if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) {
ca4eda13 1175 $result .= '<p><font color="#cc0000">'.get_string('exceededmaxattempts', 'scorm').'</font></p>';
6381fa56 1176 }
d32e353e
DM
1177 if (!empty($cm)) {
1178 $context = context_module::instance($cm->id);
a12b1d75
DM
1179 if (has_capability('mod/scorm:deleteownresponses', $context) &&
1180 $DB->record_exists('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id))) {
1181 //check to see if any data is stored for this user:
d32e353e
DM
1182 $deleteurl = new moodle_url($PAGE->url, array('action'=>'delete', 'sesskey' => sesskey()));
1183 $result .= $OUTPUT->single_button($deleteurl, get_string('deleteallattempts', 'scorm'));
1184 }
1185 }
1186
1187
6381fa56 1188 return $result;
1189}
1190
1191/**
ca4eda13
DM
1192 * Get SCORM attempt count
1193 *
1194 * @param object $user Current context user
1195 * @param object $scorm a moodle scrom object - mdl_scorm
1196 * @param bool $attempts return the list of attempts
1197 * @return int - no. of attempts so far
1198 */
b1ca5d31 1199function scorm_get_attempt_count($userid, $scorm, $attempts_only=false) {
6381fa56 1200 global $DB;
1201 $attemptcount = 0;
1202 $element = 'cmi.core.score.raw';
78b203ca
DM
1203 if ($scorm->grademethod == GRADESCOES) {
1204 $element = 'cmi.core.lesson_status';
1205 }
e6402b54 1206 if (scorm_version_check($scorm->version, SCORM_13)) {
6381fa56 1207 $element = 'cmi.score.raw';
1208 }
ca4eda13 1209 $attempts = $DB->get_records_select('scorm_scoes_track', "element=? AND userid=? AND scormid=?", array($element, $userid, $scorm->id), 'attempt', 'DISTINCT attempt AS attemptnumber');
e24445c7
PH
1210 if ($attempts_only) {
1211 return $attempts;
1212 }
ca4eda13 1213 if (!empty($attempts)) {
6381fa56 1214 $attemptcount = count($attempts);
1215 }
1216 return $attemptcount;
1217}
1881df27 1218
1219/**
ca4eda13
DM
1220 * Figure out with this is a debug situation
1221 *
1222 * @param object $scorm a moodle scrom object - mdl_scorm
1223 * @return boolean - debugging true/false
1224 */
1881df27 1225function scorm_debugging($scorm) {
1226 global $CFG, $USER;
30fc6e2d 1227 $cfg_scorm = get_config('scorm');
7554f671 1228
30fc6e2d 1229 if (!$cfg_scorm->allowapidebug) {
1881df27 1230 return false;
1231 }
1232 $identifier = $USER->username.':'.$scorm->name;
30fc6e2d 1233 $test = $cfg_scorm->apidebugmask;
1881df27 1234 // check the regex is only a short list of safe characters
1235 if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) {
1236 return false;
1237 }
1238 $res = false;
1239 eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;');
1240 return $res;
1241}
7554f671 1242
1243/**
ca4eda13
DM
1244 * Delete Scorm tracks for selected users
1245 *
1246 * @param array $attemptids list of attempts that need to be deleted
1247 * @param int $scorm instance
1248 *
1249 * return bool true deleted all responses, false failed deleting an attempt - stopped here
1250 */
8716cc35 1251function scorm_delete_responses($attemptids, $scorm) {
ca4eda13 1252 if (!is_array($attemptids) || empty($attemptids)) {
7554f671 1253 return false;
1254 }
1255
ca4eda13
DM
1256 foreach ($attemptids as $num => $attemptid) {
1257 if (empty($attemptid)) {
7554f671 1258 unset($attemptids[$num]);
1259 }
1260 }
1261
ca4eda13 1262 foreach ($attemptids as $attempt) {
7554f671 1263 $keys = explode(':', $attempt);
1264 if (count($keys) == 2) {
1265 $userid = clean_param($keys[0], PARAM_INT);
1266 $attemptid = clean_param($keys[1], PARAM_INT);
8716cc35 1267 if (!$userid || !$attemptid || !scorm_delete_attempt($userid, $scorm, $attemptid)) {
7554f671 1268 return false;
1269 }
1270 } else {
1271 return false;
1272 }
1273 }
1274 return true;
1275}
1276
1277/**
ca4eda13
DM
1278 * Delete Scorm tracks for selected users
1279 *
1280 * @param int $userid ID of User
1281 * @param int $scormid ID of Scorm
1282 * @param int $attemptid user attempt that need to be deleted
1283 *
1284 * return bool true suceeded
1285 */
8716cc35 1286function scorm_delete_attempt($userid, $scorm, $attemptid) {
7554f671 1287 global $DB;
1288
8716cc35
DM
1289 $DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attemptid));
1290 include_once('lib.php');
1291 scorm_update_grades($scorm, $userid, true);
7554f671 1292 return true;
1293}
00c1677a
DM
1294
1295/**
2700b283 1296 * Converts SCORM duration notation to human-readable format
00c1677a 1297 * The function works with both SCORM 1.2 and SCORM 2004 time formats
2700b283 1298 * @param $duration string SCORM duration
00c1677a
DM
1299 * @return string human-readable date/time
1300 */
2700b283 1301function scorm_format_duration($duration) {
00c1677a 1302 // fetch date/time strings
119d64ee 1303 $stryears = get_string('years');
00c1677a 1304 $strmonths = get_string('nummonths');
119d64ee
DM
1305 $strdays = get_string('days');
1306 $strhours = get_string('hours');
1307 $strminutes = get_string('minutes');
1308 $strseconds = get_string('seconds');
64f93798 1309
2700b283 1310 if ($duration[0] == 'P') {
00c1677a
DM
1311 // if timestamp starts with 'P' - it's a SCORM 2004 format
1312 // this regexp discards empty sections, takes Month/Minute ambiguity into consideration,
1313 // and outputs filled sections, discarding leading zeroes and any format literals
1314 // also saves the only zero before seconds decimals (if there are any) and discards decimals if they are zero
1315 $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#',
1316 '#([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#' );
119d64ee
DM
1317 $replace = array( '$1', '$1', '$1', '$1$2 '.$strmonths.' ', '$1 '.$stryears.' ', '$1 '.$strdays.' ', '',
1318 '$1', '$1', 'S', '$1$2 '.$strminutes.' ', '$1 '.$strhours.' ', '0.$1 '.$strseconds, '$1 '.$strseconds, '');
00c1677a
DM
1319 } else {
1320 // else we have SCORM 1.2 format there
1321 // first convert the timestamp to some SCORM 2004-like format for conveniency
2700b283 1322 $duration = preg_replace('#^(\d+):(\d+):([\d.]+)$#', 'T$1H$2M$3S', $duration);
00c1677a
DM
1323 // then convert in the same way as SCORM 2004
1324 $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#' );
119d64ee 1325 $replace = array( 'T', '$1', '$1', 'S', '$1 '.$strhours.' ', '$1 '.$strminutes.' ', '0.$1 '.$strseconds, '$1 '.$strseconds, '' );
00c1677a 1326 }
64f93798 1327
2700b283 1328 $result = preg_replace($pattern, $replace, $duration);
00c1677a
DM
1329
1330 return $result;
1331}
0797000a
DM
1332
1333function scorm_get_toc($user,$scorm,$cmid,$toclink=TOCJSLINK,$currentorg='',$scoid='',$mode='normal',$attempt='',$play=false, $tocheader=false) {
1334 global $CFG, $DB, $PAGE, $OUTPUT;
1335
1336 $modestr = '';
1337 if ($mode == 'browse') {
1338 $modestr = '&amp;mode='.$mode;
1339 }
1340
1341 $result = new stdClass();
1342 if ($tocheader) {
1343 $result->toc = '<div id="scorm_layout">';
1344 $result->toc .= '<div id="scorm_toc">';
1345 $result->toc .= '<div id="scorm_tree">';
1346 }
1347 $result->toc .= '<ul>';
1348 $tocmenus = array();
1349 $result->prerequisites = true;
1350 $incomplete = false;
1351
1352 //
1353 // Get the current organization infos
1354 //
1355 if (!empty($currentorg)) {
1356 if (($organizationtitle = $DB->get_field('scorm_scoes','title', array('scorm'=>$scorm->id,'identifier'=>$currentorg))) != '') {
1357 if ($play) {
1358 $result->toctitle = "$organizationtitle";
1359 }
1360 else {
1361 $result->toc .= "\t<li>$organizationtitle</li>\n";
1362 }
1363 $tocmenus[] = $organizationtitle;
1364 }
1365 }
1366
1367 //
1368 // If not specified retrieve the last attempt number
1369 //
1370 if (empty($attempt)) {
1371 $attempt = scorm_get_attempt_count($user->id, $scorm);
1372 }
1373 $result->attemptleft = $scorm->maxattempt == 0 ? 1 : $scorm->maxattempt - $attempt;
1374 if ($scoes = scorm_get_scoes($scorm->id, $currentorg)){
1375 //
1376 // Retrieve user tracking data for each learning object
1377 //
1378 $usertracks = array();
1379 foreach ($scoes as $sco) {
1380 if (!empty($sco->launch)) {
0797000a
DM
1381 if ($usertrack = scorm_get_tracks($sco->id,$user->id,$attempt)) {
1382 if ($usertrack->status == '') {
1383 $usertrack->status = 'notattempted';
1384 }
1385 $usertracks[$sco->identifier] = $usertrack;
1386 }
1387 }
1388 }
1389
1390 $level=0;
1391 $sublist=1;
1392 $previd = 0;
1393 $nextid = 0;
1394 $findnext = false;
1395 $parents[$level]='/';
6917eee8 1396 $prevsco = '';
0797000a
DM
1397 foreach ($scoes as $pos => $sco) {
1398 $isvisible = false;
1399 $sco->title = $sco->title;
1400 if (!isset($sco->isvisible) || (isset($sco->isvisible) && ($sco->isvisible == 'true'))) {
1401 $isvisible = true;
1402 }
1403 if ($parents[$level] != $sco->parent) {
8ac8aae4
MS
1404 $newlevel = array_search($sco->parent,$parents);
1405 if ($newlevel !== false) {
0797000a
DM
1406 for ($i=0; $i<($level-$newlevel); $i++) {
1407 $result->toc .= "\t\t</li></ul></li>\n";
1408 }
1409 $level = $newlevel;
1410 } else {
1411 $i = $level;
1412 $closelist = '';
1413 while (($i > 0) && ($parents[$level] != $sco->parent)) {
8cb7096b 1414 if ($i === 1 && $level > 1) {
b6c88d75
DM
1415 $closelist .= "\t\t</ul></li>\n";
1416 } else {
1417 $closelist .= "\t</li></ul></li>\n";
1418 }
0797000a
DM
1419 $i--;
1420 }
1421 if (($i == 0) && ($sco->parent != $currentorg)) {
b6c88d75 1422 $result->toc .= "\n\t<ul>\n";
0797000a
DM
1423 $level++;
1424 } else {
1425 $result->toc .= $closelist;
1426 $level = $i;
1427 }
1428 $parents[$level] = $sco->parent;
1429 }
1430 }
1431 if ($isvisible) {
1432 $result->toc .= "<li>";
1433 }
1434 if (isset($scoes[$pos+1])) {
1435 $nextsco = $scoes[$pos+1];
1436 } else {
1437 $nextsco = false;
1438 }
1439 $nextisvisible = false;
1440 if (($nextsco !== false) && (!isset($nextsco->isvisible) || (isset($nextsco->isvisible) && ($nextsco->isvisible == 'true')))) {
1441 $nextisvisible = true;
1442 }
1443 if ($nextisvisible && ($nextsco !== false) && ($sco->parent != $nextsco->parent) &&
1444 (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
1445 $sublist++;
1446 }
1447 if (empty($sco->title)) {
1448 $sco->title = $sco->identifier;
1449 }
b6c88d75
DM
1450 if ($isvisible) {
1451 if (!empty($sco->launch)) {
0797000a
DM
1452 $score = '';
1453 if (empty($scoid) && ($mode != 'normal')) {
1454 $scoid = $sco->id;
1455 }
1456 if (isset($usertracks[$sco->identifier])) {
1457 $usertrack = $usertracks[$sco->identifier];
1458 $strstatus = get_string($usertrack->status,'scorm');
1459 if ($sco->scormtype == 'sco') {
1460 $statusicon = '<img src="'.$OUTPUT->pix_url($usertrack->status, 'scorm').'" alt="'.$strstatus.'" title="'.$strstatus.'" />';
1461 } else {
1462 $statusicon = '<img src="'.$OUTPUT->pix_url('assetc', 'scorm').'" alt="'.get_string('assetlaunched','scorm').'" title="'.get_string('assetlaunched','scorm').'" />';
1463 }
1464
1465 if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) {
1466 $incomplete = true;
1467 if ($play && empty($scoid)) {
1468 $scoid = $sco->id;
1469 }
1470 }
a3fc4b3a 1471 if ($usertrack->score_raw != '' && has_capability('mod/scorm:viewscores', context_module::instance($cmid))) {
0797000a
DM
1472 $score = '('.get_string('score','scorm').':&nbsp;'.$usertrack->score_raw.')';
1473 }
1474 $strsuspended = get_string('suspended','scorm');
1475 $exitvar = 'cmi.core.exit';
e6402b54 1476 if (scorm_version_check($scorm->version, SCORM_13)) {
0797000a
DM
1477 $exitvar = 'cmi.exit';
1478 }
1479 if ($incomplete && isset($usertrack->{$exitvar}) && ($usertrack->{$exitvar} == 'suspend')) {
1480 $statusicon = '<img src="'.$OUTPUT->pix_url('suspend', 'scorm').'" alt="'.$strstatus.' - '.$strsuspended.'" title="'.$strstatus.' - '.$strsuspended.'" />';
1481 }
1482 } else {
1483 if ($play && empty($scoid)) {
1484 $scoid = $sco->id;
1485 }
1486 $incomplete = true;
1487 if ($sco->scormtype == 'sco') {
1488 $statusicon = '<img src="'.$OUTPUT->pix_url('notattempted', 'scorm').'" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
1489 } else {
1490 $statusicon = '<img src="'.$OUTPUT->pix_url('asset', 'scorm').'" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
1491 }
1492 }
1493 if ($sco->id == $scoid) {
1494 $findnext = true;
1495 }
1496
1497 if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1) && ($nextsco!==false) && (!$findnext)) {
1498 if (!empty($sco->launch)) {
1499 $previd = $sco->id;
1500 }
1501 }
e6402b54 1502 if (scorm_version_check($scorm->version, SCORM_13)) {
0797000a
DM
1503 require_once($CFG->dirroot.'/mod/scorm/datamodels/sequencinglib.php');
1504 $prereq = scorm_seq_evaluate($sco->id,$usertracks);
1505 } else {
1506 //TODO: split check for sco->prerequisites only for AICC as I think that's the only case it's set.
1507 $prereq = empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites,$usertracks);
1508 }
1509 if ($prereq) {
1510 if ($sco->id == $scoid) {
1511 $result->prerequisites = true;
1512 }
6917eee8
DM
1513 if (!empty($prevsco) && scorm_version_check($scorm->version, SCORM_13) && !empty($prevsco->hidecontinue)) {
1514 $result->toc .= '<span>'.$statusicon.'&nbsp;'.format_string($sco->title).'</span>';
1515 } else if ($toclink == TOCFULLURL) { //display toc with urls for structure page
0797000a
DM
1516 $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&amp;currentorg='.$currentorg.$modestr.'&amp;scoid='.$sco->id;
1517 $result->toc .= $statusicon.'&nbsp;<a href="'.$url.'">'.format_string($sco->title).'</a>'.$score."\n";
1518 } else { //display toc for inside scorm player
1519 if ($sco->launch) {
1520 $link = 'a='.$scorm->id.'&scoid='.$sco->id.'&currentorg='.$currentorg.$modestr.'&attempt='.$attempt;
1521 $result->toc .= '<a title="'.$link.'">'.$statusicon.'&nbsp;'.format_string($sco->title).'&nbsp;'.$score.'</a>';
1522 } else {
1523 $result->toc .= '<span>'.$statusicon.'&nbsp;'.format_string($sco->title).'</span>';
1524 }
1525 }
1526 $tocmenus[$sco->id] = scorm_repeater('&minus;',$level) . '&gt;' . format_string($sco->title);
1527 } else {
1528 if ($sco->id == $scoid) {
1529 $result->prerequisites = false;
1530 }
1531 if ($play) {
1532 // should be disabled
1533 $result->toc .= '<span>'.$statusicon.'&nbsp;'.format_string($sco->title).'</span>';
1534 } else {
1535 $result->toc .= $statusicon.'&nbsp;'.format_string($sco->title)."\n";
1536 }
1537 }
b6c88d75
DM
1538 } else {
1539 $result->toc .= '&nbsp;'.format_string($sco->title);
1540 }
1541 if (($nextsco === false) || $nextsco->parent == $sco->parent) {
1542 $result->toc .= "</li>\n";
0797000a 1543 }
0797000a
DM
1544 }
1545 if (($nextsco !== false) && ($nextid == 0) && ($findnext)) {
1546 if (!empty($nextsco->launch)) {
1547 $nextid = $nextsco->id;
1548 }
1549 }
6917eee8 1550 $prevsco = $sco;
0797000a
DM
1551 }
1552 for ($i=0;$i<$level;$i++) {
1553 $result->toc .= "\t\t</ul></li>\n";
1554 }
1555
1556 if ($play) {
f35b5884
DM
1557 // it is possible that $scoid is still not set, in this case we don't want an empty object
1558 if ($scoid) {
1559 $sco = scorm_get_sco($scoid);
8a4561ac 1560 }
0797000a
DM
1561 $sco->previd = $previd;
1562 $sco->nextid = $nextid;
1563 $result->sco = $sco;
1564 $result->incomplete = $incomplete;
1565 } else {
1566 $result->incomplete = $incomplete;
1567 }
1568 }
1569 $result->toc .= '</ul>';
1570
1571 // NEW IMS TOC
1572 if ($tocheader) {
1573 $result->toc .= '</div></div></div>';
1574 $result->toc .= '<div id="scorm_navpanel"></div>';
1575 }
1576
0797000a 1577 $url = new moodle_url('/mod/scorm/player.php?a='.$scorm->id.'&currentorg='.$currentorg.$modestr);
3211569a 1578 $result->tocmenu = $OUTPUT->single_select($url, 'scoid', $tocmenus, $sco->id, null, "tocmenu");
0797000a
DM
1579
1580 return $result;
701570b5 1581}