Commit | Line | Data |
---|---|---|
d1b7e03d | 1 | <?php |
d1b7e03d 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 | ||
d1b7e03d TH |
17 | /** |
18 | * This file defines the class {@link question_definition} and its subclasses. | |
19 | * | |
b04a4319 | 20 | * @package moodlecore |
d1b7e03d | 21 | * @subpackage questiontypes |
b04a4319 TH |
22 | * @copyright 2009 The Open University |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
24 | */ |
25 | ||
26 | ||
a17b297d TH |
27 | defined('MOODLE_INTERNAL') || die(); |
28 | ||
29 | ||
d1b7e03d TH |
30 | /** |
31 | * The definition of a question of a particular type. | |
32 | * | |
33 | * This class is a close match to the question table in the database. | |
34 | * Definitions of question of a particular type normally subclass one of the | |
35 | * more specific classes {@link question_with_responses}, | |
36 | * {@link question_graded_automatically} or {@link question_information_item}. | |
37 | * | |
7764183a TH |
38 | * @copyright 2009 The Open University |
39 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
40 | */ |
41 | abstract class question_definition { | |
42 | /** @var integer id of the question in the datase, or null if this question | |
43 | * is not in the database. */ | |
44 | public $id; | |
45 | ||
46 | /** @var integer question category id. */ | |
47 | public $category; | |
48 | ||
e35ba43c | 49 | /** @var integer question context id. */ |
56e82d99 TH |
50 | public $contextid; |
51 | ||
d1b7e03d TH |
52 | /** @var integer parent question id. */ |
53 | public $parent = 0; | |
54 | ||
55 | /** @var question_type the question type this question is. */ | |
56 | public $qtype; | |
57 | ||
58 | /** @var string question name. */ | |
59 | public $name; | |
60 | ||
61 | /** @var string question text. */ | |
62 | public $questiontext; | |
63 | ||
64 | /** @var integer question test format. */ | |
65 | public $questiontextformat; | |
66 | ||
67 | /** @var string question general feedback. */ | |
68 | public $generalfeedback; | |
69 | ||
1c2ed7c5 TH |
70 | /** @var integer question test format. */ |
71 | public $generalfeedbackformat; | |
72 | ||
d1b7e03d TH |
73 | /** @var number what this quetsion is marked out of, by default. */ |
74 | public $defaultmark = 1; | |
75 | ||
76 | /** @var integer How many question numbers this question consumes. */ | |
77 | public $length = 1; | |
78 | ||
79 | /** @var number penalty factor of this question. */ | |
80 | public $penalty = 0; | |
81 | ||
82 | /** @var string unique identifier of this question. */ | |
83 | public $stamp; | |
84 | ||
85 | /** @var string unique identifier of this version of this question. */ | |
86 | public $version; | |
87 | ||
88 | /** @var boolean whethre this question has been deleted/hidden in the question bank. */ | |
89 | public $hidden = 0; | |
90 | ||
91 | /** @var integer timestamp when this question was created. */ | |
92 | public $timecreated; | |
93 | ||
94 | /** @var integer timestamp when this question was modified. */ | |
95 | public $timemodified; | |
96 | ||
97 | /** @var integer userid of the use who created this question. */ | |
98 | public $createdby; | |
99 | ||
100 | /** @var integer userid of the use who modified this question. */ | |
101 | public $modifiedby; | |
102 | ||
103 | /** @var array of question_hints. */ | |
104 | public $hints = array(); | |
105 | ||
106 | /** | |
107 | * Constructor. Normally to get a question, you call | |
108 | * {@link question_bank::load_question()}, but questions can be created | |
109 | * directly, for example in unit test code. | |
110 | * @return unknown_type | |
111 | */ | |
112 | public function __construct() { | |
113 | } | |
114 | ||
115 | /** | |
116 | * @return the name of the question type (for example multichoice) that this | |
117 | * question is. | |
118 | */ | |
119 | public function get_type_name() { | |
120 | return $this->qtype->name(); | |
121 | } | |
122 | ||
123 | /** | |
124 | * Creat the appropriate behaviour for an attempt at this quetsion, | |
125 | * given the desired (archetypal) behaviour. | |
126 | * | |
127 | * This default implementation will suit most normal graded questions. | |
128 | * | |
129 | * If your question is of a patricular type, then it may need to do something | |
130 | * different. For example, if your question can only be graded manually, then | |
131 | * it should probably return a manualgraded behaviour, irrespective of | |
132 | * what is asked for. | |
133 | * | |
134 | * If your question wants to do somthing especially complicated is some situations, | |
135 | * then you may wish to return a particular behaviour related to the | |
136 | * one asked for. For example, you migth want to return a | |
137 | * qbehaviour_interactive_adapted_for_myqtype. | |
138 | * | |
139 | * @param question_attempt $qa the attempt we are creating an behaviour for. | |
140 | * @param string $preferredbehaviour the requested type of behaviour. | |
141 | * @return question_behaviour the new behaviour object. | |
142 | */ | |
143 | public function make_behaviour(question_attempt $qa, $preferredbehaviour) { | |
144 | return question_engine::make_archetypal_behaviour($preferredbehaviour, $qa); | |
145 | } | |
146 | ||
147 | /** | |
ef31a283 TH |
148 | * Start a new attempt at this question, storing any information that will |
149 | * be needed later in the step. | |
d1b7e03d | 150 | * |
ef31a283 TH |
151 | * This is where the question can do any initialisation required on a |
152 | * per-attempt basis. For example, this is where the multiple choice | |
153 | * question type randomly shuffles the choices (if that option is set). | |
d1b7e03d | 154 | * |
ef31a283 TH |
155 | * Any information about how the question has been set up for this attempt |
156 | * should be stored in the $step, by calling $step->set_qt_var(...). | |
157 | * | |
158 | * @param question_attempt_step The first step of the {@link question_attempt} | |
159 | * being started. Can be used to store state. | |
160 | */ | |
161 | public function start_attempt(question_attempt_step $step) { | |
162 | } | |
163 | ||
164 | /** | |
165 | * When an in-progress {@link question_attempt} is re-loaded from the | |
166 | * database, this method is called so that the question can re-initialise | |
167 | * its internal state as needed by this attempt. | |
168 | * | |
169 | * For example, the multiple choice question type needs to set the order | |
170 | * of the choices to the order that was set up when start_attempt was called | |
171 | * originally. All the information required to do this should be in the | |
172 | * $step object, which is the first step of the question_attempt being loaded. | |
173 | * | |
174 | * @param question_attempt_step The first step of the {@link question_attempt} | |
175 | * being loaded. | |
d1b7e03d | 176 | */ |
ef31a283 | 177 | public function apply_attempt_state(question_attempt_step $step) { |
d1b7e03d TH |
178 | } |
179 | ||
180 | /** | |
181 | * Generate a brief, plain-text, summary of this question. This is used by | |
182 | * various reports. This should show the particular variant of the question | |
183 | * as presented to students. For example, the calculated quetsion type would | |
184 | * fill in the particular numbers that were presented to the student. | |
185 | * This method will return null if such a summary is not possible, or | |
186 | * inappropriate. | |
187 | * @return string|null a plain text summary of this question. | |
188 | */ | |
ec3d4ef5 | 189 | public function get_question_summary() { |
22cebed5 | 190 | return $this->html_to_text($this->questiontext, $this->questiontextformat); |
d1b7e03d TH |
191 | } |
192 | ||
193 | /** | |
194 | * Some questions can return a negative mark if the student gets it wrong. | |
195 | * | |
196 | * This method returns the lowest mark the question can return, on the | |
197 | * fraction scale. that is, where the maximum possible mark is 1.0. | |
198 | * | |
199 | * @return number minimum fraction this question will ever return. | |
200 | */ | |
201 | public function get_min_fraction() { | |
202 | return 0; | |
203 | } | |
204 | ||
205 | /** | |
206 | * Given a response, rest the parts that are wrong. | |
207 | * @param array $response a response | |
208 | * @return array a cleaned up response with the wrong bits reset. | |
209 | */ | |
210 | public function clear_wrong_from_response(array $response) { | |
211 | return array(); | |
212 | } | |
213 | ||
214 | /** | |
215 | * Return the number of subparts of this response that are right. | |
216 | * @param array $response a response | |
217 | * @return array with two elements, the number of correct subparts, and | |
218 | * the total number of subparts. | |
219 | */ | |
220 | public function get_num_parts_right(array $response) { | |
221 | return array(null, null); | |
222 | } | |
223 | ||
224 | /** | |
b36d2d06 | 225 | * @param moodle_page the page we are outputting to. |
d1b7e03d TH |
226 | * @return qtype_renderer the renderer to use for outputting this question. |
227 | */ | |
2daffca5 TH |
228 | public function get_renderer(moodle_page $page) { |
229 | return $page->get_renderer($this->qtype->plugin_name()); | |
d1b7e03d TH |
230 | } |
231 | ||
232 | /** | |
233 | * What data may be included in the form submission when a student submits | |
234 | * this question in its current state? | |
235 | * | |
236 | * This information is used in calls to optional_param. The parameter name | |
237 | * has {@link question_attempt::get_field_prefix()} automatically prepended. | |
238 | * | |
239 | * @return array|string variable name => PARAM_... constant, or, as a special case | |
240 | * that should only be used in unavoidable, the constant question_attempt::USE_RAW_DATA | |
241 | * meaning take all the raw submitted data belonging to this question. | |
242 | */ | |
243 | public abstract function get_expected_data(); | |
244 | ||
245 | /** | |
246 | * What data would need to be submitted to get this question correct. | |
247 | * If there is more than one correct answer, this method should just | |
248 | * return one possibility. | |
249 | * | |
250 | * @return array parameter name => value. | |
251 | */ | |
252 | public abstract function get_correct_response(); | |
253 | ||
254 | /** | |
255 | * Apply {@link format_text()} to some content with appropriate settings for | |
256 | * this question. | |
257 | * | |
258 | * @param string $text some content that needs to be output. | |
22cebed5 | 259 | * @param int $format the FORMAT_... constant. |
068b4594 TH |
260 | * @param question_attempt $qa the question attempt. |
261 | * @param string $component used for rewriting file area URLs. | |
262 | * @param string $filearea used for rewriting file area URLs. | |
f7970e3c | 263 | * @param bool $clean Whether the HTML needs to be cleaned. Generally, |
d1b7e03d TH |
264 | * parts of the question do not need to be cleaned, and student input does. |
265 | * @return string the text formatted for output by format_text. | |
266 | */ | |
eaeb6b51 TH |
267 | public function format_text($text, $format, $qa, $component, $filearea, $itemid, |
268 | $clean = false) { | |
0ff4bd08 | 269 | $formatoptions = new stdClass(); |
d1b7e03d TH |
270 | $formatoptions->noclean = !$clean; |
271 | $formatoptions->para = false; | |
7a719748 | 272 | $text = $qa->rewrite_pluginfile_urls($text, $component, $filearea, $itemid); |
22cebed5 | 273 | return format_text($text, $format, $formatoptions); |
d1b7e03d TH |
274 | } |
275 | ||
ec3d4ef5 TH |
276 | /** |
277 | * Convert some part of the question text to plain text. This might be used, | |
278 | * for example, by get_response_summary(). | |
279 | * @param string $text The HTML to reduce to plain text. | |
22cebed5 TH |
280 | * @param int $format the FORMAT_... constant. |
281 | * @return string the equivalent plain text. | |
ec3d4ef5 | 282 | */ |
22cebed5 | 283 | public function html_to_text($text, $format) { |
0ff4bd08 | 284 | $formatoptions = new stdClass(); |
ec3d4ef5 | 285 | $formatoptions->noclean = true; |
22cebed5 | 286 | return html_to_text(format_text($text, $format, $formatoptions), 0, false); |
ec3d4ef5 TH |
287 | } |
288 | ||
d1b7e03d | 289 | /** @return the result of applying {@link format_text()} to the question text. */ |
2b7da645 | 290 | public function format_questiontext($qa) { |
22cebed5 TH |
291 | return $this->format_text($this->questiontext, $this->questiontextformat, |
292 | $qa, 'question', 'questiontext', $this->id); | |
d1b7e03d TH |
293 | } |
294 | ||
295 | /** @return the result of applying {@link format_text()} to the general feedback. */ | |
2b7da645 | 296 | public function format_generalfeedback($qa) { |
22cebed5 TH |
297 | return $this->format_text($this->generalfeedback, $this->generalfeedbackformat, |
298 | $qa, 'question', 'generalfeedback', $this->id); | |
7a719748 TH |
299 | } |
300 | ||
301 | /** | |
302 | * Checks whether the users is allow to be served a particular file. | |
93cadb1e | 303 | * @param question_attempt $qa the question attempt being displayed. |
7a719748 TH |
304 | * @param question_display_options $options the options that control display of the question. |
305 | * @param string $component the name of the component we are serving files for. | |
306 | * @param string $filearea the name of the file area. | |
307 | * @param array $args the remaining bits of the file path. | |
f7970e3c TH |
308 | * @param bool $forcedownload whether the user must be forced to download the file. |
309 | * @return bool true if the user can access this file. | |
7a719748 TH |
310 | */ |
311 | public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { | |
312 | if ($component == 'question' && $filearea == 'questiontext') { | |
313 | // Question text always visible. | |
314 | return true; | |
315 | ||
316 | } else if ($component == 'question' && $filearea == 'generalfeedback') { | |
317 | return $options->generalfeedback; | |
318 | ||
319 | } else { | |
320 | // Unrecognised component or filearea. | |
321 | return false; | |
322 | } | |
d1b7e03d TH |
323 | } |
324 | } | |
325 | ||
326 | ||
327 | /** | |
328 | * This class represents a 'question' that actually does not allow the student | |
329 | * to respond, like the description 'question' type. | |
330 | * | |
7764183a TH |
331 | * @copyright 2009 The Open University |
332 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
333 | */ |
334 | class question_information_item extends question_definition { | |
335 | public function __construct() { | |
336 | parent::__construct(); | |
337 | $this->defaultmark = 0; | |
338 | $this->penalty = 0; | |
339 | $this->length = 0; | |
340 | } | |
341 | ||
342 | public function make_behaviour(question_attempt $qa, $preferredbehaviour) { | |
343 | question_engine::load_behaviour_class('informationitem'); | |
344 | return new qbehaviour_informationitem($qa, $preferredbehaviour); | |
345 | } | |
346 | ||
347 | public function get_expected_data() { | |
348 | return array(); | |
349 | } | |
350 | ||
351 | public function get_correct_response() { | |
352 | return array(); | |
353 | } | |
354 | ||
355 | public function get_question_summary() { | |
356 | return null; | |
357 | } | |
358 | } | |
359 | ||
360 | ||
361 | /** | |
362 | * Interface that a {@link question_definition} must implement to be usable by | |
363 | * the manual graded behaviour. | |
364 | * | |
7764183a TH |
365 | * @copyright 2009 The Open University |
366 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
367 | */ |
368 | interface question_manually_gradable { | |
369 | /** | |
370 | * Used by many of the behaviours, to work out whether the student's | |
371 | * response to the question is complete. That is, whether the question attempt | |
372 | * should move to the COMPLETE or INCOMPLETE state. | |
373 | * | |
eaeb6b51 TH |
374 | * @param array $response responses, as returned by |
375 | * {@link question_attempt_step::get_qt_data()}. | |
f7970e3c | 376 | * @return bool whether this response is a complete answer to this question. |
d1b7e03d TH |
377 | */ |
378 | public function is_complete_response(array $response); | |
379 | ||
380 | /** | |
381 | * Use by many of the behaviours to determine whether the student's | |
382 | * response has changed. This is normally used to determine that a new set | |
383 | * of responses can safely be discarded. | |
384 | * | |
385 | * @param array $prevresponse the responses previously recorded for this question, | |
386 | * as returned by {@link question_attempt_step::get_qt_data()} | |
387 | * @param array $newresponse the new responses, in the same format. | |
f7970e3c | 388 | * @return bool whether the two sets of responses are the same - that is |
d1b7e03d TH |
389 | * whether the new set of responses can safely be discarded. |
390 | */ | |
391 | public function is_same_response(array $prevresponse, array $newresponse); | |
392 | ||
393 | /** | |
394 | * Produce a plain text summary of a response. | |
395 | * @param $response a response, as might be passed to {@link grade_response()}. | |
396 | * @return string a plain text summary of that response, that could be used in reports. | |
397 | */ | |
398 | public function summarise_response(array $response); | |
399 | ||
400 | /** | |
401 | * Categorise the student's response according to the categories defined by | |
402 | * get_possible_responses. | |
403 | * @param $response a response, as might be passed to {@link grade_response()}. | |
404 | * @return array subpartid => {@link question_classified_response} objects. | |
405 | * returns an empty array if no analysis is possible. | |
406 | */ | |
407 | public function classify_response(array $response); | |
408 | } | |
409 | ||
410 | ||
411 | /** | |
412 | * This class is used in the return value from | |
413 | * {@link question_manually_gradable::classify_response()}. | |
414 | * | |
7764183a TH |
415 | * @copyright 2010 The Open University |
416 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
417 | */ |
418 | class question_classified_response { | |
419 | /** | |
420 | * @var string the classification of this response the student gave to this | |
421 | * part of the question. Must match one of the responseclasses returned by | |
422 | * {@link question_type::get_possible_responses()}. | |
423 | */ | |
424 | public $responseclassid; | |
425 | /** @var string the actual response the student gave to this part. */ | |
426 | public $response; | |
427 | /** @var number the fraction this part of the response earned. */ | |
428 | public $fraction; | |
429 | /** | |
430 | * Constructor, just an easy way to set the fields. | |
431 | * @param string $responseclassid see the field descriptions above. | |
432 | * @param string $response see the field descriptions above. | |
433 | * @param number $fraction see the field descriptions above. | |
434 | */ | |
435 | public function __construct($responseclassid, $response, $fraction) { | |
436 | $this->responseclassid = $responseclassid; | |
437 | $this->response = $response; | |
438 | $this->fraction = $fraction; | |
439 | } | |
440 | ||
441 | public static function no_response() { | |
442 | return new question_classified_response(null, null, null); | |
443 | } | |
444 | } | |
445 | ||
446 | ||
447 | /** | |
448 | * Interface that a {@link question_definition} must implement to be usable by | |
449 | * the various automatic grading behaviours. | |
450 | * | |
7764183a TH |
451 | * @copyright 2009 The Open University |
452 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
453 | */ |
454 | interface question_automatically_gradable extends question_manually_gradable { | |
455 | /** | |
456 | * Use by many of the behaviours to determine whether the student | |
457 | * has provided enough of an answer for the question to be graded automatically, | |
458 | * or whether it must be considered aborted. | |
459 | * | |
eaeb6b51 TH |
460 | * @param array $response responses, as returned by |
461 | * {@link question_attempt_step::get_qt_data()}. | |
f7970e3c | 462 | * @return bool whether this response can be graded. |
d1b7e03d TH |
463 | */ |
464 | public function is_gradable_response(array $response); | |
465 | ||
466 | /** | |
467 | * In situations where is_gradable_response() returns false, this method | |
468 | * should generate a description of what the problem is. | |
469 | * @return string the message. | |
470 | */ | |
471 | public function get_validation_error(array $response); | |
472 | ||
473 | /** | |
eaeb6b51 TH |
474 | * Grade a response to the question, returning a fraction between |
475 | * get_min_fraction() and 1.0, and the corresponding {@link question_state} | |
476 | * right, partial or wrong. | |
477 | * @param array $response responses, as returned by | |
478 | * {@link question_attempt_step::get_qt_data()}. | |
d1b7e03d TH |
479 | * @return array (number, integer) the fraction, and the state. |
480 | */ | |
481 | public function grade_response(array $response); | |
482 | ||
483 | /** | |
484 | * Get one of the question hints. The question_attempt is passed in case | |
485 | * the question type wants to do something complex. For example, the | |
486 | * multiple choice with multiple responses question type will turn off most | |
487 | * of the hint options if the student has selected too many opitions. | |
f7970e3c | 488 | * @param int $hintnumber Which hint to display. Indexed starting from 0 |
d1b7e03d TH |
489 | * @param question_attempt $qa The question_attempt. |
490 | */ | |
491 | public function get_hint($hintnumber, question_attempt $qa); | |
492 | ||
493 | /** | |
494 | * Generate a brief, plain-text, summary of the correct answer to this question. | |
495 | * This is used by various reports, and can also be useful when testing. | |
496 | * This method will return null if such a summary is not possible, or | |
497 | * inappropriate. | |
498 | * @return string|null a plain text summary of the right answer to this question. | |
499 | */ | |
500 | public function get_right_answer_summary(); | |
501 | } | |
502 | ||
503 | ||
504 | /** | |
505 | * Interface that a {@link question_definition} must implement to be usable by | |
506 | * the interactivecountback behaviour. | |
507 | * | |
7764183a TH |
508 | * @copyright 2010 The Open University |
509 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
510 | */ |
511 | interface question_automatically_gradable_with_countback extends question_automatically_gradable { | |
512 | /** | |
513 | * Work out a final grade for this attempt, taking into account all the | |
514 | * tries the student made. | |
515 | * @param array $responses the response for each try. Each element of this | |
516 | * array is a response array, as would be passed to {@link grade_response()}. | |
517 | * There may be between 1 and $totaltries responses. | |
f7970e3c | 518 | * @param int $totaltries The maximum number of tries allowed. |
d1b7e03d TH |
519 | * @return numeric the fraction that should be awarded for this |
520 | * sequence of response. | |
521 | */ | |
522 | public function compute_final_grade($responses, $totaltries); | |
523 | } | |
524 | ||
525 | ||
526 | /** | |
527 | * This class represents a real question. That is, one that is not a | |
528 | * {@link question_information_item}. | |
529 | * | |
7764183a TH |
530 | * @copyright 2009 The Open University |
531 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
532 | */ |
533 | abstract class question_with_responses extends question_definition | |
534 | implements question_manually_gradable { | |
c7df5006 | 535 | public function classify_response(array $response) { |
d1b7e03d TH |
536 | return array(); |
537 | } | |
538 | } | |
539 | ||
540 | ||
541 | /** | |
542 | * This class represents a question that can be graded automatically. | |
543 | * | |
7764183a TH |
544 | * @copyright 2009 The Open University |
545 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
546 | */ |
547 | abstract class question_graded_automatically extends question_with_responses | |
548 | implements question_automatically_gradable { | |
549 | /** @var Some question types have the option to show the number of sub-parts correct. */ | |
550 | public $shownumcorrect = false; | |
551 | ||
552 | public function is_gradable_response(array $response) { | |
553 | return $this->is_complete_response($response); | |
554 | } | |
555 | ||
556 | public function get_right_answer_summary() { | |
557 | $correctresponse = $this->get_correct_response(); | |
558 | if (empty($correctresponse)) { | |
559 | return null; | |
560 | } | |
561 | return $this->summarise_response($correctresponse); | |
562 | } | |
563 | ||
93cadb1e TH |
564 | /** |
565 | * Check a request for access to a file belonging to a combined feedback field. | |
566 | * @param question_attempt $qa the question attempt being displayed. | |
567 | * @param question_display_options $options the options that control display of the question. | |
568 | * @param string $filearea the name of the file area. | |
f7970e3c | 569 | * @return bool whether access to the file should be allowed. |
93cadb1e TH |
570 | */ |
571 | protected function check_combined_feedback_file_access($qa, $options, $filearea) { | |
572 | $state = $qa->get_state(); | |
573 | ||
574 | if (!$state->is_finished()) { | |
575 | $response = $qa->get_last_qt_data(); | |
576 | if (!$this->is_gradable_response($response)) { | |
577 | return false; | |
578 | } | |
579 | list($notused, $state) = $this->grade_response($response); | |
580 | } | |
581 | ||
582 | return $options->feedback && $state->get_feedback_class() . 'feedback' == $filearea; | |
583 | } | |
584 | ||
585 | /** | |
586 | * Check a request for access to a file belonging to a hint. | |
587 | * @param question_attempt $qa the question attempt being displayed. | |
588 | * @param question_display_options $options the options that control display of the question. | |
589 | * @param array $args the remaining bits of the file path. | |
f7970e3c | 590 | * @return bool whether access to the file should be allowed. |
93cadb1e TH |
591 | */ |
592 | protected function check_hint_file_access($qa, $options, $args) { | |
593 | if (!$options->feedback) { | |
594 | return false; | |
595 | } | |
596 | $hint = $qa->get_applicable_hint(); | |
597 | $hintid = reset($args); // itemid is hint id. | |
598 | return $hintid == $hint->id; | |
599 | } | |
600 | ||
d1b7e03d TH |
601 | public function get_hint($hintnumber, question_attempt $qa) { |
602 | if (!isset($this->hints[$hintnumber])) { | |
603 | return null; | |
604 | } | |
605 | return $this->hints[$hintnumber]; | |
606 | } | |
7a719748 TH |
607 | |
608 | public function format_hint(question_hint $hint, question_attempt $qa) { | |
eaeb6b51 TH |
609 | return $this->format_text($hint->hint, $hint->hintformat, $qa, |
610 | 'question', 'hint', $hint->id); | |
7a719748 | 611 | } |
d1b7e03d TH |
612 | } |
613 | ||
614 | ||
615 | /** | |
616 | * This class represents a question that can be graded automatically with | |
617 | * countback grading in interactive mode. | |
618 | * | |
7764183a TH |
619 | * @copyright 2010 The Open University |
620 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
621 | */ |
622 | abstract class question_graded_automatically_with_countback | |
623 | extends question_graded_automatically | |
624 | implements question_automatically_gradable_with_countback { | |
625 | ||
626 | public function make_behaviour(question_attempt $qa, $preferredbehaviour) { | |
627 | if ($preferredbehaviour == 'interactive') { | |
eaeb6b51 TH |
628 | return question_engine::make_behaviour('interactivecountback', |
629 | $qa, $preferredbehaviour); | |
d1b7e03d TH |
630 | } |
631 | return question_engine::make_archetypal_behaviour($preferredbehaviour, $qa); | |
632 | } | |
633 | } | |
634 | ||
635 | ||
636 | /** | |
637 | * This class represents a question that can be graded automatically by using | |
638 | * a {@link question_grading_strategy}. | |
639 | * | |
7764183a TH |
640 | * @copyright 2009 The Open University |
641 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
642 | */ |
643 | abstract class question_graded_by_strategy extends question_graded_automatically { | |
644 | /** @var question_grading_strategy the strategy to use for grading. */ | |
645 | protected $gradingstrategy; | |
646 | ||
647 | /** @param question_grading_strategy $strategy the strategy to use for grading. */ | |
648 | public function __construct(question_grading_strategy $strategy) { | |
649 | parent::__construct(); | |
650 | $this->gradingstrategy = $strategy; | |
651 | } | |
652 | ||
653 | public function get_correct_response() { | |
654 | $answer = $this->get_correct_answer(); | |
655 | if (!$answer) { | |
656 | return array(); | |
657 | } | |
658 | ||
659 | return array('answer' => $answer->answer); | |
660 | } | |
661 | ||
662 | /** | |
663 | * Get an answer that contains the feedback and fraction that should be | |
664 | * awarded for this resonse. | |
665 | * @param array $response a response. | |
666 | * @return question_answer the matching answer. | |
667 | */ | |
668 | public function get_matching_answer(array $response) { | |
669 | return $this->gradingstrategy->grade($response); | |
670 | } | |
671 | ||
672 | /** | |
673 | * @return question_answer an answer that contains the a response that would | |
674 | * get full marks. | |
675 | */ | |
676 | public function get_correct_answer() { | |
677 | return $this->gradingstrategy->get_correct_answer(); | |
678 | } | |
679 | ||
680 | public function grade_response(array $response) { | |
681 | $answer = $this->get_matching_answer($response); | |
682 | if ($answer) { | |
eaeb6b51 TH |
683 | return array($answer->fraction, |
684 | question_state::graded_state_for_fraction($answer->fraction)); | |
d1b7e03d TH |
685 | } else { |
686 | return array(0, question_state::$gradedwrong); | |
687 | } | |
688 | } | |
689 | ||
690 | public function classify_response(array $response) { | |
691 | if (empty($response['answer'])) { | |
692 | return array($this->id => question_classified_response::no_response()); | |
693 | } | |
694 | ||
695 | $ans = $this->get_matching_answer($response); | |
696 | if (!$ans) { | |
697 | return array($this->id => question_classified_response::no_response()); | |
698 | } | |
699 | return array($this->id => new question_classified_response( | |
700 | $ans->id, $response['answer'], $ans->fraction)); | |
701 | } | |
702 | } | |
703 | ||
704 | ||
705 | /** | |
706 | * Class to represent a question answer, loaded from the question_answers table | |
707 | * in the database. | |
708 | * | |
7764183a TH |
709 | * @copyright 2009 The Open University |
710 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
711 | */ |
712 | class question_answer { | |
5f7cfba7 TH |
713 | /** @var integer the answer id. */ |
714 | public $id; | |
715 | ||
d1b7e03d TH |
716 | /** @var string the answer. */ |
717 | public $answer; | |
718 | ||
22cebed5 TH |
719 | /** @var integer one of the FORMAT_... constans. */ |
720 | public $answerformat = FORMAT_PLAIN; | |
721 | ||
d1b7e03d TH |
722 | /** @var number the fraction this answer is worth. */ |
723 | public $fraction; | |
724 | ||
725 | /** @var string the feedback for this answer. */ | |
726 | public $feedback; | |
727 | ||
5f7cfba7 TH |
728 | /** @var integer one of the FORMAT_... constans. */ |
729 | public $feedbackformat; | |
730 | ||
d1b7e03d TH |
731 | /** |
732 | * Constructor. | |
22cebed5 | 733 | * @param int $id the answer. |
d1b7e03d | 734 | * @param string $answer the answer. |
22cebed5 | 735 | * @param int $answerformat the format of the answer. |
d1b7e03d TH |
736 | * @param number $fraction the fraction this answer is worth. |
737 | * @param string $feedback the feedback for this answer. | |
22cebed5 | 738 | * @param int $feedbackformat the format of the feedback. |
d1b7e03d | 739 | */ |
5f7cfba7 TH |
740 | public function __construct($id, $answer, $fraction, $feedback, $feedbackformat) { |
741 | $this->id = $id; | |
d1b7e03d TH |
742 | $this->answer = $answer; |
743 | $this->fraction = $fraction; | |
744 | $this->feedback = $feedback; | |
5f7cfba7 | 745 | $this->feedbackformat = $feedbackformat; |
d1b7e03d TH |
746 | } |
747 | } | |
748 | ||
749 | ||
750 | /** | |
751 | * Class to represent a hint associated with a question. | |
752 | * Used by iteractive mode, etc. A question has an array of these. | |
753 | * | |
7764183a TH |
754 | * @copyright 2010 The Open University |
755 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
756 | */ |
757 | class question_hint { | |
7a719748 TH |
758 | /** @var integer The hint id. */ |
759 | public $id; | |
760 | /** @var string The feedback hint to be shown. */ | |
d1b7e03d | 761 | public $hint; |
7a719748 TH |
762 | /** @var integer The corresponding text FORMAT_... type. */ |
763 | public $hintformat; | |
d1b7e03d TH |
764 | |
765 | /** | |
766 | * Constructor. | |
f7970e3c | 767 | * @param int the hint id from the database. |
d1b7e03d | 768 | * @param string $hint The hint text |
f7970e3c | 769 | * @param int the corresponding text FORMAT_... type. |
d1b7e03d | 770 | */ |
7a719748 TH |
771 | public function __construct($id, $hint, $hintformat) { |
772 | $this->id = $id; | |
d1b7e03d | 773 | $this->hint = $hint; |
7a719748 | 774 | $this->hintformat = $hintformat; |
d1b7e03d TH |
775 | } |
776 | ||
777 | /** | |
778 | * Create a basic hint from a row loaded from the question_hints table in the database. | |
779 | * @param object $row with $row->hint set. | |
780 | * @return question_hint | |
781 | */ | |
782 | public static function load_from_record($row) { | |
7a719748 | 783 | return new question_hint($row->id, $row->hint, $row->hintformat); |
d1b7e03d TH |
784 | } |
785 | ||
786 | /** | |
787 | * Adjust this display options according to the hint settings. | |
788 | * @param question_display_options $options | |
789 | */ | |
790 | public function adjust_display_options(question_display_options $options) { | |
791 | // Do nothing. | |
792 | } | |
793 | } | |
794 | ||
795 | ||
796 | /** | |
797 | * An extension of {@link question_hint} for questions like match and multiple | |
798 | * choice with multile answers, where there are options for whether to show the | |
799 | * number of parts right at each stage, and to reset the wrong parts. | |
800 | * | |
7764183a TH |
801 | * @copyright 2010 The Open University |
802 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
803 | */ |
804 | class question_hint_with_parts extends question_hint { | |
805 | /** @var boolean option to show the number of sub-parts of the question that were right. */ | |
806 | public $shownumcorrect; | |
807 | ||
808 | /** @var boolean option to clear the parts of the question that were wrong on retry. */ | |
809 | public $clearwrong; | |
810 | ||
811 | /** | |
812 | * Constructor. | |
f7970e3c | 813 | * @param int the hint id from the database. |
d1b7e03d | 814 | * @param string $hint The hint text |
f7970e3c TH |
815 | * @param int the corresponding text FORMAT_... type. |
816 | * @param bool $shownumcorrect whether the number of right parts should be shown | |
817 | * @param bool $clearwrong whether the wrong parts should be reset. | |
d1b7e03d | 818 | */ |
7a719748 TH |
819 | public function __construct($id, $hint, $hintformat, $shownumcorrect, $clearwrong) { |
820 | parent::__construct($id, $hint, $hintformat); | |
d1b7e03d TH |
821 | $this->shownumcorrect = $shownumcorrect; |
822 | $this->clearwrong = $clearwrong; | |
823 | } | |
824 | ||
825 | /** | |
826 | * Create a basic hint from a row loaded from the question_hints table in the database. | |
827 | * @param object $row with $row->hint, ->shownumcorrect and ->clearwrong set. | |
828 | * @return question_hint_with_parts | |
829 | */ | |
830 | public static function load_from_record($row) { | |
7a719748 TH |
831 | return new question_hint_with_parts($row->id, $row->hint, $row->hintformat, |
832 | $row->shownumcorrect, $row->clearwrong); | |
d1b7e03d TH |
833 | } |
834 | ||
835 | public function adjust_display_options(question_display_options $options) { | |
836 | parent::adjust_display_options($options); | |
837 | if ($this->clearwrong) { | |
838 | $options->clearwrong = true; | |
839 | } | |
840 | $options->numpartscorrect = $this->shownumcorrect; | |
841 | } | |
842 | } | |
843 | ||
844 | ||
845 | /** | |
846 | * This question_grading_strategy interface. Used to share grading code between | |
847 | * questions that that subclass {@link question_graded_by_strategy}. | |
848 | * | |
7764183a TH |
849 | * @copyright 2009 The Open University |
850 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
851 | */ |
852 | interface question_grading_strategy { | |
853 | /** | |
854 | * Return a question answer that describes the outcome (fraction and feeback) | |
855 | * for a particular respons. | |
856 | * @param array $response the response. | |
857 | * @return question_answer the answer describing the outcome. | |
858 | */ | |
859 | public function grade(array $response); | |
860 | ||
861 | /** | |
862 | * @return question_answer an answer that contains the a response that would | |
863 | * get full marks. | |
864 | */ | |
865 | public function get_correct_answer(); | |
866 | } | |
867 | ||
868 | ||
869 | /** | |
870 | * This interface defines the methods that a {@link question_definition} must | |
871 | * implement if it is to be graded by the | |
872 | * {@link question_first_matching_answer_grading_strategy}. | |
873 | * | |
7764183a TH |
874 | * @copyright 2009 The Open University |
875 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
876 | */ |
877 | interface question_response_answer_comparer { | |
878 | /** @return array of {@link question_answers}. */ | |
879 | public function get_answers(); | |
880 | ||
881 | /** | |
882 | * @param array $response the response. | |
883 | * @param question_answer $answer an answer. | |
f7970e3c | 884 | * @return bool whether the response matches the answer. |
d1b7e03d TH |
885 | */ |
886 | public function compare_response_with_answer(array $response, question_answer $answer); | |
887 | } | |
888 | ||
889 | ||
890 | /** | |
891 | * This grading strategy is used by question types like shortanswer an numerical. | |
892 | * It gets a list of possible answers from the question, and returns the first one | |
893 | * that matches the given response. It returns the first answer with fraction 1.0 | |
894 | * when asked for the correct answer. | |
895 | * | |
7764183a TH |
896 | * @copyright 2009 The Open University |
897 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
d1b7e03d TH |
898 | */ |
899 | class question_first_matching_answer_grading_strategy implements question_grading_strategy { | |
900 | /** | |
901 | * @var question_response_answer_comparer (presumably also a | |
902 | * {@link question_definition}) the question we are doing the grading for. | |
903 | */ | |
904 | protected $question; | |
905 | ||
906 | /** | |
907 | * @param question_response_answer_comparer $question (presumably also a | |
908 | * {@link question_definition}) the question we are doing the grading for. | |
909 | */ | |
910 | public function __construct(question_response_answer_comparer $question) { | |
911 | $this->question = $question; | |
912 | } | |
913 | ||
914 | public function grade(array $response) { | |
915 | foreach ($this->question->get_answers() as $aid => $answer) { | |
916 | if ($this->question->compare_response_with_answer($response, $answer)) { | |
917 | $answer->id = $aid; | |
918 | return $answer; | |
919 | } | |
920 | } | |
921 | return null; | |
922 | } | |
923 | ||
924 | public function get_correct_answer() { | |
925 | foreach ($this->question->get_answers() as $answer) { | |
926 | $state = question_state::graded_state_for_fraction($answer->fraction); | |
927 | if ($state == question_state::$gradedright) { | |
928 | return $answer; | |
929 | } | |
930 | } | |
931 | return null; | |
932 | } | |
933 | } |