MDL-7323 AICC Import does not handle Core_Vendor - applied patch from Keith Starling...
[moodle.git] / mod / scorm / datamodels / aicclib.php
1 <?php // $Id$
2 function scorm_add_time($a, $b) {
3     $aes = explode(':',$a);
4     $bes = explode(':',$b);
5     $aseconds = explode('.',$aes[2]);
6     $bseconds = explode('.',$bes[2]);
7     $change = 0;
9     $acents = 0;  //Cents
10     if (count($aseconds) > 1) {
11         $acents = $aseconds[1];
12     }
13     $bcents = 0;
14     if (count($bseconds) > 1) {
15         $bcents = $bseconds[1];
16     }
17     $cents = $acents + $bcents;
18     $change = floor($cents / 100);
19     $cents = $cents - ($change * 100);
20     if (floor($cents) < 10) {
21         $cents = '0'. $cents;
22     }
24     $secs = $aseconds[0] + $bseconds[0] + $change;  //Seconds
25     $change = floor($secs / 60);
26     $secs = $secs - ($change * 60);
27     if (floor($secs) < 10) {
28         $secs = '0'. $secs;
29     }
31     $mins = $aes[1] + $bes[1] + $change;   //Minutes
32     $change = floor($mins / 60);
33     $mins = $mins - ($change * 60);
34     if ($mins < 10) {
35         $mins = '0' .  $mins;
36     }
38     $hours = $aes[0] + $bes[0] + $change;  //Hours
39     if ($hours < 10) {
40         $hours = '0' . $hours;
41     }
43     if ($cents != '0') {
44         return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
45     } else {
46         return $hours . ":" . $mins . ":" . $secs;
47     }
48 }
50 /**
51 * Take the header row of an AICC definition file
52 * and returns sequence of columns and a pointer to
53 * the sco identifier column.
54 *
55 * @param string $row AICC header row
56 * @param string $mastername AICC sco identifier column
57 * @return mixed
58 */
59 function scorm_get_aicc_columns($row,$mastername='system_id') {
60     $tok = strtok(strtolower($row),"\",\n\r");
61     $result->columns = array();
62     $i=0;
63     while ($tok) {
64         if ($tok !='') {
65             $result->columns[] = $tok;
66             if ($tok == $mastername) {
67                 $result->mastercol = $i;
68             }
69             $i++;
70         }
71         $tok = strtok("\",\n\r");
72     }
73     return $result;
74 }
76 /**
77 * Given a colums array return a string containing the regular
78 * expression to match the columns in a text row.
79 *
80 * @param array $column The header columns
81 * @param string $remodule The regular expression module for a single column
82 * @return string
83 */
84 function scorm_forge_cols_regexp($columns,$remodule='(".*")?,') {
85     $regexp = '/^';
86     foreach ($columns as $column) {
87         $regexp .= $remodule;
88     }
89     $regexp = substr($regexp,0,-1) . '/';
90     return $regexp;
91 }
93 function scorm_parse_aicc($pkgdir,$scormid) {
94     global $DB;
96     $version = 'AICC';
97     $ids = array();
98     $courses = array();
99     $extaiccfiles = array('crs','des','au','cst','ort','pre','cmp');
100     if ($handle = opendir($pkgdir)) {
101         while (($file = readdir($handle)) !== false) {
102             if ($file[0] != '.') {
103                 $ext = substr($file,strrpos($file,'.'));
104                 $extension = strtolower(substr($ext,1));
105                 if (in_array($extension,$extaiccfiles)) {
106                     $id = strtolower(basename($file,$ext));
107                     $ids[$id]->$extension = $file;
108                 }
109             }
110         }
111         closedir($handle);
112     }
113     foreach ($ids as $courseid => $id) {
114         if (isset($id->crs)) {
115             if (is_file($pkgdir.'/'.$id->crs)) {
116                 $rows = file($pkgdir.'/'.$id->crs);
117                 foreach ($rows as $row) {
118                     if (preg_match("/^(.+)=(.+)$/",$row,$matches)) {
119                         switch (strtolower(trim($matches[1]))) {
120                             case 'course_id':
121                                 $courses[$courseid]->id = trim($matches[2]);
122                             break;
123                             case 'course_title':
124                                 $courses[$courseid]->title = trim($matches[2]);
125                             break;
126                             case 'version':
127                                 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
128                             break;
129                         }
130                     }
131                 }
132             }
133         }
134         if (isset($id->des)) {
135             $rows = file($pkgdir.'/'.$id->des);
136             $columns = scorm_get_aicc_columns($rows[0]);
137             $regexp = scorm_forge_cols_regexp($columns->columns);
138             for ($i=1;$i<count($rows);$i++) {
139                 if (preg_match($regexp,$rows[$i],$matches)) {
140                     for ($j=0;$j<count($columns->columns);$j++) {
141                         $column = $columns->columns[$j];
142                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
143                     }
144                 }
145             }
146         }
147         if (isset($id->au)) {
148             $rows = file($pkgdir.'/'.$id->au);
149             $columns = scorm_get_aicc_columns($rows[0]);
150             $regexp = scorm_forge_cols_regexp($columns->columns);
151             for ($i=1;$i<count($rows);$i++) {
152                 if (preg_match($regexp,$rows[$i],$matches)) {
153                     for ($j=0;$j<count($columns->columns);$j++) {
154                         $column = $columns->columns[$j];
155                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]),1,-1)]->$column = substr(trim($matches[$j+1]),1,-1);
156                     }
157                 }
158             }
159         }
160         if (isset($id->cst)) {
161             $rows = file($pkgdir.'/'.$id->cst);
162             $columns = scorm_get_aicc_columns($rows[0],'block');
163             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
164             for ($i=1;$i<count($rows);$i++) {
165                 if (preg_match($regexp,$rows[$i],$matches)) {
166                     for ($j=0;$j<count($columns->columns);$j++) {
167                         if ($j != $columns->mastercol) {
168                             $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
169                         }
170                     }
171                 }
172             }
173         }
174         if (isset($id->ort)) {
175             $rows = file($pkgdir.'/'.$id->ort);
176             $columns = scorm_get_aicc_columns($rows[0],'course_element');
177             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+)?,');
178             for ($i=1;$i<count($rows);$i++) {
179                 if (preg_match($regexp,$rows[$i],$matches)) {
180                     for ($j=0;$j<count($matches)-1;$j++) {
181                         if ($j != $columns->mastercol) {
182                             $courses[$courseid]->elements[substr(trim($matches[$j+1]),1,-1)]->parent = substr(trim($matches[$columns->mastercol+1]),1,-1);
183                         }
184                     }
185                 }
186             }
187         }
188         if (isset($id->pre)) {
189             $rows = file($pkgdir.'/'.$id->pre);
190             $columns = scorm_get_aicc_columns($rows[0],'structure_element');
191             $regexp = scorm_forge_cols_regexp($columns->columns,'(.+),');
192             for ($i=1;$i<count($rows);$i++) {
193                 if (preg_match($regexp,$rows[$i],$matches)) {
194                     $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]),1,-1);
195                 }
196             }
197         }
198         if (isset($id->cmp)) {
199             $rows = file($pkgdir.'/'.$id->cmp);
200         }
201     }
202     //print_r($courses);
204     $oldscoes = $DB->get_records('scorm_scoes', array('scorm'=>$scormid));
205     
206     $launch = 0;
207     if (isset($courses)) {
208         foreach ($courses as $course) {
209             $sco = new object();
210             $sco->identifier = $course->id;
211             $sco->scorm = $scormid;
212             $sco->organization = '';
213             $sco->title = $course->title;
214             $sco->parent = '/';
215             $sco->launch = '';
216             $sco->scormtype = '';
218             //print_r($sco);
219             if ($ss = $DB->get_record('scorm_scoes', array('scorm'=>$scormid,'identifier'=>$sco->identifier))) {
220                 $id = $ss->id;
221                 $DB->update_record('scorm_scoes',$sco);
222                 unset($oldscoes[$id]);
223             } else {
224                 $id = $DB->insert_record('scorm_scoes',$sco);
225             }
227             if ($launch == 0) {
228                 $launch = $id;
229             }
230             if (isset($course->elements)) {
231                 foreach($course->elements as $element) {
232                     unset($sco);
233                     $sco->identifier = $element->system_id;
234                     $sco->scorm = $scormid;
235                     $sco->organization = $course->id;
236                     $sco->title = $element->title;
237                  
238                     if (!isset($element->parent) || strtolower($element->parent) == 'root') {
239                         $sco->parent = '/';
240                     } else {
241                         $sco->parent = $element->parent;
242                     }
243                     if (isset($element->file_name)) {
244                         $sco->launch = $element->file_name;
245                         $sco->scormtype = 'sco';
246                         $sco->previous = 0;
247                         $sco->next = 0;
248                         $id = null;
249                         if ($oldscoid = scorm_array_search('identifier',$sco->identifier,$oldscoes)) {
250                             $sco->id = $oldscoid;
251                             $id = $DB->update_record('scorm_scoes',$sco);
252                             $DB->delete_records('scorm_scoes_data', array('scoid'=>$oldscoid));
253                             unset($oldscoes[$oldscoid]);
254                         } else {
255                             $id = $DB->insert_record('scorm_scoes',$sco);
256                         }
257                         if (!empty($id)) {
258                             unset($scodata);
259                             $scodata->scoid = $id;
260                             if (isset($element->web_launch)) {
261                                 $scodata->name = 'parameters';
262                                 $scodata->value = $element->web_launch;
263                                 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
264                             }
265                             if (isset($element->prerequisites)) {
266                                 $scodata->name = 'prerequisites';
267                                 $scodata->value = $element->prerequisites;
268                                 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
269                             }
270                             if (isset($element->max_time_allowed)) {
271                                 $scodata->name = 'max_time_allowed';
272                                 $scodata->value = $element->max_time_allowed;
273                                 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
274                             }
275                             if (isset($element->time_limit_action)) {
276                                 $scodata->name = 'time_limit_action';
277                                 $scodata->value = $element->time_limit_action;
278                                 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
279                             }
280                             if (isset($element->mastery_score)) {
281                                 $scodata->name = 'mastery_score';
282                                 $scodata->value = $element->mastery_score;
283                                 $dataid = $DB->insert_record('scorm_scoes_data',$scodata);
284                             }
285                             if (isset($element->core_vendor)) {
286                                 $scodata->name = 'datafromlms';
287                                 $scodata->value = eregi_replace('<cr>', "\r\n", $element->core_vendor);
288                                 $dataid = insert_record('scorm_scoes_data',$scodata);
289                             }
290                         }
291                         if ($launch==0) {
292                             $launch = $id;
293                         }
294                     }
295                 }
296             }
297         }
298     }
299     if (!empty($oldscoes)) {
300         foreach($oldscoes as $oldsco) {
301             $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
302             $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
303         }
304     }
305     $DB->set_field('scorm','version','AICC', array('id'=>$scormid));
306     return $launch;
309 function scorm_get_toc($user,$scorm,$liststyle,$currentorg='',$scoid='',$mode='normal',$attempt='',$play=false) {
310     global $CFG, $DB;
311     
312     $strexpand = get_string('expcoll','scorm');
313     $modestr = '';
314     if ($mode == 'browse') {
315         $modestr = '&amp;mode='.$mode;
316     } 
317     $scormpixdir = $CFG->modpixpath.'/scorm/pix';
318     
319     $result = new stdClass();
320     $result->toc = "<ul id='s0' class='$liststyle'>\n";
321     $tocmenus = array();
322     $result->prerequisites = true;
323     $incomplete = false;
324     
325     //
326     // Get the current organization infos
327     //
328     $conditions = array();
329     if (!empty($currentorg)) {
330         if (($organizationtitle = $DB->get_field('scorm_scoes','title', array('scorm'=>$scorm->id,'identifier'=>$currentorg))) != '') {
331             $result->toc .= "\t<li>$organizationtitle</li>\n";
332             $tocmenus[] = $organizationtitle;
333         }
334         $conditions['organization'] = $currentorg;
335     }
336     //
337     // If not specified retrieve the last attempt number
338     //
339     if (empty($attempt)) {
340         $attempt = scorm_get_last_attempt($scorm->id, $user->id);
341     }
342     $result->attemptleft = $scorm->maxattempt - $attempt;
343     $conditions['scorm'] = $scorm->id;
344     if ($scoes = $DB->get_records('scorm_scoes', $conditions, "id ASC")){
345         // drop keys so that we can access array sequentially
346         $scoes = array_values($scoes); 
347         //
348         // Retrieve user tracking data for each learning object
349         // 
350         $usertracks = array();
351         foreach ($scoes as $sco) {
352             if (!empty($sco->launch)) {
353                 if ($usertrack=scorm_get_tracks($sco->id,$user->id,$attempt)) {
354                     if ($usertrack->status == '') {
355                         $usertrack->status = 'notattempted';
356                     }
357                     $usertracks[$sco->identifier] = $usertrack;
358                 }
359             }
360         }
362         $level=0;
363         $sublist=1;
364         $previd = 0;
365         $nextid = 0;
366         $findnext = false;
367         $parents[$level]='/';
368         
369         foreach ($scoes as $pos=>$sco) {
370             $isvisible = false;
371             $sco->title = $sco->title;
372             if ($optionaldatas = scorm_get_sco($sco->id, SCO_DATA)) {
373                 if (!isset($optionaldatas->isvisible) || (isset($optionaldatas->isvisible) && ($optionaldatas->isvisible == 'true'))) {
374                     $isvisible = true;
375                 }
376             }
377             else
378                                 $isvisible = true;
379             if ($parents[$level]!=$sco->parent) {
380                 if ($newlevel = array_search($sco->parent,$parents)) {
381                     for ($i=0; $i<($level-$newlevel); $i++) {
382                         $result->toc .= "\t\t</ul></li>\n";
383                     }
384                     $level = $newlevel;
385                 } else {
386                     $i = $level;
387                     $closelist = '';
388                     while (($i > 0) && ($parents[$level] != $sco->parent)) {
389                         $closelist .= "\t\t</ul></li>\n";
390                         $i--;
391                     }
392                     if (($i == 0) && ($sco->parent != $currentorg)) {
393                         $style = '';
394                         if (isset($_COOKIE['hide:SCORMitem'.$sco->id])) {
395                             $style = ' style="display: none;"';
396                         }
397                         $result->toc .= "\t\t<li><ul id='s$sublist' class='$liststyle'$style>\n";
398                         $level++;
399                     } else {
400                         $result->toc .= $closelist;
401                         $level = $i;
402                     }
403                     $parents[$level]=$sco->parent;
404                 }
405             }
406             if ($isvisible) {
407                 $result->toc .= "\t\t<li>";
408             }
409             if (isset($scoes[$pos+1])) {
410                 $nextsco = $scoes[$pos+1];
411             } else {
412                 $nextsco = false;
413             }
414             $nextisvisible = false;
415             if (($nextsco !== false) && ($optionaldatas = scorm_get_sco($nextsco->id, SCO_DATA))) {
416                 if (!isset($optionaldatas->isvisible) || (isset($optionaldatas->isvisible) && ($optionaldatas->isvisible == 'true'))) {
417                     $nextisvisible = true;
418                 }
419             }
420             if ($nextisvisible && ($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
421                 $sublist++;
422                 $icon = 'minus';
423                 if (isset($_COOKIE['hide:SCORMitem'.$nextsco->id])) {
424                     $icon = 'plus';
425                 }
426                 $result->toc .= '<a href="javascript:expandCollide(\'img'.$sublist.'\',\'s'.$sublist.'\','.$nextsco->id.');"><img id="img'.$sublist.'" src="'.$scormpixdir.'/'.$icon.'.gif" alt="'.$strexpand.'" title="'.$strexpand.'"/></a>';
427             } else if ($isvisible) {
428                 $result->toc .= '<img src="'.$scormpixdir.'/spacer.gif" />';
429             }
430             if (empty($sco->title)) {
431                 $sco->title = $sco->identifier;
432             }
433             if (!empty($sco->launch)) {
434                 if ($isvisible) {
435                     $startbold = '';
436                     $endbold = '';
437                     $score = '';
438                     if (empty($scoid) && ($mode != 'normal')) {
439                         $scoid = $sco->id;
440                     }
441                     if (isset($usertracks[$sco->identifier])) {
442                         $usertrack = $usertracks[$sco->identifier];
443                         $strstatus = get_string($usertrack->status,'scorm');
444                         if ($sco->scormtype == 'sco') {
445                             $statusicon = '<img src="'.$scormpixdir.'/'.$usertrack->status.'.gif" alt="'.$strstatus.'" title="'.$strstatus.'" />';
446                         } else {
447                             $statusicon = '<img src="'.$scormpixdir.'/assetc.gif" alt="'.get_string('assetlaunched','scorm').'" title="'.get_string('assetlaunched','scorm').'" />';
448                         }
449                     
450                         if (($usertrack->status == 'notattempted') || ($usertrack->status == 'incomplete') || ($usertrack->status == 'browsed')) {
451                             $incomplete = true;
452                             if ($play && empty($scoid)) {
453                                 $scoid = $sco->id;
454                             }
455                         }
456                         if ($usertrack->score_raw != '') {
457                             $score = '('.get_string('score','scorm').':&nbsp;'.$usertrack->score_raw.')';
458                         }
459                         $strsuspended = get_string('suspended','scorm');
460                         if (isset($usertrack->{'cmi.core.exit'}) && ($usertrack->{'cmi.core.exit'} == 'suspend')) {
461                             $statusicon = '<img src="'.$scormpixdir.'/suspend.gif" alt="'.$strstatus.' - '.$strsuspended.'" title="'.$strstatus.' - '.$strsuspended.'" />';
462                         }
463                     } else {
464                         if ($play && empty($scoid)) {
465                             $scoid = $sco->id;
466                         }
467                         $incomplete = true;
468                         if ($sco->scormtype == 'sco') {
469                             $statusicon = '<img src="'.$scormpixdir.'/notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
470                         } else {
471                             $statusicon = '<img src="'.$scormpixdir.'/asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
472                         }
473                     }
474                     if ($sco->id == $scoid) {
475                         $scodata = scorm_get_sco($sco->id, SCO_DATA);
476                         $startbold = '<b>';
477                         $endbold = '</b>';
478                         $findnext = true;
479                         $shownext = isset($scodata->next) ? $scodata->next : 0;
480                         $showprev = isset($scodata->previous) ? $scodata->previous : 0;
481                     }
482                 
483                     if (($nextid == 0) && (scorm_count_launchable($scorm->id,$currentorg) > 1) && ($nextsco!==false) && (!$findnext)) {
484                         if (!empty($sco->launch)) {
485                             $previd = $sco->id;
486                         }
487                     }
488                     if (empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites,$usertracks)) {
489                         if ($sco->id == $scoid) {
490                             $result->prerequisites = true;
491                         }
492                         $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&amp;currentorg='.$currentorg.$modestr.'&amp;scoid='.$sco->id;
493                         $result->toc .= $statusicon.'&nbsp;'.$startbold.'<a href="'.$url.'">'.format_string($sco->title).'</a>'.$score.$endbold."</li>\n";
494                         $tocmenus[$sco->id] = scorm_repeater('&minus;',$level) . '&gt;' . format_string($sco->title);
495                     } else {
496                         if ($sco->id == $scoid) {
497                             $result->prerequisites = false;
498                         }
499                         $result->toc .= $statusicon.'&nbsp;'.format_string($sco->title)."</li>\n";
500                     }
501                 }
502             } else {
503                 $result->toc .= '&nbsp;'.format_string($sco->title)."</li>\n";
504             }
505             if (($nextsco !== false) && ($nextid == 0) && ($findnext)) {
506                 if (!empty($nextsco->launch)) {
507                     $nextid = $nextsco->id;
508                 }
509             }
510         }
511         for ($i=0;$i<$level;$i++) {
512             $result->toc .= "\t\t</ul></li>\n";
513         }
514         
515         if ($play) {
516             $sco = scorm_get_sco($scoid);
517             $sco->previd = $previd;
518             $sco->nextid = $nextid;
519             $result->sco = $sco;
520             $result->incomplete = $incomplete;
521         } else {
522             $result->incomplete = $incomplete;
523         }
524     }
525     $result->toc .= "\t</ul>\n";
526     if ($scorm->hidetoc == 0) {
527         $result->toc .= '
528           <script type="text/javascript">
529           //<![CDATA[
530               function expandCollide(which,list,item) {
531                   var el = document.ids ? document.ids[list] : document.getElementById ? document.getElementById(list) : document.all[list];
532                   which = which.substring(0,(which.length));
533                   var el2 = document.ids ? document.ids[which] : document.getElementById ? document.getElementById(which) : document.all[which];
534                   if (el.style.display != "none") {
535                       el2.src = "'.$scormpixdir.'/plus.gif";
536                       el.style.display=\'none\';
537                       new cookie("hide:SCORMitem" + item, 1, 356, "/").set();
538                   } else {
539                       el2.src = "'.$scormpixdir.'/minus.gif";
540                       el.style.display=\'block\';
541                       new cookie("hide:SCORMitem" + item, 1, -1, "/").set();
542                   }
543               }
544           //]]>
545           </script>'."\n";
546     }
547     
548     $url = $CFG->wwwroot.'/mod/scorm/player.php?a='.$scorm->id.'&amp;currentorg='.$currentorg.$modestr.'&amp;scoid=';
549     $result->tocmenu = popup_form($url,$tocmenus, "tocmenu", $sco->id, '', '', '', true);
551     return $result;
554 ?>