|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 00041 abstract class question_definition { 00044 public $id; 00045 00047 public $category; 00048 00050 public $contextid; 00051 00053 public $parent = 0; 00054 00056 public $qtype; 00057 00059 public $name; 00060 00062 public $questiontext; 00063 00065 public $questiontextformat; 00066 00068 public $generalfeedback; 00069 00071 public $generalfeedbackformat; 00072 00074 public $defaultmark = 1; 00075 00077 public $length = 1; 00078 00080 public $penalty = 0; 00081 00083 public $stamp; 00084 00086 public $version; 00087 00089 public $hidden = 0; 00090 00092 public $timecreated; 00093 00095 public $timemodified; 00096 00098 public $createdby; 00099 00101 public $modifiedby; 00102 00104 public $hints = array(); 00105 00112 public function __construct() { 00113 } 00114 00119 public function get_type_name() { 00120 return $this->qtype->name(); 00121 } 00122 00143 public function make_behaviour(question_attempt $qa, $preferredbehaviour) { 00144 return question_engine::make_archetypal_behaviour($preferredbehaviour, $qa); 00145 } 00146 00163 public function start_attempt(question_attempt_step $step, $variant) { 00164 } 00165 00179 public function apply_attempt_state(question_attempt_step $step) { 00180 } 00181 00191 public function get_question_summary() { 00192 return $this->html_to_text($this->questiontext, $this->questiontextformat); 00193 } 00194 00198 public function get_num_variants() { 00199 return 1; 00200 } 00201 00206 public function get_variants_selection_seed() { 00207 return $this->stamp; 00208 } 00209 00218 public function get_min_fraction() { 00219 return 0; 00220 } 00221 00227 public function clear_wrong_from_response(array $response) { 00228 return array(); 00229 } 00230 00237 public function get_num_parts_right(array $response) { 00238 return array(null, null); 00239 } 00240 00245 public function get_renderer(moodle_page $page) { 00246 return $page->get_renderer($this->qtype->plugin_name()); 00247 } 00248 00260 public abstract function get_expected_data(); 00261 00270 public abstract function get_correct_response(); 00271 00285 public function format_text($text, $format, $qa, $component, $filearea, $itemid, 00286 $clean = false) { 00287 $formatoptions = new stdClass(); 00288 $formatoptions->noclean = !$clean; 00289 $formatoptions->para = false; 00290 $text = $qa->rewrite_pluginfile_urls($text, $component, $filearea, $itemid); 00291 return format_text($text, $format, $formatoptions); 00292 } 00293 00301 public function html_to_text($text, $format) { 00302 return html_to_text(format_text($text, $format, array('noclean' => true)), 0, false); 00303 } 00304 00306 public function format_questiontext($qa) { 00307 return $this->format_text($this->questiontext, $this->questiontextformat, 00308 $qa, 'question', 'questiontext', $this->id); 00309 } 00310 00312 public function format_generalfeedback($qa) { 00313 return $this->format_text($this->generalfeedback, $this->generalfeedbackformat, 00314 $qa, 'question', 'generalfeedback', $this->id); 00315 } 00316 00327 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { 00328 if ($component == 'question' && $filearea == 'questiontext') { 00329 // Question text always visible. 00330 return true; 00331 00332 } else if ($component == 'question' && $filearea == 'generalfeedback') { 00333 return $options->generalfeedback; 00334 00335 } else { 00336 // Unrecognised component or filearea. 00337 return false; 00338 } 00339 } 00340 } 00341 00342 00350 class question_information_item extends question_definition { 00351 public function __construct() { 00352 parent::__construct(); 00353 $this->defaultmark = 0; 00354 $this->penalty = 0; 00355 $this->length = 0; 00356 } 00357 00358 public function make_behaviour(question_attempt $qa, $preferredbehaviour) { 00359 question_engine::load_behaviour_class('informationitem'); 00360 return new qbehaviour_informationitem($qa, $preferredbehaviour); 00361 } 00362 00363 public function get_expected_data() { 00364 return array(); 00365 } 00366 00367 public function get_correct_response() { 00368 return array(); 00369 } 00370 00371 public function get_question_summary() { 00372 return null; 00373 } 00374 } 00375 00376 00384 interface question_manually_gradable { 00394 public function is_complete_response(array $response); 00395 00407 public function is_same_response(array $prevresponse, array $newresponse); 00408 00414 public function summarise_response(array $response); 00415 00423 public function classify_response(array $response); 00424 } 00425 00426 00434 class question_classified_response { 00440 public $responseclassid; 00442 public $response; 00444 public $fraction; 00451 public function __construct($responseclassid, $response, $fraction) { 00452 $this->responseclassid = $responseclassid; 00453 $this->response = $response; 00454 $this->fraction = $fraction; 00455 } 00456 00457 public static function no_response() { 00458 return new question_classified_response(null, get_string('noresponse', 'question'), null); 00459 } 00460 } 00461 00462 00470 interface question_automatically_gradable extends question_manually_gradable { 00480 public function is_gradable_response(array $response); 00481 00487 public function get_validation_error(array $response); 00488 00497 public function grade_response(array $response); 00498 00507 public function get_hint($hintnumber, question_attempt $qa); 00508 00516 public function get_right_answer_summary(); 00517 } 00518 00519 00527 interface question_automatically_gradable_with_countback extends question_automatically_gradable { 00538 public function compute_final_grade($responses, $totaltries); 00539 } 00540 00541 00549 abstract class question_with_responses extends question_definition 00550 implements question_manually_gradable { 00551 public function classify_response(array $response) { 00552 return array(); 00553 } 00554 } 00555 00556 00563 abstract class question_graded_automatically extends question_with_responses 00564 implements question_automatically_gradable { 00566 public $shownumcorrect = false; 00567 00568 public function is_gradable_response(array $response) { 00569 return $this->is_complete_response($response); 00570 } 00571 00572 public function get_right_answer_summary() { 00573 $correctresponse = $this->get_correct_response(); 00574 if (empty($correctresponse)) { 00575 return null; 00576 } 00577 return $this->summarise_response($correctresponse); 00578 } 00579 00587 protected function check_combined_feedback_file_access($qa, $options, $filearea) { 00588 $state = $qa->get_state(); 00589 00590 if (!$state->is_finished()) { 00591 $response = $qa->get_last_qt_data(); 00592 if (!$this->is_gradable_response($response)) { 00593 return false; 00594 } 00595 list($notused, $state) = $this->grade_response($response); 00596 } 00597 00598 return $options->feedback && $state->get_feedback_class() . 'feedback' == $filearea; 00599 } 00600 00608 protected function check_hint_file_access($qa, $options, $args) { 00609 if (!$options->feedback) { 00610 return false; 00611 } 00612 $hint = $qa->get_applicable_hint(); 00613 $hintid = reset($args); // itemid is hint id. 00614 return $hintid == $hint->id; 00615 } 00616 00617 public function get_hint($hintnumber, question_attempt $qa) { 00618 if (!isset($this->hints[$hintnumber])) { 00619 return null; 00620 } 00621 return $this->hints[$hintnumber]; 00622 } 00623 00624 public function format_hint(question_hint $hint, question_attempt $qa) { 00625 return $this->format_text($hint->hint, $hint->hintformat, $qa, 00626 'question', 'hint', $hint->id); 00627 } 00628 } 00629 00630 00638 abstract class question_graded_automatically_with_countback 00639 extends question_graded_automatically 00640 implements question_automatically_gradable_with_countback { 00641 00642 public function make_behaviour(question_attempt $qa, $preferredbehaviour) { 00643 if ($preferredbehaviour == 'interactive') { 00644 return question_engine::make_behaviour('interactivecountback', 00645 $qa, $preferredbehaviour); 00646 } 00647 return question_engine::make_archetypal_behaviour($preferredbehaviour, $qa); 00648 } 00649 } 00650 00651 00659 abstract class question_graded_by_strategy extends question_graded_automatically { 00661 protected $gradingstrategy; 00662 00664 public function __construct(question_grading_strategy $strategy) { 00665 parent::__construct(); 00666 $this->gradingstrategy = $strategy; 00667 } 00668 00669 public function get_correct_response() { 00670 $answer = $this->get_correct_answer(); 00671 if (!$answer) { 00672 return array(); 00673 } 00674 00675 return array('answer' => $answer->answer); 00676 } 00677 00684 public function get_matching_answer(array $response) { 00685 return $this->gradingstrategy->grade($response); 00686 } 00687 00692 public function get_correct_answer() { 00693 return $this->gradingstrategy->get_correct_answer(); 00694 } 00695 00696 public function grade_response(array $response) { 00697 $answer = $this->get_matching_answer($response); 00698 if ($answer) { 00699 return array($answer->fraction, 00700 question_state::graded_state_for_fraction($answer->fraction)); 00701 } else { 00702 return array(0, question_state::$gradedwrong); 00703 } 00704 } 00705 00706 public function classify_response(array $response) { 00707 if (empty($response['answer'])) { 00708 return array($this->id => question_classified_response::no_response()); 00709 } 00710 00711 $ans = $this->get_matching_answer($response); 00712 if (!$ans) { 00713 return array($this->id => new question_classified_response( 00714 0, $response['answer'], 0)); 00715 } 00716 00717 return array($this->id => new question_classified_response( 00718 $ans->id, $response['answer'], $ans->fraction)); 00719 } 00720 } 00721 00722 00730 class question_answer { 00732 public $id; 00733 00735 public $answer; 00736 00738 public $answerformat = FORMAT_PLAIN; 00739 00741 public $fraction; 00742 00744 public $feedback; 00745 00747 public $feedbackformat; 00748 00758 public function __construct($id, $answer, $fraction, $feedback, $feedbackformat) { 00759 $this->id = $id; 00760 $this->answer = $answer; 00761 $this->fraction = $fraction; 00762 $this->feedback = $feedback; 00763 $this->feedbackformat = $feedbackformat; 00764 } 00765 } 00766 00767 00775 class question_hint { 00777 public $id; 00779 public $hint; 00781 public $hintformat; 00782 00789 public function __construct($id, $hint, $hintformat) { 00790 $this->id = $id; 00791 $this->hint = $hint; 00792 $this->hintformat = $hintformat; 00793 } 00794 00800 public static function load_from_record($row) { 00801 return new question_hint($row->id, $row->hint, $row->hintformat); 00802 } 00803 00808 public function adjust_display_options(question_display_options $options) { 00809 // Do nothing. 00810 } 00811 } 00812 00813 00822 class question_hint_with_parts extends question_hint { 00824 public $shownumcorrect; 00825 00827 public $clearwrong; 00828 00837 public function __construct($id, $hint, $hintformat, $shownumcorrect, $clearwrong) { 00838 parent::__construct($id, $hint, $hintformat); 00839 $this->shownumcorrect = $shownumcorrect; 00840 $this->clearwrong = $clearwrong; 00841 } 00842 00848 public static function load_from_record($row) { 00849 return new question_hint_with_parts($row->id, $row->hint, $row->hintformat, 00850 $row->shownumcorrect, $row->clearwrong); 00851 } 00852 00853 public function adjust_display_options(question_display_options $options) { 00854 parent::adjust_display_options($options); 00855 if ($this->clearwrong) { 00856 $options->clearwrong = true; 00857 } 00858 $options->numpartscorrect = $this->shownumcorrect; 00859 } 00860 } 00861 00862 00870 interface question_grading_strategy { 00877 public function grade(array $response); 00878 00883 public function get_correct_answer(); 00884 } 00885 00886 00895 interface question_response_answer_comparer { 00897 public function get_answers(); 00898 00904 public function compare_response_with_answer(array $response, question_answer $answer); 00905 } 00906 00907 00917 class question_first_matching_answer_grading_strategy implements question_grading_strategy { 00922 protected $question; 00923 00928 public function __construct(question_response_answer_comparer $question) { 00929 $this->question = $question; 00930 } 00931 00932 public function grade(array $response) { 00933 foreach ($this->question->get_answers() as $aid => $answer) { 00934 if ($this->question->compare_response_with_answer($response, $answer)) { 00935 $answer->id = $aid; 00936 return $answer; 00937 } 00938 } 00939 return null; 00940 } 00941 00942 public function get_correct_answer() { 00943 foreach ($this->question->get_answers() as $answer) { 00944 $state = question_state::graded_state_for_fraction($answer->fraction); 00945 if ($state == question_state::$gradedright) { 00946 return $answer; 00947 } 00948 } 00949 return null; 00950 } 00951 }