Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 * Upgrade library code for the randomsamatch question type. 19 * 20 * @package qtype_randomsamatch 21 * @copyright 2013 Jean-Michel Vedrine 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 29 /** 30 * Class for converting attempt data for randomsamatch questions when upgrading 31 * attempts to the new question engine. 32 * 33 * This class is used by the code in question/engine/upgrade/upgradelib.php. 34 * 35 * @copyright 2013 Jean-Michel Vedrine 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class qtype_randomsamatch_qe2_attempt_updater extends question_qtype_attempt_updater { 39 /** @var array of question stems. */ 40 protected $stems; 41 /** @var array of question stems format. */ 42 protected $stemformat; 43 /** @var array of choices that can be matched to each stem. */ 44 protected $choices; 45 /** @var array index of the right choice for each stem. */ 46 protected $right; 47 /** @var array id of the right answer for each stem (used by {@link lookup_choice}). */ 48 protected $rightanswerid; 49 /** @var array shuffled stem indexes. */ 50 protected $stemorder; 51 /** @var array shuffled choice indexes. */ 52 protected $choiceorder; 53 /** @var array flipped version of the choiceorder array. */ 54 protected $flippedchoiceorder; 55 /** @var array of right answer for each stem. */ 56 protected $rightanswer; 57 58 public function question_summary() { 59 return ''; // Done later, after we know which shortanswer questions are used. 60 } 61 62 public function right_answer() { 63 return ''; // Done later, after we know which shortanswer questions are used. 64 } 65 66 /** 67 * Explode the answer saved as a string in state 68 * 69 * @param string $answer comma separated list of dash separated pairs 70 * @return array 71 */ 72 protected function explode_answer($answer) { 73 if (!$answer) { 74 return array(); 75 } 76 $bits = explode(',', $answer); 77 $selections = array(); 78 foreach ($bits as $bit) { 79 list($stem, $choice) = explode('-', $bit); 80 $selections[$stem] = $choice; 81 } 82 return $selections; 83 } 84 85 protected function make_summary($pairs) { 86 $bits = array(); 87 foreach ($pairs as $stem => $answer) { 88 $bits[] = $stem . ' -> ' . $answer; 89 } 90 return implode('; ', $bits); 91 } 92 93 /** 94 * Find the index corresponding to a choice 95 * 96 * @param integer $choice 97 * @return integer 98 */ 99 protected function lookup_choice($choice) { 100 if (array_key_exists($choice, $this->choices)) { 101 // Easy case: choice is a key in the choices array. 102 return $choice; 103 } else { 104 // But choice can also be the id of a shortanser correct answer 105 // without been a key of the choices array, in that case we need 106 // to first find the shortanswer id, then find the choices index 107 // associated to it. 108 $questionid = array_search($choice, $this->rightanswerid); 109 if ($questionid) { 110 return $this->right[$questionid]; 111 } 112 } 113 return null; 114 } 115 116 public function response_summary($state) { 117 $choices = $this->explode_answer($state->answer); 118 if (empty($choices)) { 119 return null; 120 } 121 122 $pairs = array(); 123 foreach ($choices as $stemid => $choicekey) { 124 if (array_key_exists($stemid, $this->stems) && $choices[$stemid]) { 125 $choiceid = $this->lookup_choice($choicekey); 126 if ($choiceid) { 127 $pairs[$this->stems[$stemid]] = $this->choices[$choiceid]; 128 } else { 129 $this->logger->log_assumption("Dealing with a place where the 130 student selected a choice that was later deleted for 131 randomsamatch question {$this->question->id}"); 132 $pairs[$this->stems[$stemid]] = '[CHOICE THAT WAS LATER DELETED]'; 133 } 134 } 135 } 136 137 if ($pairs) { 138 return $this->make_summary($pairs); 139 } else { 140 return ''; 141 } 142 } 143 144 public function was_answered($state) { 145 $choices = $this->explode_answer($state->answer); 146 foreach ($choices as $choice) { 147 if ($choice) { 148 return true; 149 } 150 } 151 return false; 152 } 153 154 public function set_first_step_data_elements($state, &$data) { 155 $this->stems = array(); 156 $this->stemformat = array(); 157 $this->choices = array(); 158 $this->right = array(); 159 $this->rightanswer = array(); 160 $choices = $this->explode_answer($state->answer); 161 $this->stemorder = array(); 162 foreach ($choices as $key => $notused) { 163 $this->stemorder[] = $key; 164 } 165 $wrappedquestions = array(); 166 // TODO test what happen when some questions are missing. 167 foreach ($this->stemorder as $questionid) { 168 $wrappedquestions[] = $this->load_question($questionid); 169 } 170 foreach ($wrappedquestions as $wrappedquestion) { 171 172 // We only take into account the first correct answer. 173 $foundcorrect = false; 174 foreach ($wrappedquestion->options->answers as $answer) { 175 if ($foundcorrect || $answer->fraction != 1.0) { 176 unset($wrappedquestion->options->answers[$answer->id]); 177 } else if (!$foundcorrect) { 178 $foundcorrect = true; 179 // Store right answer id, so we can use it later in lookup_choice. 180 $this->rightanswerid[$wrappedquestion->id] = $answer->id; 181 $key = array_search($answer->answer, $this->choices); 182 if ($key === false) { 183 $key = $answer->id; 184 $this->choices[$key] = $answer->answer; 185 $data['_choice_' . $key] = $answer->answer; 186 } 187 $this->stems[$wrappedquestion->id] = $wrappedquestion->questiontext; 188 $this->stemformat[$wrappedquestion->id] = $wrappedquestion->questiontextformat; 189 $this->right[$wrappedquestion->id] = $key; 190 $this->rightanswer[$wrappedquestion->id] = $answer->answer; 191 192 $data['_stem_' . $wrappedquestion->id] = $wrappedquestion->questiontext; 193 $data['_stemformat_' . $wrappedquestion->id] = $wrappedquestion->questiontextformat; 194 $data['_right_' . $wrappedquestion->id] = $key; 195 196 } 197 } 198 } 199 $this->choiceorder = array_keys($this->choices); 200 // We don't shuffle the choices as that seems unnecessary for old upgraded attempts. 201 $this->flippedchoiceorder = array_combine( 202 array_values($this->choiceorder), array_keys($this->choiceorder)); 203 204 $data['_stemorder'] = implode(',', $this->stemorder); 205 $data['_choiceorder'] = implode(',', $this->choiceorder); 206 207 $this->updater->qa->questionsummary = $this->to_text($this->question->questiontext) . ' {' . 208 implode('; ', $this->stems) . '} -> {' . implode('; ', $this->choices) . '}'; 209 210 $answer = array(); 211 foreach ($this->stems as $key => $stem) { 212 $answer[$stem] = $this->choices[$this->right[$key]]; 213 } 214 $this->updater->qa->rightanswer = $this->make_summary($answer); 215 } 216 217 public function supply_missing_first_step_data(&$data) { 218 throw new coding_exception('qtype_randomsamatch_updater::supply_missing_first_step_data ' . 219 'not tested'); 220 $data['_stemorder'] = array(); 221 $data['_choiceorder'] = array(); 222 } 223 224 public function set_data_elements_for_step($state, &$data) { 225 $choices = $this->explode_answer($state->answer); 226 227 foreach ($this->stemorder as $i => $key) { 228 if (empty($choices[$key])) { 229 $data['sub' . $i] = 0; 230 continue; 231 } 232 $choice = $this->lookup_choice($choices[$key]); 233 234 if (array_key_exists($choice, $this->flippedchoiceorder)) { 235 $data['sub' . $i] = $this->flippedchoiceorder[$choice] + 1; 236 } else { 237 $data['sub' . $i] = 0; 238 } 239 } 240 } 241 242 public function load_question($questionid) { 243 return $this->qeupdater->load_question($questionid); 244 } 245 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body