|
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 00036 class qtype_match_question extends question_graded_automatically_with_countback { 00038 public $shufflestems; 00039 00040 public $correctfeedback; 00041 public $correctfeedbackformat; 00042 public $partiallycorrectfeedback; 00043 public $partiallycorrectfeedbackformat; 00044 public $incorrectfeedback; 00045 public $incorrectfeedbackformat; 00046 00048 public $stems; 00050 public $choices; 00052 public $right; 00053 00055 protected $stemorder; 00057 protected $choiceorder; 00058 00059 public function start_attempt(question_attempt_step $step, $variant) { 00060 $this->stemorder = array_keys($this->stems); 00061 if ($this->shufflestems) { 00062 shuffle($this->stemorder); 00063 } 00064 $step->set_qt_var('_stemorder', implode(',', $this->stemorder)); 00065 00066 $choiceorder = array_keys($this->choices); 00067 shuffle($choiceorder); 00068 $step->set_qt_var('_choiceorder', implode(',', $choiceorder)); 00069 $this->set_choiceorder($choiceorder); 00070 } 00071 00072 public function apply_attempt_state(question_attempt_step $step) { 00073 $this->stemorder = explode(',', $step->get_qt_var('_stemorder')); 00074 $this->set_choiceorder(explode(',', $step->get_qt_var('_choiceorder'))); 00075 } 00076 00082 protected function set_choiceorder($choiceorder) { 00083 $this->choiceorder = array(); 00084 foreach ($choiceorder as $key => $value) { 00085 $this->choiceorder[$key + 1] = $value; 00086 } 00087 } 00088 00089 public function get_question_summary() { 00090 $question = $this->html_to_text($this->questiontext, $this->questiontextformat); 00091 $stems = array(); 00092 foreach ($this->stemorder as $stemid) { 00093 $stems[] = $this->html_to_text($this->stems[$stemid], $this->stemformat[$stemid]); 00094 } 00095 $choices = array(); 00096 foreach ($this->choiceorder as $choiceid) { 00097 $choices[] = $this->choices[$choiceid]; 00098 } 00099 return $question . ' {' . implode('; ', $stems) . '} -> {' . 00100 implode('; ', $choices) . '}'; 00101 } 00102 00103 public function summarise_response(array $response) { 00104 $matches = array(); 00105 foreach ($this->stemorder as $key => $stemid) { 00106 if (array_key_exists($this->field($key), $response) && $response[$this->field($key)]) { 00107 $matches[] = $this->html_to_text($this->stems[$stemid], 00108 $this->stemformat[$stemid]) . ' -> ' . 00109 $this->choices[$this->choiceorder[$response[$this->field($key)]]]; 00110 } 00111 } 00112 if (empty($matches)) { 00113 return null; 00114 } 00115 return implode('; ', $matches); 00116 } 00117 00118 public function classify_response(array $response) { 00119 $selectedchoices = array(); 00120 foreach ($this->stemorder as $key => $stemid) { 00121 if (array_key_exists($this->field($key), $response) && $response[$this->field($key)]) { 00122 $selectedchoices[$stemid] = $this->choiceorder[$response[$this->field($key)]]; 00123 } else { 00124 $selectedchoices[$stemid] = 0; 00125 } 00126 } 00127 00128 $parts = array(); 00129 foreach ($this->stems as $stemid => $stem) { 00130 if (empty($selectedchoices[$stemid])) { 00131 $parts[$stemid] = question_classified_response::no_response(); 00132 continue; 00133 } 00134 $choice = $this->choices[$selectedchoices[$stemid]]; 00135 $parts[$stemid] = new question_classified_response( 00136 $selectedchoices[$stemid], $choice, 00137 ($selectedchoices[$stemid] == $this->right[$stemid]) / count($this->stems)); 00138 } 00139 return $parts; 00140 } 00141 00142 public function clear_wrong_from_response(array $response) { 00143 foreach ($this->stemorder as $key => $stemid) { 00144 if (!array_key_exists($this->field($key), $response) || 00145 $response[$this->field($key)] != $this->get_right_choice_for($stemid)) { 00146 $response[$this->field($key)] = 0; 00147 } 00148 } 00149 return $response; 00150 } 00151 00152 public function get_num_parts_right(array $response) { 00153 $numright = 0; 00154 foreach ($this->stemorder as $key => $stemid) { 00155 $fieldname = $this->field($key); 00156 if (!array_key_exists($fieldname, $response)) { 00157 continue; 00158 } 00159 00160 $choice = $response[$fieldname]; 00161 if ($choice && $this->choiceorder[$choice] == $this->right[$stemid]) { 00162 $numright += 1; 00163 } 00164 } 00165 return array($numright, count($this->stemorder)); 00166 } 00167 00172 protected function field($key) { 00173 return 'sub' . $key; 00174 } 00175 00176 public function get_expected_data() { 00177 $vars = array(); 00178 foreach ($this->stemorder as $key => $notused) { 00179 $vars[$this->field($key)] = PARAM_INTEGER; 00180 } 00181 return $vars; 00182 } 00183 00184 public function get_correct_response() { 00185 $response = array(); 00186 foreach ($this->stemorder as $key => $stemid) { 00187 $response[$this->field($key)] = $this->get_right_choice_for($stemid); 00188 } 00189 return $response; 00190 } 00191 00192 public function get_right_choice_for($stemid) { 00193 foreach ($this->choiceorder as $choicekey => $choiceid) { 00194 if ($this->right[$stemid] == $choiceid) { 00195 return $choicekey; 00196 } 00197 } 00198 } 00199 00200 public function is_complete_response(array $response) { 00201 $complete = true; 00202 foreach ($this->stemorder as $key => $stemid) { 00203 $complete = $complete && !empty($response[$this->field($key)]); 00204 } 00205 return $complete; 00206 } 00207 00208 public function is_gradable_response(array $response) { 00209 foreach ($this->stemorder as $key => $stemid) { 00210 if (!empty($response[$this->field($key)])) { 00211 return true; 00212 } 00213 } 00214 return false; 00215 } 00216 00217 public function get_validation_error(array $response) { 00218 if ($this->is_complete_response($response)) { 00219 return ''; 00220 } 00221 return get_string('pleaseananswerallparts', 'qtype_match'); 00222 } 00223 00224 public function is_same_response(array $prevresponse, array $newresponse) { 00225 foreach ($this->stemorder as $key => $notused) { 00226 $fieldname = $this->field($key); 00227 if (!question_utils::arrays_same_at_key_integer( 00228 $prevresponse, $newresponse, $fieldname)) { 00229 return false; 00230 } 00231 } 00232 return true; 00233 } 00234 00235 public function grade_response(array $response) { 00236 list($right, $total) = $this->get_num_parts_right($response); 00237 $fraction = $right / $total; 00238 return array($fraction, question_state::graded_state_for_fraction($fraction)); 00239 } 00240 00241 public function compute_final_grade($responses, $totaltries) { 00242 $totalstemscore = 0; 00243 foreach ($this->stemorder as $key => $stemid) { 00244 $fieldname = $this->field($key); 00245 00246 $lastwrongindex = -1; 00247 $finallyright = false; 00248 foreach ($responses as $i => $response) { 00249 if (!array_key_exists($fieldname, $response) || !$response[$fieldname] || 00250 $this->choiceorder[$response[$fieldname]] != $this->right[$stemid]) { 00251 $lastwrongindex = $i; 00252 $finallyright = false; 00253 } else { 00254 $finallyright = true; 00255 } 00256 } 00257 00258 if ($finallyright) { 00259 $totalstemscore += max(0, 1 - ($lastwrongindex + 1) * $this->penalty); 00260 } 00261 } 00262 00263 return $totalstemscore / count($this->stemorder); 00264 } 00265 00266 public function get_stem_order() { 00267 return $this->stemorder; 00268 } 00269 00270 public function get_choice_order() { 00271 return $this->choiceorder; 00272 } 00273 00274 public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { 00275 if ($component == 'qtype_match' && $filearea == 'subquestion') { 00276 $subqid = reset($args); // itemid is sub question id 00277 return array_key_exists($subqid, $this->stems); 00278 00279 } else if ($component == 'question' && in_array($filearea, 00280 array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) { 00281 return $this->check_combined_feedback_file_access($qa, $options, $filearea); 00282 00283 } else if ($component == 'question' && $filearea == 'hint') { 00284 return $this->check_hint_file_access($qa, $options, $args); 00285 00286 } else { 00287 return parent::check_file_access($qa, $options, $component, $filearea, 00288 $args, $forcedownload); 00289 } 00290 } 00291 }