Added ability to reset page to defaults. bug #4123
[moodle-linuxchix.git] / mod / scorm / locallib.php
blobe53a923b57fc812b7e835e8365d67507d58afe86
1 <?php // $Id$
3 /// Local Library of functions and constants for module scorm
5 /**
6 * Create a new temporary subdirectory with a random name in the given path
8 * @param string $strpath The scorm data directory
9 * @return string/boolean
11 function scorm_datadir($strPath)
13 global $CFG;
15 if (is_dir($strPath)) {
16 do {
17 // Create a random string of 8 chars
18 $randstring = NULL;
19 $lchar = '';
20 $len = 8;
21 for ($i=0; $i<$len; $i++) {
22 $char = chr(rand(48,122));
23 while (!ereg('[a-zA-Z0-9]', $char)){
24 if ($char == $lchar) continue;
25 $char = chr(rand(48,90));
27 $randstring .= $char;
28 $lchar = $char;
30 $datadir='/'.$randstring;
31 } while (file_exists($strPath.$datadir));
32 mkdir($strPath.$datadir, $CFG->directorypermissions);
33 @chmod($strPath.$datadir, $CFG->directorypermissions); // Just in case mkdir didn't do it
34 return $strPath.$datadir;
35 } else {
36 return false;
40 /**
41 * Given a package directory, this function will check if the package is valid
43 * @param string $packagedir The package directory
44 * @return mixed
46 function scorm_validate($packagedir) {
47 $validation = new stdClass();
48 if (is_file($packagedir.'/imsmanifest.xml')) {
49 $validation->result = 'found';
50 $validation->pkgtype = 'SCORM';
51 } else {
52 if ($handle = opendir($packagedir)) {
53 while (($file = readdir($handle)) !== false) {
54 $ext = substr($file,strrpos($file,'.'));
55 if (strtolower($ext) == '.cst') {
56 $validation->result = 'found';
57 $validation->pkgtype = 'AICC';
58 break;
61 closedir($handle);
63 if (!isset($validation)) {
64 $validation->result = 'nomanifest';
65 $validation->pkgtype = 'SCORM';
68 return $validation;
71 function scorm_get_user_data($userid) {
72 /// Gets user info required to display the table of scorm results
73 /// for report.php
75 return get_record('user','id',$userid,'','','','','firstname, lastname, picture');
78 function scorm_string_wrap($stringa, $len=15) {
79 // Crop the given string into max $len characters lines
80 if (strlen($stringa) > $len) {
81 $words = explode(' ', $stringa);
82 $newstring = '';
83 $substring = '';
84 foreach ($words as $word) {
85 if ((strlen($substring)+strlen($word)+1) < $len) {
86 $substring .= ' '.$word;
87 } else {
88 $newstring .= ' '.$substring.'<br />';
89 $substring = $word;
92 if (!empty($substring)) {
93 $newstring .= ' '.$substring;
95 return $newstring;
96 } else {
97 return $stringa;
101 function scorm_eval_prerequisites($prerequisites,$usertracks) {
102 $element = '';
103 $stack = array();
104 $statuses = array(
105 'passed' => 'passed',
106 'completed' => 'completed',
107 'failed' => 'failed',
108 'incomplete' => 'incomplete',
109 'browsed' => 'browsed',
110 'not attempted' => 'notattempted',
111 'p' => 'passed',
112 'c' => 'completed',
113 'f' => 'failed',
114 'i' => 'incomplete',
115 'b' => 'browsed',
116 'n' => 'notattempted'
118 $i=0;
119 while ($i<strlen($prerequisites)) {
120 $symbol = $prerequisites[$i];
121 switch ($symbol) {
122 case '&':
123 case '|':
124 $symbol .= $symbol;
125 case '~':
126 case '(':
127 case ')':
128 case '*':
129 //case '{':
130 //case '}':
131 //case ',':
132 $element = trim($element);
134 if (!empty($element)) {
135 $element = trim($element);
136 if (isset($usertracks[$element])) {
137 $element = '((\''.$usertracks[$element]->status.'\' == \'completed\') || '.
138 '(\''.$usertracks[$element]->status.'\' == \'passed\'))';
139 } else if (($operator = strpos($element,'=')) !== false) {
140 $item = trim(substr($element,0,$operator));
141 if (!isset($usertracks[$item])) {
142 return false;
145 $value = trim(trim(substr($element,$operator+1)),'"');
146 if (isset($statuses[$value])) {
147 $status = $statuses[$value];
148 } else {
149 return false;
152 $element = '(\''.$usertracks[$item]->status.'\' == \''.$status.'\')';
153 } else if (($operator = strpos($element,'<>')) !== false) {
154 $item = trim(substr($element,0,$operator));
155 if (!isset($usertracks[$item])) {
156 return false;
159 $value = trim(trim(substr($element,$operator+2)),'"');
160 if (isset($statuses[$value])) {
161 $status = $statuses[$value];
162 } else {
163 return false;
166 $element = '(\''.$usertracks[$item]->status.'\' != \''.$status.'\')';
167 } else if (is_numeric($element)) {
168 if ($symbol == '*') {
169 $symbol = '';
170 $open = strpos($prerequisites,'{',$i);
171 $opened = 1;
172 $closed = 0;
173 for ($close=$open+1; (($opened > $closed) && ($close<strlen($prerequisites))); $close++) {
174 if ($prerequisites[$close] == '}') {
175 $closed++;
176 } else if ($prerequisites[$close] == '{') {
177 $opened++;
180 $i = $close;
182 $setelements = explode(',', substr($prerequisites, $open+1, $close-($open+1)-1));
183 $settrue = 0;
184 foreach ($setelements as $setelement) {
185 if (scorm_eval_prerequisites($setelement,$usertracks)) {
186 $settrue++;
190 if ($settrue >= $element) {
191 $element = 'true';
192 } else {
193 $element = 'false';
196 } else {
197 return false;
200 array_push($stack,$element);
201 $element = '';
203 if ($symbol == '~') {
204 $symbol = '!';
206 if (!empty($symbol)) {
207 array_push($stack,$symbol);
209 break;
210 default:
211 $element .= $symbol;
212 break;
214 $i++;
216 if (!empty($element)) {
217 $element = trim($element);
218 if (isset($usertracks[$element])) {
219 $element = '((\''.$usertracks[$element]->status.'\' == \'completed\') || '.
220 '(\''.$usertracks[$element]->status.'\' == \'passed\'))';
221 } else if (($operator = strpos($element,'=')) !== false) {
222 $item = trim(substr($element,0,$operator));
223 if (!isset($usertracks[$item])) {
224 return false;
227 $value = trim(trim(substr($element,$operator+1)),'"');
228 if (isset($statuses[$value])) {
229 $status = $statuses[$value];
230 } else {
231 return false;
234 $element = '(\''.$usertracks[$item]->status.'\' == \''.$status.'\')';
235 } else if (($operator = strpos($element,'<>')) !== false) {
236 $item = trim(substr($element,0,$operator));
237 if (!isset($usertracks[$item])) {
238 return false;
241 $value = trim(trim(substr($element,$operator+1)),'"');
242 if (isset($statuses[$value])) {
243 $status = $statuses[$value];
244 } else {
245 return false;
248 $element = '(\''.$usertracks[$item]->status.'\' != \''.trim($status).'\')';
249 } else {
250 return false;
253 array_push($stack,$element);
255 return eval('return '.implode($stack).';');
259 function scorm_insert_track($userid,$scormid,$scoid,$attempt,$element,$value) {
260 $id = null;
261 if ($track = get_record_select('scorm_scoes_track',"userid='$userid' AND scormid='$scormid' AND scoid='$scoid' AND attempt='$attempt' AND element='$element'")) {
262 $track->value = $value;
263 $track->timemodified = time();
264 $id = update_record('scorm_scoes_track',$track);
265 } else {
266 $track->userid = $userid;
267 $track->scormid = $scormid;
268 $track->scoid = $scoid;
269 $track->attempt = $attempt;
270 $track->element = $element;
271 $track->value = addslashes($value);
272 $track->timemodified = time();
273 $id = insert_record('scorm_scoes_track',$track);
275 return $id;
279 function scorm_add_time($a, $b) {
280 $aes = explode(':',$a);
281 $bes = explode(':',$b);
282 $aseconds = explode('.',$aes[2]);
283 $bseconds = explode('.',$bes[2]);
284 $change = 0;
286 $acents = 0; //Cents
287 if (count($aseconds) > 1) {
288 $acents = $aseconds[1];
290 $bcents = 0;
291 if (count($bseconds) > 1) {
292 $bcents = $bseconds[1];
294 $cents = $acents + $bcents;
295 $change = floor($cents / 100);
296 $cents = $cents - ($change * 100);
297 if (floor($cents) < 10) {
298 $cents = '0'. $cents;
301 $secs = $aseconds[0] + $bseconds[0] + $change; //Seconds
302 $change = floor($secs / 60);
303 $secs = $secs - ($change * 60);
304 if (floor($secs) < 10) {
305 $secs = '0'. $secs;
308 $mins = $aes[1] + $bes[1] + $change; //Minutes
309 $change = floor($mins / 60);
310 $mins = $mins - ($change * 60);
311 if ($mins < 10) {
312 $mins = '0' . $mins;
315 $hours = $aes[0] + $bes[0] + $change; //Hours
316 if ($hours < 10) {
317 $hours = '0' . $hours;
320 if ($cents != '0') {
321 return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
322 } else {
323 return $hours . ":" . $mins . ":" . $secs;
327 function scorm_external_link($link) {
328 // check if a link is external
329 $result = false;
330 $link = strtolower($link);
331 if (substr($link,0,7) == 'http://') {
332 $result = true;
333 } else if (substr($link,0,8) == 'https://') {
334 $result = true;
335 } else if (substr($link,0,4) == 'www.') {
336 $result = true;
338 return $result;
341 function scorm_grade_user($scoes, $userid, $grademethod=VALUESCOES) {
342 $scores = NULL;
343 $scores->scoes = 0;
344 $scores->values = 0;
345 $scores->max = 0;
346 $scores->sum = 0;
348 if (!$scoes) {
349 return '';
352 $current = current($scoes);
353 $attempt = scorm_get_last_attempt($current->scorm, $userid);
354 foreach ($scoes as $sco) {
355 if ($userdata=scorm_get_tracks($sco->id, $userid,$attempt)) {
356 if (($userdata->status == 'completed') || ($userdata->status == 'passed')) {
357 $scores->scoes++;
359 if (!empty($userdata->score_raw)) {
360 $scores->values++;
361 $scores->sum += $userdata->score_raw;
362 $scores->max = ($userdata->score_raw > $scores->max)?$userdata->score_raw:$scores->max;
366 switch ($grademethod) {
367 case VALUEHIGHEST:
368 return $scores->max;
369 break;
370 case VALUEAVERAGE:
371 if ($scores->values > 0) {
372 return $scores->sum/$scores->values;
373 } else {
374 return 0;
376 break;
377 case VALUESUM:
378 return $scores->sum;
379 break;
380 case VALUESCOES:
381 return $scores->scoes;
382 break;
386 function scorm_count_launchable($scormid,$organization) {
387 return count_records_select('scorm_scoes',"scorm=$scormid AND organization='$organization' AND launch<>''");
390 function scorm_get_toc($user,$scorm,$liststyle,$currentorg='',$scoid='',$mode='normal',$attempt='',$play=false) {
391 global $CFG;
393 $strexpand = get_string('expcoll','scorm');
394 $modestr = '';
395 if ($mode == 'browse') {
396 $modestr = '&amp;mode='.$mode;
398 $scormpixdir = $CFG->modpixpath.'/scorm/pix';
400 $result = new stdClass();
401 $result->toc = "<ul id='0' class='$liststyle'>\n";
402 $tocmenus = array();
403 $result->prerequisites = true;
404 $incomplete = false;
407 // Get the current organization infos
409 $organizationsql = '';
410 if (!empty($currentorg)) {
411 if (($organizationtitle = get_field('scorm_scoes','title','scorm',$scorm->id,'identifier',$currentorg)) != '') {
412 $result->toc .= "\t<li>$organizationtitle</li>\n";
413 $tocmenus[] = $organizationtitle;
415 $organizationsql = "AND organization='$currentorg'";
418 // If not specified retrieve the last attempt number
420 if (empty($attempt)) {
421 $attempt = scorm_get_last_attempt($scorm->id, $user->id);
423 $result->attemptleft = $scorm->maxattempt - $attempt;
424 if ($scoes = get_records_select('scorm_scoes',"scorm='$scorm->id' $organizationsql order by id ASC")){
426 // Retrieve user tracking data for each learning object
428 $usertracks = array();
429 foreach ($scoes as $sco) {
430 if (!empty($sco->launch)) {
431 if ($usertrack=scorm_get_tracks($sco->id,$user->id,$attempt)) {
432 if ($usertrack->status == '') {
433 $usertrack->status = 'notattempted';
435 $usertracks[$sco->identifier] = $usertrack;
440 $level=0;
441 $sublist=1;
442 $previd = 0;
443 $nextid = 0;
444 $findnext = false;
445 $parents[$level]='/';
447 foreach ($scoes as $sco) {
448 if ($parents[$level]!=$sco->parent) {
449 if ($newlevel = array_search($sco->parent,$parents)) {
450 for ($i=0; $i<($level-$newlevel); $i++) {
451 $result->toc .= "\t\t</ul></li>\n";
453 $level = $newlevel;
454 } else {
455 $i = $level;
456 $closelist = '';
457 while (($i > 0) && ($parents[$level] != $sco->parent)) {
458 $closelist .= "\t\t</ul></li>\n";
459 $i--;
461 if (($i == 0) && ($sco->parent != $currentorg)) {
462 $result->toc .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
463 $level++;
464 } else {
465 $result->toc .= $closelist;
466 $level = $i;
468 $parents[$level]=$sco->parent;
471 $result->toc .= "\t\t<li>";
472 $nextsco = next($scoes);
473 if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
474 $sublist++;
475 $result->toc .= '<a href="javascript:expandCollide(img'.$sublist.','.$sublist.');"><img id="img'.$sublist.'" src="'.$scormpixdir.'/minus.gif" alt="'.$strexpand.'" title="'.$strexpand.'"/></a>';
476 } else {
477 $result->toc .= '<img src="'.$scormpixdir.'/spacer.gif" />';
479 if (empty($sco->title)) {
480 $sco->title = $sco->identifier;
482 if (!empty($sco->launch)) {
483 $startbold = '';
484 $endbold = '';
485 $score = '';
486 if (empty($scoid) && ($mode != 'normal')) {
487 $scoid = $sco->id;
489 if (isset($usertracks[$sco->identifier])) {
490 $usertrack = $usertracks[$sco->identifier];
491 $strstatus = get_string($usertrack->status,'scorm');
492 $result->toc .= '<img src="'.$scormpixdir.'/'.$usertrack->status.'.gif" alt="'.$strstatus.'" title="'.$strstatus.'" />';
493 if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) {
494 $incomplete = true;
495 if ($play && empty($scoid)) {
496 $scoid = $sco->id;
499 if ($usertrack->score_raw != '') {
500 $score = '('.get_string('score','scorm').':&nbsp;'.$usertrack->score_raw.')';
502 } else {
503 if ($play && empty($scoid)) {
504 $scoid = $sco->id;
506 if ($sco->scormtype == 'sco') {
507 $result->toc .= '<img src="'.$scormpixdir.'/notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
508 $incomplete = true;
509 } else {
510 $result->toc .= '<img src="'.$scormpixdir.'/asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
513 if ($sco->id == $scoid) {
514 $startbold = '<b>';
515 $endbold = '</b>';
516 $findnext = true;
517 $shownext = $sco->next;
518 $showprev = $sco->previous;
521 if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1) && ($nextsco!==false) && (!$findnext)) {
522 if (!empty($sco->launch)) {
523 $previd = $sco->id;
526 if (empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites,$usertracks)) {
527 if ($sco->id == $scoid) {
528 $result->prerequisites = true;
530 $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&amp;currentorg='.$currentorg.$modestr.'&amp;scoid='.$sco->id;
531 $result->toc .= '&nbsp'.$startbold.'<a href="'.$url.'">'.format_string($sco->title).'</a>'.$score.$endbold."</li>\n";
532 $tocmenus[$sco->id] = scorm_repeater('&minus;',$level) . '&gt;' . format_string($sco->title);
533 } else {
534 if ($sco->id == $scoid) {
535 $result->prerequisites = false;
537 $result->toc .= '&nbsp;'.$sco->title."</li>\n";
539 } else {
540 $result->toc .= '&nbsp;'.$sco->title."</li>\n";
542 if (($nextsco !== false) && ($nextid == 0) && ($findnext)) {
543 if (!empty($nextsco->launch)) {
544 $nextid = $nextsco->id;
548 for ($i=0;$i<$level;$i++) {
549 $result->toc .= "\t\t</ul></li>\n";
552 if ($play) {
553 $sco = get_record('scorm_scoes','id',$scoid);
554 $sco->previd = $previd;
555 $sco->nextid = $nextid;
556 $result->sco = $sco;
557 $result->incomplete = $incomplete;
558 } else {
559 $result->incomplete = $incomplete;
562 $result->toc .= "\t</ul>\n";
563 if ($scorm->hidetoc == 0) {
564 $result->toc .= '
565 <script language="javascript" type="text/javascript">
566 <!--
567 function expandCollide(which,list) {
568 var nn=document.ids?true:false
569 var w3c=document.getElementById?true:false
570 var beg=nn?"document.ids.":w3c?"document.getElementById(":"document.all.";
571 var mid=w3c?").style":".style";
573 if (eval(beg+list+mid+".display") != "none") {
574 which.src = "'.$scormpixdir.'/plus.gif";
575 eval(beg+list+mid+".display=\'none\';");
576 } else {
577 which.src = "'.$scormpixdir.'/minus.gif";
578 eval(beg+list+mid+".display=\'block\';");
582 </script>'."\n";
585 $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&amp;currentorg='.$currentorg.$modestr.'&amp;scoid=';
586 $result->tocmenu = popup_form($url,$tocmenus, "tocmenu", $sco->id, '', '', '', true);
588 return $result;
591 function scorm_get_last_attempt($scormid, $userid) {
592 /// Find the last attempt number for the given user id and scorm id
593 if ($lastattempt = get_record('scorm_scoes_track', 'userid', $userid, 'scormid', $scormid, '', '', 'max(attempt) as a')) {
594 if (empty($lastattempt->a)) {
595 return '1';
596 } else {
597 return $lastattempt->a;
602 function scorm_get_tracks($scoid,$userid,$attempt='') {
603 /// Gets all tracks of specified sco and user
604 global $CFG;
606 if (empty($attempt)) {
607 if ($scormid = get_field('scorm_scoes','scorm','id',$scoid)) {
608 $attempt = scorm_get_last_attempt($scormid,$userid);
609 } else {
610 $attempt = 1;
613 $attemptsql = ' AND attempt=' . $attempt;
614 if ($tracks = get_records_select('scorm_scoes_track',"userid=$userid AND scoid=$scoid".$attemptsql,'element ASC')) {
615 $usertrack->userid = $userid;
616 $usertrack->scoid = $scoid;
617 $usertrack->score_raw = '';
618 $usertrack->status = '';
619 $usertrack->total_time = '00:00:00';
620 $usertrack->session_time = '00:00:00';
621 $usertrack->timemodified = 0;
622 foreach ($tracks as $track) {
623 $element = $track->element;
624 $usertrack->{$element} = $track->value;
625 switch ($element) {
626 case 'cmi.core.lesson_status':
627 case 'cmi.completion_status':
628 if ($track->value == 'not attempted') {
629 $track->value = 'notattempted';
631 $usertrack->status = $track->value;
632 break;
633 case 'cmi.core.score.raw':
634 case 'cmi.score.raw':
635 $usertrack->score_raw = $track->value;
636 break;
637 case 'cmi.core.session_time':
638 case 'cmi.session_time':
639 $usertrack->session_time = $track->value;
640 break;
641 case 'cmi.core.total_time':
642 case 'cmi.total_time':
643 $usertrack->total_time = $track->value;
644 break;
646 if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) {
647 $usertrack->timemodified = $track->timemodified;
650 return $usertrack;
651 } else {
652 return false;
657 /// Library of functions and constants for parsing packages
659 function scorm_parse($scorm) {
660 global $CFG;
662 // Parse scorm manifest
663 if ($scorm->pkgtype == 'AICC') {
664 $scorm->launch = scorm_parse_aicc($scorm->dir.'/'.$scorm->id,$scorm->id);
665 } else {
666 if (basename($scorm->reference) != 'imsmanifest.xml') {
667 $scorm->launch = scorm_parse_scorm($scorm->dir.'/'.$scorm->id,$scorm->id);
668 } else {
669 $scorm->launch = scorm_parse_scorm($CFG->dataroot.'/'.$scorm->course.'/'.dirname($scorm->reference),$scorm->id);
673 return $scorm->launch;
677 * Take the header row of an AICC definition file
678 * and returns sequence of columns and a pointer to
679 * the sco identifier column.
681 * @param string $row AICC header row
682 * @param string $mastername AICC sco identifier column
683 * @return mixed
685 function scorm_get_aicc_columns($row,$mastername='system_id') {
686 $tok = strtok(strtolower($row),"\",\n\r");
687 $result->columns = array();
688 $i=0;
689 while ($tok) {
690 if ($tok !='') {
691 $result->columns[] = $tok;
692 if ($tok == $mastername) {
693 $result->mastercol = $i;
695 $i++;
697 $tok = strtok("\",\n\r");
699 return $result;
703 * Given a colums array return a string containing the regular
704 * expression to match the columns in a text row.
706 * @param array $column The header columns
707 * @param string $remodule The regular expression module for a single column
708 * @return string
710 function scorm_forge_cols_regexp($columns,$remodule='(".*")?,') {
711 $regexp = '/^';
712 foreach ($columns as $column) {
713 $regexp .= $remodule;
715 $regexp = substr($regexp,0,-1) . '/';
716 return $regexp;
719 function scorm_parse_aicc($pkgdir,$scormid){
720 $version = 'AICC';
721 $ids = array();
722 $courses = array();
723 if ($handle = opendir($pkgdir)) {
724 while (($file = readdir($handle)) !== false) {
725 $ext = substr($file,strrpos($file,'.'));
726 $extension = strtolower(substr($ext,1));
727 $id = strtolower(basename($file,$ext));
728 $ids[$id]->$extension = $file;
730 closedir($handle);
732 foreach ($ids as $courseid => $id) {
733 if (isset($id->crs)) {
734 if (is_file($pkgdir.'/'.$id->crs)) {
735 $rows = file($pkgdir.'/'.$id->crs);
736 foreach ($rows as $row) {
737 if (preg_match("/^(.+)=(.+)$/",$row,$matches)) {
738 switch (strtolower(trim($matches[1]))) {
739 case 'course_id':
740 $courses[$courseid]->id = trim($matches[2]);
741 break;
742 case 'course_title':
743 $courses[$courseid]->title = trim($matches[2]);
744 break;
745 case 'version':
746 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
747 break;
753 if (isset($id->des)) {
754 $rows = file($pkgdir.'/'.$id->des);
755 $columns = scorm_get_aicc_columns($rows[0]);
756 $regexp = scorm_forge_cols_regexp($columns->columns);
757 for ($i=1;$i<count($rows);$i++) {
758 if (preg_match($regexp,$rows[$i],$matches)) {
759 for ($j=0;$j<count($columns->columns);$j++) {
760 $column = $columns->columns[$j];
761 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
766 if (isset($id->au)) {
767 $rows = file($pkgdir.'/'.$id->au);
768 $columns = scorm_get_aicc_columns($rows[0]);
769 $regexp = scorm_forge_cols_regexp($columns->columns);
770 for ($i=1;$i<count($rows);$i++) {
771 if (preg_match($regexp,$rows[$i],$matches)) {
772 for ($j=0;$j<count($columns->columns);$j++) {
773 $column = $columns->columns[$j];
774 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
779 if (isset($id->cst)) {
780 $rows = file($pkgdir.'/'.$id->cst);
781 $columns = scorm_get_aicc_columns($rows[0],'block');
782 $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
783 for ($i=1;$i<count($rows);$i++) {
784 if (preg_match($regexp,$rows[$i],$matches)) {
785 for ($j=0;$j<count($columns->columns);$j++) {
786 if ($j != $columns->mastercol) {
787 $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
793 if (isset($id->ort)) {
794 $rows = file($pkgdir.'/'.$id->ort);
796 if (isset($id->pre)) {
797 $rows = file($pkgdir.'/'.$id->pre);
798 $columns = scorm_get_aicc_columns($rows[0],'structure_element');
799 $regexp = scorm_forge_cols_regexp($columns->columns,'(.+),');
800 for ($i=1;$i<count($rows);$i++) {
801 if (preg_match($regexp,$rows[$i],$matches)) {
802 $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]),1,-1);
806 if (isset($id->cmp)) {
807 $rows = file($pkgdir.'/'.$id->cmp);
810 //print_r($courses);
811 $launch = 0;
812 if (isset($courses)) {
813 foreach ($courses as $course) {
814 unset($sco);
815 $sco->identifier = $course->id;
816 $sco->scorm = $scormid;
817 $sco->organization = '';
818 $sco->title = $course->title;
819 $sco->parent = '/';
820 $sco->launch = '';
821 $sco->scormtype = '';
822 //print_r($sco);
823 $id = insert_record('scorm_scoes',$sco);
824 if ($launch == 0) {
825 $launch = $id;
827 if (isset($course->elements)) {
828 foreach($course->elements as $element) {
829 unset($sco);
830 $sco->identifier = $element->system_id;
831 $sco->scorm = $scormid;
832 $sco->organization = $course->id;
833 $sco->title = $element->title;
834 if (strtolower($element->parent) == 'root') {
835 $sco->parent = '/';
836 } else {
837 $sco->parent = $element->parent;
839 if (isset($element->file_name)) {
840 $sco->launch = $element->file_name;
841 $sco->scormtype = 'sco';
842 } else {
843 $element->file_name = '';
844 $sco->scormtype = '';
846 if (!isset($element->prerequisites)) {
847 $element->prerequisites = '';
849 $sco->prerequisites = $element->prerequisites;
850 if (!isset($element->max_time_allowed)) {
851 $element->max_time_allowed = '';
853 $sco->maxtimeallowed = $element->max_time_allowed;
854 if (!isset($element->time_limit_action)) {
855 $element->time_limit_action = '';
857 $sco->timelimitaction = $element->time_limit_action;
858 if (!isset($element->mastery_score)) {
859 $element->mastery_score = '';
861 $sco->masteryscore = $element->mastery_score;
862 $sco->previous = 0;
863 $sco->next = 0;
864 $id = insert_record('scorm_scoes',$sco);
865 if ($launch==0) {
866 $launch = $id;
872 set_field('scorm','version','AICC','id',$scormid);
873 return $launch;
876 function scorm_get_resources($blocks) {
877 foreach ($blocks as $block) {
878 if ($block['name'] == 'RESOURCES') {
879 foreach ($block['children'] as $resource) {
880 if ($resource['name'] == 'RESOURCE') {
881 $resources[addslashes($resource['attrs']['IDENTIFIER'])] = $resource['attrs'];
886 return $resources;
889 function scorm_get_manifest($blocks,$scoes) {
890 static $parents = array();
891 static $resources;
893 static $manifest;
894 static $organization;
896 if (count($blocks) > 0) {
897 foreach ($blocks as $block) {
898 switch ($block['name']) {
899 case 'METADATA':
900 if (isset($block['children'])) {
901 foreach ($block['children'] as $metadata) {
902 if ($metadata['name'] == 'SCHEMAVERSION') {
903 if (empty($scoes->version)) {
904 if (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/",$metadata['tagData'],$matches)) {
905 $scoes->version = 'SCORM_'.$matches[count($matches)-1];
906 } else {
907 $scoes->version = 'SCORM_1.2';
913 break;
914 case 'MANIFEST':
915 $manifest = addslashes($block['attrs']['IDENTIFIER']);
916 $organization = '';
917 $resources = array();
918 $resources = scorm_get_resources($block['children']);
919 $scoes = scorm_get_manifest($block['children'],$scoes);
920 if (count($scoes->elements) <= 0) {
921 foreach ($resources as $item => $resource) {
922 if (!empty($resource['HREF'])) {
923 $sco = new stdClass();
924 $sco->identifier = $item;
925 $sco->title = $item;
926 $sco->parent = '/';
927 $sco->launch = addslashes($resource['HREF']);
928 $sco->scormtype = addslashes($resource['ADLCP:SCORMTYPE']);
929 $scoes->elements[$manifest][$organization][$item] = $sco;
933 break;
934 case 'ORGANIZATIONS':
935 if (!isset($scoes->defaultorg)) {
936 $scoes->defaultorg = addslashes($block['attrs']['DEFAULT']);
938 $scoes = scorm_get_manifest($block['children'],$scoes);
939 break;
940 case 'ORGANIZATION':
941 $identifier = addslashes($block['attrs']['IDENTIFIER']);
942 $organization = '';
943 $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
944 $scoes->elements[$manifest][$organization][$identifier]->parent = '/';
945 $scoes->elements[$manifest][$organization][$identifier]->launch = '';
946 $scoes->elements[$manifest][$organization][$identifier]->scormtype = '';
948 $parents = array();
949 $parent = new stdClass();
950 $parent->identifier = $identifier;
951 $parent->organization = $organization;
952 array_push($parents, $parent);
953 $organization = $identifier;
955 $scoes = scorm_get_manifest($block['children'],$scoes);
957 array_pop($parents);
958 break;
959 case 'ITEM':
960 $parent = array_pop($parents);
961 array_push($parents, $parent);
963 $identifier = addslashes($block['attrs']['IDENTIFIER']);
964 $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
965 $scoes->elements[$manifest][$organization][$identifier]->parent = $parent->identifier;
966 if (!isset($block['attrs']['ISVISIBLE'])) {
967 $block['attrs']['ISVISIBLE'] = 'true';
969 $scoes->elements[$manifest][$organization][$identifier]->isvisible = addslashes($block['attrs']['ISVISIBLE']);
970 if (!isset($block['attrs']['PARAMETERS'])) {
971 $block['attrs']['PARAMETERS'] = '';
973 $scoes->elements[$manifest][$organization][$identifier]->parameters = addslashes($block['attrs']['PARAMETERS']);
974 if (!isset($block['attrs']['IDENTIFIERREF'])) {
975 $scoes->elements[$manifest][$organization][$identifier]->launch = '';
976 $scoes->elements[$manifest][$organization][$identifier]->scormtype = 'asset';
977 } else {
978 $idref = addslashes($block['attrs']['IDENTIFIERREF']);
979 $base = '';
980 if (isset($resources[$idref]['XML:BASE'])) {
981 $base = $resources[$idref]['XML:BASE'];
983 $scoes->elements[$manifest][$organization][$identifier]->launch = addslashes($base.$resources[$idref]['HREF']);
984 if (empty($resources[$idref]['ADLCP:SCORMTYPE'])) {
985 $resources[$idref]['ADLCP:SCORMTYPE'] = 'asset';
987 $scoes->elements[$manifest][$organization][$identifier]->scormtype = addslashes($resources[$idref]['ADLCP:SCORMTYPE']);
990 $parent = new stdClass();
991 $parent->identifier = $identifier;
992 $parent->organization = $organization;
993 array_push($parents, $parent);
995 $scoes = scorm_get_manifest($block['children'],$scoes);
997 array_pop($parents);
998 break;
999 case 'TITLE':
1000 $parent = array_pop($parents);
1001 array_push($parents, $parent);
1002 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->title = addslashes($block['tagData']);
1003 break;
1004 case 'ADLCP:PREREQUISITES':
1005 if ($block['attrs']['TYPE'] == 'aicc_script') {
1006 $parent = array_pop($parents);
1007 array_push($parents, $parent);
1008 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->prerequisites = addslashes($block['tagData']);
1010 break;
1011 case 'ADLCP:MAXTIMEALLOWED':
1012 $parent = array_pop($parents);
1013 array_push($parents, $parent);
1014 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->maxtimeallowed = addslashes($block['tagData']);
1015 break;
1016 case 'ADLCP:TIMELIMITACTION':
1017 $parent = array_pop($parents);
1018 array_push($parents, $parent);
1019 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->timelimitaction = addslashes($block['tagData']);
1020 break;
1021 case 'ADLCP:DATAFROMLMS':
1022 $parent = array_pop($parents);
1023 array_push($parents, $parent);
1024 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->datafromlms = addslashes($block['tagData']);
1025 break;
1026 case 'ADLCP:MASTERYSCORE':
1027 $parent = array_pop($parents);
1028 array_push($parents, $parent);
1029 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->masteryscore = addslashes($block['tagData']);
1030 break;
1035 return $scoes;
1038 function scorm_parse_scorm($pkgdir,$scormid) {
1039 global $CFG;
1041 $launch = 0;
1042 $manifestfile = $pkgdir.'/imsmanifest.xml';
1044 if (is_file($manifestfile)) {
1046 $xmlstring = file_get_contents($manifestfile);
1047 $objXML = new xml2Array();
1048 $manifests = $objXML->parse($xmlstring);
1050 $scoes = new stdClass();
1051 $scoes->version = '';
1052 $scoes = scorm_get_manifest($manifests,$scoes);
1054 if (count($scoes->elements) > 0) {
1055 foreach ($scoes->elements as $manifest => $organizations) {
1056 foreach ($organizations as $organization => $items) {
1057 foreach ($items as $identifier => $item) {
1058 $item->scorm = $scormid;
1059 $item->manifest = $manifest;
1060 $item->organization = $organization;
1061 $id = insert_record('scorm_scoes',$item);
1063 if (($launch == 0) && ((empty($scoes->defaultorg)) || ($scoes->defaultorg == $identifier))) {
1064 $launch = $id;
1069 set_field('scorm','version',$scoes->version,'id',$scormid);
1073 return $launch;
1076 function scorm_course_format_display($user,$course) {
1077 global $CFG;
1079 $strupdate = get_string('update');
1080 $strmodule = get_string('modulename','scorm');
1082 echo '<div class="mod-scorm">';
1083 if ($scorms = get_all_instances_in_course('scorm', $course)) {
1084 // The module SCORM activity with the least id is the course
1085 $scorm = current($scorms);
1086 if (! $cm = get_coursemodule_from_instance('scorm', $scorm->id, $course->id)) {
1087 error("Course Module ID was incorrect");
1089 $colspan = '';
1090 $headertext = '<table width="100%"><tr><td class="title">'.get_string('name').': <b>'.format_string($scorm->name).'</b>';
1091 if (isteacher($course->id, $user->id, true)) {
1092 if (isediting($course->id)) {
1093 // Display update icon
1094 $path = $CFG->wwwroot.'/course';
1095 $headertext .= '<span class="commands">'.
1096 '<a title="'.$strupdate.'" href="'.$path.'/mod.php?update='.$cm->id.'&amp;sesskey='.sesskey().'">'.
1097 '<img src="'.$CFG->pixpath.'/t/edit.gif" hspace="2" height="11" width="11" border="0" alt="'.$strupdate.'" /></a></span>';
1099 $headertext .= '</td>';
1100 // Display report link
1101 $trackedusers = get_record('scorm_scoes_track', 'scormid', $scorm->id, '', '', '', '', 'count(distinct(userid)) as c');
1102 if ($trackedusers->c > 0) {
1103 $headertext .= '<td class="reportlink">'.
1104 '<a target="'.$CFG->framename.'" href="'.$CFG->wwwroot.'/mod/scorm/report.php?id='.$cm->id.'">'.
1105 get_string('viewallreports','scorm',$trackedusers->c).'</a>';
1106 } else {
1107 $headertext .= '<td class="reportlink">'.get_string('noreports','scorm');
1109 $colspan = ' colspan="2"';
1111 $headertext .= '</td></tr><tr><td'.$colspan.'>'.format_text(get_string('summary').':<br />'.$scorm->summary).'</td></tr></table>';
1112 print_simple_box($headertext,'','100%');
1113 scorm_view_display($user, $scorm, 'view.php?id='.$course->id, $cm, '100%');
1114 } else {
1115 if (isteacheredit($course->id, $user->id)) {
1116 // Create a new activity
1117 redirect('mod.php?id='.$course->id.'&amp;section=0&sesskey='.sesskey().'&amp;add=scorm');
1118 } else {
1119 notify('Could not find a scorm course here');
1122 echo '</div>';
1125 function scorm_view_display ($user, $scorm, $action, $cm, $blockwidth='') {
1126 global $CFG;
1128 $organization = optional_param('organization', '', PARAM_INT);
1130 print_simple_box_start('center',$blockwidth);
1132 <div class="structurehead"><?php print_string('coursestruct','scorm') ?></div>
1133 <?php
1134 if (empty($organization)) {
1135 $organization = $scorm->launch;
1137 if ($orgs = get_records_select_menu('scorm_scoes',"scorm='$scorm->id' AND organization='' AND launch=''",'id','id,title')) {
1138 if (count($orgs) > 1) {
1140 <div class='center'>
1141 <?php print_string('organizations','scorm') ?>
1142 <form name='changeorg' method='post' action='<?php echo $action ?>'>
1143 <?php choose_from_menu($orgs, 'organization', "$organization", '','submit()') ?>
1144 </form>
1145 </div>
1146 <?php
1149 $orgidentifier = '';
1150 if ($org = get_record('scorm_scoes','id',$organization)) {
1151 if (($org->organization == '') && ($org->launch == '')) {
1152 $orgidentifier = $org->identifier;
1153 } else {
1154 $orgidentifier = $org->organization;
1157 $result = scorm_get_toc($user,$scorm,'structlist',$orgidentifier);
1158 $incomplete = $result->incomplete;
1159 echo $result->toc;
1160 print_simple_box_end();
1162 <div class="center">
1163 <form name="theform" method="post" action="<?php echo $CFG->wwwroot ?>/mod/scorm/player.php?id=<?php echo $cm->id ?>"<?php echo $scorm->popup == 1?' target="newwin"':'' ?>>
1164 <?php
1165 if ($scorm->hidebrowse == 0) {
1166 print_string("mode","scorm");
1167 echo ': <input type="radio" id="b" name="mode" value="browse" /><label for="b">'.get_string('browse','scorm').'</label>'."\n";
1168 if ($incomplete === true) {
1169 echo '<input type="radio" id="n" name="mode" value="normal" checked="checked" /><label for="n">'.get_string('normal','scorm')."</label>\n";
1170 } else {
1171 echo '<input type="radio" id="r" name="mode" value="review" checked="checked" /><label for="r">'.get_string('review','scorm')."</label>\n";
1173 } else {
1174 if ($incomplete === true) {
1175 echo '<input type="hidden" name="mode" value="normal" />'."\n";
1176 } else {
1177 echo '<input type="hidden" name="mode" value="review" />'."\n";
1180 if (($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) {
1182 <br />
1183 <input type="checkbox" id="a" name="newattempt" />
1184 <label for="a"><?php print_string('newattempt','scorm') ?></label>
1185 <?php
1188 <br />
1189 <input type="hidden" name="scoid" />
1190 <input type="hidden" name="currentorg" value="<?php echo $orgidentifier ?>" />
1191 <input type="submit" value="<? print_string('entercourse','scorm') ?>" />
1192 </form>
1193 </div>
1194 <?php
1198 function scorm_repeater($what, $times) {
1199 if ($times <= 0) {
1200 return null;
1202 $return = '';
1203 for ($i=0; $i<$times;$i++) {
1204 $return .= $what;
1206 return $return;
1209 /* Usage
1210 Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML;
1212 $objXML = new xml2Array();
1213 $arrOutput = $objXML->parse($strYourXML);
1214 print_r($arrOutput); //print it out, or do whatever!
1217 class xml2Array {
1219 var $arrOutput = array();
1220 var $resParser;
1221 var $strXmlData;
1224 * Convert a utf-8 string to html entities
1226 * @param string $str The UTF-8 string
1227 * @return string
1229 function utf8_to_entities($str) {
1230 $entities = '';
1231 $values = array();
1232 $lookingfor = 1;
1234 for ($i = 0; $i < strlen($str); $i++) {
1235 $thisvalue = ord($str[$i]);
1236 if ($thisvalue < 128) {
1237 $entities .= $str[$i]; // Leave ASCII chars unchanged
1238 } else {
1239 if (count($values) == 0) {
1240 $lookingfor = ($thisvalue < 224) ? 2 : 3;
1242 $values[] = $thisvalue;
1243 if (count($values) == $lookingfor) {
1244 $number = ($lookingfor == 3) ?
1245 (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64):
1246 (($values[0] % 32) * 64) + ($values[1] % 64);
1247 $entities .= '&#' . $number . ';';
1248 $values = array();
1249 $lookingfor = 1;
1253 return $entities;
1257 * Parse an XML text string and create an array tree that rapresent the XML structure
1259 * @param string $strInputXML The XML string
1260 * @return array
1262 function parse($strInputXML) {
1263 $this->resParser = xml_parser_create ('UTF-8');
1264 xml_set_object($this->resParser,$this);
1265 xml_set_element_handler($this->resParser, "tagOpen", "tagClosed");
1267 xml_set_character_data_handler($this->resParser, "tagData");
1269 $this->strXmlData = xml_parse($this->resParser,$strInputXML );
1270 if(!$this->strXmlData) {
1271 die(sprintf("XML error: %s at line %d",
1272 xml_error_string(xml_get_error_code($this->resParser)),
1273 xml_get_current_line_number($this->resParser)));
1276 xml_parser_free($this->resParser);
1278 return $this->arrOutput;
1281 function tagOpen($parser, $name, $attrs) {
1282 $tag=array("name"=>$name,"attrs"=>$attrs);
1283 array_push($this->arrOutput,$tag);
1286 function tagData($parser, $tagData) {
1287 if(trim($tagData)) {
1288 if(isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) {
1289 $this->arrOutput[count($this->arrOutput)-1]['tagData'] .= $this->utf8_to_entities($tagData);
1290 } else {
1291 $this->arrOutput[count($this->arrOutput)-1]['tagData'] = $this->utf8_to_entities($tagData);
1296 function tagClosed($parser, $name) {
1297 $this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this->arrOutput)-1];
1298 array_pop($this->arrOutput);