Fixing qti import total question weight bug see BT#7780

Fixing export to QTI
1.9.x
Julio Montoya 11 years ago
parent f437236b69
commit 4f9c0ebeea
  1. 45
      main/exercice/exercice.php
  2. 315
      main/exercice/export/exercise_import.inc.php
  3. 177
      main/exercice/export/qti2/qti2_classes.php
  4. 165
      main/exercice/export/qti2/qti2_export.php
  5. 22
      main/inc/lib/fckeditor/editor/plugins/ajaxfilemanager/inc/class.pagination.php

@ -1,5 +1,4 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Exercise list: This script shows the list of exercises for administrators and students.
@ -122,30 +121,40 @@ if (!empty($gradebook) && $gradebook == 'view') {
}
$nameTools = get_lang('Exercices');
$errorXmlExport = null;
if ($is_allowedToEdit && !empty($choice) && $choice == 'exportqti2') {
require_once 'export/qti2/qti2_export.php';
$export = export_exercise($exerciseId, true);
require_once api_get_path(LIBRARY_PATH).'pclzip/pclzip.lib.php';
$archive_path = api_get_path(SYS_ARCHIVE_PATH);
$temp_dir_short = api_get_unique_id();
$temp_zip_dir = $archive_path."/".$temp_dir_short;
if (!is_dir($temp_zip_dir))
$temp_zip_dir = $archive_path.$temp_dir_short;
if (!is_dir($temp_zip_dir)) {
mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
}
$temp_zip_file = $temp_zip_dir."/".api_get_unique_id().".zip";
$temp_xml_file = $temp_zip_dir."/qti2export_".$exerciseId.'.xml';
file_put_contents($temp_xml_file, $export);
$zip_folder = new PclZip($temp_zip_file);
$zip_folder->add($temp_xml_file, PCLZIP_OPT_REMOVE_ALL_PATH);
$name = 'qti2_export_'.$exerciseId.'.zip';
//DocumentManager::string_send_for_download($export,true,'qti2export_'.$exerciseId.'.xml');
DocumentManager :: file_send_for_download($temp_zip_file, true, $name);
unlink($temp_zip_file);
unlink($temp_xml_file);
rmdir($temp_zip_dir);
exit; //otherwise following clicks may become buggy
$xmlReader = new XMLReader();
$xmlReader->open($temp_xml_file);
$xmlReader->setParserProperty(XMLReader::VALIDATE, true);
$isValid = $xmlReader->isValid();
if ($isValid) {
$zip_folder = new PclZip($temp_zip_file);
$zip_folder->add($temp_xml_file, PCLZIP_OPT_REMOVE_ALL_PATH);
$name = 'qti2_export_'.$exerciseId.'.zip';
DocumentManager::file_send_for_download($temp_zip_file, true, $name);
unlink($temp_zip_file);
unlink($temp_xml_file);
rmdir($temp_zip_dir);
exit; //otherwise following clicks may become buggy
} else {
$errorXmlExport = Display :: return_message(get_lang('ErrorWritingXMLFile'), 'error');
}
}
$htmlHeadXtra[] = '<script>
@ -195,14 +204,16 @@ event_access_tool(TOOL_QUIZ);
// Tool introduction
Display :: display_introduction_section(TOOL_QUIZ);
if (!empty($errorXmlExport)) {
echo $errorXmlExport;
}
HotPotGCt($documentPath, 1, api_get_user_id());
// only for administrator
if ($is_allowedToEdit) {
if (!empty($choice)) {
// All test choice, clean all test's results
if ($choice == 'clean_all_test') {
$check = Security::check_token('get');
@ -649,7 +660,7 @@ if (!empty($exercise_list)) {
}
}
// Export qti ...
$actions .= Display::url(Display::return_icon('export_qti2.png', 'IMS/QTI', '', ICON_SIZE_SMALL), 'exercice.php?choice=exportqti2&exerciseId='.$row['id']);
$actions .= Display::url(Display::return_icon('export_qti2.png', 'IMS/QTI', '', ICON_SIZE_SMALL), 'exercice.php?choice=exportqti2&exerciseId='.$row['id'].'&'.api_get_cidreq());
} else {
// not session
$actions = Display::return_icon('edit_na.png', get_lang('ExerciseEditionNotAvailableInSession'));

@ -109,7 +109,7 @@ function import_exercise($file)
$file_found = false;
$operation = false;
$result = false;
$filePath = null;
// parse every subdirectory to search xml question files
while (false !== ($file = readdir($exerciseHandle))) {
if (is_dir($baseWorkDir . '/' . $file) && $file != "." && $file != "..") {
@ -118,23 +118,32 @@ function import_exercise($file)
while (false !== ($questionFile = readdir($questionHandle))) {
if (preg_match('/.xml$/i', $questionFile)) {
$result = parse_file($baseWorkDir, $file, $questionFile);
$filePath = $baseWorkDir.$file;
$file_found = true;
}
}
} elseif (preg_match('/.xml$/i', $file)) {
// Else ignore file
$result = parse_file($baseWorkDir, '', $file);
$filePath = $baseWorkDir.'/'.$file;
$file_found = true;
} // else ignore file
}
}
if (!$file_found) {
Display :: display_error_message(get_lang('No XML file found in the zip'));
return false;
}
if ($result == false ) {
if ($result == false) {
return false;
}
$doc = new DOMDocument();
$doc->load($filePath);
$encoding = $doc->encoding;
// 1. Create exercise.
$exercise = new Exercise();
$exercise->exercise = $exercise_info['name'];
@ -143,15 +152,16 @@ function import_exercise($file)
$last_exercise_id = $exercise->selectId();
if (!empty($last_exercise_id)) {
// For each question found...
foreach ($exercise_info['question'] as $key => $question_array) {
foreach ($exercise_info['question'] as $question_array) {
//2. Create question
$question = new Ims2Question();
$question->type = $question_array['type'];
$question->setAnswer();
$question->updateTitle($question_array['title']); // question ...
$question->updateTitle(formatText($question_array['title']));
//$question->updateDescription($question_array['title']);
$type = $question->selectType();
$question->type = constant($type); // type ...
$question->save($last_exercise_id); // save computed grade
$question->type = constant($type);
$question->save($last_exercise_id);
$last_question_id = $question->selectId();
//3. Create answer
$answer = new Answer($last_question_id);
@ -161,9 +171,9 @@ function import_exercise($file)
$split = explode('_', $key);
$i = $split[1];
// Answer
$answer->new_answer[$i] = $answers['value'];
$answer->new_answer[$i] = formatText($answers['value']);
// Comment
$answer->new_comment[$i] = isset($answers['feedback']) ? $answers['feedback'] : null;
$answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null;
// Position
$answer->new_position[$i] = $i;
// Correct answers
@ -188,6 +198,13 @@ function import_exercise($file)
}
return $operation;
}
/**
* We assume the file charset is UTF8
**/
function formatText($text)
{
return api_html_entity_decode($text);
}
function parse_file($exercisePath, $file, $questionFile)
{
@ -199,6 +216,7 @@ function parse_file($exercisePath, $file, $questionFile)
$questionTempDir = $exercisePath . '/' . $file . '/';
$questionFilePath = $questionTempDir . $questionFile;
if (!($fp = fopen($questionFilePath, 'r'))) {
Display :: display_error_message(get_lang('Error opening question\'s XML file'));
return false;
@ -245,15 +263,15 @@ function parse_file($exercisePath, $file, $questionFile)
"RESPONSECONDITION",
"RESPONSEIF"
);
$question_format_supported = true;
$question_format_supported = true;
$xml_parser = xml_parser_create();
xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, false);
xml_set_element_handler($xml_parser, 'startElement', 'endElement');
xml_set_character_data_handler($xml_parser, 'elementData');
if (!xml_parse($xml_parser, $data, feof($fp))) {
// if reading of the xml file in not successfull :
// if reading of the xml file in not successful :
// set errorFound, set error msg, break while statement
Display :: display_error_message(get_lang('Error reading XML file'));
return false;
@ -329,143 +347,89 @@ function startElement($parser, $name, $attributes) {
$current_question_item_body .= "<BR/>";
}
}
}
switch ($current_element) {
case 'ASSESSMENTITEM' :
{
//retrieve current question
$current_question_ident = $attributes['IDENTIFIER'];
$exercise_info['question'][$current_question_ident] = array ();
$exercise_info['question'][$current_question_ident]['answer'] = array ();
$exercise_info['question'][$current_question_ident]['correct_answers'] = array ();
$exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE'];
$exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
}
break;
case 'SECTION' :
{
//retrieve exercise name
$exercise_info['name'] = $attributes['TITLE'];
}
//retrieve current question
$current_question_ident = $attributes['IDENTIFIER'];
$exercise_info['question'][$current_question_ident] = array ();
$exercise_info['question'][$current_question_ident]['answer'] = array ();
$exercise_info['question'][$current_question_ident]['correct_answers'] = array ();
$exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE'];
$exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
break;
case 'SECTION':
//retrieve exercise name
$exercise_info['name'] = $attributes['TITLE'];
break;
case 'RESPONSEDECLARATION' :
{
//retrieve question type
if ("multiple" == $attributes['CARDINALITY']) {
$exercise_info['question'][$current_question_ident]['type'] = 'MCMA';
$cardinality = 'multiple';
}
if ("single" == $attributes['CARDINALITY']) {
$exercise_info['question'][$current_question_ident]['type'] = 'MCUA';
$cardinality = 'single';
}
//needed for FIB
$current_answer_id = $attributes['IDENTIFIER'];
}
case 'RESPONSEDECLARATION':
// Retrieve question type
if ("multiple" == $attributes['CARDINALITY']) {
$exercise_info['question'][$current_question_ident]['type'] = 'MCMA';
$cardinality = 'multiple';
}
if ("single" == $attributes['CARDINALITY']) {
$exercise_info['question'][$current_question_ident]['type'] = 'MCUA';
$cardinality = 'single';
}
//needed for FIB
$current_answer_id = $attributes['IDENTIFIER'];
break;
case 'INLINECHOICEINTERACTION' :
{
$exercise_info['question'][$current_question_ident]['type'] = 'FIB';
$exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL';
$current_answer_id = $attributes['RESPONSEIDENTIFIER'];
}
$exercise_info['question'][$current_question_ident]['type'] = 'FIB';
$exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL';
$current_answer_id = $attributes['RESPONSEIDENTIFIER'];
break;
case 'INLINECHOICE' :
{
$current_inlinechoice_id = $attributes['IDENTIFIER'];
}
$current_inlinechoice_id = $attributes['IDENTIFIER'];
break;
case 'TEXTENTRYINTERACTION' :
{
$exercise_info['question'][$current_question_ident]['type'] = 'FIB';
$exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL';
$exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
//replace claroline tags
}
case 'TEXTENTRYINTERACTION':
$exercise_info['question'][$current_question_ident]['type'] = 'FIB';
$exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL';
$exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
//replace claroline tags
break;
case 'MATCHINTERACTION' :
{
$exercise_info['question'][$current_question_ident]['type'] = 'MATCHING';
}
case 'MATCHINTERACTION':
$exercise_info['question'][$current_question_ident]['type'] = 'MATCHING';
break;
case 'SIMPLEMATCHSET' :
{
if (!isset ($current_match_set)) {
$current_match_set = 1;
} else {
$current_match_set++;
}
$exercise_info['question'][$current_question_ident]['answer'][$current_match_set] = array ();
}
if (!isset ($current_match_set)) {
$current_match_set = 1;
} else {
$current_match_set++;
}
$exercise_info['question'][$current_question_ident]['answer'][$current_match_set] = array ();
break;
case 'SIMPLEASSOCIABLECHOICE' :
{
$currentAssociableChoice = $attributes['IDENTIFIER'];
}
case 'SIMPLEASSOCIABLECHOICE':
$currentAssociableChoice = $attributes['IDENTIFIER'];
break;
//retrieve answers id for MCUA and MCMA questions
case 'SIMPLECHOICE' :
case 'SIMPLECHOICE':
$current_answer_id = $attributes['IDENTIFIER'];
if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id])) {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = array ();
}
break;
case 'MAPENTRY':
if ($parent_element == "MAPPING") {
$answer_id = $attributes['MAPKEY'];
case 'MAPENTRY' :
{
if ($parent_element == "MAPPING") {
$answer_id = $attributes['MAPKEY'];
if (!isset ($exercise_info['question'][$current_question_ident]['weighting'])) {
$exercise_info['question'][$current_question_ident]['weighting'] = array ();
}
$exercise_info['question'][$current_question_ident]['weighting'][$answer_id] = $attributes['MAPPEDVALUE'];
}
}
if (!isset ($exercise_info['question'][$current_question_ident]['weighting'])) {
$exercise_info['question'][$current_question_ident]['weighting'] = array ();
}
$exercise_info['question'][$current_question_ident]['weighting'][$answer_id] = $attributes['MAPPEDVALUE'];
}
break;
case 'MAPPING' :
{
if (isset ($attributes['DEFAULTVALUE'])) {
$exercise_info['question'][$current_question_ident]['default_weighting'] = $attributes['DEFAULTVALUE'];
}
}
case 'ITEMBODY' :
{
$record_item_body = true;
$current_question_item_body = '';
}
case 'MAPPING':
if (isset ($attributes['DEFAULTVALUE'])) {
$exercise_info['question'][$current_question_ident]['default_weighting'] = $attributes['DEFAULTVALUE'];
}
case 'ITEMBODY':
$record_item_body = true;
$current_question_item_body = '';
break;
case 'IMG' :
{
$exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
}
case 'IMG':
$exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
break;
}
}
@ -495,15 +459,13 @@ function endElement($parser, $name) {
}
switch ($name) {
case 'ITEMBODY' :
{
$record_item_body = false;
if ($exercise_info['question'][$current_question_ident]['type'] == 'FIB') {
$exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
} else {
$exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body;
}
}
case 'ITEMBODY':
$record_item_body = false;
if ($exercise_info['question'][$current_question_ident]['type'] == 'FIB') {
$exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
} else {
$exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body;
}
break;
}
array_pop($element_pile);
@ -541,68 +503,47 @@ function elementData($parser, $data) {
}
switch ($current_element) {
case 'SIMPLECHOICE' :
if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'])) {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] = trim($data);
} else {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] .= ''.trim($data);
}
case 'SIMPLECHOICE':
if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'])) {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] = trim($data);
} else {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] .= ''.trim($data);
}
break;
case 'FEEDBACKINLINE' :
{
if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'])) {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] = trim($data);
} else {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] .= ' ' . trim($data);
}
}
if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'])) {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] = trim($data);
} else {
$exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] .= ' ' . trim($data);
}
break;
case 'SIMPLEASSOCIABLECHOICE' :
{
$exercise_info['question'][$current_question_ident]['answer'][$current_match_set][$currentAssociableChoice] = trim($data);
}
case 'SIMPLEASSOCIABLECHOICE':
$exercise_info['question'][$current_question_ident]['answer'][$current_match_set][$currentAssociableChoice] = trim($data);
break;
case 'VALUE' :
{
if ($parent_element == "CORRECTRESPONSE") {
if ($cardinality == "single") {
$exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id] = $data;
} else {
$exercise_info['question'][$current_question_ident]['correct_answers'][] = $data;
}
}
}
if ($parent_element == "CORRECTRESPONSE") {
if ($cardinality == "single") {
$exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id] = $data;
} else {
$exercise_info['question'][$current_question_ident]['correct_answers'][] = $data;
}
}
break;
case 'ITEMBODY' :
{
$current_question_item_body .= $data;
}
$current_question_item_body .= $data;
break;
case 'INLINECHOICE' :
{
// if this is the right answer, then we must replace the claroline tags in the FIB text bye the answer between "[" and "]" :
$answer_identifier = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
if ($current_inlinechoice_id == $answer_identifier) {
$current_question_item_body = str_replace("**claroline_start**" . $current_answer_id . "**claroline_end**", "[" . $data . "]", $current_question_item_body);
} else // save wrong answers in an array
{
if (!isset ($exercise_info['question'][$current_question_ident]['wrong_answers'])) {
$exercise_info['question'][$current_question_ident]['wrong_answers'] = array ();
}
$exercise_info['question'][$current_question_ident]['wrong_answers'][] = $data;
}
}
// if this is the right answer, then we must replace the claroline tags in the FIB text bye the answer between "[" and "]" :
$answer_identifier = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
if ($current_inlinechoice_id == $answer_identifier) {
$current_question_item_body = str_replace("**claroline_start**" . $current_answer_id . "**claroline_end**", "[" . $data . "]", $current_question_item_body);
} else {
if (!isset ($exercise_info['question'][$current_question_ident]['wrong_answers'])) {
$exercise_info['question'][$current_question_ident]['wrong_answers'] = array ();
}
$exercise_info['question'][$current_question_ident]['wrong_answers'][] = $data;
}
break;
}
}

@ -1,4 +1,4 @@
<?php // $Id: $
<?php
/* For licensing terms, see /license.txt */
/**
* @author Claro Team <cvs@claroline.net>
@ -8,14 +8,6 @@
/**
* Code
*/
if ( count( get_included_files() ) == 1 ) die( '---' );
if (!function_exists('mime_content_type')) {
require_once api_get_path(LIBRARY_PATH).'document.lib.php';
function mime_content_type($filename) {
return DocumentManager::file_get_mime_type((string)$filename);
}
}
require_once(api_get_path(SYS_CODE_PATH).'/exercice/answer.class.php');
require_once(api_get_path(SYS_CODE_PATH).'/exercice/exercise.class.php');
@ -39,32 +31,40 @@ class Ims2Question extends Question
{
/**
* Include the correct answer class and create answer
* @return Answer
*/
function setAnswer()
public function setAnswer()
{
switch($this->type) {
case MCUA :
case MCUA:
$answer = new ImsAnswerMultipleChoice($this->id);
return $answer;
case MCMA :
case MCMA:
$answer = new ImsAnswerMultipleChoice($this->id);
return $answer;
case TF :
$answer = new ImsAnswerMultipleChoice($this->id);
return $answer;
case FIB :
case FIB:
$answer = new ImsAnswerFillInBlanks($this->id);
return $answer;
case MATCHING :
case MATCHING:
$answer = new ImsAnswerMatching($this->id);
return $answer;
case FREE_ANSWER :
case FREE_ANSWER:
$answer = new ImsAnswerFree($this->id);
return $answer;
case HOT_SPOT :
case HOT_SPOT:
$answer = new ImsAnswerHotspot($this->id);
return $answer;
default :
default:
$answer = null;
break;
}
@ -92,21 +92,26 @@ class ImsAnswerMultipleChoice extends Answer
* Return the XML flow for the possible answers.
*
*/
function imsExportResponses($questionIdent, $questionStatment)
public function imsExportResponses($questionIdent, $questionStatment)
{
// @todo getAnswersList() converts the answers using api_html_entity_decode()
$this->answerList = $this->getAnswersList(true);
$out = ' <choiceInteraction responseIdentifier="' . $questionIdent . '" >' . "\n";
$out .= ' <prompt> ' . $questionStatment . ' </prompt>'. "\n";
$out .= ' <prompt><![CDATA['.formatExerciseQtiTitle($questionStatment) . ']]></prompt>'. "\n";
if (is_array($this->answerList)) {
foreach ($this->answerList as $current_answer) {
$out .= ' <simpleChoice identifier="answer_' . $current_answer['id'] . '" fixed="false">' . $current_answer['answer'];
$out .= '<simpleChoice identifier="answer_' . $current_answer['id'] . '" fixed="false">
<![CDATA['.formatExerciseQtiTitle($current_answer['answer']).']]>';
if (isset($current_answer['comment']) && $current_answer['comment'] != '') {
$out .= '<feedbackInline identifier="answer_' . $current_answer['id'] . '">' . $current_answer['comment'] . '</feedbackInline>';
$out .= '<feedbackInline identifier="answer_' . $current_answer['id'] . '">
<![CDATA['.formatExerciseQtiTitle($current_answer['comment']).']]>
</feedbackInline>';
}
$out .= '</simpleChoice>'. "\n";
}
}
$out .= ' </choiceInteraction>'. "\n";
return $out;
}
@ -114,7 +119,7 @@ class ImsAnswerMultipleChoice extends Answer
* Return the XML flow of answer ResponsesDeclaration
*
*/
function imsExportResponsesDeclaration($questionIdent)
public function imsExportResponsesDeclaration($questionIdent)
{
$this->answerList = $this->getAnswersList(true);
$type = $this->getQuestionType();
@ -122,7 +127,7 @@ class ImsAnswerMultipleChoice extends Answer
$out = ' <responseDeclaration identifier="' . $questionIdent . '" cardinality="' . $cardinality . '" baseType="identifier">' . "\n";
//Match the correct answers
// Match the correct answers.
$out .= ' <correctResponse>'. "\n";
if (is_array($this->answerList)) {
@ -162,70 +167,20 @@ class ImsAnswerFillInBlanks extends Answer
*
*
*/
function imsExportResponses($questionIdent, $questionStatment)
public function imsExportResponses($questionIdent, $questionStatment)
{
global $charset;
$this->answerList = $this->getAnswersList(true);
//switch ($this->type)
//{
// case TEXTFIELD_FILL :
// {
$text = '';
$text .= $this->answerText;
if (is_array($this->answerList)) {
foreach ($this->answerList as $key=>$answer) {
$key = $answer['id'];
$answer = $answer['answer'];
$len = api_strlen($answer);
$text = str_replace('['.$answer.']','<textEntryInteraction responseIdentifier="fill_'.$key.'" expectedLength="'.api_strlen($answer).'"/>', $text);
}
}
$out = $text;
// }
// break;
/*
case LISTBOX_FILL :
{
$text = $this->answerText;
foreach ($this->answerList as $answerKey=>$answer)
{
//build inlinechoice list
$inlineChoiceList = '';
//1-start interaction tag
$inlineChoiceList .= '<inlineChoiceInteraction responseIdentifier="fill_'.$answerKey.'" >'. "\n";
//2- add wrong answer array
foreach ($this->wrongAnswerList as $choiceKey=>$wrongAnswer)
{
$inlineChoiceList .= ' <inlineChoice identifier="choice_w_'.$answerKey.'_'.$choiceKey.'">'.$wrongAnswer.'</inlineChoice>'. "\n";
}
//3- add correct answers array
foreach ($this->answerList as $choiceKey=>$correctAnswer)
{
$inlineChoiceList .= ' <inlineChoice identifier="choice_c_'.$answerKey.'_'.$choiceKey.'">'.$correctAnswer.'</inlineChoice>'. "\n";
}
//4- finish interaction tag
$inlineChoiceList .= '</inlineChoiceInteraction>';
$text = str_replace('['.$answer.']',$inlineChoiceList, $text);
}
$out = $text;
$text = '';
$text .= $this->answerText;
if (is_array($this->answerList)) {
foreach ($this->answerList as $key=>$answer) {
$key = $answer['id'];
$answer = $answer['answer'];
$len = api_strlen($answer);
$text = str_replace('['.$answer.']','<textEntryInteraction responseIdentifier="fill_'.$key.'" expectedLength="'.api_strlen($answer).'"/>', $text);
}
break;
*/
//}
}
$out = $text;
return $out;
}
@ -233,39 +188,19 @@ class ImsAnswerFillInBlanks extends Answer
/**
*
*/
function imsExportResponsesDeclaration($questionIdent)
public function imsExportResponsesDeclaration($questionIdent)
{
$this->answerList = $this->getAnswersList(true);
$this->gradeList = $this->getGradesList();
$out = '';
if (is_array($this->answerList)) {
foreach ($this->answerList as $answerKey=>$answer) {
foreach ($this->answerList as $answer) {
$answerKey = $answer['id'];
$answer = $answer['answer'];
$out .= ' <responseDeclaration identifier="fill_' . $answerKey . '" cardinality="single" baseType="identifier">' . "\n";
$out .= ' <correctResponse>'. "\n";
//if ($this->type==TEXTFIELD_FILL)
//{
$out .= ' <value>'.$answer.'</value>'. "\n";
//}
/*
else
{
//find correct answer key to apply in manifest and output it
foreach ($this->answerList as $choiceKey=>$correctAnswer)
{
if ($correctAnswer==$answer)
{
$out .= ' <value>choice_c_'.$answerKey.'_'.$choiceKey.'</value>'. "\n";
}
}
}
*/
$out .= ' <value><![CDATA['.formatExerciseQtiTitle($answer).']]></value>'. "\n";
$out .= ' </correctResponse>'. "\n";
if (isset($this->gradeList[$answerKey])) {
$out .= ' <mapping>'. "\n";
$out .= ' <mapEntry mapKey="'.$answer.'" mappedValue="'.$this->gradeList[$answerKey].'"/>'. "\n";
@ -289,7 +224,7 @@ class ImsAnswerMatching extends Answer
/**
* Export the question part as a matrix-choice, with only one possible answer per line.
*/
function imsExportResponses($questionIdent, $questionStatment)
public function imsExportResponses($questionIdent, $questionStatment)
{
$this->answerList = $this->getAnswersList(true);
$maxAssociation = max(count($this->leftList), count($this->rightList));
@ -304,7 +239,10 @@ class ImsAnswerMatching extends Answer
$out .= ' <simpleMatchSet>'. "\n";
if (is_array($this->leftList)) {
foreach ($this->leftList as $leftKey=>$leftElement) {
$out .= ' <simpleAssociableChoice identifier="left_'.$leftKey.'" >'. $leftElement['answer'] .'</simpleAssociableChoice>'. "\n";
$out .= '
<simpleAssociableChoice identifier="left_'.$leftKey.'" >
<![CDATA['.formatExerciseQtiTitle($leftElement['answer']).']]>
</simpleAssociableChoice>'. "\n";
}
}
@ -318,7 +256,9 @@ class ImsAnswerMatching extends Answer
if (is_array($this->rightList)) {
foreach($this->rightList as $rightKey=>$rightElement) {
$out .= ' <simpleAssociableChoice identifier="right_'.$i.'" >'. $rightElement['answer'] .'</simpleAssociableChoice>'. "\n";
$out .= '<simpleAssociableChoice identifier="right_'.$i.'" >
<![CDATA['.formatExerciseQtiTitle($rightElement['answer']).']]>
</simpleAssociableChoice>'. "\n";
$i++;
}
}
@ -331,7 +271,7 @@ class ImsAnswerMatching extends Answer
/**
*
*/
function imsExportResponsesDeclaration($questionIdent)
public function imsExportResponsesDeclaration($questionIdent)
{
$this->answerList = $this->getAnswersList(true);
$out = ' <responseDeclaration identifier="' . $questionIdent . '" cardinality="single" baseType="identifier">' . "\n";
@ -363,7 +303,6 @@ class ImsAnswerMatching extends Answer
return $out;
}
}
/**
@ -373,12 +312,11 @@ class ImsAnswerMatching extends Answer
class ImsAnswerHotspot extends Answer
{
/**
* TODO update this to match hotspots instead of copying matching
* TODO update this to match hot spots instead of copying matching
* Export the question part as a matrix-choice, with only one possible answer per line.
*/
function imsExportResponses($questionIdent, $questionStatment, $questionDesc='', $questionMedia='')
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc='', $questionMedia='')
{
global $charset;
$this->answerList = $this->getAnswersList(true);
$questionMedia = api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/images/'.$questionMedia;
$mimetype = mime_content_type($questionMedia);
@ -427,13 +365,12 @@ class ImsAnswerHotspot extends Answer
$out = $text;
return $out;
}
/**
*
*/
function imsExportResponsesDeclaration($questionIdent)
public function imsExportResponsesDeclaration($questionIdent)
{
$this->answerList = $this->getAnswersList(true);
$this->gradeList = $this->getGradesList();
@ -445,7 +382,7 @@ class ImsAnswerHotspot extends Answer
foreach ($this->answerList as $answerKey=>$answer) {
$answerKey = $answer['id'];
$answer = $answer['answer'];
$out .= ' <value>'.$answerKey.'</value>'. "\n";
$out .= '<value><![CDATA['.formatExerciseQtiTitle($answerKey).']]></value>';
}
}
$out .= ' </correctResponse>'. "\n";
@ -465,14 +402,14 @@ class ImsAnswerFree extends Answer
* TODO implement
* Export the question part as a matrix-choice, with only one possible answer per line.
*/
function imsExportResponses($questionIdent, $questionStatment, $questionDesc='', $questionMedia='')
public function imsExportResponses($questionIdent, $questionStatment, $questionDesc='', $questionMedia='')
{
return '';
}
/**
*
*/
function imsExportResponsesDeclaration($questionIdent)
public function imsExportResponsesDeclaration($questionIdent)
{
return '';
}

@ -1,4 +1,4 @@
<?php // $Id: $
<?php
/* For licensing terms, see /license.txt */
/**
* @author Claro Team <cvs@claroline.net>
@ -8,7 +8,6 @@
/**
* Code
*/
if ( count( get_included_files() ) == 1 ) die( '---' );
require dirname(__FILE__) . '/qti2_classes.php';
/**
* Classes
@ -26,19 +25,18 @@ require dirname(__FILE__) . '/qti2_classes.php';
*/
class ImsAssessmentItem
{
var $question;
var $question_ident;
var $answer;
public $question;
public $question_ident;
public $answer;
/**
* Constructor.
*
* @param $question The Question object we want to export.
* @param $question Ims2Question object we want to export.
*/
function ImsAssessmentItem($question)
{
$this->question = $question;
//$this->answer = new Answer($question->id);
$this->answer = $this->question->setAnswer();
$this->questionIdent = "QST_" . $question->id ;
}
@ -51,20 +49,13 @@ class ImsAssessmentItem
*/
function start_item()
{
/*
return '<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p0 imsqti_v2p0.xsd"
identifier="'.$this->questionIdent.'"
title="'.htmlspecialchars($this->question->selectTitle()).'">'."\n";
*/
$string = '<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_v2p1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 imsqti_v2p1.xsd"
identifier="'.$this->questionIdent.'"
title="'.htmlspecialchars($this->question->selectTitle()).'">'."\n";
return $string;
title="'.htmlspecialchars(formatExerciseQtiTitle($this->question->selectTitle())).'">'."\n";
return $string;
}
/**
@ -101,11 +92,9 @@ class ImsAssessmentItem
function add_response_processing()
{
//return ' <responseProcessing template="http://www.imsglobal.org/question/qti_v2p0/rptemplates/map_response"/>' . "\n";
return ' <responseProcessing template="http://www.imsglobal.org/question/qti_v2p1/rptemplates/map_correct"/>' . "\n";
}
/**
* Export the question as an IMS/QTI Item.
*
@ -116,24 +105,31 @@ class ImsAssessmentItem
*/
function export($standalone = false)
{
global $charset;
$head = $foot = "";
if( $standalone )
{
$head = '<?xml version="1.0" encoding="'.$charset.'" standalone="no"?>' . "\n";
if ($standalone) {
$head = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' . "\n";
}
//TODO understand why answer might be a non-object sometimes
if (!is_object($this->answer)) { return $head; }
//TODO understand why answer might be a non-object sometimes
if (!is_object($this->answer)) {
return $head;
}
$res = $head
. $this->start_item()
.$this->answer->imsExportResponsesDeclaration($this->questionIdent)
. $this->start_item_body()
. $this->answer->imsExportResponses($this->questionIdent, $this->question->question, $this->question->description, $this->question->picture)
. $this->end_item_body()
. $this->add_response_processing()
. $this->end_item()
. $foot;
.$this->start_item()
.$this->answer->imsExportResponsesDeclaration($this->questionIdent)
.$this->start_item_body()
.$this->answer->imsExportResponses(
$this->questionIdent,
$this->question->question,
$this->question->description,
$this->question->picture
)
.$this->end_item_body()
.$this->add_response_processing()
.$this->end_item()
.$foot;
return $res;
}
}
@ -168,7 +164,7 @@ class ImsSection
function start_section()
{
$out = '<section ident="EXO_' . $this->exercise->selectId() . '" title="' . $this->exercise->selectTitle() . '">' . "\n";
$out = '<section ident="EXO_' . $this->exercise->selectId() . '" title="' .cleanAttribute(formatExerciseQtiDescription($this->exercise->selectTitle())) . '">' . "\n";
return $out;
}
@ -185,9 +181,7 @@ class ImsSection
$minutes = floor($max_time / 60);
$seconds = $max_time % 60;
return '<duration>PT' . $minutes . 'M' . $seconds . "S</duration>\n";
}
else
{
} else {
return '';
}
}
@ -199,7 +193,7 @@ class ImsSection
function export_presentation()
{
$out = "<presentation_material><flow_mat><material>\n"
. " <mattext><![CDATA[" . $this->exercise->selectDescription() . "]]></mattext>\n"
. " <mattext><![CDATA[" . formatExerciseQtiDescription($this->exercise->selectDescription()) . "]]></mattext>\n"
. "</material></flow_mat></presentation_material>\n";
return $out;
}
@ -211,6 +205,7 @@ class ImsSection
*/
function export_ordering()
{
$out = '';
if ($n = $this->exercise->getShuffle()) {
$out.= "<selection_ordering>"
@ -219,9 +214,7 @@ class ImsSection
. " </selection>\n"
. ' <order order_type="Random" />'
. "\n</selection_ordering>\n";
}
else
{
} else {
$out.= '<selection_ordering sequence_type="Normal">' . "\n"
. " <selection />\n"
. "</selection_ordering>\n";
@ -237,8 +230,7 @@ class ImsSection
function export_questions()
{
$out = "";
foreach ($this->exercise->selectQuestionList() as $q)
{
foreach ($this->exercise->selectQuestionList() as $q) {
$out .= export_question($q, false);
}
return $out;
@ -253,11 +245,9 @@ class ImsSection
*/
function export($standalone)
{
global $charset;
$head = $foot = "";
if ($standalone) {
$head = '<?xml version = "1.0" encoding = "' . $charset . '" standalone = "no"?>' . "\n"
$head = '<?xml version = "1.0" encoding = "UTF-8" standalone = "no"?>' . "\n"
. '<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">' . "\n"
. "<questestinterop>\n";
$foot = "</questestinterop>\n";
@ -289,6 +279,8 @@ class ImsSection
Feedback identifier :: <Question identifier> + "_F_" + <Response Id from the DB>
*/
/**
* Class ImsItem
*
* An IMS/QTI item. It corresponds to a single question.
* This class allows export from Claroline to IMS/QTI XML format.
* It is not usable as-is, but must be subclassed, to support different kinds of questions.
@ -297,13 +289,15 @@ class ImsSection
*
* warning: Attached files are NOT exported.
* @author Amand Tihon <amand@alrj.org>
*
* @package chamilo.exercise
*/
class ImsItem
{
var $question;
var $question_ident;
var $answer;
public $question;
public $question_ident;
public $answer;
/**
* Constructor.
@ -327,7 +321,7 @@ class ImsItem
*/
function start_item()
{
return '<item title="' . htmlspecialchars($this->question->selectTitle()) . '" ident="' . $this->questionIdent . '">' . "\n";
return '<item title="' . cleanAttribute(formatExerciseQtiDescription($this->question->selectTitle())) . '" ident="' . $this->questionIdent . '">' . "\n";
}
/**
@ -344,14 +338,14 @@ class ImsItem
* 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.
* In between, 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";
. '<material><mattext>' . formatExerciseQtiDescription($this->question->selectDescription()) . "</mattext></material>\n";
}
/**
@ -384,7 +378,6 @@ class ImsItem
return "</resprocessing>\n";
}
/**
* Export the question as an IMS/QTI Item.
*
@ -399,8 +392,7 @@ class ImsItem
global $charset;
$head = $foot = "";
if( $standalone )
{
if ($standalone) {
$head = '<?xml version = "1.0" encoding = "'.$charset.'" standalone = "no"?>' . "\n"
. '<!DOCTYPE questestinterop SYSTEM "ims_qtiasiv2p1.dtd">' . "\n"
. "<questestinterop>\n";
@ -408,36 +400,30 @@ class ImsItem
}
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;
. $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 IMS/QTI format, from its ID
*
* @param int $exerciseId The exercise to exporte
* @param int $exerciseId The exercise to export
* @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)
function export_exercise($exerciseId, $standalone = true)
{
$exercise = new Exercise();
if (! $exercise->read($exerciseId))
{
if (!$exercise->read($exerciseId)) {
return '';
}
$ims = new ImsSection($exercise);
@ -448,15 +434,14 @@ function export_exercise($exerciseId, $standalone=true)
/**
* Returns the XML flow corresponding to one question
*
* @param int The question ID
* @param bool standalone (ie including XML tag, DTD declaration, etc)
* @param int $questionId
* @param bool $standalone (ie including XML tag, DTD declaration, etc)
*/
function export_question($questionId, $standalone=true)
function export_question($questionId, $standalone = true)
{
$question = new Ims2Question();
$qst = $question->read($questionId);
if( !$qst or $qst->type == FREE_ANSWER)
{
if (!$qst or $qst->type == FREE_ANSWER) {
return '';
}
$question->id = $qst->id;
@ -470,3 +455,27 @@ function export_question($questionId, $standalone=true)
return $ims->export($standalone);
}
/**
* Clean text like a description
**/
function formatExerciseQtiDescription($text)
{
$entities = api_html_entity_decode($text);
return htmlspecialchars($entities);
}
/**
* Clean titles
* @param $text
* @return string
*/
function formatExerciseQtiTitle($text)
{
return htmlspecialchars($text);
}
function cleanAttribute($text)
{
return $text;
}

@ -122,9 +122,9 @@ class pagination
function setUrl($value="") {
$protocol = "http://";
if (isset($_SERVER['HTTPS'])) {
$protocol = "https://";
$protocol = "https://";
}
if(empty($value))
{
if($this->friendlyUrl)
@ -216,7 +216,7 @@ class pagination
/**
* get the first item number
*
* @return interger the first item number displayed within current page
* @return int the first item number displayed within current page
*/
function getFirstItem()
{
@ -232,7 +232,7 @@ class pagination
/**
* get the last item number displayed within current page
*
* @return interger the last item number
* @return int the last item number
*/
function getLastItem()
{
@ -532,8 +532,8 @@ class pagination
{
case "2":
//comment while integrating with Chamilo
//$output .= "<span class=\"pagination_summany\">" . $this->getFirstItem() . " to " . $this->getLastItem() . " of " . $this->getTotal() . " results.</span> ";
//$output .= "<span class=\"pagination_summany\">" . $this->getFirstItem() . " to " . $this->getLastItem() . " of " . $this->getTotal() . " results.</span> ";
//if($previousUrl = $this->getPreviousUrl())
//{
// $output .= " " . $previousUrl;
@ -542,7 +542,7 @@ class pagination
//if($nextUrl = $this->getNextUrl())
{
// $output .= " " . $nextUrl;
}
}
break;
case 1:
//get full summary pagination
@ -575,13 +575,13 @@ class pagination
//{
// $itemPerPage .= "<option value=\"" . $v . "\" " . ($v==$this->itemsPerPage?'selected="selected"':'') . ">" . $v . "</option>\n";
//}
//$itemPerPage .= "</select>\n";
//$itemPerPage .= "</select>\n";
$itemPerPage ="100000";//hack for Chamilo
//$output .= "<span class=\"pagination_items_per_page\">";
//$output .= sprintf(PAGINATION_ITEMS_PER_PAGE, $itemPerPage);
//$output .= "</span>";
//end comment while integrating with Chamilo
$output .= "<span class=\"pagination_parent\"><a href=\"#\" onclick=\"goParentFolder();\" id=\"pagination_parent_link\" title=\"" . PAGINATION_GO_PARENT . "\">&nbsp;".PAGINATION_GO_PARENT."</a></span>";
}
@ -591,4 +591,4 @@ class pagination
}
}
?>
?>

Loading…
Cancel
Save