Fix for Bug 6119 - gives error when choice full, and user submits a choice they have...
[moodle.git] / mod / scorm / lib.php
CommitLineData
1a50a1f9 1<?php // $Id$\r
2\r
3/// Library of functions and constants for module scorm\r
4\r
5define('GRADESCOES', '0');\r
6define('GRADEHIGHEST', '1');\r
7define('GRADEAVERAGE', '2');\r
8define('GRADESUM', '3');\r
9$SCORM_GRADE_METHOD = array (GRADESCOES => get_string('gradescoes', 'scorm'),\r
10 GRADEHIGHEST => get_string('gradehighest', 'scorm'),\r
11 GRADEAVERAGE => get_string('gradeaverage', 'scorm'),\r
12 GRADESUM => get_string('gradesum', 'scorm'));\r
13\r
14define('VALUEHIGHEST', '0');\r
15define('VALUEAVERAGE', '1');\r
16define('VALUEFIRST', '2');\r
17define('VALUELAST', '3');\r
18$SCORM_WHAT_GRADE = array (VALUEHIGHEST => get_string('highestattempt', 'scorm'),\r
19 VALUEAVERAGE => get_string('averageattempt', 'scorm'),\r
20 VALUEFIRST => get_string('firstattempt', 'scorm'),\r
21 VALUELAST => get_string('lastattempt', 'scorm'));\r
22\r
23$SCORM_POPUP_OPTIONS = array('resizable'=>1, \r
24 'scrollbars'=>1, \r
25 'directories'=>0, \r
26 'location'=>0,\r
27 'menubar'=>0, \r
28 'toolbar'=>0, \r
29 'status'=>0);\r
30$stdoptions = '';\r
31foreach ($SCORM_POPUP_OPTIONS as $popupopt => $value) {\r
32 $stdoptions .= $popupopt.'='.$value;\r
33 if ($popupopt != 'status') {\r
34 $stdoptions .= ',';\r
35 }\r
36}\r
37\r
38if (!isset($CFG->scorm_maxattempts)) {\r
39 set_config('scorm_maxattempts','6');\r
40}\r
41\r
42if (!isset($CFG->scorm_frameheight)) {\r
43 set_config('scorm_frameheight','500');\r
44}\r
45\r
46if (!isset($CFG->scorm_framewidth)) {\r
47 set_config('scorm_framewidth','100%');\r
48}\r
49\r
50/**\r
51* Given an object containing all the necessary data,\r
52* (defined by the form in mod.html) this function\r
53* will create a new instance and return the id number\r
54* of the new instance.\r
55*\r
56* @param mixed $scorm Form data\r
57* @return int\r
58*/\r
59function scorm_add_instance($scorm) {\r
60\r
61 global $CFG;\r
62 $scorm->timemodified = time();\r
63\r
64 $scorm = scorm_option2text($scorm);\r
65 $scorm->width = str_replace('%','',$scorm->width);\r
66 $scorm->height = str_replace('%','',$scorm->height);\r
67\r
9054ba92 68 //sanitize submitted values a bit\r
69 $scorm->width = clean_param($scorm->width, PARAM_INT);\r
70 $scorm->height = clean_param($scorm->height, PARAM_INT);\r
71\r
1a50a1f9 72 $id = insert_record('scorm', $scorm);\r
73\r
74 if (basename($scorm->reference) != 'imsmanifest.xml') {\r
75 // Rename temp scorm dir to scorm id\r
76 $scorm->dir = $CFG->dataroot.'/'.$scorm->course.'/moddata/scorm';\r
77 rename($scorm->dir.$scorm->datadir,$scorm->dir.'/'.$id);\r
78 }\r
79\r
80 // Parse scorm manifest\r
81 if ($scorm->launch == 0) {\r
82 require_once('locallib.php');\r
83 $scorm->id = $id;\r
84 $scorm->launch = scorm_parse($scorm);\r
85 set_field('scorm','launch',$scorm->launch,'id',$scorm->id);\r
86 }\r
87\r
88 return $id;\r
89}\r
90\r
91/**\r
92* Given an object containing all the necessary data,\r
93* (defined by the form in mod.html) this function\r
94* will update an existing instance with new data.\r
95*\r
96* @param mixed $scorm Form data\r
97* @return int\r
98*/\r
99function scorm_update_instance($scorm) {\r
100\r
101 global $CFG;\r
102\r
103 $scorm->timemodified = time();\r
104 $scorm->id = $scorm->instance;\r
105\r
106 $scorm = scorm_option2text($scorm);\r
107 $scorm->width = str_replace('%','',$scorm->width);\r
108 $scorm->height = str_replace('%','',$scorm->height);\r
109\r
110 // Check if scorm manifest needs to be reparsed\r
111 if ($scorm->launch == 0) {\r
112 //$f = "D:\\test.txt";\r
113 //@$ft = fopen($f,"a");\r
114 //fwrite($ft,"\n Xu ly trong update trong lib.php \n");\r
115 //fwrite($ft,"\n Lauch co gia tri \n".($scorm->launch));\r
116\r
117 // Delete old related records\r
118 delete_records('scorm_scoes','scorm',$scorm->id);\r
119 delete_records('scorm_scoes_track','scormid',$scorm->id);\r
120 delete_records('scorm_sequencing_controlmode','scormid',$scorm->id);\r
121 delete_records('scorm_sequencing_rolluprules','scormid',$scorm->id);\r
122 delete_records('scorm_sequencing_rolluprule','scormid',$scorm->id);\r
123 delete_records('scorm_sequencing_rollupruleconditions','scormid',$scorm->id);\r
124 delete_records('scorm_sequencing_rolluprulecondition','scormid',$scorm->id); \r
125 delete_records('scorm_sequencing_ruleconditions','scormid',$scorm->id);\r
126 delete_records('scorm_sequencing_rulecondition','scormid',$scorm->id); \r
127\r
128 \r
129 $scorm->dir = $CFG->dataroot.'/'.$scorm->course.'/moddata/scorm';\r
130 if (isset($scorm->datadir) && ($scorm->datadir != $scorm->id) && (basename($scorm->reference) != 'imsmanifest.xml')) {\r
131 scorm_delete_files($scorm->dir.'/'.$scorm->id);\r
132 rename($scorm->dir.$scorm->datadir,$scorm->dir.'/'.$scorm->id);\r
133 }\r
134 \r
135 require_once('locallib.php');\r
136 $scorm->launch = scorm_parse($scorm);\r
137 }\r
138\r
139 return update_record('scorm', $scorm);\r
140}\r
141\r
142/**\r
143* Given an ID of an instance of this module,\r
144* this function will permanently delete the instance\r
145* and any data that depends on it.\r
146*\r
147* @param int $id Scorm instance id\r
148* @return boolean\r
149*/\r
150function scorm_delete_instance($id) {\r
151\r
152 global $CFG;\r
153\r
154 if (! $scorm = get_record('scorm', 'id', $id)) {\r
155 return false;\r
156 }\r
157\r
158 $result = true;\r
159\r
160 // Delete any dependent files\r
161 scorm_delete_files($CFG->dataroot.'/'.$scorm->course.'/moddata/scorm/'.$scorm->id);\r
162\r
163 // Delete any dependent records\r
164 if (! delete_records('scorm_scoes_track', 'scormid', $scorm->id)) {\r
165 $result = false;\r
166 }\r
167 if (! delete_records('scorm_scoes', 'scorm', $scorm->id)) {\r
168 $result = false;\r
169 }\r
170 if (! delete_records('scorm', 'id', $scorm->id)) {\r
171 $result = false;\r
172 }\r
173 if (! delete_records('scorm_sequencing_controlmode', 'scormid', $scorm->id)) {\r
174 $result = false;\r
175 }\r
176 if (! delete_records('scorm_sequencing_rolluprules', 'scormid', $scorm->id)) {\r
177 $result = false;\r
178 }\r
179 if (! delete_records('scorm_sequencing_rolluprule', 'scormid', $scorm->id)) {\r
180 $result = false;\r
181 }\r
182 if (! delete_records('scorm_sequencing_rollupruleconditions', 'scormid', $scorm->id)) {\r
183 $result = false;\r
184 }\r
185 if (! delete_records('scorm_sequencing_rolluprulecondition', 'scormid', $scorm->id)) {\r
186 $result = false;\r
187 }\r
188 if (! delete_records('scorm_sequencing_rulecondition', 'scormid', $scorm->id)) {\r
189 $result = false;\r
190 }\r
191 if (! delete_records('scorm_sequencing_ruleconditions', 'scormid', $scorm->id)) {\r
192 $result = false;\r
193 } \r
194 return $result;\r
195}\r
196\r
197/**\r
198* Return a small object with summary information about what a\r
199* user has done with a given particular instance of this module\r
200* Used for user activity reports.\r
201*\r
202* @param int $course Course id\r
203* @param int $user User id\r
204* @param int $mod \r
205* @param int $scorm The scorm id\r
206* @return mixed\r
207*/\r
208function scorm_user_outline($course, $user, $mod, $scorm) { \r
209\r
210 $return = NULL;\r
211 $scores->values = 0;\r
212 $scores->sum = 0;\r
213 $scores->max = 0;\r
214 $scores->lastmodify = 0;\r
215 $scores->count = 0;\r
216 if ($scoes = get_records_select("scorm_scoes","scorm='$scorm->id' ORDER BY id")) {\r
217 require_once('locallib.php');\r
218 foreach ($scoes as $sco) {\r
219 if ($sco->launch!='') {\r
220 $scores->count++;\r
221 if ($userdata = scorm_get_tracks($sco->id, $user->id)) {\r
222 if (!isset($scores->{$userdata->status})) {\r
223 $scores->{$userdata->status} = 1;\r
224 } else { \r
225 $scores->{$userdata->status}++;\r
226 }\r
227 if (!empty($userdata->score_raw)) {\r
228 $scores->values++;\r
229 $scores->sum += $userdata->score_raw;\r
230 $scores->max = ($userdata->score_raw > $scores->max)?$userdata->score_raw:$scores->max;\r
231 }\r
232 if (isset($userdata->timemodified) && ($userdata->timemodified > $scores->lastmodify)) {\r
233 $scores->lastmodify = $userdata->timemodified;\r
234 }\r
235 }\r
236 }\r
237 }\r
238 switch ($scorm->grademethod) {\r
b7679957 239 case GRADEHIGHEST:\r
1a50a1f9 240 if ($scores->values > 0) {\r
241 $return->info = get_string('score','scorm').':&nbsp;'.$scores->max;\r
242 $return->time = $scores->lastmodify;\r
243 }\r
244 break;\r
b7679957 245 case GRADEAVERAGE:\r
1a50a1f9 246 if ($scores->values > 0) {\r
247 $return->info = get_string('score','scorm').':&nbsp;'.$scores->sum/$scores->values;\r
248 $return->time = $scores->lastmodify;\r
249 }\r
250 break;\r
b7679957 251 case GRADESUM:\r
1a50a1f9 252 if ($scores->values > 0) {\r
253 $return->info = get_string('score','scorm').':&nbsp;'.$scores->sum;\r
254 $return->time = $scores->lastmodify;\r
255 }\r
256 break;\r
b7679957 257 case GRADESCOES:\r
1a50a1f9 258 $return->info = '';\r
259 $scores->notattempted = $scores->count;\r
260 if (isset($scores->completed)) {\r
261 $return->info .= get_string('completed','scorm').':&nbsp;'.$scores->completed.'<br />';\r
262 $scores->notattempted -= $scores->completed;\r
263 }\r
264 if (isset($scores->passed)) {\r
265 $return->info .= get_string('passed','scorm').':&nbsp;'.$scores->passed.'<br />';\r
266 $scores->notattempted -= $scores->passed;\r
267 }\r
268 if (isset($scores->failed)) {\r
269 $return->info .= get_string('failed','scorm').':&nbsp;'.$scores->failed.'<br />';\r
270 $scores->notattempted -= $scores->failed;\r
271 }\r
272 if (isset($scores->incomplete)) {\r
273 $return->info .= get_string('incomplete','scorm').':&nbsp;'.$scores->incomplete.'<br />';\r
274 $scores->notattempted -= $scores->incomplete;\r
275 }\r
276 if (isset($scores->browsed)) {\r
277 $return->info .= get_string('browsed','scorm').':&nbsp;'.$scores->browsed.'<br />';\r
278 $scores->notattempted -= $scores->browsed;\r
279 }\r
280 $return->time = $scores->lastmodify;\r
281 if ($return->info == '') {\r
282 $return = NULL;\r
283 } else {\r
284 $return->info .= get_string('notattempted','scorm').':&nbsp;'.$scores->notattempted.'<br />';\r
285 }\r
286 break;\r
287 }\r
288 }\r
289 return $return;\r
290}\r
291\r
292/**\r
293* Print a detailed representation of what a user has done with\r
294* a given particular instance of this module, for user activity reports.\r
295*\r
296* @param int $course Course id\r
297* @param int $user User id\r
298* @param int $mod \r
299* @param int $scorm The scorm id\r
300* @return boolean\r
301*/\r
302function scorm_user_complete($course, $user, $mod, $scorm) {\r
303 global $CFG;\r
304\r
305 $liststyle = 'structlist';\r
306 $scormpixdir = $CFG->modpixpath.'/scorm/pix';\r
307 $now = time();\r
308 $firstmodify = $now;\r
309 $lastmodify = 0;\r
310 $sometoreport = false;\r
311 $report = '';\r
312 \r
313 if ($orgs = get_records_select('scorm_scoes',"scorm='$scorm->id' AND organization='' AND launch=''",'id','id,identifier,title')) {\r
314 if (count($orgs) <= 1) {\r
315 unset($orgs);\r
316 $orgs[]->identifier = '';\r
317 }\r
318 $report .= '<div class="mod-scorm">'."\n";\r
319 foreach ($orgs as $org) {\r
320 $organizationsql = '';\r
321 $currentorg = '';\r
322 if (!empty($org->identifier)) {\r
323 $report .= '<div class="orgtitle">'.$org->title.'</div>';\r
324 $currentorg = $org->identifier;\r
325 $organizationsql = "AND organization='$currentorg'";\r
326 }\r
327 $report .= "<ul id='0' class='$liststyle'>";\r
328 if ($scoes = get_records_select('scorm_scoes',"scorm='$scorm->id' $organizationsql order by id ASC")){\r
329 $level=0;\r
330 $sublist=1;\r
331 $parents[$level]='/';\r
332 foreach ($scoes as $sco) {\r
333 if ($parents[$level]!=$sco->parent) {\r
334 if ($level>0 && $parents[$level-1]==$sco->parent) {\r
335 $report .= "\t\t</ul></li>\n";\r
336 $level--;\r
337 } else {\r
338 $i = $level;\r
339 $closelist = '';\r
340 while (($i > 0) && ($parents[$level] != $sco->parent)) {\r
341 $closelist .= "\t\t</ul></li>\n";\r
342 $i--;\r
343 }\r
344 if (($i == 0) && ($sco->parent != $currentorg)) {\r
345 $report .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";\r
346 $level++;\r
347 } else {\r
348 $report .= $closelist;\r
349 $level = $i;\r
350 }\r
351 $parents[$level]=$sco->parent;\r
352 }\r
353 }\r
354 $report .= "\t\t<li>";\r
355 $nextsco = next($scoes);\r
356 if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {\r
357 $sublist++;\r
358 } else {\r
359 $report .= '<img src="'.$scormpixdir.'/spacer.gif" />';\r
360 }\r
361\r
362 if ($sco->launch) {\r
363 require_once('locallib.php');\r
364 $score = '';\r
365 $totaltime = '';\r
366 if ($usertrack=scorm_get_tracks($sco->id,$user->id)) {\r
367 if ($usertrack->status == '') {\r
368 $usertrack->status = 'notattempted';\r
369 }\r
370 $strstatus = get_string($usertrack->status,'scorm');\r
371 $report .= "<img src='".$scormpixdir.'/'.$usertrack->status.".gif' alt='$strstatus' title='$strstatus' />";\r
372 if ($usertrack->timemodified != 0) {\r
373 if ($usertrack->timemodified > $lastmodify) {\r
374 $lastmodify = $usertrack->timemodified;\r
375 }\r
376 if ($usertrack->timemodified < $firstmodify) {\r
377 $firstmodify = $usertrack->timemodified;\r
378 }\r
379 }\r
380 } else {\r
381 if ($sco->scormtype == 'sco') {\r
382 $report .= '<img src="'.$scormpixdir.'/'.'notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';\r
383 } else {\r
384 $report .= '<img src="'.$scormpixdir.'/'.'asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';\r
385 }\r
386 }\r
387 $report .= "&nbsp;$sco->title $score$totaltime</li>\n";\r
388 if ($usertrack !== false) {\r
389 $sometoreport = true;\r
390 $report .= "\t\t\t<li><ul class='$liststyle'>\n";\r
391 foreach($usertrack as $element => $value) {\r
392 if (substr($element,0,3) == 'cmi') {\r
393 $report .= '<li>'.$element.' => '.$value.'</li>';\r
394 }\r
395 }\r
396 $report .= "\t\t\t</ul></li>\n";\r
397 } \r
398 } else {\r
399 $report .= "&nbsp;$sco->title</li>\n";\r
400 }\r
401 }\r
402 for ($i=0;$i<$level;$i++) {\r
403 $report .= "\t\t</ul></li>\n";\r
404 }\r
405 }\r
406 $report .= "\t</ul><br />\n";\r
407 }\r
408 $report .= "</div>\n";\r
409 }\r
410 if ($sometoreport) {\r
411 if ($firstmodify < $now) {\r
412 $timeago = format_time($now - $firstmodify);\r
413 echo get_string('firstaccess','scorm').': '.userdate($firstmodify).' ('.$timeago.")<br />\n";\r
414 }\r
415 if ($lastmodify > 0) {\r
416 $timeago = format_time($now - $lastmodify);\r
417 echo get_string('lastaccess','scorm').': '.userdate($lastmodify).' ('.$timeago.")<br />\n";\r
418 }\r
419 echo get_string('report','scorm').":<br />\n";\r
420 echo $report;\r
421 } else {\r
422 print_string('noactivity','scorm');\r
423 }\r
424\r
425 return true;\r
426}\r
427\r
428/**\r
429* Given a list of logs, assumed to be those since the last login\r
430* this function prints a short list of changes related to this module\r
431* If isteacher is true then perhaps additional information is printed.\r
432* This function is called from course/lib.php: print_recent_activity()\r
433*\r
434* @param reference $logs Logs reference\r
435* @param boolean $isteacher\r
436* @return boolean\r
437*/\r
438function scorm_print_recent_activity(&$logs, $isteacher=false) {\r
439 return false; // True if anything was printed, otherwise false\r
440}\r
441\r
442/**\r
443* Function to be run periodically according to the moodle cron\r
444* This function searches for things that need to be done, such\r
445* as sending out mail, toggling flags etc ...\r
446*\r
447* @return boolean\r
448*/\r
449function scorm_cron () {\r
450\r
451 global $CFG;\r
452\r
453 return true;\r
454}\r
455\r
456/**\r
457* Given a scorm id return all the grades of that activity\r
458*\r
459* @param int $scormid Scorm instance id\r
460* @return mixed\r
461*/\r
462function scorm_grades($scormid) {\r
463\r
464 global $CFG;\r
465\r
466 if (!$scorm = get_record('scorm', 'id', $scormid)) {\r
467 return NULL;\r
468 }\r
469 if (!$scoes = get_records('scorm_scoes','scorm',$scormid)) {\r
470 return NULL;\r
471 }\r
472\r
473 if ($scorm->grademethod == VALUESCOES) {\r
474 if (!$return->maxgrade = count_records_select('scorm_scoes',"scorm='$scormid' AND launch<>''")) {\r
475 return NULL;\r
476 }\r
477 } else {\r
478 $return->maxgrade = $scorm->maxgrade;\r
479 }\r
480\r
481 $return->grades = NULL;\r
482 if ($scousers=get_records_select('scorm_scoes_track', "scormid='$scormid' GROUP BY userid", "", "userid,null")) {\r
483 require_once('locallib.php');\r
484 foreach ($scousers as $scouser) {\r
485 $return->grades[$scouser->userid] = scorm_grade_user($scoes, $scouser->userid, $scorm->grademethod);\r
486 }\r
487 }\r
488 return $return;\r
489}\r
490\r
491function scorm_get_view_actions() {\r
492 return array('pre-view','view','view all','report');\r
493}\r
494\r
495function scorm_get_post_actions() {\r
496 return array();\r
497}\r
498\r
499/**\r
500* This function will permanently delete the given\r
501* directory and all files and subdirectories.\r
502*\r
503* @param string $directory The directory to remove\r
504* @return boolean\r
505*/\r
506function scorm_delete_files($directory) {\r
507 if (is_dir($directory)) {\r
508 $files=scorm_scandir($directory);\r
509 foreach($files as $file) {\r
510 if (($file != '.') && ($file != '..')) {\r
511 if (!is_dir($directory.'/'.$file)) {\r
512 unlink($directory.'/'.$file);\r
513 } else {\r
514 scorm_delete_files($directory.'/'.$file);\r
515 }\r
516 }\r
517 }\r
518 rmdir($directory);\r
519 return true;\r
520 }\r
521 return false;\r
522}\r
523\r
524/**\r
525* Given a diretory path returns the file list\r
526*\r
527* @param string $directory\r
528* @return array\r
529*/\r
530function scorm_scandir($directory) {\r
531 if (version_compare(phpversion(),'5.0.0','>=')) {\r
532 return scandir($directory);\r
533 } else {\r
534 $files = array();\r
535 if ($dh = opendir($directory)) {\r
536 while (($file = readdir($dh)) !== false) {\r
537 $files[] = $file;\r
538 }\r
539 closedir($dh);\r
540 }\r
541 return $files;\r
542 }\r
543}\r
544\r
da52f152 545\r
1a50a1f9 546function scorm_option2text($scorm) {\r
547 global $SCORM_POPUP_OPTIONS;\r
548\r
549 if (isset($scorm->popup)) {\r
550 if ($scorm->popup) {\r
551 $optionlist = array();\r
da52f152 552 foreach ($SCORM_POPUP_OPTIONS as $name => $option) {\r
553 if (isset($scorm->$name)) {\r
554 $optionlist[] = $name.'='.$scorm->$name;\r
1a50a1f9 555 } else {\r
da52f152 556 $optionlist[] = $name.'=0';\r
1a50a1f9 557 }\r
558 } \r
559 $scorm->options = implode(',', $optionlist);\r
560 } else {\r
561 $scorm->options = '';\r
562 } \r
563 } else {\r
564 $scorm->popup = 0;\r
565 $scorm->options = '';\r
566 }\r
567 return $scorm;\r
568}\r
569\r
da52f152 570?>