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