Merge branch 'MDL-67513-m310' of https://github.com/NeillM/moodle into MOODLE_310_STABLE
[moodle.git] / question / type / randomsamatch / 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  * Question type class for the randomsamatch question type.
19  *
20  * @package    qtype_randomsamatch
21  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
26 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->dirroot . '/question/type/questiontypebase.php');
29 require_once($CFG->dirroot . '/question/type/questionbase.php');
30 require_once($CFG->dirroot . '/question/type/numerical/question.php');
32 /**
33  * The randomsamatch question type class.
34  *
35  * TODO: Make sure short answer questions chosen by a randomsamatch question
36  * can not also be used by a random question
37  *
38  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
39  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  */
41 class qtype_randomsamatch extends question_type {
42     /**
43      * Cache of available shortanswer question ids from a particular category.
44      * @var array two-dimensional array. The first key is a category id, the
45      * second key is wether subcategories should be included.
46      */
47     private $availablesaquestionsbycategory = array();
48     const MAX_SUBQUESTIONS = 10;
50     public function is_usable_by_random() {
51         return false;
52     }
54     public function get_question_options($question) {
55         global $DB;
56         parent::get_question_options($question);
57         $question->options = $DB->get_record('qtype_randomsamatch_options',
58                 array('questionid' => $question->id));
60         return true;
62     }
64     public function save_question_options($question) {
65         global $DB;
67         if (2 > $question->choose) {
68             $result = new stdClass();
69             $result->error = "At least two shortanswer questions need to be chosen!";
70             return $result;
71         }
73         $context = $question->context;
75         // Save the question options.
76         $options = $DB->get_record('qtype_randomsamatch_options', array('questionid' => $question->id));
77         if (!$options) {
78             $options = new stdClass();
79             $options->questionid = $question->id;
80             $options->correctfeedback = '';
81             $options->partiallycorrectfeedback = '';
82             $options->incorrectfeedback = '';
83             $options->id = $DB->insert_record('qtype_randomsamatch_options', $options);
84         }
86         $options->choose = $question->choose;
87         $options->subcats = $question->subcats;
88         $options = $this->save_combined_feedback_helper($options, $question, $context, true);
89         $DB->update_record('qtype_randomsamatch_options', $options);
91         $this->save_hints($question, true);
93         return true;
94     }
96     protected function make_hint($hint) {
97         return question_hint_with_parts::load_from_record($hint);
98     }
100     public function delete_question($questionid, $contextid) {
101         global $DB;
102         $DB->delete_records('qtype_randomsamatch_options', array('questionid' => $questionid));
104         parent::delete_question($questionid, $contextid);
105     }
107     public function move_files($questionid, $oldcontextid, $newcontextid) {
108         parent::move_files($questionid, $oldcontextid, $newcontextid);
110         $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid);
111         $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
112     }
114     protected function delete_files($questionid, $contextid) {
115         parent::delete_files($questionid, $contextid);
117         $this->delete_files_in_combined_feedback($questionid, $contextid);
118         $this->delete_files_in_hints($questionid, $contextid);
119     }
121     protected function initialise_question_instance(question_definition $question, $questiondata) {
122         parent::initialise_question_instance($question, $questiondata);
123         $availablesaquestions = $this->get_available_saquestions_from_category(
124                 $question->category, $questiondata->options->subcats);
125         $question->shufflestems = false;
126         $question->stems = array();
127         $question->choices = array();
128         $question->right = array();
129         $this->initialise_combined_feedback($question, $questiondata);
130         $question->questionsloader = new qtype_randomsamatch_question_loader(
131                 $availablesaquestions, $questiondata->options->choose);
132     }
134     public function can_analyse_responses() {
135         return false;
136     }
138     /**
139      * Get all the usable shortanswer questions from a particular question category.
140      *
141      * @param integer $categoryid the id of a question category.
142      * @param bool $subcategories whether to include questions from subcategories.
143      * @return array of question records.
144      */
145     public function get_available_saquestions_from_category($categoryid, $subcategories) {
146         if (isset($this->availablesaquestionsbycategory[$categoryid][$subcategories])) {
147             return $this->availablesaquestionsbycategory[$categoryid][$subcategories];
148         }
150         if ($subcategories) {
151             $categoryids = question_categorylist($categoryid);
152         } else {
153             $categoryids = array($categoryid);
154         }
156         $questionids = question_bank::get_finder()->get_questions_from_categories(
157                 $categoryids, "qtype = 'shortanswer'");
158         $this->availablesaquestionsbycategory[$categoryid][$subcategories] = $questionids;
159         return $questionids;
160     }
162     /**
163      * @param object $question
164      * @return mixed either a integer score out of 1 that the average random
165      * guess by a student might give or an empty string which means will not
166      * calculate.
167      */
168     public function get_random_guess_score($question) {
169         return 1/$question->options->choose;
170     }
172     /**
173      * Defines the table which extends the question table. This allows the base questiontype
174      * to automatically save, backup and restore the extra fields.
175      *
176      * @return an array with the table name (first) and then the column names (apart from id and questionid)
177      */
178     public function extra_question_fields() {
179         return array('qtype_randomsamatch_options',
180                      'choose',        // Number of shortanswer questions to choose.
181                      'subcats',       // Questions can be choosen from subcategories.
182                      );
183     }
185     /**
186      * Imports the question from Moodle XML format.
187      *
188      * @param array $xml structure containing the XML data
189      * @param object $fromform question object to fill: ignored by this function (assumed to be null)
190      * @param qformat_xml $format format class exporting the question
191      * @param object $extra extra information (not required for importing this question in this format)
192      * @return object question object
193      */
194     public function import_from_xml($xml, $fromform, qformat_xml $format, $extra=null) {
195         // Return if data type is not our own one.
196         if (!isset($xml['@']['type']) || $xml['@']['type'] != $this->name()) {
197             return false;
198         }
200         // Import the common question headers and set the corresponding field.
201         $fromform = $format->import_headers($xml);
202         $fromform->qtype = $this->name();
203         $format->import_combined_feedback($fromform, $xml, true);
204         $format->import_hints($fromform, $xml, true);
206         $extras = $this->extra_question_fields();
207         array_shift($extras);
208         foreach ($extras as $extra) {
209             $fromform->$extra = $format->getpath($xml, array('#', $extra, 0, '#'), '', true);
210         }
212         return $fromform;
213     }
215     /**
216      * Exports the question to Moodle XML format.
217      *
218      * @param object $question question to be exported into XML format
219      * @param qformat_xml $format format class exporting the question
220      * @param object $extra extra information (not required for exporting this question in this format)
221      * @return string containing the question data in XML format
222      */
223     public function export_to_xml($question, qformat_xml $format, $extra=null) {
224         $expout = '';
225         $expout .= $format->write_combined_feedback($question->options,
226                                                     $question->id,
227                                                     $question->contextid);
228         $extraquestionfields = $this->extra_question_fields();
229         array_shift($extraquestionfields);
230         foreach ($extraquestionfields as $extra) {
231             $expout .= "    <{$extra}>" . $question->options->$extra . "</{$extra}>\n";
232         }
233         return $expout;
234     }