Fixed a problem with AICC tracking
[moodle.git] / mod / scorm / lib.php
1 <?php  // $Id$
3 /// Library of functions and constants for module scorm
4 /// (replace scorm with the name of your module and delete this line)
6 include_once($CFG->dirroot.'/mod/scorm/xml2array.class.php');
8 define('VALUESCOES', '0');
9 define('VALUEHIGHEST', '1');
10 define('VALUEAVERAGE', '2');
11 define('VALUESUM', '3');
12 $SCORM_GRADE_METHOD = array (VALUESCOES => get_string('gradescoes', 'scorm'),
13                              VALUEHIGHEST => get_string('gradehighest', 'scorm'),
14                              VALUEAVERAGE => get_string('gradeaverage', 'scorm'),
15                              VALUESUM => get_string('gradesum', 'scorm'));
17 //if (!isset($CFG->scorm_validate)) {
18 //    $scormvalidate = 'none';
19 //    if (extension_loaded('domxml') && version_compare(phpversion(),'5.0.0','<')) {
20 //        $scormvalidate = 'domxml';
21 //    }
22 //    if (version_compare(phpversion(),'5.0.0','>=')) {
23 //        $scormvalidate = 'php5';
24 //    }
25 //    set_config('scorm_validate', $scormvalidate);
26 //}
28 if (!isset($CFG->scorm_frameheight)) {
29     set_config('scorm_frameheight','600');
30 }
32 if (!isset($CFG->scorm_framewidth)) {
33     set_config('scorm_framewidth','800');
34 }
36 function scorm_add_instance($scorm) {
37 /// Given an object containing all the necessary data,
38 /// (defined by the form in mod.html) this function
39 /// will create a new instance and return the id number
40 /// of the new instance.
42     $scorm->timemodified = time();
44     # May have to add extra stuff in here #
45     global $CFG;
47     $id = insert_record('scorm', $scorm);
49     //
50     // Rename temp scorm dir to scorm id
51     //
53     $scormdir = $CFG->dataroot.'/'.$scorm->course.'/moddata/scorm';
54     rename($scormdir.$scorm->datadir,$scormdir.'/'.$id);
55     //
56     // Parse scorm manifest
57     //
58     if ($scorm->launch == 0) {
59         $scorm->launch = scorm_parse($scormdir.'/'.$id,$scorm->pkgtype,$id);
60         set_field('scorm','launch',$scorm->launch,'id',$id);
61     }
63     return $id;
64 }
67 function scorm_update_instance($scorm) {
68 /// Given an object containing all the necessary data,
69 /// (defined by the form in mod.html) this function
70 /// will update an existing instance with new data.
72     $scorm->timemodified = time();
73     $scorm->id = $scorm->instance;
75     # May have to add extra stuff in here #
76     global $CFG;
79     //
80     // Check if scorm manifest needs to be reparsed
81     //
82     if ($scorm->launch == 0) {
83         $scormdir = $CFG->dataroot.'/'.$scorm->course.'/moddata/scorm';
84         if (isset($scorm->datadir) && ($scorm->datadir != $scorm->id)) {
85             scorm_delete_files($scormdir.'/'.$scorm->id);
86             rename($scormdir.$scorm->datadir,$scormdir.'/'.$scorm->id);
87         }
88         $scorm->launch = scorm_parse($scormdir.'/'.$scorm->id,$scorm->pkgtype,$scorm->id);
89     }
91     return update_record('scorm', $scorm);
92 }
95 function scorm_delete_instance($id) {
96 /// Given an ID of an instance of this module,
97 /// this function will permanently delete the instance
98 /// and any data that depends on it.
100     require('../config.php');
102     if (! $scorm = get_record('scorm', 'id', $id)) {
103         return false;
104     }
106     $result = true;
108     # Delete any dependent files #
109     scorm_delete_files($CFG->dataroot.'/'.$scorm->course.'/moddata/scorm/'.$scorm->id);
111     # Delete any dependent records here #
112     if (! delete_records('scorm_scoes_track', 'scormid', $scorm->id)) {
113         $result = false;
114     }
115     if (! delete_records('scorm_scoes', 'scorm', $scorm->id)) {
116         $result = false;
117     }
118     if (! delete_records('scorm', 'id', $scorm->id)) {
119         $result = false;
120     }
123     return $result;
126 function scorm_user_outline($course, $user, $mod, $scorm) {
127 /// Return a small object with summary information about what a
128 /// user has done with a given particular instance of this module
129 /// Used for user activity reports.
131     $return = NULL;
132     $scores->values = 0;
133     $scores->sum = 0;
134     $scores->max = 0;
135     $scores->lastmodify = 0;
136     $scores->count = 0;
137     if ($scoes = get_records_select("scorm_scoes","scorm='$scorm->id' ORDER BY id")) {
138         foreach ($scoes as $sco) {
139             if ($sco->launch!='') {
140                 $scores->count++;
141                 if ($userdata = scorm_get_tracks($sco->id, $user->id)) {
142                     if (!isset($scores->{$userdata->status})) {
143                         $scores->{$userdata->status} = 1;
144                     } else {    
145                         $scores->{$userdata->status}++;
146                     }
147                     if (!empty($userdata->score_raw)) {
148                         $scores->values++;
149                         $scores->sum += $userdata->score_raw;
150                         $scores->max = ($userdata->score_raw > $scores->max)?$userdata->score_raw:$scores->max;
151                     }
152                     if (isset($userdata->timemodified) && ($userdata->timemodified > $scores->lastmodify)) {
153                         $scores->lastmodify = $userdata->timemodified;
154                     }
155                 }
156             }
157         }
158         switch ($scorm->grademethod) {
159             case VALUEHIGHEST:
160                 if ($scores->values > 0) {
161                     $return->info = get_string('score','scorm').':&nbsp;'.$scores->max;
162                     $return->time = $scores->lastmodify;
163                 }
164             break;
165             case VALUEAVERAGE:
166                 if ($scores->values > 0) {
167                     $return->info = get_string('score','scorm').':&nbsp;'.$scores->sum/$scores->values;
168                     $return->time = $scores->lastmodify;
169                 }
170             break;
171             case VALUESUM:
172                 if ($scores->values > 0) {
173                     $return->info = get_string('score','scorm').':&nbsp;'.$scores->sum;
174                     $return->time = $scores->lastmodify;
175                 }
176             break;
177             case VALUESCOES:
178                 $return->info = '';
179                 $scores->notattempted = $scores->count;
180                 if (isset($scores->completed)) {
181                     $return->info .= get_string('completed','scorm').':&nbsp;'.$scores->completed.'<br />';
182                     $scores->notattempted -= $scores->completed;
183                 }
184                 if (isset($scores->passed)) {
185                     $return->info .= get_string('passed','scorm').':&nbsp;'.$scores->passed.'<br />';
186                     $scores->notattempted -= $scores->passed;
187                 }
188                 if (isset($scores->failed)) {
189                     $return->info .= get_string('failed','scorm').':&nbsp;'.$scores->failed.'<br />';
190                     $scores->notattempted -= $scores->failed;
191                 }
192                 if (isset($scores->incomplete)) {
193                     $return->info .= get_string('incomplete','scorm').':&nbsp;'.$scores->incomplete.'<br />';
194                     $scores->notattempted -= $scores->incomplete;
195                 }
196                 if (isset($scores->browsed)) {
197                     $return->info .= get_string('browsed','scorm').':&nbsp;'.$scores->browsed.'<br />';
198                     $scores->notattempted -= $scores->browsed;
199                 }
200                 $return->time = $scores->lastmodify;
201                 if ($return->info == '') {
202                     $return = NULL;
203                 } else {
204                     $return->info .= get_string('notattempted','scorm').':&nbsp;'.$scores->notattempted.'<br />';
205                 }
206             break;
207         }
208     }
209     return $return;
212 function scorm_user_complete($course, $user, $mod, $scorm) {
213 /// Print a detailed representation of what a  user has done with
214 /// a given particular instance of this module, for user activity reports.
215     global $CFG;
217     $liststyle = 'structlist';
218     $scormpixdir = $CFG->wwwroot.'/mod/scorm/pix/';
219     $now = time();
220     $firstmodify = $now;
221     $lastmodify = 0;
222     $sometoreport = false;
223     $report = '';
224     
225     if ($orgs = get_records_select('scorm_scoes',"scorm='$scorm->id' AND organization='' AND launch=''",'id','id,identifier,title')) {
226         if (count($orgs) <= 1) {
227             unset($orgs);
228             $orgs[]->identifier = '';
229         }
230         foreach ($orgs as $org) {
231             $organizationsql = '';
232             $currentorg = '';
233             if (!empty($org->identifier)) {
234                 $report .= '<div class="orgtitle">'.$org->title.'</div>';
235                 $currentorg = $org->identifier;
236                 $organizationsql = "AND organization='$currentorg'";
237             }
238             $report .= "<ul id='0' class='$liststyle'>";
239             if ($scoes = get_records_select('scorm_scoes',"scorm='$scorm->id' $organizationsql order by id ASC")){
240                 $level=0;
241                 $sublist=1;
242                 $parents[$level]='/';
243                 foreach ($scoes as $sco) {
244                     if ($parents[$level]!=$sco->parent) {
245                         if ($level>0 && $parents[$level-1]==$sco->parent) {
246                             $report .= "\t\t</ul></li>\n";
247                             $level--;
248                         } else {
249                             $i = $level;
250                             $closelist = '';
251                             while (($i > 0) && ($parents[$level] != $sco->parent)) {
252                                 $closelist .= "\t\t</ul></li>\n";
253                                 $i--;
254                             }
255                             if (($i == 0) && ($sco->parent != $currentorg)) {
256                                 $report .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
257                                 $level++;
258                             } else {
259                                 $report .= $closelist;
260                                 $level = $i;
261                             }
262                             $parents[$level]=$sco->parent;
263                         }
264                     }
265                     $report .= "\t\t<li>";
266                     $nextsco = next($scoes);
267                     if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
268                         $sublist++;
269                     } else {
270                         $report .= '<img src="'.$scormpixdir.'spacer.gif" />';
271                     }
273                     if ($sco->launch) {
274                         $score = '';
275                         $totaltime = '';
276                         if ($usertrack=scorm_get_tracks($sco->id,$user->id)) {
277                             if ($usertrack->status == '') {
278                                 $usertrack->status = 'notattempted';
279                             }
280                             $strstatus = get_string($usertrack->status,'scorm');
281                             $report .= "<img src='".$scormpixdir.$usertrack->status.".gif' alt='$strstatus' title='$strstatus' />";
282                             //if ($usertrack->score_raw != '') {
283                             //    $score = ' - ('.get_string('score','scorm').':&nbsp;'.$usertrack->score_raw.')';
284                             //}
285                             //if ($usertrack->total_time != '00:00:00') {
286                             //    $totaltime = ' - ('.get_string('totaltime','scorm').':&nbsp;'.$usertrack->total_time.')';
287                             //}
288                             if ($usertrack->timemodified != 0) {
289                                 if ($usertrack->timemodified > $lastmodify) {
290                                     $lastmodify = $usertrack->timemodified;
291                                 }
292                                 if ($usertrack->timemodified < $firstmodify) {
293                                     $firstmodify = $usertrack->timemodified;
294                                 }
295                             }
296                         } else {
297                             if ($sco->scormtype == 'sco') {
298                                 $report .= '<img src="'.$scormpixdir.'notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
299                             } else {
300                                 $report .= '<img src="'.$scormpixdir.'asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
301                             }
302                         }
303                         $report .= "&nbsp;$sco->title $score$totaltime</li>\n";
304                         if ($usertrack !== false) {
305                             $sometoreport = true;
306                             $report .= "\t\t\t<li><ul class='$liststyle'>\n";
307                             foreach($usertrack as $element => $value) {
308                                 if (substr($element,0,3) == 'cmi') {
309                                     $report .= '<li>'.$element.' => '.$value.'</li>';
310                                 }
311                             }
312                             $report .= "\t\t\t</ul></li>\n";
313                         } 
314                     } else {
315                         $report .= "&nbsp;$sco->title</li>\n";
316                     }
317                 }
318                 for ($i=0;$i<$level;$i++) {
319                     $report .= "\t\t</ul></li>\n";
320                 }
321             }
322             $report .= "\t</ul><br />\n";
323         }
324     }
325     if ($sometoreport) {
326         if ($firstmodify < $now) {
327             $timeago = format_time($now - $firstmodify);
328             echo get_string('firstaccess','scorm').': '.userdate($firstmodify).' ('.$timeago.")<br />\n";
329         }
330         if ($lastmodify > 0) {
331             $timeago = format_time($now - $lastmodify);
332             echo get_string('lastaccess','scorm').': '.userdate($lastmodify).' ('.$timeago.")<br />\n";
333         }
334         echo get_string('report','scorm').":<br />\n";
335         echo $report;
336     } else {
337         print_string('noactivity','scorm');
338     }
340     return true;
343 function scorm_print_recent_activity(&$logs, $isteacher=false) {
344 /// Given a list of logs, assumed to be those since the last login
345 /// this function prints a short list of changes related to this module
346 /// If isteacher is true then perhaps additional information is printed.
347 /// This function is called from course/lib.php: print_recent_activity()
349     return false;  // True if anything was printed, otherwise false
352 function scorm_cron () {
353 /// Function to be run periodically according to the moodle cron
354 /// This function searches for things that need to be done, such
355 /// as sending out mail, toggling flags etc ...
357     global $CFG;
359     return true;
362 function scorm_grades($scormid) {
363 /// Must return an array of grades for a given instance of this module,
364 /// indexed by user.  It also returns a maximum allowed grade.
366     global $CFG;
368     if (!$scorm = get_record('scorm', 'id', $scormid)) {
369         return NULL;
370     }
371     if (!$scoes = get_records('scorm_scoes','scorm',$scormid)) {
372         return NULL;
373     }
375     if ($scorm->grademethod == VALUESCOES) {
376         if (!$return->maxgrade = count_records_select('scorm_scoes',"scorm='$scormid' AND launch<>''")) {
377             return NULL;
378         }
379     } else {
380         $return->maxgrade = $scorm->maxgrade;
381     }
383     $return->grades = NULL;
384     if ($scousers=get_records_select('scorm_scoes_track', "scormid='$scormid' GROUP BY userid", "", "userid,null")) {
385         foreach ($scousers as $scouser) {
386             $scores = NULL;
387             $scores->scoes = 0;
388             $scores->values = 0;
389             $scores->max = 0;
390             $scores->sum = 0;
392             foreach ($scoes as $sco) {
393                 $userdata=scorm_get_tracks($sco->id, $scouser->userid);
394                 if (($userdata->status == 'completed') || ($userdata->status == 'passed')) {
395                     $scores->scoes++;
396                 }
397                 if (!empty($userdata->score_raw)) {
398                     $scores->values++;
399                     $scores->sum += $userdata->score_raw;
400                     $scores->max = ($userdata->score_raw > $scores->max)?$userdata->score_raw:$scores->max;
401                 }
402             }
403             switch ($scorm->grademethod) {
404                 case VALUEHIGHEST:
405                     $return->grades[$scouser->userid] = $scores->max;
406                 break;
407                 case VALUEAVERAGE:
408                     if ($scores->values > 0) {
409                         $return->grades[$scouser->userid] = $scores->sum/$scores->values;
410                     } else {
411                         $return->grades[$scouser->userid] = 0;
412                     }
413                 break;
414                 case VALUESUM:
415                     $return->grades[$scouser->userid] = $scores->sum;
416                 break;
417                 case VALUESCOES:
418                     $return->grades[$scouser->userid] = $scores->scoes;
419                 break;
420             }
421        }
422     }
423     return $return;
427 //////////////////////////////////////////////////////////////////////////////////////
428 /// Any other scorm functions go here.  Each of them must have a name that
429 /// starts with scorm_
432 function scorm_randstring($len = '8') {
433     $rstring = NULL;
434     $lchar = '';
435     for ($i=0; $i<$len; $i++) {
436         $char = chr(rand(48,122));
437         while (!ereg('[a-zA-Z0-9]', $char)){
438             if ($char == $lchar) continue;
439             $char = chr(rand(48,90));
440         }
441         $rstring .= $char;
442         $lchar = $char;
443     }
444     return $rstring;
448 function scorm_datadir($strPath, $existingdir='')
450     global $CFG;
452     if (($existingdir!='') && (is_dir($strPath.'/'.$existingdir)))
453         return $strPath.'/'.$existingdir;
455     if (is_dir($strPath)) {
456         do {
457             $datadir='/'.scorm_randstring();
458         } while (file_exists($strPath.$datadir));
459         mkdir($strPath.$datadir, $CFG->directorypermissions);
460         @chmod($strPath.$datadir, $CFG->directorypermissions);  // Just in case mkdir didn't do it
461         return $strPath.$datadir;
462     } else {
463         return false;
464     }
467 function scorm_validate($packagedir) {
468     if (is_file($packagedir.'/imsmanifest.xml')) {
469         $validation->result = 'found';
470         $validation->pkgtype = 'SCORM';
471     } else {
472         if ($handle = opendir($packagedir)) {
473             while (($file = readdir($handle)) !== false) {
474                 $ext = substr($file,strrpos($file,'.'));
475                 if (strtolower($ext) == '.cst') {
476                     $validation->result = 'found';
477                     $validation->pkgtype = 'AICC';
478                     break;
479                 }
480             }
481             closedir($handle);
482         }
483         if (!isset($validation)) {
484             $validation->result = 'nomanifest';
485             $validation->pkgtype = 'SCORM';
486         }
487     }
488     return $validation;
491 function scorm_delete_files($directory) {
492     if (is_dir($directory)) {
493         $files=scorm_scandir($directory);
494         foreach($files as $file) {
495             if (($file != '.') && ($file != '..')) {
496                 if (!is_dir($directory.'/'.$file)) {
497                     unlink($directory.'/'.$file);
498                 } else {
499                     scorm_delete_files($directory.'/'.$file);
500                 }
501             }
502         }
503         rmdir($directory);
504     }
507 function scorm_scandir($directory) {
508     if (version_compare(phpversion(),'5.0.0','>=')) {
509         return scandir($directory);
510     } else {
511         $files = null;
512         if ($dh = opendir($directory)) {
513             while (($file = readdir($dh)) !== false) {
514                $files[] = $file;
515             }
516             closedir($dh);
517         }
518         return $files;
519     }
522 function scorm_parse($pkgdir,$pkgtype,$scormid){
523     delete_records('scorm_scoes','scorm',$scormid);
524     delete_records('scorm_scoes_track','scormid',$scormid);
526     if ($pkgtype == 'AICC') {
527         return scorm_parse_aicc($pkgdir,$scormid);
528     } else {
529         return scorm_parse_scorm($pkgdir.'/imsmanifest.xml',$scormid);
530     }
533 function scorm_get_aicc_columns($row,$mastername='system_id') {
534     $tok = strtok(strtolower($row),"\",\n\r");
535     $result->columns = array();
536     $i=0;
537     while ($tok) {
538         if ($tok !='') {
539             $result->columns[] = $tok;
540             if ($tok == $mastername) {
541                 $result->mastercol = $i;
542             }
543             $i++;
544         }
545         $tok = strtok("\",\n\r");
546     }
547     return $result;
550 function scorm_forge_cols_regexp($columns,$remodule='(".*")?,') {
551     $regexp = '/^';
552     foreach ($columns as $column) {
553         $regexp .= $remodule;
554     }
555     $regexp = substr($regexp,0,-1) . '/';
556     return $regexp;
559 function scorm_parse_aicc($pkgdir,$scormid){
560     $version = 'AICC';
561     $ids = array();
562     $courses = array();
563     if ($handle = opendir($pkgdir)) {
564         while (($file = readdir($handle)) !== false) {
565             $ext = substr($file,strrpos($file,'.'));
566             $extension = strtolower(substr($ext,1));
567             $id = strtolower(basename($file,$ext));
568             $ids[$id]->$extension = $file;
569         }
570         closedir($handle);
571     }
572     foreach ($ids as $courseid => $id) {
573         if (isset($id->crs)) {
574             if (is_file($pkgdir.'/'.$id->crs)) {
575                 $rows = file($pkgdir.'/'.$id->crs);
576                 foreach ($rows as $row) {
577                     if (preg_match("/^(.+)=(.+)$/",$row,$matches)) {
578                         switch (strtolower(trim($matches[1]))) {
579                             case 'course_id':
580                                 $courses[$courseid]->id = trim($matches[2]);
581                             break;
582                             case 'course_title':
583                                 $courses[$courseid]->title = trim($matches[2]);
584                             break;
585                             case 'version':
586                                 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
587                             break;
588                         }
589                     }
590                 }
591             }
592         }
593         if (isset($id->des)) {
594             $rows = file($pkgdir.'/'.$id->des);
595             $columns = scorm_get_aicc_columns($rows[0]);
596             $regexp = scorm_forge_cols_regexp($columns->columns);
597             for ($i=1;$i<count($rows);$i++) {
598                 if (preg_match($regexp,$rows[$i],$matches)) {
599                     for ($j=0;$j<count($columns->columns);$j++) {
600                         $column = $columns->columns[$j];
601                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
602                     }
603                 }
604             }
605         }
606         if (isset($id->au)) {
607             $rows = file($pkgdir.'/'.$id->au);
608             $columns = scorm_get_aicc_columns($rows[0]);
609             $regexp = scorm_forge_cols_regexp($columns->columns);
610             for ($i=1;$i<count($rows);$i++) {
611                 if (preg_match($regexp,$rows[$i],$matches)) {
612                     for ($j=0;$j<count($columns->columns);$j++) {
613                         $column = $columns->columns[$j];
614                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
615                     }
616                 }
617             }
618         }
619         if (isset($id->cst)) {
620             $rows = file($pkgdir.'/'.$id->cst);
621             $columns = scorm_get_aicc_columns($rows[0],'block');
622             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
623             for ($i=1;$i<count($rows);$i++) {
624                 if (preg_match($regexp,$rows[$i],$matches)) {
625                     for ($j=0;$j<count($columns->columns);$j++) {
626                         if ($j != $columns->mastercol) {
627                             $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
628                         }
629                     }
630                 }
631             }
632         }
633         if (isset($id->ort)) {
634             $rows = file($pkgdir.'/'.$id->ort);
635         }
636         if (isset($id->pre)) {
637             $rows = file($pkgdir.'/'.$id->pre);
638             $columns = scorm_get_aicc_columns($rows[0],'structure_element');
639             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+),');
640             for ($i=1;$i<count($rows);$i++) {
641                 if (preg_match($regexp,$rows[$i],$matches)) {
642                     $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]),1,-1);
643                 }
644             }
645         }
646         if (isset($id->cmp)) {
647             $rows = file($pkgdir.'/'.$id->cmp);
648         }
649     }
650     //print_r($courses);
651     $launch = 0;
652     if (isset($courses)) {
653         foreach ($courses as $course) {
654             unset($sco);
655             $sco->identifier = $course->id;
656             $sco->scorm = $scormid;
657             $sco->organization = '';
658             $sco->title = $course->title;
659             $sco->parent = '/';
660             $sco->launch = '';
661             $sco->scormtype = '';
662             //print_r($sco);
663             $id = insert_record('scorm_scoes',$sco);
664             if ($launch == 0) {
665                 $launch = $id;
666             }
667             if (isset($course->elements)) {
668                 foreach($course->elements as $element) {
669                     unset($sco);
670                     $sco->identifier = $element->system_id;
671                     $sco->scorm = $scormid;
672                     $sco->organization = $course->id;
673                     $sco->title = $element->title;
674                     if (strtolower($element->parent) == 'root') {
675                         $sco->parent = '/';
676                     } else {
677                         $sco->parent = $element->parent;
678                     }
679                     if (isset($element->file_name)) {
680                         $sco->launch = $element->file_name;
681                         $sco->scormtype = 'sco';
682                     } else {
683                         $element->file_name = '';
684                         $sco->scormtype = '';
685                     }
686                     if (!isset($element->prerequisites)) {
687                         $element->prerequisites = '';
688                     }
689                     $sco->prerequisites = $element->prerequisites;
690                     if (!isset($element->max_time_allowed)) {
691                         $element->max_time_allowed = '';
692                     }
693                     $sco->maxtimeallowed = $element->max_time_allowed;
694                     if (!isset($element->time_limit_action)) {
695                         $element->time_limit_action = '';
696                     }
697                     $sco->timelimitaction = $element->time_limit_action;
698                     if (!isset($element->mastery_score)) {
699                         $element->mastery_score = '';
700                     }
701                     $sco->masteryscore = $element->mastery_score;
702                     $sco->previous = 0;
703                     $sco->next = 0;
704                     $id = insert_record('scorm_scoes',$sco);
705                     if ($launch==0) {
706                         $launch = $id;
707                     }
708                 }
709             }
710         }
711     }
712     set_field('scorm','version','AICC','id',$scormid);
713     return $launch;
716 function scorm_get_resources($blocks) {
717     foreach ($blocks as $block) {
718         if ($block['name'] == 'RESOURCES') {
719             foreach ($block['children'] as $resource) {
720                 if ($resource['name'] == 'RESOURCE') {
721                     $resources[addslashes($resource['attrs']['IDENTIFIER'])] = $resource['attrs'];
722                 }
723             }
724         }
725     }
726     return $resources;
729 function scorm_get_manifest($blocks,$scoes) {
730     static $parents = array();
731     static $resources;
733     static $manifest;
734     static $organization;
736     if (count($blocks) > 0) {
737         foreach ($blocks as $block) {
738             switch ($block['name']) {
739                 case 'METADATA':
740                     if (isset($block['children'])) {
741                         foreach ($block['children'] as $metadata) {
742                             if ($metadata['name'] == 'SCHEMAVERSION') {
743                                 if (empty($scoes->version)) {
744                                     if (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/",$metadata['tagData'],$matches)) {
745                                         $scoes->version = 'SCORM_'.$matches[count($matches)-1];
746                                     } else {
747                                         $scoes->version = 'SCORM_1.2';
748                                     }
749                                 }
750                             }
751                         }
752                     }
753                 break;
754                 case 'MANIFEST':
755                     $manifest = addslashes($block['attrs']['IDENTIFIER']);
756                     $organization = '';
757                     $resources = array();
758                     $resources = scorm_get_resources($block['children']);
759                     $scoes = scorm_get_manifest($block['children'],$scoes);
760                     if (count($scoes->elements) <= 0) {
761                         foreach ($resources as $item => $resource) {
762                             if (!empty($resource['HREF'])) {
763                                 $sco = new stdClass();
764                                 $sco->identifier = $item;
765                                 $sco->title = $item;
766                                 $sco->parent = '/';
767                                 $sco->launch = addslashes($resource['HREF']);
768                                 $sco->scormtype = addslashes($resource['ADLCP:SCORMTYPE']);
769                                 $scoes->elements[$manifest][$organization][$item] = $sco;
770                             }
771                         }
772                     }
773                 break;
774                 case 'ORGANIZATIONS':
775                     if (!isset($scoes->defaultorg)) {
776                         $scoes->defaultorg = addslashes($block['attrs']['DEFAULT']);
777                     }
778                     $scoes = scorm_get_manifest($block['children'],$scoes);
779                 break;
780                 case 'ORGANIZATION':
781                     $identifier = addslashes($block['attrs']['IDENTIFIER']);
782                     $organization = '';
783                     $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
784                     $scoes->elements[$manifest][$organization][$identifier]->parent = '/';
785                     $scoes->elements[$manifest][$organization][$identifier]->launch = '';
786                     $scoes->elements[$manifest][$organization][$identifier]->scormtype = '';
788                     $parents = array();
789                     $parent = new stdClass();
790                     $parent->identifier = $identifier;
791                     $parent->organization = $organization;
792                     array_push($parents, $parent);
793                     $organization = $identifier;
795                     $scoes = scorm_get_manifest($block['children'],$scoes);
796                     
797                     array_pop($parents);
798                 break;
799                 case 'ITEM':
800                     $parent = array_pop($parents);
801                     array_push($parents, $parent);
802                     
803                     $identifier = addslashes($block['attrs']['IDENTIFIER']);
804                     $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
805                     $scoes->elements[$manifest][$organization][$identifier]->parent = $parent->identifier;
806                     if (!isset($block['attrs']['ISVISIBLE'])) {
807                         $block['attrs']['ISVISIBLE'] = 'true';
808                     }
809                     $scoes->elements[$manifest][$organization][$identifier]->isvisible = addslashes($block['attrs']['ISVISIBLE']);
810                     if (!isset($block['attrs']['PARAMETERS'])) {
811                         $block['attrs']['PARAMETERS'] = '';
812                     }
813                     $scoes->elements[$manifest][$organization][$identifier]->parameters = addslashes($block['attrs']['PARAMETERS']);
814                     if (!isset($block['attrs']['IDENTIFIERREF'])) {
815                         $scoes->elements[$manifest][$organization][$identifier]->launch = '';
816                         $scoes->elements[$manifest][$organization][$identifier]->scormtype = 'asset';
817                     } else {
818                         $idref = addslashes($block['attrs']['IDENTIFIERREF']);
819                         $scoes->elements[$manifest][$organization][$identifier]->launch = addslashes($resources[$idref]['HREF']);
820                         if (empty($resources[$idref]['ADLCP:SCORMTYPE'])) {
821                             $resources[$idref]['ADLCP:SCORMTYPE'] = 'asset';
822                         }
823                         $scoes->elements[$manifest][$organization][$identifier]->scormtype = addslashes($resources[$idref]['ADLCP:SCORMTYPE']);
824                     }
826                     $parent = new stdClass();
827                     $parent->identifier = $identifier;
828                     $parent->organization = $organization;
829                     array_push($parents, $parent);
831                     $scoes = scorm_get_manifest($block['children'],$scoes);
832                     
833                     array_pop($parents);
834                 break;
835                 case 'TITLE':
836                     $parent = array_pop($parents);
837                     array_push($parents, $parent);
838                     $scoes->elements[$manifest][$parent->organization][$parent->identifier]->title = addslashes($block['tagData']);
839                 break;
840                 case 'ADLCP:PREREQUISITES':
841                     if ($block['attrs']['TYPE'] == 'aicc_script') {
842                         $parent = array_pop($parents);
843                         array_push($parents, $parent);
844                         $scoes->elements[$manifest][$parent->organization][$parent->identifier]->prerequisites = addslashes($block['tagData']);
845                     }
846                 break;
847                 case 'ADLCP:MAXTIMEALLOWED':
848                     $parent = array_pop($parents);
849                     array_push($parents, $parent);
850                     $scoes->elements[$manifest][$parent->organization][$parent->identifier]->maxtimeallowed = addslashes($block['tagData']);
851                 break;
852                 case 'ADLCP:TIMELIMITACTION':
853                     $parent = array_pop($parents);
854                     array_push($parents, $parent);
855                     $scoes->elements[$manifest][$parent->organization][$parent->identifier]->timelimitaction = addslashes($block['tagData']);
856                 break;
857                 case 'ADLCP:DATAFROMLMS':
858                     $parent = array_pop($parents);
859                     array_push($parents, $parent);
860                     $scoes->elements[$manifest][$parent->organization][$parent->identifier]->datafromlms = addslashes($block['tagData']);
861                 break;
862                 case 'ADLCP:MASTERYSCORE':
863                     $parent = array_pop($parents);
864                     array_push($parents, $parent);
865                     $scoes->elements[$manifest][$parent->organization][$parent->identifier]->masteryscore = addslashes($block['tagData']);
866                 break;
867             }
868         }
869     }
871     return $scoes;
875 function scorm_parse_scorm($manifestfile,$scormid) {
876     
877     $launch = 0;
879     if (is_file($manifestfile)) {
880         $xmlstring = file_get_contents($manifestfile);
881         $objXML = new xml2Array();
882         $manifests = $objXML->parse($xmlstring);
883             
884         $scoes = new stdClass();
885         $scoes->version = 'SCORM';
886         $scoes = scorm_get_manifest($manifests,$scoes);
888         if (count($scoes->elements) > 0) {
889             foreach ($scoes->elements as $manifest => $organizations) {
890                 foreach ($organizations as $organization => $items) {
891                     foreach ($items as $identifier => $item) {
892                         $item->scorm = $scormid;
893                         $item->manifest = $manifest;
894                         $item->organization = $organization;
895                         $id = insert_record('scorm_scoes',$item);
896                 
897                         if (($launch == 0) && ((empty($scoes->defaultorg)) || ($scoes->defaultorg == $identifier))) {
898                             $launch = $id;
899                         }
900                     }
901                 }
902             }
903             set_field('scorm','version',$scoes->version,'id',$scormid);
904         }
905     } 
906     
907     return $launch;
910 function scorm_get_tracks($scoid,$userid) {
911 /// Gets all tracks of specified sco and user
912     global $CFG;
914     if ($tracks = get_records_select('scorm_scoes_track',"userid=$userid AND scoid=$scoid",'element ASC')) {
915         $usertrack->userid = $userid;
916         $usertrack->scoid = $scoid;
917         $usertrack->score_raw = '';
918         $usertrack->status = '';
919         $usertrack->total_time = '00:00:00';
920         $usertrack->session_time = '00:00:00';
921         $usertrack->timemodified = 0;
922         foreach ($tracks as $track) {
923             $element = $track->element;
924             $usertrack->{$element} = $track->value;
925             switch ($element) {
926                 case 'cmi.core.lesson_status':
927                 case 'cmi.completition_status':
928                     if ($track->value == 'not attempted') {
929                         $track->value = 'notattempted';
930                     }
931                     $usertrack->status = $track->value;
932                 break;
933                 case 'cmi.core.score.raw':
934                 case 'cmi.score.raw':
935                     $usertrack->score_raw = $track->value;
936                 break;
937                 case 'cmi.core.session_time':
938                 case 'cmi.session_time':
939                     $usertrack->session_time = $track->value;
940                 break;
941                 case 'cmi.core.total_time':
942                 case 'cmi.total_time':
943                     $usertrack->total_time = $track->value;
944                 break;
945             }
946             if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) {
947                 $usertrack->timemodified = $track->timemodified;
948             }
949         }
950         return $usertrack;
951     } else {
952         return false;
953     }
956 function scorm_get_user_data($userid) {
957 /// Gets user info required to display the table of scorm results
958 /// for report.php
960     return get_record('user','id',$userid,'','','','','firstname, lastname, picture');
963 function scorm_string_round($stringa, $len=11) {
964 // Crop a string to $len character and set an anchor title to the full string
965     if (strlen($stringa)>$len) {
966         return '<a name="none" title="'.$stringa.'">'.substr($stringa,0,$len-4).'...'.substr($stringa,strlen($stringa)-1,1).'</a>';
967     } else {
968         return $stringa;
969     }
971     
972 function scorm_eval_prerequisites($prerequisites,$usertracks) {
973     $element = '';
974     $stack = array();
975     $statuses = array(
976                 'passed' => 'passed',
977                 'completed' => 'completed',
978                 'failed' => 'failed',
979                 'incomplete' => 'incomplete',
980                 'browsed' => 'browsed',
981                 'not attempted' => 'notattempted',
982                 'p' => 'passed',
983                 'c' => 'completed',
984                 'f' => 'failed',
985                 'i' => 'incomplete',
986                 'b' => 'browsed',
987                 'n' => 'notattempted'
988                 );
989     $i=0;  
990     while ($i<strlen($prerequisites)) {
991         $symbol = $prerequisites[$i];
992         switch ($symbol) {
993             case '&':
994             case '|':
995                 $symbol .= $symbol;
996             case '~':
997             case '(':
998             case ')':
999             case '*':
1000             case '{':
1001             case '}':
1002             case ',':
1003                 $element = trim($element);
1004                 
1005                 if (!empty($element)) {
1006                     $element = trim($element);
1007                     if (isset($usertracks[$element])) {
1008                         $element = '((\''.$usertracks[$element]->status.'\' == \'completed\') || '.
1009                                   '(\''.$usertracks[$element]->status.'\' == \'passed\'))'; 
1010                     } else if (($operator = strpos($element,'=')) !== false) {
1011                         $item = trim(substr($element,0,$operator));
1012                         if (!isset($usertracks[$item])) {
1013                             return false;
1014                         }
1015                         
1016                         $value = trim(trim(substr($element,$operator+1)),'"');
1017                         if (isset($statuses[$value])) {
1018                             $status = $statuses[$value];
1019                         } else {
1020                             return false;
1021                         }
1022                                               
1023                         $element = '(\''.$usertracks[$item]->status.'\' == \''.$status.'\')';
1024                     } else if (($operator = strpos($element,'<>')) !== false) {
1025                         $item = trim(substr($element,0,$operator));
1026                         if (!isset($usertracks[$item])) {
1027                             return false;
1028                         }
1029                         
1030                         $value = trim(trim(substr($element,$operator+2)),'"');
1031                         if (isset($statuses[$value])) {
1032                             $status = $statuses[$value];
1033                         } else {
1034                             return false;
1035                         }
1036                         
1037                         $element = '(\''.$usertracks[$item]->status.'\' != \''.$status.'\')';
1038                     } else if (is_numeric($element)) {
1039                         if ($symbol == '*') {
1040                             $symbol = '';
1041                             $open = strpos($prerequisites,'{',$i);
1042                             $opened = 1;
1043                             $closed = 0;
1044                             for ($close=$open+1; (($opened > $closed) && ($close<strlen($prerequisites))); $close++) { 
1045                                  if ($prerequisites[$close] == '}') {
1046                                      $closed++;
1047                                  } else if ($prerequisites[$close] == '{') {
1048                                      $opened++;
1049                                  }
1050                             } 
1051                             $i = $close;
1052                             
1053                             $setelements = explode(',', substr($prerequisites, $open+1, $close-($open+1)-1));
1054                             $settrue = 0;
1055                             foreach ($setelements as $setelement) {
1056                                 if (scorm_eval_prerequisites($setelement,$usertracks)) {
1057                                     $settrue++;
1058                                 }
1059                             }
1060                             
1061                             if ($settrue >= $element) {
1062                                 $element = 'true'; 
1063                             } else {
1064                                 $element = 'false';
1065                             }
1066                         }
1067                     } else {
1068                         return false;
1069                     }
1070                     
1071                     array_push($stack,$element);
1072                     $element = '';
1073                 }
1074                 if ($symbol == '~') {
1075                     $symbol = '!';
1076                 }
1077                 if (!empty($symbol)) {
1078                     array_push($stack,$symbol);
1079                 }
1080             break;
1081             default:
1082                 $element .= $symbol;
1083             break;
1084         }
1085         $i++;
1086     }
1087     if (!empty($element)) {
1088         $element = trim($element);
1089         if (isset($usertracks[$element])) {
1090             $element = '((\''.$usertracks[$element]->status.'\' == \'completed\') || '.
1091                        '(\''.$usertracks[$element]->status.'\' == \'passed\'))'; 
1092         } else if (($operator = strpos($element,'=')) !== false) {
1093             $item = trim(substr($element,0,$operator));
1094             if (!isset($usertracks[$item])) {
1095                 return false;
1096             }
1097             
1098             $value = trim(trim(substr($element,$operator+1)),'"');
1099             if (isset($statuses[$value])) {
1100                 $status = $statuses[$value];
1101             } else {
1102                 return false;
1103             }
1104             
1105             $element = '(\''.$usertracks[$item]->status.'\' == \''.$status.'\')';
1106         } else if (($operator = strpos($element,'<>')) !== false) {
1107             $item = trim(substr($element,0,$operator));
1108             if (!isset($usertracks[$item])) {
1109                 return false;
1110             }
1111             
1112             $value = trim(trim(substr($element,$operator+1)),'"');
1113             if (isset($statuses[$value])) {
1114                 $status = $statuses[$value];
1115             } else {
1116                 return false;
1117             }
1118             
1119             $element = '(\''.$usertracks[$item]->status.'\' != \''.trim($status).'\')';
1120         } else {
1121             return false;
1122         }
1123         
1124         array_push($stack,$element);
1125     }
1126     return eval('return '.implode($stack).';');
1129 function scorm_insert_track($userid,$scormid,$scoid,$element,$value) {
1130     $id = null;
1131     if ($track = get_record_select('scorm_scoes_track',"userid='$userid' AND scormid='$scormid' AND scoid='$scoid' AND element='$element'")) {
1132         $track->value = $value;
1133         $track->timemodified = time();
1134         $id = update_record('scorm_scoes_track',$track);
1135     } else {
1136         $track->userid = $userid;
1137         $track->scormid = $scormid;
1138         $track->scoid = $scoid;
1139         $track->element = $element;
1140         $track->value = $value;
1141         $track->timemodified = time();
1142         $id = insert_record('scorm_scoes_track',$track);
1143     }
1144     return $id;
1147 function scorm_add_time($a, $b) {
1148     $aes = explode(':',$a);
1149     $bes = explode(':',$b);
1150     $aseconds = explode('.',$aes[2]);
1151     $bseconds = explode('.',$bes[2]);
1152     $change = 0;
1154     $acents = 0;  //Cents
1155     if (count($aseconds) > 1) {
1156         $acents = $aseconds[1];
1157     }
1158     $bcents = 0;
1159     if (count($bseconds) > 1) {
1160         $bcents = $bseconds[1];
1161     }
1162     $cents = $acents + $bcents;
1163     $change = floor($cents / 100);
1164     $cents = $cents - ($change * 100);
1165     if (floor($cents) < 10) {
1166         $cents = '0'. $cents;
1167     }
1169     $secs = $aseconds[0] + $bseconds[0] + $change;  //Seconds
1170     $change = floor($secs / 60);
1171     $secs = $secs - ($change * 60);
1172     if (floor($secs) < 10) {
1173         $secs = '0'. $secs;
1174     }
1176     $mins = $aes[1] + $bes[1] + $change;   //Minutes
1177     $change = floor($mins / 60);
1178     $mins = $mins - ($change * 60);
1179     if ($mins < 10) {
1180         $mins = '0' .  $mins;
1181     }
1183     $hours = $aes[0] + $bes[0] + $change;  //Hours
1184     if ($hours < 10) {
1185         $hours = '0' . $hours;
1186     }
1188     if ($cents != '0') {
1189         return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
1190     } else {
1191         return $hours . ":" . $mins . ":" . $secs;
1192     }
1195 function scorm_external_link($link) {
1196 // check if a link is external
1197     $result = false;
1198     $link = strtolower($link);
1199     if (substr($link,0,7) == 'http://') {
1200         $result = true;
1201     } else if (substr($link,0,8) == 'https://') {
1202         $result = true;
1203     } else if (substr($link,0,4) == 'www.') {
1204         $result = true;
1205     }
1206     return $result;
1209 function scorm_count_launchable($scormid,$organization) {
1210     return count_records_select('scorm_scoes',"scorm=$scormid AND organization='$organization' AND launch<>''");
1213 function scorm_get_toc($scorm,$liststyle,$currentorg='',$scoid='',$mode='normal',$play=false) {
1214     global $USER, $CFG;
1216     $strexpand = get_string('expcoll','scorm');
1218     $result->toc = "<ul id='0' class='$liststyle'>";
1219     $incomplete = false;
1220     $organizationsql = '';
1221     if (!empty($currentorg)) {
1222         $organizationsql = "AND organization='$currentorg'";
1223     }
1224     if ($scoes = get_records_select('scorm_scoes',"scorm='$scorm->id' $organizationsql order by id ASC")){
1225         $usertracks = array();
1226         foreach ($scoes as $sco) {
1227             if (!empty($sco->launch)) {
1228                 if ($usertrack=scorm_get_tracks($sco->id,$USER->id)) {
1229                     if ($usertrack->status == '') {
1230                         $usertrack->status = 'notattempted';
1231                     }
1232                     $usertracks[$sco->identifier] = $usertrack;
1233                 }
1234             }
1235         }
1237         $level=0;
1238         $sublist=1;
1239         $previd = 0;
1240         $nextid = 0;
1241         $parents[$level]='/';
1242         foreach ($scoes as $sco) {
1243             if ($parents[$level]!=$sco->parent) {
1244                 if ($newlevel = array_search($sco->parent,$parents)) {
1245                     for ($i=0; $i<($level-$newlevel); $i++) {
1246                         $result->toc .= "\t\t</ul></li>\n";
1247                     }
1248                     $level = $newlevel;
1249                 } else {
1250                     $i = $level;
1251                     $closelist = '';
1252                     while (($i > 0) && ($parents[$level] != $sco->parent)) {
1253                         $closelist .= "\t\t</ul></li>\n";
1254                         $i--;
1255                     }
1256                     if (($i == 0) && ($sco->parent != $currentorg)) {
1257                         $result->toc .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
1258                         $level++;
1259                     } else {
1260                         $result->toc .= $closelist;
1261                         $level = $i;
1262                     }
1263                     $parents[$level]=$sco->parent;
1264                 }
1265             }
1266             $result->toc .= "\t\t<li>";
1267             $nextsco = next($scoes);
1268             if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
1269                 $sublist++;
1270                 $result->toc .= '<a href="#" onClick="expandCollide(img'.$sublist.','.$sublist.');"><img id="img'.$sublist.'" src="'.$CFG->wwwroot.'/mod/scorm/pix/minus.gif" alt="'.$strexpand.'" title="'.$strexpand.'"/></a>';
1271             } else {
1272                 $result->toc .= '<img src="'.$CFG->wwwroot.'/mod/scorm/pix/spacer.gif" />';
1273             }
1274             if (empty($sco->title)) {
1275                 $sco->title = $sco->identifier;
1276             }
1277             if (!empty($sco->launch)) {
1278                 $startbold = '';
1279                 $endbold = '';
1280                 $score = '';
1281                 if (empty($scoid) && ($mode != 'normal')) {
1282                     $scoid = $sco->id;
1283                 }
1284                 if (isset($usertracks[$sco->identifier])) {
1285                     $usertrack = $usertracks[$sco->identifier];
1286                     $strstatus = get_string($usertrack->status,'scorm');
1287                     $result->toc .= "<img src='".$CFG->wwwroot."/mod/scorm/pix/".$usertrack->status.".gif' alt='$strstatus' title='$strstatus' />";
1288                     if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) {
1289                         $incomplete = true;
1290                         if ($play && empty($scoid)) {
1291                             $scoid = $sco->id;
1292                         }
1293                     }
1294                     if ($usertrack->score_raw != '') {
1295                         $score = '('.get_string('score','scorm').':&nbsp;'.$usertrack->score_raw.')';
1296                     }
1297                 } else {
1298                     if ($play && empty($scoid)) {
1299                         $scoid = $sco->id;
1300                     }
1301                     if ($sco->scormtype == 'sco') {
1302                         $result->toc .= '<img src="'.$CFG->wwwroot.'/mod/scorm/pix/notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
1303                         $incomplete = true;
1304                     } else {
1305                         $result->toc .= '<img src="'.$CFG->wwwroot.'/mod/scorm/pix/asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
1306                     }
1307                 }
1308                 if ($sco->id == $scoid) {
1309                     $startbold = '<b>';
1310                     $endbold = '</b>';
1311                     if ($nextsco !== false) {
1312                         $nextid = $nextsco->id;
1313                     } else {
1314                         $nextid = 0;
1315                     }
1316                     $shownext = $sco->next;
1317                     $showprev = $sco->previous;
1318                 }
1319                 if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1) && ( $nextsco!==false)) {
1320                     $previd = $sco->id;
1321                 }
1322                 if (empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites,$usertracks)) {
1323                     if ($sco->id == $scoid) {
1324                         $result->prerequisites = true;
1325                     }
1326                     $result->toc .= "&nbsp;$startbold<a href='javascript:playSCO(".$sco->id.");'>$sco->title</a> $score$endbold</li>\n";
1327                 } else {
1328                     if ($sco->id == $scoid) {
1329                         $result->prerequisites = false;
1330                     }
1331                     $result->toc .= "&nbsp;$sco->title</li>\n";
1332                 }
1333             } else {
1334                 $result->toc .= "&nbsp;$sco->title</li>\n";
1335             }
1336         }
1337         for ($i=0;$i<$level;$i++) {
1338             $result->toc .= "\t\t</ul></li>\n";
1339         }
1340     }
1341     $result->toc .= "\t</ul>\n";
1342     
1343     if ($play) {
1344         unset($sco);
1345         $sco->id = $scoid;
1346         $sco->prev = $previd;
1347         $sco->next = $nextid;
1348         $sco->showprev = $showprev;
1349         $sco->shownext = $shownext;
1350         $result->sco = $sco;
1351     } else {
1352         $result->incomplete = $incomplete;
1353     }
1354     return $result;
1356 ?>