Merge branch 'MDL-45339-master' of git://github.com/xow/moodle
[moodle.git] / mod / scorm / datamodels / scorm_12.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 //
17 // SCORM 1.2 API Implementation
18 //
19 function SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg) {
21     var prerequrl = cfgwwwroot + "/mod/scorm/prereqs.php?a="+scormid+"&scoid="+scoid+"&attempt="+attempt+"&mode="+viewmode+"&currentorg="+currentorg+"&sesskey="+sesskey;
22     var datamodelurl = cfgwwwroot + "/mod/scorm/datamodel.php";
23     var datamodelurlparams = "id="+cmid+"&a="+scormid+"&sesskey="+sesskey+"&attempt="+attempt+"&scoid="+scoid;
25     // Standard Data Type Definition
26     CMIString256 = cmistring256;
27     CMIString4096 = cmistring4096;
28     CMITime = '^([0-2]{1}[0-9]{1}):([0-5]{1}[0-9]{1}):([0-5]{1}[0-9]{1})(\.[0-9]{1,2})?$';
29     CMITimespan = '^([0-9]{2,4}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,2})?$';
30     CMIInteger = '^\\d+$';
31     CMISInteger = '^-?([0-9]+)$';
32     CMIDecimal = '^-?([0-9]{0,3})(\.[0-9]*)?$';
33     CMIIdentifier = '^[\\u0021-\\u007E]{0,255}$';
34     CMIFeedback = CMIString256; // This must be redefined
35     CMIIndex = '[._](\\d+).';
36     // Vocabulary Data Type Definition
37     CMIStatus = '^passed$|^completed$|^failed$|^incomplete$|^browsed$';
38     CMIStatus2 = '^passed$|^completed$|^failed$|^incomplete$|^browsed$|^not attempted$';
39     CMIExit = '^time-out$|^suspend$|^logout$|^$';
40     CMIType = '^true-false$|^choice$|^fill-in$|^matching$|^performance$|^sequencing$|^likert$|^numeric$';
41     CMIResult = '^correct$|^wrong$|^unanticipated$|^neutral$|^([0-9]{0,3})?(\.[0-9]*)?$';
42     NAVEvent = '^previous$|^continue$';
43     // Children lists
44     cmi_children = 'core,suspend_data,launch_data,comments,objectives,student_data,student_preference,interactions';
45     core_children = 'student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,lesson_mode,exit,session_time';
46     score_children = 'raw,min,max';
47     comments_children = 'content,location,time';
48     objectives_children = 'id,score,status';
49     correct_responses_children = 'pattern';
50     student_data_children = 'mastery_score,max_time_allowed,time_limit_action';
51     student_preference_children = 'audio,language,speed,text';
52     interactions_children = 'id,objectives,time,type,correct_responses,weighting,student_response,result,latency';
53     // Data ranges
54     score_range = '0#100';
55     audio_range = '-1#100';
56     speed_range = '-100#100';
57     weighting_range = '-100#100';
58     text_range = '-1#1';
59     // The SCORM 1.2 data model
60     var datamodel =  {
61         'cmi._children':{'defaultvalue':cmi_children, 'mod':'r', 'writeerror':'402'},
62         'cmi._version':{'defaultvalue':'3.4', 'mod':'r', 'writeerror':'402'},
63         'cmi.core._children':{'defaultvalue':core_children, 'mod':'r', 'writeerror':'402'},
64         'cmi.core.student_id':{'defaultvalue':def['cmi.core.student_id'], 'mod':'r', 'writeerror':'403'},
65         'cmi.core.student_name':{'defaultvalue':def['cmi.core.student_name'], 'mod':'r', 'writeerror':'403'},
66         'cmi.core.lesson_location':{'defaultvalue':def['cmi.core.lesson_location'], 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
67         'cmi.core.credit':{'defaultvalue':def['cmi.core.credit'], 'mod':'r', 'writeerror':'403'},
68         'cmi.core.lesson_status':{'defaultvalue':def['cmi.core.lesson_status'], 'format':CMIStatus, 'mod':'rw', 'writeerror':'405'},
69         'cmi.core.entry':{'defaultvalue':def['cmi.core.entry'], 'mod':'r', 'writeerror':'403'},
70         'cmi.core.score._children':{'defaultvalue':score_children, 'mod':'r', 'writeerror':'402'},
71         'cmi.core.score.raw':{'defaultvalue':def['cmi.core.score.raw'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
72         'cmi.core.score.max':{'defaultvalue':def['cmi.core.score.max'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
73         'cmi.core.score.min':{'defaultvalue':def['cmi.core.score.min'], 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
74         'cmi.core.total_time':{'defaultvalue':def['cmi.core.total_time'], 'mod':'r', 'writeerror':'403'},
75         'cmi.core.lesson_mode':{'defaultvalue':def['cmi.core.lesson_mode'], 'mod':'r', 'writeerror':'403'},
76         'cmi.core.exit':{'defaultvalue':def['cmi.core.exit'], 'format':CMIExit, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
77         'cmi.core.session_time':{'format':CMITimespan, 'mod':'w', 'defaultvalue':'00:00:00', 'readerror':'404', 'writeerror':'405'},
78         'cmi.suspend_data':{'defaultvalue':def['cmi.suspend_data'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'},
79         'cmi.launch_data':{'defaultvalue':def['cmi.launch_data'], 'mod':'r', 'writeerror':'403'},
80         'cmi.comments':{'defaultvalue':def['cmi.comments'], 'format':CMIString4096, 'mod':'rw', 'writeerror':'405'},
81         // deprecated evaluation attributes
82         'cmi.evaluation.comments._count':{'defaultvalue':'0', 'mod':'r', 'writeerror':'402'},
83         'cmi.evaluation.comments._children':{'defaultvalue':comments_children, 'mod':'r', 'writeerror':'402'},
84         'cmi.evaluation.comments.n.content':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
85         'cmi.evaluation.comments.n.location':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
86         'cmi.evaluation.comments.n.time':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMITime, 'mod':'rw', 'writeerror':'405'},
87         'cmi.comments_from_lms':{'mod':'r', 'writeerror':'403'},
88         'cmi.objectives._children':{'defaultvalue':objectives_children, 'mod':'r', 'writeerror':'402'},
89         'cmi.objectives._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
90         'cmi.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'rw', 'writeerror':'405'},
91         'cmi.objectives.n.score._children':{'pattern':CMIIndex, 'mod':'r', 'writeerror':'402'},
92         'cmi.objectives.n.score.raw':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
93         'cmi.objectives.n.score.min':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
94         'cmi.objectives.n.score.max':{'defaultvalue':'', 'pattern':CMIIndex, 'format':CMIDecimal, 'range':score_range, 'mod':'rw', 'writeerror':'405'},
95         'cmi.objectives.n.status':{'pattern':CMIIndex, 'format':CMIStatus2, 'mod':'rw', 'writeerror':'405'},
96         'cmi.student_data._children':{'defaultvalue':student_data_children, 'mod':'r', 'writeerror':'402'},
97         'cmi.student_data.mastery_score':{'defaultvalue':def['cmi.student_data.mastery_score'], 'mod':'r', 'writeerror':'403'},
98         'cmi.student_data.max_time_allowed':{'defaultvalue':def['cmi.student_data.max_time_allowed'], 'mod':'r', 'writeerror':'403'},
99         'cmi.student_data.time_limit_action':{'defaultvalue':def['cmi.student_data.time_limit_action'], 'mod':'r', 'writeerror':'403'},
100         'cmi.student_preference._children':{'defaultvalue':student_preference_children, 'mod':'r', 'writeerror':'402'},
101         'cmi.student_preference.audio':{'defaultvalue':'0', 'format':CMISInteger, 'range':audio_range, 'mod':'rw', 'writeerror':'405'},
102         'cmi.student_preference.language':{'defaultvalue':'', 'format':CMIString256, 'mod':'rw', 'writeerror':'405'},
103         'cmi.student_preference.speed':{'defaultvalue':'0', 'format':CMISInteger, 'range':speed_range, 'mod':'rw', 'writeerror':'405'},
104         'cmi.student_preference.text':{'defaultvalue':'0', 'format':CMISInteger, 'range':text_range, 'mod':'rw', 'writeerror':'405'},
105         'cmi.interactions._children':{'defaultvalue':interactions_children, 'mod':'r', 'writeerror':'402'},
106         'cmi.interactions._count':{'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
107         'cmi.interactions.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
108         'cmi.interactions.n.objectives._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
109         'cmi.interactions.n.objectives.n.id':{'pattern':CMIIndex, 'format':CMIIdentifier, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
110         'cmi.interactions.n.time':{'pattern':CMIIndex, 'format':CMITime, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
111         'cmi.interactions.n.type':{'pattern':CMIIndex, 'format':CMIType, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
112         'cmi.interactions.n.correct_responses._count':{'pattern':CMIIndex, 'mod':'r', 'defaultvalue':'0', 'writeerror':'402'},
113         'cmi.interactions.n.correct_responses.n.pattern':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
114         'cmi.interactions.n.weighting':{'pattern':CMIIndex, 'format':CMIDecimal, 'range':weighting_range, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
115         'cmi.interactions.n.student_response':{'pattern':CMIIndex, 'format':CMIFeedback, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
116         'cmi.interactions.n.result':{'pattern':CMIIndex, 'format':CMIResult, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
117         'cmi.interactions.n.latency':{'pattern':CMIIndex, 'format':CMITimespan, 'mod':'w', 'readerror':'404', 'writeerror':'405'},
118         'nav.event':{'defaultvalue':'', 'format':NAVEvent, 'mod':'w', 'readerror':'404', 'writeerror':'405'}
119     };
120     //
121     // Datamodel inizialization
122     //
123     var cmi = new Object();
124         cmi.core = new Object();
125         cmi.core.score = new Object();
126         cmi.objectives = new Object();
127         cmi.student_data = new Object();
128         cmi.student_preference = new Object();
129         cmi.interactions = new Object();
130         // deprecated evaluation attributes
131         cmi.evaluation = new Object();
132         cmi.evaluation.comments = new Object();
134     // Navigation Object
135     var nav = new Object();
137     for (element in datamodel) {
138         if (element.match(/\.n\./) == null) {
139             if ((typeof eval('datamodel["'+element+'"].defaultvalue')) != 'undefined') {
140                 eval(element+' = datamodel["'+element+'"].defaultvalue;');
141             } else {
142                 eval(element+' = "";');
143             }
144         }
145     }
147     eval(cmiobj);
148     eval(cmiint);
150     if (cmi.core.lesson_status == '') {
151         cmi.core.lesson_status = 'not attempted';
152     }
154     //
155     // API Methods definition
156     //
157     var Initialized = false;
159     function LMSInitialize (param) {
160         errorCode = "0";
161         if (param == "") {
162             if (!Initialized) {
163                 Initialized = true;
164                 errorCode = "0";
165                 if (scormdebugging) {
166                     LogAPICall("LMSInitialize", param, "", errorCode);
167                 }
168                 return "true";
169             } else {
170                 errorCode = "101";
171             }
172         } else {
173             errorCode = "201";
174         }
175         if (scormdebugging) {
176             LogAPICall("LMSInitialize", param, "", errorCode);
177         }
178         return "false";
179     }
181     function LMSFinish (param) {
182         errorCode = "0";
183         if (param == "") {
184             if (Initialized) {
185                 Initialized = false;
186                 result = StoreData(cmi,true);
187                 if (nav.event != '') {
188                     if (nav.event == 'continue') {
189                         setTimeout('mod_scorm_launch_next_sco();',500);
190                     } else {
191                         setTimeout('mod_scorm_launch_prev_sco();',500);
192                     }
193                 } else {
194                     if (scormauto == 1) {
195                         setTimeout('mod_scorm_launch_next_sco();',500);
196                     }
197                 }
198                 if (scormdebugging) {
199                     LogAPICall("LMSFinish", "AJAXResult", result, 0);
200                 }
201                 result = ('true' == result) ? 'true' : 'false';
202                 errorCode = (result == 'true')? '0' : '101';
203                 if (scormdebugging) {
204                     LogAPICall("LMSFinish", "result", result, 0);
205                     LogAPICall("LMSFinish", param, "", 0);
206                 }
207                 // trigger TOC update
208                 var callback = M.mod_scorm.connectPrereqCallback;
209                 YUI().use('io-base', function(Y) {
210                     Y.on('io:complete', callback.success, Y);
211                     Y.io(prerequrl);
212                 });
213                 return result;
214             } else {
215                 errorCode = "301";
216             }
217         } else {
218             errorCode = "201";
219         }
220         if (scormdebugging) {
221             LogAPICall("LMSFinish", param, "", errorCode);
222         }
223         return "false";
224     }
226     function LMSGetValue (element) {
227         errorCode = "0";
228         if (Initialized) {
229             if (element !="") {
230                 expression = new RegExp(CMIIndex,'g');
231                 elementmodel = String(element).replace(expression,'.n.');
232                 if ((typeof eval('datamodel["'+elementmodel+'"]')) != "undefined") {
233                     if (eval('datamodel["'+elementmodel+'"].mod') != 'w') {
234                         element = String(element).replace(expression, "_$1.");
235                         elementIndexes = element.split('.');
236                         subelement = 'cmi';
237                         i = 1;
238                         while ((i < elementIndexes.length) && (typeof eval(subelement) != "undefined")) {
239                             subelement += '.'+elementIndexes[i++];
240                         }
241                             if (subelement == element) {
242                             errorCode = "0";
243                             if (scormdebugging) {
244                                 LogAPICall("LMSGetValue", element, eval(element), 0);
245                             }
246                             return eval(element);
247                         } else {
248                             errorCode = "0"; // Need to check if it is the right errorCode
249                         }
250                     } else {
251                         errorCode = eval('datamodel["'+elementmodel+'"].readerror');
252                     }
253                 } else {
254                     childrenstr = '._children';
255                     countstr = '._count';
256                     if (elementmodel.substr(elementmodel.length-childrenstr.length,elementmodel.length) == childrenstr) {
257                         parentmodel = elementmodel.substr(0,elementmodel.length-childrenstr.length);
258                         if ((typeof eval('datamodel["'+parentmodel+'"]')) != "undefined") {
259                             errorCode = "202";
260                         } else {
261                             errorCode = "201";
262                         }
263                     } else if (elementmodel.substr(elementmodel.length-countstr.length,elementmodel.length) == countstr) {
264                         parentmodel = elementmodel.substr(0,elementmodel.length-countstr.length);
265                         if ((typeof eval('datamodel["'+parentmodel+'"]')) != "undefined") {
266                             errorCode = "203";
267                         } else {
268                             errorCode = "201";
269                         }
270                     } else {
271                         errorCode = "201";
272                     }
273                 }
274             } else {
275                 errorCode = "201";
276             }
277         } else {
278             errorCode = "301";
279         }
280         if (scormdebugging) {
281             LogAPICall("LMSGetValue", element, "", errorCode);
282         }
283         return "";
284     }
286     function LMSSetValue (element,value) {
287         errorCode = "0";
288         if (Initialized) {
289             if (element != "") {
290                 expression = new RegExp(CMIIndex,'g');
291                 elementmodel = String(element).replace(expression,'.n.');
292                 if ((typeof eval('datamodel["'+elementmodel+'"]')) != "undefined") {
293                     if (eval('datamodel["'+elementmodel+'"].mod') != 'r') {
294                         expression = new RegExp(eval('datamodel["'+elementmodel+'"].format'));
295                         value = value+'';
296                         matches = value.match(expression);
297                         if (matches != null) {
298                             //Create dynamic data model element
299                             if (element != elementmodel) {
300                                 elementIndexes = element.split('.');
301                                 subelement = 'cmi';
302                                 for (i=1;i < elementIndexes.length-1;i++) {
303                                     elementIndex = elementIndexes[i];
304                                     if (elementIndexes[i+1].match(/^\d+$/)) {
305                                         if ((typeof eval(subelement+'.'+elementIndex)) == "undefined") {
306                                             eval(subelement+'.'+elementIndex+' = new Object();');
307                                             eval(subelement+'.'+elementIndex+'._count = 0;');
308                                         }
309                                         if (elementIndexes[i+1] == eval(subelement+'.'+elementIndex+'._count')) {
310                                             eval(subelement+'.'+elementIndex+'._count++;');
311                                         }
312                                         if (elementIndexes[i+1] > eval(subelement+'.'+elementIndex+'._count')) {
313                                             errorCode = "201";
314                                         }
315                                         subelement = subelement.concat('.'+elementIndex+'_'+elementIndexes[i+1]);
316                                         i++;
317                                     } else {
318                                         subelement = subelement.concat('.'+elementIndex);
319                                     }
320                                     if ((typeof eval(subelement)) == "undefined") {
321                                         eval(subelement+' = new Object();');
322                                         if (subelement.substr(0,14) == 'cmi.objectives') {
323                                             eval(subelement+'.score = new Object();');
324                                             eval(subelement+'.score._children = score_children;');
325                                             eval(subelement+'.score.raw = "";');
326                                             eval(subelement+'.score.min = "";');
327                                             eval(subelement+'.score.max = "";');
328                                         }
329                                         if (subelement.substr(0,16) == 'cmi.interactions') {
330                                             eval(subelement+'.objectives = new Object();');
331                                             eval(subelement+'.objectives._count = 0;');
332                                             eval(subelement+'.correct_responses = new Object();');
333                                             eval(subelement+'.correct_responses._count = 0;');
334                                         }
335                                     }
336                                 }
337                                 element = subelement.concat('.'+elementIndexes[elementIndexes.length-1]);
338                             }
339                             //Store data
340                             if (errorCode == "0") {
341                                 if ((typeof eval('datamodel["'+elementmodel+'"].range')) != "undefined") {
342                                     range = eval('datamodel["'+elementmodel+'"].range');
343                                     ranges = range.split('#');
344                                     value = value*1.0;
345                                     if ((value >= ranges[0]) && (value <= ranges[1])) {
346                                         eval(element+'=value;');
347                                         errorCode = "0";
348                                         if (scormdebugging) {
349                                             LogAPICall("LMSSetValue", element, value, errorCode);
350                                         }
351                                         return "true";
352                                     } else {
353                                         errorCode = eval('datamodel["'+elementmodel+'"].writeerror');
354                                     }
355                                 } else {
356                                     if (element == 'cmi.comments') {
357                                         cmi.comments = cmi.comments + value;
358                                     } else {
359                                         eval(element+'=value;');
360                                     }
361                                     errorCode = "0";
362                                     if (scormdebugging) {
363                                         LogAPICall("LMSSetValue", element, value, errorCode);
364                                     }
365                                     return "true";
366                                 }
367                             }
368                         } else {
369                             errorCode = eval('datamodel["'+elementmodel+'"].writeerror');
370                         }
371                     } else {
372                         errorCode = eval('datamodel["'+elementmodel+'"].writeerror');
373                     }
374                 } else {
375                     errorCode = "201"
376                 }
377             } else {
378                 errorCode = "201";
379             }
380         } else {
381             errorCode = "301";
382         }
383         if (scormdebugging) {
384             LogAPICall("LMSSetValue", element, value, errorCode);
385         }
386         return "false";
387     }
389     function LMSCommit (param) {
390         errorCode = "0";
391         if (param == "") {
392             if (Initialized) {
393                 result = StoreData(cmi,false);
394                 // trigger TOC update
395                 var callback = M.mod_scorm.connectPrereqCallback;
396                 YUI().use('io-base', function(Y) {
397                     Y.on('io:complete', callback.success, Y);
398                     Y.io(prerequrl);
399                 });
400                 if (scormdebugging) {
401                     LogAPICall("Commit", param, "", 0);
402                 }
403                 if (scormdebugging) {
404                     LogAPICall("LMSCommit", "AJAXResult", result, 0);
405                 }
406                 result = ('true' == result) ? 'true' : 'false';
407                 errorCode = (result =='true')? '0' : '101';
408                 if (scormdebugging) {
409                     LogAPICall("LMSCommit", "result", result, 0);
410                     LogAPICall("LMSCommit", "errorCode", errorCode, 0);
411                 }
412                 return result;
413             } else {
414                 errorCode = "301";
415             }
416         } else {
417             errorCode = "201";
418         }
419         if (scormdebugging) {
420             LogAPICall("LMSCommit", param, "", 0);
421         }
422         return "false";
423     }
425     function LMSGetLastError () {
426         if (scormdebugging) {
427             LogAPICall("LMSGetLastError", "", "", errorCode);
428         }
429         return errorCode;
430     }
432     function LMSGetErrorString (param) {
433         if (param != "") {
434             var errorString = new Array();
435             errorString["0"] = "No error";
436             errorString["101"] = "General exception";
437             errorString["201"] = "Invalid argument error";
438             errorString["202"] = "Element cannot have children";
439             errorString["203"] = "Element not an array - cannot have count";
440             errorString["301"] = "Not initialized";
441             errorString["401"] = "Not implemented error";
442             errorString["402"] = "Invalid set value, element is a keyword";
443             errorString["403"] = "Element is read only";
444             errorString["404"] = "Element is write only";
445             errorString["405"] = "Incorrect data type";
446             if (scormdebugging) {
447                 LogAPICall("LMSGetErrorString", param,  errorString[param], 0);
448             }
449             return errorString[param];
450         } else {
451             if (scormdebugging) {
452                 LogAPICall("LMSGetErrorString", param,  "No error string found!", 0);
453             }
454            return "";
455         }
456     }
458     function LMSGetDiagnostic (param) {
459         if (param == "") {
460             param = errorCode;
461         }
462         if (scormdebugging) {
463             LogAPICall("LMSGetDiagnostic", param, param, 0);
464         }
465         return param;
466     }
468     function AddTime (first, second) {
469         var sFirst = first.split(":");
470         var sSecond = second.split(":");
471         var cFirst = sFirst[2].split(".");
472         var cSecond = sSecond[2].split(".");
473         var change = 0;
475         FirstCents = 0;  //Cents
476         if (cFirst.length > 1) {
477             FirstCents = parseInt(cFirst[1],10);
478         }
479         SecondCents = 0;
480         if (cSecond.length > 1) {
481             SecondCents = parseInt(cSecond[1],10);
482         }
483         var cents = FirstCents + SecondCents;
484         change = Math.floor(cents / 100);
485         cents = cents - (change * 100);
486         if (Math.floor(cents) < 10) {
487             cents = "0" + cents.toString();
488         }
490         var secs = parseInt(cFirst[0],10)+parseInt(cSecond[0],10)+change;  //Seconds
491         change = Math.floor(secs / 60);
492         secs = secs - (change * 60);
493         if (Math.floor(secs) < 10) {
494             secs = "0" + secs.toString();
495         }
497         mins = parseInt(sFirst[1],10)+parseInt(sSecond[1],10)+change;   //Minutes
498         change = Math.floor(mins / 60);
499         mins = mins - (change * 60);
500         if (mins < 10) {
501             mins = "0" + mins.toString();
502         }
504         hours = parseInt(sFirst[0],10)+parseInt(sSecond[0],10)+change;  //Hours
505         if (hours < 10) {
506             hours = "0" + hours.toString();
507         }
509         if (cents != '0') {
510             return hours + ":" + mins + ":" + secs + '.' + cents;
511         } else {
512             return hours + ":" + mins + ":" + secs;
513         }
514     }
516     function TotalTime() {
517         total_time = AddTime(cmi.core.total_time, cmi.core.session_time);
518         return '&'+underscore('cmi.core.total_time')+'='+encodeURIComponent(total_time);
519     }
521     function CollectData(data,parent) {
522         var datastring = '';
523         for (property in data) {
524             if (typeof data[property] == 'object') {
525                 datastring += CollectData(data[property],parent+'.'+property);
526             } else {
527                 element = parent+'.'+property;
528                 expression = new RegExp(CMIIndex,'g');
530                 // get the generic name for this element (e.g. convert 'cmi.interactions.1.id' to 'cmi.interactions.n.id')
531                 elementmodel = String(element).replace(expression,'.n.');
533                 // ignore the session time element
534                 if (element != "cmi.core.session_time") {
536                     // check if this specific element is not defined in the datamodel,
537                     // but the generic element name is
538                     if ((eval('typeof datamodel["'+element+'"]')) == "undefined"
539                         && (eval('typeof datamodel["'+elementmodel+'"]')) != "undefined") {
541                         // add this specific element to the data model (by cloning
542                         // the generic element) so we can track changes to it
543                         eval('datamodel["'+element+'"]=CloneObj(datamodel["'+elementmodel+'"]);');
544                     }
546                     // check if the current element exists in the datamodel
547                     if ((typeof eval('datamodel["'+element+'"]')) != "undefined") {
549                         // make sure this is not a read only element
550                         if (eval('datamodel["'+element+'"].mod') != 'r') {
552                             elementstring = '&'+underscore(element)+'='+encodeURIComponent(data[property]);
554                             // check if the element has a default value
555                             if ((typeof eval('datamodel["'+element+'"].defaultvalue')) != "undefined") {
557                                 // check if the default value is different from the current value
558                                 if (eval('datamodel["'+element+'"].defaultvalue') != data[property]
559                                     || eval('typeof(datamodel["'+element+'"].defaultvalue)') != typeof(data[property])) {
561                                     // append the URI fragment to the string we plan to commit
562                                     datastring += elementstring;
564                                     // update the element default to reflect the current committed value
565                                     eval('datamodel["'+element+'"].defaultvalue=data[property];');
566                                 }
567                             } else {
568                                 // append the URI fragment to the string we plan to commit
569                                 datastring += elementstring;
570                                 // no default value for the element, so set it now
571                                 eval('datamodel["'+element+'"].defaultvalue=data[property];');
572                             }
573                         }
574                     }
575                 }
576             }
577         }
578         return datastring;
579     }
581     function CloneObj(obj){
582         if(obj == null || typeof(obj) != 'object') {
583             return obj;
584         }
586         var temp = new obj.constructor(); // changed (twice)
587         for(var key in obj) {
588             temp[key] = CloneObj(obj[key]);
589         }
591         return temp;
592     }
594     function StoreData(data,storetotaltime) {
595         if (storetotaltime) {
596             if (cmi.core.lesson_status == 'not attempted') {
597                 cmi.core.lesson_status = 'completed';
598             }
599             if (cmi.core.lesson_mode == 'normal') {
600                 if (cmi.core.credit == 'credit') {
601                     if (cmi.student_data.mastery_score !== '' && cmi.core.score.raw !== '') {
602                         if (parseFloat(cmi.core.score.raw) >= parseFloat(cmi.student_data.mastery_score)) {
603                             cmi.core.lesson_status = 'passed';
604                         } else {
605                             cmi.core.lesson_status = 'failed';
606                         }
607                     }
608                 }
609             }
610             if (cmi.core.lesson_mode == 'browse') {
611                 if (datamodel['cmi.core.lesson_status'].defaultvalue == '' && cmi.core.lesson_status == 'not attempted') {
612                     cmi.core.lesson_status = 'browsed';
613                 }
614             }
615             datastring = CollectData(data,'cmi');
616             datastring += TotalTime();
617         } else {
618             datastring = CollectData(data,'cmi');
619         }
621         var myRequest = NewHttpReq();
622         //alert('going to:' + "<?php p($CFG->wwwroot) ?>/mod/scorm/datamodel.php" + "id=<?php p($id) ?>&a=<?php p($a) ?>&sesskey=<?php echo sesskey() ?>"+datastring);
623         result = DoRequest(myRequest,datamodelurl,datamodelurlparams+datastring);
624         results = String(result).split('\n');
625         errorCode = results[1];
626         return results[0];
627     }
629     this.LMSInitialize = LMSInitialize;
630     this.LMSFinish = LMSFinish;
631     this.LMSGetValue = LMSGetValue;
632     this.LMSSetValue = LMSSetValue;
633     this.LMSCommit = LMSCommit;
634     this.LMSGetLastError = LMSGetLastError;
635     this.LMSGetErrorString = LMSGetErrorString;
636     this.LMSGetDiagnostic = LMSGetDiagnostic;
639 M.scorm_api = {};
641 M.scorm_api.init = function(Y, def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg) {
642     window.API = new SCORMapi1_2(def, cmiobj, cmiint, cmistring256, cmistring4096, scormdebugging, scormauto, scormid, cfgwwwroot, sesskey, scoid, attempt, viewmode, cmid, currentorg);