* @author Yannick Warnier * * @package chamilo.exercise */ require __DIR__.'/qti2_classes.php'; /** * An IMS/QTI item. It corresponds to a single question. * This class allows export from Claroline to IMS/QTI2.0 XML 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. * * note: Attached files are NOT exported. * * @package chamilo.exercise */ class ImsAssessmentItem { /** * @var Ims2Question */ public $question; /** * @var string */ public $questionIdent; /** * @var ImsAnswerInterface */ public $answer; /** * Constructor. * * @param Ims2Question $question ims2Question object we want to export */ public function __construct($question) { $this->question = $question; $this->answer = $this->question->setAnswer(); $this->questionIdent = "QST_".$question->id; } /** * Start the XML flow. * * This opens the block, with correct attributes. */ public function start_item() { $categoryTitle = ''; if (!empty($this->question->category)) { $category = new TestCategory(); $category = $category->getCategory($this->question->category); if ($category) { $categoryTitle = htmlspecialchars(formatExerciseQtiText($category->name)); } } $string = ''."\n"; return $string; } /** * End the XML flow, closing the tag. */ public function end_item() { return "\n"; } /** * Start the itemBody. */ public function start_item_body() { return ' '."\n"; } /** * End the itemBody part. */ public function end_item_body() { return " \n"; } /** * add the response processing template used. */ public function add_response_processing() { return ' '."\n"; } /** * Export the question as an IMS/QTI 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 string string, the XML flow for an Item */ public function export($standalone = false) { $head = $foot = ''; if ($standalone) { $head = ''."\n"; } //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->question) .$this->start_item_body() .$this->answer->imsExportResponses( $this->questionIdent, $this->question->question, $this->question->description, $this->question->getPictureFilename() ) .$this->end_item_body() .$this->add_response_processing() .$this->end_item() .$foot; return $res; } } /** * This class represents an entire exercise to be exported in IMS/QTI. * It will be represented by a single
containing several . * * Some properties cannot be exported, as IMS does not support them : * - type (one page or multiple pages) * - start_date and end_date * - max_attempts * - show_answer * - anonymous_attempts * * @author Amand Tihon * * @package chamilo.exercise */ class ImsSection { public $exercise; /** * Constructor. * * @param Exercise $exe The Exercise instance to export * * @author Amand Tihon */ public function __construct($exe) { $this->exercise = $exe; } public function start_section() { $out = '
'."\n"; return $out; } public function end_section() { return "
\n"; } public 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 'PT'.$minutes.'M'.$seconds."S\n"; } else { return ''; } } /** * Export the presentation (Exercise's description). * * @author Amand Tihon */ public function export_presentation() { $out = "\n" ." exercise->selectDescription())."]]>\n" ."\n"; return $out; } /** * Export the ordering information. * Either sequential, through all questions, or random, with a selected number of questions. * * @author Amand Tihon */ public function export_ordering() { $out = ''; if ($n = $this->exercise->getShuffle()) { $out .= "" ." \n" ." ".$n."\n" ." \n" .' ' ."\n\n"; } else { $out .= ''."\n" ." \n" ."\n"; } return $out; } /** * Export the questions, as a succession of . * * @author Amand Tihon */ public function exportQuestions() { $out = ''; foreach ($this->exercise->selectQuestionList() as $q) { $out .= export_question_qti($q, false); } return $out; } /** * Export the exercise in IMS/QTI. * * @param bool $standalone wether it should include XML tag and DTD line * * @return string string containing the XML flow * * @author Amand Tihon */ public function export($standalone) { $head = $foot = ''; if ($standalone) { $head = ''."\n" .''."\n" ."\n"; $foot = "\n"; } $out = $head .$this->start_section() .$this->export_duration() .$this->export_presentation() .$this->export_ordering() .$this->exportQuestions() .$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_" + + "_" + Response identifier :: + "_A_" + Condition identifier :: + "_C_" + Feedback identifier :: + "_F_" + */ /** * 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. * * Every start_*() and corresponding end_*(), as well as export_*() methods return a string. * * warning: Attached files are NOT exported. * * @author Amand Tihon * * @package chamilo.exercise */ class ImsItem { public $question; public $questionIdent; public $answer; /** * Constructor. * * @param Question $question the Question object we want to export * * @author Anamd Tihon */ public function __construct($question) { $this->question = $question; $this->answer = $question->answer; $this->questionIdent = "QST_".$question->selectId(); } /** * Start the XML flow. * * This opens the block, with correct attributes. * * @author Amand Tihon */ public function start_item() { return ''."\n"; } /** * End the XML flow, closing the tag. * * @author Amand Tihon */ public function end_item() { return "\n"; } /** * Create the opening, with the question itself. * * This means it opens the but doesn't close it, as this is the role of end_presentation(). * In between, the export_responses from the subclass should have been called. * * @author Amand Tihon */ public function start_presentation() { return ''."\n" .''.formatExerciseQtiDescription($this->question->selectDescription())."\n"; } /** * End the part, opened by export_header. * * @author Amand Tihon */ public function end_presentation() { return "\n"; } /** * Start the response processing, and declare the default variable, SCORE, at 0 in the outcomes. * * @author Amand Tihon */ public function start_processing() { return ''."\n"; } /** * End the response processing part. * * @author Amand Tihon */ public function end_processing() { return "\n"; } /** * Export the question as an IMS/QTI 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 string string, the XML flow for an Item * * @author Amand Tihon */ public function export($standalone = false) { global $charset; $head = $foot = ""; if ($standalone) { $head = ''."\n" .''."\n" ."\n"; $foot = "\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; } } /** * Send a complete exercise in IMS/QTI format, from its ID. * * @param int $exerciseId The exercise to export * @param bool $standalone wether it should include XML tag and DTD line * * @return string XML as a string, or an empty string if there's no exercise with given ID */ function export_exercise_to_qti($exerciseId, $standalone = true) { $exercise = new Exercise(); if (!$exercise->read($exerciseId)) { return ''; } $ims = new ImsSection($exercise); $xml = $ims->export($standalone); return $xml; } /** * Returns the XML flow corresponding to one question. * * @param int $questionId * @param bool $standalone (ie including XML tag, DTD declaration, etc) * * @return string */ function export_question_qti($questionId, $standalone = true) { $question = new Ims2Question(); $qst = $question->read($questionId); if (!$qst) { return ''; } $isValid = $qst instanceof UniqueAnswer || $qst instanceof MultipleAnswer || $qst instanceof FreeAnswer; if (!$isValid) { 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; $question->category = $qst->category; $ims = new ImsAssessmentItem($question); 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 formatExerciseQtiText($text) { return htmlspecialchars($text); } /** * @param string $text * * @return string */ function cleanAttribute($text) { return $text; }