[svn r13196] Largely improved SCORM 1.2 export but work is still in progress

skala
Yannick Warnier 18 years ago
parent b8bd9432c1
commit d15d7462a6
  1. 429
      main/exercice/export/scorm/scorm_classes.php
  2. 542
      main/exercice/export/scorm/scorm_export.php

@ -9,16 +9,16 @@ if ( count( get_included_files() ) == 1 ) die( '---' );
* @author Claro Team <cvs@claroline.net>
* @author Yannick Warnier <yannick.warnier@dokeos.com>
*/
require_once('../../exercise.class.php');
require_once('../../question.class.php');
require_once('../../answer.class.php');
require_once('../../unique_answer.class.php');
require_once('../../multiple_answer.class.php');
require_once('../../fill_blanks.class.php');
require_once('../../freeanswer.class.php');
require_once('../../hotspot.class.php');
require_once('../../matching.class.php');
require_once('../../hotspot.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/exercise.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/question.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/answer.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/unique_answer.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/multiple_answer.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/fill_blanks.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/freeanswer.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/hotspot.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/matching.class.php');
require_once(api_get_path(SYS_CODE_PATH).'exercice/hotspot.class.php');
// answer types
define('UNIQUE_ANSWER', 1);
@ -32,6 +32,15 @@ define('MATCHING', 4);
define('FREE_ANSWER', 5);
define('HOTSPOT', 6);
/**
* The ScormQuestion class is a gateway to getting the answers exported
* (the question is just an HTML text, while the answers are the most important).
* It is important to note that the SCORM export process is done in two parts.
* First, the HTML part (which is the presentation), and second the JavaScript
* part (the process).
* The two bits are separate to allow for a one-big-javascript and a one-big-html
* files to be built. Each export function thus returns an array of HTML+JS
*/
class ScormQuestion extends Question
{
/**
@ -56,6 +65,12 @@ class ScormQuestion extends Question
case MATCHING :
$this->answer = new ScormAnswerMatching($this->id);
break;
case FREE_ANSWER :
$this->answer = new ScormAnswerFree($this->id);
break;
case HOTSPOT:
$this->answer = new ScormAnswerHotspot($this->id);
break;
default :
$this->answer = null;
break;
@ -66,89 +81,149 @@ class ScormQuestion extends Question
function export()
{
$out = $this->getQuestionHtml();
//echo "<pre>".print_r($this,1)."</pre>";
$html = $this->getQuestionHTML();
$js = $this->getQuestionJS();
if( is_object($this->answer) )
{
$out .= $this->answer->export();
list($js2,$html2) = $this->answer->export();
$js .= $js2;
$html .= $html2;
}
return $out;
return array($js,$html);
}
function createAnswersForm($form)
{
return true;
}
class ScormAnswerMultipleChoice extends answerMultipleChoice
function processAnswersCreation($form)
{
return true;
}
/**
* Return the XML flow for the possible answers.
* That's one <response_lid>, containing several <flow_label>
*
* @author Amand Tihon <amand@alrj.org>
* Returns an HTML-formatted question
*/
function export()
function getQuestionHtml()
{
$out =
'<table width="100%">' . "\n\n";
$title = $this->selectTitle();
$description = $this->selectDescription();
$type = $this->selectType();
if( $this->multipleAnswer )
$cols = 0;
switch($type)
{
$questionTypeLang = get_lang('Multiple choice (Multiple answers)');
foreach( $this->answerList as $answer )
case MCUA:
case MCMA:
case TF:
case FIB:
case FREE_ANSWER:
case HOTSPOT:
$cols = 2;
break;
case MATCHING:
$cols = 3;
break;
}
$s='<tr>' .
'<td valign="top" colspan="'.$cols.'" id="question_'.$this->id.'_title">' . "\n" .
api_parse_tex($title).
'</td>' . "\n" .
'</tr>' . "\n" .
'<tr>' . "\n" .
'<td valign="top" colspan="'.$cols.'">' . "\n" .
' <i>'.api_parse_tex($description).'</i>' . "\n" .
'</td>' . "\n" .
'</tr>' . "\n";
return $s;
}
/**
* Return the JavaScript code bound to the question
*/
function getQuestionJS()
{
$identifier = 'multiple_'.$this->questionId.'_'.$answer['id'];
$scormIdentifier = 'scorm_'.getIdCounter();
//$id = $this->id;
$s = 'questions.push('.$this->id.');'."\n";
return $s;
}
}
$out .=
/**
* This class handles the export to SCORM of a multiple choice question
* (be it single answer or multiple answers)
*/
class ScormAnswerMultipleChoice extends Answer
{
/**
* Return HTML code for possible answers
*/
function export()
{
$html = '';
$js = '';
$html = '<tr><td colspan="2"><table width="100%">' . "\n";
$type = $this->getQuestionType();
if ($type == MCMA)
{
//$questionTypeLang = get_lang('MultipleChoiceMultipleAnswers');
$id = 1;
$jstmp = '';
$jstmpc = '';
foreach( $this->answer as $i => $answer )
{
$identifier = 'question_'.$this->questionId.'_multiple_'.$i;
$html .=
'<tr>' . "\n"
. '<td align="center" width="5%">' . "\n"
. '<input name="'.$identifier.'" id="'.$scormIdentifier.'" value="'.$answer['grade'].'" type="checkbox" '
. ($this->response == 'TRUE'? 'checked="checked"':'')
. '/>' . "\n"
. '<input name="'.$identifier.'" id="'.$identifier.'" value="'.$i.'" type="checkbox" />' . "\n"
. '</td>' . "\n"
. '<td width="95%">' . "\n"
. '<label for="'.$scormIdentifier.'">' . $answer['answer'] . '</label>' . "\n"
. '<label for="'.$identifier.'">' . $this->answer[$i] . '</label>' . "\n"
. '</td>' . "\n"
. '</tr>' . "\n\n";
$jstmp .= $i.',';
if($this->correct[$i])
{
$jstmpc .= $i.',';
}
$id++;
}
$js .= 'questions_answers['.$this->questionId.'] = new Array('.substr($jstmp,0,-1).');'."\n";
$js .= 'questions_answers_correct['.$this->questionId.'] = new Array('.substr($jstmpc,0,-1).');'."\n";
$js .= 'questions_types['.$this->questionId.'] = \'mcma\';'."\n";
}
else
{
$questionTypeLang = get_lang('Multiple choice (Unique answer)');
$identifier = 'unique_'.$this->questionId.'_x';
foreach( $this->answerList as $answer )
//$questionTypeLang = get_lang('MultipleChoiceUniqueAnswer');
$id = 1;
foreach( $this->answer as $i => $answer )
{
$scormIdentifier = 'scorm_'.getIdCounter();
$out .=
$identifier = 'question_'.$this->questionId.'_unique_'.$i;
$html .=
'<tr>' . "\n"
. '<td align="center" width="5%">' . "\n"
. '<input name="'.$identifier.'" id="'.$scormIdentifier.'" value="'.$answer['grade'].'" type="radio" '
. ($this->response == 'TRUE'? 'checked="checked"':'')
. '<input name="'.$identifier.'" id="'.$identifier.'" value="'.$i.'" type="radio" '
. ($this->correct[$i] == 1? 'checked="checked"':'')
. '/>' . "\n"
. '</td>' . "\n"
. '<td width="95%">' . "\n"
. '<label for="'.$scormIdentifier.'">' . $answer['answer'] . '</label>' . "\n"
. '<label for="'.$identifier.'">' . $this->answer[$i] . '</label>' . "\n"
. '</td>' . "\n"
. '</tr>' . "\n\n";
$id++;
}
}
$out .=
'</table>' . "\n"
. '<p><small>' . $questionTypeLang . '</small></p>' . "\n";
return $out;
$html .= '</table></td></tr>' . "\n";
return array($js,$html);
}
}
class ScormAnswerTrueFalse extends answerTrueFalse
/**
* This class handles the SCORM export of true/false questions
*/
class ScormAnswerTrueFalse extends Answer
{
/**
* Return the XML flow for the possible answers.
@ -158,14 +233,16 @@ class ScormAnswerTrueFalse extends answerTrueFalse
*/
function export()
{
$js = '';
$html = '';
$identifier = 'unique_'.$this->questionId.'_x';
$out =
$html .=
'<table width="100%">' . "\n\n";
$scormIdentifier = 'scorm_'.getIdCounter();
$out .=
$html .=
'<tr>' . "\n"
. '<td align="center" width="5%">' . "\n"
. '<input name="'.$identifier.'" id="'.$scormIdentifier.'" value="'.$this->trueGrade.'" type="radio" '
@ -179,7 +256,7 @@ class ScormAnswerTrueFalse extends answerTrueFalse
$scormIdentifier = 'scorm_'.getIdCounter();
$out .=
$html .=
'<tr>' . "\n"
. '<td align="center" width="5%">' . "\n"
. '<input name="'.$identifier.'" id="'.$scormIdentifier.'" value="'.$this->falseGrade.'" type="radio" '
@ -194,11 +271,14 @@ class ScormAnswerTrueFalse extends answerTrueFalse
. '</table>' . "\n"
. '<p><small>' . get_lang('True/False') . '</small></p>' . "\n";
return $out;
return array($js,$html);
}
}
class ScormAnswerFillInBlanks extends answerFillInBlanks
/**
* This class handles the SCORM export of fill-in-the-blanks questions
*/
class ScormAnswerFillInBlanks extends Answer
{
/**
* Export the text with missing words.
@ -210,6 +290,8 @@ class ScormAnswerFillInBlanks extends answerFillInBlanks
*/
function export()
{
$js = '';
$html = '';
// get all enclosed answers
foreach( $this->answerList as $answer )
{
@ -260,18 +342,18 @@ class ScormAnswerFillInBlanks extends answerFillInBlanks
$displayedAnswer = str_replace( $blankList, $replacementList, claro_parse_user_text($this->answerText) );
// some javascript must be added for that kind of questions
$out =
'<script type="text/javascript" language="javascript">' . "\n";
$js .= '';
//'<script type="text/javascript" language="javascript">' . "\n";
// Add the data for fillAnswerList
for( $i = 0; $i < $answerCount; $i++ )
{
$out .= " fillAnswerList['fill_" . $this->questionId . "_" . $i . "'] = new Array('" . $this->answerList[$i] . "', '" . $this->gradeList[$i] . "');\n";
$js .= " fillAnswerList['fill_" . $this->questionId . "_" . $i . "'] = new Array('" . $this->answerList[$i] . "', '" . $this->gradeList[$i] . "');\n";
}
$out .=
'</script>' . "\n"
. '<table width="100%">' . "\n\n"
$js .= '';
//'</script>' . "\n"
$html .= '<table width="100%">' . "\n\n"
. '<tr>' . "\n"
. '<td>' . "\n"
@ -284,13 +366,16 @@ class ScormAnswerFillInBlanks extends answerFillInBlanks
. '</table>' . "\n"
. '<p><small>' . get_lang('Fill in blanks') . '</small></p>' . "\n";
return $out;
return array($js,$html);
}
}
class ScormAnswerMatching extends answerMatching
/**
* This class handles the SCORM export of matching questions
*/
class ScormAnswerMatching extends Answer
{
/**
* Export the question part as a matrix-choice, with only one possible answer per line.
@ -298,6 +383,8 @@ class ScormAnswerMatching extends answerMatching
*/
function export()
{
$js = '';
$html = '';
// prepare list of right proposition to allow
// - easiest display
// - easiest randomisation if needed one day
@ -307,7 +394,7 @@ class ScormAnswerMatching extends answerMatching
// get max length of displayed array
$arrayLength = max( count($this->leftList), count($this->rightList) );
$out = '<table width="100%">' . "\n\n";
$html .= '<table width="100%">' . "\n\n";
$leftCpt = 1;
$rightCpt = 'A';
@ -347,7 +434,7 @@ class ScormAnswerMatching extends answerMatching
$rightHtml = '&nbsp;';
}
$out .=
$html .=
'<tr>' . "\n"
. '<td valign="top" width="40%">' . "\n" . $leftHtml . "\n" . '</td>' . "\n"
. '<td valign="top" width="20%">' . "\n" . $centerHtml . "\n" . '</td>' . "\n"
@ -359,11 +446,211 @@ class ScormAnswerMatching extends answerMatching
}
$out .=
$html .=
'</table>' . "\n"
. '<p><small>' . get_lang('Matching') . '</small></p>' . "\n";
return $out;
return array($js,$html);
}
}
/**
* This class handles the SCORM export of free-answer questions
*/
class ScormAnswerFree extends Answer
{
/**
* Export the text with missing words.
*
* As a side effect, it stores two lists in the class :
* the missing words and their respective weightings.
*
*/
function export()
{
$js = '';
$html = '';
// some javascript must be added for that kind of questions
$js .= '';
//'<script type="text/javascript" language="javascript">' . "\n";
$js .= '';
//'</script>' . "\n"
$html .= '<table width="100%">' . "\n\n"
. '<tr>' . "\n"
. '<td>' . "\n"
. $displayedAnswer . "\n"
. '</td>' . "\n"
. '</tr>' . "\n\n"
. '</table>' . "\n"
. '<p><small>' . get_lang('FreeAnswer') . '</small></p>' . "\n";
return array($js,$html);
}
}
/**
* This class handles the SCORM export of hotpot questions
*/
class ScormAnswerHotspot extends Answer
{
/**
* Returns the javascript code that goes with HotSpot exercises
* @return string The JavaScript code
*/
function get_js_header()
{
$header = "<script type=\"text/javascript\" src=\"hotspot/JavaScriptFlashGateway.js\"></script>
<script src=\"hotspot/hotspot.js\" type=\"text/javascript\"></script>
<script language=\"JavaScript\" type=\"text/javascript\">
<!--
// -----------------------------------------------------------------------------
// Globals
// Major version of Flash required
var requiredMajorVersion = 7;
// Minor version of Flash required
var requiredMinorVersion = 0;
// Minor version of Flash required
var requiredRevision = 0;
// the version of javascript supported
var jsVersion = 1.0;
// -----------------------------------------------------------------------------
// -->
</script>
<script language=\"VBScript\" type=\"text/vbscript\">
<!-- // Visual basic helper required to detect Flash Player ActiveX control version information
Function VBGetSwfVer(i)
on error resume next
Dim swControl, swVersion
swVersion = 0
set swControl = CreateObject(\"ShockwaveFlash.ShockwaveFlash.\" + CStr(i))
if (IsObject(swControl)) then
swVersion = swControl.GetVariable(\"\$version\")
end if
VBGetSwfVer = swVersion
End Function
// -->
</script>
<script language=\"JavaScript1.1\" type=\"text/javascript\">
<!-- // Detect Client Browser type
var isIE = (navigator.appVersion.indexOf(\"MSIE\") != -1) ? true : false;
var isWin = (navigator.appVersion.toLowerCase().indexOf(\"win\") != -1) ? true : false;
var isOpera = (navigator.userAgent.indexOf(\"Opera\") != -1) ? true : false;
jsVersion = 1.1;
// JavaScript helper required to detect Flash Player PlugIn version information
function JSGetSwfVer(i){
// NS/Opera version >= 3 check for Flash plugin in plugin array
if (navigator.plugins != null && navigator.plugins.length > 0) {
if (navigator.plugins[\"Shockwave Flash 2.0\"] || navigator.plugins[\"Shockwave Flash\"]) {
var swVer2 = navigator.plugins[\"Shockwave Flash 2.0\"] ? \" 2.0\" : \"\";
var flashDescription = navigator.plugins[\"Shockwave Flash\" + swVer2].description;
descArray = flashDescription.split(\" \");
tempArrayMajor = descArray[2].split(\".\");
versionMajor = tempArrayMajor[0];
versionMinor = tempArrayMajor[1];
if ( descArray[3] != \"\" ) {
tempArrayMinor = descArray[3].split(\"r\");
} else {
tempArrayMinor = descArray[4].split(\"r\");
}
versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
flashVer = versionMajor + \".\" + versionMinor + \".\" + versionRevision;
} else {
flashVer = -1;
}
}
// MSN/WebTV 2.6 supports Flash 4
else if (navigator.userAgent.toLowerCase().indexOf(\"webtv/2.6\") != -1) flashVer = 4;
// WebTV 2.5 supports Flash 3
else if (navigator.userAgent.toLowerCase().indexOf(\"webtv/2.5\") != -1) flashVer = 3;
// older WebTV supports Flash 2
else if (navigator.userAgent.toLowerCase().indexOf(\"webtv\") != -1) flashVer = 2;
// Can't detect in all other cases
else {
flashVer = -1;
}
return flashVer;
}
// When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision)
{
reqVer = parseFloat(reqMajorVer + \".\" + reqRevision);
// loop backwards through the versions until we find the newest version
for (i=25;i>0;i--) {
if (isIE && isWin && !isOpera) {
versionStr = VBGetSwfVer(i);
} else {
versionStr = JSGetSwfVer(i);
}
if (versionStr == -1 ) {
return false;
} else if (versionStr != 0) {
if(isIE && isWin && !isOpera) {
tempArray = versionStr.split(\" \");
tempString = tempArray[1];
versionArray = tempString .split(\",\");
} else {
versionArray = versionStr.split(\".\");
}
versionMajor = versionArray[0];
versionMinor = versionArray[1];
versionRevision = versionArray[2];
versionString = versionMajor + \".\" + versionRevision; // 7.0r24 == 7.24
versionNum = parseFloat(versionString);
// is the major.revision >= requested major.revision AND the minor version >= requested minor
if ( (versionMajor > reqMajorVer) && (versionNum >= reqVer) ) {
return true;
} else {
return ((versionNum >= reqVer && versionMinor >= reqMinorVer) ? true : false );
}
}
}
}
// -->
</script>";
return $header;
}
/**
* Export the text with missing words.
*
* As a side effect, it stores two lists in the class :
* the missing words and their respective weightings.
*
*/
function export()
{
$js = '';
$html = '';
// some javascript must be added for that kind of questions
$js .= '';
//'<script type="text/javascript" language="javascript">' . "\n";
$js .= '';
//'</script>' . "\n"
$html .= '<table width="100%">' . "\n\n"
. '<tr>' . "\n"
. '<td>' . "\n"
. $displayedAnswer . "\n"
. '</td>' . "\n"
. '</tr>' . "\n\n"
. '</table>' . "\n"
. '<p><small>' . get_lang('HotSpot') . '</small></p>' . "\n";
return array($js,$html);
}
}
?>

@ -0,0 +1,542 @@
<?php // $Id: $
if ( count( get_included_files() ) == 1 ) die( '---' );
/**
* @copyright (c) 2007 Dokeos
* @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
*
* @license http://www.gnu.org/copyleft/gpl.html (GPL) GENERAL PUBLIC LICENSE
*
* @author Claro Team <cvs@claroline.net>
* @author Yannick Warnier <yannick.warnier@dokeos.com>
*/
require dirname(__FILE__) . '/scorm_classes.php';
/*--------------------------------------------------------
Classes
--------------------------------------------------------*/
// answer types
define(UNIQUE_ANSWER, 1);
define(MULTIPLE_ANSWER, 2);
define(FILL_IN_BLANKS, 3);
define(MATCHING, 4);
define(FREE_ANSWER, 5);
define(HOT_SPOT, 6);
define(HOT_SPOT_ORDER, 7);
/**
* A SCORM item. It corresponds to a single question.
* This class allows export from Dokeos SCORM 1.2 format of a single question.
* It is not usable as-is, but must be subclassed, to support different kinds of questions.
*
* Every start_*() and corresponding end_*(), as well as export_*() methods return a string.
*
* @warning Attached files are NOT exported.
*/
class ScormAssessmentItem
{
var $question;
var $question_ident;
var $answer;
/**
* Constructor.
*
* @param $question The Question object we want to export.
*/
function ScormAssessmentItem($question,$standalone=false)
{
$this->question = $question;
//$this->answer = new Answer($question->id);
$this->answer = $this->question->setAnswer();
$this->questionIdent = "QST_" . $question->id ;
$this->standalone = $standalone;
//echo "<pre>".print_r($this,1)."</pre>";
}
/**
* Start the XML flow.
*
* This opens the <item> block, with correct attributes.
*
*/
function start_page()
{
global $charset;
$head = $foot = "";
if( $this->standalone)
{
/*
$head = '<?xml version="1.0" encoding="'.$charset.'" standalone="no"?>' . "\n";
*/
}
return $head.'<html>'. "\n";
}
/**
* End the XML flow, closing the </item> tag.
*
*/
function end_page()
{
return '</html>';
}
/**
* Start document header
*/
function start_header()
{
return '<head>'. "\n";
}
/**
* End document header
*/
function end_header()
{
return '</head>'. "\n";
}
/**
* Start the itemBody
*
*/
function start_js()
{
return '<script type="text/javascript" language="javascript">'. "\n";
}
/**
* Common JS functions
*/
function common_js()
{
$js = file_get_contents('../newscorm/js/api_wrapper.js');
$js .= 'var questions = new Array();' . "\n";
$js .= 'var questions_answers = new Array();' . "\n";
$js .= 'var questions_answers_correct = new Array();' . "\n";
$js .= 'var questions_types = new Array();' . "\n";
$js .= "\n" .
'/**
* Assigns any event handler to any element
* @param object Element on which the event is added
* @param string Name of event
* @param string Function to trigger on event
* @param boolean Capture the event and prevent
*/
function addEvent(elm, evType, fn, useCapture)
{ //by Scott Andrew
if(elm.addEventListener){
elm.addEventListener(evType, fn, useCapture);
return true;
} else if(elm.attachEvent) {
var r = elm.attachEvent(\'on\' + evType, fn);
return r;
} else {
elm[\'on\' + evType] = fn;
}
}
/**
* Adds the event listener
*/
function addListeners(e) {
loadPage();
/*var my_form = document.getElementById(\'dokeos_scorm_form\');
addEvent(my_form,\'submit\',checkAnswers,false);
*/
var my_button = document.getElementById(\'dokeos_scorm_submit\');
addEvent(my_button,\'click\',checkAnswers,false);
addEvent(my_button,\'change\',checkAnswers,false);
addEvent(window,\'unload\',unloadPage,false);
}'."\n";
$js .= '';
//$js .= 'addEvent(window,\'load\',loadPage,false);'."\n";
//$js .= 'addEvent(window,\'unload\',unloadPage,false);'."\n";
$js .= 'addEvent(window,\'load\',addListeners,false);'."\n";
return $js. "\n";
}
/**
* End the itemBody part.
*
*/
function end_js()
{
return '</script>'. "\n";
}
/**
* Start the itemBody
*
*/
function start_body()
{
return '<body>'. "\n".'<form id="dokeos_scorm_form" method="post" action="">'."\n";
}
/**
* End the itemBody part.
*
*/
function end_body()
{
return '<br /><input type="button" id="dokeos_scorm_submit" name="dokeos_scorm_submit" value="OK" /></form>'."\n".'</body>'. "\n";
}
/**
* Export the question as a SCORM Item.
*
* This is a default behaviour, some classes may want to override this.
*
* @param $standalone: Boolean stating if it should be exported as a stand-alone question
* @return A string, the XML flow for an Item.
*/
function export($standalone = false)
{
list($js,$html) = $this->question->export();
//list($js,$html) = $this->question->answer->export();
$res = $this->start_page($standalone)
. $this->start_header()
. $this->start_js()
. $this->common_js()
. $js
. $this->end_js()
. $this->end_header()
. $this->start_body()
// .$this->answer->imsExportResponsesDeclaration($this->questionIdent)
// . $this->start_item_body()
// . $this->answer->scormExportResponses($this->questionIdent, $this->question->question, $this->question->description, $this->question->picture)
// .$question
.$html
. $this->end_body()
. $this->end_page();
return $res;
}
}
/**
* This class represents an entire exercise to be exported in SCORM.
* It will be represented by a single <section> containing several <item>.
*
* Some properties cannot be exported, as SCORM does not support them :
* - type (one page or multiple pages)
* - start_date and end_date
* - max_attempts
* - show_answer
* - anonymous_attempts
*/
class ScormSection
{
var $exercise;
/**
* Constructor.
* @param $exe The Exercise instance to export
* @author Amand Tihon <amand@alrj.org>
*/
function ScormSection($exe)
{
$this->exercise = $exe;
}
function start_section()
{
$out = '<section ident="EXO_' . $this->exercise->selectId() . '" title="' . $this->exercise->selectTitle() . '">' . "\n";
return $out;
}
function end_section()
{
return "</section>\n";
}
function export_duration()
{
if ($max_time = $this->exercise->selectTimeLimit())
{
// return exercise duration in ISO8601 format.
$minutes = floor($max_time / 60);
$seconds = $max_time % 60;
return '<duration>PT' . $minutes . 'M' . $seconds . "S</duration>\n";
}
else
{
return '';
}
}
/**
* Export the presentation (Exercise's description)
* @author Amand Tihon <amand@alrj.org>
*/
function export_presentation()
{
$out = "<presentation_material><flow_mat><material>\n"
. " <mattext><![CDATA[" . $this->exercise->selectDescription() . "]]></mattext>\n"
. "</material></flow_mat></presentation_material>\n";
return $out;
}
/**
* Export the ordering information.
* Either sequential, through all questions, or random, with a selected number of questions.
* @author Amand Tihon <amand@alrj.org>
*/
function export_ordering()
{
$out = '';
if ($n = $this->exercise->getShuffle()) {
$out.= "<selection_ordering>"
. " <selection>\n"
. " <selection_number>" . $n . "</selection_number>\n"
. " </selection>\n"
. ' <order order_type="Random" />'
. "\n</selection_ordering>\n";
}
else
{
$out.= '<selection_ordering sequence_type="Normal">' . "\n"
. " <selection />\n"
. "</selection_ordering>\n";
}
return $out;
}
/**
* Export the questions, as a succession of <items>
* @author Amand Tihon <amand@alrj.org>
*/
function export_questions()
{
$out = "";
foreach ($this->exercise->selectQuestionList() as $q)
{
$out .= export_question($q, false);
}
return $out;
}
/**
* Export the exercise in SCORM.
*
* @param bool $standalone Wether it should include XML tag and DTD line.
* @return a string containing the XML flow
* @author Amand Tihon <amand@alrj.org>
*/
function export($standalone)
{
global $charset;
$head = $foot = "";
if ($standalone) {
$head = '<?xml version = "1.0" encoding = "' . $charset . '" standalone = "no"?>' . "\n"
. '<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">' . "\n"
. "<questestinterop>\n";
$foot = "</questestinterop>\n";
}
$out = $head
. $this->start_section()
. $this->export_duration()
. $this->export_presentation()
. $this->export_ordering()
. $this->export_questions()
. $this->end_section()
. $foot;
return $out;
}
}
/*
Some quick notes on identifiers generation.
The IMS format requires some blocks, like items, responses, feedbacks, to be uniquely
identified.
The unicity is mandatory in a single XML, of course, but it's prefered that the identifier stays
coherent for an entire site.
Here's the method used to generate those identifiers.
Question identifier :: "QST_" + <Question Id from the DB> + "_" + <Question numeric type>
Response identifier :: <Question identifier> + "_A_" + <Response Id from the DB>
Condition identifier :: <Question identifier> + "_C_" + <Response Id from the DB>
Feedback identifier :: <Question identifier> + "_F_" + <Response Id from the DB>
*/
/**
* A SCORM item. It corresponds to a single question.
* This class allows export from Dokeos to SCORM 1.2 format.
* It is not usable as-is, but must be subclassed, to support different kinds of questions.
*
* Every start_*() and corresponding end_*(), as well as export_*() methods return a string.
*
* @warning Attached files are NOT exported.
*/
class ScormItem
{
var $question;
var $question_ident;
var $answer;
/**
* Constructor.
*
* @param $question The Question object we want to export.
* @author Anamd Tihon
*/
function ScormItem($question)
{
$this->question = $question;
$this->answer = $question->answer;
$this->questionIdent = "QST_" . $question->selectId() ;
}
/**
* Start the XML flow.
*
* This opens the <item> block, with correct attributes.
*
* @author Amand Tihon <amand@alrj.org>
*/
function start_item()
{
return '<item title="' . htmlspecialchars($this->question->selectTitle()) . '" ident="' . $this->questionIdent . '">' . "\n";
}
/**
* End the XML flow, closing the </item> tag.
*
* @author Amand Tihon <amand@alrj.org>
*/
function end_item()
{
return "</item>\n";
}
/**
* Create the opening, with the question itself.
*
* This means it opens the <presentation> but doesn't close it, as this is the role of end_presentation().
* Inbetween, the export_responses from the subclass should have been called.
*
* @author Amand Tihon <amand@alrj.org>
*/
function start_presentation()
{
return '<presentation label="' . $this->questionIdent . '"><flow>' . "\n"
. '<material><mattext><![CDATA[' . $this->question->selectDescription() . "]]></mattext></material>\n";
}
/**
* End the </presentation> part, opened by export_header.
*
* @author Amand Tihon <amand@alrj.org>
*/
function end_presentation()
{
return "</flow></presentation>\n";
}
/**
* Start the response processing, and declare the default variable, SCORE, at 0 in the outcomes.
*
* @author Amand Tihon <amand@alrj.org>
*/
function start_processing()
{
return '<resprocessing><outcomes><decvar vartype="Integer" defaultval="0" /></outcomes>' . "\n";
}
/**
* End the response processing part.
*
* @author Amand Tihon <amand@alrj.org>
*/
function end_processing()
{
return "</resprocessing>\n";
}
/**
* Export the question as a SCORM Item.
*
* This is a default behaviour, some classes may want to override this.
*
* @param $standalone: Boolean stating if it should be exported as a stand-alone question
* @return A string, the XML flow for an Item.
* @author Amand Tihon <amand@alrj.org>
*/
function export($standalone = False)
{
global $charset;
$head = $foot = "";
if( $standalone )
{
$head = '<?xml version = "1.0" encoding = "'.$charset.'" standalone = "no"?>' . "\n"
. '<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">' . "\n"
. "<questestinterop>\n";
$foot = "</questestinterop>\n";
}
return $head
. $this->start_item()
. $this->start_presentation()
. $this->answer->imsExportResponses($this->questionIdent)
. $this->end_presentation()
. $this->start_processing()
. $this->answer->imsExportProcessing($this->questionIdent)
. $this->end_processing()
. $this->answer->imsExportFeedback($this->questionIdent)
. $this->end_item()
. $foot;
}
}
/*--------------------------------------------------------
Functions
--------------------------------------------------------*/
/**
* Send a complete exercise in SCORM format, from its ID
*
* @param int $exerciseId The exercise to exporte
* @param boolean $standalone Wether it should include XML tag and DTD line.
* @return The XML as a string, or an empty string if there's no exercise with given ID.
*/
function export_exercise($exerciseId, $standalone=true)
{
$exercise = new Exercise();
if (! $exercise->read($exerciseId))
{
return '';
}
$ims = new ScormSection($exercise);
$xml = $ims->export($standalone);
return $xml;
}
/**
* Returns the HTML + JS flow corresponding to one question
*
* @param int The question ID
* @param bool standalone (ie including XML tag, DTD declaration, etc)
*/
function export_question($questionId, $standalone=true)
{
$question = new ScormQuestion();
$qst = $question->read($questionId);
if( !$qst )
{
return '';
}
$question->id = $qst->id;
$question->type = $qst->type;
$question->question = $qst->question;
$question->description = $qst->description;
$question->weighting=$qst->weighting;
$question->position=$qst->position;
$question->picture=$qst->picture;
$assessmentItem = new ScormAssessmentItem($question);
//echo "<pre>".print_r($scorm,1)."</pre>";exit;
return $assessmentItem->export($standalone);
}
?>
Loading…
Cancel
Save