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