[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 /** 19 * Multianswer question definition class. 20 * 21 * @package qtype 22 * @subpackage multianswer 23 * @copyright 2010 Pierre Pichet 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 require_once($CFG->dirroot . '/question/type/shortanswer/question.php'); 28 require_once($CFG->dirroot . '/question/type/numerical/question.php'); 29 require_once($CFG->dirroot . '/question/type/multichoice/question.php'); 30 31 32 /** 33 * Represents a multianswer question. 34 * 35 * A multi-answer question is made of of several subquestions of various types. 36 * You can think of it as an application of the composite pattern to qusetion 37 * types. 38 * 39 * @copyright 2010 Pierre Pichet 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class qtype_multianswer_question extends question_graded_automatically_with_countback { 43 /** @var array of question_graded_automatically. */ 44 public $subquestions = array(); 45 46 /** 47 * @var array place number => insex in the $subquestions array. Places are 48 * numbered from 1. 49 */ 50 public $places; 51 52 /** 53 * @var array of strings, one longer than $places, which is achieved by 54 * indexing from 0. The bits of question text that go between the subquestions. 55 */ 56 public $textfragments; 57 58 /** 59 * Get a question_attempt_step_subquestion_adapter 60 * @param question_attempt_step $step the step to adapt. 61 * @param int $i the subquestion index. 62 * @return question_attempt_step_subquestion_adapter. 63 */ 64 protected function get_substep($step, $i) { 65 return new question_attempt_step_subquestion_adapter($step, 'sub' . $i . '_'); 66 } 67 68 public function start_attempt(question_attempt_step $step, $variant) { 69 foreach ($this->subquestions as $i => $subq) { 70 $subq->start_attempt($this->get_substep($step, $i), $variant); 71 } 72 } 73 74 public function apply_attempt_state(question_attempt_step $step) { 75 foreach ($this->subquestions as $i => $subq) { 76 $subq->apply_attempt_state($this->get_substep($step, $i)); 77 } 78 } 79 80 public function get_question_summary() { 81 $summary = $this->html_to_text($this->questiontext, $this->questiontextformat); 82 foreach ($this->subquestions as $i => $subq) { 83 switch ($subq->qtype->name()) { 84 case 'multichoice': 85 $choices = array(); 86 $dummyqa = new question_attempt($subq, $this->contextid); 87 foreach ($subq->get_order($dummyqa) as $ansid) { 88 $choices[] = $this->html_to_text($subq->answers[$ansid]->answer, 89 $subq->answers[$ansid]->answerformat); 90 } 91 $answerbit = '{' . implode('; ', $choices) . '}'; 92 break; 93 case 'numerical': 94 case 'shortanswer': 95 $answerbit = '_____'; 96 break; 97 default: 98 $answerbit = '{ERR unknown sub-question type}'; 99 } 100 $summary = str_replace('{#' . $i . '}', $answerbit, $summary); 101 } 102 return $summary; 103 } 104 105 public function get_min_fraction() { 106 $fractionsum = 0; 107 $fractionmax = 0; 108 foreach ($this->subquestions as $i => $subq) { 109 $fractionmax += $subq->defaultmark; 110 $fractionsum += $subq->defaultmark * $subq->get_min_fraction(); 111 } 112 return $fractionsum / $fractionmax; 113 } 114 115 public function get_max_fraction() { 116 $fractionsum = 0; 117 $fractionmax = 0; 118 foreach ($this->subquestions as $i => $subq) { 119 $fractionmax += $subq->defaultmark; 120 $fractionsum += $subq->defaultmark * $subq->get_max_fraction(); 121 } 122 return $fractionsum / $fractionmax; 123 } 124 125 public function get_expected_data() { 126 $expected = array(); 127 foreach ($this->subquestions as $i => $subq) { 128 $substep = $this->get_substep(null, $i); 129 foreach ($subq->get_expected_data() as $name => $type) { 130 if ($subq->qtype->name() == 'multichoice' && 131 $subq->layout == qtype_multichoice_base::LAYOUT_DROPDOWN) { 132 // Hack or MC inline does not work. 133 $expected[$substep->add_prefix($name)] = PARAM_RAW; 134 } else { 135 $expected[$substep->add_prefix($name)] = $type; 136 } 137 } 138 } 139 return $expected; 140 } 141 142 public function get_correct_response() { 143 $right = array(); 144 foreach ($this->subquestions as $i => $subq) { 145 $substep = $this->get_substep(null, $i); 146 foreach ($subq->get_correct_response() as $name => $type) { 147 $right[$substep->add_prefix($name)] = $type; 148 } 149 } 150 return $right; 151 } 152 153 public function prepare_simulated_post_data($simulatedresponse) { 154 $postdata = array(); 155 foreach ($this->subquestions as $i => $subq) { 156 $substep = $this->get_substep(null, $i); 157 foreach ($subq->prepare_simulated_post_data($simulatedresponse[$i]) as $name => $value) { 158 $postdata[$substep->add_prefix($name)] = $value; 159 } 160 } 161 return $postdata; 162 } 163 164 public function get_student_response_values_for_simulation($postdata) { 165 $simulatedresponse = array(); 166 foreach ($this->subquestions as $i => $subq) { 167 $substep = $this->get_substep(null, $i); 168 $subqpostdata = $substep->filter_array($postdata); 169 $subqsimulatedresponse = $subq->get_student_response_values_for_simulation($subqpostdata); 170 foreach ($subqsimulatedresponse as $subresponsekey => $responsevalue) { 171 $simulatedresponse[$i.'.'.$subresponsekey] = $responsevalue; 172 } 173 } 174 ksort($simulatedresponse); 175 return $simulatedresponse; 176 } 177 178 public function is_complete_response(array $response) { 179 foreach ($this->subquestions as $i => $subq) { 180 $substep = $this->get_substep(null, $i); 181 if (!$subq->is_complete_response($substep->filter_array($response))) { 182 return false; 183 } 184 } 185 return true; 186 } 187 188 public function is_gradable_response(array $response) { 189 foreach ($this->subquestions as $i => $subq) { 190 $substep = $this->get_substep(null, $i); 191 if ($subq->is_gradable_response($substep->filter_array($response))) { 192 return true; 193 } 194 } 195 return false; 196 } 197 198 public function is_same_response(array $prevresponse, array $newresponse) { 199 foreach ($this->subquestions as $i => $subq) { 200 $substep = $this->get_substep(null, $i); 201 if (!$subq->is_same_response($substep->filter_array($prevresponse), 202 $substep->filter_array($newresponse))) { 203 return false; 204 } 205 } 206 return true; 207 } 208 209 public function get_validation_error(array $response) { 210 $errors = array(); 211 foreach ($this->subquestions as $i => $subq) { 212 $substep = $this->get_substep(null, $i); 213 $errors[] = $subq->get_validation_error($substep->filter_array($response)); 214 } 215 return implode('<br />', $errors); 216 } 217 218 /** 219 * Used by grade_response to combine the states of the subquestions. 220 * The combined state is accumulates in $overallstate. That will be right 221 * if all the separate states are right; and wrong if all the separate states 222 * are wrong, otherwise, it will be partially right. 223 * @param question_state $overallstate the result so far. 224 * @param question_state $newstate the new state to add to the combination. 225 * @return question_state the new combined state. 226 */ 227 protected function combine_states($overallstate, $newstate) { 228 if (is_null($overallstate)) { 229 return $newstate; 230 } else if ($overallstate == question_state::$gaveup && 231 $newstate == question_state::$gaveup) { 232 return question_state::$gaveup; 233 } else if ($overallstate == question_state::$gaveup && 234 $newstate == question_state::$gradedwrong) { 235 return question_state::$gradedwrong; 236 } else if ($overallstate == question_state::$gradedwrong && 237 $newstate == question_state::$gaveup) { 238 return question_state::$gradedwrong; 239 } else if ($overallstate == question_state::$gradedwrong && 240 $newstate == question_state::$gradedwrong) { 241 return question_state::$gradedwrong; 242 } else if ($overallstate == question_state::$gradedright && 243 $newstate == question_state::$gradedright) { 244 return question_state::$gradedright; 245 } else { 246 return question_state::$gradedpartial; 247 } 248 } 249 250 public function grade_response(array $response) { 251 $overallstate = null; 252 $fractionsum = 0; 253 $fractionmax = 0; 254 foreach ($this->subquestions as $i => $subq) { 255 $fractionmax += $subq->defaultmark; 256 $substep = $this->get_substep(null, $i); 257 $subresp = $substep->filter_array($response); 258 if (!$subq->is_gradable_response($subresp)) { 259 $overallstate = $this->combine_states($overallstate, question_state::$gaveup); 260 } else { 261 list($subfraction, $newstate) = $subq->grade_response($subresp); 262 $fractionsum += $subfraction * $subq->defaultmark; 263 $overallstate = $this->combine_states($overallstate, $newstate); 264 } 265 } 266 return array($fractionsum / $fractionmax, $overallstate); 267 } 268 269 public function clear_wrong_from_response(array $response) { 270 foreach ($this->subquestions as $i => $subq) { 271 $substep = $this->get_substep(null, $i); 272 $subresp = $substep->filter_array($response); 273 list($subfraction, $newstate) = $subq->grade_response($subresp); 274 if ($newstate != question_state::$gradedright) { 275 foreach ($subresp as $ind => $resp) { 276 if ($subq->qtype == 'multichoice' && ($subq->layout == qtype_multichoice_base::LAYOUT_VERTICAL 277 || $subq->layout == qtype_multichoice_base::LAYOUT_HORIZONTAL)) { 278 $response[$substep->add_prefix($ind)] = '-1'; 279 } else { 280 $response[$substep->add_prefix($ind)] = ''; 281 } 282 } 283 } 284 } 285 return $response; 286 } 287 288 public function get_num_parts_right(array $response) { 289 $numright = 0; 290 foreach ($this->subquestions as $i => $subq) { 291 $substep = $this->get_substep(null, $i); 292 $subresp = $substep->filter_array($response); 293 list($subfraction, $newstate) = $subq->grade_response($subresp); 294 if ($newstate == question_state::$gradedright) { 295 $numright += 1; 296 } 297 } 298 return array($numright, count($this->subquestions)); 299 } 300 301 public function compute_final_grade($responses, $totaltries) { 302 $fractionsum = 0; 303 $fractionmax = 0; 304 foreach ($this->subquestions as $i => $subq) { 305 $fractionmax += $subq->defaultmark; 306 307 $lastresponse = array(); 308 $lastchange = 0; 309 $subfraction = 0; 310 foreach ($responses as $responseindex => $response) { 311 $substep = $this->get_substep(null, $i); 312 $subresp = $substep->filter_array($response); 313 if ($subq->is_same_response($lastresponse, $subresp)) { 314 continue; 315 } 316 $lastresponse = $subresp; 317 $lastchange = $responseindex; 318 list($subfraction, $newstate) = $subq->grade_response($subresp); 319 } 320 321 $fractionsum += $subq->defaultmark * max(0, $subfraction - $lastchange * $this->penalty); 322 } 323 324 return $fractionsum / $fractionmax; 325 } 326 327 public function summarise_response(array $response) { 328 $summary = array(); 329 foreach ($this->subquestions as $i => $subq) { 330 $substep = $this->get_substep(null, $i); 331 $a = new stdClass(); 332 $a->i = $i; 333 $a->response = $subq->summarise_response($substep->filter_array($response)); 334 $summary[] = get_string('subqresponse', 'qtype_multianswer', $a); 335 } 336 337 return implode('; ', $summary); 338 } 339 340 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { 341 if ($component == 'question' && $filearea == 'answer') { 342 return true; 343 344 } else if ($component == 'question' && $filearea == 'answerfeedback') { 345 // Full logic to control which feedbacks a student can see is too complex. 346 // Just allow access to all images. There is a theoretical chance the 347 // students could see files they are not meant to see by guessing URLs, 348 // but it is remote. 349 return $options->feedback; 350 351 } else if ($component == 'question' && $filearea == 'hint') { 352 return $this->check_hint_file_access($qa, $options, $args); 353 354 } else { 355 return parent::check_file_access($qa, $options, $component, $filearea, 356 $args, $forcedownload); 357 } 358 } 359 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |