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