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