Added multiorg display and patched some typo
[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) {
392     global $scoes,$i,$resources,$parent,$level,$organization,$manifest,$defaultorg;
394     if ($name == 'ITEM') {
395         $i++;
396         $scoes[$i]['manifest'] = $manifest;
397         $scoes[$i]['organization'] = $organization;
398         $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
399         if (empty($attrs['IDENTIFIERREF']))
400             $attrs['IDENTIFIERREF'] = '';
401         $scoes[$i]['identifierref'] = $attrs['IDENTIFIERREF'];
402         if (empty($attrs['ISVISIBLE']))
403             $attrs['ISVISIBLE'] = '';
404         $scoes[$i]['isvisible'] = $attrs['ISVISIBLE'];
405         $scoes[$i]['parent'] = $parent[$level];
406         $level++;
407         $parent[$level] = $attrs['IDENTIFIER'];
408     }
409     if ($name == 'RESOURCE') {
410         if (!isset($attrs['HREF'])) {
411             $attrs['HREF'] = '';
412         }
413         $resources[$attrs['IDENTIFIER']]['href']=$attrs['HREF'];
414         if (!isset($attrs['ADLCP:SCORMTYPE'])) {
415             $attrs['ADLCP:SCORMTYPE'] = '';
416         }
417         $resources[$attrs['IDENTIFIER']]['type']=$attrs['ADLCP:SCORMTYPE'];
418     }
419     if ($name == 'ORGANIZATION') {
420         $i++;
421         $scoes[$i]['manifest'] = $manifest;
422         $scoes[$i]['organization'] = '';
423         $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
424         $scoes[$i]['identifierref'] = '';
425         $scoes[$i]['isvisible'] = '';
426         $scoes[$i]['parent'] = $parent[$level];
427         $level++;
428         $parent[$level] = $attrs['IDENTIFIER'];
429         $organization = $attrs['IDENTIFIER'];
430     }
431     if ($name == 'MANIFEST') {
432         $manifest = $attrs['IDENTIFIER'];
433     }
434     if ($name == 'ORGANIZATIONS') {
435         if (!isset($attrs['DEFAULT'])) {
436             $attrs['DEFAULT'] = '';
437         }
438         $defaultorg = $attrs['DEFAULT'];
439     }
442 function scorm_endElement($parser, $name) {
443     global $scoes,$i,$level,$datacontent,$navigation;
444     if ($name == 'ITEM') {
445         $level--;
446     }
447     //if ($name == 'TITLE' && $level>0) {
448     if ($name == 'TITLE') {
449         $scoes[$i]['title'] = $datacontent;
450     }
451     if ($name == 'ADLCP:HIDERTSUI') {
452         $scoes[$i][$datacontent] = 1;
453     }
454     if ($name == 'ADLCP:DATAFROMLMS') {
455         $scoes[$i]['datafromlms'] = $datacontent;
456     }
457     if ($name == 'ORGANIZATION') {
458         $organization = '';
459         $level--;
460     }
461     if ($name == 'MANIFEST') {
462         $manifest = '';
463     }
466 function scorm_characterData($parser, $data) {
467     global $datacontent;
468     $datacontent = $data;
471 function scorm_parse($basedir,$file,$scorm_id) {
472     global $scoes,$i,$resources,$parent,$level,$defaultorg;
473     $datacontent = '';
474     $scoes[][] = '';
475     $resources[] = '';
476     $organization = '';
477     $defaultorg = '';
478     $i = 0;
479     $level = 0;
480     $parent[$level] = '/';
482     $xml_parser = xml_parser_create();
483     // use case-folding so we are sure to find the tag in $map_array
484     xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
485     xml_set_element_handler($xml_parser, 'scorm_startElement', 'scorm_endElement');
486     xml_set_character_data_handler($xml_parser, 'scorm_characterData');
487     if (!($fp = fopen($basedir.$file, 'r'))) {
488        die('could not open XML input');
489     }
491     while ($data = fread($fp, 4096)) {
492         if (!xml_parse($xml_parser, $data, feof($fp))) {
493             die(sprintf('XML error: %s at line %d',
494                     xml_error_string(xml_get_error_code($xml_parser)),
495                     xml_get_current_line_number($xml_parser)));
496         }
497     }
498     xml_parser_free($xml_parser);
499     $launch = 0;
501     $sco->scorm = $scorm_id;
502     delete_records('scorm_scoes','scorm',$scorm_id);
503     delete_records('scorm_sco_users','scormid',$scorm_id);
504     
505     if (isset($scoes[1])) {
506         for ($j=1; $j<=$i; $j++) {
507             $sco->identifier = $scoes[$j]['identifier'];
508             $sco->parent = $scoes[$j]['parent'];
509             $sco->title = $scoes[$j]['title'];
510             $sco->organization = $scoes[$j]['organization'];
511             if (!isset($scoes[$j]['datafromlms'])) {
512                 $scoes[$j]['datafromlms'] = '';
513             } 
514             $sco->datafromlms = $scoes[$j]['datafromlms'];
515         
516             if (!isset($resources[($scoes[$j]['identifierref'])]['href'])) {
517                 $resources[($scoes[$j]['identifierref'])]['href'] = '';
518             }
519             $sco->launch = $resources[($scoes[$j]['identifierref'])]['href'];
520         
521             if (!isset($resources[($scoes[$j]['identifierref'])]['type'])) {
522                 $resources[($scoes[$j]['identifierref'])]['type'] = '';
523             }
524             $sco->type = $resources[($scoes[$j]['identifierref'])]['type'];
525         
526             if (!isset($scoes[$j]['previous'])) {
527                 $scoes[$j]['previous'] = 0;
528             }
529             $sco->previous = $scoes[$j]['previous'];
530         
531             if (!isset($scoes[$j]['continue'])) {
532                 $scoes[$j]['continue'] = 0;
533             }
534             $sco->next = $scoes[$j]['continue'];
535         
536             if (scorm_remove_spaces($scoes[$j]['isvisible']) != 'false') {
537                 $id = insert_record('scorm_scoes',$sco);
538             }
539             //if (($launch==0) && (isset($sco->launch)) && ($defaultorg==$sco->organization)) {
540             if (($launch==0) && ($defaultorg==$sco->identifier)) {
541                 $launch = $id;
542             }
543         }
544     } else {
545         foreach ($resources as $label => $resource) {
546             if ((isset($resource['type'])) && ($resource['type'] == 'sco')) {
547                 $sco->identifier = $label;
548                 $sco->title = $label;
549                 $sco->parent = '/';
550                 $sco->launch = $resource['href'];
551                 $sco->type = $resource['type'];
552                 $id = insert_record('scorm_scoes',$sco);
553                 
554                 if ($launch == 0) {
555                     $launch = $id;
556                 }
557             }
558         }
559     }
560     return $launch;
563 function scorm_get_scoes_records($sco_user) {
564 /// Gets all info required to display the table of scorm results
565 /// for report.php
566     global $CFG;
568     return get_records_sql("SELECT su.*, u.firstname, u.lastname, u.picture 
569                             FROM {$CFG->prefix}scorm_sco_users su, 
570                                  {$CFG->prefix}user u
571                             WHERE su.scormid = '$sco_user->scormid'
572                               AND su.userid = u.id
573                               AND su.userid = $sco_user->userid
574                               ORDER BY scoid");
577 function scorm_remove_spaces($sourcestr) {
578 // Remove blank space from a string
579     $newstr='';
580     for( $i=0; $i<strlen($sourcestr); $i++) {
581         if ($sourcestr[$i]!=' ')
582             $newstr .=$sourcestr[$i];
583     }
584     return $newstr;
587 function scorm_string_round($stringa) {
588 // Crop a string to $len character and set an anchor title to the full string
589     $len=11;
590     if ( strlen($stringa)>$len ) {
591     return "<A name=\"\" title=\"$stringa\">".substr($stringa,0,$len-4).'...'.substr($stringa,strlen($stringa)-1,1).'</A>';
592     } else
593     return $stringa;
596 function scorm_external_link($link) {
597 // check if a link is external
598     $result = false;
599     $link = strtolower($link);
600     if (substr($link,0,7) == 'http://')
601         $result = true;
602     else if (substr($link,0,8) == 'https://')
603         $result = true;
604     else if (substr($link,0,4) == 'www.')
605         $result = true;
606     /*else if (substr($link,0,7) == 'rstp://')
607         $result = true;
608     else if (substr($link,0,6) == 'rtp://')
609         $result = true;
610     else if (substr($link,0,6) == 'ftp://')
611         $result = true;
612     else if (substr($link,0,9) == 'gopher://')
613         $result = true; */
614     return $result;
615 }    
616 ?>