qti2_classes.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <?php // $Id: $
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * @author Claro Team <cvs@claroline.net>
  5. * @author Yannick Warnier <yannick.warnier@beeznest.com> - updated ImsAnswerHotspot to match QTI norms
  6. * @package chamilo.exercise
  7. */
  8. /**
  9. * Code
  10. */
  11. require_once(api_get_path(SYS_CODE_PATH).'/exercice/answer.class.php');
  12. require_once(api_get_path(SYS_CODE_PATH).'/exercice/exercise.class.php');
  13. require_once(api_get_path(SYS_CODE_PATH).'/exercice/question.class.php');
  14. require_once(api_get_path(SYS_CODE_PATH).'/exercice/hotspot.class.php');
  15. require_once(api_get_path(SYS_CODE_PATH).'/exercice/unique_answer.class.php');
  16. require_once(api_get_path(SYS_CODE_PATH).'/exercice/multiple_answer.class.php');
  17. //require_once(api_get_path(SYS_CODE_PATH).'/exercice/multiple_answer_combination.class.php');
  18. require_once(api_get_path(SYS_CODE_PATH).'/exercice/matching.class.php');
  19. require_once(api_get_path(SYS_CODE_PATH).'/exercice/freeanswer.class.php');
  20. require_once(api_get_path(SYS_CODE_PATH).'/exercice/fill_blanks.class.php');
  21. //include_once $path . '/../../lib/answer_multiplechoice.class.php';
  22. //include_once $path . '/../../lib/answer_truefalse.class.php';
  23. //include_once $path . '/../../lib/answer_fib.class.php';
  24. //include_once $path . '/../../lib/answer_matching.class.php';
  25. /**
  26. *
  27. * @package chamilo.exercise
  28. */
  29. class Ims2Question extends Question
  30. {
  31. /**
  32. * Include the correct answer class and create answer
  33. * @return Answer
  34. */
  35. public function setAnswer()
  36. {
  37. switch($this->type) {
  38. case MCUA:
  39. $answer = new ImsAnswerMultipleChoice($this->id);
  40. return $answer;
  41. case MCMA:
  42. $answer = new ImsAnswerMultipleChoice($this->id);
  43. return $answer;
  44. case TF :
  45. $answer = new ImsAnswerMultipleChoice($this->id);
  46. return $answer;
  47. case FIB:
  48. $answer = new ImsAnswerFillInBlanks($this->id);
  49. return $answer;
  50. case MATCHING:
  51. $answer = new ImsAnswerMatching($this->id);
  52. return $answer;
  53. case FREE_ANSWER:
  54. $answer = new ImsAnswerFree($this->id);
  55. return $answer;
  56. case HOT_SPOT:
  57. $answer = new ImsAnswerHotspot($this->id);
  58. return $answer;
  59. default:
  60. $answer = null;
  61. break;
  62. }
  63. return $answer;
  64. }
  65. function createAnswersForm($form)
  66. {
  67. return true;
  68. }
  69. function processAnswersCreation($form)
  70. {
  71. return true;
  72. }
  73. }
  74. /**
  75. * Class
  76. * @package chamilo.exercise
  77. */
  78. class ImsAnswerMultipleChoice extends Answer
  79. {
  80. /**
  81. * Return the XML flow for the possible answers.
  82. *
  83. */
  84. public function imsExportResponses($questionIdent, $questionStatment)
  85. {
  86. // @todo getAnswersList() converts the answers using api_html_entity_decode()
  87. $this->answerList = $this->getAnswersList(true);
  88. $out = ' <choiceInteraction responseIdentifier="' . $questionIdent . '" >' . "\n";
  89. $out .= ' <prompt><![CDATA['.formatExerciseQtiTitle($questionStatment) . ']]></prompt>'. "\n";
  90. if (is_array($this->answerList)) {
  91. foreach ($this->answerList as $current_answer) {
  92. $out .= '<simpleChoice identifier="answer_' . $current_answer['id'] . '" fixed="false">
  93. <![CDATA['.formatExerciseQtiTitle($current_answer['answer']).']]>';
  94. if (isset($current_answer['comment']) && $current_answer['comment'] != '') {
  95. $out .= '<feedbackInline identifier="answer_' . $current_answer['id'] . '">
  96. <![CDATA['.formatExerciseQtiTitle($current_answer['comment']).']]>
  97. </feedbackInline>';
  98. }
  99. $out .= '</simpleChoice>'. "\n";
  100. }
  101. }
  102. $out .= ' </choiceInteraction>'. "\n";
  103. return $out;
  104. }
  105. /**
  106. * Return the XML flow of answer ResponsesDeclaration
  107. *
  108. */
  109. public function imsExportResponsesDeclaration($questionIdent)
  110. {
  111. $this->answerList = $this->getAnswersList(true);
  112. $type = $this->getQuestionType();
  113. if ($type == MCMA) $cardinality = 'multiple'; else $cardinality = 'single';
  114. $out = ' <responseDeclaration identifier="' . $questionIdent . '" cardinality="' . $cardinality . '" baseType="identifier">' . "\n";
  115. //Match the correct answers
  116. $out .= ' <correctResponse>'. "\n";
  117. if (is_array($this->answerList)) {
  118. foreach($this->answerList as $current_answer) {
  119. if ($current_answer['correct']) {
  120. $out .= ' <value>answer_'. $current_answer['id'] .'</value>'. "\n";
  121. }
  122. }
  123. }
  124. $out .= ' </correctResponse>'. "\n";
  125. //Add the grading
  126. $out .= ' <mapping>'. "\n";
  127. if (is_array($this->answerList)) {
  128. foreach($this->answerList as $current_answer) {
  129. if (isset($current_answer['grade'])) {
  130. $out .= ' <mapEntry mapKey="answer_'. $current_answer['id'] .'" mappedValue="'.$current_answer['grade'].'" />'. "\n";
  131. }
  132. }
  133. }
  134. $out .= ' </mapping>'. "\n";
  135. $out .= ' </responseDeclaration>'. "\n";
  136. return $out;
  137. }
  138. }
  139. /**
  140. * Class
  141. * @package chamilo.exercise
  142. */
  143. class ImsAnswerFillInBlanks extends Answer
  144. {
  145. /**
  146. * Export the text with missing words.
  147. *
  148. *
  149. */
  150. public function imsExportResponses($questionIdent, $questionStatment)
  151. {
  152. $this->answerList = $this->getAnswersList(true);
  153. $text = '';
  154. $text .= $this->answerText;
  155. if (is_array($this->answerList)) {
  156. foreach ($this->answerList as $key=>$answer) {
  157. $key = $answer['id'];
  158. $answer = $answer['answer'];
  159. $len = api_strlen($answer);
  160. $text = str_replace('['.$answer.']','<textEntryInteraction responseIdentifier="fill_'.$key.'" expectedLength="'.api_strlen($answer).'"/>', $text);
  161. }
  162. }
  163. $out = $text;
  164. return $out;
  165. }
  166. /**
  167. *
  168. */
  169. public function imsExportResponsesDeclaration($questionIdent)
  170. {
  171. $this->answerList = $this->getAnswersList(true);
  172. $this->gradeList = $this->getGradesList();
  173. $out = '';
  174. if (is_array($this->answerList)) {
  175. foreach ($this->answerList as $answer) {
  176. $answerKey = $answer['id'];
  177. $answer = $answer['answer'];
  178. $out .= ' <responseDeclaration identifier="fill_' . $answerKey . '" cardinality="single" baseType="identifier">' . "\n";
  179. $out .= ' <correctResponse>'. "\n";
  180. $out .= ' <value><![CDATA['.formatExerciseQtiTitle($answer).']]></value>'. "\n";
  181. $out .= ' </correctResponse>'. "\n";
  182. if (isset($this->gradeList[$answerKey])) {
  183. $out .= ' <mapping>'. "\n";
  184. $out .= ' <mapEntry mapKey="'.$answer.'" mappedValue="'.$this->gradeList[$answerKey].'"/>'. "\n";
  185. $out .= ' </mapping>'. "\n";
  186. }
  187. $out .= ' </responseDeclaration>'. "\n";
  188. }
  189. }
  190. return $out;
  191. }
  192. }
  193. /**
  194. * Class
  195. * @package chamilo.exercise
  196. */
  197. class ImsAnswerMatching extends Answer
  198. {
  199. /**
  200. * Export the question part as a matrix-choice, with only one possible answer per line.
  201. */
  202. public function imsExportResponses($questionIdent, $questionStatment)
  203. {
  204. $this->answerList = $this->getAnswersList(true);
  205. $maxAssociation = max(count($this->leftList), count($this->rightList));
  206. $out = "";
  207. $out .= '<matchInteraction responseIdentifier="' . $questionIdent . '" maxAssociations="'. $maxAssociation .'">'. "\n";
  208. $out .= $questionStatment;
  209. //add left column
  210. $out .= ' <simpleMatchSet>'. "\n";
  211. if (is_array($this->leftList)) {
  212. foreach ($this->leftList as $leftKey=>$leftElement) {
  213. $out .= '
  214. <simpleAssociableChoice identifier="left_'.$leftKey.'" >
  215. <![CDATA['.formatExerciseQtiTitle($leftElement['answer']).']]>
  216. </simpleAssociableChoice>'. "\n";
  217. }
  218. }
  219. $out .= ' </simpleMatchSet>'. "\n";
  220. //add right column
  221. $out .= ' <simpleMatchSet>'. "\n";
  222. $i = 0;
  223. if (is_array($this->rightList)) {
  224. foreach($this->rightList as $rightKey=>$rightElement) {
  225. $out .= '<simpleAssociableChoice identifier="right_'.$i.'" >
  226. <![CDATA['.formatExerciseQtiTitle($rightElement['answer']).']]>
  227. </simpleAssociableChoice>'. "\n";
  228. $i++;
  229. }
  230. }
  231. $out .= ' </simpleMatchSet>'. "\n";
  232. $out .= '</matchInteraction>'. "\n";
  233. return $out;
  234. }
  235. /**
  236. *
  237. */
  238. public function imsExportResponsesDeclaration($questionIdent)
  239. {
  240. $this->answerList = $this->getAnswersList(true);
  241. $out = ' <responseDeclaration identifier="' . $questionIdent . '" cardinality="single" baseType="identifier">' . "\n";
  242. $out .= ' <correctResponse>' . "\n";
  243. $gradeArray = array();
  244. if (is_array($this->leftList)) {
  245. foreach ($this->leftList as $leftKey=>$leftElement) {
  246. $i=0;
  247. foreach ($this->rightList as $rightKey=>$rightElement) {
  248. if (($leftElement['match'] == $rightElement['code'])) {
  249. $out .= ' <value>left_' . $leftKey . ' right_'.$i.'</value>'. "\n";
  250. $gradeArray['left_' . $leftKey . ' right_'.$i] = $leftElement['grade'];
  251. }
  252. $i++;
  253. }
  254. }
  255. }
  256. $out .= ' </correctResponse>'. "\n";
  257. $out .= ' <mapping>' . "\n";
  258. if (is_array($gradeArray)) {
  259. foreach ($gradeArray as $gradeKey=>$grade) {
  260. $out .= ' <mapEntry mapKey="'.$gradeKey.'" mappedValue="'.$grade.'"/>' . "\n";
  261. }
  262. }
  263. $out .= ' </mapping>' . "\n";
  264. $out .= ' </responseDeclaration>'. "\n";
  265. return $out;
  266. }
  267. }
  268. /**
  269. * Class
  270. * @package chamilo.exercise
  271. */
  272. class ImsAnswerHotspot extends Answer
  273. {
  274. /**
  275. * TODO update this to match hot spots instead of copying matching
  276. * Export the question part as a matrix-choice, with only one possible answer per line.
  277. */
  278. public function imsExportResponses($questionIdent, $questionStatment, $questionDesc='', $questionMedia='')
  279. {
  280. $this->answerList = $this->getAnswersList(true);
  281. $questionMedia = api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/images/'.$questionMedia;
  282. $mimetype = mime_content_type($questionMedia);
  283. if(empty($mimetype)){
  284. $mimetype = 'image/jpeg';
  285. }
  286. $text = ' <p>'.$questionStatment.'</p>'."\n";
  287. $text .= ' <graphicOrderInteraction responseIdentifier="hotspot_'.$questionIdent.'">'."\n";
  288. $text .= ' <prompt>'.$questionDesc.'</prompt>'."\n";
  289. $text .= ' <object type="'.$mimetype.'" width="250" height="230" data="'.$questionMedia.'">-</object>'."\n";
  290. if (is_array($this->answerList)) {
  291. foreach ($this->answerList as $key=>$answer) {
  292. $key = $answer['id'];
  293. $answerTxt = $answer['answer'];
  294. $len = api_strlen($answerTxt);
  295. //coords are transformed according to QTIv2 rules here: http://www.imsproject.org/question/qtiv2p1pd/imsqti_infov2p1pd.html#element10663
  296. $coords = '';
  297. $type = 'default';
  298. switch($answer['hotspot_type']){
  299. case 'square':
  300. $type = 'rect';
  301. $res = array();
  302. $coords = preg_match('/^\s*(\d+);(\d+)\|(\d+)\|(\d+)\s*$/',$answer['hotspot_coord'],$res);
  303. $coords = $res[1].','.$res[2].','.((int)$res[1]+(int)$res[3]).",".((int)$res[2]+(int)$res[4]);
  304. break;
  305. case 'circle':
  306. $type = 'circle';
  307. $res = array();
  308. $coords = preg_match('/^\s*(\d+);(\d+)\|(\d+)\|(\d+)\s*$/',$answer['hotspot_coord'],$res);
  309. $coords = $res[1].','.$res[2].','.sqrt(pow(($res[1]-$res[3]),2)+pow(($res[2]-$res[4])));
  310. break;
  311. case 'poly':
  312. $type = 'poly';
  313. $coords = str_replace(array(';','|'),array(',',','),$answer['hotspot_coord']);
  314. break;
  315. case 'delineation' :
  316. $type = 'delineation';
  317. $coords = str_replace(array(';','|'),array(',',','),$answer['hotspot_coord']);
  318. break;
  319. }
  320. $text .= ' <hotspotChoice shape="'.$type.'" coords="'.$coords.'" identifier="'.$key.'"/>'."\n";
  321. }
  322. }
  323. $text .= ' </graphicOrderInteraction>'."\n";
  324. $out = $text;
  325. return $out;
  326. }
  327. /**
  328. *
  329. */
  330. public function imsExportResponsesDeclaration($questionIdent)
  331. {
  332. $this->answerList = $this->getAnswersList(true);
  333. $this->gradeList = $this->getGradesList();
  334. $out = '';
  335. $out .= ' <responseDeclaration identifier="hotspot_'.$questionIdent.'" cardinality="ordered" baseType="identifier">' . "\n";
  336. $out .= ' <correctResponse>'. "\n";
  337. if (is_array($this->answerList)) {
  338. foreach ($this->answerList as $answerKey=>$answer) {
  339. $answerKey = $answer['id'];
  340. $answer = $answer['answer'];
  341. $out .= '<value><![CDATA['.formatExerciseQtiTitle($answerKey).']]></value>';
  342. }
  343. }
  344. $out .= ' </correctResponse>'. "\n";
  345. $out .= ' </responseDeclaration>'. "\n";
  346. return $out;
  347. }
  348. }
  349. /**
  350. * Class
  351. * @package chamilo.exercise
  352. */
  353. class ImsAnswerFree extends Answer
  354. {
  355. /**
  356. * TODO implement
  357. * Export the question part as a matrix-choice, with only one possible answer per line.
  358. */
  359. public function imsExportResponses($questionIdent, $questionStatment, $questionDesc='', $questionMedia='')
  360. {
  361. return '';
  362. }
  363. /**
  364. *
  365. */
  366. public function imsExportResponsesDeclaration($questionIdent)
  367. {
  368. return '';
  369. }
  370. }