PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/question/type/randomsamatch/questiontype.php

https://github.com/bobpuffer/1.9.12-LAE1.3
PHP | 363 lines | 271 code | 39 blank | 53 comment | 49 complexity | 90e9ae18282dd09cb5e412eef046c687 MD5 | raw file
  1. <?php // $Id: questiontype.php 68 2009-07-31 18:23:01Z dlandau $
  2. /////////////////////
  3. /// RANDOMSAMATCH ///
  4. /////////////////////
  5. /// TODO: Make sure short answer questions chosen by a randomsamatch question
  6. /// can not also be used by a random question
  7. /// QUESTION TYPE CLASS //////////////////
  8. /**
  9. * @package questionbank
  10. * @subpackage questiontypes
  11. */
  12. class question_randomsamatch_qtype extends question_match_qtype {
  13. /// Extends 'match' as there are quite a few simularities...
  14. function name() {
  15. return 'randomsamatch';
  16. }
  17. function is_usable_by_random() {
  18. return false;
  19. }
  20. function get_question_options(&$question) {
  21. if (!$question->options = get_record('question_randomsamatch', 'question', $question->id)) {
  22. notify('Error: Missing question options for random short answer question '.$question->id.'!');
  23. return false;
  24. }
  25. // This could be included as a flag in the database. It's already
  26. // supported by the code.
  27. // Recurse subcategories: 0 = no recursion, 1 = recursion
  28. $question->options->subcats = 1;
  29. return true;
  30. }
  31. function save_question_options($question) {
  32. $options->question = $question->id;
  33. $options->choose = $question->choose;
  34. if (2 > $question->choose) {
  35. $result->error = "At least two shortanswer questions need to be chosen!";
  36. return $result;
  37. }
  38. if ($existing = get_record("question_randomsamatch",
  39. "question", $options->question)) {
  40. $options->id = $existing->id;
  41. if (!update_record("question_randomsamatch", $options)) {
  42. $result->error = "Could not update quiz randomsamatch options!";
  43. return $result;
  44. }
  45. } else {
  46. if (!insert_record("question_randomsamatch", $options)) {
  47. $result->error = "Could not insert quiz randomsamatch options!";
  48. return $result;
  49. }
  50. }
  51. return true;
  52. }
  53. /**
  54. * Deletes question from the question-type specific tables
  55. *
  56. * @return boolean Success/Failure
  57. * @param object $question The question being deleted
  58. */
  59. function delete_question($questionid) {
  60. delete_records("question_randomsamatch", "question", $questionid);
  61. return true;
  62. }
  63. function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
  64. // Choose a random shortanswer question from the category:
  65. // We need to make sure that no question is used more than once in the
  66. // quiz. Therfore the following need to be excluded:
  67. // 1. All questions that are explicitly assigned to the quiz
  68. // 2. All random questions
  69. // 3. All questions that are already chosen by an other random question
  70. global $QTYPES;
  71. if (!isset($cmoptions->questionsinuse)) {
  72. $cmoptions->questionsinuse = $cmoptions->questions;
  73. }
  74. if ($question->options->subcats) {
  75. // recurse into subcategories
  76. $categorylist = question_categorylist($question->category);
  77. } else {
  78. $categorylist = $question->category;
  79. }
  80. $saquestions = $this->get_sa_candidates($categorylist, $cmoptions->questionsinuse);
  81. $count = count($saquestions);
  82. $wanted = $question->options->choose;
  83. $errorstr = '';
  84. if ($count < $wanted && isteacherinanycourse()) {
  85. if ($count >= 2) {
  86. $errorstr = "Error: could not get enough Short-Answer questions!
  87. Got $count Short-Answer questions, but wanted $wanted.
  88. Reducing number to choose from to $count!";
  89. $wanted = $question->options->choose = $count;
  90. } else {
  91. $errorstr = "Error: could not get enough Short-Answer questions!
  92. This can happen if all available Short-Answer questions are already
  93. taken up by other Random questions or Random Short-Answer question.
  94. Another possible cause for this error is that Short-Answer
  95. questions were deleted after this Random Short-Answer question was
  96. created.";
  97. }
  98. notify($errorstr);
  99. $errorstr = '<span class="notifyproblem">' . $errorstr . '</span>';
  100. }
  101. if ($count < $wanted) {
  102. $question->questiontext = "$errorstr<br /><br />Insufficient selection options are
  103. available for this question, therefore it is not available in this
  104. quiz. Please inform your teacher.";
  105. // Treat this as a description from this point on
  106. $question->qtype = DESCRIPTION;
  107. return true;
  108. }
  109. $saquestions =
  110. draw_rand_array($saquestions, $question->options->choose); // from bug 1889
  111. foreach ($saquestions as $key => $wrappedquestion) {
  112. if (!$QTYPES[$wrappedquestion->qtype]
  113. ->get_question_options($wrappedquestion)) {
  114. return false;
  115. }
  116. // Now we overwrite the $question->options->answers field to only
  117. // *one* (the first) correct answer. This loop can be deleted to
  118. // take all answers into account (i.e. put them all into the
  119. // drop-down menu.
  120. $foundcorrect = false;
  121. foreach ($wrappedquestion->options->answers as $answer) {
  122. if ($foundcorrect || $answer->fraction != 1.0) {
  123. unset($wrappedquestion->options->answers[$answer->id]);
  124. } else if (!$foundcorrect) {
  125. $foundcorrect = true;
  126. }
  127. }
  128. if (!$QTYPES[$wrappedquestion->qtype]
  129. ->create_session_and_responses($wrappedquestion, $state, $cmoptions,
  130. $attempt)) {
  131. return false;
  132. }
  133. $wrappedquestion->name_prefix = $question->name_prefix;
  134. $wrappedquestion->maxgrade = $question->maxgrade;
  135. $cmoptions->questionsinuse .= ",$wrappedquestion->id";
  136. $state->options->subquestions[$key] = clone($wrappedquestion);
  137. }
  138. // Shuffle the answers (Do this always because this is a random question type)
  139. $subquestionids = array_values(array_map(create_function('$val',
  140. 'return $val->id;'), $state->options->subquestions));
  141. $subquestionids = swapshuffle($subquestionids);
  142. // Create empty responses
  143. foreach ($subquestionids as $val) {
  144. $state->responses[$val] = '';
  145. }
  146. return true;
  147. }
  148. function restore_session_and_responses(&$question, &$state) {
  149. global $QTYPES;
  150. if (empty($state->responses[''])) {
  151. $question->questiontext = "Insufficient selection options are
  152. available for this question, therefore it is not available in this
  153. quiz. Please inform your teacher.";
  154. // Treat this as a description from this point on
  155. $question->qtype = DESCRIPTION;
  156. } else {
  157. $responses = explode(',', $state->responses['']);
  158. $responses = array_map(create_function('$val',
  159. 'return explode("-", $val);'), $responses);
  160. // Restore the previous responses
  161. $state->responses = array();
  162. foreach ($responses as $response) {
  163. $state->responses[$response[0]] = $response[1];
  164. if (!$wrappedquestion = get_record('question', 'id',
  165. $response[0])) {
  166. notify("Couldn't get question (id=$response[0])!");
  167. return false;
  168. }
  169. if (!$QTYPES[$wrappedquestion->qtype]
  170. ->get_question_options($wrappedquestion)) {
  171. notify("Couldn't get question options (id=$response[0])!");
  172. return false;
  173. }
  174. // Now we overwrite the $question->options->answers field to only
  175. // *one* (the first) correct answer. This loop can be deleted to
  176. // take all answers into account (i.e. put them all into the
  177. // drop-down menu.
  178. $foundcorrect = false;
  179. foreach ($wrappedquestion->options->answers as $answer) {
  180. if ($foundcorrect || $answer->fraction != 1.0) {
  181. unset($wrappedquestion->options->answers[$answer->id]);
  182. } else if (!$foundcorrect) {
  183. $foundcorrect = true;
  184. }
  185. }
  186. if (!$QTYPES[$wrappedquestion->qtype]
  187. ->restore_session_and_responses($wrappedquestion, $state)) {
  188. notify("Couldn't restore session of question (id=$response[0])!");
  189. return false;
  190. }
  191. $wrappedquestion->name_prefix = $question->name_prefix;
  192. $wrappedquestion->maxgrade = $question->maxgrade;
  193. $state->options->subquestions[$wrappedquestion->id] =
  194. clone($wrappedquestion);
  195. }
  196. }
  197. return true;
  198. }
  199. function extract_response($rawresponse, $nameprefix) {
  200. /// Simple implementation that does not check with the database
  201. /// and thus - does not bother to check whether there has been
  202. /// any changes to the question options.
  203. $response = array();
  204. $rawitems = explode(',', $rawresponse->answer);
  205. foreach ($rawitems as $rawitem) {
  206. $splits = explode('-', $rawitem, 2);
  207. $response[$nameprefix.$splits[0]] = $splits[1];
  208. }
  209. return $response;
  210. }
  211. function get_sa_candidates($categorylist, $questionsinuse=0) {
  212. return get_records_select('question',
  213. "qtype = '".'shortanswer'."' " .
  214. "AND category IN ($categorylist) " .
  215. "AND parent = '0' " .
  216. "AND hidden = '0'" .
  217. "AND id NOT IN ($questionsinuse)");
  218. }
  219. /// BACKUP FUNCTIONS ////////////////////////////
  220. /*
  221. * Backup the data in the question
  222. *
  223. * This is used in question/backuplib.php
  224. */
  225. function backup($bf,$preferences,$question,$level=6) {
  226. $status = true;
  227. $randomsamatchs = get_records("question_randomsamatch","question",$question,"id");
  228. //If there are randomsamatchs
  229. if ($randomsamatchs) {
  230. //Iterate over each randomsamatch
  231. foreach ($randomsamatchs as $randomsamatch) {
  232. $status = fwrite ($bf,start_tag("RANDOMSAMATCH",6,true));
  233. //Print randomsamatch contents
  234. fwrite ($bf,full_tag("CHOOSE",7,false,$randomsamatch->choose));
  235. $status = fwrite ($bf,end_tag("RANDOMSAMATCH",6,true));
  236. }
  237. }
  238. return $status;
  239. }
  240. /// RESTORE FUNCTIONS /////////////////
  241. /*
  242. * Restores the data in the question
  243. *
  244. * This is used in question/restorelib.php
  245. */
  246. function restore($old_question_id,$new_question_id,$info,$restore) {
  247. $status = true;
  248. //Get the randomsamatchs array
  249. $randomsamatchs = $info['#']['RANDOMSAMATCH'];
  250. //Iterate over randomsamatchs
  251. for($i = 0; $i < sizeof($randomsamatchs); $i++) {
  252. $ran_info = $randomsamatchs[$i];
  253. //Now, build the question_randomsamatch record structure
  254. $randomsamatch->question = $new_question_id;
  255. $randomsamatch->choose = backup_todb($ran_info['#']['CHOOSE']['0']['#']);
  256. //The structure is equal to the db, so insert the question_randomsamatch
  257. $newid = insert_record ("question_randomsamatch",$randomsamatch);
  258. //Do some output
  259. if (($i+1) % 50 == 0) {
  260. if (!defined('RESTORE_SILENTLY')) {
  261. echo ".";
  262. if (($i+1) % 1000 == 0) {
  263. echo "<br />";
  264. }
  265. }
  266. backup_flush(300);
  267. }
  268. if (!$newid) {
  269. $status = false;
  270. }
  271. }
  272. return $status;
  273. }
  274. function restore_recode_answer($state, $restore) {
  275. //The answer is a comma separated list of hypen separated question_id and answer_id. We must recode them
  276. $answer_field = "";
  277. $in_first = true;
  278. $tok = strtok($state->answer,",");
  279. while ($tok) {
  280. //Extract the question_id and the answer_id
  281. $exploded = explode("-",$tok);
  282. $question_id = $exploded[0];
  283. $answer_id = $exploded[1];
  284. //Get the question from backup_ids
  285. if (!$que = backup_getid($restore->backup_unique_code,"question",$question_id)) {
  286. echo 'Could not recode randomsamatch question '.$question_id.'<br />';
  287. }
  288. if ($answer_id == 0) { // no response yet
  289. $ans->new_id = 0;
  290. } else {
  291. //Get the answer from backup_ids
  292. if (!$ans = backup_getid($restore->backup_unique_code,"question_answers",$answer_id)) {
  293. echo 'Could not recode randomsamatch answer '.$answer_id.'<br />';
  294. }
  295. }
  296. if ($in_first) {
  297. $answer_field .= $que->new_id."-".$ans->new_id;
  298. $in_first = false;
  299. } else {
  300. $answer_field .= ",".$que->new_id."-".$ans->new_id;
  301. }
  302. //check for next
  303. $tok = strtok(",");
  304. }
  305. return $answer_field;
  306. }
  307. }
  308. //// END OF CLASS ////
  309. //////////////////////////////////////////////////////////////////////////
  310. //// INITIATION - Without this line the question type is not in use... ///
  311. //////////////////////////////////////////////////////////////////////////
  312. question_register_questiontype(new question_randomsamatch_qtype());
  313. ?>