qti2_classes.php 16 KB

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