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