MDL-16464 qtype_multichoice: fix missed question -> questionid.
[moodle.git] / question / type / multichoice / questiontype.php
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/>.
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  */
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->libdir . '/questionlib.php');
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         $question->options = $DB->get_record('qtype_multichoice_options',
43                 array('questionid' => $question->id), '*', MUST_EXIST);
44         parent::get_question_options($question);
45     }
47     public function save_question_options($question) {
48         global $DB;
49         $context = $question->context;
50         $result = new stdClass();
52         $oldanswers = $DB->get_records('question_answers',
53                 array('question' => $question->id), 'id ASC');
55         // Following hack to check at least two answers exist.
56         $answercount = 0;
57         foreach ($question->answer as $key => $answer) {
58             if ($answer != '') {
59                 $answercount++;
60             }
61         }
62         if ($answercount < 2) { // Check there are at lest 2 answers for multiple choice.
63             $result->notice = get_string('notenoughanswers', 'qtype_multichoice', '2');
64             return $result;
65         }
67         // Insert all the new answers.
68         $totalfraction = 0;
69         $maxfraction = -1;
70         foreach ($question->answer as $key => $answerdata) {
71             if (trim($answerdata['text']) == '') {
72                 continue;
73             }
75             // Update an existing answer if possible.
76             $answer = array_shift($oldanswers);
77             if (!$answer) {
78                 $answer = new stdClass();
79                 $answer->question = $question->id;
80                 $answer->answer = '';
81                 $answer->feedback = '';
82                 $answer->id = $DB->insert_record('question_answers', $answer);
83             }
85             // Doing an import.
86             $answer->answer = $this->import_or_save_files($answerdata,
87                     $context, 'question', 'answer', $answer->id);
88             $answer->answerformat = $answerdata['format'];
89             $answer->fraction = $question->fraction[$key];
90             $answer->feedback = $this->import_or_save_files($question->feedback[$key],
91                     $context, 'question', 'answerfeedback', $answer->id);
92             $answer->feedbackformat = $question->feedback[$key]['format'];
94             $DB->update_record('question_answers', $answer);
96             if ($question->fraction[$key] > 0) {
97                 $totalfraction += $question->fraction[$key];
98             }
99             if ($question->fraction[$key] > $maxfraction) {
100                 $maxfraction = $question->fraction[$key];
101             }
102         }
104         // Delete any left over old answer records.
105         $fs = get_file_storage();
106         foreach ($oldanswers as $oldanswer) {
107             $fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
108             $DB->delete_records('question_answers', array('id' => $oldanswer->id));
109         }
111         $options = $DB->get_record('qtype_multichoice_options', array('questionid' => $question->id));
112         if (!$options) {
113             $options = new stdClass();
114             $options->questionid = $question->id;
115             $options->correctfeedback = '';
116             $options->partiallycorrectfeedback = '';
117             $options->incorrectfeedback = '';
118             $options->id = $DB->insert_record('qtype_multichoice_options', $options);
119         }
121         $options->single = $question->single;
122         if (isset($question->layout)) {
123             $options->layout = $question->layout;
124         }
125         $options->answernumbering = $question->answernumbering;
126         $options->shuffleanswers = $question->shuffleanswers;
127         $options = $this->save_combined_feedback_helper($options, $question, $context, true);
128         $DB->update_record('qtype_multichoice_options', $options);
130         $this->save_hints($question, true);
132         // Perform sanity checks on fractional grades.
133         if ($options->single) {
134             if ($maxfraction != 1) {
135                 $result->noticeyesno = get_string('fractionsnomax', 'qtype_multichoice',
136                         $maxfraction * 100);
137                 return $result;
138             }
139         } else {
140             $totalfraction = round($totalfraction, 2);
141             if ($totalfraction != 1) {
142                 $result->noticeyesno = get_string('fractionsaddwrong', 'qtype_multichoice',
143                         $totalfraction * 100);
144                 return $result;
145             }
146         }
147     }
149     protected function make_question_instance($questiondata) {
150         question_bank::load_question_definition_classes($this->name());
151         if ($questiondata->options->single) {
152             $class = 'qtype_multichoice_single_question';
153         } else {
154             $class = 'qtype_multichoice_multi_question';
155         }
156         return new $class();
157     }
159     protected function make_hint($hint) {
160         return question_hint_with_parts::load_from_record($hint);
161     }
163     protected function initialise_question_instance(question_definition $question, $questiondata) {
164         parent::initialise_question_instance($question, $questiondata);
165         $question->shuffleanswers = $questiondata->options->shuffleanswers;
166         $question->answernumbering = $questiondata->options->answernumbering;
167         if (!empty($questiondata->options->layout)) {
168             $question->layout = $questiondata->options->layout;
169         } else {
170             $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL;
171         }
172         $this->initialise_combined_feedback($question, $questiondata, true);
174         $this->initialise_question_answers($question, $questiondata, false);
175     }
177     public function delete_question($questionid, $contextid) {
178         global $DB;
179         $DB->delete_records('qtype_multichoice_options', array('questionid' => $questionid));
181         parent::delete_question($questionid, $contextid);
182     }
184     public function get_random_guess_score($questiondata) {
185         if (!$questiondata->options->single) {
186             // Pretty much impossible to compute for _multi questions. Don't try.
187             return null;
188         }
190         // Single choice questions - average choice fraction.
191         $totalfraction = 0;
192         foreach ($questiondata->options->answers as $answer) {
193             $totalfraction += $answer->fraction;
194         }
195         return $totalfraction / count($questiondata->options->answers);
196     }
198     public function get_possible_responses($questiondata) {
199         if ($questiondata->options->single) {
200             $responses = array();
202             foreach ($questiondata->options->answers as $aid => $answer) {
203                 $responses[$aid] = new question_possible_response(
204                         question_utils::to_plain_text($answer->answer, $answer->answerformat),
205                         $answer->fraction);
206             }
208             $responses[null] = question_possible_response::no_response();
209             return array($questiondata->id => $responses);
210         } else {
211             $parts = array();
213             foreach ($questiondata->options->answers as $aid => $answer) {
214                 $parts[$aid] = array($aid => new question_possible_response(
215                         question_utils::to_plain_text($answer->answer, $answer->answerformat),
216                         $answer->fraction));
217             }
219             return $parts;
220         }
221     }
223     /**
224      * @return array of the numbering styles supported. For each one, there
225      *      should be a lang string answernumberingxxx in teh qtype_multichoice
226      *      language file, and a case in the switch statement in number_in_style,
227      *      and it should be listed in the definition of this column in install.xml.
228      */
229     public static function get_numbering_styles() {
230         $styles = array();
231         foreach (array('abc', 'ABCD', '123', 'iii', 'IIII', 'none') as $numberingoption) {
232             $styles[$numberingoption] =
233                     get_string('answernumbering' . $numberingoption, 'qtype_multichoice');
234         }
235         return $styles;
236     }
238     public function move_files($questionid, $oldcontextid, $newcontextid) {
239         parent::move_files($questionid, $oldcontextid, $newcontextid);
240         $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true);
241         $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid);
242         $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
243     }
245     protected function delete_files($questionid, $contextid) {
246         parent::delete_files($questionid, $contextid);
247         $this->delete_files_in_answers($questionid, $contextid, true);
248         $this->delete_files_in_combined_feedback($questionid, $contextid);
249         $this->delete_files_in_hints($questionid, $contextid);
250     }