|
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 00042 abstract class question_behaviour { 00054 const IS_ARCHETYPAL = false; 00055 00057 protected $qa; 00059 protected $question; 00060 00070 public function __construct(question_attempt $qa, $preferredbehaviour) { 00071 $this->qa = $qa; 00072 $this->question = $qa->get_question(); 00073 if (!$this->is_compatible_question($this->question)) { 00074 throw new coding_exception('This behaviour (' . $this->get_name() . 00075 ') cannot work with this question (' . get_class($this->question) . ')'); 00076 } 00077 } 00078 00088 public function is_compatible_question(question_definition $question) { 00089 $requiredclass = $this->required_question_definition_type(); 00090 return $this->question instanceof $requiredclass; 00091 } 00092 00103 protected function required_question_definition_type() { 00104 return 'question_definition'; 00105 } 00106 00111 public function get_name() { 00112 return substr(get_class($this), 11); 00113 } 00114 00121 public static function get_unused_display_options() { 00122 return array(); 00123 } 00124 00136 public function render(question_display_options $options, $number, 00137 core_question_renderer $qoutput, qtype_renderer $qtoutput) { 00138 $behaviouroutput = $this->get_renderer($qoutput->get_page()); 00139 $options = clone($options); 00140 $this->adjust_display_options($options); 00141 return $qoutput->question($this->qa, $behaviouroutput, $qtoutput, $options, $number); 00142 } 00143 00153 public function check_file_access($options, $component, $filearea, $args, $forcedownload) { 00154 $this->adjust_display_options($options); 00155 return $this->question->check_file_access($this->qa, $options, $component, 00156 $filearea, $args, $forcedownload); 00157 } 00158 00163 public function get_renderer(moodle_page $page) { 00164 return $page->get_renderer(get_class($this)); 00165 } 00166 00175 public function adjust_display_options(question_display_options $options) { 00176 if (!$this->qa->has_marks()) { 00177 $options->correctness = false; 00178 $options->numpartscorrect = false; 00179 } 00180 if ($this->qa->get_state()->is_finished()) { 00181 $options->readonly = true; 00182 $options->numpartscorrect = $options->numpartscorrect && 00183 $this->qa->get_state()->is_partially_correct() && 00184 !empty($this->question->shownumcorrect); 00185 } else { 00186 $options->hide_all_feedback(); 00187 } 00188 } 00189 00194 public function get_applicable_hint() { 00195 return null; 00196 } 00197 00206 public function get_min_fraction() { 00207 return 0; 00208 } 00209 00217 public static function adjust_random_guess_score($fraction) { 00218 return $fraction; 00219 } 00220 00227 public function get_expected_data() { 00228 if (!$this->qa->get_state()->is_finished()) { 00229 return array(); 00230 } 00231 00232 $vars = array('comment' => PARAM_RAW); 00233 if ($this->qa->get_max_mark()) { 00234 $vars['mark'] = question_attempt::PARAM_MARK; 00235 $vars['maxmark'] = PARAM_NUMBER; 00236 } 00237 return $vars; 00238 } 00239 00251 public function get_expected_qt_data() { 00252 $fakeoptions = new question_display_options(); 00253 $fakeoptions->readonly = false; 00254 $this->adjust_display_options($fakeoptions); 00255 if ($fakeoptions->readonly) { 00256 return array(); 00257 } else { 00258 return $this->question->get_expected_data(); 00259 } 00260 } 00261 00267 public function get_correct_response() { 00268 return array(); 00269 } 00270 00283 public function get_question_summary() { 00284 return $this->question->get_question_summary(); 00285 } 00286 00295 public function get_right_answer_summary() { 00296 return null; 00297 } 00298 00304 public function get_resume_data() { 00305 $olddata = $this->qa->get_step(0)->get_all_data(); 00306 $olddata = $this->qa->get_last_qt_data() + $olddata; 00307 $olddata = $this->get_our_resume_data() + $olddata; 00308 return $olddata; 00309 } 00310 00316 protected function get_our_resume_data() { 00317 return array(); 00318 } 00319 00328 public function classify_response() { 00329 return $this->question->classify_response($this->qa->get_last_qt_data()); 00330 } 00331 00340 public function get_state_string($showcorrectness) { 00341 return $this->qa->get_state()->default_string($showcorrectness); 00342 } 00343 00344 public abstract function summarise_action(question_attempt_step $step); 00345 00357 public function init_first_step(question_attempt_step $step, $variant) { 00358 $this->question->start_attempt($step, $variant); 00359 } 00360 00368 protected function is_same_comment($pendingstep) { 00369 $previouscomment = $this->qa->get_last_behaviour_var('comment'); 00370 $newcomment = $pendingstep->get_behaviour_var('comment'); 00371 00372 if (is_null($previouscomment) && !html_is_blank($newcomment) || 00373 $previouscomment != $newcomment) { 00374 return false; 00375 } 00376 00377 // So, now we know the comment is the same, so check the mark, if present. 00378 $previousfraction = $this->qa->get_fraction(); 00379 $newmark = $pendingstep->get_behaviour_var('mark'); 00380 00381 if (is_null($previousfraction)) { 00382 return is_null($newmark) || $newmark === ''; 00383 } else if (is_null($newmark) || $newmark === '') { 00384 return false; 00385 } 00386 00387 $newfraction = $newmark / $pendingstep->get_behaviour_var('maxmark'); 00388 00389 return abs($newfraction - $previousfraction) < 0.0000001; 00390 } 00391 00436 public abstract function process_action(question_attempt_pending_step $pendingstep); 00437 00445 public function process_comment(question_attempt_pending_step $pendingstep) { 00446 if (!$this->qa->get_state()->is_finished()) { 00447 throw new coding_exception('Cannot manually grade a question before it is finshed.'); 00448 } 00449 00450 if ($this->is_same_comment($pendingstep)) { 00451 return question_attempt::DISCARD; 00452 } 00453 00454 if ($pendingstep->has_behaviour_var('mark')) { 00455 $fraction = $pendingstep->get_behaviour_var('mark') / 00456 $pendingstep->get_behaviour_var('maxmark'); 00457 if ($pendingstep->get_behaviour_var('mark') === '') { 00458 $fraction = null; 00459 } else if ($fraction > 1 || $fraction < $this->qa->get_min_fraction()) { 00460 throw new coding_exception('Score out of range when processing ' . 00461 'a manual grading action.', $pendingstep); 00462 } 00463 $pendingstep->set_fraction($fraction); 00464 } 00465 00466 $pendingstep->set_state($this->qa->get_state()->corresponding_commented_state( 00467 $pendingstep->get_fraction())); 00468 return question_attempt::KEEP; 00469 } 00470 00477 public function format_comment($comment = null, $commentformat = null) { 00478 $formatoptions = new stdClass(); 00479 $formatoptions->noclean = true; 00480 $formatoptions->para = false; 00481 00482 if (is_null($comment)) { 00483 list($comment, $commentformat) = $this->qa->get_manual_comment(); 00484 } 00485 00486 return format_text($comment, $commentformat, $formatoptions); 00487 } 00488 00493 protected function summarise_manual_comment($step) { 00494 $a = new stdClass(); 00495 if ($step->has_behaviour_var('comment')) { 00496 $a->comment = shorten_text(html_to_text($this->format_comment( 00497 $step->get_behaviour_var('comment')), 0, false), 200); 00498 } else { 00499 $a->comment = ''; 00500 } 00501 00502 $mark = $step->get_behaviour_var('mark'); 00503 if (is_null($mark) || $mark === '') { 00504 return get_string('commented', 'question', $a->comment); 00505 } else { 00506 $a->mark = $mark / $step->get_behaviour_var('maxmark') * $this->qa->get_max_mark(); 00507 return get_string('manuallygraded', 'question', $a); 00508 } 00509 } 00510 00511 public function summarise_start($step) { 00512 return get_string('started', 'question'); 00513 } 00514 00515 public function summarise_finish($step) { 00516 return get_string('attemptfinished', 'question'); 00517 } 00518 } 00519 00520 00529 abstract class question_behaviour_with_save extends question_behaviour { 00530 public function required_question_definition_type() { 00531 return 'question_manually_gradable'; 00532 } 00533 00540 protected function is_same_response(question_attempt_step $pendingstep) { 00541 return $this->question->is_same_response( 00542 $this->qa->get_last_step()->get_qt_data(), $pendingstep->get_qt_data()); 00543 } 00544 00554 protected function is_complete_response(question_attempt_step $pendingstep) { 00555 return $this->question->is_complete_response($pendingstep->get_qt_data()); 00556 } 00557 00565 public function process_save(question_attempt_pending_step $pendingstep) { 00566 if ($this->qa->get_state()->is_finished()) { 00567 return question_attempt::DISCARD; 00568 } else if (!$this->qa->get_state()->is_active()) { 00569 throw new coding_exception('Question is not active, cannot process_actions.'); 00570 } 00571 00572 if ($this->is_same_response($pendingstep)) { 00573 return question_attempt::DISCARD; 00574 } 00575 00576 if ($this->is_complete_response($pendingstep)) { 00577 $pendingstep->set_state(question_state::$complete); 00578 } else { 00579 $pendingstep->set_state(question_state::$todo); 00580 } 00581 return question_attempt::KEEP; 00582 } 00583 00584 public function summarise_submit(question_attempt_step $step) { 00585 return get_string('submitted', 'question', 00586 $this->question->summarise_response($step->get_qt_data())); 00587 } 00588 00589 public function summarise_save(question_attempt_step $step) { 00590 $data = $step->get_submitted_data(); 00591 if (empty($data)) { 00592 return $this->summarise_start($step); 00593 } 00594 return get_string('saved', 'question', 00595 $this->question->summarise_response($step->get_qt_data())); 00596 } 00597 00598 00599 public function summarise_finish($step) { 00600 $data = $step->get_qt_data(); 00601 if ($data) { 00602 return get_string('attemptfinishedsubmitting', 'question', 00603 $this->question->summarise_response($data)); 00604 } 00605 return get_string('attemptfinished', 'question'); 00606 } 00607 } 00608 00609 00617 abstract class question_cbm { 00619 const LOW = 1; 00620 const MED = 2; 00621 const HIGH = 3; 00625 public static $certainties = array(self::LOW, self::MED, self::HIGH); 00626 00628 protected static $factor = array( 00629 self::LOW => 0.333333333333333, 00630 self::MED => 1.333333333333333, 00631 self::HIGH => 3, 00632 ); 00633 protected static $offset = array( 00634 self::LOW => 0, 00635 self::MED => -0.666666666666667, 00636 self::HIGH => -2, 00637 ); 00644 public static function default_certainty() { 00645 return self::LOW; 00646 } 00647 00654 public static function adjust_fraction($fraction, $certainty) { 00655 return self::$offset[$certainty] + self::$factor[$certainty] * $fraction; 00656 } 00657 00662 public static function get_string($certainty) { 00663 return get_string('certainty' . $certainty, 'qbehaviour_deferredcbm'); 00664 } 00665 00666 public static function summary_with_certainty($summary, $certainty) { 00667 if (is_null($certainty)) { 00668 return $summary; 00669 } 00670 return $summary . ' [' . self::get_string($certainty) . ']'; 00671 } 00672 }