Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 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 * Question type class for the calculated multiple-choice question type. 19 * 20 * @package qtype 21 * @subpackage calculatedmulti 22 * @copyright 2009 Pierre Pichet 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 use qtype_calculatedmulti\qtype_calculatedmulti_answer; 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 require_once($CFG->dirroot . '/question/type/multichoice/questiontype.php'); 31 require_once($CFG->dirroot . '/question/type/calculated/questiontype.php'); 32 33 34 /** 35 * The calculated multiple-choice question type. 36 * 37 * @copyright 2009 Pierre Pichet 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class qtype_calculatedmulti extends qtype_calculated { 41 42 public function save_question_options($question) { 43 global $CFG, $DB; 44 $context = $question->context; 45 46 // Make it impossible to save bad formulas anywhere. 47 $this->validate_question_data($question); 48 49 // Calculated options. 50 $update = true; 51 $options = $DB->get_record('question_calculated_options', 52 array('question' => $question->id)); 53 if (!$options) { 54 $options = new stdClass(); 55 $options->question = $question->id; 56 $options->correctfeedback = ''; 57 $options->partiallycorrectfeedback = ''; 58 $options->incorrectfeedback = ''; 59 $options->id = $DB->insert_record('question_calculated_options', $options); 60 } 61 $options->synchronize = $question->synchronize; 62 $options->single = $question->single; 63 $options->answernumbering = $question->answernumbering; 64 $options->shuffleanswers = $question->shuffleanswers; 65 $options = $this->save_combined_feedback_helper($options, $question, $context, true); 66 $DB->update_record('question_calculated_options', $options); 67 68 // Get old versions of the objects. 69 if (!$oldanswers = $DB->get_records('question_answers', 70 array('question' => $question->id), 'id ASC')) { 71 $oldanswers = array(); 72 } 73 if (!$oldoptions = $DB->get_records('question_calculated', 74 array('question' => $question->id), 'answer ASC')) { 75 $oldoptions = array(); 76 } 77 78 // Insert all the new answers. 79 foreach ($question->answer as $key => $answerdata) { 80 if (is_array($answerdata)) { 81 $answerdata = $answerdata['text']; 82 } 83 if (trim($answerdata) == '') { 84 continue; 85 } 86 87 // Update an existing answer if possible. 88 $answer = array_shift($oldanswers); 89 if (!$answer) { 90 $answer = new stdClass(); 91 $answer->question = $question->id; 92 $answer->answer = ''; 93 $answer->feedback = ''; 94 $answer->id = $DB->insert_record('question_answers', $answer); 95 } 96 97 if (is_array($answerdata)) { 98 // Doing an import. 99 $answer->answer = $this->import_or_save_files($answerdata, 100 $context, 'question', 'answer', $answer->id); 101 $answer->answerformat = $answerdata['format']; 102 } else { 103 // Saving the form. 104 $answer->answer = $answerdata; 105 $answer->answerformat = FORMAT_HTML; 106 } 107 $answer->fraction = $question->fraction[$key]; 108 $answer->feedback = $this->import_or_save_files($question->feedback[$key], 109 $context, 'question', 'answerfeedback', $answer->id); 110 $answer->feedbackformat = $question->feedback[$key]['format']; 111 112 $DB->update_record("question_answers", $answer); 113 114 // Set up the options object. 115 if (!$options = array_shift($oldoptions)) { 116 $options = new stdClass(); 117 } 118 $options->question = $question->id; 119 $options->answer = $answer->id; 120 $options->tolerance = trim($question->tolerance[$key]); 121 $options->tolerancetype = trim($question->tolerancetype[$key]); 122 $options->correctanswerlength = trim($question->correctanswerlength[$key]); 123 $options->correctanswerformat = trim($question->correctanswerformat[$key]); 124 125 // Save options. 126 if (isset($options->id)) { 127 // Reusing existing record. 128 $DB->update_record('question_calculated', $options); 129 } else { 130 // New options. 131 $DB->insert_record('question_calculated', $options); 132 } 133 } 134 135 // Delete old answer records. 136 if (!empty($oldanswers)) { 137 foreach ($oldanswers as $oa) { 138 $DB->delete_records('question_answers', array('id' => $oa->id)); 139 } 140 } 141 if (!empty($oldoptions)) { 142 foreach ($oldoptions as $oo) { 143 $DB->delete_records('question_calculated', array('id' => $oo->id)); 144 } 145 } 146 147 $this->save_hints($question, true); 148 149 if (isset($question->import_process) && $question->import_process) { 150 $this->import_datasets($question); 151 } 152 // Report any problems. 153 if (!empty($result->notice)) { 154 return $result; 155 } 156 157 return true; 158 } 159 160 protected function validate_answer($answer) { 161 $error = qtype_calculated_find_formula_errors_in_text($answer); 162 if ($error) { 163 throw new coding_exception($error); 164 } 165 } 166 167 protected function validate_question_data($question) { 168 parent::validate_question_data($question); 169 $this->validate_text($question->correctfeedback['text']); 170 $this->validate_text($question->partiallycorrectfeedback['text']); 171 $this->validate_text($question->incorrectfeedback['text']); 172 } 173 174 protected function make_question_instance($questiondata) { 175 question_bank::load_question_definition_classes($this->name()); 176 if ($questiondata->options->single) { 177 $class = 'qtype_calculatedmulti_single_question'; 178 } else { 179 $class = 'qtype_calculatedmulti_multi_question'; 180 } 181 return new $class(); 182 } 183 184 protected function initialise_question_instance(question_definition $question, $questiondata) { 185 question_type::initialise_question_instance($question, $questiondata); 186 187 $question->shuffleanswers = $questiondata->options->shuffleanswers; 188 $question->answernumbering = $questiondata->options->answernumbering; 189 if (!empty($questiondata->options->layout)) { 190 $question->layout = $questiondata->options->layout; 191 } else { 192 $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL; 193 } 194 195 $question->synchronised = $questiondata->options->synchronize; 196 197 $this->initialise_combined_feedback($question, $questiondata, true); 198 $this->initialise_question_answers($question, $questiondata); 199 200 foreach ($questiondata->options->answers as $a) { 201 $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength; 202 $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat; 203 } 204 205 $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id); 206 } 207 208 /** 209 * Public override method, created so that make_answer will be available 210 * for use by classes which extend qtype_multichoice_base. 211 * 212 * @param stdClass $answer Moodle DB question_answers object. 213 * @return question_answer 214 */ 215 public function make_answer($answer) { 216 return new qtype_calculatedmulti_answer($answer->id, $answer->answer, 217 $answer->fraction, $answer->feedback, $answer->feedbackformat); 218 } 219 220 protected function make_hint($hint) { 221 return question_hint_with_parts::load_from_record($hint); 222 } 223 224 public function comment_header($question) { 225 $strheader = ''; 226 $delimiter = ''; 227 228 $answers = $question->options->answers; 229 230 foreach ($answers as $key => $answer) { 231 $ans = shorten_text($answer->answer, 17, true); 232 $strheader .= $delimiter.$ans; 233 $delimiter = '<br/><br/>'; 234 } 235 return $strheader; 236 } 237 238 public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext, 239 $answers, $data, $number) { 240 241 $comment = new stdClass(); 242 $comment->stranswers = array(); 243 $comment->outsidelimit = false; 244 $comment->answers = array(); 245 246 $answers = fullclone($answers); 247 foreach ($answers as $key => $answer) { 248 // Evaluate the equations i.e {=5+4). 249 $anssubstituted = $this->substitute_variables($answer->answer, $data); 250 $formulas = $this->find_formulas($anssubstituted); 251 $replaces = []; 252 foreach ($formulas as $formula) { 253 if ($formulaerrors = qtype_calculated_find_formula_errors($formula)) { 254 $str = $formulaerrors; 255 } else { 256 eval('$str = ' . $formula . ';'); 257 } 258 $replaces[$formula] = $str; 259 } 260 $anstext = str_replace(array_keys($replaces), array_values($replaces), $anssubstituted); 261 $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext; 262 } 263 return fullclone($comment); 264 } 265 266 public function get_virtual_qtype() { 267 return question_bank::get_qtype('multichoice'); 268 } 269 270 public function get_possible_responses($questiondata) { 271 if ($questiondata->options->single) { 272 $responses = array(); 273 274 foreach ($questiondata->options->answers as $aid => $answer) { 275 $responses[$aid] = new question_possible_response($answer->answer, 276 $answer->fraction); 277 } 278 279 $responses[null] = question_possible_response::no_response(); 280 return array($questiondata->id => $responses); 281 } else { 282 $parts = array(); 283 284 foreach ($questiondata->options->answers as $aid => $answer) { 285 $parts[$aid] = array($aid => 286 new question_possible_response($answer->answer, $answer->fraction)); 287 } 288 289 return $parts; 290 } 291 } 292 293 public function move_files($questionid, $oldcontextid, $newcontextid) { 294 $fs = get_file_storage(); 295 296 parent::move_files($questionid, $oldcontextid, $newcontextid); 297 $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true); 298 $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid); 299 300 $fs->move_area_files_to_new_context($oldcontextid, 301 $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid); 302 $fs->move_area_files_to_new_context($oldcontextid, 303 $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid); 304 $fs->move_area_files_to_new_context($oldcontextid, 305 $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid); 306 } 307 308 protected function delete_files($questionid, $contextid) { 309 $fs = get_file_storage(); 310 311 parent::delete_files($questionid, $contextid); 312 $this->delete_files_in_answers($questionid, $contextid, true); 313 $this->delete_files_in_hints($questionid, $contextid); 314 315 $fs->delete_area_files($contextid, 'qtype_calculatedmulti', 316 'correctfeedback', $questionid); 317 $fs->delete_area_files($contextid, 'qtype_calculatedmulti', 318 'partiallycorrectfeedback', $questionid); 319 $fs->delete_area_files($contextid, 'qtype_calculatedmulti', 320 'incorrectfeedback', $questionid); 321 } 322 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body