SCORM reports now actually show user pictures
[moodle.git] / mod / scorm / lib.php
blob82d0f98e6f4e36e6786cc6f2a179191520ef53db
1 <?PHP // $Id$
3 /// Library of functions and constants for module scorm
4 /// (replace scorm with the name of your module and delete this line)
6 define('VALUESCOES', '0');
7 define('VALUEHIGHEST', '1');
8 define('VALUEAVERAGE', '2');
9 define('VALUESUM', '3');
10 $SCORM_GRADE_METHOD = array (VALUESCOES => get_string("gradescoes", "scorm"),
11 VALUEHIGHEST => get_string("gradehighest", "scorm"),
12 VALUEAVERAGE => get_string("gradeaverage", "scorm"),
13 VALUESUM => get_string("gradesum", "scorm"));
15 $SCORM_WINDOW_OPTIONS = array('resizable', 'scrollbars', 'status', 'height', 'width');
17 if (!isset($CFG->scorm_popup)) {
18 set_config('scorm_popup', '');
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')) {
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);
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');
46 if (!isset($CFG->scorm_framesize)) {
47 set_config('scorm_framesize', 140);
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;
61 $scorm->popup = '';
63 $optionlist = array();
64 foreach ($SCORM_WINDOW_OPTIONS as $option) {
65 if (isset($scorm->$option)) {
66 $optionlist[] = $option.'='.$scorm->$option;
69 $scorm->popup = implode(',', $optionlist);
72 if ($scorm->popup != '') {
73 $scorm->popup .= ',location=0,menubar=0,toolbar=0';
74 $scorm->auto = '0';
77 return insert_record('scorm', $scorm);
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.
86 $scorm->timemodified = time();
87 $scorm->id = $scorm->instance;
89 # May have to add extra stuff in here #
90 global $SCORM_WINDOW_OPTIONS;
92 $scorm->popup = '';
94 $optionlist = array();
95 foreach ($SCORM_WINDOW_OPTIONS as $option) {
96 if (isset($scorm->$option)) {
97 $optionlist[] = $option.'='.$scorm->$option;
100 $scorm->popup = implode(',', $optionlist);
102 if ($scorm->popup != '') {
103 $scorm->popup .= ',location=0,menubar=0,toolbar=0';
104 $scorm->auto = '0';
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.
115 require('../config.php');
117 if (! $scorm = get_record('scorm', 'id', $id)) {
118 return false;
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;
130 if (! delete_records('scorm_scoes', 'scorm', $scorm->id)) {
131 $result = false;
133 if (! delete_records('scorm', 'id', $scorm->id)) {
134 $result = false;
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 $return;
151 function scorm_user_complete($course, $user, $mod, $scorm) {
152 /// Print a detailed representation of what a user has done with
153 /// a given particular instance of this module, for user activity reports.
155 return true;
158 function scorm_print_recent_activity(&$logs, $isteacher=false) {
159 /// Given a list of logs, assumed to be those since the last login
160 /// this function prints a short list of changes related to this module
161 /// If isteacher is true then perhaps additional information is printed.
162 /// This function is called from course/lib.php: print_recent_activity()
164 global $CFG, $COURSE_TEACHER_COLOR;
166 $content = NULL;
168 return $content; // True if anything was printed, otherwise false
171 function scorm_cron () {
172 /// Function to be run periodically according to the moodle cron
173 /// This function searches for things that need to be done, such
174 /// as sending out mail, toggling flags etc ...
176 global $CFG;
178 return true;
181 function scorm_grades($scormid) {
182 /// Must return an array of grades for a given instance of this module,
183 /// indexed by user. It also returns a maximum allowed grade.
185 global $CFG;
187 if (!$scorm = get_record("scorm", "id", $scormid)) {
188 return NULL;
191 if ($scorm->grademethod == VALUESCOES) {
192 if (!$return->maxgrade = count_records_select('scorm_scoes',"scorm='$scormid' AND launch<>''")) {
193 return NULL;
196 $return->grades = NULL;
197 if ($sco_users=get_records_select('scorm_sco_users', "scormid='$scormid' GROUP BY userid")) {
198 foreach ($sco_users as $sco_user) {
199 $user_data=get_records_select('scorm_sco_users',"scormid='$scormid' AND userid='$sco_user->userid'");
200 $scores->completed=0;
201 $scores->browsed=0;
202 $scores->incomplete=0;
203 $scores->failed=0;
204 $scores->notattempted=0;
205 $result='';
206 $data = current($user_data);
207 foreach ($user_data as $data) {
208 if ($data->cmi_core_lesson_status=='passed')
209 $scores->completed++;
210 else
211 $scores->{scorm_remove_spaces($data->cmi_core_lesson_status)}++;
213 if ($scores->completed)
214 $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/completed.gif\" alt=\"".get_string('completed','scorm')."\" title=\"".get_string('completed','scorm')."\"> $scores->completed ";
215 if ($scores->incomplete)
216 $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/incomplete.gif\" alt=\"".get_string('incomplete','scorm')."\" title=\"".get_string('incomplete','scorm')."\"> $scores->incomplete ";
217 if ($scores->failed)
218 $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/failed.gif\" alt=\"".get_string('failed','scorm')."\" title=\"".get_string('failed','scorm')."\"> $scores->failed ";
219 if ($scores->browsed)
220 $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/browsed.gif\" alt=\"".get_string('browsed','scorm')."\" title=\"".get_string('browsed','scorm')."\"> $scores->browsed ";
221 if ($scores->notattempted)
222 $result.="<img src=\"$CFG->wwwroot/mod/scorm/pix/notattempted.gif\" alt=\"".get_string('notattempted','scorm')."\" title=\"".get_string('notattempted','scorm')."\"> $scores->notattempted ";
224 $return->grades[$sco_user->userid]=$result;
228 } else {
229 $grades = get_records_select("scorm_sco_users", "scormid=$scormid AND cmi_core_score_raw>0","","id,userid,cmi_core_score_raw");
230 //$grades = get_records_menu("scorm_sco_users", "scormid",$scormid,"","userid,cmi_core_score_raw");
231 $valutations = array();
232 foreach ($grades as $grade) {
233 if (!isset($valutations[$grade->userid])) {
234 if ($scorm->grademethod == VALUEAVERAGE) {
235 $values = array();
236 $values[$grade->userid]->grade = 0;
237 $values[$grade->userid]->values = 0;
239 $valutations[$grade->userid] = 0;
241 switch ($scorm->grademethod) {
242 case VALUEHIGHEST:
243 if ($grade->cmi_core_score_raw > $valutations[$grade->userid]) {
244 $valutations[$grade->userid] = $grade->cmi_core_score_raw;
246 break;
247 case VALUEAVERAGE:
248 $values[$grade->userid]->grade += $grade->cmi_core_score_raw;
249 $values[$grade->userid]->values++;
250 break;
251 case VALUESUM:
252 $valutations[$grade->userid] += $grade->cmi_core_score_raw;
253 break;
256 if ($scorm->grademethod == VALUEAVERAGE) {
257 foreach($values as $userid => $value) {
258 $valutations[$userid] = $value->grade/$value->values;
261 //print_r($grades);
262 $return->grades = $valutations;
263 $return->maxgrade = $scorm->maxgrade;
265 return $return;
269 //////////////////////////////////////////////////////////////////////////////////////
270 /// Any other scorm functions go here. Each of them must have a name that
271 /// starts with scorm_
274 function scorm_randstring($len = '8')
276 $rstring = NULL;
277 $lchar = '';
278 for($i=0; $i<$len; $i++) {
279 $char = chr(rand(48,122));
280 while (!ereg('[a-zA-Z0-9]', $char)){
281 if($char == $lchar) continue;
282 $char = chr(rand(48,90));
284 $rstring .= $char;
285 $lchar = $char;
287 return $rstring;
291 function scorm_datadir($strPath, $existingdir='', $prefix = 'SCORM')
293 global $CFG;
295 if (($existingdir!='') && (is_dir($strPath.$existingdir)))
296 return $strPath.$existingdir;
298 if (is_dir($strPath)) {
299 do {
300 $datadir='/'.$prefix.scorm_randstring();
301 } while (file_exists($strPath.$datadir));
302 mkdir($strPath.$datadir, $CFG->directorypermissions);
303 @chmod($strPath.$datadir, $CFG->directorypermissions); // Just in case mkdir didn't do it
304 return $strPath.$datadir;
305 } else {
306 return false;
310 if ($CFG->scorm_validate == 'domxml') {
311 require_once('validatordomxml.php');
314 function scorm_validate($manifest)
316 global $CFG;
318 global $item_idref_array;
319 global $idres_array;
320 global $def_org_array;
321 global $id_org_array;
323 if (is_file ($manifest)) {
324 if (file_exists($manifest)) {
325 if ($CFG->scorm_validate == 'domxml') {
326 $manifest_string = file_get_contents($manifest);
328 /* Elimino i caratteri speciali di spaziatura e ritorno a capo dal file xml */
330 $spec = array('\n', '\r', '\t', '\0', '\x0B');
331 $content = str_replace($spec, '', $manifest_string);
333 if ($xmldoc = domxml_open_mem($content)) {
334 $root = $xmldoc->document_element();
335 if (!testRoot($root)) {
336 return 'syntax';
338 if (testNode($root)) {
339 // Nel corpo di questo if si controllano le corrispondenze fra gli attributi
340 // Nello Standard SCORM ad ogni attributo idRef di <item> deve corrispondere
341 // un attributo ID di <resource>
342 // Gli array degli attributi sono stati dichiarati globali in validator.php
343 // pertanto possono essere utilizzati direttamente all'interno di main.php
345 foreach($item_idref_array as $elem_it) {
346 if (array_search($elem_it, $idres_array) === false) {
347 return 'mismatch';
351 foreach($def_org_array as $elem_def) {
352 if (array_search($elem_it, $id_org_array) === false) {
353 return 'mismatch';
357 } else {
358 return 'badmanifest';
361 return 'regular';
362 } else {
363 return 'found';
366 } else {
367 return 'nomanifest';
371 function scorm_delete_files($directory)
373 if (is_dir($directory))
375 $handle=opendir($directory);
376 while (($file = readdir($handle)) != '')
378 if ($file != '.' && $file != '..')
380 if (!is_dir($directory.'/'.$file)) {
381 //chmod($directory.'/'.$file,0777);
382 unlink($directory.'/'.$file);
383 } else {
384 scorm_delete_files($directory.'/'.$file);
388 rmdir($directory);
392 function scorm_startElement($parser, $name, $attrs) {
394 global $scoes,$i,$resources,$parent,$level,$organization,$manifest,$defaultorg;
396 if ($name == 'ITEM') {
397 $i++;
398 $scoes[$i]['manifest'] = $manifest;
399 $scoes[$i]['organization'] = $organization;
400 $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
401 if (empty($attrs['IDENTIFIERREF']))
402 $attrs['IDENTIFIERREF'] = '';
403 $scoes[$i]['identifierref'] = $attrs['IDENTIFIERREF'];
404 if (empty($attrs['ISVISIBLE']))
405 $attrs['ISVISIBLE'] = '';
406 $scoes[$i]['isvisible'] = $attrs['ISVISIBLE'];
407 $scoes[$i]['parent'] = $parent[$level];
408 $level++;
409 $parent[$level] = $attrs['IDENTIFIER'];
411 if ($name == 'RESOURCE') {
412 if (!isset($attrs['HREF'])) {
413 $attrs['HREF'] = '';
415 $resources[$attrs['IDENTIFIER']]['href']=$attrs['HREF'];
416 if (!isset($attrs['ADLCP:SCORMTYPE'])) {
417 $attrs['ADLCP:SCORMTYPE'] = '';
419 $resources[$attrs['IDENTIFIER']]['type']=$attrs['ADLCP:SCORMTYPE'];
421 if ($name == 'ORGANIZATION') {
422 $i++;
423 $scoes[$i]['manifest'] = $manifest;
424 $scoes[$i]['organization'] = '';
425 $scoes[$i]['identifier'] = $attrs['IDENTIFIER'];
426 $scoes[$i]['identifierref'] = '';
427 $scoes[$i]['isvisible'] = '';
428 $scoes[$i]['parent'] = $parent[$level];
429 $level++;
430 $parent[$level] = $attrs['IDENTIFIER'];
431 $organization = $attrs['IDENTIFIER'];
433 if ($name == 'MANIFEST') {
434 $manifest = $attrs['IDENTIFIER'];
436 if ($name == 'ORGANIZATIONS') {
437 if (!isset($attrs['DEFAULT'])) {
438 $attrs['DEFAULT'] = '';
440 $defaultorg = $attrs['DEFAULT'];
444 function scorm_endElement($parser, $name) {
445 global $scoes,$i,$level,$datacontent,$navigation;
446 if ($name == 'ITEM') {
447 $level--;
449 //if ($name == 'TITLE' && $level>0) {
450 if ($name == 'TITLE') {
451 $scoes[$i]['title'] = $datacontent;
453 if ($name == 'ADLCP:HIDERTSUI') {
454 $scoes[$i][$datacontent] = 1;
456 if ($name == 'ADLCP:DATAFROMLMS') {
457 $scoes[$i]['datafromlms'] = $datacontent;
459 if ($name == 'ORGANIZATION') {
460 $organization = '';
461 $level--;
463 if ($name == 'MANIFEST') {
464 $manifest = '';
468 function scorm_characterData($parser, $data) {
469 global $datacontent;
470 $datacontent = $data;
473 function scorm_parse($basedir,$file,$scorm_id) {
474 global $scoes,$i,$resources,$parent,$level,$defaultorg;
475 $datacontent = '';
476 $scoes[][] = '';
477 $resources[] = '';
478 $organization = '';
479 $defaultorg = '';
480 $i = 0;
481 $level = 0;
482 $parent[$level] = '/';
484 $xml_parser = xml_parser_create('UTF-8');
485 // use case-folding so we are sure to find the tag in $map_array
486 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);
487 xml_set_element_handler($xml_parser, 'scorm_startElement', 'scorm_endElement');
488 xml_set_character_data_handler($xml_parser, 'scorm_characterData');
489 if (!($fp = fopen($basedir.$file, 'r'))) {
490 die('could not open XML input');
493 while ($data = fread($fp, 4096)) {
494 if (!xml_parse($xml_parser, $data, feof($fp))) {
495 die(sprintf('XML error: %s at line %d',
496 xml_error_string(xml_get_error_code($xml_parser)),
497 xml_get_current_line_number($xml_parser)));
500 xml_parser_free($xml_parser);
501 $launch = 0;
503 $sco->scorm = $scorm_id;
504 delete_records('scorm_scoes','scorm',$scorm_id);
505 delete_records('scorm_sco_users','scormid',$scorm_id);
507 if (isset($scoes[1])) {
508 for ($j=1; $j<=$i; $j++) {
509 $sco->identifier = $scoes[$j]['identifier'];
510 $sco->parent = $scoes[$j]['parent'];
511 $sco->title = $scoes[$j]['title'];
512 $sco->organization = $scoes[$j]['organization'];
513 if (!isset($scoes[$j]['datafromlms'])) {
514 $scoes[$j]['datafromlms'] = '';
516 $sco->datafromlms = $scoes[$j]['datafromlms'];
518 if (!isset($resources[($scoes[$j]['identifierref'])]['href'])) {
519 $resources[($scoes[$j]['identifierref'])]['href'] = '';
521 $sco->launch = $resources[($scoes[$j]['identifierref'])]['href'];
523 if (!isset($resources[($scoes[$j]['identifierref'])]['type'])) {
524 $resources[($scoes[$j]['identifierref'])]['type'] = '';
526 $sco->type = $resources[($scoes[$j]['identifierref'])]['type'];
528 if (!isset($scoes[$j]['previous'])) {
529 $scoes[$j]['previous'] = 0;
531 $sco->previous = $scoes[$j]['previous'];
533 if (!isset($scoes[$j]['continue'])) {
534 $scoes[$j]['continue'] = 0;
536 $sco->next = $scoes[$j]['continue'];
538 if (scorm_remove_spaces($scoes[$j]['isvisible']) != 'false') {
539 $id = insert_record('scorm_scoes',$sco);
541 //if (($launch==0) && (isset($sco->launch)) && ($defaultorg==$sco->organization)) {
542 if (($launch==0) && ($defaultorg==$sco->identifier)) {
543 $launch = $id;
546 } else {
547 foreach ($resources as $label => $resource) {
548 if (!empty($resource['href'])) {
549 $sco->identifier = $label;
550 $sco->title = $label;
551 $sco->parent = '/';
552 $sco->launch = $resource['href'];
553 $sco->type = $resource['type'];
554 $id = insert_record('scorm_scoes',$sco);
556 if ($launch == 0) {
557 $launch = $id;
562 return $launch;
565 function scorm_get_scoes_records($sco_user) {
566 /// Gets all info required to display the table of scorm results
567 /// for report.php
568 global $CFG;
570 return get_records_sql("SELECT su.*, u.firstname, u.lastname, u.picture
571 FROM {$CFG->prefix}scorm_sco_users su,
572 {$CFG->prefix}user u
573 WHERE su.scormid = '$sco_user->scormid'
574 AND su.userid = u.id
575 AND su.userid = '$sco_user->userid'
576 ORDER BY scoid");
579 function scorm_remove_spaces($sourcestr) {
580 // Remove blank space from a string
581 $newstr='';
582 for( $i=0; $i<strlen($sourcestr); $i++) {
583 if ($sourcestr[$i]!=' ')
584 $newstr .=$sourcestr[$i];
586 return $newstr;
589 function scorm_string_round($stringa) {
590 // Crop a string to $len character and set an anchor title to the full string
591 $len=11;
592 if ( strlen($stringa)>$len ) {
593 return "<A name=\"\" title=\"$stringa\">".substr($stringa,0,$len-4).'...'.substr($stringa,strlen($stringa)-1,1).'</A>';
594 } else
595 return $stringa;
598 function scorm_external_link($link) {
599 // check if a link is external
600 $result = false;
601 $link = strtolower($link);
602 if (substr($link,0,7) == 'http://')
603 $result = true;
604 else if (substr($link,0,8) == 'https://')
605 $result = true;
606 else if (substr($link,0,4) == 'www.')
607 $result = true;
608 /*else if (substr($link,0,7) == 'rstp://')
609 $result = true;
610 else if (substr($link,0,6) == 'rtp://')
611 $result = true;
612 else if (substr($link,0,6) == 'ftp://')
613 $result = true;
614 else if (substr($link,0,9) == 'gopher://')
615 $result = true; */
616 return $result;