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