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