813efcfa26b0bc1f86880b2234118c668371eb48
[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 $SCORM_WINDOW_OPTIONS;
60     
61     $scorm->popup = '';
62     
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     
77     return insert_record('scorm', $scorm);
78 }
81 function scorm_update_instance($scorm) {
82 /// Given an object containing all the necessary data, 
83 /// (defined by the form in mod.html) this function 
84 /// will update an existing instance with new data.
85     
86     $scorm->timemodified = time();
87     $scorm->id = $scorm->instance;
89     # May have to add extra stuff in here #
90     global $SCORM_WINDOW_OPTIONS;
91     
92     $scorm->popup = '';
93     
94     $optionlist = array();
95     foreach ($SCORM_WINDOW_OPTIONS as $option) {
96         if (isset($scorm->$option)) {
97             $optionlist[] = $option.'='.$scorm->$option;
98         }
99     }
100     $scorm->popup = implode(',', $optionlist);
102     if ($scorm->popup != '') {
103         $scorm->popup .= ',location=0,menubar=0,toolbar=0';
104         $scorm->auto = '0';
105     }
106     return update_record('scorm', $scorm);
110 function scorm_delete_instance($id) {
111 /// Given an ID of an instance of this module, 
112 /// this function will permanently delete the instance 
113 /// and any data that depends on it.  
114     
115     global $CFG;
117     if (! $scorm = get_record('scorm', 'id', $id)) {
118         return false;
119     }
121     $result = true;
123     # Delete any dependent files #
124     scorm_delete_files($CFG->dataroot.'/'.$scorm->course.'/moddata/scorm'.$scorm->datadir);
126     # Delete any dependent records here #
127     if (! delete_records('scorm_sco_users', 'scormid', $scorm->id)) {
128         $result = false;
129     }
130     if (! delete_records('scorm_scoes', 'scorm', $scorm->id)) {
131         $result = false;
132     }
133     if (! delete_records('scorm', 'id', $scorm->id)) {
134         $result = false;
135     }
136     
138     return $result;
141 function scorm_user_outline($course, $user, $mod, $scorm) {
142 /// Return a small object with summary information about what a 
143 /// user has done with a given particular instance of this module
144 /// Used for user activity reports.
145 /// $return->time = the time they did it
146 /// $return->info = a short text description
148     $return = NULL;
150     return $return;
153 function scorm_user_complete($course, $user, $mod, $scorm) {
154 /// Print a detailed representation of what a  user has done with 
155 /// a given particular instance of this module, for user activity reports.
157     return true;
160 function scorm_print_recent_activity(&$logs, $isteacher=false) {
161 /// Given a list of logs, assumed to be those since the last login 
162 /// this function prints a short list of changes related to this module
163 /// If isteacher is true then perhaps additional information is printed.
164 /// This function is called from course/lib.php: print_recent_activity()
166     global $CFG, $COURSE_TEACHER_COLOR;
168     $content = NULL;
170     return $content;  // True if anything was printed, otherwise false
173 function scorm_cron () {
174 /// Function to be run periodically according to the moodle cron
175 /// This function searches for things that need to be done, such 
176 /// as sending out mail, toggling flags etc ... 
178     global $CFG;
180     return true;
183 function scorm_grades($scormid) {
184 /// Must return an array of grades for a given instance of this module, 
185 /// indexed by user.  It also returns a maximum allowed grade.
187     global $CFG;
188     
189     if (!$scorm = get_record("scorm", "id", $scormid)) {
190         return NULL;
191     }
192     
193     if ($scorm->grademethod == VALUESCOES) {
194         if (!$return->maxgrade = count_records_select('scorm_scoes',"scorm='$scormid' AND launch<>''")) {
195             return NULL;
196         }
197     
198         $return->grades = NULL;
199         if ($sco_users=get_records_select('scorm_sco_users', "scormid='$scormid' GROUP BY userid")) {
200             foreach ($sco_users as $sco_user) {
201                 $user_data=get_records_select('scorm_sco_users',"scormid='$scormid' AND userid='$sco_user->userid'");
202                 $scores->completed=0;
203                 $scores->browsed=0;
204                 $scores->incomplete=0;
205                 $scores->failed=0;
206                 $scores->notattempted=0;
207                 $result='';
208                 $data = current($user_data);
209                 foreach ($user_data as $data) {
210                     if ($data->cmi_core_lesson_status=='passed')
211                         $scores->completed++;
212                     else
213                         $scores->{scorm_remove_spaces($data->cmi_core_lesson_status)}++;
214                 }
215                 if ($scores->completed)
216                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/completed.gif\" alt=\"".get_string('completed','scorm')."\" title=\"".get_string('completed','scorm')."\" /> $scores->completed ";
217                 if ($scores->incomplete)
218                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/incomplete.gif\" alt=\"".get_string('incomplete','scorm')."\" title=\"".get_string('incomplete','scorm')."\" /> $scores->incomplete ";
219                 if ($scores->failed)
220                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/failed.gif\" alt=\"".get_string('failed','scorm')."\" title=\"".get_string('failed','scorm')."\" /> $scores->failed ";
221                 if ($scores->browsed)
222                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/browsed.gif\" alt=\"".get_string('browsed','scorm')."\" title=\"".get_string('browsed','scorm')."\" /> $scores->browsed ";
223                 if ($scores->notattempted)
224                     $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/notattempted.gif\" alt=\"".get_string('notattempted','scorm')."\" title=\"".get_string('notattempted','scorm')."\" /> $scores->notattempted ";
225             
226                 $return->grades[$sco_user->userid]=$result;
227             }
228         
229         }
230     } else {
231         $grades = get_records_select("scorm_sco_users", "scormid=$scormid AND cmi_core_score_raw>0","","id,userid,cmi_core_score_raw");
232         //$grades = get_records_menu("scorm_sco_users", "scormid",$scormid,"","userid,cmi_core_score_raw");
233         $valutations = array();
234         foreach ($grades as $grade) {
235             if (!isset($valutations[$grade->userid])) {
236                 if ($scorm->grademethod == VALUEAVERAGE) {
237                     $values = array();
238                     $values[$grade->userid]->grade = 0;
239                     $values[$grade->userid]->values = 0;
240                 }
241                 $valutations[$grade->userid] = 0;
242             }
243             switch ($scorm->grademethod) {
244                 case VALUEHIGHEST:
245                     if ($grade->cmi_core_score_raw > $valutations[$grade->userid]) {
246                         $valutations[$grade->userid] = $grade->cmi_core_score_raw;
247                     }
248                 break;
249                 case VALUEAVERAGE:
250                     $values[$grade->userid]->grade += $grade->cmi_core_score_raw;
251                     $values[$grade->userid]->values++;
252                 break;
253                 case VALUESUM:
254                     $valutations[$grade->userid] += $grade->cmi_core_score_raw;
255                 break;
256             }
257         }
258         if ($scorm->grademethod == VALUEAVERAGE) {
259             foreach($values as $userid => $value) {
260                 $valutations[$userid] = $value->grade/$value->values;
261             }
262         }
263         //print_r($grades);
264         $return->grades = $valutations;
265         $return->maxgrade = $scorm->maxgrade;
266     }
267     return $return;
271 //////////////////////////////////////////////////////////////////////////////////////
272 /// Any other scorm functions go here.  Each of them must have a name that 
273 /// starts with scorm_
276 function scorm_randstring($len = '8')
278         $rstring = NULL;
279         $lchar = '';
280         for($i=0; $i<$len; $i++) {
281                 $char = chr(rand(48,122));
282                 while (!ereg('[a-zA-Z0-9]', $char)){
283                         if($char == $lchar) continue;
284                         $char = chr(rand(48,90));
285                 }
286                 $rstring .= $char;
287                 $lchar = $char;
288         }
289         return $rstring;
292  
293 function scorm_datadir($strPath, $existingdir='', $prefix = 'SCORM')
295     global $CFG;
297     if (($existingdir!='') && (is_dir($strPath.$existingdir)))
298         return $strPath.$existingdir;
299         
300     if (is_dir($strPath)) {
301         do {
302             $datadir='/'.$prefix.scorm_randstring();
303         } while (file_exists($strPath.$datadir));
304         mkdir($strPath.$datadir, $CFG->directorypermissions);
305         @chmod($strPath.$datadir, $CFG->directorypermissions);  // Just in case mkdir didn't do it
306         return $strPath.$datadir;
307     } else {
308         return false;
309     }
310
312 if ($CFG->scorm_validate == 'domxml') {
313     require_once('validatordomxml.php');
316 function scorm_validate($manifest)
318     global $CFG;
319     
320     global $item_idref_array;
321     global $idres_array;
322     global $def_org_array;
323     global $id_org_array;
324     
325     if (is_file ($manifest)) {
326         if (file_exists($manifest)) {
327             if ($CFG->scorm_validate == 'domxml') {
328                 $manifest_string = file_get_contents($manifest);
330                 /* Elimino i caratteri speciali di spaziatura e ritorno a capo dal file xml */
332                 $spec = array('\n', '\r', '\t', '\0', '\x0B');
333                 $content = str_replace($spec, '', $manifest_string);
335                 if ($xmldoc = domxml_open_mem($content)) {
336                     $root = $xmldoc->document_element();
337                     if (!testRoot($root)) {
338                         return 'syntax';
339                     }
340                     if (testNode($root)) {      
341                         // Nel corpo di questo if si controllano le corrispondenze fra gli attributi
342                         // Nello Standard SCORM ad ogni attributo idRef di <item> deve corrispondere
343                         // un attributo ID di <resource>
344                         // Gli array degli attributi sono stati dichiarati globali in validator.php
345                         // pertanto possono essere utilizzati direttamente all'interno di main.php
347                         foreach($item_idref_array as $elem_it) {  
348                             if (array_search($elem_it, $idres_array) === false) {
349                                 return 'mismatch';
350                             }
351                         }
352   
353                         foreach($def_org_array as $elem_def) {  
354                             if (array_search($elem_it, $id_org_array) === false) {
355                                 return 'mismatch';
356                             }
357                         }
358                    
359                     } else {
360                         return 'badmanifest';
361                     }
362                 }
363                 return 'regular';
364             } else {
365                 return 'found';
366             }
367         }
368     } else {
369         return 'nomanifest';
370     }
373 function scorm_delete_files($directory) {
374     if (is_dir($directory)) {
375         $files=scorm_scandir($directory);
376         //print_r($files);
377         foreach($files as $file) {
378             if (($file != '.') && ($file != '..')) {
379                 if (!is_dir($directory.'/'.$file)) {
380                     //chmod($directory.'/'.$file,0777);
381                     unlink($directory.'/'.$file);
382                 } else {
383                     scorm_delete_files($directory.'/'.$file);
384                 }
385             }
386         }
387         rmdir($directory);
388     }
391 function scorm_scandir($directory) {
392     if (version_compare(phpversion(),'5.0.0','>=')) {
393         return scandir($directory);
394     } else {
395         $files = null;
396         if ($dh = opendir($directory)) {
397             while (($file = readdir($dh)) !== false) {
398                 $files[] = $file;
399             }
400             closedir($dh);
401         }
402         return $files;
403     }
406 function scorm_startElement($parser, $name, $attrs) {
408     global $scoes,$i,$resources,$parent,$level,$organization,$manifest,$defaultorg;
410     if ($name == 'ITEM') {
411         $i++;
412         $scoes[$i]['manifest'] = $manifest;
413         $scoes[$i]['organization'] = $organization;
414         $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
415         if (empty($attrs['IDENTIFIERREF']))
416             $attrs['IDENTIFIERREF'] = '';
417         $scoes[$i]['identifierref'] = $attrs['IDENTIFIERREF'];
418         if (empty($attrs['ISVISIBLE']))
419             $attrs['ISVISIBLE'] = '';
420         $scoes[$i]['isvisible'] = $attrs['ISVISIBLE'];
421         $scoes[$i]['parent'] = $parent[$level];
422         $level++;
423         $parent[$level] = $attrs['IDENTIFIER'];
424     }
425     if ($name == 'RESOURCE') {
426         if (!isset($attrs['HREF'])) {
427             $attrs['HREF'] = '';
428         }
429         $resources[$attrs['IDENTIFIER']]['href']=$attrs['HREF'];
430         if (!isset($attrs['ADLCP:SCORMTYPE'])) {
431             $attrs['ADLCP:SCORMTYPE'] = '';
432         }
433         $resources[$attrs['IDENTIFIER']]['type']=$attrs['ADLCP:SCORMTYPE'];
434     }
435     if ($name == 'ORGANIZATION') {
436         $i++;
437         $scoes[$i]['manifest'] = $manifest;
438         $scoes[$i]['organization'] = '';
439         $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
440         $scoes[$i]['identifierref'] = '';
441         $scoes[$i]['isvisible'] = '';
442         $scoes[$i]['parent'] = $parent[$level];
443         $level++;
444         $parent[$level] = $attrs['IDENTIFIER'];
445         $organization = $attrs['IDENTIFIER'];
446     }
447     if ($name == 'MANIFEST') {
448         $manifest = $attrs['IDENTIFIER'];
449     }
450     if ($name == 'ORGANIZATIONS') {
451         if (!isset($attrs['DEFAULT'])) {
452             $attrs['DEFAULT'] = '';
453         }
454         $defaultorg = $attrs['DEFAULT'];
455     }
458 function scorm_endElement($parser, $name) {
459     global $scoes,$i,$level,$datacontent,$navigation;
460     if ($name == 'ITEM') {
461         $level--;
462     }
463     //if ($name == 'TITLE' && $level>0) {
464     if ($name == 'TITLE') {
465         $scoes[$i]['title'] = $datacontent;
466     }
467     if ($name == 'ADLCP:HIDERTSUI') {
468         $scoes[$i][$datacontent] = 1;
469     }
470     if ($name == 'ADLCP:DATAFROMLMS') {
471         $scoes[$i]['datafromlms'] = $datacontent;
472     }
473     if ($name == 'ORGANIZATION') {
474         $organization = '';
475         $level--;
476     }
477     if ($name == 'MANIFEST') {
478         $manifest = '';
479     }
482 function scorm_characterData($parser, $data) {
483     global $datacontent;
484     $datacontent = utf8_decode($data);
487 function scorm_parse($basedir,$file,$scorm_id) {
488     global $scoes,$i,$resources,$parent,$level,$defaultorg;
489     $datacontent = '';
490     $scoes[][] = '';
491     $resources[] = '';
492     $organization = '';
493     $defaultorg = '';
494     $i = 0;
495     $level = 0;
496     $parent[$level] = '/';
498     $xml_parser = xml_parser_create('UTF-8');
499     // use case-folding so we are sure to find the tag in $map_array
500     xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
501     xml_set_element_handler($xml_parser, 'scorm_startElement', 'scorm_endElement');
502     xml_set_character_data_handler($xml_parser, 'scorm_characterData');
503     if (!($fp = fopen($basedir.$file, 'r'))) {
504        die('could not open XML input');
505     }
507     while ($data = fread($fp, 4096)) {
508         if (!xml_parse($xml_parser, $data, feof($fp))) {
509             die(sprintf('XML error: %s at line %d',
510                     xml_error_string(xml_get_error_code($xml_parser)),
511                     xml_get_current_line_number($xml_parser)));
512         }
513     }
514     xml_parser_free($xml_parser);
515     $launch = 0;
517     $sco->scorm = $scorm_id;
518     delete_records('scorm_scoes','scorm',$scorm_id);
519     delete_records('scorm_sco_users','scormid',$scorm_id);
520     
521     if (isset($scoes[1])) {
522         for ($j=1; $j<=$i; $j++) {
523             $sco->identifier = $scoes[$j]['identifier'];
524             $sco->parent = $scoes[$j]['parent'];
525             $sco->title = $scoes[$j]['title'];
526             $sco->organization = $scoes[$j]['organization'];
527             if (!isset($scoes[$j]['datafromlms'])) {
528                 $scoes[$j]['datafromlms'] = '';
529             } 
530             $sco->datafromlms = $scoes[$j]['datafromlms'];
531         
532             if (!isset($resources[($scoes[$j]['identifierref'])]['href'])) {
533                 $resources[($scoes[$j]['identifierref'])]['href'] = '';
534             }
535             $sco->launch = $resources[($scoes[$j]['identifierref'])]['href'];
536         
537             if (!isset($resources[($scoes[$j]['identifierref'])]['type'])) {
538                 $resources[($scoes[$j]['identifierref'])]['type'] = '';
539             }
540             $sco->type = $resources[($scoes[$j]['identifierref'])]['type'];
541         
542             if (!isset($scoes[$j]['previous'])) {
543                 $scoes[$j]['previous'] = 0;
544             }
545             $sco->previous = $scoes[$j]['previous'];
546         
547             if (!isset($scoes[$j]['continue'])) {
548                 $scoes[$j]['continue'] = 0;
549             }
550             $sco->next = $scoes[$j]['continue'];
551         
552             if (scorm_remove_spaces($scoes[$j]['isvisible']) != 'false') {
553                 $id = insert_record('scorm_scoes',$sco);
554             }
555             //if (($launch==0) && (isset($sco->launch)) && ($defaultorg==$sco->organization)) {
556             if (($launch==0) && ($defaultorg==$sco->identifier)) {
557                 $launch = $id;
558             }
559         }
560     } else {
561         foreach ($resources as $label => $resource) {
562             if (!empty($resource['href'])) {
563                 $sco->identifier = $label;
564                 $sco->title = $label;
565                 $sco->parent = '/';
566                 $sco->launch = $resource['href'];
567                 $sco->type = $resource['type'];
568                 $id = insert_record('scorm_scoes',$sco);
569                 
570                 if ($launch == 0) {
571                     $launch = $id;
572                 }
573             }
574         }
575     }
576     return $launch;
579 function scorm_get_scoes_records($sco_user) {
580 /// Gets all info required to display the table of scorm results
581 /// for report.php
582     global $CFG;
584     return get_records_sql("SELECT su.*, u.firstname, u.lastname, u.picture 
585                             FROM {$CFG->prefix}scorm_sco_users su, 
586                                  {$CFG->prefix}user u
587                             WHERE su.scormid = '$sco_user->scormid'
588                               AND su.userid = u.id
589                               AND su.userid = '$sco_user->userid'
590                               ORDER BY scoid");
593 function scorm_remove_spaces($sourcestr) {
594 // Remove blank space from a string
595     $newstr='';
596     for( $i=0; $i<strlen($sourcestr); $i++) {
597         if ($sourcestr[$i]!=' ')
598             $newstr .=$sourcestr[$i];
599     }
600     return $newstr;
603 function scorm_string_round($stringa) {
604 // Crop a string to $len character and set an anchor title to the full string
605     $len=11;
606     if ( strlen($stringa)>$len ) {
607     return "<a name=\"none\" title=\"$stringa\">".substr($stringa,0,$len-4).'...'.substr($stringa,strlen($stringa)-1,1).'</a>';
608     } else
609     return $stringa;
612 function scorm_external_link($link) {
613 // check if a link is external
614     $result = false;
615     $link = strtolower($link);
616     if (substr($link,0,7) == 'http://')
617         $result = true;
618     else if (substr($link,0,8) == 'https://')
619         $result = true;
620     else if (substr($link,0,4) == 'www.')
621         $result = true;
622     /*else if (substr($link,0,7) == 'rstp://')
623         $result = true;
624     else if (substr($link,0,6) == 'rtp://')
625         $result = true;
626     else if (substr($link,0,6) == 'ftp://')
627         $result = true;
628     else if (substr($link,0,9) == 'gopher://')
629         $result = true; */
630     return $result;
631 }    
632 ?>