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