a6888eababb4d7765caa600c7333e40c16f7c141
[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"));
14                              
15 $SCORM_WINDOW_OPTIONS = array('resizable', 'scrollbars', 'status', 'height', 'width');
17 if (!isset($CFG->scorm_popup)) {
18     set_config('scorm_popup', '');
19 }
20 if (!isset($CFG->scorm_validate)) {
21     $scorm_validate = 'none';
22     //I've commented this out for Moodle 1.4, as I've seen errors in 
23     //SCORM packages even though the actual package worked fine. -- Martin Dougiamas
24     if (extension_loaded('domxml') && version_compare(phpversion(),'5.0.0','<')) {
25         $scorm_validate = 'domxml';
26     }
27     if (version_compare(phpversion(),'5.0.0','>=')) {
28         $scorm_validate = 'php5';
29     }
30     set_config('scorm_validate', $scorm_validate);
31 }
33 foreach ($SCORM_WINDOW_OPTIONS as $popupoption) {
34     $popupoption = "scorm_popup$popupoption";
35     if (!isset($CFG->$popupoption)) {
36         if ($popupoption == 'scorm_popupheight') {
37             set_config($popupoption, 450);
38         } else if ($popupoption == 'scorm_popupwidth') {
39             set_config($popupoption, 620);
40         } else {
41             set_config($popupoption, 'checked');
42         }
43     }  
44 }
46 if (!isset($CFG->scorm_framesize)) {
47     set_config('scorm_framesize', 140);
48 }
50 function scorm_add_instance($scorm) {
51 /// Given an object containing all the necessary data, 
52 /// (defined by the form in mod.html) this function 
53 /// will create a new instance and return the id number 
54 /// of the new instance.
56     $scorm->timemodified = time();
58     # May have to add extra stuff in here #
59     global $CFG,$SCORM_WINDOW_OPTIONS;
60     
61     $scorm->popup = '';
63     $optionlist = array();
64     foreach ($SCORM_WINDOW_OPTIONS as $option) {
65         if (isset($scorm->$option)) {
66             $optionlist[] = $option.'='.$scorm->$option;
67         }
68     }
69     $scorm->popup = implode(',', $optionlist);
70     
72     if ($scorm->popup != '') {
73         $scorm->popup .= ',location=0,menubar=0,toolbar=0';
74         $scorm->auto = '0';
75     }
76     $id = insert_record('scorm', $scorm);
77     
78     //
79     // Parse scorm manifest
80     //
81     if ($scorm->launch == 0) {
82         $basedir = $CFG->dataroot."/".$scorm->course;
83         $scormdir = "/moddata/scorm";
84         $scorm->launch = scorm_parse($basedir,$scormdir.$scorm->datadir."/imsmanifest.xml",$id);
85         set_field("scorm","launch",$scorm->launch,"id",$id);
86     }
87     
88     return $id;
89 }
92 function scorm_update_instance($scorm) {
93 /// Given an object containing all the necessary data, 
94 /// (defined by the form in mod.html) this function 
95 /// will update an existing instance with new data.
96     
97     $scorm->timemodified = time();
98     $scorm->id = $scorm->instance;
100     # May have to add extra stuff in here #
101     global $CFG,$SCORM_WINDOW_OPTIONS;
102     
103     $scorm->popup = '';
104     
105     $optionlist = array();
106     foreach ($SCORM_WINDOW_OPTIONS as $option) {
107         if (isset($scorm->$option)) {
108             $optionlist[] = $option.'='.$scorm->$option;
109         }
110     }
111     $scorm->popup = implode(',', $optionlist);
113     if ($scorm->popup != '') {
114         $scorm->popup .= ',location=0,menubar=0,toolbar=0';
115         $scorm->auto = '0';
116     }
117     
118     $id = update_record('scorm', $scorm);
119     
120     //
121     // Check if scorm manifest needs to be reparsed
122     //
123     if ($scorm->launch == 0) {
124         $basedir = $CFG->dataroot."/".$scorm->course;
125         $scormdir = "/moddata/scorm";
126         $scorm->launch = scorm_parse($basedir,$scormdir.$scorm->datadir."/imsmanifest.xml",$id);
127         set_field("scorm","launch",$scorm->launch,"id",$id);
128     }
129     
130     return $id;
134 function scorm_delete_instance($id) {
135 /// Given an ID of an instance of this module, 
136 /// this function will permanently delete the instance 
137 /// and any data that depends on it.  
138     
139     require('../config.php');
141     if (! $scorm = get_record('scorm', 'id', $id)) {
142         return false;
143     }
145     $result = true;
147     # Delete any dependent files #
148     scorm_delete_files($CFG->dataroot.'/'.$scorm->course.'/moddata/scorm'.$scorm->datadir);
150     # Delete any dependent records here #
151     if (! delete_records('scorm_sco_users', 'scormid', $scorm->id)) {
152         $result = false;
153     }
154     if (! delete_records('scorm_scoes', 'scorm', $scorm->id)) {
155         $result = false;
156     }
157     if (! delete_records('scorm', 'id', $scorm->id)) {
158         $result = false;
159     }
160     
162     return $result;
165 function scorm_user_outline($course, $user, $mod, $scorm) {
166 /// Return a small object with summary information about what a 
167 /// user has done with a given particular instance of this module
168 /// Used for user activity reports.
169 /// $return->time = the time they did it
170 /// $return->info = a short text description
172     return $return;
175 function scorm_user_complete($course, $user, $mod, $scorm) {
176 /// Print a detailed representation of what a  user has done with 
177 /// a given particular instance of this module, for user activity reports.
179     return true;
182 function scorm_print_recent_activity(&$logs, $isteacher=false) {
183 /// Given a list of logs, assumed to be those since the last login 
184 /// this function prints a short list of changes related to this module
185 /// If isteacher is true then perhaps additional information is printed.
186 /// This function is called from course/lib.php: print_recent_activity()
188     global $CFG, $COURSE_TEACHER_COLOR;
190     $content = NULL;
192     return $content;  // True if anything was printed, otherwise false
195 function scorm_cron () {
196 /// Function to be run periodically according to the moodle cron
197 /// This function searches for things that need to be done, such 
198 /// as sending out mail, toggling flags etc ... 
200     global $CFG;
202     return true;
205 function scorm_grades($scormid) {
206 /// Must return an array of grades for a given instance of this module, 
207 /// indexed by user.  It also returns a maximum allowed grade.
209     global $CFG;
210     
211     if (!$scorm = get_record("scorm", "id", $scormid)) {
212         return NULL;
213     }
214     
215     if ($scorm->grademethod == VALUESCOES) {
216         if (!$return->maxgrade = count_records_select('scorm_scoes',"scorm='$scormid' AND launch<>''")) {
217             return NULL;
218         }
219     
220         $return->grades = NULL;
221         if ($sco_users=get_records_select('scorm_sco_users', "scormid='$scormid' GROUP BY userid")) {
222             foreach ($sco_users as $sco_user) {
223                 $user_data=get_records_select('scorm_sco_users',"scormid='$scormid' AND userid='$sco_user->userid'");
224                 $scores->completed=0;
225                 $scores->browsed=0;
226                 $scores->incomplete=0;
227                 $scores->failed=0;
228                 $scores->notattempted=0;
229                 $result='';
230                 $data = current($user_data);
231                 foreach ($user_data as $data) {
232                     if ($data->cmi_core_lesson_status=='passed')
233                         $scores->completed++;
234                     else
235                         $scores->{scorm_remove_spaces($data->cmi_core_lesson_status)}++;
236                 }
237                 if ($scores->completed)
238                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/completed.gif\" alt=\"".get_string('completed','scorm')."\" title=\"".get_string('completed','scorm')."\" /> $scores->completed ";
239                 if ($scores->incomplete)
240                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/incomplete.gif\" alt=\"".get_string('incomplete','scorm')."\" title=\"".get_string('incomplete','scorm')."\" /> $scores->incomplete ";
241                 if ($scores->failed)
242                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/failed.gif\" alt=\"".get_string('failed','scorm')."\" title=\"".get_string('failed','scorm')."\" /> $scores->failed ";
243                 if ($scores->browsed)
244                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/browsed.gif\" alt=\"".get_string('browsed','scorm')."\" title=\"".get_string('browsed','scorm')."\" /> $scores->browsed ";
245                 if ($scores->notattempted)
246                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/notattempted.gif\" alt=\"".get_string('notattempted','scorm')."\" title=\"".get_string('notattempted','scorm')."\" /> $scores->notattempted ";
247             
248                 $return->grades[$sco_user->userid]=$result;
249             }
250         
251         }
252     } else {
253         $grades = get_records_select("scorm_sco_users", "scormid=$scormid AND cmi_core_score_raw>0","","id,userid,cmi_core_score_raw");
254         //$grades = get_records_menu("scorm_sco_users", "scormid",$scormid,"","userid,cmi_core_score_raw");
255         $valutations = array();
256         foreach ($grades as $grade) {
257             if (!isset($valutations[$grade->userid])) {
258                 if ($scorm->grademethod == VALUEAVERAGE) {
259                     $values = array();
260                     $values[$grade->userid]->grade = 0;
261                     $values[$grade->userid]->values = 0;
262                 }
263                 $valutations[$grade->userid] = 0;
264             }
265             switch ($scorm->grademethod) {
266                 case VALUEHIGHEST:
267                     if ($grade->cmi_core_score_raw > $valutations[$grade->userid]) {
268                         $valutations[$grade->userid] = $grade->cmi_core_score_raw;
269                     }
270                 break;
271                 case VALUEAVERAGE:
272                     $values[$grade->userid]->grade += $grade->cmi_core_score_raw;
273                     $values[$grade->userid]->values++;
274                 break;
275                 case VALUESUM:
276                     $valutations[$grade->userid] += $grade->cmi_core_score_raw;
277                 break;
278             }
279         }
280         if ($scorm->grademethod == VALUEAVERAGE) {
281             foreach($values as $userid => $value) {
282                 $valutations[$userid] = $value->grade/$value->values;
283             }
284         }
285         //print_r($grades);
286         $return->grades = $valutations;
287         $return->maxgrade = $scorm->maxgrade;
288     }
289     return $return;
293 //////////////////////////////////////////////////////////////////////////////////////
294 /// Any other scorm functions go here.  Each of them must have a name that 
295 /// starts with scorm_
298 function scorm_randstring($len = '8')
300         $rstring = NULL;
301         $lchar = '';
302         for($i=0; $i<$len; $i++) {
303                 $char = chr(rand(48,122));
304                 while (!ereg('[a-zA-Z0-9]', $char)){
305                         if($char == $lchar) continue;
306                         $char = chr(rand(48,90));
307                 }
308                 $rstring .= $char;
309                 $lchar = $char;
310         }
311         return $rstring;
314  
315 function scorm_datadir($strPath, $existingdir='', $prefix = 'SCORM')
317     global $CFG;
319     if (($existingdir!='') && (is_dir($strPath.$existingdir)))
320         return $strPath.$existingdir;
321         
322     if (is_dir($strPath)) {
323         do {
324             $datadir='/'.$prefix.scorm_randstring();
325         } while (file_exists($strPath.$datadir));
326         mkdir($strPath.$datadir, $CFG->directorypermissions);
327         @chmod($strPath.$datadir, $CFG->directorypermissions);  // Just in case mkdir didn't do it
328         return $strPath.$datadir;
329     } else {
330         return false;
331     }
332
334 if ($CFG->scorm_validate == 'domxml') {
335     require_once('validatordomxml.php');
338 function scorm_validate($manifest)
340     global $CFG;
341     
342     global $item_idref_array;
343     global $idres_array;
344     global $def_org_array;
345     global $id_org_array;
346     
347     if (is_file ($manifest)) {
348         if (file_exists($manifest)) {
349             if ($CFG->scorm_validate == 'domxml') {
350                 $manifest_string = file_get_contents($manifest);
352                 /* Elimino i caratteri speciali di spaziatura e ritorno a capo dal file xml */
354                 $spec = array('\n', '\r', '\t', '\0', '\x0B');
355                 $content = str_replace($spec, '', $manifest_string);
357                 if ($xmldoc = domxml_open_mem($content)) {
358                     $root = $xmldoc->document_element();
359                     if (!testRoot($root)) {
360                         return 'syntax';
361                     }
362                     if (testNode($root)) {      
363                         // Nel corpo di questo if si controllano le corrispondenze fra gli attributi
364                         // Nello Standard SCORM ad ogni attributo idRef di <item> deve corrispondere
365                         // un attributo ID di <resource>
366                         // Gli array degli attributi sono stati dichiarati globali in validator.php
367                         // pertanto possono essere utilizzati direttamente all'interno di main.php
369                         foreach($item_idref_array as $elem_it) {  
370                             if (array_search($elem_it, $idres_array) === false) {
371                                 return 'mismatch';
372                             }
373                         }
374   
375                         foreach($def_org_array as $elem_def) {  
376                             if (array_search($elem_it, $id_org_array) === false) {
377                                 return 'mismatch';
378                             }
379                         }
380                    
381                     } else {
382                         return 'badmanifest';
383                     }
384                 }
385                 return 'regular';
386             } else {
387                 return 'found';
388             }
389         }
390     } else {
391         return 'nomanifest';
392     }
395 function scorm_delete_files($directory) {
396     if (is_dir($directory)) {
397         $files=scorm_scandir($directory);
398         //print_r($files);
399         foreach($files as $file) {
400             if (($file != '.') && ($file != '..')) {
401                 if (!is_dir($directory.'/'.$file)) {
402                     //chmod($directory.'/'.$file,0777);
403                     unlink($directory.'/'.$file);
404                 } else {
405                     scorm_delete_files($directory.'/'.$file);
406                 }
407             }
408         }
409         rmdir($directory);
410     }
413 function scorm_scandir($directory) {
414     if (version_compare(phpversion(),'5.0.0','>=')) {
415         return scandir($directory);
416     } else {
417         $files = null;
418         if ($dh = opendir($directory)) {
419             while (($file = readdir($dh)) !== false) {
420                 $files[] = $file;
421             }
422             closedir($dh);
423         }
424         return $files;
425     }
428 function scorm_startElement($parser, $name, $attrs) {
430     global $scoes,$i,$resources,$parent,$level,$organization,$manifest,$defaultorg;
432     if ($name == 'ITEM') {
433         $i++;
434         $scoes[$i]['manifest'] = $manifest;
435         $scoes[$i]['organization'] = $organization;
436         $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
437         if (empty($attrs['IDENTIFIERREF']))
438             $attrs['IDENTIFIERREF'] = '';
439         $scoes[$i]['identifierref'] = $attrs['IDENTIFIERREF'];
440         if (empty($attrs['ISVISIBLE']))
441             $attrs['ISVISIBLE'] = '';
442         $scoes[$i]['isvisible'] = $attrs['ISVISIBLE'];
443         $scoes[$i]['parent'] = $parent[$level];
444         $level++;
445         $parent[$level] = $attrs['IDENTIFIER'];
446     }
447     if ($name == 'RESOURCE') {
448         if (!isset($attrs['HREF'])) {
449             $attrs['HREF'] = '';
450         }
451         $resources[$attrs['IDENTIFIER']]['href']=$attrs['HREF'];
452         if (!isset($attrs['ADLCP:SCORMTYPE'])) {
453             $attrs['ADLCP:SCORMTYPE'] = '';
454         }
455         $resources[$attrs['IDENTIFIER']]['type']=$attrs['ADLCP:SCORMTYPE'];
456     }
457     if ($name == 'ORGANIZATION') {
458         $i++;
459         $scoes[$i]['manifest'] = $manifest;
460         $scoes[$i]['organization'] = '';
461         $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
462         $scoes[$i]['identifierref'] = '';
463         $scoes[$i]['isvisible'] = '';
464         $scoes[$i]['parent'] = $parent[$level];
465         $level++;
466         $parent[$level] = $attrs['IDENTIFIER'];
467         $organization = $attrs['IDENTIFIER'];
468     }
469     if ($name == 'MANIFEST') {
470         $manifest = $attrs['IDENTIFIER'];
471     }
472     if ($name == 'ORGANIZATIONS') {
473         if (!isset($attrs['DEFAULT'])) {
474             $attrs['DEFAULT'] = '';
475         }
476         $defaultorg = $attrs['DEFAULT'];
477     }
480 function scorm_endElement($parser, $name) {
481     global $scoes,$i,$level,$datacontent,$navigation;
482     if ($name == 'ITEM') {
483         $level--;
484     }
485     //if ($name == 'TITLE' && $level>0) {
486     if ($name == 'TITLE') {
487         $scoes[$i]['title'] = $datacontent;
488     }
489     if ($name == 'ADLCP:HIDERTSUI') {
490         $scoes[$i][$datacontent] = 1;
491     }
492     if ($name == 'ADLCP:DATAFROMLMS') {
493         $scoes[$i]['datafromlms'] = $datacontent;
494     }
495     if ($name == 'ORGANIZATION') {
496         $organization = '';
497         $level--;
498     }
499     if ($name == 'MANIFEST') {
500         $manifest = '';
501     }
504 function scorm_characterData($parser, $data) {
505     global $datacontent;
506     $datacontent = utf8_decode($data);
509 function scorm_parse($basedir,$file,$scorm_id) {
510     global $scoes,$i,$resources,$parent,$level,$defaultorg;
511     $datacontent = '';
512     $scoes[][] = '';
513     $resources[] = '';
514     $organization = '';
515     $defaultorg = '';
516     $i = 0;
517     $level = 0;
518     $parent[$level] = '/';
520     $xml_parser = xml_parser_create('UTF-8');
521     // use case-folding so we are sure to find the tag in $map_array
522     xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
523     xml_set_element_handler($xml_parser, 'scorm_startElement', 'scorm_endElement');
524     xml_set_character_data_handler($xml_parser, 'scorm_characterData');
525     if (!($fp = fopen($basedir.$file, 'r'))) {
526        die('could not open XML input');
527     }
529     while ($data = fread($fp, 4096)) {
530         if (!xml_parse($xml_parser, $data, feof($fp))) {
531             die(sprintf('XML error: %s at line %d',
532                     xml_error_string(xml_get_error_code($xml_parser)),
533                     xml_get_current_line_number($xml_parser)));
534         }
535     }
536     xml_parser_free($xml_parser);
537     $launch = 0;
539     $sco->scorm = $scorm_id;
540     delete_records('scorm_scoes','scorm',$scorm_id);
541     delete_records('scorm_sco_users','scormid',$scorm_id);
542     
543     if (isset($scoes[1])) {
544         for ($j=1; $j<=$i; $j++) {
545             $sco->identifier = $scoes[$j]['identifier'];
546             $sco->parent = $scoes[$j]['parent'];
547             $sco->title = $scoes[$j]['title'];
548             $sco->organization = $scoes[$j]['organization'];
549             if (!isset($scoes[$j]['datafromlms'])) {
550                 $scoes[$j]['datafromlms'] = '';
551             } 
552             $sco->datafromlms = $scoes[$j]['datafromlms'];
553         
554             if (!isset($resources[($scoes[$j]['identifierref'])]['href'])) {
555                 $resources[($scoes[$j]['identifierref'])]['href'] = '';
556             }
557             $sco->launch = $resources[($scoes[$j]['identifierref'])]['href'];
558         
559             if (!isset($resources[($scoes[$j]['identifierref'])]['type'])) {
560                 $resources[($scoes[$j]['identifierref'])]['type'] = '';
561             }
562             $sco->type = $resources[($scoes[$j]['identifierref'])]['type'];
563         
564             if (!isset($scoes[$j]['previous'])) {
565                 $scoes[$j]['previous'] = 0;
566             }
567             $sco->previous = $scoes[$j]['previous'];
568         
569             if (!isset($scoes[$j]['continue'])) {
570                 $scoes[$j]['continue'] = 0;
571             }
572             $sco->next = $scoes[$j]['continue'];
573         
574             if (scorm_remove_spaces($scoes[$j]['isvisible']) != 'false') {
575                 $id = insert_record('scorm_scoes',$sco);
576             }
577             //if (($launch==0) && (isset($sco->launch)) && ($defaultorg==$sco->organization)) {
578             if (($launch==0) && ($defaultorg==$sco->identifier)) {
579                 $launch = $id;
580             }
581         }
582     } else {
583         foreach ($resources as $label => $resource) {
584             if (!empty($resource['href'])) {
585                 $sco->identifier = $label;
586                 $sco->title = $label;
587                 $sco->parent = '/';
588                 $sco->launch = $resource['href'];
589                 $sco->type = $resource['type'];
590                 $id = insert_record('scorm_scoes',$sco);
591                 
592                 if ($launch == 0) {
593                     $launch = $id;
594                 }
595             }
596         }
597     }
598     return $launch;
601 function scorm_get_scoes_records($sco_user) {
602 /// Gets all info required to display the table of scorm results
603 /// for report.php
604     global $CFG;
606     return get_records_sql("SELECT su.*, u.firstname, u.lastname, u.picture 
607                             FROM {$CFG->prefix}scorm_sco_users su, 
608                                  {$CFG->prefix}user u
609                             WHERE su.scormid = '$sco_user->scormid'
610                               AND su.userid = u.id
611                               AND su.userid = '$sco_user->userid'
612                               ORDER BY scoid");
615 function scorm_remove_spaces($sourcestr) {
616 // Remove blank space from a string
617     $newstr='';
618     for( $i=0; $i<strlen($sourcestr); $i++) {
619         if ($sourcestr[$i]!=' ')
620             $newstr .=$sourcestr[$i];
621     }
622     return $newstr;
625 function scorm_string_round($stringa) {
626 // Crop a string to $len character and set an anchor title to the full string
627     $len=11;
628     if ( strlen($stringa)>$len ) {
629     return "<a name=\"none\" title=\"$stringa\">".substr($stringa,0,$len-4).'...'.substr($stringa,strlen($stringa)-1,1).'</a>';
630     } else
631     return $stringa;
634 function scorm_external_link($link) {
635 // check if a link is external
636     $result = false;
637     $link = strtolower($link);
638     if (substr($link,0,7) == 'http://')
639         $result = true;
640     else if (substr($link,0,8) == 'https://')
641         $result = true;
642     else if (substr($link,0,4) == 'www.')
643         $result = true;
644     /*else if (substr($link,0,7) == 'rstp://')
645         $result = true;
646     else if (substr($link,0,6) == 'rtp://')
647         $result = true;
648     else if (substr($link,0,6) == 'ftp://')
649         $result = true;
650     else if (substr($link,0,9) == 'gopher://')
651         $result = true; */
652     return $result;
653 }    
654 ?>