qti_classes.php 19 KB

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