[ Index ] |
PHP Cross Reference of Unnamed Project |
[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 * Matching question definition class. 19 * 20 * @package qtype_match 21 * @copyright 2009 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 require_once($CFG->dirroot . '/question/type/questionbase.php'); 29 30 /** 31 * Represents a matching question. 32 * 33 * @copyright 2009 The Open University 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class qtype_match_question extends question_graded_automatically_with_countback { 37 /** @var boolean Whether the question stems should be shuffled. */ 38 public $shufflestems; 39 40 public $correctfeedback; 41 public $correctfeedbackformat; 42 public $partiallycorrectfeedback; 43 public $partiallycorrectfeedbackformat; 44 public $incorrectfeedback; 45 public $incorrectfeedbackformat; 46 47 /** @var array of question stems. */ 48 public $stems; 49 /** @var array of choices that can be matched to each stem. */ 50 public $choices; 51 /** @var array index of the right choice for each stem. */ 52 public $right; 53 54 /** @var array shuffled stem indexes. */ 55 protected $stemorder; 56 /** @var array shuffled choice indexes. */ 57 protected $choiceorder; 58 59 public function start_attempt(question_attempt_step $step, $variant) { 60 $this->stemorder = array_keys($this->stems); 61 if ($this->shufflestems) { 62 shuffle($this->stemorder); 63 } 64 $step->set_qt_var('_stemorder', implode(',', $this->stemorder)); 65 66 $choiceorder = array_keys($this->choices); 67 shuffle($choiceorder); 68 $step->set_qt_var('_choiceorder', implode(',', $choiceorder)); 69 $this->set_choiceorder($choiceorder); 70 } 71 72 public function apply_attempt_state(question_attempt_step $step) { 73 $this->stemorder = explode(',', $step->get_qt_var('_stemorder')); 74 $this->set_choiceorder(explode(',', $step->get_qt_var('_choiceorder'))); 75 76 // Add any missing subquestions. Sometimes people edit questions after they 77 // have been attempted which breaks things. 78 foreach ($this->stemorder as $stemid) { 79 if (!isset($this->stems[$stemid])) { 80 $this->stems[$stemid] = html_writer::span( 81 get_string('deletedsubquestion', 'qtype_match'), 'notifyproblem'); 82 $this->stemformat[$stemid] = FORMAT_HTML; 83 $this->right[$stemid] = 0; 84 } 85 } 86 87 // Add any missing choices. Sometimes people edit questions after they 88 // have been attempted which breaks things. 89 foreach ($this->choiceorder as $choiceid) { 90 if (!isset($this->choices[$choiceid])) { 91 $this->choices[$choiceid] = get_string('deletedchoice', 'qtype_match'); 92 } 93 } 94 } 95 96 /** 97 * Helper method used by both {@link start_attempt()} and 98 * {@link apply_attempt_state()}. 99 * @param array $choiceorder the choices, in order. 100 */ 101 protected function set_choiceorder($choiceorder) { 102 $this->choiceorder = array(); 103 foreach ($choiceorder as $key => $choiceid) { 104 $this->choiceorder[$key + 1] = $choiceid; 105 } 106 } 107 108 public function get_question_summary() { 109 $question = $this->html_to_text($this->questiontext, $this->questiontextformat); 110 $stems = array(); 111 foreach ($this->stemorder as $stemid) { 112 $stems[] = $this->html_to_text($this->stems[$stemid], $this->stemformat[$stemid]); 113 } 114 $choices = array(); 115 foreach ($this->choiceorder as $choiceid) { 116 $choices[] = $this->choices[$choiceid]; 117 } 118 return $question . ' {' . implode('; ', $stems) . '} -> {' . 119 implode('; ', $choices) . '}'; 120 } 121 122 public function summarise_response(array $response) { 123 $matches = array(); 124 foreach ($this->stemorder as $key => $stemid) { 125 if (array_key_exists($this->field($key), $response) && $response[$this->field($key)]) { 126 $matches[] = $this->html_to_text($this->stems[$stemid], 127 $this->stemformat[$stemid]) . ' -> ' . 128 $this->choices[$this->choiceorder[$response[$this->field($key)]]]; 129 } 130 } 131 if (empty($matches)) { 132 return null; 133 } 134 return implode('; ', $matches); 135 } 136 137 public function classify_response(array $response) { 138 $selectedchoicekeys = array(); 139 foreach ($this->stemorder as $key => $stemid) { 140 if (array_key_exists($this->field($key), $response) && $response[$this->field($key)]) { 141 $selectedchoicekeys[$stemid] = $this->choiceorder[$response[$this->field($key)]]; 142 } else { 143 $selectedchoicekeys[$stemid] = 0; 144 } 145 } 146 147 $parts = array(); 148 foreach ($this->stems as $stemid => $stem) { 149 if ($this->right[$stemid] == 0 || !isset($selectedchoicekeys[$stemid])) { 150 // Choice for a deleted subquestion, ignore. (See apply_attempt_state.) 151 continue; 152 } 153 $selectedchoicekey = $selectedchoicekeys[$stemid]; 154 if (empty($selectedchoicekey)) { 155 $parts[$stemid] = question_classified_response::no_response(); 156 continue; 157 } 158 $choice = $this->choices[$selectedchoicekey]; 159 if ($choice == get_string('deletedchoice', 'qtype_match')) { 160 // Deleted choice, ignore. (See apply_attempt_state.) 161 continue; 162 } 163 $parts[$stemid] = new question_classified_response( 164 $selectedchoicekey, $choice, 165 ($selectedchoicekey == $this->right[$stemid]) / count($this->stems)); 166 } 167 return $parts; 168 } 169 170 public function clear_wrong_from_response(array $response) { 171 foreach ($this->stemorder as $key => $stemid) { 172 if (!array_key_exists($this->field($key), $response) || 173 $response[$this->field($key)] != $this->get_right_choice_for($stemid)) { 174 $response[$this->field($key)] = 0; 175 } 176 } 177 return $response; 178 } 179 180 public function get_num_parts_right(array $response) { 181 $numright = 0; 182 foreach ($this->stemorder as $key => $stemid) { 183 $fieldname = $this->field($key); 184 if (!array_key_exists($fieldname, $response)) { 185 continue; 186 } 187 188 $choice = $response[$fieldname]; 189 if ($choice && $this->choiceorder[$choice] == $this->right[$stemid]) { 190 $numright += 1; 191 } 192 } 193 return array($numright, count($this->stemorder)); 194 } 195 196 /** 197 * @param int $key stem number 198 * @return string the question-type variable name. 199 */ 200 protected function field($key) { 201 return 'sub' . $key; 202 } 203 204 public function get_expected_data() { 205 $vars = array(); 206 foreach ($this->stemorder as $key => $notused) { 207 $vars[$this->field($key)] = PARAM_INT; 208 } 209 return $vars; 210 } 211 212 public function get_correct_response() { 213 $response = array(); 214 foreach ($this->stemorder as $key => $stemid) { 215 $response[$this->field($key)] = $this->get_right_choice_for($stemid); 216 } 217 return $response; 218 } 219 220 public function prepare_simulated_post_data($simulatedresponse) { 221 $postdata = array(); 222 $stemtostemids = array_flip(clean_param_array($this->stems, PARAM_NOTAGS)); 223 $choicetochoiceno = array_flip($this->choices); 224 $choicenotochoiceselectvalue = array_flip($this->choiceorder); 225 foreach ($simulatedresponse as $stem => $choice) { 226 $choice = clean_param($choice, PARAM_NOTAGS); 227 $stemid = $stemtostemids[$stem]; 228 $shuffledstemno = array_search($stemid, $this->stemorder); 229 if (empty($choice)) { 230 $choiceselectvalue = 0; 231 } else if ($choicetochoiceno[$choice]) { 232 $choiceselectvalue = $choicenotochoiceselectvalue[$choicetochoiceno[$choice]]; 233 } else { 234 throw new coding_exception("Unknown choice {$choice} in matching question - {$this->name}."); 235 } 236 $postdata[$this->field($shuffledstemno)] = $choiceselectvalue; 237 } 238 return $postdata; 239 } 240 241 public function get_student_response_values_for_simulation($postdata) { 242 $simulatedresponse = array(); 243 foreach ($this->stemorder as $shuffledstemno => $stemid) { 244 if (!empty($postdata[$this->field($shuffledstemno)])) { 245 $choiceselectvalue = $postdata[$this->field($shuffledstemno)]; 246 $choiceno = $this->choiceorder[$choiceselectvalue]; 247 $choice = clean_param($this->choices[$choiceno], PARAM_NOTAGS); 248 $stem = clean_param($this->stems[$stemid], PARAM_NOTAGS); 249 $simulatedresponse[$stem] = $choice; 250 } 251 } 252 ksort($simulatedresponse); 253 return $simulatedresponse; 254 } 255 256 public function get_right_choice_for($stemid) { 257 foreach ($this->choiceorder as $choicekey => $choiceid) { 258 if ($this->right[$stemid] == $choiceid) { 259 return $choicekey; 260 } 261 } 262 } 263 264 public function is_complete_response(array $response) { 265 $complete = true; 266 foreach ($this->stemorder as $key => $stemid) { 267 $complete = $complete && !empty($response[$this->field($key)]); 268 } 269 return $complete; 270 } 271 272 public function is_gradable_response(array $response) { 273 foreach ($this->stemorder as $key => $stemid) { 274 if (!empty($response[$this->field($key)])) { 275 return true; 276 } 277 } 278 return false; 279 } 280 281 public function get_validation_error(array $response) { 282 if ($this->is_complete_response($response)) { 283 return ''; 284 } 285 return get_string('pleaseananswerallparts', 'qtype_match'); 286 } 287 288 public function is_same_response(array $prevresponse, array $newresponse) { 289 foreach ($this->stemorder as $key => $notused) { 290 $fieldname = $this->field($key); 291 if (!question_utils::arrays_same_at_key_integer( 292 $prevresponse, $newresponse, $fieldname)) { 293 return false; 294 } 295 } 296 return true; 297 } 298 299 public function grade_response(array $response) { 300 list($right, $total) = $this->get_num_parts_right($response); 301 $fraction = $right / $total; 302 return array($fraction, question_state::graded_state_for_fraction($fraction)); 303 } 304 305 public function compute_final_grade($responses, $totaltries) { 306 $totalstemscore = 0; 307 foreach ($this->stemorder as $key => $stemid) { 308 $fieldname = $this->field($key); 309 310 $lastwrongindex = -1; 311 $finallyright = false; 312 foreach ($responses as $i => $response) { 313 if (!array_key_exists($fieldname, $response) || !$response[$fieldname] || 314 $this->choiceorder[$response[$fieldname]] != $this->right[$stemid]) { 315 $lastwrongindex = $i; 316 $finallyright = false; 317 } else { 318 $finallyright = true; 319 } 320 } 321 322 if ($finallyright) { 323 $totalstemscore += max(0, 1 - ($lastwrongindex + 1) * $this->penalty); 324 } 325 } 326 327 return $totalstemscore / count($this->stemorder); 328 } 329 330 public function get_stem_order() { 331 return $this->stemorder; 332 } 333 334 public function get_choice_order() { 335 return $this->choiceorder; 336 } 337 338 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { 339 if ($component == 'qtype_match' && $filearea == 'subquestion') { 340 $subqid = reset($args); // Itemid is sub question id. 341 return array_key_exists($subqid, $this->stems); 342 343 } else if ($component == 'question' && in_array($filearea, 344 array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) { 345 return $this->check_combined_feedback_file_access($qa, $options, $filearea); 346 347 } else if ($component == 'question' && $filearea == 'hint') { 348 return $this->check_hint_file_access($qa, $options, $args); 349 350 } else { 351 return parent::check_file_access($qa, $options, $component, $filearea, 352 $args, $forcedownload); 353 } 354 } 355 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |