Added user activity support
[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 define('VALUESCOES', '0');
7 define('VALUEHIGHEST', '1');
8 define('VALUEAVERAGE', '2');
9 define('VALUESUM', '3');
10 $SCORM_GRADE_METHOD = array (VALUESCOES => get_string('gradescoes', 'scorm'),
11                              VALUEHIGHEST => get_string('gradehighest', 'scorm'),
12                              VALUEAVERAGE => get_string('gradeaverage', 'scorm'),
13                              VALUESUM => get_string('gradesum', 'scorm'));
15 //if (!isset($CFG->scorm_validate)) {
16 //    $scormvalidate = 'none';
17 //    if (extension_loaded('domxml') && version_compare(phpversion(),'5.0.0','<')) {
18 //        $scormvalidate = 'domxml';
19 //    }
20 //    if (version_compare(phpversion(),'5.0.0','>=')) {
21 //        $scormvalidate = 'php5';
22 //    }
23 //    set_config('scorm_validate', $scormvalidate);
24 //}
26 if (!isset($CFG->scorm_frameheight)) {
27     set_config('scorm_frameheight','600');
28 }
30 if (!isset($CFG->scorm_framewidth)) {
31     set_config('scorm_framewidth','800');
32 }
34 function scorm_add_instance($scorm) {
35 /// Given an object containing all the necessary data,
36 /// (defined by the form in mod.html) this function
37 /// will create a new instance and return the id number
38 /// of the new instance.
40     $scorm->timemodified = time();
42     # May have to add extra stuff in here #
43     global $CFG;
45     $id = insert_record('scorm', $scorm);
47     //
48     // Rename temp scorm dir to scorm id
49     //
51     $scormdir = $CFG->dataroot.'/'.$scorm->course.'/moddata/scorm';
52     rename($scormdir.$scorm->datadir,$scormdir.'/'.$id);
53     //
54     // Parse scorm manifest
55     //
56     if ($scorm->launch == 0) {
57         $scorm->launch = scorm_parse($scormdir.'/'.$id,$scorm->pkgtype,$id);
58         set_field('scorm','launch',$scorm->launch,'id',$id);
59     }
61     return $id;
62 }
65 function scorm_update_instance($scorm) {
66 /// Given an object containing all the necessary data,
67 /// (defined by the form in mod.html) this function
68 /// will update an existing instance with new data.
70     $scorm->timemodified = time();
71     $scorm->id = $scorm->instance;
73     # May have to add extra stuff in here #
74     global $CFG;
77     //
78     // Check if scorm manifest needs to be reparsed
79     //
80     if ($scorm->launch == 0) {
81         $scormdir = $CFG->dataroot.'/'.$scorm->course.'/moddata/scorm';
82         if (isset($scorm->datadir) && ($scorm->datadir != $scorm->id)) {
83             scorm_delete_files($scormdir.'/'.$scorm->id);
84             rename($scormdir.$scorm->datadir,$scormdir.'/'.$scorm->id);
85         }
86         $scorm->launch = scorm_parse($scormdir.'/'.$scorm->id,$scorm->pkgtype,$scorm->id);
87     }
89     return update_record('scorm', $scorm);
90 }
93 function scorm_delete_instance($id) {
94 /// Given an ID of an instance of this module,
95 /// this function will permanently delete the instance
96 /// and any data that depends on it.
98     require('../config.php');
100     if (! $scorm = get_record('scorm', 'id', $id)) {
101         return false;
102     }
104     $result = true;
106     # Delete any dependent files #
107     scorm_delete_files($CFG->dataroot.'/'.$scorm->course.'/moddata/scorm/'.$scorm->id);
109     # Delete any dependent records here #
110     if (! delete_records('scorm_scoes_track', 'scormid', $scorm->id)) {
111         $result = false;
112     }
113     if (! delete_records('scorm_scoes', 'scorm', $scorm->id)) {
114         $result = false;
115     }
116     if (! delete_records('scorm', 'id', $scorm->id)) {
117         $result = false;
118     }
121     return $result;
124 function scorm_user_outline($course, $user, $mod, $scorm) {
125 /// Return a small object with summary information about what a
126 /// user has done with a given particular instance of this module
127 /// Used for user activity reports.
129     $return = NULL;
130     $scores->values = 0;
131     $scores->sum = 0;
132     $scores->max = 0;
133     $scores->lastmodify = 0;
134     $scores->count = 0;
135     if ($scoes = get_records_select("scorm_scoes","scorm='$scorm->id' ORDER BY id")) {
136         foreach ($scoes as $sco) {
137             if ($sco->launch!='') {
138                 $scores->count++;
139                 $userdata = scorm_get_tracks($sco->id, $user->id);
140                 if (!isset($scores->{$userdata->status})) {
141                     $scores->{$userdata->status} = 1;
142                 } else {    
143                     $scores->{$userdata->status}++;
144                 }
145                 if (!empty($userdata->score_raw)) {
146                     $scores->values++;
147                     $scores->sum += $userdata->score_raw;
148                     $scores->max = ($userdata->score_raw > $scores->max)?$userdata->score_raw:$scores->max;
149                 }
150                 if (isset($userdata->timemodified) && ($userdata->timemodified > $scores->lastmodify)) {
151                     $scores->lastmodify = $userdata->timemodified;
152                 }
153             }
154         }
155         switch ($scorm->grademethod) {
156             case VALUEHIGHEST:
157                 if ($scores->values > 0) {
158                     $return->info = get_string('score','scorm').':&nbsp;'.$scores->max;
159                     $return->time = $scores->lastmodify;
160                 }
161             break;
162             case VALUEAVERAGE:
163                 if ($scores->values > 0) {
164                     $return->info = get_string('score','scorm').':&nbsp;'.$scores->sum/$scores->values;
165                     $return->time = $scores->lastmodify;
166                 }
167             break;
168             case VALUESUM:
169                 if ($scores->values > 0) {
170                     $return->info = get_string('score','scorm').':&nbsp;'.$scores->sum;
171                     $return->time = $scores->lastmodify;
172                 }
173             break;
174             case VALUESCOES:
175                 $return->info = '';
176                 $scores->notattempted = $scores->count;
177                 if (isset($scores->completed)) {
178                     $return->info .= get_string('completed','scorm').':&nbsp;'.$scores->completed.'<br />';
179                     $scores->notattempted -= $scores->completed;
180                 }
181                 if (isset($scores->passed)) {
182                     $return->info .= get_string('passed','scorm').':&nbsp;'.$scores->passed.'<br />';
183                     $scores->notattempted -= $scores->passed;
184                 }
185                 if (isset($scores->failed)) {
186                     $return->info .= get_string('failed','scorm').':&nbsp;'.$scores->failed.'<br />';
187                     $scores->notattempted -= $scores->failed;
188                 }
189                 if (isset($scores->incomplete)) {
190                     $return->info .= get_string('incomplete','scorm').':&nbsp;'.$scores->incomplete.'<br />';
191                     $scores->notattempted -= $scores->incomplete;
192                 }
193                 if (isset($scores->browsed)) {
194                     $return->info .= get_string('browsed','scorm').':&nbsp;'.$scores->browsed.'<br />';
195                     $scores->notattempted -= $scores->browsed;
196                 }
197                 $return->time = $scores->lastmodify;
198                 if ($return->info == '') {
199                     $return = NULL;
200                 } else {
201                     $return->info .= get_string('notattempted','scorm').':&nbsp;'.$scores->notattempted.'<br />';
202                 }
203             break;
204         }
205     }
206     return $return;
209 function scorm_user_complete($course, $user, $mod, $scorm) {
210 /// Print a detailed representation of what a  user has done with
211 /// a given particular instance of this module, for user activity reports.
212     global $CFG;
214     $liststyle = 'structlist';
215     $scormpixdir = $CFG->wwwroot.'/mod/scorm/pix/';
217     echo "<ul id='0' class='$liststyle'>";
218     $currentorg = '';
219     $organizationsql = '';
220     if (!empty($currentorg)) {
221         $organizationsql = "AND organization='$currentorg'";
222     }
223     if ($scoes = get_records_select('scorm_scoes',"scorm='$scorm->id' $organizationsql order by id ASC")){
224         $level=0;
225         $sublist=1;
226         $parents[$level]='/';
227         foreach ($scoes as $sco) {
228             if ($parents[$level]!=$sco->parent) {
229                 if ($level>0 && $parents[$level-1]==$sco->parent) {
230                     echo "\t\t</ul></li>\n";
231                     $level--;
232                 } else {
233                     $i = $level;
234                     $closelist = '';
235                     while (($i > 0) && ($parents[$level] != $sco->parent)) {
236                         $closelist .= "\t\t</ul></li>\n";
237                         $i--;
238                     }
239                     if (($i == 0) && ($sco->parent != $currentorg)) {
240                         echo "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
241                         $level++;
242                     } else {
243                         echo $closelist;
244                         $level = $i;
245                     }
246                     $parents[$level]=$sco->parent;
247                 }
248             }
249             echo "\t\t<li>";
250             $nextsco = next($scoes);
251             if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
252                 $sublist++;
253             } else {
254                 echo '<img src="'.$scormpixdir.'spacer.gif" />';
255             }
257             if ($sco->launch) {
258                 $score = '';
259                 $totaltime = '';
260                 if ($usertrack=scorm_get_tracks($sco->id,$user->id)) {
261                     if ($usertrack->status == '') {
262                         $usertrack->status = 'notattempted';
263                     }
264                     $strstatus = get_string($usertrack->status,'scorm');
265                     echo "<img src='".$scormpixdir.$usertrack->status.".gif' alt='$strstatus' title='$strstatus' />";
266                     if ($usertrack->score_raw != '') {
267                         $score = get_string('score','scorm').':&nbsp;'.$usertrack->score_raw;
268                     }
269                     if ($usertrack->total_time != '00:00:00') {
270                         $totaltime = get_string('totaltime','scorm').':&nbsp;'.$usertrack->total_time;
271                         if (!empty($score)) {
272                             $totaltime = ' - '.$totaltime;
273                         }
274                     }
275                 } else {
276                     if ($sco->scormtype == 'sco') {
277                         echo '<img src="'.$scormpixdir.'notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
278                     } else {
279                         echo '<img src="'.$scormpixdir.'asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
280                     }
281                 }
282                 echo "&nbsp;$sco->title</li>\n";
283                 if ($usertrack !== false) {
284                     echo "\t\t\t<li><ul class='$liststyle'>\n";
285                     foreach($usertrack as $element => $value) {
286                         if (substr($element,0,3) == 'cmi') {
287                             echo '<li>'.$element.' => '.$value.'</li>';
288                         }
289                     }
290                     echo "\t\t\t</ul></li>\n";
291                 } 
292             } else {
293                 echo "&nbsp;$sco->title</li>\n";
294             }
295         }
296         for ($i=0;$i<$level;$i++) {
297             echo "\t\t</ul></li>\n";
298         }
299     }
300     echo "\t</ul>\n";
302     return true;
305 function scorm_print_recent_activity(&$logs, $isteacher=false) {
306 /// Given a list of logs, assumed to be those since the last login
307 /// this function prints a short list of changes related to this module
308 /// If isteacher is true then perhaps additional information is printed.
309 /// This function is called from course/lib.php: print_recent_activity()
311     return false;  // True if anything was printed, otherwise false
314 function scorm_cron () {
315 /// Function to be run periodically according to the moodle cron
316 /// This function searches for things that need to be done, such
317 /// as sending out mail, toggling flags etc ...
319     global $CFG;
321     return true;
324 function scorm_grades($scormid) {
325 /// Must return an array of grades for a given instance of this module,
326 /// indexed by user.  It also returns a maximum allowed grade.
328     global $CFG;
330     if (!$scorm = get_record('scorm', 'id', $scormid)) {
331         return NULL;
332     }
333     if (!$scoes = get_records('scorm_scoes','scorm',$scormid)) {
334         return NULL;
335     }
337     if ($scorm->grademethod == VALUESCOES) {
338         if (!$return->maxgrade = count_records_select('scorm_scoes',"scorm='$scormid' AND launch<>''")) {
339             return NULL;
340         }
341     } else {
342         $return->maxgrade = $scorm->maxgrade;
343     }
345     $return->grades = NULL;
346     if ($scousers=get_records_select('scorm_scoes_track', "scormid='$scormid' GROUP BY userid", "", "userid,null")) {
347         foreach ($scousers as $scouser) {
348             $scores = NULL;
349             $scores->scoes = 0;
350             $scores->values = 0;
351             $scores->max = 0;
352             $scores->sum = 0;
354             foreach ($scoes as $sco) {
355                 $userdata=scorm_get_tracks($sco->id, $scouser->userid);
356                 if (($userdata->status == 'completed') || ($userdata->status == 'passed')) {
357                     $scores->scoes++;
358                 }
359                 if (!empty($userdata->score_raw)) {
360                     $scores->values++;
361                     $scores->sum += $userdata->score_raw;
362                     $scores->max = ($userdata->score_raw > $scores->max)?$userdata->score_raw:$scores->max;
363                 }
364             }
365             switch ($scorm->grademethod) {
366                 case VALUEHIGHEST:
367                     $return->grades[$scouser->userid] = $scores->max;
368                 break;
369                 case VALUEAVERAGE:
370                     if ($scores->values > 0) {
371                         $return->grades[$scouser->userid] = $scores->sum/$scores->values;
372                     } else {
373                         $return->grades[$scouser->userid] = 0;
374                     }
375                 break;
376                 case VALUESUM:
377                     $return->grades[$scouser->userid] = $scores->sum;
378                 break;
379                 case VALUESCOES:
380                     $return->grades[$scouser->userid] = $scores->scoes;
381                 break;
382             }
383        }
384     }
385     return $return;
389 //////////////////////////////////////////////////////////////////////////////////////
390 /// Any other scorm functions go here.  Each of them must have a name that
391 /// starts with scorm_
394 function scorm_randstring($len = '8') {
395     $rstring = NULL;
396     $lchar = '';
397     for ($i=0; $i<$len; $i++) {
398         $char = chr(rand(48,122));
399         while (!ereg('[a-zA-Z0-9]', $char)){
400             if ($char == $lchar) continue;
401             $char = chr(rand(48,90));
402         }
403         $rstring .= $char;
404         $lchar = $char;
405     }
406     return $rstring;
410 function scorm_datadir($strPath, $existingdir='')
412     global $CFG;
414     if (($existingdir!='') && (is_dir($strPath.'/'.$existingdir)))
415         return $strPath.'/'.$existingdir;
417     if (is_dir($strPath)) {
418         do {
419             $datadir='/'.scorm_randstring();
420         } while (file_exists($strPath.$datadir));
421         mkdir($strPath.$datadir, $CFG->directorypermissions);
422         @chmod($strPath.$datadir, $CFG->directorypermissions);  // Just in case mkdir didn't do it
423         return $strPath.$datadir;
424     } else {
425         return false;
426     }
429 function scorm_validate($packagedir) {
430     if (is_file($packagedir.'/imsmanifest.xml')) {
431         $validation->result = 'found';
432         $validation->pkgtype = 'SCORM';
433     } else {
434         if ($handle = opendir($packagedir)) {
435             while (($file = readdir($handle)) !== false) {
436                 $ext = substr($file,strrpos($file,'.'));
437                 if (strtolower($ext) == '.cst') {
438                     $validation->result = 'found';
439                     $validation->pkgtype = 'AICC';
440                     break;
441                 }
442             }
443             closedir($handle);
444         }
445         if (!isset($validation)) {
446             $validation->result = 'nomanifest';
447             $validation->pkgtype = 'SCORM';
448         }
449     }
450     return $validation;
453 function scorm_delete_files($directory) {
454     if (is_dir($directory)) {
455         $files=scorm_scandir($directory);
456         foreach($files as $file) {
457             if (($file != '.') && ($file != '..')) {
458                 if (!is_dir($directory.'/'.$file)) {
459                     unlink($directory.'/'.$file);
460                 } else {
461                     scorm_delete_files($directory.'/'.$file);
462                 }
463             }
464         }
465         rmdir($directory);
466     }
469 function scorm_scandir($directory) {
470     if (version_compare(phpversion(),'5.0.0','>=')) {
471         return scandir($directory);
472     } else {
473         $files = null;
474         if ($dh = opendir($directory)) {
475             while (($file = readdir($dh)) !== false) {
476                $files[] = $file;
477             }
478             closedir($dh);
479         }
480         return $files;
481     }
484 function scorm_startElement($parser, $name, $attrs) {
486     global $scoes,$i,$resources,$parent,$level,$organization,$manifest,$defaultorg;
488     switch ($name) {
489         case 'ITEM':
490             $i++;
491             $scoes[$i]['manifest'] = $manifest;
492             $scoes[$i]['organization'] = $organization;
493             $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
494             if (empty($attrs['IDENTIFIERREF'])) {
495                 $attrs['IDENTIFIERREF'] = '';
496             }
497             $scoes[$i]['identifierref'] = $attrs['IDENTIFIERREF'];
498             if (empty($attrs['ISVISIBLE'])) {
499                 $attrs['ISVISIBLE'] = '';
500             }
501             $scoes[$i]['isvisible'] = $attrs['ISVISIBLE'];
502             if (empty($attrs['PARAMETERS'])) {
503                 $attrs['PARAMETERS'] = '';
504             }
505             $scoes[$i]['parameters'] = $attrs['PARAMETERS'];
506             $scoes[$i]['parent'] = $parent[$level];
507             $level++;
508             $parent[$level] = $attrs['IDENTIFIER'];
509         break;
510         case 'RESOURCE':
511             if (!isset($attrs['HREF'])) {
512                 $attrs['HREF'] = '';
513             }
514             $resources[$attrs['IDENTIFIER']]['href']=$attrs['HREF'];
515             if (!isset($attrs['ADLCP:SCORMTYPE'])) {
516                 $attrs['ADLCP:SCORMTYPE'] = 'sco'; //Default scorm type
517             }
518             $resources[$attrs['IDENTIFIER']]['scormtype']=$attrs['ADLCP:SCORMTYPE'];
519         break;
520         case 'ORGANIZATION':
521             $i++;
522             $scoes[$i]['manifest'] = $manifest;
523             $scoes[$i]['organization'] = '';
524             $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
525             $scoes[$i]['identifierref'] = '';
526             $scoes[$i]['isvisible'] = '';
527             $scoes[$i]['parent'] = $parent[$level];
528             $level++;
529             $parent[$level] = $attrs['IDENTIFIER'];
530             $organization = $attrs['IDENTIFIER'];
531         break;
532         case 'MANIFEST':
533             $manifest = $attrs['IDENTIFIER'];
534         break;
535         case 'ORGANIZATIONS':
536             if (!isset($attrs['DEFAULT'])) {
537                 $attrs['DEFAULT'] = '';
538             }
539             $defaultorg = $attrs['DEFAULT'];
540         break;
541     }
544 function scorm_endElement($parser, $name) {
545     global $scoes,$i,$level,$datacontent,$manifest,$organization,$version;
546     if ($name == 'ITEM') {
547         $level--;
548     }
549     //if ($name == 'TITLE' && $level>0) {
550     if ($name == 'TITLE') {
551         $scoes[$i]['title'] = $datacontent;
552     }
553     if ($name == 'ADLCP:HIDERTSUI') {
554         $scoes[$i][$datacontent] = 1;
555     }
556     if ($name == 'ADLCP:DATAFROMLMS') {
557         $scoes[$i]['datafromlms'] = $datacontent;
558     }
559     if ($name == 'ADLCP:PREREQUISITES') {
560         $scoes[$i]['prerequisites'] = $datacontent;
561     }
562     if ($name == 'ADLCP:MAXTIMEALLOWED') {
563         $scoes[$i]['maxtimeallowed'] = $datacontent;
564     }
565     if ($name == 'ADLCP:TIMELIMITACTION') {
566         $scoes[$i]['timelimitaction'] = $datacontent;
567     }
568     if ($name == 'ADLCP:MASTERYSCORE') {
569         $scoes[$i]['masteryscore'] = $datacontent;
570     }
571     if ($name == 'ORGANIZATION') {
572         $organization = '';
573         $level--;
574     }
575     if ($name == 'MANIFEST') {
576         $manifest = '';
577     }
578     if ($name == 'SCHEMAVERSION') {
579         if (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/",$datacontent,$matches)) {
580             $version = 'SCORM_'.$matches[count($matches)-1];
581         } else {
582             $version = 'SCORM_1.2';
583         }
584     }
587 function scorm_characterData($parser, $data) {
588     global $datacontent;
589     $datacontent = utf8_decode($data);
592 function scorm_parse($pkgdir,$pkgtype,$scormid){
593     if ($pkgtype == 'AICC') {
594         return scorm_parse_aicc($pkgdir,$scormid);
595     } else {
596         return scorm_parse_scorm($pkgdir,'/imsmanifest.xml',$scormid);
597     }
600 function scorm_get_aicc_columns($row,$mastername='system_id') {
601     $tok = strtok(strtolower($row),"\",\n\r");
602     $result->columns = array();
603     $i=0;
604     while ($tok) {
605         if ($tok !='') {
606             $result->columns[] = $tok;
607             if ($tok == $mastername) {
608                 $result->mastercol = $i;
609             }
610             $i++;
611         }
612         $tok = strtok("\",\n\r");
613     }
614     return $result;
617 function scorm_forge_cols_regexp($columns,$remodule='(".*")?,') {
618     $regexp = '/^';
619     foreach ($columns as $column) {
620         $regexp .= $remodule;
621     }
622     $regexp = substr($regexp,0,-1) . '/';
623     return $regexp;
626 function scorm_parse_aicc($pkgdir,$scormid){
627     $version = 'AICC';
628     $ids = array();
629     $courses = array();
630     if ($handle = opendir($pkgdir)) {
631         while (($file = readdir($handle)) !== false) {
632             $ext = substr($file,strrpos($file,'.'));
633             $extension = strtolower(substr($ext,1));
634             $id = strtolower(basename($file,$ext));
635             $ids[$id]->$extension = $file;
636         }
637         closedir($handle);
638     }
639     foreach ($ids as $courseid => $id) {
640         if (isset($id->crs)) {
641             if (is_file($pkgdir.'/'.$id->crs)) {
642                 $rows = file($pkgdir.'/'.$id->crs);
643                 foreach ($rows as $row) {
644                     if (preg_match("/^(.+)=(.+)$/",$row,$matches)) {
645                         switch (strtolower(trim($matches[1]))) {
646                             case 'course_id':
647                                 $courses[$courseid]->id = trim($matches[2]);
648                             break;
649                             case 'course_title':
650                                 $courses[$courseid]->title = trim($matches[2]);
651                             break;
652                             case 'version':
653                                 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
654                             break;
655                         }
656                     }
657                 }
658             }
659         }
660         if (isset($id->des)) {
661             $rows = file($pkgdir.'/'.$id->des);
662             $columns = scorm_get_aicc_columns($rows[0]);
663             $regexp = scorm_forge_cols_regexp($columns->columns);
664             for ($i=1;$i<count($rows);$i++) {
665                 if (preg_match($regexp,$rows[$i],$matches)) {
666                     for ($j=0;$j<count($columns->columns);$j++) {
667                         $column = $columns->columns[$j];
668                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
669                     }
670                 }
671             }
672         }
673         if (isset($id->au)) {
674             $rows = file($pkgdir.'/'.$id->au);
675             $columns = scorm_get_aicc_columns($rows[0]);
676             $regexp = scorm_forge_cols_regexp($columns->columns);
677             for ($i=1;$i<count($rows);$i++) {
678                 if (preg_match($regexp,$rows[$i],$matches)) {
679                     for ($j=0;$j<count($columns->columns);$j++) {
680                         $column = $columns->columns[$j];
681                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
682                     }
683                 }
684             }
685         }
686         if (isset($id->cst)) {
687             $rows = file($pkgdir.'/'.$id->cst);
688             $columns = scorm_get_aicc_columns($rows[0],'block');
689             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
690             for ($i=1;$i<count($rows);$i++) {
691                 if (preg_match($regexp,$rows[$i],$matches)) {
692                     for ($j=0;$j<count($columns->columns);$j++) {
693                         if ($j != $columns->mastercol) {
694                             $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
695                         }
696                     }
697                 }
698             }
699         }
700         if (isset($id->ort)) {
701             $rows = file($pkgdir.'/'.$id->ort);
702         }
703         if (isset($id->pre)) {
704             $rows = file($pkgdir.'/'.$id->pre);
705             $columns = scorm_get_aicc_columns($rows[0],'structure_element');
706             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+),');
707             for ($i=1;$i<count($rows);$i++) {
708                 if (preg_match($regexp,$rows[$i],$matches)) {
709                     $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]),1,-1);
710                 }
711             }
712         }
713         if (isset($id->cmp)) {
714             $rows = file($pkgdir.'/'.$id->cmp);
715         }
716     }
717     //print_r($courses);
718     $launch = 0;
719     if (isset($courses)) {
720         foreach ($courses as $course) {
721             unset($sco);
722             $sco->identifier = $course->id;
723             $sco->scorm = $scormid;
724             $sco->organization = '';
725             $sco->title = $course->title;
726             $sco->parent = '/';
727             $sco->launch = '';
728             $sco->scormtype = '';
729             //print_r($sco);
730             $id = insert_record('scorm_scoes',$sco);
731             if ($launch == 0) {
732                 $launch = $id;
733             }
734             if (isset($course->elements)) {
735                 foreach($course->elements as $element) {
736                     unset($sco);
737                     $sco->identifier = $element->system_id;
738                     $sco->scorm = $scormid;
739                     $sco->organization = $course->id;
740                     $sco->title = $element->title;
741                     if (strtolower($element->parent) == 'root') {
742                         $sco->parent = '/';
743                     } else {
744                         $sco->parent = $element->parent;
745                     }
746                     if (isset($element->file_name)) {
747                         $sco->launch = $element->file_name;
748                         $sco->scormtype = 'sco';
749                     } else {
750                         $element->file_name = '';
751                         $sco->scormtype = '';
752                     }
753                     if (!isset($element->prerequisites)) {
754                         $element->prerequisites = '';
755                     }
756                     $sco->prerequisites = $element->prerequisites;
757                     if (!isset($element->max_time_allowed)) {
758                         $element->max_time_allowed = '';
759                     }
760                     $sco->maxtimeallowed = $element->max_time_allowed;
761                     if (!isset($element->time_limit_action)) {
762                         $element->time_limit_action = '';
763                     }
764                     $sco->timelimitaction = $element->time_limit_action;
765                     if (!isset($element->mastery_score)) {
766                         $element->mastery_score = '';
767                     }
768                     $sco->masteryscore = $element->mastery_score;
769                     $sco->previous = 0;
770                     $sco->next = 0;
771                     $id = insert_record('scorm_scoes',$sco);
772                     if ($launch==0) {
773                         $launch = $id;
774                     }
775                 }
776             }
777         }
778     }
779     set_field('scorm','version','AICC','id',$scormid);
780     return $launch;
783 function scorm_parse_scorm($pkgdir,$file,$scormid) {
784     global $scoes,$i,$resources,$parent,$level,$defaultorg,$version,$organization,$manifest;
785     $datacontent = '';
786     $scoes[][] = '';
787     $resources[] = '';
788     $manifest = '';
789     $version = 'SCORM';
790     $organization = '';
791     $defaultorg = '';
792     $i = 0;
793     $level = 0;
794     $parent[$level] = '/';
796     $xmlparser = xml_parser_create('UTF-8');
797     // use case-folding so we are sure to find the tag in $map_array
798     xml_parser_set_option($xmlparser, XML_OPTION_CASE_FOLDING, true);
799     xml_set_element_handler($xmlparser, 'scorm_startElement', 'scorm_endElement');
800     xml_set_character_data_handler($xmlparser, 'scorm_characterData');
801     if (!($fp = fopen($pkgdir.$file, 'r'))) {
802        die('could not open XML input');
803     }
805     while ($data = fread($fp, 4096)) {
806         if (!xml_parse($xmlparser, $data, feof($fp))) {
807             die(sprintf('XML error: %s at line %d',
808             xml_error_string(xml_get_error_code($xmlparser)),
809             xml_get_current_line_number($xmlparser)));
810         }
811     }
812     xml_parser_free($xmlparser);
813     $launch = 0;
815     $sco->scorm = $scormid;
816     delete_records('scorm_scoes','scorm',$scormid);
817     delete_records('scorm_scoes_track','scormid',$scormid);
819     if (isset($scoes[1])) {
820         for ($j=1; $j<=$i; $j++) {
821             $sco->identifier = $scoes[$j]['identifier'];
822             $sco->parent = $scoes[$j]['parent'];
823             $sco->title = $scoes[$j]['title'];
824             $sco->manifest = $scoes[$j]['manifest'];
825             $sco->organization = $scoes[$j]['organization'];
826             if (!isset($scoes[$j]['datafromlms'])) {
827                 $scoes[$j]['datafromlms'] = '';
828             }
829             $sco->datafromlms = $scoes[$j]['datafromlms'];
830             if (!isset($scoes[$j]['prerequisites'])) {
831                 $scoes[$j]['prerequisites'] = '';
832             }
833             $sco->prerequisites = $scoes[$j]['prerequisites'];
834             if (!isset($scoes[$j]['maxtimeallowed'])) {
835                 $scoes[$j]['maxtimeallowed'] = '';
836             }
837             $sco->maxtimeallowed = $scoes[$j]['maxtimeallowed'];
838             if (!isset($scoes[$j]['timelimitaction'])) {
839                 $scoes[$j]['timelimitaction'] = '';
840             }
841             $sco->timelimitaction = $scoes[$j]['timelimitaction'];
842             if (!isset($scoes[$j]['masteryscore'])) {
843                 $scoes[$j]['masteryscore'] = '';
844             }
845             $sco->masteryscore = $scoes[$j]['masteryscore'];
847             if (!isset($resources[($scoes[$j]['identifierref'])]['href'])) {
848                 $resources[($scoes[$j]['identifierref'])]['href'] = '';
849             }
850             $sco->launch = $resources[($scoes[$j]['identifierref'])]['href'];
852             if (!isset($scoes[$j]['parameters'])) {
853                 $scoes[$j]['parameters'] = '';
854             }
855             $sco->parameters = $scoes[$j]['parameters'];
857             if (!isset($resources[($scoes[$j]['identifierref'])]['scormtype'])) {
858                 $resources[($scoes[$j]['identifierref'])]['scormtype'] = '';
859             }
860             $sco->scormtype = $resources[($scoes[$j]['identifierref'])]['scormtype'];
862             if (!isset($scoes[$j]['previous'])) {
863                 $scoes[$j]['previous'] = 0;
864             }
865             $sco->previous = $scoes[$j]['previous'];
867             if (!isset($scoes[$j]['continue'])) {
868                 $scoes[$j]['continue'] = 0;
869             }
870             $sco->next = $scoes[$j]['continue'];
872             if (scorm_remove_spaces($scoes[$j]['isvisible']) != 'false') {
873                 $id = insert_record('scorm_scoes',$sco);
874             }
875             if (($launch==0) && ($defaultorg==$sco->identifier)) {
876                 $launch = $id;
877             }
878         }
879     } else {
880         foreach ($resources as $label => $resource) {
881             if (!empty($resource['href'])) {
882                 $sco->identifier = $label;
883                 $sco->title = $label;
884                 $sco->parent = '/';
885                 $sco->launch = $resource['href'];
886                 $sco->scormtype = $resource['type'];
887                 $id = insert_record('scorm_scoes',$sco);
889                 if ($launch == 0) {
890                     $launch = $id;
891                 }
892             }
893         }
894     }
895     set_field('scorm','version',$version,'id',$scormid);
896     return $launch;
899 function scorm_get_tracks($scoid,$userid) {
900 /// Gets all tracks of specified sco and user
901     global $CFG;
903     if ($tracks = get_records_select('scorm_scoes_track',"userid=$userid AND scoid=$scoid",'element ASC')) {
904         $usertrack->userid = $userid;
905         $usertrack->scoid = $scoid;
906         $usertrack->score_raw = '';
907         $usertrack->status = '';
908         $usertrack->total_time = '00:00:00';
909         $usertrack->session_time = '00:00:00';
910         $usertrack->timemodified = 0;
911         foreach ($tracks as $track) {
912             $element = $track->element;
913             $usertrack->{$element} = $track->value;
914             switch ($element) {
915                 case 'cmi.core.lesson_status':
916                 case 'cmi.completition_status':
917                     if ($track->value == 'not attempted') {
918                         $track->value = 'notattempted';
919                     }
920                     $usertrack->status = $track->value;
921                 break;
922                 case 'cmi.core.score.raw':
923                 case 'cmi.score.raw':
924                     $usertrack->score_raw = $track->value;
925                 break;
926                 case 'cmi.core.session_time':
927                 case 'cmi.session_time':
928                     $usertrack->session_time = $track->value;
929                 break;
930                 case 'cmi.core.total_time':
931                 case 'cmi.total_time':
932                     $usertrack->total_time = $track->value;
933                 break;
934             }
935             if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) {
936                 $usertrack->timemodified = $track->timemodified;
937             }
938         }
939         return $usertrack;
940     } else {
941         return false;
942     }
945 function scorm_get_user_data($userid) {
946 /// Gets user info required to display the table of scorm results
947 /// for report.php
949     return get_record('user','id',$userid,'','','','','firstname, lastname, picture');
952 function scorm_remove_spaces($sourcestr) {
953 // Remove blank space from a string
954     $newstr='';
955     for( $i=0; $i<strlen($sourcestr); $i++) {
956         if ($sourcestr[$i]!=' ') {
957             $newstr .=$sourcestr[$i];
958         }
959     }
960     return $newstr;
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     }
972 function scorm_external_link($link) {
973 // check if a link is external
974     $result = false;
975     $link = strtolower($link);
976     if (substr($link,0,7) == 'http://') {
977         $result = true;
978     } else if (substr($link,0,8) == 'https://') {
979         $result = true;
980     } else if (substr($link,0,4) == 'www.') {
981         $result = true;
982     }
983     return $result;
986 function scorm_count_launchable($scormid,$organization) {
987     return count_records_select('scorm_scoes',"scorm=$scormid AND organization='$organization' AND launch<>''");
990 function scorm_display_structure($scorm,$liststyle,$currentorg='',$scoid='',$mode='normal',$play=false) {
991     global $USER;
993     $strexpand = get_string('expcoll','scorm');
995     echo "<ul id='0' class='$liststyle'>";
996     $incomplete = false;
997     $organizationsql = '';
998     if (!empty($currentorg)) {
999         $organizationsql = "AND organization='$currentorg'";
1000     }
1001     if ($scoes = get_records_select('scorm_scoes',"scorm='$scorm->id' $organizationsql order by id ASC")){
1002         $level=0;
1003         $sublist=1;
1004         $previd = 0;
1005         $nextid = 0;
1006         $parents[$level]='/';
1007         foreach ($scoes as $sco) {
1008             if ($parents[$level]!=$sco->parent) {
1009                 if ($level>0 && $parents[$level-1]==$sco->parent) {
1010                     echo "\t\t</ul></li>\n";
1011                     $level--;
1012                 } else {
1013                     $i = $level;
1014                     $closelist = '';
1015                     while (($i > 0) && ($parents[$level] != $sco->parent)) {
1016                         $closelist .= "\t\t</ul></li>\n";
1017                         $i--;
1018                     }
1019                     if (($i == 0) && ($sco->parent != $currentorg)) {
1020                         echo "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
1021                         $level++;
1022                     } else {
1023                         echo $closelist;
1024                         $level = $i;
1025                     }
1026                     $parents[$level]=$sco->parent;
1027                 }
1028             }
1029             echo "\t\t<li>";
1030             $nextsco = next($scoes);
1031             if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
1032                 $sublist++;
1033                 echo '<a href="#" onClick="expandCollide(img'.$sublist.','.$sublist.');"><img id="img'.$sublist.'" src="pix/minus.gif" alt="'.$strexpand.'" title="'.$strexpand.'"/></a>';
1034             } else {
1035                 echo '<img src="pix/spacer.gif" />';
1036             }
1038             if ($sco->launch) {
1039                 $startbold = '';
1040                 $endbold = '';
1041                 $score = '';
1042                 if (empty($scoid) && ($mode != 'normal')) {
1043                     $scoid = $sco->id;
1044                 }
1045                 if ($usertrack=scorm_get_tracks($sco->id,$USER->id)) {
1046                     if ($usertrack->status == '') {
1047                         $usertrack->status = 'notattempted';
1048                     }
1049                     $strstatus = get_string($usertrack->status,'scorm');
1050                     echo "<img src='pix/".$usertrack->status.".gif' alt='$strstatus' title='$strstatus' />";
1051                     if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) {
1052                         $incomplete = true;
1053                         if ($play && empty($scoid)) {
1054                             $scoid = $sco->id;
1055                         }
1056                     }
1057                     if ($usertrack->score_raw != '') {
1058                         $score = '('.get_string('score','scorm').':&nbsp;'.$usertrack->score_raw.')';
1059                     }
1060                 } else {
1061                     if ($play && empty($scoid)) {
1062                         $scoid = $sco->id;
1063                     }
1064                     if ($sco->scormtype == 'sco') {
1065                         echo '<img src="pix/notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
1066                         $incomplete = true;
1067                     } else {
1068                         echo '<img src="pix/asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
1069                     }
1070                 }
1071                 if ($sco->id == $scoid) {
1072                     $startbold = '<b>';
1073                     $endbold = '</b>';
1074                     if ($nextsco !== false) {
1075                         $nextid = $nextsco->id;
1076                     } else {
1077                         $nextid = 0;
1078                     }
1079                     $shownext = $sco->next;
1080                     $showprev = $sco->previous;
1081                 }
1082                 if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1)) {
1083                     $previd = $sco->id;
1084                 }
1085                 echo "&nbsp;$startbold<a href='javascript:playSCO(".$sco->id.");'>$sco->title</a> $score$endbold</li>\n";
1086             } else {
1087                 echo "&nbsp;$sco->title</li>\n";
1088             }
1089         }
1090         for ($i=0;$i<$level;$i++) {
1091             echo "\t\t</ul></li>\n";
1092         }
1093     }
1094     echo "\t</ul>\n";
1095     if ($play) {
1096         $result->id = $scoid;
1097         $result->prev = $previd;
1098         $result->next = $nextid;
1099         $result->showprev = $showprev;
1100         $result->shownext = $shownext;
1101         return $result;
1102     } else {
1103         return $incomplete;
1104     }
1106 ?>