3 function scorm_add_time($a, $b) {
4 $aes = explode(':',$a);
5 $bes = explode(':',$b);
6 $aseconds = explode('.',$aes[2]);
7 $bseconds = explode('.',$bes[2]);
11 if (count($aseconds) > 1) {
12 $acents = $aseconds[1];
15 if (count($bseconds) > 1) {
16 $bcents = $bseconds[1];
18 $cents = $acents + $bcents;
19 $change = floor($cents / 100);
20 $cents = $cents - ($change * 100);
21 if (floor($cents) < 10) {
25 $secs = $aseconds[0] + $bseconds[0] + $change; //Seconds
26 $change = floor($secs / 60);
27 $secs = $secs - ($change * 60);
28 if (floor($secs) < 10) {
32 $mins = $aes[1] + $bes[1] + $change; //Minutes
33 $change = floor($mins / 60);
34 $mins = $mins - ($change * 60);
39 $hours = $aes[0] + $bes[0] + $change; //Hours
41 $hours = '0' . $hours;
45 return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
47 return $hours . ":" . $mins . ":" . $secs;
52 * Take the header row of an AICC definition file
53 * and returns sequence of columns and a pointer to
54 * the sco identifier column.
56 * @param string $row AICC header row
57 * @param string $mastername AICC sco identifier column
60 function scorm_get_aicc_columns($row,$mastername='system_id') {
61 $tok = strtok(strtolower($row),"\",\n\r");
62 $result = new stdClass();
63 $result->columns = array();
67 $result->columns[] = $tok;
68 if ($tok == $mastername) {
69 $result->mastercol = $i;
73 $tok = strtok("\",\n\r");
79 * Given a colums array return a string containing the regular
80 * expression to match the columns in a text row.
82 * @param array $column The header columns
83 * @param string $remodule The regular expression module for a single column
86 function scorm_forge_cols_regexp($columns,$remodule='(".*")?,') {
88 foreach ($columns as $column) {
91 $regexp = substr($regexp,0,-1) . '/';
96 function scorm_parse_aicc($scorm) {
99 if (!isset($scorm->cmid)) {
100 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
101 $scorm->cmid = $cm->id;
103 $context = get_context_instance(CONTEXT_MODULE, $scorm->cmid);
105 $fs = get_file_storage();
107 $files = $fs->get_area_files($context->id, 'mod_scorm', 'content', 0, '', false);
113 $extaiccfiles = array('crs','des','au','cst','ort','pre','cmp');
115 foreach ($files as $file) {
116 $filename = $file->get_filename();
117 $ext = substr($filename,strrpos($filename,'.'));
118 $extension = strtolower(substr($ext,1));
119 if (in_array($extension,$extaiccfiles)) {
120 $id = strtolower(basename($filename,$ext));
121 $ids[$id]->$extension = $file;
125 foreach ($ids as $courseid => $id) {
126 if (isset($id->crs)) {
127 $contents = $id->crs->get_content();
128 $rows = explode("\r\n", $contents);
129 if (is_array($rows)) {
130 foreach ($rows as $row) {
131 if (preg_match("/^(.+)=(.+)$/",$row,$matches)) {
132 switch (strtolower(trim($matches[1]))) {
134 $courses[$courseid]->id = trim($matches[2]);
137 $courses[$courseid]->title = trim($matches[2]);
140 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
147 if (isset($id->des)) {
148 $contents = $id->des->get_content();
149 $rows = explode("\r\n", $contents);
150 $columns = scorm_get_aicc_columns($rows[0]);
151 $regexp = scorm_forge_cols_regexp($columns->columns);
152 for ($i=1;$i<count($rows);$i++) {
153 if (preg_match($regexp,$rows[$i],$matches)) {
154 for ($j=0;$j<count($columns->columns);$j++) {
155 $column = $columns->columns[$j];
156 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
161 if (isset($id->au)) {
162 $contents = $id->au->get_content();
163 $rows = explode("\r\n", $contents);
164 $columns = scorm_get_aicc_columns($rows[0]);
165 $regexp = scorm_forge_cols_regexp($columns->columns);
166 for ($i=1;$i<count($rows);$i++) {
167 if (preg_match($regexp,$rows[$i],$matches)) {
168 for ($j=0;$j<count($columns->columns);$j++) {
169 $column = $columns->columns[$j];
170 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
175 if (isset($id->cst)) {
176 $contents = $id->cst->get_content();
177 $rows = explode("\r\n", $contents);
178 $columns = scorm_get_aicc_columns($rows[0],'block');
179 $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
180 for ($i=1;$i<count($rows);$i++) {
181 if (preg_match($regexp,$rows[$i],$matches)) {
182 for ($j=0;$j<count($columns->columns);$j++) {
183 if ($j != $columns->mastercol) {
184 $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
190 if (isset($id->ort)) {
191 $contents = $id->ort->get_content();
192 $rows = explode("\r\n", $contents);
193 $columns = scorm_get_aicc_columns($rows[0],'course_element');
194 $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
195 for ($i=1;$i<count($rows);$i++) {
196 if (preg_match($regexp,$rows[$i],$matches)) {
197 for ($j=0;$j<count($matches)-1;$j++) {
198 if ($j != $columns->mastercol) {
199 $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
205 if (isset($id->pre)) {
206 $contents = $id->pre->get_content();
207 $rows = explode("\r\n", $contents);
208 $columns = scorm_get_aicc_columns($rows[0],'structure_element');
209 $regexp = scorm_forge_cols_regexp($columns->columns,'(.+),');
210 for ($i=1;$i<count($rows);$i++) {
211 if (preg_match($regexp,$rows[$i],$matches)) {
212 $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]),1,-1);
216 if (isset($id->cmp)) {
217 $contents = $id->cmp->get_content();
218 $rows = explode("\r\n", $contents);
223 $oldscoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
226 if (isset($courses)) {
227 foreach ($courses as $course) {
228 $sco = new stdClass();
229 $sco->identifier = $course->id;
230 $sco->scorm = $scorm->id;
231 $sco->organization = '';
232 $sco->title = $course->title;
235 $sco->scormtype = '';
238 if ($ss = $DB->get_record('scorm_scoes', array('scorm'=>$scorm->id,'identifier'=>$sco->identifier))) {
240 $DB->update_record('scorm_scoes',$sco);
241 unset($oldscoes[$id]);
243 $id = $DB->insert_record('scorm_scoes',$sco);
249 if (isset($course->elements)) {
250 foreach($course->elements as $element) {
252 $sco->identifier = $element->system_id;
253 $sco->scorm = $scorm->id;
254 $sco->organization = $course->id;
255 $sco->title = $element->title;
257 if (!isset($element->parent) || strtolower($element->parent) == 'root') {
260 $sco->parent = $element->parent;
262 if (isset($element->file_name)) {
263 $sco->launch = $element->file_name;
264 $sco->scormtype = 'sco';
268 if ($oldscoid = scorm_array_search('identifier',$sco->identifier,$oldscoes)) {
269 $sco->id = $oldscoid;
270 $DB->update_record('scorm_scoes',$sco);
272 $DB->delete_records('scorm_scoes_data', array('scoid'=>$oldscoid));
273 unset($oldscoes[$oldscoid]);
275 $id = $DB->insert_record('scorm_scoes',$sco);
278 $scodata = new stdClass();
279 $scodata->scoid = $id;
280 if (isset($element->web_launch)) {
281 $scodata->name = 'parameters';
282 $scodata->value = $element->web_launch;
283 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
285 if (isset($element->prerequisites)) {
286 $scodata->name = 'prerequisites';
287 $scodata->value = $element->prerequisites;
288 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
290 if (isset($element->max_time_allowed)) {
291 $scodata->name = 'max_time_allowed';
292 $scodata->value = $element->max_time_allowed;
293 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
295 if (isset($element->time_limit_action)) {
296 $scodata->name = 'time_limit_action';
297 $scodata->value = $element->time_limit_action;
298 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
300 if (isset($element->mastery_score)) {
301 $scodata->name = 'mastery_score';
302 $scodata->value = $element->mastery_score;
303 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
305 if (isset($element->core_vendor)) {
306 $scodata->name = 'datafromlms';
307 $scodata->value = preg_replace('/<cr>/i', "\r\n", $element->core_vendor);
308 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
319 if (!empty($oldscoes)) {
320 foreach($oldscoes as $oldsco) {
321 $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
322 $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
326 $scorm->version = 'AICC';
328 $scorm->launch = $launch;
333 function scorm_get_toc($user,$scorm,$toclink=TOCJSLINK,$currentorg='',$scoid='',$mode='normal',$attempt='',$play=false, $tocheader=false) {
334 global $CFG, $DB, $PAGE, $OUTPUT;
336 $strexpand = get_string('expcoll','scorm');
338 if ($mode == 'browse') {
339 $modestr = '&mode='.$mode;
342 $result = new stdClass();
343 //$result->toc = "<ul id='s0' class='$liststyle'>\n";
345 $result->toc = '<div id="scorm_layout">';
346 $result->toc .= '<div id="scorm_toc">';
347 $result->toc .= '<div id="scorm_tree">';
349 $result->toc .= '<ul>';
351 $result->prerequisites = true;
355 // Get the current organization infos
357 if (!empty($currentorg)) {
358 if (($organizationtitle = $DB->get_field('scorm_scoes','title', array('scorm'=>$scorm->id,'identifier'=>$currentorg))) != '') {
360 $result->toctitle = "$organizationtitle";
363 $result->toc .= "\t<li>$organizationtitle</li>\n";
365 $tocmenus[] = $organizationtitle;
369 // If not specified retrieve the last attempt number
371 if (empty($attempt)) {
372 $attempt = scorm_get_last_attempt($scorm->id, $user->id);
374 $result->attemptleft = $scorm->maxattempt - $attempt;
375 if ($scoes = scorm_get_scoes($scorm->id, $currentorg)){
377 // Retrieve user tracking data for each learning object
379 $usertracks = array();
380 foreach ($scoes as $sco) {
381 if (!empty($sco->launch)) {
385 if ($usertrack = scorm_get_tracks($sco->id,$user->id,$attempt)) {
386 if ($usertrack->status == '') {
387 $usertrack->status = 'notattempted';
389 $usertracks[$sco->identifier] = $usertrack;
399 $parents[$level]='/';
401 foreach ($scoes as $pos => $sco) {
403 $sco->title = $sco->title;
404 if (!isset($sco->isvisible) || (isset($sco->isvisible) && ($sco->isvisible == 'true'))) {
407 if ($parents[$level]!=$sco->parent) {
408 if ($newlevel = array_search($sco->parent,$parents)) {
409 for ($i=0; $i<($level-$newlevel); $i++) {
410 $result->toc .= "\t\t</li></ul></li>\n";
416 while (($i > 0) && ($parents[$level] != $sco->parent)) {
417 $closelist .= "\t\t</li></ul></li>\n";
420 if (($i == 0) && ($sco->parent != $currentorg)) {
422 if (isset($_COOKIE['hide:SCORMitem'.$sco->id])) {
423 $style = ' style="display: none;"';
425 //$result->toc .= "\t\t<li><ul id='s$sublist' class='$liststyle'$style>\n";
426 $result->toc .= "\t\t<ul>\n";
429 $result->toc .= $closelist;
432 $parents[$level]=$sco->parent;
436 $result->toc .= "\t\t<li>";
438 if (isset($scoes[$pos+1])) {
439 $nextsco = $scoes[$pos+1];
443 $nextisvisible = false;
444 if (!isset($nextsco->isvisible) || (isset($nextsco->isvisible) && ($nextsco->isvisible == 'true'))) {
445 $nextisvisible = true;
447 if ($nextisvisible && ($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
450 if (isset($_COOKIE['hide:SCORMitem'.$nextsco->id])) {
453 //$result->toc .= '<a href="javascript:expandCollide(\'img'.$sublist.'\',\'s'.$sublist.'\','.$nextsco->id.');"><img id="img'.$sublist.'" src="'.$OUTPUT->pix_url($icon, 'scorm').'" alt="'.$strexpand.'" title="'.$strexpand.'"/></a>';
454 } else if ($isvisible) {
455 //$result->toc .= '<img src="'.$OUTPUT->pix_url('spacer', 'scorm').'" alt="" />';
457 if (empty($sco->title)) {
458 $sco->title = $sco->identifier;
460 if (!empty($sco->launch)) {
465 if (empty($scoid) && ($mode != 'normal')) {
468 if (isset($usertracks[$sco->identifier])) {
469 $usertrack = $usertracks[$sco->identifier];
470 $strstatus = get_string($usertrack->status,'scorm');
471 if ($sco->scormtype == 'sco') {
472 $statusicon = '<img src="'.$OUTPUT->pix_url($usertrack->status, 'scorm').'" alt="'.$strstatus.'" title="'.$strstatus.'" />';
474 $statusicon = '<img src="'.$OUTPUT->pix_url('assetc', 'scorm').'" alt="'.get_string('assetlaunched','scorm').'" title="'.get_string('assetlaunched','scorm').'" />';
477 if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) {
479 if ($play && empty($scoid)) {
483 if ($usertrack->score_raw != '') {
484 $score = '('.get_string('score','scorm').': '.$usertrack->score_raw.')';
486 $strsuspended = get_string('suspended','scorm');
487 if ($incomplete && isset($usertrack->{'cmi.core.exit'}) && ($usertrack->{'cmi.core.exit'} == 'suspend')) {
488 $statusicon = '<img src="'.$OUTPUT->pix_url('suspend', 'scorm').'" alt="'.$strstatus.' - '.$strsuspended.'" title="'.$strstatus.' - '.$strsuspended.'" />';
491 if ($play && empty($scoid)) {
495 if ($sco->scormtype == 'sco') {
496 $statusicon = '<img src="'.$OUTPUT->pix_url('notattempted', 'scorm').'" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
498 $statusicon = '<img src="'.$OUTPUT->pix_url('asset', 'scorm').'" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
501 if ($sco->id == $scoid) {
505 $shownext = isset($sco->next) ? $sco->next : 0;
506 $showprev = isset($sco->previous) ? $sco->previous : 0;
509 if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1) && ($nextsco!==false) && (!$findnext)) {
510 if (!empty($sco->launch)) {
514 if (empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites,$usertracks)) {
515 if ($sco->id == $scoid) {
516 $result->prerequisites = true;
519 $thisscoidstr = '&scoid='.$sco->id;
520 $link = 'a='.$scorm->id.$thisscoidstr.'¤torg='.$currentorg.$modestr.'&attempt='.$attempt;
522 if ($toclink == TOCFULLURL) { //display toc with urls for structure page
523 $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&currentorg='.$currentorg.$modestr.'&scoid='.$sco->id;
524 $result->toc .= $statusicon.' <a href="'.$url.'">'.format_string($sco->title).'</a>'.$score."\n";
527 $result->toc .= '<a title="'.$link.'">'.$statusicon.' '.format_string($sco->title).' '.$score.'</a>';
529 $result->toc .= '<span>'.$statusicon.' '.format_string($sco->title).'</span>';
532 $tocmenus[$sco->id] = scorm_repeater('−',$level) . '>' . format_string($sco->title);
534 if ($sco->id == $scoid) {
535 $result->prerequisites = false;
538 // should be disabled
539 $result->toc .= '<span>'.$statusicon.' '.format_string($sco->title).'</span>';
542 $result->toc .= $statusicon.' '.format_string($sco->title)."\n";
545 if (($nextsco === false) || $nextsco->parent == $sco->parent) {
546 $result->toc .= '</li>';
550 $result->toc .= ' '.format_string($sco->title)."</li>\n";
552 if (($nextsco !== false) && ($nextid == 0) && ($findnext)) {
553 if (!empty($nextsco->launch)) {
554 $nextid = $nextsco->id;
558 for ($i=0;$i<$level;$i++) {
559 $result->toc .= "\t\t</ul></li>\n";
563 $sco = scorm_get_sco($scoid);
564 $sco->previd = $previd;
565 $sco->nextid = $nextid;
567 $result->incomplete = $incomplete;
569 $result->incomplete = $incomplete;
572 $result->toc .= '</ul>';
576 $result->toc .= '</div></div></div>';
577 $result->toc .= '<div id="scorm_navpanel"></div>';
581 if ($scorm->hidetoc == 0) {
582 $result->toc .= html_writer::script(js_writer::set_variable('scormdata', array(
583 'plusicon' => $OUTPUT->pix_url('plus', 'scorm'),
584 'minusicon' => $OUTPUT->pix_url('minus', 'scorm'))));
585 $result->toc .= html_writer::script('', $CFG->wwwroot.'/lib/cookies.js');
586 $result->toc .= html_writer::script('', $CFG->wwwroot.'/mod/scorm/datamodels/scorm_datamodels.js');
589 $url = new moodle_url('/mod/scorm/player.php?a='.$scorm->id.'¤torg='.$currentorg.$modestr);
590 $result->tocmenu = $OUTPUT->single_select($url, 'scoid', $tocmenus, $sco->id, null, "tocmenu");