7 /// TODO: Make sure short answer questions chosen by a randomsamatch question
8 /// can not also be used by a random question
10 /// QUESTION TYPE CLASS //////////////////
12 * @package questionbank
13 * @subpackage questiontypes
15 class question_randomsamatch_qtype
extends question_match_qtype
{
16 /// Extends 'match' as there are quite a few simularities...
19 return 'randomsamatch';
22 function is_usable_by_random() {
26 function get_question_options(&$question) {
27 if (!$question->options
= get_record('question_randomsamatch', 'question', $question->id
)) {
28 notify('Error: Missing question options for random short answer question '.$question->id
.'!');
32 // This could be included as a flag in the database. It's already
33 // supported by the code.
34 // Recurse subcategories: 0 = no recursion, 1 = recursion
35 $question->options
->subcats
= 1;
40 function save_question_options($question) {
41 $options->question
= $question->id
;
42 $options->choose
= $question->choose
;
44 if (2 > $question->choose
) {
45 $result->error
= "At least two shortanswer questions need to be chosen!";
49 if ($existing = get_record("question_randomsamatch",
50 "question", $options->question
)) {
51 $options->id
= $existing->id
;
52 if (!update_record("question_randomsamatch", $options)) {
53 $result->error
= "Could not update quiz randomsamatch options!";
57 if (!insert_record("question_randomsamatch", $options)) {
58 $result->error
= "Could not insert quiz randomsamatch options!";
66 * Deletes question from the question-type specific tables
68 * @return boolean Success/Failure
69 * @param object $question The question being deleted
71 function delete_question($questionid) {
72 delete_records("question_randomsamatch", "question", $questionid);
76 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
77 // Choose a random shortanswer question from the category:
78 // We need to make sure that no question is used more than once in the
79 // quiz. Therfore the following need to be excluded:
80 // 1. All questions that are explicitly assigned to the quiz
81 // 2. All random questions
82 // 3. All questions that are already chosen by an other random question
84 if (!isset($cmoptions->questionsinuse
)) {
85 $cmoptions->questionsinuse
= $cmoptions->questions
;
88 if ($question->options
->subcats
) {
89 // recurse into subcategories
90 $categorylist = question_categorylist($question->category
);
92 $categorylist = $question->category
;
95 $saquestions = $this->get_sa_candidates($categorylist, $cmoptions->questionsinuse
);
97 $count = count($saquestions);
98 $wanted = $question->options
->choose
;
100 if ($count < $wanted && isteacherinanycourse()) {
102 $errorstr = "Error: could not get enough Short-Answer questions!
103 Got $count Short-Answer questions, but wanted $wanted.
104 Reducing number to choose from to $count!";
105 $wanted = $question->options
->choose
= $count;
107 $errorstr = "Error: could not get enough Short-Answer questions!
108 This can happen if all available Short-Answer questions are already
109 taken up by other Random questions or Random Short-Answer question.
110 Another possible cause for this error is that Short-Answer
111 questions were deleted after this Random Short-Answer question was
115 $errorstr = '<span class="notifyproblem">' . $errorstr . '</span>';
118 if ($count < $wanted) {
119 $question->questiontext
= "$errorstr<br /><br />Insufficient selection options are
120 available for this question, therefore it is not available in this
121 quiz. Please inform your teacher.";
122 // Treat this as a description from this point on
123 $question->qtype
= DESCRIPTION
;
128 draw_rand_array($saquestions, $question->options
->choose
); // from bug 1889
130 foreach ($saquestions as $key => $wrappedquestion) {
131 if (!$QTYPES[$wrappedquestion->qtype
]
132 ->get_question_options($wrappedquestion)) {
136 // Now we overwrite the $question->options->answers field to only
137 // *one* (the first) correct answer. This loop can be deleted to
138 // take all answers into account (i.e. put them all into the
140 $foundcorrect = false;
141 foreach ($wrappedquestion->options
->answers
as $answer) {
142 if ($foundcorrect ||
$answer->fraction
!= 1.0) {
143 unset($wrappedquestion->options
->answers
[$answer->id
]);
144 } else if (!$foundcorrect) {
145 $foundcorrect = true;
149 if (!$QTYPES[$wrappedquestion->qtype
]
150 ->create_session_and_responses($wrappedquestion, $state, $cmoptions,
154 $wrappedquestion->name_prefix
= $question->name_prefix
;
155 $wrappedquestion->maxgrade
= $question->maxgrade
;
156 $cmoptions->questionsinuse
.= ",$wrappedquestion->id";
157 $state->options
->subquestions
[$key] = clone($wrappedquestion);
160 // Shuffle the answers (Do this always because this is a random question type)
161 $subquestionids = array_values(array_map(create_function('$val',
162 'return $val->id;'), $state->options
->subquestions
));
163 $subquestionids = swapshuffle($subquestionids);
165 // Create empty responses
166 foreach ($subquestionids as $val) {
167 $state->responses
[$val] = '';
172 function restore_session_and_responses(&$question, &$state) {
174 if (empty($state->responses
[''])) {
175 $question->questiontext
= "Insufficient selection options are
176 available for this question, therefore it is not available in this
177 quiz. Please inform your teacher.";
178 // Treat this as a description from this point on
179 $question->qtype
= DESCRIPTION
;
181 $responses = explode(',', $state->responses
['']);
182 $responses = array_map(create_function('$val',
183 'return explode("-", $val);'), $responses);
185 // Restore the previous responses
186 $state->responses
= array();
187 foreach ($responses as $response) {
188 $state->responses
[$response[0]] = $response[1];
189 if (!$wrappedquestion = get_record('question', 'id',
191 notify("Couldn't get question (id=$response[0])!");
194 if (!$QTYPES[$wrappedquestion->qtype
]
195 ->get_question_options($wrappedquestion)) {
196 notify("Couldn't get question options (id=$response[0])!");
200 // Now we overwrite the $question->options->answers field to only
201 // *one* (the first) correct answer. This loop can be deleted to
202 // take all answers into account (i.e. put them all into the
204 $foundcorrect = false;
205 foreach ($wrappedquestion->options
->answers
as $answer) {
206 if ($foundcorrect ||
$answer->fraction
!= 1.0) {
207 unset($wrappedquestion->options
->answers
[$answer->id
]);
208 } else if (!$foundcorrect) {
209 $foundcorrect = true;
213 if (!$QTYPES[$wrappedquestion->qtype
]
214 ->restore_session_and_responses($wrappedquestion, $state)) {
215 notify("Couldn't restore session of question (id=$response[0])!");
218 $wrappedquestion->name_prefix
= $question->name_prefix
;
219 $wrappedquestion->maxgrade
= $question->maxgrade
;
221 $state->options
->subquestions
[$wrappedquestion->id
] =
222 clone($wrappedquestion);
228 function extract_response($rawresponse, $nameprefix) {
229 /// Simple implementation that does not check with the database
230 /// and thus - does not bother to check whether there has been
231 /// any changes to the question options.
233 $rawitems = explode(',', $rawresponse->answer
);
234 foreach ($rawitems as $rawitem) {
235 $splits = explode('-', $rawitem, 2);
236 $response[$nameprefix.$splits[0]] = $splits[1];
241 function get_sa_candidates($categorylist, $questionsinuse=0) {
242 return get_records_select('question',
243 "qtype = '".'shortanswer'."' " .
244 "AND category IN ($categorylist) " .
245 "AND parent = '0' " .
247 "AND id NOT IN ($questionsinuse)");
250 /// BACKUP FUNCTIONS ////////////////////////////
253 * Backup the data in the question
255 * This is used in question/backuplib.php
257 function backup($bf,$preferences,$question,$level=6) {
261 $randomsamatchs = get_records("question_randomsamatch","question",$question,"id");
262 //If there are randomsamatchs
263 if ($randomsamatchs) {
264 //Iterate over each randomsamatch
265 foreach ($randomsamatchs as $randomsamatch) {
266 $status = fwrite ($bf,start_tag("RANDOMSAMATCH",6,true));
267 //Print randomsamatch contents
268 fwrite ($bf,full_tag("CHOOSE",7,false,$randomsamatch->choose
));
269 $status = fwrite ($bf,end_tag("RANDOMSAMATCH",6,true));
275 /// RESTORE FUNCTIONS /////////////////
278 * Restores the data in the question
280 * This is used in question/restorelib.php
282 function restore($old_question_id,$new_question_id,$info,$restore) {
286 //Get the randomsamatchs array
287 $randomsamatchs = $info['#']['RANDOMSAMATCH'];
289 //Iterate over randomsamatchs
290 for($i = 0; $i < sizeof($randomsamatchs); $i++
) {
291 $ran_info = $randomsamatchs[$i];
293 //Now, build the question_randomsamatch record structure
294 $randomsamatch->question
= $new_question_id;
295 $randomsamatch->choose
= backup_todb($ran_info['#']['CHOOSE']['0']['#']);
297 //The structure is equal to the db, so insert the question_randomsamatch
298 $newid = insert_record ("question_randomsamatch",$randomsamatch);
301 if (($i+
1) %
50 == 0) {
302 if (!defined('RESTORE_SILENTLY')) {
304 if (($i+
1) %
1000 == 0) {
319 function restore_recode_answer($state, $restore) {
321 //The answer is a comma separated list of hypen separated question_id and answer_id. We must recode them
324 $tok = strtok($state->answer
,",");
326 //Extract the question_id and the answer_id
327 $exploded = explode("-",$tok);
328 $question_id = $exploded[0];
329 $answer_id = $exploded[1];
330 //Get the question from backup_ids
331 if (!$que = backup_getid($restore->backup_unique_code
,"question",$question_id)) {
332 echo 'Could not recode randomsamatch question '.$question_id.'<br />';
335 if ($answer_id == 0) { // no response yet
338 //Get the answer from backup_ids
339 if (!$ans = backup_getid($restore->backup_unique_code
,"question_answers",$answer_id)) {
340 echo 'Could not recode randomsamatch answer '.$answer_id.'<br />';
344 $answer_field .= $que->new_id
."-".$ans->new_id
;
347 $answer_field .= ",".$que->new_id
."-".$ans->new_id
;
352 return $answer_field;
357 //// END OF CLASS ////
359 //////////////////////////////////////////////////////////////////////////
360 //// INITIATION - Without this line the question type is not in use... ///
361 //////////////////////////////////////////////////////////////////////////
362 question_register_questiontype(new question_randomsamatch_qtype());