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