-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathquestiontype.php
More file actions
316 lines (294 loc) · 11.1 KB
/
questiontype.php
File metadata and controls
316 lines (294 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The question type class for the guessit question type.
*
* @package qtype_guessit
* @subpackage guessit
* @copyright 2025 Joseph Rézeau <moodle@rezeau.org>
* @copyright based on GapFill by 2018 Marcus Green <marcusavgreen@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/engine/lib.php');
/**
*
* The guessit question class
*
* Load from database, and initialise class
* A "fill in the gaps" cloze style question type
* @package qtype_guessit
* @copyright 2025 Joseph Rézeau <moodle@rezeau.org>
* @copyright based on GapFill by 2018 Marcus Green <marcusavgreen@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_guessit extends question_type {
/**
* Whether the quiz statistics report can analyse
* all the student responses. See questiontypebase for more
*
* @return bool
*/
public function can_analyse_responses() {
return false;
}
/**
* data used by export_to_xml (among other things possibly
* @return array
*/
public function extra_question_fields() {
return ['question_guessit', 'guessitgaps', 'nbtriesbeforehelp', 'nbmaxtrieswordle', 'wordle'];
}
/**
* Called during question editing
*
* @param stdClass $question
*/
public function get_question_options($question) {
global $DB;
$question->options = $DB->get_record('question_guessit', ['question' => $question->id], '*', MUST_EXIST);
parent::get_question_options($question);
}
/**
* called when previewing or at runtime in a quiz
*
* @param question_definition $question
* @param stdClass $questiondata
* @param boolean $forceplaintextanswers
*/
protected function initialise_question_answers(question_definition $question, $questiondata, $forceplaintextanswers = true) {
$question->answers = [];
if (empty($questiondata->options->answers)) {
return;
}
foreach ($questiondata->options->answers as $a) {
/* answer in this context means correct answers, i.e. where
* fraction contains a 1 */
if (strpos($a->fraction, '1') !== false) {
$question->answers[$a->id] = new question_answer(
$a->id,
$a->answer,
$a->fraction,
$a->feedback,
$a->feedbackformat
);
$question->gapcount++;
}
}
}
/**
* Called when previewing a question or when displayed in a quiz
* (not from within the editing form)
*
* @param question_definition $question
* @param stdClass $questiondata
*/
protected function initialise_question_instance(question_definition $question, $questiondata) {
parent::initialise_question_instance($question, $questiondata);
$this->initialise_question_answers($question, $questiondata, true);
$question->places = [];
$counter = 1;
$question->maxgapsize = 0;
foreach ($questiondata->options->answers as $choicedata) {
/* fraction contains a 1 */
if (strpos($choicedata->fraction, '1') !== false) {
$question->places[$counter] = $choicedata->answer;
$counter++;
}
}
}
/**
* Saves the question with gap-based default marks.
*
* Calculates the number of gaps from the form data and sets the
* default mark accordingly. Delegates the saving process to the
* parent `save_question` method.
*
* @param stdClass $question The question data to save.
* @param stdClass $form The form data containing user input.
*
* @return mixed The result from the parent `save_question` method.
*/
public function save_question($question, $form) {
$gaps = $this->get_gaps($form->guessitgaps, $form->wordle);
/* count the number of gaps
* this is used to set the maximum
* value for the whole question. Value for
* each gap can be only 0 or 1
*/
$form->defaultmark = count($gaps);
return parent::save_question($question, $form);
}
/**
* it really does need to be static
*
* @param string $guessitgaps
* @param int $wordle
* @return array
*/
public static function get_gaps($guessitgaps, $wordle) {
// Convert the string into an array.
if ($wordle) {
$gaps = str_split($guessitgaps);
} else {
$gaps = explode(' ', $guessitgaps);
}
return $gaps;
}
/**
* Save the answers and options associated with this question.
* @param stdClass $question
* @return boolean to indicate success or failure.
**/
public function save_question_options($question) {
/* Save the extra data to your database tables from the
$question object, which has all the post data from editquestion.html */
global $DB;
$gaps = $this->get_gaps($question->guessitgaps, $question->wordle);
$answerfields = $this->get_answer_fields($gaps, $question);
$context = $question->context;
// Fetch old answer ids so that we can reuse them.
$this->update_question_answers($question, $answerfields);
$options = $DB->get_record('question_guessit', ['question' => $question->id]);
$this->update_question_guessit($question, $options, $context);
return true;
}
/**
* Writes to the database, runs from question editing form
*
* @param stdClass $question
* @param stdClass $options
* @param context_course_object $context
*/
public function update_question_guessit($question, $options, $context) {
global $DB;
$options = $DB->get_record('question_guessit', ['question' => $question->id]);
if (!$options) {
$options = new stdClass();
$options->question = $question->id;
$options->guessitgaps = '';
$options->nbtriesbeforehelp = '';
$options->nbmaxtrieswordle = '';
$options->wordle = '';
$options->id = $DB->insert_record('question_guessit', $options);
}
$options->guessitgaps = $question->guessitgaps;
$options->nbtriesbeforehelp = $question->nbtriesbeforehelp;
$options->nbmaxtrieswordle = $question->nbmaxtrieswordle;
$options->wordle = $question->wordle;
$DB->update_record('question_guessit', $options);
}
/**
* Write to the database during editing
*
* @param stdClass $question
* @param array $answerfields
*/
public function update_question_answers($question, array $answerfields) {
global $DB;
$oldanswers = $DB->get_records('question_answers', ['question' => $question->id], 'id ASC');
// Insert all the new answers.
foreach ($answerfields as $field) {
// Save the true answer - update an existing answer if possible.
if ($answer = array_shift($oldanswers)) {
$answer->question = $question->id;
$answer->answer = $field['value'];
$answer->feedback = '';
$answer->fraction = $field['fraction'];
$DB->update_record('question_answers', $answer);
} else {
// Insert a blank record.
$answer = new stdClass();
$answer->question = $question->id;
$answer->answer = $field['value'];
$answer->feedback = '';
$answer->fraction = $field['fraction'];
$answer->id = $DB->insert_record('question_answers', $answer);
}
}
// Delete old answer records.
foreach ($oldanswers as $oa) {
$DB->delete_records('question_answers', ['id' => $oa->id]);
}
}
/**
* Set up all the answer fields with respective fraction (mark values)
* This is used to update the question_answers table.
*
* @param array $answerwords
* @param stdClass $question
* @return array
*/
public function get_answer_fields(array $answerwords, $question) {
/* This code runs both on saving from a form and from importing. */
if (!property_exists($question, 'answer')) {
foreach ($answerwords as $key => $value) {
$answerfields[$key]['value'] = $value;
$answerfields[$key]['fraction'] = 1;
}
}
return $answerfields;
}
/**
* The name of the key column in the foreign table (might have been questionid instead)
* @return string
*/
public function questionid_column_name() {
return 'question';
}
/**
* Create a question from reading in a file in Moodle xml format
*
* @param array $data
* @param stdClass $question (might be an array)
* @param qformat_xml $format
* @param stdClass $extra
* @return boolean
*/
public function import_from_xml($data, $question, qformat_xml $format, $extra = null) {
if (!isset($data['@']['type']) || $data['@']['type'] != 'guessit') {
return false;
}
/* There are no answers to import as they will be constructed later on */
$data['#']['answer'] = [];
$question = parent::import_from_xml($data, $question, $format, null);
$question->isimport = true;
return $question;
}
/**
* Export question to the Moodle XML format
*
* @param object $question
* @param qformat_xml $format
* @param object $extra
* @return string
*/
public function export_to_xml($question, qformat_xml $format, $extra = null) {
global $CFG;
/* No need to export the answers as they will be constructed upon import */
$question->options->answers = [];
$pluginmanager = core_plugin_manager::instance();
$guessitinfo = $pluginmanager->get_plugin_info('qtype_guessit');
$output = parent::export_to_xml($question, $format);
$output .= ' <!-- guessit release:'
. $guessitinfo->release . ' version:' . $guessitinfo->versiondisk . ' Moodle version:'
. $CFG->version . ' release:' . $CFG->release
. " -->\n";
return $output;
}
}