1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Question behaviour for the old adaptive mode. 19 * 20 * @package qbehaviour 21 * @subpackage adaptive 22 * @copyright 2009 The Open University 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 30 /** 31 * Question behaviour for adaptive mode. 32 * 33 * This is the old version of interactive mode. 34 * 35 * @copyright 2009 The Open University 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class qbehaviour_adaptive extends question_behaviour_with_multiple_tries { 39 const IS_ARCHETYPAL = true; 40 41 public function is_compatible_question(question_definition $question) { 42 return $question instanceof question_automatically_gradable; 43 } 44 45 public function get_expected_data() { 46 if ($this->qa->get_state()->is_active()) { 47 return array('submit' => PARAM_BOOL); 48 } 49 return parent::get_expected_data(); 50 } 51 52 public function get_state_string($showcorrectness) { 53 $laststep = $this->qa->get_last_step(); 54 if ($laststep->has_behaviour_var('_try')) { 55 $state = question_state::graded_state_for_fraction( 56 $laststep->get_behaviour_var('_rawfraction')); 57 return $state->default_string(true); 58 } 59 60 $state = $this->qa->get_state(); 61 if ($state == question_state::$todo) { 62 return get_string('notcomplete', 'qbehaviour_adaptive'); 63 } else { 64 return parent::get_state_string($showcorrectness); 65 } 66 } 67 68 public function get_right_answer_summary() { 69 return $this->question->get_right_answer_summary(); 70 } 71 72 public function adjust_display_options(question_display_options $options) { 73 // Save some bits so we can put them back later. 74 $save = clone($options); 75 76 // Do the default thing. 77 parent::adjust_display_options($options); 78 79 // Then, if they have just Checked an answer, show them the applicable bits of feedback. 80 if (!$this->qa->get_state()->is_finished() && 81 $this->qa->get_last_behaviour_var('_try')) { 82 $options->feedback = $save->feedback; 83 $options->correctness = $save->correctness; 84 $options->numpartscorrect = $save->numpartscorrect; 85 86 } 87 } 88 89 public function process_action(question_attempt_pending_step $pendingstep) { 90 if ($pendingstep->has_behaviour_var('comment')) { 91 return $this->process_comment($pendingstep); 92 } else if ($pendingstep->has_behaviour_var('finish')) { 93 return $this->process_finish($pendingstep); 94 } else if ($pendingstep->has_behaviour_var('submit')) { 95 return $this->process_submit($pendingstep); 96 } else { 97 return $this->process_save($pendingstep); 98 } 99 } 100 101 public function summarise_action(question_attempt_step $step) { 102 if ($step->has_behaviour_var('comment')) { 103 return $this->summarise_manual_comment($step); 104 } else if ($step->has_behaviour_var('finish')) { 105 return $this->summarise_finish($step); 106 } else if ($step->has_behaviour_var('submit')) { 107 return $this->summarise_submit($step); 108 } else { 109 return $this->summarise_save($step); 110 } 111 } 112 113 public function process_save(question_attempt_pending_step $pendingstep) { 114 $status = parent::process_save($pendingstep); 115 $prevgrade = $this->qa->get_fraction(); 116 if (!is_null($prevgrade)) { 117 $pendingstep->set_fraction($prevgrade); 118 } 119 $pendingstep->set_state(question_state::$todo); 120 return $status; 121 } 122 123 protected function adjusted_fraction($fraction, $prevtries) { 124 return $fraction - $this->question->penalty * $prevtries; 125 } 126 127 public function process_submit(question_attempt_pending_step $pendingstep) { 128 $status = $this->process_save($pendingstep); 129 130 $response = $pendingstep->get_qt_data(); 131 if (!$this->question->is_complete_response($response)) { 132 $pendingstep->set_state(question_state::$invalid); 133 if ($this->qa->get_state() != question_state::$invalid) { 134 $status = question_attempt::KEEP; 135 } 136 return $status; 137 } 138 139 $prevstep = $this->qa->get_last_step_with_behaviour_var('_try'); 140 $prevresponse = $prevstep->get_qt_data(); 141 $prevtries = $this->qa->get_last_behaviour_var('_try', 0); 142 $prevbest = $pendingstep->get_fraction(); 143 if (is_null($prevbest)) { 144 $prevbest = 0; 145 } 146 147 if ($this->question->is_same_response($response, $prevresponse)) { 148 return question_attempt::DISCARD; 149 } 150 151 list($fraction, $state) = $this->question->grade_response($response); 152 153 $pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries))); 154 if ($prevstep->get_state() == question_state::$complete) { 155 $pendingstep->set_state(question_state::$complete); 156 } else if ($state == question_state::$gradedright) { 157 $pendingstep->set_state(question_state::$complete); 158 } else { 159 $pendingstep->set_state(question_state::$todo); 160 } 161 $pendingstep->set_behaviour_var('_try', $prevtries + 1); 162 $pendingstep->set_behaviour_var('_rawfraction', $fraction); 163 $pendingstep->set_new_response_summary($this->question->summarise_response($response)); 164 165 return question_attempt::KEEP; 166 } 167 168 public function process_finish(question_attempt_pending_step $pendingstep) { 169 if ($this->qa->get_state()->is_finished()) { 170 return question_attempt::DISCARD; 171 } 172 173 $prevtries = $this->qa->get_last_behaviour_var('_try', 0); 174 $prevbest = $this->qa->get_fraction(); 175 if (is_null($prevbest)) { 176 $prevbest = 0; 177 } 178 179 $laststep = $this->qa->get_last_step(); 180 $response = $laststep->get_qt_data(); 181 if (!$this->question->is_gradable_response($response)) { 182 $state = question_state::$gaveup; 183 $fraction = 0; 184 } else { 185 186 if ($laststep->has_behaviour_var('_try')) { 187 // Last answer was graded, we want to regrade it. Otherwise the answer 188 // has changed, and we are grading a new try. 189 $prevtries -= 1; 190 } 191 192 list($fraction, $state) = $this->question->grade_response($response); 193 194 $pendingstep->set_behaviour_var('_try', $prevtries + 1); 195 $pendingstep->set_behaviour_var('_rawfraction', $fraction); 196 $pendingstep->set_new_response_summary($this->question->summarise_response($response)); 197 } 198 199 $pendingstep->set_state($state); 200 $pendingstep->set_fraction(max($prevbest, $this->adjusted_fraction($fraction, $prevtries))); 201 return question_attempt::KEEP; 202 } 203 204 /** 205 * Got the most recently graded step. This is mainly intended for use by the 206 * renderer. 207 * @return question_attempt_step the most recently graded step. 208 */ 209 public function get_graded_step() { 210 $step = $this->qa->get_last_step_with_behaviour_var('_try'); 211 if ($step->has_behaviour_var('_try')) { 212 return $step; 213 } else { 214 return null; 215 } 216 } 217 218 /** 219 * Determine whether a question state represents an "improvable" result, 220 * that is, whether the user can still improve their score. 221 * 222 * @param question_state $state the question state. 223 * @return bool whether the state is improvable 224 */ 225 public function is_state_improvable(question_state $state) { 226 return $state == question_state::$todo; 227 } 228 229 /** 230 * @return qbehaviour_adaptive_mark_details the information about the current state-of-play, scoring-wise, 231 * for this adaptive attempt. 232 */ 233 public function get_adaptive_marks() { 234 235 // Try to find the last graded step. 236 $gradedstep = $this->get_graded_step(); 237 if (is_null($gradedstep) || $this->qa->get_max_mark() == 0) { 238 // No score yet. 239 return new qbehaviour_adaptive_mark_details(question_state::$todo); 240 } 241 242 // Work out the applicable state. 243 if ($this->qa->get_state()->is_commented()) { 244 $state = $this->qa->get_state(); 245 } else { 246 $state = question_state::graded_state_for_fraction( 247 $gradedstep->get_behaviour_var('_rawfraction')); 248 } 249 250 // Prepare the grading details. 251 $details = $this->adaptive_mark_details_from_step($gradedstep, $state, $this->qa->get_max_mark(), $this->question->penalty); 252 $details->improvable = $this->is_state_improvable($this->qa->get_state()); 253 return $details; 254 } 255 256 /** 257 * Actually populate the qbehaviour_adaptive_mark_details object. 258 * @param question_attempt_step $gradedstep the step that holds the relevant mark details. 259 * @param question_state $state the state corresponding to $gradedstep. 260 * @param unknown_type $maxmark the maximum mark for this question_attempt. 261 * @param unknown_type $penalty the penalty for this question, as a fraction. 262 */ 263 protected function adaptive_mark_details_from_step(question_attempt_step $gradedstep, 264 question_state $state, $maxmark, $penalty) { 265 266 $details = new qbehaviour_adaptive_mark_details($state); 267 $details->maxmark = $maxmark; 268 $details->actualmark = $gradedstep->get_fraction() * $details->maxmark; 269 $details->rawmark = $gradedstep->get_behaviour_var('_rawfraction') * $details->maxmark; 270 271 $details->currentpenalty = $penalty * $details->maxmark; 272 $details->totalpenalty = $details->currentpenalty * $this->qa->get_last_behaviour_var('_try', 0); 273 274 $details->improvable = $this->is_state_improvable($gradedstep->get_state()); 275 276 return $details; 277 } 278 } 279 280 281 /** 282 * This class encapsulates all the information about the current state-of-play 283 * scoring-wise. It is used to communicate between the beahviour and the renderer. 284 * 285 * @copyright 2012 The Open University 286 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 287 */ 288 class qbehaviour_adaptive_mark_details { 289 /** @var question_state the current state of the question. */ 290 public $state; 291 292 /** @var float the maximum mark for this question. */ 293 public $maxmark; 294 295 /** @var float the current mark for this question. */ 296 public $actualmark; 297 298 /** @var float the raw mark for this question before penalties were applied. */ 299 public $rawmark; 300 301 /** @var float the the amount of additional penalty this attempt attracted. */ 302 public $currentpenalty; 303 304 /** @var float the total that will apply to future attempts. */ 305 public $totalpenalty; 306 307 /** @var bool whether it is possible for this mark to be improved in future. */ 308 public $improvable; 309 310 /** 311 * Constructor. 312 * @param question_state $state 313 */ 314 public function __construct($state, $maxmark = null, $actualmark = null, $rawmark = null, 315 $currentpenalty = null, $totalpenalty = null, $improvable = null) { 316 $this->state = $state; 317 $this->maxmark = $maxmark; 318 $this->actualmark = $actualmark; 319 $this->rawmark = $rawmark; 320 $this->currentpenalty = $currentpenalty; 321 $this->totalpenalty = $totalpenalty; 322 $this->improvable = $improvable; 323 } 324 325 /** 326 * Get the marks, formatted to a certain number of decimal places, in the 327 * form required by calls like get_string('gradingdetails', 'qbehaviour_adaptive', $a). 328 * @param int $markdp the number of decimal places required. 329 * @return array ready to substitute into language strings. 330 */ 331 public function get_formatted_marks($markdp) { 332 return array( 333 'max' => format_float($this->maxmark, $markdp), 334 'cur' => format_float($this->actualmark, $markdp), 335 'raw' => format_float($this->rawmark, $markdp), 336 'penalty' => format_float($this->currentpenalty, $markdp), 337 'totalpenalty' => format_float($this->totalpenalty, $markdp), 338 ); 339 } 340 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body