Step 26 – Getting Data from the LMS

How I get data from the LMS to initialize the SCO session is going to depend on the way that the LMS is coded. So it’s not possible to write this in a completely generic way. Instead, I’m going to take some time to re-structure the code to move all LMS-specific and database-specific code to one place. This should make it considerably easier to work with.

Here’s how my new version of the VS SCORM 1.2 Run-Time Environment (RTE) is structured.

rtestructure

These are the core components:

  • At the top level, there’s a simple HTML frameset that runs in the student’s browser. This consists of the frameset itself (rte.html), the SCO (you’ll have to define where that’s situated), and the JavaScript SCORM API (api.html).
     
  • The client-side JavaScript API communicates with API code that sits on the server using AJAX calls.
     
  • The server-side API – written in PHP – consists of 4 scripts that initialize a SCO (initialize.php), read and write data elements in response to SCO requests (getValue.php and setValue.php), and finish the session (finish.php).
     
  • The PHP API scripts communicate with the LMS server through database-specific, and LMS-specific code that’s contained in the subs.php script.
     
  • Database login information is contained in the config.php file.
     

Let’s look at each of these in turn.

1. rte.html

This is a simple frameset – nothing very special here except to note the frame called ‘API’ which is where the SCO will look for the SCORM API code.

<html>
<head>
  <title>VS SCORM</title>
  <!-- Rev 1.1 - Wednesday, July 29, 2009 -->
</head>
<frameset frameborder="0" framespacing="0" border="0" rows="0,*" cols="*">
  <frame src="api.html" name="API" noresize>
  <frame src="../course/index.html" name="course">
</frameset>
</html>

2. api.html

This file contains the client-side JavaScript API that the SCO calls. It supports the 8 SCORM API calls that are used to initialize the SCO, get and set values for data elements in the database, close the SCO session, and handle error reporting. It also contains the client-side JavaScript used to generate the AJAX calls for communication with the server-side part of the API, and for URL encoding.

<html>
<head>

<title>VS SCORM - RTE API</title>

<!--  

VS SCORM - RTE API FOR SCORM 1.2
Rev 2.3 - Wednesday, July 29, 2009
Copyright (C) 2009, Addison Robson LLC

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA  02110-1301, USA.

-->

<script language="javascript">

// ------------------------------------------
//   SCORM RTE Functions - Initialization
// ------------------------------------------
function LMSInitialize(dummyString) {

  // create request object
  var req = createRequest();

  // code to prevent caching
  var d = new Date();

  // set up request parameters - uses GET method
  req.open('GET','initialize.php?code='+d.getTime(),false);

  // submit to the server for processing
  req.send(null);

  // process returned data - error condition
  if (req.status != 200) {
    alert('Problem with Request');
    return "";
  }

  // process returned data - OK
  else {
    return "true";
  }

}

// ------------------------------------------
//   SCORM RTE Functions - Getting and Setting Values
// ------------------------------------------
function LMSGetValue(varname) {

  // create request object
  var req = createRequest();

  // code to prevent caching
  var d = new Date();

  // set up request parameters - uses GET method
  req.open('GET','getValue.php?varname='+urlencode(varname)+'&code='+d.getTime(),false);

  // submit to the server for processing
  req.send(null);

  // process returned data - error condition
  if (req.status != 200) {
    alert('Problem with Request');
    return "";
  }

  // process returned data - OK
  else {
    return req.responseText;
  }

}

function LMSSetValue(varname,varvalue) {

  // create request object
  var req = createRequest();

  // code to prevent caching
  var d = new Date();

  // set up request parameters - uses combined GET and POST methods
  req.open('POST','setValue.php?varname='+urlencode(varname)+'&code='+d.getTime(),false);

  // send header information along with the POST data
  var params = 'varvalue='+urlencode(varvalue);
  req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  req.setRequestHeader("Content-length", params.length);
  req.setRequestHeader("Connection", "close");

  // submit to the server for processing
  req.send(params);

  // process returned data - error condition
  if (req.status != 200) {
    alert('Problem with Request');
    return "false";
  }

  // process returned data - OK
  else {
    return "true";
  }

}

function LMSCommit(dummyString) {
  LMSGetValue('');
  return "true";
}

// ------------------------------------------
//   SCORM RTE Functions - Closing The Session
// ------------------------------------------
function LMSFinish(dummyString) {

  // create request object
  var req = createRequest();

  // code to prevent caching
  var d = new Date();

  // set up request parameters - uses GET method
  req.open('GET','finish.php?code='+d.getTime(),false);

  // submit to the server for processing
  req.send(null);

  // process returned data - error condition
  if (req.status != 200) {
    alert('Problem with Request');
    return "";
  }

  // process returned data - OK
  else {
    return "true";
  }

}

// ------------------------------------------
//   SCORM RTE Functions - Error Handling
// ------------------------------------------
function LMSGetLastError() {
  return 0;
}

function LMSGetDiagnostic(errorCode) {
  return "diagnostic string";
}

function LMSGetErrorString(errorCode) {
  return "error string";
}

// ------------------------------------------
//   AJAX Request Handling
// ------------------------------------------
function createRequest() {
  var request;
  try {
    request = new XMLHttpRequest();
  }
  catch (tryIE) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch (tryOlderIE) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      }
      catch (failed) {
        alert("Error creating XMLHttpRequest");
      }
    }
  }
  return request;
}

// ------------------------------------------
//   URL Encoding
// ------------------------------------------
function urlencode( str ) {
  //
  // Ref: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_urlencode/
  //
    // http://kevin.vanzonneveld.net
    // +   original by: Philip Peterson
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: AJ
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Brett Zamir (http://brettz9.blogspot.com)
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: travc
    // +      input by: Brett Zamir (http://brettz9.blogspot.com)
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Lars Fischer
    // %          note 1: info on what encoding functions to use from: http://xkr.us/articles/javascript/encode-compare/
    // *     example 1: urlencode('Kevin van Zonneveld!');
    // *     returns 1: 'Kevin+van+Zonneveld%21'
    // *     example 2: urlencode('http://kevin.vanzonneveld.net/');
    // *     returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F'
    // *     example 3: urlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a');
    // *     returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a'

    var histogram = {}, unicodeStr='', hexEscStr='';
    var ret = (str+'').toString();

    var replacer = function(search, replace, str) {
        var tmp_arr = [];
        tmp_arr = str.split(search);
        return tmp_arr.join(replace);
    };

    // The histogram is identical to the one in urldecode.
    histogram["'"]   = '%27';
    histogram['(']   = '%28';
    histogram[')']   = '%29';
    histogram['*']   = '%2A';
    histogram['~']   = '%7E';
    histogram['!']   = '%21';
    histogram['%20'] = '+';
    histogram['\u00DC'] = '%DC';
    histogram['\u00FC'] = '%FC';
    histogram['\u00C4'] = '%D4';
    histogram['\u00E4'] = '%E4';
    histogram['\u00D6'] = '%D6';
    histogram['\u00F6'] = '%F6';
    histogram['\u00DF'] = '%DF';
    histogram['\u20AC'] = '%80';
    histogram['\u0081'] = '%81';
    histogram['\u201A'] = '%82';
    histogram['\u0192'] = '%83';
    histogram['\u201E'] = '%84';
    histogram['\u2026'] = '%85';
    histogram['\u2020'] = '%86';
    histogram['\u2021'] = '%87';
    histogram['\u02C6'] = '%88';
    histogram['\u2030'] = '%89';
    histogram['\u0160'] = '%8A';
    histogram['\u2039'] = '%8B';
    histogram['\u0152'] = '%8C';
    histogram['\u008D'] = '%8D';
    histogram['\u017D'] = '%8E';
    histogram['\u008F'] = '%8F';
    histogram['\u0090'] = '%90';
    histogram['\u2018'] = '%91';
    histogram['\u2019'] = '%92';
    histogram['\u201C'] = '%93';
    histogram['\u201D'] = '%94';
    histogram['\u2022'] = '%95';
    histogram['\u2013'] = '%96';
    histogram['\u2014'] = '%97';
    histogram['\u02DC'] = '%98';
    histogram['\u2122'] = '%99';
    histogram['\u0161'] = '%9A';
    histogram['\u203A'] = '%9B';
    histogram['\u0153'] = '%9C';
    histogram['\u009D'] = '%9D';
    histogram['\u017E'] = '%9E';
    histogram['\u0178'] = '%9F';

    // Begin with encodeURIComponent, which most resembles PHP's encoding functions
    ret = encodeURIComponent(ret);

    for (unicodeStr in histogram) {
        hexEscStr = histogram[unicodeStr];
        ret = replacer(unicodeStr, hexEscStr, ret); // Custom replace. No regexing
    }

    // Uppercase for full PHP compatibility
    return ret.replace(/(\%([a-z0-9]{2}))/g, function(full, m1, m2) {
        return "%"+m2.toUpperCase();
    });
}

</script>

</head>
<body>

<p> 

</body>
</html>

3. config.php

This file simply contains the login information for the database.

<?php 

$dbname = 'your database name';
$dbhost = 'your database host';
$dbuser = 'your database username';
$dbpass = 'your database password';

?>

4. initialize.php

This is the new form of the initialization code that’s called in reponse to the LMSinitialize() SCORM call. Note that all database-specific code has been eliminated and replaced with calls to initializeElement() and clearElement() php functions that are defined in the subs.php file (see later). Note also that the 4 values that have to be read from the LMS to initialize the SCO session are being read through another PHP function called getFromLMS() – again, defined in the subs.php file.

<?php 

/*

VS SCORM - initialize.php 
Rev 1.4 - Tuesday, July 28, 2009
Copyright (C) 2009, Addison Robson LLC

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, 
Boston, MA  02110-1301, USA.

*/

//  essential functions
require "subs.php";

//  read database login information and connect
require "config.php";
dbConnect();

// ------------------------------------------------------------------------------------
// elements that tell the SCO which other elements are supported by this API
initializeElement('cmi.core._children', 'student_id,student_name,lesson_location,credit,lesson_status,entry,score,total_time,exit,session_time');
initializeElement('cmi.core.score._children','raw');

// student information
initializeElement('cmi.core.student_name',getFromLMS('cmi.core.student_name'));
initializeElement('cmi.core.student_id',getFromLMS('cmi.core.student_id'));

// mastery test score from IMS manifest file
initializeElement('adlcp:masteryscore',getFromLMS('adlcp:masteryscore'));

// SCO launch data from IMS manifest file 
initializeElement('cmi.launch_data',getFromLMS('cmi.launch_data'));

// progress and completion tracking
initializeElement('cmi.core.credit','credit');
initializeElement('cmi.core.lesson_status','not attempted');
initializeElement('cmi.core.entry','ab initio');

// total seat time
initializeElement('cmi.core.total_time','0000:00:00');

// new session so clear pre-existing session time
clearElement('cmi.core.session_time');

// ------------------------------------------------------------------------------------
// return value to the calling program
print "true";
die;

?>

5. getValue.php

In the earlier versions of the code, getValue.php handled certain data-element values such as cmi.core._children and cmi.core.score._children which are always the same. In this new version of the code, these are set during the initialization process by initialize.php. The earlier version also contained database-specific code which has now been relegated to the subs.php file (see below). This what the new – much simpler – getValue.php file looks like.

<?php 

/*

VS SCORM - getValue.php 
Rev 2.2 - Wednesday, July 08, 2009
Copyright (C) 2009, Addison Robson LLC

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, 
Boston, MA  02110-1301, USA.

*/

//  essential functions
require "subs.php";

//  read database login information and connect
require "config.php";
dbConnect();

// read GET variable
$varname = trim($_REQUEST['varname']);

// no element name supplied, return an empty string
if (! $varname) {
  $varvalue = "";
}

// otherwise, read data from the 'scormvars' table
else {
  $varvalue = readElement($varname);
}

// return element value to the calling program
print $varvalue;

?>

6. setValue.php

The setValue.php code is also simplified by moving database-specific code to subs.php.

<?php 

/*

VS SCORM - setValue.php 
Rev 1.0 - Wednesday, June 10, 2009
Copyright (C) 2009, Addison Robson LLC

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, 
Boston, MA  02110-1301, USA.

*/

//  essential functions
require "subs.php";

//  read database login information and connect
require "config.php";
dbConnect();

// read GET and POST variables
$varname = $_REQUEST['varname'];
$varvalue = $_REQUEST['varvalue'];

// save data to the 'scormvars' table
writeElement($varname,$varvalue);

// return value to the calling program
print "true";

?>

7. finish.php

This code is called when the SCO session is finished. It’s not very different to the way that it was structured previously, but I’ve taken the opportunity to move the database-specific calls into the subs.php file, and instead, I’m using the readElement(), writeElement() and clearElement() functions. Here’s how it looks.

<?php 

/*

VS SCORM - finish.php 
Rev 1.2 - Monday, July 27, 2009
Copyright (C) 2009, Addison Robson LLC

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, 
Boston, MA  02110-1301, USA.

*/

//  essential functions
require "subs.php";

//  read database login information and connect
require "config.php";
dbConnect();

// ------------------------------------------------------------------------------------
// set cmi.core.lesson_status

// find existing value of cmi.core.lesson_status
$lessonstatus = readElement('cmi.core.lesson_status');

// if it's 'not attempted', change it to 'completed'
if ($lessonstatus == 'not attempted') {
  writeElement('cmi.core.lesson_status','completed');
}

// has a mastery score been specified in the IMS manifest file?
$masteryscore = readElement('adlcp:masteryscore');
$masteryscore *= 1;

if ($masteryscore) {

  // yes - so read the score
  $rawscore = readElement('cmi.score.raw');
  $rawscore *= 1;

  // set cmi.core.lesson_status to passed/failed
  if ($rawscore >= $masteryscore) {
    writeElement('cmi.core.lesson_status','passed');
  }
  else {
    writeElement('cmi.core.lesson_status','failed');
  }

}

// ------------------------------------------------------------------------------------
// set cmi.core.entry based on the value of cmi.core.exit

// clear existing value
clearElement('cmi.core.entry');

// new entry value depends on exit value
$exit = readElement('cmi.core.exit');
if ($exit == 'suspend') {
  writeElement('cmi.core.entry','resume');
}
else {
  writeElement('cmi.core.entry','');
}

// ------------------------------------------------------------------------------------
// process changes to cmi.core.total_time

// read cmi.core.total_time from the 'scormvars' table
$totaltime = readElement('cmi.core.total_time');

// convert total time to seconds
$time = explode(':',$totaltime);
$totalseconds = $time[0]*60*60 + $time[1]*60 + $time[2];

// read the last-set cmi.core.session_time from the 'scormvars' table
$sessiontime = readElement('cmi.core.session_time');

// no session time set by SCO - set to zero
if (! $sessiontime) {
  $sessiontime = "00:00:00";
}

// convert session time to seconds
$time = explode(':',$sessiontime);
$sessionseconds = $time[0]*60*60 + $time[1]*60 + $time[2];

// new total time is ...
$totalseconds += $sessionseconds;

// break total time into hours, minutes and seconds
$totalhours = intval($totalseconds / 3600);
$totalseconds -= $totalhours * 3600;
$totalminutes = intval($totalseconds / 60);
$totalseconds -= $totalminutes * 60;

// reformat to comply with the SCORM data model
$totaltime = sprintf("%04d:%02d:%02d",$totalhours,$totalminutes,$totalseconds);

// save new total time to the 'scormvars' table
writeElement('cmi.core.total_time',$totaltime);

// delete the last session time
clearElement('cmi.core.session_time');

// ------------------------------------------------------------------------------------
// return value to the calling program
print "true";
die;

?>

8. subs.php

This is the new – and key – piece of the puzzle. It contains the following database-specific functions:

  • dbConnect()
  • initialiizeElement()
  • clearElement()
  • readElement()
  • writeElement()

and a new LMS-specific function:

  • getFromLMS()

Here’s what it looks like:

<?php 

/*

VS SCORM - subs.php 
contains commonly-used subroutines including database-specific 
code, and LMS-specific code
Rev 1.0 - Wednesday, July 29, 2009
Copyright (C) 2009, Addison Robson LLC

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, 
Boston, MA  02110-1301, USA.

*/

// ------------------------------------------------------------------------------------
// Database-specific code
// ------------------------------------------------------------------------------------

function dbConnect() {

  // database login details
  global $dbname;
  global $dbhost;
  global $dbuser;
  global $dbpass;

  // link
  global $link;

  // connect to the database
  $link = mysql_connect($dbhost,$dbuser,$dbpass);
  mysql_select_db($dbname,$link);

}

function readElement($varName) {

  global $link;

  $safeVarName = mysql_escape_string($varName);
  $result = mysql_query("select varValue from scormvars where (varName='$safeVarName')",$link);
  list($value) = mysql_fetch_row($result);

  return $value;

}

function writeElement($varName,$varValue) { 

  global $link;

  $safeVarName = mysql_escape_string($varName);
  $safeVarValue = mysql_escape_string($varValue);
  mysql_query("delete from scormvars where (varName='$safeVarName')",$link);
  mysql_query("insert into scormvars (varName,varValue) values ('$safeVarName','$safeVarValue')",$link);

  return;

}

function clearElement($varName) {

  global $link;

  $safeVarName = mysql_escape_string($varName);
  mysql_query("delete from scormvars where (varName='$safeVarName')",$link);

  return;

}

function initializeElement($varName,$varValue) {

  global $link;

  // make safe for the database
  $safeVarName = mysql_escape_string($varName);
  $safeVarValue = mysql_escape_string($varValue);

  // look for pre-existing values
  $result = mysql_query("select varValue from scormvars where (varName='$safeVarName')",$link);

  // only if nothing found ...
  if (! mysql_num_rows($result)) {
    mysql_query("insert into scormvars (varName,varValue) values ('$safeVarName','$safeVarValue')",$link);
  }

}

// ------------------------------------------------------------------------------------
// LMS-specific code
// ------------------------------------------------------------------------------------
function getFromLMS($varname) {

  switch ($varname) {

    case 'cmi.core.student_name':
      $varvalue = 'Addison, Steve';
      break;

    case 'cmi.core.student_id':
      $varvalue = '007';
      break;

    case 'adlcp:masteryscore':
      $varvalue = '90';
      break;

    case 'cmi.launch_data':
      $varvalue = '';
      break;

    default:
      $varvalue = '';

  }

  return $varvalue;

}

?>

Phew! That was a lot of work. But the key is that I can now connect the VS SCORM RTE to a LMS – for SCO initialization, at least – by customizing the getFromLMS() code in the subs.php file.

But I’m still restricted to a single student and a single SCO. Next step – supporting multiple students and multiple SCOs.

Be Sociable, Share!
This entry was posted in Run Time Environment. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *