Commit | Line | Data |
---|---|---|
0d24b17a TH |
1 | <?php |
2 | ||
3 | // This file is part of Moodle - http://moodle.org/ | |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | ||
19 | /** | |
20 | * Question type class for the embedded element in question text question types. | |
21 | * | |
22 | * @package qtype | |
23 | * @subpackage gapselect | |
24 | * @copyright 2011 The Open University | |
25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
26 | */ | |
27 | ||
28 | ||
29 | require_once($CFG->libdir . '/questionlib.php'); | |
30 | require_once($CFG->dirroot . '/question/engine/lib.php'); | |
31 | require_once($CFG->dirroot . '/question/format/xml/format.php'); | |
32 | ||
33 | ||
34 | /** | |
35 | * The embedded element in question text question type class. | |
36 | * | |
37 | * @copyright 2009 The Open University | |
38 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
39 | */ | |
40 | class qtype_gapselect_base extends question_type { | |
41 | /** | |
42 | * Choices are stored in the question_answers table, and any options need to | |
43 | * be put into the feedback field somehow. This method is responsible for | |
44 | * converting all the options to a single string for this purpose. It is used | |
45 | * by {@link save_question_options()}. | |
46 | * @param array $choice the form data relating to this choice. | |
47 | * @return string ready to store in the database. | |
48 | */ | |
49 | protected abstract function choice_options_to_feedback($choice); | |
50 | ||
51 | public function save_question_options($question) { | |
52 | $result = new stdClass(); | |
53 | ||
54 | if (!$oldanswers = get_records('question_answers', 'question', $question->id, 'id ASC')) { | |
55 | $oldanswers = array(); | |
56 | } | |
57 | ||
58 | // Insert all the new answers | |
59 | foreach ($question->choices as $key => $choice) { | |
60 | ||
61 | if (trim($choice['answer']) == '') { | |
62 | continue; | |
63 | } | |
64 | ||
65 | $feedback = $this->choice_options_to_feedback($choice); | |
66 | ||
67 | if ($answer = array_shift($oldanswers)) { // Existing answer, so reuse it | |
68 | $answer->answer = $choice['answer']; | |
69 | $answer->fraction = 0; | |
70 | $answer->feedback = $feedback; | |
71 | if (!update_record('question_answers', $answer)) { | |
72 | $result->error = "Could not update question type '".$this->name()."' question answer! (id=$answer->id)"; | |
73 | return $result; | |
74 | } | |
75 | } else { | |
76 | $answer = new stdClass; | |
77 | $answer->answer = $choice['answer']; | |
78 | $answer->question = $question->id; | |
79 | $answer->fraction = 0; | |
80 | $answer->feedback = $feedback; | |
81 | if (!$answer->id = insert_record('question_answers', $answer)) { | |
82 | $result->error = 'Could not insert question type \''.$this->name().'\' question answer!'; | |
83 | return $result; | |
84 | } | |
85 | } | |
86 | } | |
87 | ||
88 | // Delete old answer records | |
89 | if (!empty($oldanswers)) { | |
90 | foreach($oldanswers as $oa) { | |
91 | delete_records('question_answers', 'id', $oa->id); | |
92 | } | |
93 | } | |
94 | ||
95 | $update = true; | |
96 | $options = get_record('question_'.$this->name(), 'questionid', $question->id); | |
97 | if (!$options) { | |
98 | $update = false; | |
99 | $options = new stdClass; | |
100 | $options->questionid = $question->id; | |
101 | } | |
102 | ||
103 | $options->shuffleanswers = !empty($question->shuffleanswers); | |
104 | $options->correctfeedback = trim($question->correctfeedback); | |
105 | $options->partiallycorrectfeedback = trim($question->partiallycorrectfeedback); | |
106 | $options->shownumcorrect = !empty($question->shownumcorrect); | |
107 | $options->incorrectfeedback = trim($question->incorrectfeedback); | |
108 | ||
109 | if ($update) { | |
110 | if (!update_record('question_'.$this->name(), $options)) { | |
111 | $result->error = "Could not update question type '".$this->name()."' options! (id=$options->id)"; | |
112 | return $result; | |
113 | } | |
114 | ||
115 | } else { | |
116 | if (!insert_record('question_gapselect', $options)) { | |
117 | $result->error = 'Could not insert question type \''.$this->name().'\' options!'; | |
118 | return $result; | |
119 | } | |
120 | } | |
121 | ||
122 | $this->save_hints($question, true); | |
123 | ||
124 | return true; | |
125 | } | |
126 | ||
127 | public function get_question_options($question) { | |
128 | // Get additional information from database and attach it to the question object | |
129 | if (!$question->options = get_record('question_'.$this->name(), 'questionid', $question->id)) { | |
130 | notify('Error: Missing question options for question type \''.$this->name().'\' question '.$question->id.'!'); | |
131 | return false; | |
132 | } | |
133 | ||
134 | parent::get_question_options($question); | |
135 | return true; | |
136 | } | |
137 | ||
138 | public function delete_question($questionid) { | |
139 | delete_records('question_'.$this->name(), 'questionid', $questionid); | |
140 | return parent::delete_question($questionid); | |
141 | } | |
142 | ||
143 | /** | |
144 | * Used by {@link initialise_question_instance()} to set up the choice-specific data. | |
145 | * @param object $choicedata as loaded from the question_answers table. | |
146 | * @return object an appropriate object for representing the choice. | |
147 | */ | |
148 | protected abstract function make_choice($choicedata); | |
149 | ||
150 | protected function initialise_question_instance(question_definition $question, $questiondata) { | |
151 | parent::initialise_question_instance($question, $questiondata); | |
152 | ||
153 | $question->shufflechoices = $questiondata->options->shuffleanswers; | |
154 | ||
155 | $question->correctfeedback = $questiondata->options->correctfeedback; | |
156 | $question->partiallycorrectfeedback = $questiondata->options->partiallycorrectfeedback; | |
157 | $question->incorrectfeedback = $questiondata->options->incorrectfeedback; | |
158 | $question->shownumcorrect = $questiondata->options->shownumcorrect; | |
159 | ||
160 | $question->choices = array(); | |
161 | $choiceindexmap= array(); | |
162 | ||
163 | // Store the choices in arrays by group. | |
164 | $i = 1; | |
165 | foreach ($questiondata->options->answers as $choicedata) { | |
166 | $choice = $this->make_choice($choicedata); | |
167 | ||
168 | if (array_key_exists($choice->choice_group(), $question->choices)) { | |
169 | $question->choices[$choice->choice_group()][] = $choice; | |
170 | } else { | |
171 | $question->choices[$choice->choice_group()][1] = $choice; | |
172 | } | |
173 | ||
174 | end($question->choices[$choice->choice_group()]); | |
175 | $choiceindexmap[$i] = array($choice->choice_group(), | |
176 | key($question->choices[$choice->choice_group()])); | |
177 | $i += 1; | |
178 | } | |
179 | ||
180 | $question->places = array(); | |
181 | $question->textfragments = array(); | |
182 | $question->rightchoices = array(); | |
183 | // Break up the question text, and store the fragments, places and right answers. | |
184 | ||
185 | $bits = preg_split('/\[\[(\d+)]]/', $question->questiontext, null, PREG_SPLIT_DELIM_CAPTURE); | |
186 | $question->textfragments[0] = array_shift($bits); | |
187 | $i = 1; | |
188 | ||
189 | while (!empty($bits)) { | |
190 | $choice = array_shift($bits); | |
191 | ||
192 | list($group, $choiceindex) = $choiceindexmap[$choice]; | |
193 | $question->places[$i] = $group; | |
194 | $question->rightchoices[$i] = $choiceindex; | |
195 | ||
196 | $question->textfragments[$i] = array_shift($bits); | |
197 | $i += 1; | |
198 | } | |
199 | } | |
200 | ||
201 | protected function make_hint($hint) { | |
202 | return question_hint_with_parts::load_from_record($hint); | |
203 | } | |
204 | ||
205 | public function get_random_guess_score($questiondata) { | |
206 | $question = $this->make_question($questiondata); | |
207 | return $question->get_random_guess_score(); | |
208 | } | |
209 | ||
210 | /** | |
211 | * This function should reverse {@link choice_options_to_feedback()}. | |
212 | * @param string $feedback the data loaded from the database. | |
213 | * @return array the choice options. | |
214 | */ | |
215 | protected abstract function feedback_to_choice_options($feedback); | |
216 | ||
217 | /** | |
218 | * This method gets the choices (answers) | |
219 | * in a 2 dimentional array. | |
220 | * | |
221 | * @param object $question | |
222 | * @return array of groups | |
223 | */ | |
224 | protected function get_array_of_choices($question) { | |
225 | $subquestions = $question->options->answers; | |
226 | $count = 0; | |
227 | foreach ($subquestions as $key=>$subquestion) { | |
228 | $answers[$count]['id'] = $subquestion->id; | |
229 | $answers[$count]['answer'] = $subquestion->answer; | |
230 | $answers[$count]['fraction'] = $subquestion->fraction; | |
231 | $answers[$count] += $this->feedback_to_choice_options($subquestion->feedback); | |
232 | $answers[$count]['choice'] = $count+1; | |
233 | ++$count; | |
234 | } | |
235 | return $answers; | |
236 | } | |
237 | ||
238 | /* This method gets the choices (answers) and sort them by groups | |
239 | * in a 2 dimentional array. | |
240 | * | |
241 | * @param object $question | |
242 | * @return array of groups | |
243 | */ | |
244 | protected function get_array_of_groups($question, $state) { | |
245 | $answers = $this->get_array_of_choices($question); | |
246 | $arr = array(); | |
247 | for($group=1;$group<count($answers);$group++) { | |
248 | $players = $this->get_group_of_players ($question, $state, $answers, $group); | |
249 | if($players) { | |
250 | $arr [$group]= $players; | |
251 | } | |
252 | } | |
253 | return $arr; | |
254 | } | |
255 | ||
256 | /** | |
257 | * This method gets the correct answers in a 2 dimentional array. | |
258 | * | |
259 | * @param object $question | |
260 | * @return array of groups | |
261 | */ | |
262 | protected function get_correct_answers($question) { | |
263 | $arrayofchoices = $this->get_array_of_choices($question); | |
264 | $arrayofplaceholdeers = $this->get_array_of_placeholders($question); | |
265 | ||
266 | $correctplayeers = array(); | |
267 | foreach($arrayofplaceholdeers as $ph) { | |
268 | foreach($arrayofchoices as $key=>$choice) { | |
269 | if(($key+1) == $ph) { | |
270 | $correctplayeers[]= $choice; | |
271 | } | |
272 | } | |
273 | } | |
274 | return $correctplayeers; | |
275 | } | |
276 | ||
277 | protected function get_array_of_placeholders($question) { | |
278 | $qtext = $question->questiontext; | |
279 | $error = '<b> ERROR</b>: Please check the form for this question. '; | |
280 | if(!$qtext) { | |
281 | echo $error . 'The question text is empty!'; | |
282 | return false; | |
283 | } | |
284 | ||
285 | //get the slots | |
286 | $slots = $this->getEmbeddedTextArray($question); | |
287 | ||
288 | if(!$slots) { | |
289 | echo $error . 'The question text is not in the correct format!'; | |
290 | return false; | |
291 | } | |
292 | ||
293 | $output = array(); | |
294 | foreach ($slots as $slot) { | |
295 | $output[]=substr($slot, 2, (strlen($slot)-4));//2 is for'[[' and 4 is for '[[]]' | |
296 | } | |
297 | return $output; | |
298 | } | |
299 | ||
300 | protected function get_group_of_players ($question, $state, $subquestions, $group) { | |
301 | $goupofanswers=array(); | |
302 | foreach($subquestions as $key=>$subquestion) { | |
303 | if($subquestion[$this->choice_group_key()] == $group) { | |
304 | $goupofanswers[] = $subquestion; | |
305 | } | |
306 | } | |
307 | ||
308 | //shuffle answers within this group | |
309 | if ($question->options->shuffleanswers == 1) { | |
310 | srand($state->attempt); | |
311 | shuffle($goupofanswers); | |
312 | } | |
313 | return $goupofanswers; | |
314 | } | |
315 | ||
316 | public function get_possible_responses($questiondata) { | |
317 | $question = $this->make_question($questiondata); | |
318 | ||
319 | $parts = array(); | |
320 | foreach ($question->places as $place => $group) { | |
321 | $choices = array(); | |
322 | ||
323 | foreach ($question->choices[$group] as $i => $choice) { | |
324 | $choices[$i] = new question_possible_response( | |
325 | html_to_text($question->format_text($choice->text), 0, false), | |
326 | $question->rightchoices[$place] == $i); | |
327 | } | |
328 | $choices[null] = question_possible_response::no_response(); | |
329 | ||
330 | $parts[$place] = $choices; | |
331 | } | |
332 | ||
333 | return $parts; | |
334 | } | |
335 | ||
336 | ||
337 | } |