qti_classes.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <?php // $Id: $
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * @copyright (c) 2007 Dokeos
  5. * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
  6. *
  7. * @license http://www.gnu.org/copyleft/gpl.html (GPL) GENERAL PUBLIC LICENSE
  8. *
  9. * @author Claro Team <cvs@claroline.net>
  10. * @author Yannick Warnier <yannick.warnier@beeznest.com>
  11. * @package chamilo.exercise
  12. */
  13. /**
  14. * Code
  15. */
  16. if ( count( get_included_files() ) == 1 ) die( '---' );
  17. require_once '../../exercise.class.php';
  18. require_once '../../question.class.php';
  19. require_once '../../answer.class.php';
  20. require_once '../../unique_answer.class.php';
  21. require_once '../../multiple_answer.class.php';
  22. require_once '../../fill_blanks.class.php';
  23. require_once '../../freeanswer.class.php';
  24. require_once '../../hotspot.class.php';
  25. require_once '../../matching.class.php';
  26. require_once '../../hotspot.class.php';
  27. /**
  28. *
  29. * @package chamilo.exercise
  30. */
  31. class ImsQuestion extends Question
  32. {
  33. /**
  34. * Include the correct answer class and create answer
  35. */
  36. function setAnswer()
  37. {
  38. switch($this->type)
  39. {
  40. case MCUA :
  41. $this->answer = new ImsAnswerMultipleChoice($this->id, false);
  42. break;
  43. case MCMA :
  44. $this->answer = new ImsAnswerMultipleChoice($this->id, true);
  45. break;
  46. case TF :
  47. $this->answer = new ImsAnswerTrueFalse($this->id);
  48. break;
  49. case FIB :
  50. $this->answer = new ImsAnswerFillInBlanks($this->id);
  51. break;
  52. case MATCHING :
  53. $this->answer = new ImsAnswerMatching($this->id);
  54. break;
  55. default :
  56. $this->answer = null;
  57. break;
  58. }
  59. return true;
  60. }
  61. /**
  62. * allow to import the question
  63. *
  64. * @param questionArray is an array that must contain all the information needed to build the question
  65. * @author Guillaume Lederer <guillaume@claroline.net>
  66. */
  67. function import($questionArray, $exerciseTempPath)
  68. {
  69. //import answers
  70. $this->answer->import($questionArray);
  71. //import attached file, if any
  72. if (isset($questionArray['attached_file_url']))
  73. {
  74. $file= array();
  75. $file['name'] = $questionArray['attached_file_url'];
  76. $file['tmp_name'] = $exerciseTempPath.$file['name'];
  77. $this->setAttachment($file);
  78. }
  79. }
  80. }
  81. /**
  82. *
  83. * @package chamilo.exercise
  84. */
  85. class ImsAnswerMultipleChoice extends answerMultipleChoice
  86. {
  87. /**
  88. * Return the XML flow for the possible answers.
  89. * That's one <response_lid>, containing several <flow_label>
  90. *
  91. * @author Amand Tihon <amand@alrj.org>
  92. */
  93. function imsExportResponses($questionIdent)
  94. {
  95. // Opening of the response block.
  96. if( $this->multipleAnswer )
  97. {
  98. $out = '<response_lid ident = "MCM_' . $questionIdent . '" rcardinality = "Multiple" rtiming = "No">' . "\n"
  99. . '<render_choice shuffle = "No" minnumber = "1" maxnumber = "' . count($this->answerList) . '">' . "\n";
  100. }
  101. else
  102. {
  103. $out = '<response_lid ident="MCS_' . $questionIdent . '" rcardinality="Single" rtiming="No"><render_choice shuffle="No">' . "\n";
  104. }
  105. // Loop over answers
  106. foreach( $this->answerList as $answer )
  107. {
  108. $responseIdent = $questionIdent . "_A_" . $answer['id'];
  109. $out.= ' <flow_label><response_label ident="' . $responseIdent . '">'.(!$this->multipleAnswer ? '<flow_mat class="list">':'').'<material>' . "\n"
  110. . ' <mattext><![CDATA[' . $answer['answer'] . ']]></mattext>' . "\n"
  111. . ' </material>'.(!$this->multipleAnswer ? '</flow_mat>':'').'</response_label></flow_label>' . "\n";
  112. }
  113. $out.= "</render_choice></response_lid>\n";
  114. return $out;
  115. }
  116. /**
  117. * Return the XML flow of answer processing : a succession of <respcondition>.
  118. *
  119. * @author Amand Tihon <amand@alrj.org>
  120. */
  121. function imsExportProcessing($questionIdent)
  122. {
  123. $out = '';
  124. foreach( $this->answerList as $answer )
  125. {
  126. $responseIdent = $questionIdent . "_A_" . $answer['id'];
  127. $feedbackIdent = $questionIdent . "_F_" . $answer['id'];
  128. $conditionIdent = $questionIdent . "_C_" . $answer['id'];
  129. if( $this->multipleAnswer )
  130. {
  131. $out .= '<respcondition title="' . $conditionIdent . '" continue="Yes"><conditionvar>' . "\n"
  132. . ' <varequal respident="MCM_' . $questionIdent . '">' . $responseIdent . '</varequal>' . "\n";
  133. }
  134. else
  135. {
  136. $out .= '<respcondition title="' . $conditionIdent . '"><conditionvar>' . "\n"
  137. . ' <varequal respident="MCS_' . $questionIdent . '">' . $responseIdent . '</varequal>' . "\n";
  138. }
  139. $out .= " </conditionvar>\n" . ' <setvar action="Add">' . $answer['grade'] . "</setvar>\n";
  140. // Only add references for actually existing comments/feedbacks.
  141. if( !empty($answer['comment']) )
  142. {
  143. $out .= ' <displayfeedback feedbacktype="Response" linkrefid="' . $feedbackIdent . '" />' . "\n";
  144. }
  145. $out .= "</respcondition>\n";
  146. }
  147. return $out;
  148. }
  149. /**
  150. * Export the feedback (comments to selected answers) to IMS/QTI
  151. *
  152. * @author Amand Tihon <amand@alrj.org>
  153. */
  154. function imsExportFeedback($questionIdent)
  155. {
  156. $out = "";
  157. foreach( $this->answerList as $answer )
  158. {
  159. if( !empty($answer['comment']) )
  160. {
  161. $feedbackIdent = $questionIdent . "_F_" . $answer['id'];
  162. $out.= '<itemfeedback ident="' . $feedbackIdent . '" view="Candidate"><flow_mat><material>' . "\n"
  163. . ' <mattext><![CDATA[' . $answer['comment'] . "]]></mattext>\n"
  164. . "</material></flow_mat></itemfeedback>\n";
  165. }
  166. }
  167. return $out;
  168. }
  169. /**
  170. * allow to import the answers, feedbacks, and grades of a question
  171. * @param questionArray is an array that must contain all the information needed to build the question
  172. * @author Guillaume Lederer <guillaume@claroline.net>
  173. */
  174. function import($questionArray)
  175. {
  176. $answerArray = $questionArray['answer'];
  177. $this->answerList = array(); //re-initialize answer object content
  178. foreach ($answerArray as $key => $answer)
  179. {
  180. if (!isset($answer['feedback'])) $answer['feedback'] = "";
  181. if (!isset($questionArray['weighting'][$key]))
  182. {
  183. if (isset($questionArray['default_weighting']))
  184. {
  185. $grade = $questionArray['default_weighting'];
  186. }
  187. else
  188. {
  189. $grade = 0;
  190. }
  191. }
  192. else
  193. {
  194. $grade = $questionArray['weighting'][$key];
  195. }
  196. if (in_array($key,$questionArray['correct_answers'])) $is_correct = true; else $is_correct = false;
  197. $addedAnswer = array(
  198. 'answer' => $answer['value'],
  199. 'correct' => $is_correct,
  200. 'grade' => $grade,
  201. 'comment' => $answer['feedback'],
  202. );
  203. $this->answerList[] = $addedAnswer;
  204. }
  205. }
  206. }
  207. /**
  208. *
  209. * @package chamilo.exercise
  210. */
  211. class ImsAnswerTrueFalse extends answerTrueFalse
  212. {
  213. /**
  214. * Return the XML flow for the possible answers.
  215. * That's one <response_lid>, containing several <flow_label>
  216. *
  217. * @author Amand Tihon <amand@alrj.org>
  218. */
  219. function imsExportResponses($questionIdent)
  220. {
  221. // Opening of the response block.
  222. $out = '<response_lid ident="TF_' . $questionIdent . '" rcardinality="Single" rtiming="No"><render_choice shuffle="No">' . "\n";
  223. // true
  224. $response_ident = $questionIdent . '_A_true';
  225. $out .=
  226. ' <flow_label><response_label ident="'.$response_ident.'"><flow_mat class="list"><material>' . "\n"
  227. . ' <mattext><![CDATA[' . get_lang('True') . ']]></mattext>' . "\n"
  228. . ' </material></flow_mat></response_label></flow_label>' . "\n";
  229. // false
  230. $response_ident = $questionIdent . '_A_false';
  231. $out .=
  232. ' <flow_label><response_label ident="'.$response_ident.'"><flow_mat class="list"><material>' . "\n"
  233. . ' <mattext><![CDATA[' . get_lang('False') . ']]></mattext>' . "\n"
  234. . ' </material></flow_mat></response_label></flow_label>' . "\n";
  235. $out .= '</render_choice></response_lid>' . "\n";
  236. return $out;
  237. }
  238. /**
  239. * Return the XML flow of answer processing : a succession of <respcondition>.
  240. *
  241. * @author Amand Tihon <amand@alrj.org>
  242. */
  243. function imsExportProcessing($questionIdent)
  244. {
  245. $out = '';
  246. // true
  247. $response_ident = $questionIdent. '_A_true';
  248. $feedback_ident = $questionIdent . '_F_true';
  249. $condition_ident = $questionIdent . '_C_true';
  250. $out .=
  251. '<respcondition title="' . $condition_ident . '"><conditionvar>' . "\n"
  252. . ' <varequal respident="TF_' . $questionIdent . '">' . $response_ident . '</varequal>' . "\n"
  253. . ' </conditionvar>' . "\n" . ' <setvar action="Add">' . $this->trueGrade . '</setvar>' . "\n";
  254. // Only add references for actually existing comments/feedbacks.
  255. if( !empty($this->trueFeedback) )
  256. {
  257. $out.= ' <displayfeedback feedbacktype="Response" linkrefid="' . $this->trueFeedback . '" />' . "\n";
  258. }
  259. $out .= '</respcondition>' . "\n";
  260. // false
  261. $response_ident = $questionIdent. '_A_false';
  262. $feedback_ident = $questionIdent . '_F_false';
  263. $condition_ident = $questionIdent . '_C_false';
  264. $out .=
  265. '<respcondition title="' . $condition_ident . '"><conditionvar>' . "\n"
  266. . ' <varequal respident="TF_' . $questionIdent . '">' . $response_ident . '</varequal>' . "\n"
  267. . ' </conditionvar>' . "\n" . ' <setvar action="Add">' . $this->falseGrade . '</setvar>' . "\n";
  268. // Only add references for actually existing comments/feedbacks.
  269. if( !empty($this->falseFeedback) )
  270. {
  271. $out.= ' <displayfeedback feedbacktype="Response" linkrefid="' . $feedback_ident . '" />' . "\n";
  272. }
  273. $out .= '</respcondition>' . "\n";
  274. return $out;
  275. }
  276. /**
  277. * Export the feedback (comments to selected answers) to IMS/QTI
  278. *
  279. * @author Amand Tihon <amand@alrj.org>
  280. */
  281. function imsExportFeedback($questionIdent)
  282. {
  283. $out = "";
  284. if( !empty($this->trueFeedback) )
  285. {
  286. $feedback_ident = $questionIdent . '_F_true';
  287. $out.= '<itemfeedback ident="' . $feedback_ident . '" view="Candidate"><flow_mat><material>' . "\n"
  288. . ' <mattext><![CDATA[' . $this->trueFeedback . "]]></mattext>\n"
  289. . "</material></flow_mat></itemfeedback>\n";
  290. }
  291. if( !empty($this->falseFeedback) )
  292. {
  293. $feedback_ident = $questionIdent . '_F_false';
  294. $out.= '<itemfeedback ident="' . $feedback_ident . '" view="Candidate"><flow_mat><material>' . "\n"
  295. . ' <mattext><![CDATA[' . $this->falseFeedback . "]]></mattext>\n"
  296. . "</material></flow_mat></itemfeedback>\n";
  297. }
  298. return $out;
  299. }
  300. }
  301. /**
  302. *
  303. * @package chamilo.exercise
  304. */
  305. class ImsAnswerFillInBlanks extends answerFillInBlanks
  306. {
  307. /**
  308. * Export the text with missing words.
  309. *
  310. * As a side effect, it stores two lists in the class :
  311. * the missing words and their respective weightings.
  312. *
  313. * @author Amand Tihon <amand@alrj.org>
  314. */
  315. function imsExportResponses($questionIdent)
  316. {
  317. global $charset;
  318. $out = "<flow>\n";
  319. $responsePart = explode(']', $this->answer);
  320. $i = 0; // Used for the reference generation.
  321. foreach($responsePart as $part)
  322. {
  323. $response_ident = $questionIdent . "_A_" . $i;
  324. if( strpos($part,'[') !== false )
  325. {
  326. list($rawText, $blank) = explode('[', $part);
  327. }
  328. else
  329. {
  330. $rawText = $part;
  331. $blank = "";
  332. }
  333. if ($rawText!="")
  334. {
  335. $out.=" <material><mattext><![CDATA[" . $rawText . "]]></mattext></material>\n";
  336. }
  337. if ($blank!="")
  338. {
  339. $out.= ' <response_str ident="' . $response_ident . '" rcardinality="Single" rtiming="No">' . "\n"
  340. . ' <render_fib fibtype="String" prompt="Box" encoding="' . $charset . '">' . "\n"
  341. . ' <response_label ident="A"/>' . "\n"
  342. . " </render_fib>\n"
  343. . " </response_str>\n";
  344. }
  345. $i++;
  346. }
  347. $out.="</flow>\n";
  348. return $out;
  349. }
  350. /**
  351. * Exports the response processing.
  352. *
  353. * It uses the two lists build by export_responses(). This implies that export_responses MUST
  354. * be called before.
  355. *
  356. * @author Amand Tihon <amand@alrj.org>
  357. */
  358. function imsExportProcessing($questionIdent)
  359. {
  360. $out = "";
  361. $answerCount = count($this->answerList);
  362. for( $i = 0; $i < $answerCount ; $i++ )
  363. {
  364. $response_ident = $questionIdent . "_A_" . $i;
  365. $out.= ' <respcondition continue="Yes"><conditionvar>' . "\n"
  366. . ' <varequal respident="' . $response_ident . '" case="No"><![CDATA[' . $this->answerList[$i] . ']]></varequal>' . "\n"
  367. . ' </conditionvar><setvar action="Add">' . $this->gradeList[$i] . "</setvar>\n"
  368. . " </respcondition>\n";
  369. }
  370. return $out;
  371. }
  372. /**
  373. * Export the feedback (comments to selected answers) to IMS/QTI
  374. *
  375. * @author Amand Tihon <amand@alrj.org>
  376. */
  377. function imsExportFeedback($questionIdent)
  378. {
  379. // no feedback in this question type
  380. return '';
  381. }
  382. /**
  383. * allow to import the answers, feedbacks, and grades of a question
  384. *
  385. * @param questionArray is an array that must contain all the information needed to build the question
  386. * @author Guillaume Lederer <guillaume@claroline.net>
  387. */
  388. function import($questionArray)
  389. {
  390. $answerArray = $questionArray['answer'];
  391. $this->answerText = str_replace ("\n","",$questionArray['response_text']);
  392. if ($questionArray['subtype'] == "TEXTFIELD_FILL") $this->type = TEXTFIELD_FILL;
  393. if ($questionArray['subtype'] == "LISTBOX_FILL")
  394. {
  395. $this->wrongAnswerList = $questionArray['wrong_answers'];
  396. $this->type = LISTBOX_FILL;
  397. }
  398. //build correct_answsers array
  399. if (isset($questionArray['weighting']))
  400. {
  401. $this->gradeList = $questionArray['weighting'];
  402. }
  403. }
  404. }
  405. /**
  406. *
  407. * @package chamilo.exercise
  408. */
  409. class ImsAnswerMatching extends answerMatching
  410. {
  411. /**
  412. * Export the question part as a matrix-choice, with only one possible answer per line.
  413. * @author Amand Tihon <amand@alrj.org>
  414. */
  415. function imsExportResponses($questionIdent)
  416. {
  417. $out = "";
  418. // Now, loop again, finding questions (rows)
  419. foreach( $this->leftList as $leftElt )
  420. {
  421. $responseIdent = $questionIdent . "_A_" . $leftElt['code'];
  422. $out.= '<response_lid ident="' . $responseIdent . '" rcardinality="Single" rtiming="No">' . "\n"
  423. . '<material><mattext><![CDATA[' . $leftElt['answer'] . "]]></mattext></material>\n"
  424. . ' <render_choice shuffle="No"><flow_label>' . "\n";
  425. foreach( $this->rightList as $rightElt )
  426. {
  427. $out.= ' <response_label ident="' . $rightElt['code'] . '"><material>' . "\n"
  428. . " <mattext><![CDATA[" . $rightElt['answer'] . "]]></mattext>\n"
  429. . " </material></response_label>\n";
  430. }
  431. $out.= "</flow_label></render_choice></response_lid>\n";
  432. }
  433. return $out;
  434. }
  435. /**
  436. * Export the response processing part
  437. * @author Amand Tihon <amand@alrj.org>
  438. */
  439. function imsExportProcessing($questionIdent)
  440. {
  441. $out = "";
  442. foreach( $this->leftList as $leftElt )
  443. {
  444. $responseIdent = $questionIdent . "_A_" . $leftElt['code'];
  445. $out.= ' <respcondition continue="Yes"><conditionvar>' . "\n"
  446. . ' <varequal respident="' . $responseIdent . '">' . $leftElt['match'] . "</varequal>\n"
  447. . ' </conditionvar><setvar action="Add">' . $leftElt['grade'] . "</setvar>\n"
  448. . " </respcondition>\n";
  449. }
  450. return $out;
  451. }
  452. /**
  453. * Export the feedback (comments to selected answers) to IMS/QTI
  454. *
  455. * @author Amand Tihon <amand@alrj.org>
  456. */
  457. function imsExportFeedback($questionIdent)
  458. {
  459. // no feedback in this question type
  460. return '';
  461. }
  462. /**
  463. * allow to import the answers, feedbacks, and grades of a question
  464. *
  465. * @param questionArray is an array that must contain all the information needed to build the question
  466. * @author Guillaume Lederer <guillaume@claroline.net>
  467. */
  468. function import($questionArray)
  469. {
  470. $answerArray = $questionArray['answer'];
  471. //This tick to remove examples in the answers!!!!
  472. $this->leftList = array();
  473. $this->rightList = array();
  474. //find right and left column
  475. $right_column = array_pop($answerArray);
  476. $left_column = array_pop($answerArray);
  477. //1- build answers
  478. foreach ($right_column as $right_key => $right_element)
  479. {
  480. $code = $this->addRight($right_element);
  481. foreach ($left_column as $left_key => $left_element)
  482. {
  483. $matched_pattern = $left_key." ".$right_key;
  484. $matched_pattern_inverted = $right_key." ".$left_key;
  485. if (in_array($matched_pattern, $questionArray['correct_answers']) || in_array($matched_pattern_inverted, $questionArray['correct_answers']))
  486. {
  487. if (isset($questionArray['weighting'][$matched_pattern]))
  488. {
  489. $grade = $questionArray['weighting'][$matched_pattern];
  490. }
  491. else
  492. {
  493. $grade = 0;
  494. }
  495. $this->addLeft($left_element, $code, $grade);
  496. }
  497. }
  498. }
  499. }
  500. }
  501. ?>