See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 * The questiontype class for the multiple choice question type. 19 * 20 * @package qtype 21 * @subpackage multichoice 22 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 global $CFG; 30 require_once($CFG->libdir . '/questionlib.php'); 31 32 33 /** 34 * The multiple choice question type. 35 * 36 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class qtype_multichoice extends question_type { 40 public function get_question_options($question) { 41 global $DB, $OUTPUT; 42 43 $question->options = $DB->get_record('qtype_multichoice_options', ['questionid' => $question->id]); 44 45 if ($question->options === false) { 46 // If this has happened, then we have a problem. 47 // For the user to be able to edit or delete this question, we need options. 48 debugging("Question ID {$question->id} was missing an options record. Using default.", DEBUG_DEVELOPER); 49 50 $question->options = $this->create_default_options($question); 51 } 52 53 parent::get_question_options($question); 54 } 55 56 /** 57 * Create a default options object for the provided question. 58 * 59 * @param object $question The queston we are working with. 60 * @return object The options object. 61 */ 62 protected function create_default_options($question) { 63 // Create a default question options record. 64 $options = new stdClass(); 65 $options->questionid = $question->id; 66 67 // Get the default strings and just set the format. 68 $options->correctfeedback = get_string('correctfeedbackdefault', 'question'); 69 $options->correctfeedbackformat = FORMAT_HTML; 70 $options->partiallycorrectfeedback = get_string('partiallycorrectfeedbackdefault', 'question');; 71 $options->partiallycorrectfeedbackformat = FORMAT_HTML; 72 $options->incorrectfeedback = get_string('incorrectfeedbackdefault', 'question'); 73 $options->incorrectfeedbackformat = FORMAT_HTML; 74 75 $config = get_config('qtype_multichoice'); 76 $options->single = $config->answerhowmany; 77 if (isset($question->layout)) { 78 $options->layout = $question->layout; 79 } 80 $options->answernumbering = $config->answernumbering; 81 $options->shuffleanswers = $config->shuffleanswers; 82 $options->showstandardinstruction = 0; 83 $options->shownumcorrect = 1; 84 85 return $options; 86 } 87 88 public function save_question_options($question) { 89 global $DB; 90 $context = $question->context; 91 $result = new stdClass(); 92 93 $oldanswers = $DB->get_records('question_answers', 94 array('question' => $question->id), 'id ASC'); 95 96 // Following hack to check at least two answers exist. 97 $answercount = 0; 98 foreach ($question->answer as $key => $answer) { 99 if ($answer != '') { 100 $answercount++; 101 } 102 } 103 if ($answercount < 2) { // Check there are at lest 2 answers for multiple choice. 104 $result->error = get_string('notenoughanswers', 'qtype_multichoice', '2'); 105 return $result; 106 } 107 108 // Insert all the new answers. 109 $totalfraction = 0; 110 $maxfraction = -1; 111 foreach ($question->answer as $key => $answerdata) { 112 if (trim($answerdata['text']) == '') { 113 continue; 114 } 115 116 // Update an existing answer if possible. 117 $answer = array_shift($oldanswers); 118 if (!$answer) { 119 $answer = new stdClass(); 120 $answer->question = $question->id; 121 $answer->answer = ''; 122 $answer->feedback = ''; 123 $answer->id = $DB->insert_record('question_answers', $answer); 124 } 125 126 // Doing an import. 127 $answer->answer = $this->import_or_save_files($answerdata, 128 $context, 'question', 'answer', $answer->id); 129 $answer->answerformat = $answerdata['format']; 130 $answer->fraction = $question->fraction[$key]; 131 $answer->feedback = $this->import_or_save_files($question->feedback[$key], 132 $context, 'question', 'answerfeedback', $answer->id); 133 $answer->feedbackformat = $question->feedback[$key]['format']; 134 135 $DB->update_record('question_answers', $answer); 136 137 if ($question->fraction[$key] > 0) { 138 $totalfraction += $question->fraction[$key]; 139 } 140 if ($question->fraction[$key] > $maxfraction) { 141 $maxfraction = $question->fraction[$key]; 142 } 143 } 144 145 // Delete any left over old answer records. 146 $fs = get_file_storage(); 147 foreach ($oldanswers as $oldanswer) { 148 $fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id); 149 $DB->delete_records('question_answers', array('id' => $oldanswer->id)); 150 } 151 152 $options = $DB->get_record('qtype_multichoice_options', array('questionid' => $question->id)); 153 if (!$options) { 154 $options = new stdClass(); 155 $options->questionid = $question->id; 156 $options->correctfeedback = ''; 157 $options->partiallycorrectfeedback = ''; 158 $options->incorrectfeedback = ''; 159 $options->showstandardinstruction = 0; 160 $options->id = $DB->insert_record('qtype_multichoice_options', $options); 161 } 162 163 $options->single = $question->single; 164 if (isset($question->layout)) { 165 $options->layout = $question->layout; 166 } 167 $options->answernumbering = $question->answernumbering; 168 $options->shuffleanswers = $question->shuffleanswers; 169 $options->showstandardinstruction = !empty($question->showstandardinstruction); 170 $options = $this->save_combined_feedback_helper($options, $question, $context, true); 171 $DB->update_record('qtype_multichoice_options', $options); 172 173 $this->save_hints($question, true); 174 175 // Perform sanity checks on fractional grades. 176 if ($options->single) { 177 if ($maxfraction != 1) { 178 $result->noticeyesno = get_string('fractionsnomax', 'qtype_multichoice', 179 $maxfraction * 100); 180 return $result; 181 } 182 } else { 183 $totalfraction = round($totalfraction, 2); 184 if ($totalfraction != 1) { 185 $result->noticeyesno = get_string('fractionsaddwrong', 'qtype_multichoice', 186 $totalfraction * 100); 187 return $result; 188 } 189 } 190 } 191 192 protected function make_question_instance($questiondata) { 193 question_bank::load_question_definition_classes($this->name()); 194 if ($questiondata->options->single) { 195 $class = 'qtype_multichoice_single_question'; 196 } else { 197 $class = 'qtype_multichoice_multi_question'; 198 } 199 return new $class(); 200 } 201 202 protected function make_hint($hint) { 203 return question_hint_with_parts::load_from_record($hint); 204 } 205 206 protected function initialise_question_instance(question_definition $question, $questiondata) { 207 parent::initialise_question_instance($question, $questiondata); 208 $question->shuffleanswers = $questiondata->options->shuffleanswers; 209 $question->answernumbering = $questiondata->options->answernumbering; 210 $question->showstandardinstruction = $questiondata->options->showstandardinstruction; 211 if (!empty($questiondata->options->layout)) { 212 $question->layout = $questiondata->options->layout; 213 } else { 214 $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL; 215 } 216 $this->initialise_combined_feedback($question, $questiondata, true); 217 218 $this->initialise_question_answers($question, $questiondata, false); 219 } 220 221 public function make_answer($answer) { 222 // Overridden just so we can make it public for use by question.php. 223 return parent::make_answer($answer); 224 } 225 226 public function delete_question($questionid, $contextid) { 227 global $DB; 228 $DB->delete_records('qtype_multichoice_options', array('questionid' => $questionid)); 229 230 parent::delete_question($questionid, $contextid); 231 } 232 233 public function get_random_guess_score($questiondata) { 234 if (!$questiondata->options->single) { 235 // Pretty much impossible to compute for _multi questions. Don't try. 236 return null; 237 } 238 239 // Single choice questions - average choice fraction. 240 $totalfraction = 0; 241 foreach ($questiondata->options->answers as $answer) { 242 $totalfraction += $answer->fraction; 243 } 244 return $totalfraction / count($questiondata->options->answers); 245 } 246 247 public function get_possible_responses($questiondata) { 248 if ($questiondata->options->single) { 249 $responses = array(); 250 251 foreach ($questiondata->options->answers as $aid => $answer) { 252 $responses[$aid] = new question_possible_response( 253 question_utils::to_plain_text($answer->answer, $answer->answerformat), 254 $answer->fraction); 255 } 256 257 $responses[null] = question_possible_response::no_response(); 258 return array($questiondata->id => $responses); 259 } else { 260 $parts = array(); 261 262 foreach ($questiondata->options->answers as $aid => $answer) { 263 $parts[$aid] = array($aid => new question_possible_response( 264 question_utils::to_plain_text($answer->answer, $answer->answerformat), 265 $answer->fraction)); 266 } 267 268 return $parts; 269 } 270 } 271 272 /** 273 * @return array of the numbering styles supported. For each one, there 274 * should be a lang string answernumberingxxx in teh qtype_multichoice 275 * language file, and a case in the switch statement in number_in_style, 276 * and it should be listed in the definition of this column in install.xml. 277 */ 278 public static function get_numbering_styles() { 279 $styles = array(); 280 foreach (array('abc', 'ABCD', '123', 'iii', 'IIII', 'none') as $numberingoption) { 281 $styles[$numberingoption] = 282 get_string('answernumbering' . $numberingoption, 'qtype_multichoice'); 283 } 284 return $styles; 285 } 286 287 public function move_files($questionid, $oldcontextid, $newcontextid) { 288 parent::move_files($questionid, $oldcontextid, $newcontextid); 289 $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true); 290 $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid); 291 $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid); 292 } 293 294 protected function delete_files($questionid, $contextid) { 295 parent::delete_files($questionid, $contextid); 296 $this->delete_files_in_answers($questionid, $contextid, true); 297 $this->delete_files_in_combined_feedback($questionid, $contextid); 298 $this->delete_files_in_hints($questionid, $contextid); 299 } 300 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body