upgrade
This commit is contained in:
79
main/exercise/export/scorm/ScormAnswerFillInBlanks.php
Normal file
79
main/exercise/export/scorm/ScormAnswerFillInBlanks.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of fill-in-the-blanks questions.
|
||||
*/
|
||||
class ScormAnswerFillInBlanks 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.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
// get all enclosed answers
|
||||
$blankList = [];
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$blankList[] = '['.$answer.']';
|
||||
}
|
||||
|
||||
// splits text and weightings that are joined with the character '::'
|
||||
$listAnswerInfo = FillBlanks::getAnswerInfo($answer);
|
||||
//$switchableAnswerSet = $listAnswerInfo['switchable'];
|
||||
|
||||
// display empty [input] with the right width for student to fill it
|
||||
$answer = '';
|
||||
$answerList = [];
|
||||
for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) {
|
||||
// display the common words
|
||||
$answer .= $listAnswerInfo['common_words'][$i];
|
||||
// display the blank word
|
||||
$attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px';
|
||||
$answer .= FillBlanks::getFillTheBlankHtml(
|
||||
$this->questionJSId,
|
||||
$this->questionJSId + 1,
|
||||
'',
|
||||
$attributes,
|
||||
$answer,
|
||||
$listAnswerInfo,
|
||||
true,
|
||||
$i,
|
||||
'question_'.$this->questionJSId.'_fib_'.($i + 1)
|
||||
);
|
||||
$answerList[] = $i + 1;
|
||||
}
|
||||
|
||||
// display the last common word
|
||||
$answer .= $listAnswerInfo['common_words'][$i];
|
||||
|
||||
// because [] is parsed here we follow this procedure:
|
||||
// 1. find everything between the [ and ] tags
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
|
||||
foreach ($listAnswerInfo['weighting'] as $key => $weight) {
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.']['.($key + 1).'] = '.$weight.";\n";
|
||||
}
|
||||
|
||||
$wordList = "'".implode("', '", $listAnswerInfo['words'])."'";
|
||||
$answerList = "'".implode("', '", $answerList)."'";
|
||||
|
||||
$html = '<tr><td colspan="2"><table width="100%">';
|
||||
$html .= '<tr>
|
||||
<td>
|
||||
'.$answer.'
|
||||
</td>
|
||||
</tr></table></td></tr>';
|
||||
$js .= 'questions_answers['.$this->questionJSId.'] = new Array('.$answerList.');'."\n";
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = new Array('.$wordList.');'."\n";
|
||||
$js .= 'questions_types['.$this->questionJSId.'] = \'fib\';'."\n";
|
||||
$js .= $jstmpw;
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
98
main/exercise/export/scorm/ScormAnswerMatching.php
Normal file
98
main/exercise/export/scorm/ScormAnswerMatching.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
// prepare list of right proposition to allow
|
||||
// - easiest display
|
||||
// - easiest randomisation if needed one day
|
||||
// (here I use array_values to change array keys from $code1 $code2 ... to 0 1 ...)
|
||||
// get max length of displayed array
|
||||
$nbrAnswers = $this->selectNbrAnswers();
|
||||
$counter = 1;
|
||||
$questionId = $this->questionJSId;
|
||||
$jstmpw = 'questions_answers_ponderation['.$questionId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.'][0] = 0;'."\n";
|
||||
|
||||
// Options (A, B, C, ...) that will be put into the list-box
|
||||
$options = [];
|
||||
$letter = 'A';
|
||||
for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
|
||||
$answerCorrect = $this->isCorrect($answerId);
|
||||
$answer = $this->selectAnswer($answerId);
|
||||
$realAnswerId = $this->selectAutoId($answerId);
|
||||
if (!$answerCorrect) {
|
||||
$options[$realAnswerId]['Lettre'] = $letter;
|
||||
// answers that will be shown at the right side
|
||||
$options[$realAnswerId]['Reponse'] = $answer;
|
||||
$letter++;
|
||||
}
|
||||
}
|
||||
|
||||
$html = [];
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
|
||||
// Answers
|
||||
for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
|
||||
$identifier = 'question_'.$questionId.'_matching_';
|
||||
$answer = $this->selectAnswer($answerId);
|
||||
$answerCorrect = $this->isCorrect($answerId);
|
||||
$weight = $this->selectWeighting($answerId);
|
||||
$jstmp .= $answerId.',';
|
||||
|
||||
if ($answerCorrect) {
|
||||
$html[] = '<tr class="option_row">';
|
||||
$html[] = '<td width="40%" valign="top"> '.$answer.'</td>';
|
||||
$html[] = '<td width="20%" align="center"> ';
|
||||
$html[] = '<select name="'.$identifier.$counter.'" id="'.$identifier.$counter.'">';
|
||||
$html[] = ' <option value="0">--</option>';
|
||||
// fills the list-box
|
||||
foreach ($options as $key => $val) {
|
||||
$html[] = '<option value="'.$key.'">'.$val['Lettre'].'</option>';
|
||||
}
|
||||
|
||||
$html[] = '</select> </td>';
|
||||
$html[] = '<td width="40%" valign="top">';
|
||||
foreach ($options as $key => $val) {
|
||||
$html[] = '<b>'.$val['Lettre'].'.</b> '.$val['Reponse'].'<br />';
|
||||
}
|
||||
$html[] = '</td></tr>';
|
||||
|
||||
$jstmpc .= '['.$answerCorrect.','.$counter.'],';
|
||||
|
||||
$myWeight = explode('@', $weight);
|
||||
if (2 == count($myWeight)) {
|
||||
$weight = $myWeight[0];
|
||||
} else {
|
||||
$weight = $myWeight[0];
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$counter.'] = '.$weight.";\n";
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
$js .= 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');'."\n";
|
||||
$js .= 'questions_answers_correct['.$questionId.'] = new Array('.substr($jstmpc, 0, -1).');'."\n";
|
||||
$js .= 'questions_types['.$questionId.'] = \'matching\';'."\n";
|
||||
$js .= $jstmpw;
|
||||
|
||||
$htmlResult = '<tr><td colspan="2"><table id="question_'.$questionId.'" width="100%">';
|
||||
$htmlResult .= implode("\n", $html);
|
||||
$htmlResult .= '</table></td></tr>'."\n";
|
||||
|
||||
return [$js, $htmlResult];
|
||||
}
|
||||
}
|
||||
118
main/exercise/export/scorm/ScormAnswerMultipleChoice.php
Normal file
118
main/exercise/export/scorm/ScormAnswerMultipleChoice.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = [];
|
||||
$type = $this->getQuestionType();
|
||||
$questionId = $this->questionJSId;
|
||||
$jstmpw = 'questions_answers_ponderation['.$questionId.'] = new Array();';
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.'][0] = 0;';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.'] = new Array();';
|
||||
$html = [];
|
||||
|
||||
//not sure if we are going to export also the MULTIPLE_ANSWER_COMBINATION to SCORM
|
||||
//if ($type == MCMA || $type == MULTIPLE_ANSWER_COMBINATION ) {
|
||||
if (MCMA == $type) {
|
||||
$id = 1;
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$identifier = 'question_'.$questionId.'_multiple_'.$i;
|
||||
$html[] =
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier.'" id="'.$identifier.'" value="'.$i.'" type="checkbox" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier.'">'.Security::remove_XSS($this->answer[$i]).'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$jstmp .= $i.',';
|
||||
if ($this->correct[$i]) {
|
||||
$jstmpc .= $i.',';
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$i.'] = '.$this->weighting[$i].';';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.']['.$i.'] = '.$this->correct[$i].';';
|
||||
$id++;
|
||||
}
|
||||
$js[] = 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');'."\n";
|
||||
$js[] = 'questions_types['.$questionId.'] = \'mcma\';'."\n";
|
||||
$js[] = $jstmpw;
|
||||
} elseif (MULTIPLE_ANSWER_COMBINATION == $type) {
|
||||
$js = [];
|
||||
$id = 1;
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$identifier = 'question_'.$questionId.'_exact_'.$i;
|
||||
$html[] =
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier.'" id="'.$identifier.'" value="'.$i.'" type="checkbox" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier.'">'.Security::remove_XSS($this->answer[$i]).'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$jstmp .= $i.',';
|
||||
if ($this->correct[$i]) {
|
||||
$jstmpc .= $i.',';
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$i.'] = '.$this->weighting[$i].';';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.']['.$i.'] = '.$this->correct[$i].';';
|
||||
$id++;
|
||||
}
|
||||
$js[] = 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');';
|
||||
$js[] = 'questions_types['.$questionId.'] = "exact";';
|
||||
$js[] = $jstmpw;
|
||||
} else {
|
||||
$id = 1;
|
||||
$jstmp = '';
|
||||
$jstmpc = '';
|
||||
foreach ($this->answer as $i => $answer) {
|
||||
$identifier = 'question_'.$questionId.'_unique_'.$i;
|
||||
$identifier_name = 'question_'.$questionId.'_unique_answer';
|
||||
$html[] =
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier_name.'" id="'.$identifier.'" value="'.$i.'" type="checkbox"/>
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier.'">'.Security::remove_XSS($this->answer[$i]).'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
$jstmp .= $i.',';
|
||||
if ($this->correct[$i]) {
|
||||
$jstmpc .= $i;
|
||||
}
|
||||
$jstmpw .= 'questions_answers_ponderation['.$questionId.']['.$i.'] = '.$this->weighting[$i].';';
|
||||
$jstmpw .= 'questions_answers_correct['.$questionId.']['.$i.'] = '.$this->correct[$i].';';
|
||||
$id++;
|
||||
}
|
||||
$js[] = 'questions_answers['.$questionId.'] = new Array('.substr($jstmp, 0, -1).');';
|
||||
$js[] = 'questions_types['.$questionId.'] = \'mcua\';';
|
||||
$js[] = $jstmpw;
|
||||
}
|
||||
|
||||
$htmlResult = '<tr><td colspan="2"><table id="question_'.$questionId.'" width="100%">';
|
||||
$htmlResult .= implode("\n", $html);
|
||||
$htmlResult .= '</table></td></tr>';
|
||||
|
||||
$js = implode("\n", $js);
|
||||
|
||||
return [$js, $htmlResult];
|
||||
}
|
||||
}
|
||||
55
main/exercise/export/scorm/ScormAnswerTrueFalse.php
Normal file
55
main/exercise/export/scorm/ScormAnswerTrueFalse.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* This class handles the SCORM export of true/false questions.
|
||||
*/
|
||||
class ScormAnswerTrueFalse extends Answer
|
||||
{
|
||||
/**
|
||||
* Return the XML flow for the possible answers.
|
||||
* That's one <response_lid>, containing several <flow_label>.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
$html = '<tr><td colspan="2"><table width="100%">';
|
||||
$identifier = 'question_'.$this->questionJSId.'_tf';
|
||||
$identifier_true = $identifier.'_true';
|
||||
$identifier_false = $identifier.'_false';
|
||||
$html .=
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier_true.'" id="'.$identifier_true.'" value="'.$this->trueGrade.'" type="radio" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier_true.'">'.get_lang('True').'</label>
|
||||
</td>
|
||||
</tr>';
|
||||
$html .=
|
||||
'<tr>
|
||||
<td align="center" width="5%">
|
||||
<input name="'.$identifier_false.'" id="'.$identifier_false.'" value="'.$this->falseGrade.'" type="radio" />
|
||||
</td>
|
||||
<td width="95%">
|
||||
<label for="'.$identifier_false.'">'.get_lang('False').'</label>
|
||||
</td>
|
||||
</tr></table></td></tr>';
|
||||
$js .= 'questions_answers['.$this->questionJSId.'] = new Array(\'true\',\'false\');'."\n";
|
||||
$js .= 'questions_types['.$this->questionJSId.'] = \'tf\';'."\n";
|
||||
if ('TRUE' === $this->response) {
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = new Array(\'true\');'."\n";
|
||||
} else {
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = new Array(\'false\');'."\n";
|
||||
}
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][1] = '.$this->weighting[1].";\n";
|
||||
$js .= $jstmpw;
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
159
main/exercise/export/scorm/ScormAssessmentItem.php
Normal file
159
main/exercise/export/scorm/ScormAssessmentItem.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* A SCORM item. It corresponds to a single question.
|
||||
* This class allows export from Chamilo 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.
|
||||
*
|
||||
* Attached files are NOT exported.
|
||||
*/
|
||||
class ScormAssessmentItem
|
||||
{
|
||||
public $question;
|
||||
public $question_ident;
|
||||
public $answer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param ScormQuestion $question the Question object we want to export
|
||||
*/
|
||||
public function __construct($question)
|
||||
{
|
||||
$this->question = $question;
|
||||
$this->question->setAnswer();
|
||||
$this->questionIdent = 'QST_'.$question->iid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the XML flow.
|
||||
*
|
||||
* This opens the <item> block, with correct attributes.
|
||||
*/
|
||||
public function start_page()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the XML flow, closing the </item> tag.
|
||||
*/
|
||||
public function end_page()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '</html>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start document header.
|
||||
*/
|
||||
public function start_header()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '<head>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Print CSS inclusion.
|
||||
*/
|
||||
public function css()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End document header.
|
||||
*/
|
||||
public function end_header()
|
||||
{
|
||||
// if ($this->standalone) {
|
||||
// return '</head>';
|
||||
// }
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_js()
|
||||
{
|
||||
return '<script type="text/javascript" src="assets/api_wrapper.js"></script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_js()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '</script>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_body()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '<body><form id="dokeos_scorm_form" method="post" action="">';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_body()
|
||||
{
|
||||
/*if ($this->standalone) {
|
||||
return '<br /><input class="btn" type="button" id="dokeos_scorm_submit" name="dokeos_scorm_submit" value="OK" /></form></body>';
|
||||
}*/
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the question as a SCORM Item.
|
||||
* This is a default behaviour, some classes may want to override this.
|
||||
*
|
||||
* @return string|array a string, the XML flow for an Item
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
list($js, $html) = $this->question->export();
|
||||
/*if ($this->standalone) {
|
||||
$res = $this->start_page()
|
||||
.$this->start_header()
|
||||
.$this->css()
|
||||
.$this->start_js()
|
||||
.$this->common_js()
|
||||
.$js
|
||||
.$this->end_js()
|
||||
.$this->end_header()
|
||||
.$this->start_body()
|
||||
.$html
|
||||
.$this->end_body()
|
||||
.$this->end_page();
|
||||
|
||||
return $res;
|
||||
} else {
|
||||
return [$js, $html];
|
||||
}*/
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
216
main/exercise/export/scorm/ScormExercise.php
Normal file
216
main/exercise/export/scorm/ScormExercise.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
use Chamilo\CourseBundle\Entity\CQuiz;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @author Julio Montoya
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
class ScormExercise
|
||||
{
|
||||
public $exercise;
|
||||
public $standalone;
|
||||
|
||||
/**
|
||||
* ScormExercise constructor.
|
||||
*
|
||||
* @param Exercise $exe
|
||||
* @param bool $standalone
|
||||
*/
|
||||
public function __construct($exe, $standalone)
|
||||
{
|
||||
$this->exercise = $exe;
|
||||
$this->standalone = $standalone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the XML flow.
|
||||
*
|
||||
* This opens the <item> block, with correct attributes.
|
||||
*/
|
||||
public function startPage()
|
||||
{
|
||||
$charset = 'UTF-8';
|
||||
|
||||
return '<?xml version="1.0" encoding="'.$charset.'" standalone="no"?><html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the XML flow, closing the </item> tag.
|
||||
*/
|
||||
public function end_page()
|
||||
{
|
||||
return '</html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start document header.
|
||||
*/
|
||||
public function start_header()
|
||||
{
|
||||
return '<head>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Common JS functions.
|
||||
*/
|
||||
public function common_js()
|
||||
{
|
||||
$js = file_get_contents(api_get_path(SYS_CODE_PATH).'exercise/export/scorm/common.js');
|
||||
|
||||
return $js."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_js()
|
||||
{
|
||||
return '</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
public function start_body()
|
||||
{
|
||||
return '<body>'.
|
||||
'<h1>'.$this->exercise->selectTitle().'</h1><p>'.$this->exercise->selectDescription().'</p>'.
|
||||
'<form id="chamilo_scorm_form" method="post" action="">'.
|
||||
'<table width="100%">';
|
||||
}
|
||||
|
||||
/**
|
||||
* End the itemBody part.
|
||||
*/
|
||||
public function end_body()
|
||||
{
|
||||
$button = '<input
|
||||
id="chamilo_scorm_submit"
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
name="chamilo_scorm_submit"
|
||||
value="OK" />';
|
||||
|
||||
return '</table><br />'.$button.'</form></body>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the question as a SCORM Item.
|
||||
*
|
||||
* This is a default behaviour, some classes may want to override this.
|
||||
*
|
||||
* @return string string, the XML flow for an Item
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
global $charset;
|
||||
|
||||
/*$head = '';
|
||||
if ($this->standalone) {
|
||||
$head = '<?xml version = "1.0" encoding = "'.$charset.'" standalone = "no"?>'."\n"
|
||||
.'<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">'."\n";
|
||||
}*/
|
||||
|
||||
list($js, $html) = $this->exportQuestions();
|
||||
|
||||
return $this->startPage()
|
||||
.$this->start_header()
|
||||
.$this->css()
|
||||
.$this->globalAssets()
|
||||
.$this->start_js()
|
||||
.$this->common_js()
|
||||
.$js
|
||||
.$this->end_js()
|
||||
.$this->end_header()
|
||||
.$this->start_body()
|
||||
.$html
|
||||
.$this->end_body()
|
||||
.$this->end_page();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the questions, as a succession of <items>.
|
||||
*
|
||||
* @author Amand Tihon <amand@alrj.org>
|
||||
*/
|
||||
public function exportQuestions()
|
||||
{
|
||||
$js = $html = '';
|
||||
$encoders = [new JsonEncoder()];
|
||||
$normalizers = [new ObjectNormalizer()];
|
||||
|
||||
$em = Database::getManager();
|
||||
// Export cquiz data
|
||||
/** @var CQuiz $exercise */
|
||||
$exercise = $em->find('ChamiloCourseBundle:CQuiz', $this->exercise->iid);
|
||||
$exercise->setDescription('');
|
||||
$exercise->setTextWhenFinished('');
|
||||
|
||||
$serializer = new Serializer($normalizers, $encoders);
|
||||
$jsonContent = $serializer->serialize($exercise, 'json');
|
||||
$js .= "var exerciseInfo = JSON.parse('".$jsonContent."');\n";
|
||||
|
||||
$counter = 0;
|
||||
$scormQuestion = new ScormQuestion();
|
||||
foreach ($this->exercise->selectQuestionList() as $q) {
|
||||
list($jstmp, $htmltmp) = $scormQuestion->exportQuestionToScorm($q, $counter);
|
||||
$js .= $jstmp."\n";
|
||||
$html .= $htmltmp."\n";
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
|
||||
/**
|
||||
* Print CSS inclusion.
|
||||
*/
|
||||
private function css()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* End document header.
|
||||
*/
|
||||
private function end_header()
|
||||
{
|
||||
return '</head>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the itemBody.
|
||||
*/
|
||||
private function start_js()
|
||||
{
|
||||
return '<script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function globalAssets()
|
||||
{
|
||||
$assets = '<script type="text/javascript" src="assets/jquery/jquery.min.js"></script>'."\n";
|
||||
$assets .= '<script type="text/javascript" src="assets/api_wrapper.js"></script>'."\n";
|
||||
$assets .= '<link href="assets/bootstrap/bootstrap.min.css" rel="stylesheet" media="screen" type="text/css" />';
|
||||
|
||||
return $assets;
|
||||
}
|
||||
}
|
||||
221
main/exercise/export/scorm/ScormQuestion.php
Normal file
221
main/exercise/export/scorm/ScormQuestion.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Claro Team <cvs@claroline.net>
|
||||
* @author Yannick Warnier <yannick.warnier@beeznest.com>
|
||||
*/
|
||||
class ScormQuestion extends Question
|
||||
{
|
||||
public $js_id;
|
||||
public $answer;
|
||||
|
||||
/**
|
||||
* ScormQuestion constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML + JS flow corresponding to one question.
|
||||
*
|
||||
* @param int $questionId The question ID
|
||||
* @param int $jsId The JavaScript ID for this question.
|
||||
* Due to the nature of interactions, we must have a natural sequence for
|
||||
* questions in the generated JavaScript.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function exportQuestionToScorm(
|
||||
$questionId,
|
||||
$jsId
|
||||
) {
|
||||
$question = self::read($questionId);
|
||||
if (!$question) {
|
||||
return '';
|
||||
}
|
||||
$this->iid = $question->iid;
|
||||
$this->js_id = $jsId;
|
||||
$this->type = $question->type;
|
||||
$this->question = $question->question;
|
||||
$this->description = $question->description;
|
||||
$this->weighting = $question->weighting;
|
||||
$this->position = $question->position;
|
||||
$this->picture = $question->picture;
|
||||
$assessmentItem = new ScormAssessmentItem($this);
|
||||
|
||||
return $assessmentItem->export();
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the correct answer class and create answer.
|
||||
*/
|
||||
public function setAnswer()
|
||||
{
|
||||
switch ($this->type) {
|
||||
case MCUA:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case MCMA:
|
||||
case GLOBAL_MULTIPLE_ANSWER:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case TF:
|
||||
$this->answer = new ScormAnswerTrueFalse($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case FIB:
|
||||
$this->answer = new ScormAnswerFillInBlanks($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case MATCHING:
|
||||
case MATCHING_DRAGGABLE:
|
||||
case DRAGGABLE:
|
||||
$this->answer = new ScormAnswerMatching($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case ORAL_EXPRESSION:
|
||||
case FREE_ANSWER:
|
||||
$this->answer = new ScormAnswerFree($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case HOT_SPOT:
|
||||
case HOT_SPOT_COMBINATION:
|
||||
$this->answer = new ScormAnswerHotspot($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case MULTIPLE_ANSWER_COMBINATION:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case HOT_SPOT_ORDER:
|
||||
$this->answer = new ScormAnswerHotspot($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
case HOT_SPOT_DELINEATION:
|
||||
$this->answer = new ScormAnswerHotspot($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
// not supported
|
||||
case UNIQUE_ANSWER_NO_OPTION:
|
||||
case MULTIPLE_ANSWER_TRUE_FALSE:
|
||||
case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE:
|
||||
case UNIQUE_ANSWER_IMAGE:
|
||||
case CALCULATED_ANSWER:
|
||||
$this->answer = new ScormAnswerMultipleChoice($this->iid);
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
default:
|
||||
$this->answer = new stdClass();
|
||||
$this->answer->questionJSId = $this->js_id;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$html = $this->getQuestionHTML();
|
||||
$js = $this->getQuestionJS();
|
||||
|
||||
if (is_object($this->answer) && $this->answer instanceof Answer) {
|
||||
list($js2, $html2) = $this->answer->export();
|
||||
$js .= $js2;
|
||||
$html .= $html2;
|
||||
} else {
|
||||
throw new \Exception('Question not supported. Exercise: '.$this->selectTitle());
|
||||
}
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAnswersForm($form)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAnswersCreation($form, $exercise)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML-formatted question.
|
||||
*/
|
||||
public function getQuestionHTML()
|
||||
{
|
||||
$title = $this->selectTitle();
|
||||
$description = $this->selectDescription();
|
||||
$cols = 2;
|
||||
|
||||
return '<tr>
|
||||
<td colspan="'.$cols.'" id="question_'.$this->iid.'_title" valign="middle" style="background-color:#d6d6d6;">
|
||||
'.$title.'
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" colspan="'.$cols.'">
|
||||
<i>'.$description.'</i>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JavaScript code bound to the question.
|
||||
*/
|
||||
public function getQuestionJS()
|
||||
{
|
||||
$weight = $this->selectWeighting();
|
||||
$js = '
|
||||
questions.push('.$this->js_id.');
|
||||
$(function() {
|
||||
if (exerciseInfo.randomAnswers == true) {
|
||||
$("#question_'.$this->js_id.'").shuffleRows();
|
||||
}
|
||||
});';
|
||||
$js .= "\n";
|
||||
|
||||
switch ($this->type) {
|
||||
case ORAL_EXPRESSION:
|
||||
/*$script = file_get_contents(api_get_path(LIBRARY_PATH) . 'javascript/rtc/RecordRTC.js');
|
||||
$script .= file_get_contents(api_get_path(LIBRARY_PATH) . 'wami-recorder/recorder.js');
|
||||
$script .= file_get_contents(api_get_path(LIBRARY_PATH) . 'wami-recorder/gui.js');
|
||||
$js .= $script;*/
|
||||
break;
|
||||
case HOT_SPOT:
|
||||
case HOT_SPOT_COMBINATION:
|
||||
//put the max score to 0 to avoid discounting the points of
|
||||
//non-exported quiz types in the SCORM
|
||||
$weight = 0;
|
||||
break;
|
||||
}
|
||||
$js .= 'questions_score_max['.$this->js_id.'] = '.$weight.';';
|
||||
|
||||
return $js;
|
||||
}
|
||||
}
|
||||
25
main/exercise/export/scorm/common.js
Normal file
25
main/exercise/export/scorm/common.js
Normal file
@@ -0,0 +1,25 @@
|
||||
var questions = new Array();
|
||||
var questions_answers = new Array();
|
||||
var questions_answers_correct = new Array();
|
||||
var questions_types = new Array();
|
||||
var questions_score_max = new Array();
|
||||
var questions_answers_ponderation = new Array();
|
||||
|
||||
/**
|
||||
* Adds the event listener
|
||||
*/
|
||||
function addListeners(e) {
|
||||
loadPage();
|
||||
var myButton = document.getElementById('chamilo_scorm_submit');
|
||||
addEvent(myButton, 'click', doQuit, false);
|
||||
addEvent(myButton, 'click', disableButton, false);
|
||||
addEvent(window, 'unload', unloadPage, false);
|
||||
}
|
||||
|
||||
/** Disables the submit button on SCORM result submission **/
|
||||
function disableButton() {
|
||||
var mybtn = document.getElementById('chamilo_scorm_submit');
|
||||
mybtn.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
addEvent(window,'load', addListeners, false);
|
||||
144
main/exercise/export/scorm/scorm_classes.php
Normal file
144
main/exercise/export/scorm/scorm_classes.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = '';
|
||||
$identifier = 'question_'.$this->questionJSId.'_free';
|
||||
// currently the free answers cannot be displayed, so ignore the textarea
|
||||
$html = '<tr><td colspan="2">';
|
||||
$type = $this->getQuestionType();
|
||||
|
||||
if (ORAL_EXPRESSION == $type) {
|
||||
/*
|
||||
$template = new Template('');
|
||||
$template->assign('directory', '/tmp/');
|
||||
$template->assign('user_id', api_get_user_id());
|
||||
|
||||
$layout = $template->get_template('document/record_audio.tpl');
|
||||
$html .= $template->fetch($layout);*/
|
||||
|
||||
$html = '<tr><td colspan="2">'.get_lang('ThisItemIsNotExportable').'</td></tr>';
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
|
||||
$html .= '<textarea minlength="20" name="'.$identifier.'" id="'.$identifier.'" ></textarea>';
|
||||
$html .= '</td></tr>';
|
||||
$js .= 'questions_answers['.$this->questionJSId.'] = new Array();';
|
||||
$js .= 'questions_answers_correct['.$this->questionJSId.'] = "";';
|
||||
$js .= 'questions_types['.$this->questionJSId.'] = \'free\';';
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = "0";';
|
||||
$js .= $jstmpw;
|
||||
|
||||
return [$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
|
||||
*/
|
||||
public function get_js_header()
|
||||
{
|
||||
$header = '<script>';
|
||||
$header .= file_get_contents(api_get_path(SYS_CODE_PATH).'inc/lib/javascript/hotspot/js/hotspot.js');
|
||||
$header .= '</script>';
|
||||
|
||||
if ($this->standalone) {
|
||||
//because this header closes so many times the <script> tag, we have to reopen our own
|
||||
$header .= '<script>';
|
||||
$header .= 'questions_answers['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_answers_correct['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_types['.$this->questionJSId.'] = \'hotspot\';'."\n";
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][1] = 0;'.";\n";
|
||||
$header .= $jstmpw;
|
||||
} else {
|
||||
$header = '';
|
||||
$header .= 'questions_answers['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_answers_correct['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$header .= 'questions_types['.$this->questionJSId.'] = \'hotspot\';'."\n";
|
||||
$jstmpw = 'questions_answers_ponderation['.$this->questionJSId.'] = new Array();'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][0] = 0;'."\n";
|
||||
$jstmpw .= 'questions_answers_ponderation['.$this->questionJSId.'][1] = 0;'."\n";
|
||||
$header .= $jstmpw;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$js = $this->get_js_header();
|
||||
$html = '<tr><td colspan="2"><table width="100%">';
|
||||
// some javascript must be added for that kind of questions
|
||||
$html .= '';
|
||||
|
||||
// Get the answers, make a list
|
||||
$nbrAnswers = $this->selectNbrAnswers();
|
||||
|
||||
$answerList = '<div
|
||||
style="padding: 10px;
|
||||
margin-left: -8px;
|
||||
border: 1px solid #4271b5;
|
||||
height: 448px;
|
||||
width: 200px;"><b>'.get_lang('HotspotZones').'</b><ol>';
|
||||
for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
|
||||
$answerList .= '<li>'.$this->selectAnswer($answerId).'</li>';
|
||||
}
|
||||
$answerList .= '</ol></div>';
|
||||
$relPath = api_get_path(REL_PATH);
|
||||
$html .= <<<HTML
|
||||
<tr>
|
||||
<td>
|
||||
<div id="hotspot-{$this->questionJSId}"></div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentListener', function () {
|
||||
new HotspotQuestion({
|
||||
questionId: {$this->questionJSId},
|
||||
selector: '#hotspot-{$this->questionJSId}',
|
||||
for: 'user',
|
||||
relPath: '$relPath'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</td>
|
||||
<td>
|
||||
$answerList
|
||||
</td>
|
||||
<tr>
|
||||
HTML;
|
||||
$html .= '</table></td></tr>';
|
||||
|
||||
// currently the free answers cannot be displayed, so ignore the textarea
|
||||
$html = '<tr><td colspan="2">'.get_lang('ThisItemIsNotExportable').'</td></tr>';
|
||||
|
||||
return [$js, $html];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user