MDL-20636 improve the fomatting of the generated code.
[moodle.git] / question / type / questionbase.php
CommitLineData
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
27defined('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 */
41abstract 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 */
334class 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 */
368interface 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 */
418class 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 */
454interface 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 */
511interface 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 */
533abstract 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 */
547abstract 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 */
622abstract 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 */
643abstract 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 */
712class 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 */
757class 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 */
804class 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 */
852interface 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 */
877interface 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 */
899class 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}