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) { |
bf347041 |
343 | global $DB; |
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))) { |
898b2823 |
365 | $track->value = addslashes_js($value); |
e4aa175a |
366 | $track->timemodified = time(); |
bf347041 |
367 | $id = $DB->update_record('scorm_scoes_track',$track); |
e4aa175a |
368 | } else { |
369 | $track->userid = $userid; |
370 | $track->scormid = $scormid; |
371 | $track->scoid = $scoid; |
372 | $track->attempt = $attempt; |
373 | $track->element = $element; |
898b2823 |
374 | $track->value = addslashes_js($value); |
e4aa175a |
375 | $track->timemodified = time(); |
bf347041 |
376 | $id = $DB->insert_record('scorm_scoes_track',$track); |
e4aa175a |
377 | } |
9528568b |
378 | |
9528568b |
379 | if (strstr($element, '.score.raw') || |
a0b36684 |
380 | (($element == 'cmi.core.lesson_status' || $element == 'cmi.completion_status') && ($track->value == 'completed' || $track->value == 'passed'))) { |
381 | $scorm = $DB->get_record('scorm', array('id' => $scormid)); |
382 | $grademethod = $scorm->grademethod % 10; |
7f7946fd |
383 | include_once('lib.php'); |
384 | scorm_update_grades($scorm, $userid); |
d23121ab |
385 | } |
9528568b |
386 | |
e4aa175a |
387 | return $id; |
388 | } |
389 | |
e4aa175a |
390 | function scorm_get_tracks($scoid,$userid,$attempt='') { |
e4aa175a |
391 | /// Gets all tracks of specified sco and user |
bf347041 |
392 | global $CFG, $DB; |
e4aa175a |
393 | |
394 | if (empty($attempt)) { |
bf347041 |
395 | if ($scormid = $DB->get_field('scorm_scoes','scorm', array('id'=>$scoid))) { |
e4aa175a |
396 | $attempt = scorm_get_last_attempt($scormid,$userid); |
397 | } else { |
398 | $attempt = 1; |
399 | } |
400 | } |
bf347041 |
401 | if ($tracks = $DB->get_records('scorm_scoes_track', array('userid'=>$userid, 'scoid'=>$scoid, 'attempt'=>$attempt),'element ASC')) { |
e4aa175a |
402 | $usertrack->userid = $userid; |
9528568b |
403 | $usertrack->scoid = $scoid; |
a30b6819 |
404 | // Defined in order to unify scorm1.2 and scorm2004 |
e4aa175a |
405 | $usertrack->score_raw = ''; |
e4aa175a |
406 | $usertrack->status = ''; |
e4aa175a |
407 | $usertrack->total_time = '00:00:00'; |
408 | $usertrack->session_time = '00:00:00'; |
409 | $usertrack->timemodified = 0; |
410 | foreach ($tracks as $track) { |
411 | $element = $track->element; |
e23cc5d2 |
412 | $track->value = stripslashes($track->value); |
e4aa175a |
413 | $usertrack->{$element} = $track->value; |
414 | switch ($element) { |
51c1c4e1 |
415 | case 'x.start.time': |
416 | $usertrack->x_start_time = $track->value; |
417 | break; |
f69db63e |
418 | case 'cmi.core.lesson_status': |
419 | case 'cmi.completion_status': |
420 | if ($track->value == 'not attempted') { |
421 | $track->value = 'notattempted'; |
9528568b |
422 | } |
f69db63e |
423 | $usertrack->status = $track->value; |
9528568b |
424 | break; |
e4aa175a |
425 | case 'cmi.core.score.raw': |
426 | case 'cmi.score.raw': |
67859ce6 |
427 | $usertrack->score_raw = sprintf('%0d', $track->value); |
9528568b |
428 | break; |
e4aa175a |
429 | case 'cmi.core.session_time': |
430 | case 'cmi.session_time': |
431 | $usertrack->session_time = $track->value; |
9528568b |
432 | break; |
e4aa175a |
433 | case 'cmi.core.total_time': |
434 | case 'cmi.total_time': |
435 | $usertrack->total_time = $track->value; |
9528568b |
436 | break; |
437 | } |
e4aa175a |
438 | if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) { |
439 | $usertrack->timemodified = $track->timemodified; |
9528568b |
440 | } |
3505e82b |
441 | } |
9528568b |
442 | if (is_array($usertrack)) { |
07b905ae |
443 | ksort($usertrack); |
444 | } |
e4aa175a |
445 | return $usertrack; |
446 | } else { |
447 | return false; |
448 | } |
449 | } |
450 | |
51c1c4e1 |
451 | |
452 | /* Find the start and finsh time for a a given SCO attempt |
453 | * |
454 | * @param int $scormid SCORM Id |
455 | * @param int $scoid SCO Id |
456 | * @param int $userid User Id |
457 | * @param int $attemt Attempt Id |
458 | * |
459 | * @return object start and finsh time EPOC secods |
460 | * |
461 | */ |
462 | function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) { |
4454447d |
463 | global $DB; |
51c1c4e1 |
464 | |
465 | $timedata = new object(); |
466 | $sql = !empty($scoid) ? "userid=$userid AND scormid=$scormid AND scoid=$scoid AND attempt=$attempt" : "userid=$userid AND scormid=$scormid AND attempt=$attempt"; |
467 | $tracks = $DB->get_records_select('scorm_scoes_track',"$sql ORDER BY timemodified ASC"); |
468 | if ($tracks) { |
469 | $tracks = array_values($tracks); |
470 | } |
471 | |
472 | if ($start_track = $DB->get_records_select('scorm_scoes_track',"$sql AND element='x.start.time' ORDER BY scoid ASC")) { |
473 | $start_track = array_values($start_track); |
474 | $timedata->start = $start_track[0]->value; |
475 | } |
476 | else if ($tracks) { |
477 | $timedata->start = $tracks[0]->timemodified; |
478 | } |
479 | else { |
480 | $timedata->start = false; |
481 | } |
482 | if ($tracks && $track = array_pop($tracks)) { |
483 | $timedata->finish = $track->timemodified; |
484 | } |
485 | else { |
486 | $timedata->finish = $timedata->start; |
487 | } |
488 | return $timedata; |
489 | } |
490 | |
491 | |
2b3447c3 |
492 | function scorm_get_user_data($userid) { |
bf347041 |
493 | global $DB; |
2b3447c3 |
494 | /// Gets user info required to display the table of scorm results |
495 | /// for report.php |
e4aa175a |
496 | |
bf347041 |
497 | return $DB->get_record('user', array('id'=>$userid),'firstname, lastname, picture'); |
2b3447c3 |
498 | } |
e4aa175a |
499 | |
a30b6819 |
500 | function scorm_grade_user_attempt($scorm, $userid, $attempt=1, $time=false) { |
bf347041 |
501 | global $DB; |
9528568b |
502 | $attemptscore = NULL; |
a30b6819 |
503 | $attemptscore->scoes = 0; |
504 | $attemptscore->values = 0; |
505 | $attemptscore->max = 0; |
506 | $attemptscore->sum = 0; |
507 | $attemptscore->lastmodify = 0; |
9528568b |
508 | |
bf347041 |
509 | if (!$scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) { |
a30b6819 |
510 | return NULL; |
e4aa175a |
511 | } |
e4aa175a |
512 | |
7ce6eb87 |
513 | // this treatment is necessary as the whatgrade field was not in the DB |
514 | // and so whatgrade and grademethod are combined in grademethod 10s are whatgrade |
515 | // and 1s are grademethod |
a30b6819 |
516 | $grademethod = $scorm->grademethod % 10; |
517 | |
9528568b |
518 | foreach ($scoes as $sco) { |
2b3447c3 |
519 | if ($userdata=scorm_get_tracks($sco->id, $userid,$attempt)) { |
520 | if (($userdata->status == 'completed') || ($userdata->status == 'passed')) { |
a30b6819 |
521 | $attemptscore->scoes++; |
9528568b |
522 | } |
f6f7efee |
523 | if (isset($userdata->score_raw)) { |
a30b6819 |
524 | $attemptscore->values++; |
525 | $attemptscore->sum += $userdata->score_raw; |
526 | $attemptscore->max = ($userdata->score_raw > $attemptscore->max)?$userdata->score_raw:$attemptscore->max; |
527 | if (isset($userdata->timemodified) && ($userdata->timemodified > $attemptscore->lastmodify)) { |
528 | $attemptscore->lastmodify = $userdata->timemodified; |
529 | } else { |
530 | $attemptscore->lastmodify = 0; |
531 | } |
9528568b |
532 | } |
533 | } |
e4aa175a |
534 | } |
2b3447c3 |
535 | switch ($grademethod) { |
a30b6819 |
536 | case GRADEHIGHEST: |
537 | $score = $attemptscore->max; |
9528568b |
538 | break; |
a30b6819 |
539 | case GRADEAVERAGE: |
540 | if ($attemptscore->values > 0) { |
541 | $score = $attemptscore->sum/$attemptscore->values; |
5c1ac70c |
542 | } else { |
a30b6819 |
543 | $score = 0; |
9528568b |
544 | } |
545 | break; |
a30b6819 |
546 | case GRADESUM: |
547 | $score = $attemptscore->sum; |
9528568b |
548 | break; |
a30b6819 |
549 | case GRADESCOES: |
550 | $score = $attemptscore->scoes; |
7ef16bf9 |
551 | break; |
552 | default: |
553 | $score = $attemptscore->max; // Remote Learner GRADEHIGHEST is default |
5c1ac70c |
554 | } |
a30b6819 |
555 | |
556 | if ($time) { |
557 | $result = new stdClass(); |
558 | $result->score = $score; |
559 | $result->time = $attemptscore->lastmodify; |
560 | } else { |
561 | $result = $score; |
562 | } |
563 | |
564 | return $result; |
565 | } |
566 | |
567 | function scorm_grade_user($scorm, $userid, $time=false) { |
7ce6eb87 |
568 | // this treatment is necessary as the whatgrade field was not in the DB |
569 | // and so whatgrade and grademethod are combined in grademethod 10s are whatgrade |
570 | // and 1s are grademethod |
a30b6819 |
571 | $whatgrade = intval($scorm->grademethod / 10); |
572 | |
7ce6eb87 |
573 | // insure we dont grade user beyond $scorm->maxattempt settings |
574 | $lastattempt = scorm_get_last_attempt($scorm->id, $userid); |
575 | if($scorm->maxattempt != 0 && $lastattempt >= $scorm->maxattempt){ |
576 | $lastattempt = $scorm->maxattempt; |
577 | } |
578 | |
a30b6819 |
579 | switch ($whatgrade) { |
580 | case FIRSTATTEMPT: |
581 | return scorm_grade_user_attempt($scorm, $userid, 1, $time); |
9528568b |
582 | break; |
a30b6819 |
583 | case LASTATTEMPT: |
584 | return scorm_grade_user_attempt($scorm, $userid, scorm_get_last_attempt($scorm->id, $userid), $time); |
585 | break; |
586 | case HIGHESTATTEMPT: |
a30b6819 |
587 | $maxscore = 0; |
588 | $attempttime = 0; |
589 | for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { |
590 | $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt, $time); |
591 | if ($time) { |
592 | if ($attemptscore->score > $maxscore) { |
593 | $maxscore = $attemptscore->score; |
594 | $attempttime = $attemptscore->time; |
595 | } |
596 | } else { |
597 | $maxscore = $attemptscore > $maxscore ? $attemptscore: $maxscore; |
598 | } |
599 | } |
600 | if ($time) { |
601 | $result = new stdClass(); |
602 | $result->score = $maxscore; |
603 | $result->time = $attempttime; |
604 | return $result; |
605 | } else { |
606 | return $maxscore; |
607 | } |
608 | break; |
609 | case AVERAGEATTEMPT: |
610 | $lastattempt = scorm_get_last_attempt($scorm->id, $userid); |
611 | $sumscore = 0; |
612 | for ($attempt = 1; $attempt <= $lastattempt; $attempt++) { |
613 | $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt, $time); |
614 | if ($time) { |
615 | $sumscore += $attemptscore->score; |
616 | } else { |
617 | $sumscore += $attemptscore; |
618 | } |
619 | } |
620 | |
621 | if ($lastattempt > 0) { |
622 | $score = $sumscore / $lastattempt; |
623 | } else { |
624 | $score = 0; |
625 | } |
626 | |
627 | if ($time) { |
628 | $result = new stdClass(); |
629 | $result->score = $score; |
630 | $result->time = $attemptscore->time; |
631 | return $result; |
632 | } else { |
633 | return $score; |
634 | } |
635 | break; |
636 | } |
e4aa175a |
637 | } |
638 | |
8e45ba45 |
639 | function scorm_count_launchable($scormid,$organization='') { |
bf347041 |
640 | global $DB; |
641 | |
642 | $sqlorganization = ''; |
643 | $params = array($scormid); |
8e45ba45 |
644 | if (!empty($organization)) { |
bf347041 |
645 | $sqlorganization = " AND organization=?"; |
646 | $params[] = $organization; |
8e45ba45 |
647 | } |
6280b17a |
648 | $params []= ''; // empty launch |
649 | return $DB->count_records_select('scorm_scoes',"scorm = ? $sqlorganization AND launch <> ?", $params); |
e4aa175a |
650 | } |
651 | |
2b3447c3 |
652 | function scorm_get_last_attempt($scormid, $userid) { |
bf347041 |
653 | global $DB; |
654 | |
2b3447c3 |
655 | /// Find the last attempt number for the given user id and scorm id |
bf347041 |
656 | if ($lastattempt = $DB->get_record('scorm_scoes_track', array('userid'=>$userid, 'scormid'=>$scormid), 'max(attempt) as a')) { |
2b3447c3 |
657 | if (empty($lastattempt->a)) { |
658 | return '1'; |
659 | } else { |
660 | return $lastattempt->a; |
e4aa175a |
661 | } |
6381fa56 |
662 | } else { |
663 | return false; |
e4aa175a |
664 | } |
e4aa175a |
665 | } |
666 | |
e4aa175a |
667 | function scorm_course_format_display($user,$course) { |
f2a1963c |
668 | global $CFG, $DB, $PAGE, $OUTPUT; |
e4aa175a |
669 | |
670 | $strupdate = get_string('update'); |
671 | $strmodule = get_string('modulename','scorm'); |
77bf0c29 |
672 | $context = get_context_instance(CONTEXT_COURSE,$course->id); |
e4aa175a |
673 | |
674 | echo '<div class="mod-scorm">'; |
675 | if ($scorms = get_all_instances_in_course('scorm', $course)) { |
9528568b |
676 | // The module SCORM activity with the least id is the course |
e4aa175a |
677 | $scorm = current($scorms); |
678 | if (! $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id)) { |
08b56f93 |
679 | print_error('invalidcoursemodule'); |
e4aa175a |
680 | } |
681 | $colspan = ''; |
682 | $headertext = '<table width="100%"><tr><td class="title">'.get_string('name').': <b>'.format_string($scorm->name).'</b>'; |
2b3447c3 |
683 | if (has_capability('moodle/course:manageactivities', $context)) { |
830dd6e9 |
684 | if ($PAGE->user_is_editing()) { |
e4aa175a |
685 | // Display update icon |
686 | $path = $CFG->wwwroot.'/course'; |
687 | $headertext .= '<span class="commands">'. |
688 | '<a title="'.$strupdate.'" href="'.$path.'/mod.php?update='.$cm->id.'&sesskey='.sesskey().'">'. |
b5d0cafc |
689 | '<img src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="'.$strupdate.'" /></a></span>'; |
e4aa175a |
690 | } |
691 | $headertext .= '</td>'; |
692 | // Display report link |
bf347041 |
693 | $trackedusers = $DB->get_record('scorm_scoes_track', array('scormid'=>$scorm->id), 'count(distinct(userid)) as c'); |
e4aa175a |
694 | if ($trackedusers->c > 0) { |
695 | $headertext .= '<td class="reportlink">'. |
fa738731 |
696 | '<a '.$CFG->frametarget.'" href="'.$CFG->wwwroot.'/mod/scorm/report.php?id='.$cm->id.'">'. |
e4aa175a |
697 | get_string('viewallreports','scorm',$trackedusers->c).'</a>'; |
698 | } else { |
699 | $headertext .= '<td class="reportlink">'.get_string('noreports','scorm'); |
700 | } |
701 | $colspan = ' colspan="2"'; |
9528568b |
702 | } |
d0bcf735 |
703 | $options = (object)array('noclean'=>true); |
ac3668bf |
704 | $headertext .= '</td></tr><tr><td'.$colspan.'>'.get_string('summary').':<br />'.format_module_intro('scorm', $scorm, $scorm->coursemodule).'</td></tr></table>'; |
6aff538a |
705 | echo $OUTPUT->box($headertext,'generalbox boxwidthwide'); |
e4aa175a |
706 | scorm_view_display($user, $scorm, 'view.php?id='.$course->id, $cm, '100%'); |
707 | } else { |
0d699c24 |
708 | if (has_capability('moodle/course:update', $context)) { |
e4aa175a |
709 | // Create a new activity |
2b3447c3 |
710 | redirect($CFG->wwwroot.'/course/mod.php?id='.$course->id.'&section=0&sesskey='.sesskey().'&add=scorm'); |
e4aa175a |
711 | } else { |
6aff538a |
712 | echo $OUTPUT->notification('Could not find a scorm course here'); |
e4aa175a |
713 | } |
714 | } |
715 | echo '</div>'; |
716 | } |
717 | |
2b3447c3 |
718 | function scorm_view_display ($user, $scorm, $action, $cm, $boxwidth='') { |
4eeec9ac |
719 | global $CFG, $DB, $PAGE, $OUTPUT; |
ab3b00e1 |
720 | |
9528568b |
721 | if ($scorm->updatefreq == UPDATE_EVERYTIME) { |
722 | scorm_parse($scorm, false); |
ab3b00e1 |
723 | } |
724 | |
e4aa175a |
725 | $organization = optional_param('organization', '', PARAM_INT); |
726 | |
6381fa56 |
727 | if($scorm->displaycoursestructure == 1) { |
6aff538a |
728 | echo $OUTPUT->box_start('generalbox boxaligncenter'); |
e4aa175a |
729 | ?> |
29a43013 |
730 | <div class="structurehead"><?php print_string('contents','scorm') ?></div> |
e4aa175a |
731 | <?php |
6381fa56 |
732 | } |
e4aa175a |
733 | if (empty($organization)) { |
734 | $organization = $scorm->launch; |
735 | } |
07b905ae |
736 | if ($orgs = $DB->get_records_menu('scorm_scoes', array('scorm'=>$scorm->id, 'organization'=>'', 'launch'=>''), 'id', 'id,title')) { |
e4aa175a |
737 | if (count($orgs) > 1) { |
738 | ?> |
52a9a9b5 |
739 | <div class='scorm-center'> |
e4aa175a |
740 | <?php print_string('organizations','scorm') ?> |
b7dc2256 |
741 | <form id='changeorg' method='post' action='<?php echo $action ?>'> |
e5dd8e3b |
742 | <?php |
7b1f2c82 |
743 | $select = new html_select(); |
49c8c8d2 |
744 | $select->options = $orgs; |
745 | $select->name = 'organization'; |
746 | $select->selectedvalue = $organization; |
747 | $select->add_action('change', 'submit_form_by_id', array('id' => 'changeorg')); |
e5dd8e3b |
748 | echo $OUTPUT->select($select); |
4eeec9ac |
749 | ?> |
e4aa175a |
750 | </form> |
751 | </div> |
752 | <?php |
753 | } |
754 | } |
755 | $orgidentifier = ''; |
b3659259 |
756 | if ($sco = scorm_get_sco($organization, SCO_ONLY)) { |
757 | if (($sco->organization == '') && ($sco->launch == '')) { |
758 | $orgidentifier = $sco->identifier; |
e4aa175a |
759 | } else { |
b3659259 |
760 | $orgidentifier = $sco->organization; |
e4aa175a |
761 | } |
762 | } |
28ffbff3 |
763 | |
764 | /* |
765 | $orgidentifier = ''; |
bf347041 |
766 | if ($org = $DB->get_record('scorm_scoes', array('id'=>$organization))) { |
28ffbff3 |
767 | if (($org->organization == '') && ($org->launch == '')) { |
768 | $orgidentifier = $org->identifier; |
769 | } else { |
770 | $orgidentifier = $org->organization; |
771 | } |
772 | }*/ |
773 | |
2b3447c3 |
774 | $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR)); // Just to be safe |
dbe7e6f6 |
775 | if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) { |
776 | $scorm->version = 'scorm_12'; |
777 | } |
2b3447c3 |
778 | require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php'); |
779 | |
e4aa175a |
780 | $result = scorm_get_toc($user,$scorm,'structlist',$orgidentifier); |
781 | $incomplete = $result->incomplete; |
28ffbff3 |
782 | |
6381fa56 |
783 | // do we want the TOC to be displayed? |
784 | if($scorm->displaycoursestructure == 1) { |
785 | echo $result->toc; |
6aff538a |
786 | echo $OUTPUT->box_end(); |
6381fa56 |
787 | } |
7554f671 |
788 | |
6381fa56 |
789 | // is this the first attempt ? |
790 | $attemptcount = scorm_get_attempt_count($user, $scorm); |
7554f671 |
791 | |
6381fa56 |
792 | // do not give the player launch FORM if the SCORM object is locked after the final attempt |
793 | if ($scorm->lastattemptlock == 0 || $result->attemptleft > 0) { |
e4aa175a |
794 | ?> |
52a9a9b5 |
795 | <div class="scorm-center"> |
4675994a |
796 | <form id="theform" method="post" action="<?php echo $CFG->wwwroot ?>/mod/scorm/player.php"> |
e4aa175a |
797 | <?php |
e4aa175a |
798 | if ($scorm->hidebrowse == 0) { |
8949f8df |
799 | print_string('mode','scorm'); |
e4aa175a |
800 | echo ': <input type="radio" id="b" name="mode" value="browse" /><label for="b">'.get_string('browse','scorm').'</label>'."\n"; |
76ea4fb4 |
801 | echo '<input type="radio" id="n" name="mode" value="normal" checked="checked" /><label for="n">'.get_string('normal','scorm')."</label>\n"; |
e4aa175a |
802 | } else { |
76ea4fb4 |
803 | echo '<input type="hidden" name="mode" value="normal" />'."\n"; |
e4aa175a |
804 | } |
6381fa56 |
805 | if ($scorm->forcenewattempt == 1) { |
806 | if ($incomplete === false) { |
807 | echo '<input type="hidden" name="newattempt" value="on" />'."\n"; |
808 | } |
809 | } elseif ($attemptcount != 0 && ($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) { |
e4aa175a |
810 | ?> |
6381fa56 |
811 | <br /> |
812 | <input type="checkbox" id="a" name="newattempt" /> |
813 | <label for="a"><?php print_string('newattempt','scorm') ?></label> |
e4aa175a |
814 | <?php |
815 | } |
816 | ?> |
817 | <br /> |
4675994a |
818 | <input type="hidden" name="scoid"/> |
df7ba610 |
819 | <input type="hidden" name="id" value="<?php echo $cm->id ?>"/> |
e4aa175a |
820 | <input type="hidden" name="currentorg" value="<?php echo $orgidentifier ?>" /> |
2b600d16 |
821 | <input type="submit" value="<?php print_string('enter','scorm') ?>" /> |
e4aa175a |
822 | </form> |
823 | </div> |
824 | <?php |
6381fa56 |
825 | } |
e4aa175a |
826 | } |
6280b17a |
827 | |
28ffbff3 |
828 | function scorm_simple_play($scorm,$user) { |
bf347041 |
829 | global $DB; |
830 | |
9316beca |
831 | $result = false; |
832 | |
833 | if ($scorm->updatefreq == UPDATE_EVERYTIME) { |
834 | scorm_parse($scorm, false); |
835 | } |
836 | |
837 | $scoes = $DB->get_records_select('scorm_scoes', 'scorm = ? AND launch <> ?', array($scorm->id, $DB->sql_empty())); |
838 | |
7325b016 |
839 | if ($scoes) { |
9316beca |
840 | if ($scorm->skipview >= 1) { |
841 | $sco = current($scoes); |
842 | if (scorm_get_tracks($sco->id,$user->id) === false) { |
843 | header('Location: player.php?a='.$scorm->id.'&scoid='.$sco->id); |
844 | $result = true; |
845 | } else if ($scorm->skipview == 2) { |
846 | header('Location: player.php?a='.$scorm->id.'&scoid='.$sco->id); |
847 | $result = true; |
848 | } |
849 | } |
850 | } |
851 | return $result; |
28ffbff3 |
852 | } |
853 | /* |
8e45ba45 |
854 | function scorm_simple_play($scorm,$user) { |
bf347041 |
855 | global $DB; |
8e45ba45 |
856 | $result = false; |
bf347041 |
857 | if ($scoes = $DB->get_records_select('scorm_scoes','scorm=? AND launch<>""', array($scorm->id))) { |
76ea4fb4 |
858 | if (count($scoes) == 1) { |
859 | if ($scorm->skipview >= 1) { |
860 | $sco = current($scoes); |
861 | if (scorm_get_tracks($sco->id,$user->id) === false) { |
862 | header('Location: player.php?a='.$scorm->id.'&scoid='.$sco->id); |
863 | $result = true; |
864 | } else if ($scorm->skipview == 2) { |
865 | header('Location: player.php?a='.$scorm->id.'&scoid='.$sco->id); |
866 | $result = true; |
867 | } |
8e45ba45 |
868 | } |
869 | } |
870 | } |
871 | return $result; |
872 | } |
28ffbff3 |
873 | */ |
d8c9d8a1 |
874 | |
875 | function scorm_get_count_users($scormid, $groupingid=null) { |
bf347041 |
876 | global $CFG, $DB; |
9528568b |
877 | |
d8c9d8a1 |
878 | if (!empty($CFG->enablegroupings) && !empty($groupingid)) { |
879 | $sql = "SELECT COUNT(DISTINCT st.userid) |
bf347041 |
880 | FROM {scorm_scoes_track} st |
881 | INNER JOIN {groups_members} gm ON st.userid = gm.userid |
9528568b |
882 | INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid |
bf347041 |
883 | WHERE st.scormid = ? AND gg.groupingid = ? |
d8c9d8a1 |
884 | "; |
bf347041 |
885 | $params = array($scormid, $groupingid); |
d8c9d8a1 |
886 | } else { |
887 | $sql = "SELECT COUNT(DISTINCT st.userid) |
9528568b |
888 | FROM {scorm_scoes_track} st |
bf347041 |
889 | WHERE st.scormid = ? |
d8c9d8a1 |
890 | "; |
bf347041 |
891 | $params = array($scormid); |
d8c9d8a1 |
892 | } |
9528568b |
893 | |
bf347041 |
894 | return ($DB->count_records_sql($sql, $params)); |
d8c9d8a1 |
895 | } |
896 | |
527af457 |
897 | /** |
898 | * Build up the JavaScript representation of an array element |
899 | * |
900 | * @param string $sversion SCORM API version |
901 | * @param array $userdata User track data |
902 | * @param string $element_name Name of array element to get values for |
903 | * @param array $children list of sub elements of this array element that also need instantiating |
904 | * @return None |
7554f671 |
905 | */ |
527af457 |
906 | function scorm_reconstitute_array_element($sversion, $userdata, $element_name, $children) { |
907 | // reconstitute comments_from_learner and comments_from_lms |
908 | $current = ''; |
bf34ac0f |
909 | $current_subelement = ''; |
910 | $current_sub = ''; |
527af457 |
911 | $count = 0; |
bf34ac0f |
912 | $count_sub = 0; |
7554f671 |
913 | |
527af457 |
914 | // filter out the ones we want |
915 | $element_list = array(); |
916 | foreach($userdata as $element => $value){ |
917 | if (substr($element,0,strlen($element_name)) == $element_name) { |
918 | $element_list[$element] = $value; |
919 | } |
920 | } |
7554f671 |
921 | |
527af457 |
922 | // sort elements in .n array order |
923 | uksort($element_list, "scorm_element_cmp"); |
7554f671 |
924 | |
527af457 |
925 | // generate JavaScript |
926 | foreach($element_list as $element => $value){ |
927 | if ($sversion == 'scorm_13') { |
b6293233 |
928 | $element = preg_replace('/\.(\d+)\./', ".N\$1.", $element); |
527af457 |
929 | preg_match('/\.(N\d+)\./', $element, $matches); |
930 | } else { |
b6293233 |
931 | $element = preg_replace('/\.(\d+)\./', "_\$1.", $element); |
527af457 |
932 | preg_match('/\_(\d+)\./', $element, $matches); |
933 | } |
934 | if (count($matches) > 0 && $current != $matches[1]) { |
bf34ac0f |
935 | if ($count_sub > 0) { |
936 | echo ' '.$element_name.'_'.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; |
937 | } |
7554f671 |
938 | $current = $matches[1]; |
527af457 |
939 | $count++; |
bf34ac0f |
940 | $current_subelement = ''; |
941 | $current_sub = ''; |
942 | $count_sub = 0; |
527af457 |
943 | $end = strpos($element,$matches[1])+strlen($matches[1]); |
944 | $subelement = substr($element,0,$end); |
945 | echo ' '.$subelement." = new Object();\n"; |
946 | // now add the children |
947 | foreach ($children as $child) { |
948 | echo ' '.$subelement.".".$child." = new Object();\n"; |
949 | echo ' '.$subelement.".".$child."._children = ".$child."_children;\n"; |
950 | } |
951 | } |
7554f671 |
952 | |
bf34ac0f |
953 | // now - flesh out the second level elements if there are any |
954 | if ($sversion == 'scorm_13') { |
b6293233 |
955 | $element = preg_replace('/(.*?\.N\d+\..*?)\.(\d+)\./', "\$1.N\$2.", $element); |
bf34ac0f |
956 | preg_match('/.*?\.N\d+\.(.*?)\.(N\d+)\./', $element, $matches); |
957 | } else { |
b6293233 |
958 | $element = preg_replace('/(.*?\_\d+\..*?)\.(\d+)\./', "\$1_\$2.", $element); |
bf34ac0f |
959 | preg_match('/.*?\_\d+\.(.*?)\_(\d+)\./', $element, $matches); |
960 | } |
7554f671 |
961 | |
bf34ac0f |
962 | // check the sub element type |
963 | if (count($matches) > 0 && $current_subelement != $matches[1]) { |
964 | if ($count_sub > 0) { |
965 | echo ' '.$element_name.'_'.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; |
966 | } |
967 | $current_subelement = $matches[1]; |
968 | $current_sub = ''; |
969 | $count_sub = 0; |
970 | $end = strpos($element,$matches[1])+strlen($matches[1]); |
971 | $subelement = substr($element,0,$end); |
972 | echo ' '.$subelement." = new Object();\n"; |
973 | } |
7554f671 |
974 | |
bf34ac0f |
975 | // now check the subelement subscript |
976 | if (count($matches) > 0 && $current_sub != $matches[2]) { |
7554f671 |
977 | $current_sub = $matches[2]; |
bf34ac0f |
978 | $count_sub++; |
979 | $end = strrpos($element,$matches[2])+strlen($matches[2]); |
980 | $subelement = substr($element,0,$end); |
981 | echo ' '.$subelement." = new Object();\n"; |
982 | } |
7554f671 |
983 | |
527af457 |
984 | echo ' '.$element.' = \''.$value."';\n"; |
985 | } |
bf34ac0f |
986 | if ($count_sub > 0) { |
987 | echo ' '.$element_name.'_'.$current.'.'.$current_subelement.'._count = '.$count_sub.";\n"; |
988 | } |
527af457 |
989 | if ($count > 0) { |
990 | echo ' '.$element_name.'._count = '.$count.";\n"; |
991 | } |
992 | } |
993 | |
994 | /** |
995 | * Build up the JavaScript representation of an array element |
996 | * |
997 | * @param string $a left array element |
998 | * @param string $b right array element |
999 | * @return comparator - 0,1,-1 |
7554f671 |
1000 | */ |
527af457 |
1001 | function scorm_element_cmp($a, $b) { |
bf34ac0f |
1002 | preg_match('/.*?(\d+)\./', $a, $matches); |
527af457 |
1003 | $left = intval($matches[1]); |
bf34ac0f |
1004 | preg_match('/.?(\d+)\./', $b, $matches); |
527af457 |
1005 | $right = intval($matches[1]); |
1006 | if ($left < $right) { |
1007 | return -1; // smaller |
1008 | } elseif ($left > $right) { |
1009 | return 1; // bigger |
1010 | } else { |
bf34ac0f |
1011 | // look for a second level qualifier eg cmi.interactions_0.correct_responses_0.pattern |
1012 | if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $a, $matches)) { |
1013 | $leftterm = intval($matches[2]); |
1014 | $left = intval($matches[3]); |
1015 | if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $b, $matches)) { |
1016 | $rightterm = intval($matches[2]); |
1017 | $right = intval($matches[3]); |
1018 | if ($leftterm < $rightterm) { |
1019 | return -1; // smaller |
1020 | } elseif ($leftterm > $rightterm) { |
1021 | return 1; // bigger |
1022 | } else { |
1023 | if ($left < $right) { |
1024 | return -1; // smaller |
1025 | } elseif ($left > $right) { |
1026 | return 1; // bigger |
1027 | } |
1028 | } |
1029 | } |
1030 | } |
1031 | // fall back for no second level matches or second level matches are equal |
527af457 |
1032 | return 0; // equal to |
1033 | } |
1034 | } |
6381fa56 |
1035 | |
1036 | /** |
1037 | * Generate the user attempt status string |
1038 | * |
1039 | * @param object $user Current context user |
1040 | * @param object $scorm a moodle scrom object - mdl_scorm |
1041 | * @return string - Attempt status string |
7554f671 |
1042 | */ |
6381fa56 |
1043 | function scorm_get_attempt_status($user, $scorm) { |
1044 | global $DB; |
7554f671 |
1045 | |
6381fa56 |
1046 | $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'); |
1047 | if(empty($attempts)) { |
1048 | $attemptcount = 0; |
1049 | } else { |
1050 | $attemptcount = count($attempts); |
1051 | } |
7554f671 |
1052 | |
6381fa56 |
1053 | $result = '<p>'.get_string('noattemptsallowed', 'scorm').': '; |
1054 | if ($scorm->maxattempt > 0) { |
1055 | $result .= $scorm->maxattempt . '<BR>'; |
1056 | } else { |
1057 | $result .= get_string('unlimited').'<BR>'; |
1058 | } |
1059 | $result .= get_string('noattemptsmade', 'scorm').': ' . $attemptcount . '<BR>'; |
1060 | |
1061 | $gradereported = 0; |
1062 | $gradesum = 0; |
1063 | switch ($scorm->grademethod) { |
1064 | case GRADEHIGHEST: |
1065 | $grademethod = get_string('gradehighest', 'scorm'); |
1066 | break; |
1067 | case GRADEAVERAGE: |
1068 | $grademethod = get_string('gradeaverage', 'scorm'); |
1069 | break; |
1070 | case GRADESUM: |
1071 | $grademethod = get_string('gradesum', 'scorm'); |
1072 | break; |
1073 | case GRADESCOES: |
1074 | $grademethod = get_string('gradescoes', 'scorm'); |
1075 | break; |
1076 | } |
7554f671 |
1077 | |
6381fa56 |
1078 | if(!empty($attempts)) { |
1079 | foreach($attempts as $attempt) { |
1080 | $gradereported = scorm_grade_user_attempt($scorm, $user->id, $attempt->attemptnumber); |
1081 | $result .= get_string('gradeforattempt', 'scorm').' ' . $attempt->attemptnumber . ': ' . $attempt->grade .'%<BR>'; |
1082 | } |
1083 | } |
1084 | |
1085 | $result .= get_string('grademethod', 'scorm'). ': ' . $grademethod; |
1086 | if(empty($attempts)) { |
1087 | $result .= '<BR>' . get_string('gradereported','scorm') . ': ' . get_string('none') . '<BR>'; |
1088 | } else { |
1089 | $result .= '<BR>' . get_string('gradereported','scorm') . ': ' . $gradereported . ($scorm->grademethod == GRADESCOES ? '' : '%') .'<BR>'; |
1090 | } |
1091 | $result .= '</p>'; |
1092 | if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) { |
1093 | $result .= '<p><font color="#cc0000">'.get_string('exceededmaxattempts','scorm').'</font></p>'; |
1094 | } |
1095 | return $result; |
1096 | } |
1097 | |
1098 | /** |
1099 | * Get SCORM attempt count |
1100 | * |
1101 | * @param object $user Current context user |
1102 | * @param object $scorm a moodle scrom object - mdl_scorm |
1103 | * @return int - no. of attempts so far |
7554f671 |
1104 | */ |
6381fa56 |
1105 | function scorm_get_attempt_count($user, $scorm) { |
1106 | global $DB; |
1107 | $attemptcount = 0; |
1108 | $element = 'cmi.core.score.raw'; |
1109 | if ($scorm->version == 'scorm1_3') { |
1110 | $element = 'cmi.score.raw'; |
1111 | } |
1112 | $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'); |
1113 | if(!empty($attempts)) { |
1114 | $attemptcount = count($attempts); |
1115 | } |
1116 | return $attemptcount; |
1117 | } |
1881df27 |
1118 | |
1119 | /** |
1120 | * Figure out with this is a debug situation |
1121 | * |
1122 | * @param object $scorm a moodle scrom object - mdl_scorm |
1123 | * @return boolean - debugging true/false |
7554f671 |
1124 | */ |
1881df27 |
1125 | function scorm_debugging($scorm) { |
1126 | global $CFG, $USER; |
30fc6e2d |
1127 | $cfg_scorm = get_config('scorm'); |
7554f671 |
1128 | |
30fc6e2d |
1129 | if (!$cfg_scorm->allowapidebug) { |
1881df27 |
1130 | return false; |
1131 | } |
1132 | $identifier = $USER->username.':'.$scorm->name; |
30fc6e2d |
1133 | $test = $cfg_scorm->apidebugmask; |
1881df27 |
1134 | // check the regex is only a short list of safe characters |
1135 | if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) { |
1136 | return false; |
1137 | } |
1138 | $res = false; |
1139 | eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;'); |
1140 | return $res; |
1141 | } |
7554f671 |
1142 | |
1143 | /** |
1144 | * Delete Scorm tracks for selected users |
1145 | * |
1146 | * @param array $attemptids list of attempts that need to be deleted |
1147 | * @param int $scormid ID of Scorm |
1148 | * |
1149 | * return bool true deleted all responses, false failed deleting an attempt - stopped here |
1150 | */ |
1151 | function scorm_delete_responses($attemptids, $scormid) { |
1152 | if(!is_array($attemptids) || empty($attemptids)) { |
1153 | return false; |
1154 | } |
1155 | |
1156 | foreach($attemptids as $num => $attemptid) { |
1157 | if(empty($attemptid)) { |
1158 | unset($attemptids[$num]); |
1159 | } |
1160 | } |
1161 | |
1162 | foreach($attemptids as $attempt) { |
1163 | $keys = explode(':', $attempt); |
1164 | if (count($keys) == 2) { |
1165 | $userid = clean_param($keys[0], PARAM_INT); |
1166 | $attemptid = clean_param($keys[1], PARAM_INT); |
1167 | if (!$userid || !$attemptid || !scorm_delete_attempt($userid, $scormid, $attemptid)) { |
1168 | return false; |
1169 | } |
1170 | } else { |
1171 | return false; |
1172 | } |
1173 | } |
1174 | return true; |
1175 | } |
1176 | |
1177 | /** |
1178 | * Delete Scorm tracks for selected users |
1179 | * |
1180 | * @param int $userid ID of User |
1181 | * @param int $scormid ID of Scorm |
1182 | * @param int $attemptid user attempt that need to be deleted |
1183 | * |
1184 | * return bool true suceeded |
1185 | */ |
1186 | function scorm_delete_attempt($userid, $scormid, $attemptid) { |
1187 | global $DB; |
1188 | |
1189 | $DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scormid, 'attempt' => $attemptid)); |
1190 | return true; |
1191 | } |
4eeec9ac |
1192 | ?> |