Commit | Line | Data |
---|---|---|
e5dd8e3b | 1 | <?php |
f69db63e | 2 | |
9528568b | 3 | require_once("$CFG->dirroot/mod/scorm/lib.php"); |
64f93798 | 4 | require_once("$CFG->libdir/filelib.php"); |
9528568b | 5 | |
a30b6819 | 6 | /// Constants and settings for module scorm |
a679d64d | 7 | define('UPDATE_NEVER', '0'); |
8 | define('UPDATE_ONCHANGE', '1'); | |
9 | define('UPDATE_EVERYDAY', '2'); | |
10 | define('UPDATE_EVERYTIME', '3'); | |
11 | ||
b3659259 | 12 | define('SCO_ALL', 0); |
13 | define('SCO_DATA', 1); | |
14 | define('SCO_ONLY', 2); | |
a30b6819 | 15 | |
16 | define('GRADESCOES', '0'); | |
17 | define('GRADEHIGHEST', '1'); | |
18 | define('GRADEAVERAGE', '2'); | |
19 | define('GRADESUM', '3'); | |
a30b6819 | 20 | |
21 | define('HIGHESTATTEMPT', '0'); | |
22 | define('AVERAGEATTEMPT', '1'); | |
23 | define('FIRSTATTEMPT', '2'); | |
24 | define('LASTATTEMPT', '3'); | |
a30b6819 | 25 | |
9528568b | 26 | /// Local Library of functions for module scorm |
a30b6819 | 27 | |
64f93798 PS |
28 | /** |
29 | * @package mod-scorm | |
30 | * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} | |
31 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
32 | */ | |
33 | class scorm_package_file_info extends file_info_stored { | |
34 | public function get_parent() { | |
35 | if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { | |
36 | return $this->browser->get_file_info($this->context); | |
37 | } | |
38 | return parent::get_parent(); | |
39 | } | |
40 | public function get_visible_name() { | |
41 | if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') { | |
42 | return $this->topvisiblename; | |
43 | } | |
44 | return parent::get_visible_name(); | |
45 | } | |
46 | } | |
47 | ||
30fc6e2d | 48 | /** |
49 | * Returns an array of the popup options for SCORM and each options default value | |
7554f671 | 50 | * |
30fc6e2d | 51 | * @return array an array of popup options as the key and their defaults as the value |
52 | */ | |
53 | function scorm_get_popup_options_array(){ | |
54 | global $CFG; | |
55 | $cfg_scorm = get_config('scorm'); | |
7554f671 | 56 | |
57 | return array('resizable'=> isset($cfg_scorm->resizable) ? $cfg_scorm->resizable : 0, | |
58 | 'scrollbars'=> isset($cfg_scorm->scrollbars) ? $cfg_scorm->scrollbars : 0, | |
59 | 'directories'=> isset($cfg_scorm->directories) ? $cfg_scorm->directories : 0, | |
1adc77e6 | 60 | 'location'=> isset($cfg_scorm->location) ? $cfg_scorm->location : 0, |
7554f671 | 61 | 'menubar'=> isset($cfg_scorm->menubar) ? $cfg_scorm->menubar : 0, |
62 | 'toolbar'=> isset($cfg_scorm->toolbar) ? $cfg_scorm->toolbar : 0, | |
63 | 'status'=> isset($cfg_scorm->status) ? $cfg_scorm->status : 0); | |
30fc6e2d | 64 | } |
65 | ||
66 | /** | |
67 | * Returns an array of the array of what grade options | |
7554f671 | 68 | * |
30fc6e2d | 69 | * @return array an array of what grade options |
70 | */ | |
71 | function scorm_get_grade_method_array(){ | |
72 | return array (GRADESCOES => get_string('gradescoes', 'scorm'), | |
73 | GRADEHIGHEST => get_string('gradehighest', 'scorm'), | |
74 | GRADEAVERAGE => get_string('gradeaverage', 'scorm'), | |
7554f671 | 75 | GRADESUM => get_string('gradesum', 'scorm')); |
30fc6e2d | 76 | } |
77 | ||
78 | /** | |
79 | * Returns an array of the array of what grade options | |
7554f671 | 80 | * |
30fc6e2d | 81 | * @return array an array of what grade options |
82 | */ | |
83 | function scorm_get_what_grade_array(){ | |
84 | return array (HIGHESTATTEMPT => get_string('highestattempt', 'scorm'), | |
85 | AVERAGEATTEMPT => get_string('averageattempt', 'scorm'), | |
86 | FIRSTATTEMPT => get_string('firstattempt', 'scorm'), | |
87 | LASTATTEMPT => get_string('lastattempt', 'scorm')); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Returns an array of the array of skip view options | |
7554f671 | 92 | * |
30fc6e2d | 93 | * @return array an array of skip view options |
94 | */ | |
95 | function scorm_get_skip_view_array(){ | |
96 | return array(0 => get_string('never'), | |
97 | 1 => get_string('firstaccess','scorm'), | |
98 | 2 => get_string('always')); | |
99 | } | |
100 | ||
101 | /** | |
102 | * Returns an array of the array of hide table of contents options | |
7554f671 | 103 | * |
30fc6e2d | 104 | * @return array an array of hide table of contents options |
105 | */ | |
106 | function scorm_get_hidetoc_array(){ | |
107 | return array(0 =>get_string('sided','scorm'), | |
108 | 1 => get_string('hidden','scorm'), | |
109 | 2 => get_string('popupmenu','scorm')); | |
110 | } | |
111 | ||
112 | /** | |
113 | * Returns an array of the array of update frequency options | |
7554f671 | 114 | * |
30fc6e2d | 115 | * @return array an array of update frequency options |
116 | */ | |
117 | function scorm_get_updatefreq_array(){ | |
118 | return array(0 => get_string('never'), | |
119 | 1 => get_string('onchanges','scorm'), | |
120 | 2 => get_string('everyday','scorm'), | |
121 | 3 => get_string('everytime','scorm')); | |
122 | } | |
123 | ||
124 | /** | |
125 | * Returns an array of the array of popup display options | |
7554f671 | 126 | * |
30fc6e2d | 127 | * @return array an array of popup display options |
128 | */ | |
129 | function scorm_get_popup_display_array(){ | |
d67eb434 | 130 | return array(0 => get_string('currentwindow', 'scorm'), |
30fc6e2d | 131 | 1 => get_string('popup', 'scorm')); |
132 | } | |
133 | ||
134 | /** | |
135 | * Returns an array of the array of attempt options | |
7554f671 | 136 | * |
30fc6e2d | 137 | * @return array an array of attempt options |
138 | */ | |
139 | function scorm_get_attempts_array(){ | |
140 | $attempts = array(0 => get_string('nolimit','scorm'), | |
141 | 1 => get_string('attempt1','scorm')); | |
7554f671 | 142 | |
30fc6e2d | 143 | for ($i=2; $i<=6; $i++) { |
144 | $attempts[$i] = get_string('attemptsx','scorm', $i); | |
145 | } | |
7554f671 | 146 | |
30fc6e2d | 147 | return $attempts; |
148 | } | |
9528568b | 149 | /** |
150 | * Extracts scrom package, sets up all variables. | |
151 | * Called whenever scorm changes | |
152 | * @param object $scorm instance - fields are updated and changes saved into database | |
153 | * @param bool $full force full update if true | |
154 | * @return void | |
155 | */ | |
156 | function scorm_parse($scorm, $full) { | |
157 | global $CFG, $DB; | |
30fc6e2d | 158 | $cfg_scorm = get_config('scorm'); |
8aee93f1 | 159 | |
9528568b | 160 | if (!isset($scorm->cmid)) { |
161 | $cm = get_coursemodule_from_instance('scorm', $scorm->id); | |
162 | $scorm->cmid = $cm->id; | |
163 | } | |
164 | $context = get_context_instance(CONTEXT_MODULE, $scorm->cmid); | |
165 | $newhash = $scorm->sha1hash; | |
a30b6819 | 166 | |
9528568b | 167 | if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) { |
a30b6819 | 168 | |
9528568b | 169 | $fs = get_file_storage(); |
170 | $packagefile = false; | |
f69db63e | 171 | |
9528568b | 172 | if ($scorm->scormtype === SCORM_TYPE_LOCAL) { |
64f93798 | 173 | if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) { |
9528568b | 174 | $newhash = $packagefile->get_contenthash(); |
175 | } else { | |
176 | $newhash = null; | |
177 | } | |
178 | } else { | |
30fc6e2d | 179 | if (!$cfg_scorm->allowtypelocalsync) { |
9528568b | 180 | // sorry - localsync disabled |
181 | return; | |
182 | } | |
183 | if ($scorm->reference !== '' and (!$full or $scorm->sha1hash !== sha1($scorm->reference))) { | |
64f93798 PS |
184 | $fs->delete_area_files($context->id, 'mod_scorm', 'package'); |
185 | $file_record = array('contextid'=>$context->id, 'component'=>'mod_scorm', 'filearea'=>'package', 'itemid'=>0, 'filepath'=>'/'); | |
9528568b | 186 | if ($packagefile = $fs->create_file_from_url($file_record, $scorm->reference)) { |
187 | $newhash = sha1($scorm->reference); | |
5c1ac70c | 188 | } else { |
9528568b | 189 | $newhash = null; |
5c1ac70c | 190 | } |
f69db63e | 191 | } |
192 | } | |
f69db63e | 193 | |
9528568b | 194 | if ($packagefile) { |
195 | if (!$full and $packagefile and $scorm->sha1hash === $newhash) { | |
196 | if (strpos($scorm->version, 'SCORM') !== false) { | |
64f93798 | 197 | if ($fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) { |
9528568b | 198 | // no need to update |
199 | return; | |
e4aa175a | 200 | } |
9528568b | 201 | } else if (strpos($scorm->version, 'AICC') !== false) { |
202 | // TODO: add more sanity checks - something really exists in scorm_content area | |
203 | return; | |
204 | } | |
205 | } | |
206 | ||
207 | // now extract files | |
64f93798 | 208 | $fs->delete_area_files($context->id, 'mod_scorm', 'content'); |
9528568b | 209 | |
210 | $packer = get_file_packer('application/zip'); | |
64f93798 | 211 | $packagefile->extract_to_storage($packer, $context->id, 'mod_scorm', 'content', 0, '/'); |
9528568b | 212 | |
213 | } else if (!$full) { | |
214 | return; | |
215 | } | |
216 | ||
217 | ||
64f93798 | 218 | if ($manifest = $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) { |
9528568b | 219 | require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); |
220 | // SCORM | |
221 | if (!scorm_parse_scorm($scorm, $manifest)) { | |
222 | $scorm->version = 'ERROR'; | |
223 | } | |
224 | } else { | |
225 | require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php"); | |
226 | // AICC | |
227 | if (!scorm_parse_aicc($scorm)) { | |
228 | $scorm->version = 'ERROR'; | |
229 | } | |
230 | } | |
231 | ||
30fc6e2d | 232 | } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL and $cfg_scorm->allowtypeexternal) { |
9528568b | 233 | if (!$full and $scorm->sha1hash === sha1($scorm->reference)) { |
234 | return; | |
235 | } | |
236 | require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); | |
237 | // SCORM only, AICC can not be external | |
238 | if (!scorm_parse_scorm($scorm, $scorm->reference)) { | |
239 | $scorm->version = 'ERROR'; | |
240 | } | |
241 | $newhash = sha1($scorm->reference); | |
242 | ||
30fc6e2d | 243 | } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY and !empty($CFG->repositoryactivate) and $cfg_scorm->allowtypeimsrepository) { |
9528568b | 244 | if (!$full and $scorm->sha1hash === sha1($scorm->reference)) { |
245 | return; | |
246 | } | |
247 | require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); | |
248 | if (!scorm_parse_scorm($scorm, $CFG->repository.substr($scorm->reference,1).'/imsmanifest.xml')) { | |
249 | $scorm->version = 'ERROR'; | |
250 | } | |
251 | $newhash = sha1($scorm->reference); | |
252 | ||
e4aa175a | 253 | } else { |
9528568b | 254 | // sorry, disabled type |
255 | return; | |
e4aa175a | 256 | } |
9528568b | 257 | |
258 | $scorm->revision++; | |
259 | $scorm->sha1hash = $newhash; | |
260 | $DB->update_record('scorm', $scorm); | |
e4aa175a | 261 | } |
262 | ||
9528568b | 263 | |
2b3447c3 | 264 | function scorm_array_search($item, $needle, $haystacks, $strict=false) { |
265 | if (!empty($haystacks)) { | |
266 | foreach ($haystacks as $key => $element) { | |
267 | if ($strict) { | |
268 | if ($element->{$item} === $needle) { | |
269 | return $key; | |
270 | } | |
271 | } else { | |
272 | if ($element->{$item} == $needle) { | |
273 | return $key; | |
e4aa175a | 274 | } |
275 | } | |
e4aa175a | 276 | } |
277 | } | |
2b3447c3 | 278 | return false; |
e4aa175a | 279 | } |
280 | ||
2b3447c3 | 281 | function scorm_repeater($what, $times) { |
282 | if ($times <= 0) { | |
283 | return null; | |
284 | } | |
285 | $return = ''; | |
286 | for ($i=0; $i<$times;$i++) { | |
287 | $return .= $what; | |
288 | } | |
289 | return $return; | |
290 | } | |
e4aa175a | 291 | |
2b3447c3 | 292 | function scorm_external_link($link) { |
293 | // check if a link is external | |
294 | $result = false; | |
295 | $link = strtolower($link); | |
296 | if (substr($link,0,7) == 'http://') { | |
297 | $result = true; | |
298 | } else if (substr($link,0,8) == 'https://') { | |
299 | $result = true; | |
300 | } else if (substr($link,0,4) == 'www.') { | |
301 | $result = true; | |
302 | } | |
303 | return $result; | |
e4aa175a | 304 | } |
305 | ||
b3659259 | 306 | /** |
307 | * Returns an object containing all datas relative to the given sco ID | |
308 | * | |
309 | * @param integer $id The sco ID | |
310 | * @return mixed (false if sco id does not exists) | |
311 | */ | |
bd3523a5 | 312 | |
b3659259 | 313 | function scorm_get_sco($id,$what=SCO_ALL) { |
bf347041 | 314 | global $DB; |
315 | ||
316 | if ($sco = $DB->get_record('scorm_scoes', array('id'=>$id))) { | |
b3659259 | 317 | $sco = ($what == SCO_DATA) ? new stdClass() : $sco; |
bf347041 | 318 | if (($what != SCO_ONLY) && ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$id)))) { |
b3659259 | 319 | foreach ($scodatas as $scodata) { |
c31f631b | 320 | $sco->{$scodata->name} = $scodata->value; |
b3659259 | 321 | } |
bf347041 | 322 | } else if (($what != SCO_ONLY) && (!($scodatas = $DB->get_records('scorm_scoes_data', array('scoid'=>$id))))) { |
9528568b | 323 | $sco->parameters = ''; |
b3659259 | 324 | } |
325 | return $sco; | |
326 | } else { | |
327 | return false; | |
328 | } | |
329 | } | |
82605bea | 330 | |
331 | /** | |
332 | * Returns an object (array) containing all the scoes data related to the given sco ID | |
333 | * | |
334 | * @param integer $id The sco ID | |
335 | * @param integer $organisation an organisation ID - defaults to false if not required | |
336 | * @return mixed (false if there are no scoes or an array) | |
337 | */ | |
338 | ||
339 | function scorm_get_scoes($id,$organisation=false) { | |
b44c704f | 340 | global $DB; |
341 | ||
82605bea | 342 | $organizationsql = ''; |
b44c704f | 343 | $queryarray = array('scorm'=>$id); |
82605bea | 344 | if (!empty($organisation)) { |
b44c704f | 345 | $queryarray['organization'] = $organisation; |
82605bea | 346 | } |
b44c704f | 347 | if ($scoes = $DB->get_records('scorm_scoes', $queryarray, 'id ASC')) { |
82605bea | 348 | // drop keys so that it is a simple array as expected |
349 | $scoes = array_values($scoes); | |
350 | foreach ($scoes as $sco) { | |
b44c704f | 351 | if ($scodatas = $DB->get_records('scorm_scoes_data',array('scoid'=>$sco->id))) { |
82605bea | 352 | foreach ($scodatas as $scodata) { |
2fd0e9fe | 353 | $sco->{$scodata->name} = $scodata->value; |
82605bea | 354 | } |
355 | } | |
356 | } | |
357 | return $scoes; | |
358 | } else { | |
359 | return false; | |
360 | } | |
361 | } | |
362 | ||
6381fa56 | 363 | function scorm_insert_track($userid,$scormid,$scoid,$attempt,$element,$value,$forcecompleted=false) { |
86996ffe | 364 | global $DB, $CFG; |
bf347041 | 365 | |
e4aa175a | 366 | $id = null; |
6381fa56 | 367 | |
368 | if ($forcecompleted) { | |
369 | //TODO - this could be broadened to encompass SCORM 2004 in future | |
370 | if (($element == 'cmi.core.lesson_status') && ($value == 'incomplete')) { | |
371 | 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))) { | |
372 | $value = 'completed'; | |
373 | } | |
374 | } | |
375 | if ($element == 'cmi.core.score.raw') { | |
376 | 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))) { | |
377 | if ($tracktest->value == "incomplete") { | |
378 | $tracktest->value = "completed"; | |
379 | $idtest = $DB->update_record('scorm_scoes_track',$tracktest); | |
380 | } | |
381 | } | |
382 | } | |
383 | } | |
384 | ||
bf347041 | 385 | if ($track = $DB->get_record('scorm_scoes_track',array('userid'=>$userid, 'scormid'=>$scormid, 'scoid'=>$scoid, 'attempt'=>$attempt, 'element'=>$element))) { |
d1818fdc DM |
386 | if ($element != 'x.start.time' ) { //don't update x.start.time - keep the original value. |
387 | $track->value = addslashes_js($value); | |
388 | $track->timemodified = time(); | |
389 | $id = $DB->update_record('scorm_scoes_track',$track); | |
390 | } | |
e4aa175a | 391 | } else { |
392 | $track->userid = $userid; | |
393 | $track->scormid = $scormid; | |
394 | $track->scoid = $scoid; | |
395 | $track->attempt = $attempt; | |
396 | $track->element = $element; | |
898b2823 | 397 | $track->value = addslashes_js($value); |
e4aa175a | 398 | $track->timemodified = time(); |
bf347041 | 399 | $id = $DB->insert_record('scorm_scoes_track',$track); |
e4aa175a | 400 | } |
9528568b | 401 | |
9528568b | 402 | if (strstr($element, '.score.raw') || |
a0b36684 | 403 | (($element == 'cmi.core.lesson_status' || $element == 'cmi.completion_status') && ($track->value == 'completed' || $track->value == 'passed'))) { |
404 | $scorm = $DB->get_record('scorm', array('id' => $scormid)); | |
86996ffe | 405 | include_once($CFG->dirroot.'/mod/scorm/lib.php'); |
7f7946fd | 406 | scorm_update_grades($scorm, $userid); |
d23121ab | 407 | } |
9528568b | 408 | |
e4aa175a | 409 | return $id; |
410 | } | |
411 | ||
e4aa175a | 412 | function scorm_get_tracks($scoid,$userid,$attempt='') { |
e4aa175a | 413 | /// Gets all tracks of specified sco and user |
bf347041 | 414 | global $CFG, $DB; |
e4aa175a | 415 | |
416 | if (empty($attempt)) { | |
bf347041 | 417 | if ($scormid = $DB->get_field('scorm_scoes','scorm', array('id'=>$scoid))) { |
e4aa175a | 418 | $attempt = scorm_get_last_attempt($scormid,$userid); |
419 | } else { | |
420 | $attempt = 1; | |
421 | } | |
422 | } | |
bf347041 | 423 | if ($tracks = $DB->get_records('scorm_scoes_track', array('userid'=>$userid, 'scoid'=>$scoid, 'attempt'=>$attempt),'element ASC')) { |
e4aa175a | 424 | $usertrack->userid = $userid; |
9528568b | 425 | $usertrack->scoid = $scoid; |
a30b6819 | 426 | // Defined in order to unify scorm1.2 and scorm2004 |
e4aa175a | 427 | $usertrack->score_raw = ''; |
e4aa175a | 428 | $usertrack->status = ''; |
e4aa175a | 429 | $usertrack->total_time = '00:00:00'; |
430 | $usertrack->session_time = '00:00:00'; | |
431 | $usertrack->timemodified = 0; | |
432 | foreach ($tracks as $track) { | |
433 | $element = $track->element; | |
e23cc5d2 | 434 | $track->value = stripslashes($track->value); |
e4aa175a | 435 | $usertrack->{$element} = $track->value; |
436 | switch ($element) { | |
f69db63e | 437 | case 'cmi.core.lesson_status': |
438 | case 'cmi.completion_status': | |
439 | if ($track->value == 'not attempted') { | |
440 | $track->value = 'notattempted'; | |
9528568b | 441 | } |
f69db63e | 442 | $usertrack->status = $track->value; |
9528568b | 443 | break; |
e4aa175a | 444 | case 'cmi.core.score.raw': |
445 | case 'cmi.score.raw': | |
0d55dd4d | 446 | $usertrack->score_raw = (float) sprintf('%2.2f', $track->value); |
9528568b | 447 | break; |
e4aa175a | 448 | case 'cmi.core.session_time': |
449 | case 'cmi.session_time': | |
450 | $usertrack->session_time = $track->value; | |
9528568b | 451 | break; |
e4aa175a | 452 | case 'cmi.core.total_time': |
453 | case 'cmi.total_time': | |
454 | $usertrack->total_time = $track->value; | |
9528568b | 455 | break; |
456 | } | |
e4aa175a | 457 | if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) { |
458 | $usertrack->timemodified = $track->timemodified; | |
9528568b | 459 | } |
3505e82b | 460 | } |
9528568b | 461 | if (is_array($usertrack)) { |
07b905ae | 462 | ksort($usertrack); |
463 | } | |
e4aa175a | 464 | return $usertrack; |
465 | } else { | |
466 | return false; | |
467 | } | |
468 | } | |
469 | ||
51c1c4e1 | 470 | |
471 | /* Find the start and finsh time for a a given SCO attempt | |
472 | * | |
473 | * @param int $scormid SCORM Id | |
474 | * @param int $scoid SCO Id | |
475 | * @param int $userid User Id | |
476 | * @param int $attemt Attempt Id | |
477 | * | |
478 | * @return object start and finsh time EPOC secods | |
479 | * | |
480 | */ | |
481 | function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) { | |
4454447d | 482 | global $DB; |
51c1c4e1 | 483 | |
484 | $timedata = new object(); | |
485 | $sql = !empty($scoid) ? "userid=$userid AND scormid=$scormid AND scoid=$scoid AND attempt=$attempt" : "userid=$userid AND scormid=$scormid AND attempt=$attempt"; | |
486 | $tracks = $DB->get_records_select('scorm_scoes_track',"$sql ORDER BY timemodified ASC"); | |
487 | if ($tracks) { | |
488 | $tracks = array_values($tracks); | |
489 | } | |
490 | ||
c86a91d5 | 491 | if ($tracks) { |
51c1c4e1 | 492 | $timedata->start = $tracks[0]->timemodified; |
493 | } | |
494 | else { | |
495 | $timedata->start = false; | |
496 | } | |
497 | if ($tracks && $track = array_pop($tracks)) { | |
498 | $timedata->finish = $track->timemodified; | |
499 | } | |
500 | else { | |
501 | $timedata->finish = $timedata->start; | |
502 | } | |
503 | return $timedata; | |
504 | } | |
505 | ||
506 | ||
2b3447c3 | 507 | function scorm_get_user_data($userid) { |
bf347041 | 508 | global $DB; |
2b3447c3 | 509 | /// Gets user info required to display the table of scorm results |
510 | /// for report.php | |
e4aa175a | 511 | |
bf347041 | 512 | return $DB->get_record('user', array('id'=>$userid),'firstname, lastname, picture'); |
2b3447c3 | 513 | } |
e4aa175a | 514 | |
c0f6b608 | 515 | function scorm_grade_user_attempt($scorm, $userid, $attempt=1) { |
bf347041 | 516 | global $DB; |
9528568b | 517 | $attemptscore = NULL; |
a30b6819 | 518 | $attemptscore->scoes = 0; |
519 | $attemptscore->values = 0; | |
520 | $attemptscore->max = 0; | |
521 | $attemptscore->sum = 0; | |
522 | $attemptscore->lastmodify = 0; | |
9528568b | 523 | |
bf347041 | 524 | if (!$scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) { |
a30b6819 | 525 | return NULL; |
e4aa175a | 526 | } |
e4aa175a | 527 | |
9528568b | 528 | foreach ($scoes as $sco) { |
2b3447c3 | 529 | if ($userdata=scorm_get_tracks($sco->id, $userid,$attempt)) { |
530 | if (($userdata->status == 'completed') || ($userdata->status == 'passed')) { | |
a30b6819 | 531 | $attemptscore->scoes++; |
9528568b | 532 | } |
5c2aa157 | 533 | if (!empty($userdata->score_raw) || (isset($scorm->type) && $scorm->type=='sco' && isset($userdata->score_raw))) { |
a30b6819 | 534 | $attemptscore->values++; |
535 | $attemptscore->sum += $userdata->score_raw; | |
536 | $attemptscore->max = ($userdata->score_raw > $attemptscore->max)?$userdata->score_raw:$attemptscore->max; | |
537 | if (isset($userdata->timemodified) && ($userdata->timemodified > $attemptscore->lastmodify)) { | |
538 | $attemptscore->lastmodify = $userdata->timemodified; | |
539 | } else { | |
540 | $attemptscore->lastmodify = 0; | |
541 | } | |
9528568b | 542 | } |
543 | } | |
e4aa175a | 544 | } |
03751efe | 545 | switch ($scorm->grademethod) { |
a30b6819 | 546 | case GRADEHIGHEST: |
0d55dd4d | 547 | $score = (float) $attemptscore->max; |
9528568b | 548 | break; |
a30b6819 | 549 | case GRADEAVERAGE: |
550 | if ($attemptscore->values > 0) { | |
551 | $score = $attemptscore->sum/$attemptscore->values; | |
5c1ac70c | 552 | } else { |
a30b6819 | 553 | $score = 0; |
9528568b | 554 | } |
555 | break; | |
a30b6819 | 556 | case GRADESUM: |
557 | $score = $attemptscore->sum; | |
9528568b | 558 | break; |
a30b6819 | 559 | case GRADESCOES: |
560 | $score = $attemptscore->scoes; | |
7ef16bf9 | 561 | break; |
562 | default: | |
563 | $score = $attemptscore->max; // Remote Learner GRADEHIGHEST is default | |
5c1ac70c | 564 | } |
a30b6819 | 565 | |
c0f6b608 | 566 | return $score; |
a30b6819 | 567 | } |
568 | ||
8c5516dc | 569 | function scorm_grade_user($scorm, $userid) { |
a30b6819 | 570 | |
03751efe | 571 | // ensure we dont grade user beyond $scorm->maxattempt settings |
7ce6eb87 | 572 | $lastattempt = scorm_get_last_attempt($scorm->id, $userid); |
573 | if($scorm->maxattempt != 0 && $lastattempt >= $scorm->maxattempt){ | |
574 | $lastattempt = $scorm->maxattempt; | |
575 | } | |
576 | ||
03751efe | 577 | switch ($scorm->whatgrade) { |
a30b6819 | 578 | case FIRSTATTEMPT: |
8c5516dc | 579 | return scorm_grade_user_attempt($scorm, $userid, 1); |
9528568b | 580 | break; |
a30b6819 | 581 | case LASTATTEMPT: |
8c5516dc | 582 | return scorm_grade_user_attempt($scorm, $userid, scorm_get_last_completed_attempt($scorm->id, $userid)); |
a30b6819 | 583 | break; |
584 | case HIGHESTATTEMPT: | |
a30b6819 | 585 | $maxscore = 0; |
586 | $attempttime = 0; | |
587 | for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { | |
8c5516dc DM |
588 | $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt); |
589 | $maxscore = $attemptscore > $maxscore ? $attemptscore: $maxscore; | |
a30b6819 | 590 | } |
8c5516dc DM |
591 | return $maxscore; |
592 | ||
a30b6819 | 593 | break; |
594 | case AVERAGEATTEMPT: | |
595 | $lastattempt = scorm_get_last_attempt($scorm->id, $userid); | |
596 | $sumscore = 0; | |
597 | for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { | |
8c5516dc DM |
598 | $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt); |
599 | $sumscore += $attemptscore; | |
a30b6819 | 600 | } |
601 | ||
602 | if ($lastattempt > 0) { | |
603 | $score = $sumscore / $lastattempt; | |
604 | } else { | |
605 | $score = 0; | |
606 | } | |
607 | ||
8c5516dc | 608 | return $score; |
a30b6819 | 609 | break; |
610 | } | |
e4aa175a | 611 | } |
612 | ||
8e45ba45 | 613 | function scorm_count_launchable($scormid,$organization='') { |
bf347041 | 614 | global $DB; |
615 | ||
616 | $sqlorganization = ''; | |
617 | $params = array($scormid); | |
8e45ba45 | 618 | if (!empty($organization)) { |
bf347041 | 619 | $sqlorganization = " AND organization=?"; |
620 | $params[] = $organization; | |
8e45ba45 | 621 | } |
6280b17a | 622 | $params []= ''; // empty launch |
623 | return $DB->count_records_select('scorm_scoes',"scorm = ? $sqlorganization AND launch <> ?", $params); | |
e4aa175a | 624 | } |
625 | ||
2b3447c3 | 626 | function scorm_get_last_attempt($scormid, $userid) { |
bf347041 | 627 | global $DB; |
628 | ||
2b3447c3 | 629 | /// Find the last attempt number for the given user id and scorm id |
bf347041 | 630 | if ($lastattempt = $DB->get_record('scorm_scoes_track', array('userid'=>$userid, 'scormid'=>$scormid), 'max(attempt) as a')) { |
2b3447c3 | 631 | if (empty($lastattempt->a)) { |
632 | return '1'; | |
633 | } else { | |
634 | return $lastattempt->a; | |
e4aa175a | 635 | } |
6381fa56 | 636 | } else { |
637 | return false; | |
e4aa175a | 638 | } |
e4aa175a | 639 | } |
640 | ||
2276208a DM |
641 | function scorm_get_last_completed_attempt($scormid, $userid) { |
642 | global $DB; | |
643 | ||
644 | /// Find the last attempt number for the given user id and scorm id | |
645 | if ($lastattempt = $DB->get_record('scorm_scoes_track', array('userid'=>$userid, 'scormid'=>$scormid, 'value'=> 'completed'), 'max(attempt) as a')) { | |
646 | if (empty($lastattempt->a)) { | |
647 | return '1'; | |
648 | } else { | |
649 | return $lastattempt->a; | |
650 | } | |
651 | } else { | |
652 | return false; | |
653 | } | |
654 | } | |
655 | ||
e4aa175a | 656 | function scorm_course_format_display($user,$course) { |
f2a1963c | 657 | global $CFG, $DB, $PAGE, $OUTPUT; |
e4aa175a | 658 | |
659 | $strupdate = get_string('update'); | |
660 | $strmodule = get_string('modulename','scorm'); | |
77bf0c29 | 661 | $context = get_context_instance(CONTEXT_COURSE,$course->id); |
e4aa175a | 662 | |
663 | echo '<div class="mod-scorm">'; | |
664 | if ($scorms = get_all_instances_in_course('scorm', $course)) { | |
9528568b | 665 | // The module SCORM activity with the least id is the course |
e4aa175a | 666 | $scorm = current($scorms); |
667 | if (! $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id)) { | |
08b56f93 | 668 | print_error('invalidcoursemodule'); |
e4aa175a | 669 | } |
670 | $colspan = ''; | |
671 | $headertext = '<table width="100%"><tr><td class="title">'.get_string('name').': <b>'.format_string($scorm->name).'</b>'; | |
2b3447c3 | 672 | if (has_capability('moodle/course:manageactivities', $context)) { |
830dd6e9 | 673 | if ($PAGE->user_is_editing()) { |
e4aa175a | 674 | // Display update icon |
675 | $path = $CFG->wwwroot.'/course'; | |
676 | $headertext .= '<span class="commands">'. | |
677 | '<a title="'.$strupdate.'" href="'.$path.'/mod.php?update='.$cm->id.'&sesskey='.sesskey().'">'. | |
b5d0cafc | 678 | '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="'.$strupdate.'" /></a></span>'; |
e4aa175a | 679 | } |
680 | $headertext .= '</td>'; | |
681 | // Display report link | |
bf347041 | 682 | $trackedusers = $DB->get_record('scorm_scoes_track', array('scormid'=>$scorm->id), 'count(distinct(userid)) as c'); |
e4aa175a | 683 | if ($trackedusers->c > 0) { |
684 | $headertext .= '<td class="reportlink">'. | |
4aea3cc7 | 685 | '<a href="'.$CFG->wwwroot.'/mod/scorm/report.php?id='.$cm->id.'">'. |
e4aa175a | 686 | get_string('viewallreports','scorm',$trackedusers->c).'</a>'; |
687 | } else { | |
688 | $headertext .= '<td class="reportlink">'.get_string('noreports','scorm'); | |
689 | } | |
690 | $colspan = ' colspan="2"'; | |
9528568b | 691 | } |
d0bcf735 | 692 | $options = (object)array('noclean'=>true); |
ac3668bf | 693 | $headertext .= '</td></tr><tr><td'.$colspan.'>'.get_string('summary').':<br />'.format_module_intro('scorm', $scorm, $scorm->coursemodule).'</td></tr></table>'; |
6aff538a | 694 | echo $OUTPUT->box($headertext,'generalbox boxwidthwide'); |
e4aa175a | 695 | scorm_view_display($user, $scorm, 'view.php?id='.$course->id, $cm, '100%'); |
696 | } else { | |
0d699c24 | 697 | if (has_capability('moodle/course:update', $context)) { |
e4aa175a | 698 | // Create a new activity |
2b3447c3 | 699 | redirect($CFG->wwwroot.'/course/mod.php?id='.$course->id.'&section=0&sesskey='.sesskey().'&add=scorm'); |
e4aa175a | 700 | } else { |
6aff538a | 701 | echo $OUTPUT->notification('Could not find a scorm course here'); |
e4aa175a | 702 | } |
703 | } | |
704 | echo '</div>'; | |
705 | } | |
706 | ||
2b3447c3 | 707 | function scorm_view_display ($user, $scorm, $action, $cm, $boxwidth='') { |
4eeec9ac | 708 | global $CFG, $DB, $PAGE, $OUTPUT; |
ab3b00e1 | 709 | |
9528568b | 710 | if ($scorm->updatefreq == UPDATE_EVERYTIME) { |
711 | scorm_parse($scorm, false); | |
ab3b00e1 | 712 | } |
713 | ||
e4aa175a | 714 | $organization = optional_param('organization', '', PARAM_INT); |
715 | ||
6381fa56 | 716 | if($scorm->displaycoursestructure == 1) { |
6aff538a | 717 | echo $OUTPUT->box_start('generalbox boxaligncenter'); |
e4aa175a | 718 | ?> |
29a43013 | 719 | <div class="structurehead"><?php print_string('contents','scorm') ?></div> |
e4aa175a | 720 | <?php |
6381fa56 | 721 | } |
e4aa175a | 722 | if (empty($organization)) { |
723 | $organization = $scorm->launch; | |
724 | } | |
07b905ae | 725 | if ($orgs = $DB->get_records_menu('scorm_scoes', array('scorm'=>$scorm->id, 'organization'=>'', 'launch'=>''), 'id', 'id,title')) { |
e4aa175a | 726 | if (count($orgs) > 1) { |
edc28287 PS |
727 | $select = new single_select(new moodle_url($action), 'organization', $orgs, $organization, null); |
728 | $select->lable = get_string('organizations','scorm'); | |
729 | $select->class = 'scorm-center'; | |
730 | echo $OUTPUT->render($select); | |
e4aa175a | 731 | } |
732 | } | |
733 | $orgidentifier = ''; | |
b3659259 | 734 | if ($sco = scorm_get_sco($organization, SCO_ONLY)) { |
735 | if (($sco->organization == '') && ($sco->launch == '')) { | |
736 | $orgidentifier = $sco->identifier; | |
e4aa175a | 737 | } else { |
b3659259 | 738 | $orgidentifier = $sco->organization; |
e4aa175a | 739 | } |
740 | } | |
28ffbff3 | 741 | |
742 | /* | |
743 | $orgidentifier = ''; | |
bf347041 | 744 | if ($org = $DB->get_record('scorm_scoes', array('id'=>$organization))) { |
28ffbff3 | 745 | if (($org->organization == '') && ($org->launch == '')) { |
746 | $orgidentifier = $org->identifier; | |
747 | } else { | |
748 | $orgidentifier = $org->organization; | |
749 | } | |
750 | }*/ | |
751 | ||
2b3447c3 | 752 | $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe |
dbe7e6f6 | 753 | if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) { |
754 | $scorm->version = 'scorm_12'; | |
755 | } | |
2b3447c3 | 756 | require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php'); |
757 | ||
e4aa175a | 758 | $result = scorm_get_toc($user,$scorm,'structlist',$orgidentifier); |
759 | $incomplete = $result->incomplete; | |
28ffbff3 | 760 | |
6381fa56 | 761 | // do we want the TOC to be displayed? |
762 | if($scorm->displaycoursestructure == 1) { | |
763 | echo $result->toc; | |
6aff538a | 764 | echo $OUTPUT->box_end(); |
6381fa56 | 765 | } |
7554f671 | 766 | |
6381fa56 | 767 | // is this the first attempt ? |
768 | $attemptcount = scorm_get_attempt_count($user, $scorm); | |
7554f671 | 769 | |
6381fa56 | 770 | // do not give the player launch FORM if the SCORM object is locked after the final attempt |
771 | if ($scorm->lastattemptlock == 0 || $result->attemptleft > 0) { | |
e4aa175a | 772 | ?> |
52a9a9b5 | 773 | <div class="scorm-center"> |
4675994a | 774 | <form id="theform" method="post" action="<?php echo $CFG->wwwroot ?>/mod/scorm/player.php"> |
e4aa175a | 775 | <?php |
e4aa175a | 776 | if ($scorm->hidebrowse == 0) { |
8949f8df | 777 | print_string('mode','scorm'); |
e4aa175a | 778 | echo ': <input type="radio" id="b" name="mode" value="browse" /><label for="b">'.get_string('browse','scorm').'</label>'."\n"; |
76ea4fb4 | 779 | echo '<input type="radio" id="n" name="mode" value="normal" checked="checked" /><label for="n">'.get_string('normal','scorm')."</label>\n"; |
e4aa175a | 780 | } else { |
76ea4fb4 | 781 | echo '<input type="hidden" name="mode" value="normal" />'."\n"; |
e4aa175a | 782 | } |
6381fa56 | 783 | if ($scorm->forcenewattempt == 1) { |
784 | if ($incomplete === false) { | |
785 | echo '<input type="hidden" name="newattempt" value="on" />'."\n"; | |
786 | } | |
787 | } elseif ($attemptcount != 0 && ($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) { | |
e4aa175a | 788 | ?> |
6381fa56 | 789 | <br /> |
790 | <input type="checkbox" id="a" name="newattempt" /> | |
791 | <label for="a"><?php print_string('newattempt','scorm') ?></label> | |
e4aa175a | 792 | <?php |
793 | } | |
794 | ?> | |
795 | <br /> | |
4675994a | 796 | <input type="hidden" name="scoid"/> |
3d00d6ab | 797 | <input type="hidden" name="cm" value="<?php echo $cm->id ?>"/> |
e4aa175a | 798 | <input type="hidden" name="currentorg" value="<?php echo $orgidentifier ?>" /> |
2b600d16 | 799 | <input type="submit" value="<?php print_string('enter','scorm') ?>" /> |
e4aa175a | 800 | </form> |
801 | </div> | |
802 | <?php | |
6381fa56 | 803 | } |
e4aa175a | 804 | } |
6280b17a | 805 | |
489797d9 | 806 | function scorm_simple_play($scorm,$user, $context) { |
bf347041 | 807 | global $DB; |
808 | ||
9316beca | 809 | $result = false; |
810 | ||
811 | if ($scorm->updatefreq == UPDATE_EVERYTIME) { | |
812 | scorm_parse($scorm, false); | |
813 | } | |
64f93798 | 814 | 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 |
815 | return $result; |
816 | } | |
9316beca | 817 | |
489797d9 | 818 | $scoes = $DB->get_records_select('scorm_scoes', 'scorm = ? AND launch <> ?', array($scorm->id, $DB->sql_empty()), 'id', 'id'); |
9316beca | 819 | |
7325b016 | 820 | if ($scoes) { |
9316beca | 821 | if ($scorm->skipview >= 1) { |
822 | $sco = current($scoes); | |
823 | if (scorm_get_tracks($sco->id,$user->id) === false) { | |
824 | header('Location: player.php?a='.$scorm->id.'&scoid='.$sco->id); | |
825 | $result = true; | |
826 | } else if ($scorm->skipview == 2) { | |
827 | header('Location: player.php?a='.$scorm->id.'&scoid='.$sco->id); | |
828 | $result = true; | |
829 | } | |
830 | } | |
831 | } | |
832 | return $result; | |
28ffbff3 | 833 | } |
d8c9d8a1 | 834 | |
835 | function scorm_get_count_users($scormid, $groupingid=null) { | |
bf347041 | 836 | global $CFG, $DB; |
9528568b | 837 | |
98da6021 | 838 | if (!empty($groupingid)) { |
d8c9d8a1 | 839 | $sql = "SELECT COUNT(DISTINCT st.userid) |
bf347041 | 840 | FROM {scorm_scoes_track} st |
841 | INNER JOIN {groups_members} gm ON st.userid = gm.userid | |
9528568b | 842 | INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid |
bf347041 | 843 | WHERE st.scormid = ? AND gg.groupingid = ? |
d8c9d8a1 | 844 | "; |
bf347041 | 845 | $params = array($scormid, $groupingid); |
d8c9d8a1 | 846 | } else { |
847 | $sql = "SELECT COUNT(DISTINCT st.userid) | |
9528568b | 848 | FROM {scorm_scoes_track} st |
bf347041 | 849 | WHERE st.scormid = ? |
d8c9d8a1 | 850 | "; |
bf347041 | 851 | $params = array($scormid); |
d8c9d8a1 | 852 | } |
9528568b | 853 | |
bf347041 | 854 | return ($DB->count_records_sql($sql, $params)); |
d8c9d8a1 | 855 | } |
856 | ||
527af457 | 857 | /** |
858 | * Build up the JavaScript representation of an array element | |
859 | * | |
860 | * @param string $sversion SCORM API version | |
861 | * @param array $userdata User track data | |
862 | * @param string $element_name Name of array element to get values for | |
863 | * @param array $children list of sub elements of this array element that also need instantiating | |
864 | * @return None | |
7554f671 | 865 | */ |
527af457 | 866 | function scorm_reconstitute_array_element($sversion, $userdata, $element_name, $children) { |
867 | // reconstitute comments_from_learner and comments_from_lms | |
868 | $current = ''; | |
bf34ac0f | 869 | $current_subelement = ''; |
870 | $current_sub = ''; | |
527af457 | 871 | $count = 0; |
bf34ac0f | 872 | $count_sub = 0; |
6b635d44 DM |
873 | $scormseperator = '_'; |
874 | if ($sversion == 'scorm_13') { //scorm 1.3 elements use a . instead of an _ | |
875 | $scormseperator = '.'; | |
876 | } | |
527af457 | 877 | // filter out the ones we want |
878 | $element_list = array(); | |
879 | foreach($userdata as $element => $value){ | |
880 | if (substr($element,0,strlen($element_name)) == $element_name) { | |
881 | $element_list[$element] = $value; | |
882 | } | |
883 | } | |
7554f671 | 884 | |
527af457 | 885 | // sort elements in .n array order |
886 | uksort($element_list, "scorm_element_cmp"); | |
7554f671 | 887 | |
527af457 | 888 | // generate JavaScript |
889 | foreach($element_list as $element => $value){ | |
890 | if ($sversion == 'scorm_13') { | |
b6293233 | 891 | $element = preg_replace('/\.(\d+)\./', ".N\$1.", $element); |
527af457 | 892 | preg_match('/\.(N\d+)\./', $element, $matches); |
893 | } else { | |
b6293233 | 894 | $element = preg_replace('/\.(\d+)\./', "_\$1.", $element); |
527af457 | 895 | preg_match('/\_(\d+)\./', $element, $matches); |
896 | } | |
897 | if (count($matches) > 0 && $current != $matches[1]) { | |
bf34ac0f | 898 | if ($count_sub > 0) { |
6b635d44 | 899 | echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; |
bf34ac0f | 900 | } |
7554f671 | 901 | $current = $matches[1]; |
527af457 | 902 | $count++; |
bf34ac0f | 903 | $current_subelement = ''; |
904 | $current_sub = ''; | |
905 | $count_sub = 0; | |
527af457 | 906 | $end = strpos($element,$matches[1])+strlen($matches[1]); |
907 | $subelement = substr($element,0,$end); | |
908 | echo ' '.$subelement." = new Object();\n"; | |
909 | // now add the children | |
910 | foreach ($children as $child) { | |
911 | echo ' '.$subelement.".".$child." = new Object();\n"; | |
912 | echo ' '.$subelement.".".$child."._children = ".$child."_children;\n"; | |
913 | } | |
914 | } | |
7554f671 | 915 | |
bf34ac0f | 916 | // now - flesh out the second level elements if there are any |
917 | if ($sversion == 'scorm_13') { | |
b6293233 | 918 | $element = preg_replace('/(.*?\.N\d+\..*?)\.(\d+)\./', "\$1.N\$2.", $element); |
bf34ac0f | 919 | preg_match('/.*?\.N\d+\.(.*?)\.(N\d+)\./', $element, $matches); |
920 | } else { | |
b6293233 | 921 | $element = preg_replace('/(.*?\_\d+\..*?)\.(\d+)\./', "\$1_\$2.", $element); |
bf34ac0f | 922 | preg_match('/.*?\_\d+\.(.*?)\_(\d+)\./', $element, $matches); |
923 | } | |
7554f671 | 924 | |
bf34ac0f | 925 | // check the sub element type |
926 | if (count($matches) > 0 && $current_subelement != $matches[1]) { | |
927 | if ($count_sub > 0) { | |
03e9a122 | 928 | echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; |
bf34ac0f | 929 | } |
930 | $current_subelement = $matches[1]; | |
931 | $current_sub = ''; | |
932 | $count_sub = 0; | |
933 | $end = strpos($element,$matches[1])+strlen($matches[1]); | |
934 | $subelement = substr($element,0,$end); | |
935 | echo ' '.$subelement." = new Object();\n"; | |
936 | } | |
7554f671 | 937 | |
bf34ac0f | 938 | // now check the subelement subscript |
939 | if (count($matches) > 0 && $current_sub != $matches[2]) { | |
7554f671 | 940 | $current_sub = $matches[2]; |
bf34ac0f | 941 | $count_sub++; |
942 | $end = strrpos($element,$matches[2])+strlen($matches[2]); | |
943 | $subelement = substr($element,0,$end); | |
944 | echo ' '.$subelement." = new Object();\n"; | |
945 | } | |
7554f671 | 946 | |
527af457 | 947 | echo ' '.$element.' = \''.$value."';\n"; |
948 | } | |
bf34ac0f | 949 | if ($count_sub > 0) { |
6b635d44 | 950 | echo ' '.$element_name.$scormseperator.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; |
bf34ac0f | 951 | } |
527af457 | 952 | if ($count > 0) { |
953 | echo ' '.$element_name.'._count = '.$count.";\n"; | |
954 | } | |
955 | } | |
956 | ||
957 | /** | |
958 | * Build up the JavaScript representation of an array element | |
959 | * | |
960 | * @param string $a left array element | |
961 | * @param string $b right array element | |
962 | * @return comparator - 0,1,-1 | |
7554f671 | 963 | */ |
527af457 | 964 | function scorm_element_cmp($a, $b) { |
bf34ac0f | 965 | preg_match('/.*?(\d+)\./', $a, $matches); |
527af457 | 966 | $left = intval($matches[1]); |
bf34ac0f | 967 | preg_match('/.?(\d+)\./', $b, $matches); |
527af457 | 968 | $right = intval($matches[1]); |
969 | if ($left < $right) { | |
970 | return -1; // smaller | |
971 | } elseif ($left > $right) { | |
972 | return 1; // bigger | |
973 | } else { | |
bf34ac0f | 974 | // look for a second level qualifier eg cmi.interactions_0.correct_responses_0.pattern |
975 | if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $a, $matches)) { | |
976 | $leftterm = intval($matches[2]); | |
977 | $left = intval($matches[3]); | |
978 | if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $b, $matches)) { | |
979 | $rightterm = intval($matches[2]); | |
980 | $right = intval($matches[3]); | |
981 | if ($leftterm < $rightterm) { | |
982 | return -1; // smaller | |
983 | } elseif ($leftterm > $rightterm) { | |
984 | return 1; // bigger | |
985 | } else { | |
986 | if ($left < $right) { | |
987 | return -1; // smaller | |
988 | } elseif ($left > $right) { | |
989 | return 1; // bigger | |
990 | } | |
991 | } | |
992 | } | |
993 | } | |
994 | // fall back for no second level matches or second level matches are equal | |
527af457 | 995 | return 0; // equal to |
996 | } | |
997 | } | |
6381fa56 | 998 | |
999 | /** | |
1000 | * Generate the user attempt status string | |
1001 | * | |
1002 | * @param object $user Current context user | |
1003 | * @param object $scorm a moodle scrom object - mdl_scorm | |
1004 | * @return string - Attempt status string | |
7554f671 | 1005 | */ |
6381fa56 | 1006 | function scorm_get_attempt_status($user, $scorm) { |
1007 | global $DB; | |
7554f671 | 1008 | |
e24445c7 | 1009 | $attempts = scorm_get_attempt_count($user, $scorm, true); |
6381fa56 | 1010 | if(empty($attempts)) { |
1011 | $attemptcount = 0; | |
1012 | } else { | |
1013 | $attemptcount = count($attempts); | |
1014 | } | |
7554f671 | 1015 | |
6381fa56 | 1016 | $result = '<p>'.get_string('noattemptsallowed', 'scorm').': '; |
1017 | if ($scorm->maxattempt > 0) { | |
1018 | $result .= $scorm->maxattempt . '<BR>'; | |
1019 | } else { | |
1020 | $result .= get_string('unlimited').'<BR>'; | |
1021 | } | |
1022 | $result .= get_string('noattemptsmade', 'scorm').': ' . $attemptcount . '<BR>'; | |
1023 | ||
1024 | $gradereported = 0; | |
1025 | $gradesum = 0; | |
1026 | switch ($scorm->grademethod) { | |
1027 | case GRADEHIGHEST: | |
1028 | $grademethod = get_string('gradehighest', 'scorm'); | |
1029 | break; | |
1030 | case GRADEAVERAGE: | |
1031 | $grademethod = get_string('gradeaverage', 'scorm'); | |
1032 | break; | |
1033 | case GRADESUM: | |
1034 | $grademethod = get_string('gradesum', 'scorm'); | |
1035 | break; | |
1036 | case GRADESCOES: | |
1037 | $grademethod = get_string('gradescoes', 'scorm'); | |
1038 | break; | |
1039 | } | |
7554f671 | 1040 | |
6381fa56 | 1041 | if(!empty($attempts)) { |
1042 | foreach($attempts as $attempt) { | |
1043 | $gradereported = scorm_grade_user_attempt($scorm, $user->id, $attempt->attemptnumber); | |
4572edaa | 1044 | $result .= get_string('gradeforattempt', 'scorm').' ' . $attempt->attemptnumber . ': ' . $gradereported .'%<BR>'; |
6381fa56 | 1045 | } |
1046 | } | |
1047 | ||
1048 | $result .= get_string('grademethod', 'scorm'). ': ' . $grademethod; | |
1049 | if(empty($attempts)) { | |
1050 | $result .= '<BR>' . get_string('gradereported','scorm') . ': ' . get_string('none') . '<BR>'; | |
1051 | } else { | |
1052 | $result .= '<BR>' . get_string('gradereported','scorm') . ': ' . $gradereported . ($scorm->grademethod == GRADESCOES ? '' : '%') .'<BR>'; | |
1053 | } | |
1054 | $result .= '</p>'; | |
1055 | if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) { | |
1056 | $result .= '<p><font color="#cc0000">'.get_string('exceededmaxattempts','scorm').'</font></p>'; | |
1057 | } | |
1058 | return $result; | |
1059 | } | |
1060 | ||
1061 | /** | |
1062 | * Get SCORM attempt count | |
1063 | * | |
1064 | * @param object $user Current context user | |
1065 | * @param object $scorm a moodle scrom object - mdl_scorm | |
e24445c7 | 1066 | * @param bool $attempts return the list of attempts |
6381fa56 | 1067 | * @return int - no. of attempts so far |
7554f671 | 1068 | */ |
e24445c7 | 1069 | function scorm_get_attempt_count($user, $scorm, $attempts_only=false) { |
6381fa56 | 1070 | global $DB; |
1071 | $attemptcount = 0; | |
1072 | $element = 'cmi.core.score.raw'; | |
1073 | if ($scorm->version == 'scorm1_3') { | |
1074 | $element = 'cmi.score.raw'; | |
1075 | } | |
e24445c7 PH |
1076 | $attempts = $DB->get_records_select('scorm_scoes_track',"element=? AND userid=? AND scormid=?", array($element, $user->id, $scorm->id),'attempt','DISTINCT attempt AS attemptnumber'); |
1077 | if ($attempts_only) { | |
1078 | return $attempts; | |
1079 | } | |
6381fa56 | 1080 | if(!empty($attempts)) { |
1081 | $attemptcount = count($attempts); | |
1082 | } | |
1083 | return $attemptcount; | |
1084 | } | |
1881df27 | 1085 | |
1086 | /** | |
1087 | * Figure out with this is a debug situation | |
1088 | * | |
1089 | * @param object $scorm a moodle scrom object - mdl_scorm | |
1090 | * @return boolean - debugging true/false | |
7554f671 | 1091 | */ |
1881df27 | 1092 | function scorm_debugging($scorm) { |
1093 | global $CFG, $USER; | |
30fc6e2d | 1094 | $cfg_scorm = get_config('scorm'); |
7554f671 | 1095 | |
30fc6e2d | 1096 | if (!$cfg_scorm->allowapidebug) { |
1881df27 | 1097 | return false; |
1098 | } | |
1099 | $identifier = $USER->username.':'.$scorm->name; | |
30fc6e2d | 1100 | $test = $cfg_scorm->apidebugmask; |
1881df27 | 1101 | // check the regex is only a short list of safe characters |
1102 | if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) { | |
1103 | return false; | |
1104 | } | |
1105 | $res = false; | |
1106 | eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;'); | |
1107 | return $res; | |
1108 | } | |
7554f671 | 1109 | |
1110 | /** | |
1111 | * Delete Scorm tracks for selected users | |
1112 | * | |
1113 | * @param array $attemptids list of attempts that need to be deleted | |
8716cc35 | 1114 | * @param int $scorm instance |
7554f671 | 1115 | * |
1116 | * return bool true deleted all responses, false failed deleting an attempt - stopped here | |
1117 | */ | |
8716cc35 | 1118 | function scorm_delete_responses($attemptids, $scorm) { |
7554f671 | 1119 | if(!is_array($attemptids) || empty($attemptids)) { |
1120 | return false; | |
1121 | } | |
1122 | ||
1123 | foreach($attemptids as $num => $attemptid) { | |
1124 | if(empty($attemptid)) { | |
1125 | unset($attemptids[$num]); | |
1126 | } | |
1127 | } | |
1128 | ||
1129 | foreach($attemptids as $attempt) { | |
1130 | $keys = explode(':', $attempt); | |
1131 | if (count($keys) == 2) { | |
1132 | $userid = clean_param($keys[0], PARAM_INT); | |
1133 | $attemptid = clean_param($keys[1], PARAM_INT); | |
8716cc35 | 1134 | if (!$userid || !$attemptid || !scorm_delete_attempt($userid, $scorm, $attemptid)) { |
7554f671 | 1135 | return false; |
1136 | } | |
1137 | } else { | |
1138 | return false; | |
1139 | } | |
1140 | } | |
1141 | return true; | |
1142 | } | |
1143 | ||
1144 | /** | |
1145 | * Delete Scorm tracks for selected users | |
1146 | * | |
1147 | * @param int $userid ID of User | |
1148 | * @param int $scormid ID of Scorm | |
1149 | * @param int $attemptid user attempt that need to be deleted | |
1150 | * | |
1151 | * return bool true suceeded | |
1152 | */ | |
8716cc35 | 1153 | function scorm_delete_attempt($userid, $scorm, $attemptid) { |
7554f671 | 1154 | global $DB; |
1155 | ||
8716cc35 DM |
1156 | $DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attemptid)); |
1157 | include_once('lib.php'); | |
1158 | scorm_update_grades($scorm, $userid, true); | |
7554f671 | 1159 | return true; |
1160 | } | |
00c1677a DM |
1161 | |
1162 | /** | |
1163 | * Converts SCORM date/time notation to human-readable format | |
1164 | * The function works with both SCORM 1.2 and SCORM 2004 time formats | |
1165 | * @param $datetime string SCORM date/time | |
1166 | * @return string human-readable date/time | |
1167 | */ | |
1168 | function scorm_format_date_time($datetime) { | |
1169 | // fetch date/time strings | |
1170 | $stryears = get_string('numyears'); | |
1171 | $strmonths = get_string('nummonths'); | |
1172 | $strdays = get_string('numdays'); | |
1173 | $strhours = get_string('numhours'); | |
1174 | $strminutes = get_string('numminutes'); | |
64f93798 PS |
1175 | $strseconds = get_string('numseconds'); |
1176 | ||
00c1677a DM |
1177 | if ($datetime[0] == 'P') { |
1178 | // if timestamp starts with 'P' - it's a SCORM 2004 format | |
1179 | // this regexp discards empty sections, takes Month/Minute ambiguity into consideration, | |
1180 | // and outputs filled sections, discarding leading zeroes and any format literals | |
1181 | // also saves the only zero before seconds decimals (if there are any) and discards decimals if they are zero | |
1182 | $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#', | |
1183 | '#([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#' ); | |
1184 | $replace = array( '$1', '$1', '$1', '$1$2'.$strmonths.' ', '$1'.$stryears.' ', '$1'.$strdays.' ', '', | |
1185 | '$1', '$1', 'S', '$1$2'.$strminutes.' ', '$1'.$strhours.' ', '0.$1'.$strseconds, '$1'.$strseconds, ''); | |
1186 | } else { | |
1187 | // else we have SCORM 1.2 format there | |
1188 | // first convert the timestamp to some SCORM 2004-like format for conveniency | |
1189 | $datetime = preg_replace('#^(\d+):(\d+):([\d.]+)$#', 'T$1H$2M$3S', $datetime); | |
1190 | // then convert in the same way as SCORM 2004 | |
1191 | $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#' ); | |
1192 | $replace = array( 'T', '$1', '$1', 'S', '$1'.$strhours.' ', '$1'.$strminutes.' ', '0.$1'.$strseconds, '$1'.$strseconds, '' ); | |
1193 | //$pattern = '##'; | |
1194 | //$replace = ''; | |
1195 | } | |
64f93798 | 1196 | |
00c1677a DM |
1197 | $result = preg_replace($pattern, $replace, $datetime); |
1198 | ||
1199 | return $result; | |
1200 | } |