Merge branch 'MDL-70248-310' of https://github.com/HuongNV13/moodle into MOODLE_310_S...
[moodle.git] / question / type / ddmarker / 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 drag-and-drop images onto images question type.
19  *
20  * @package   qtype_ddmarker
21  * @copyright 2012 The Open University
22  * @author    Jamie Pratt <me@jamiep.org>
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->dirroot . '/question/type/ddimageortext/questiontypebase.php');
31 /**
32  * Question hint for ddmarker.
33  *
34  * An extension of {@link question_hint} for questions like match and multiple
35  * choice with multile answers, where there are options for whether to show the
36  * number of parts right at each stage, and to reset the wrong parts.
37  *
38  * @copyright  2010 The Open University
39  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40  */
41 class question_hint_ddmarker extends question_hint_with_parts {
43     public $statewhichincorrect;
45     /**
46      * Constructor.
47      * @param int the hint id from the database.
48      * @param string $hint The hint text
49      * @param int the corresponding text FORMAT_... type.
50      * @param bool $shownumcorrect whether the number of right parts should be shown
51      * @param bool $clearwrong whether the wrong parts should be reset.
52      */
53     public function __construct($id, $hint, $hintformat, $shownumcorrect,
54                                                             $clearwrong, $statewhichincorrect) {
55         parent::__construct($id, $hint, $hintformat, $shownumcorrect, $clearwrong);
56         $this->statewhichincorrect = $statewhichincorrect;
57     }
59     /**
60      * Create a basic hint from a row loaded from the question_hints table in the database.
61      * @param object $row with property options as well as hint, shownumcorrect and clearwrong set.
62      * @return question_hint_ddmarker
63      */
64     public static function load_from_record($row) {
65         return new question_hint_ddmarker($row->id, $row->hint, $row->hintformat,
66                 $row->shownumcorrect, $row->clearwrong, $row->options);
67     }
69     public function adjust_display_options(question_display_options $options) {
70         parent::adjust_display_options($options);
71         $options->statewhichincorrect = $this->statewhichincorrect;
72     }
73 }
77 /**
78  * The drag-and-drop markers question type class.
79  *
80  * @copyright  2009 The Open University
81  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
82  */
83 class qtype_ddmarker extends qtype_ddtoimage_base {
85     public function save_question_options($formdata) {
86         global $DB, $USER;
87         $context = $formdata->context;
89         $options = $DB->get_record('qtype_ddmarker', array('questionid' => $formdata->id));
90         if (!$options) {
91             $options = new stdClass();
92             $options->questionid = $formdata->id;
93             $options->correctfeedback = '';
94             $options->partiallycorrectfeedback = '';
95             $options->incorrectfeedback = '';
96             $options->id = $DB->insert_record('qtype_ddmarker', $options);
97         }
99         $options->shuffleanswers = !empty($formdata->shuffleanswers);
100         $options->showmisplaced = !empty($formdata->showmisplaced);
101         $options = $this->save_combined_feedback_helper($options, $formdata, $context, true);
102         $this->save_hints($formdata, true);
103         $DB->update_record('qtype_ddmarker', $options);
104         $DB->delete_records('qtype_ddmarker_drops', array('questionid' => $formdata->id));
105         foreach (array_keys($formdata->drops) as $dropno) {
106             if ($formdata->drops[$dropno]['choice'] == 0) {
107                 continue;
108             }
109             $drop = new stdClass();
110             $drop->questionid = $formdata->id;
111             $drop->no = $dropno + 1;
112             $drop->shape = $formdata->drops[$dropno]['shape'];
113             $drop->coords = $formdata->drops[$dropno]['coords'];
114             $drop->choice = $formdata->drops[$dropno]['choice'];
116             $DB->insert_record('qtype_ddmarker_drops', $drop);
117         }
119         // An array of drag no -> drag id.
120         $olddragids = $DB->get_records_menu('qtype_ddmarker_drags',
121                                     array('questionid' => $formdata->id),
122                                     '', 'no, id');
123         foreach (array_keys($formdata->drags) as $dragno) {
124             if ($formdata->drags[$dragno]['label'] !== '') {
125                 $drag = new stdClass();
126                 $drag->questionid = $formdata->id;
127                 $drag->no = $dragno + 1;
128                 if ($formdata->drags[$dragno]['noofdrags'] == 0) {
129                     $drag->infinite = 1;
130                     $drag->noofdrags = 1;
131                 } else {
132                     $drag->infinite = 0;
133                     $drag->noofdrags = $formdata->drags[$dragno]['noofdrags'];
134                 }
135                 $drag->label = $formdata->drags[$dragno]['label'];
137                 if (isset($olddragids[$dragno + 1])) {
138                     $drag->id = $olddragids[$dragno + 1];
139                     unset($olddragids[$dragno + 1]);
140                     $DB->update_record('qtype_ddmarker_drags', $drag);
141                 } else {
142                     $drag->id = $DB->insert_record('qtype_ddmarker_drags', $drag);
143                 }
144             }
145         }
147         if (!empty($olddragids)) {
148             list($sql, $params) = $DB->get_in_or_equal(array_values($olddragids));
149             $DB->delete_records_select('qtype_ddmarker_drags', "id $sql", $params);
150         }
151         file_save_draft_area_files($formdata->bgimage, $formdata->context->id,
152                                     'qtype_ddmarker', 'bgimage', $formdata->id,
153                                     array('subdirs' => 0, 'maxbytes' => 0, 'maxfiles' => 1));
154     }
156     public function save_hints($formdata, $withparts = false) {
157         global $DB;
158         $context = $formdata->context;
160         $oldhints = $DB->get_records('question_hints',
161                 array('questionid' => $formdata->id), 'id ASC');
163         if (!empty($formdata->hint)) {
164             $numhints = max(array_keys($formdata->hint)) + 1;
165         } else {
166             $numhints = 0;
167         }
169         if ($withparts) {
170             if (!empty($formdata->hintclearwrong)) {
171                 $numclears = max(array_keys($formdata->hintclearwrong)) + 1;
172             } else {
173                 $numclears = 0;
174             }
175             if (!empty($formdata->hintshownumcorrect)) {
176                 $numshows = max(array_keys($formdata->hintshownumcorrect)) + 1;
177             } else {
178                 $numshows = 0;
179             }
180             $numhints = max($numhints, $numclears, $numshows);
181         }
183         for ($i = 0; $i < $numhints; $i += 1) {
184             if (html_is_blank($formdata->hint[$i]['text'])) {
185                 $formdata->hint[$i]['text'] = '';
186             }
188             if ($withparts) {
189                 $clearwrong = !empty($formdata->hintclearwrong[$i]);
190                 $shownumcorrect = !empty($formdata->hintshownumcorrect[$i]);
191                 $statewhichincorrect = !empty($formdata->hintoptions[$i]);
192             }
194             if (empty($formdata->hint[$i]['text']) && empty($clearwrong) &&
195                     empty($shownumcorrect) && empty($statewhichincorrect)) {
196                 continue;
197             }
199             // Update an existing hint if possible.
200             $hint = array_shift($oldhints);
201             if (!$hint) {
202                 $hint = new stdClass();
203                 $hint->questionid = $formdata->id;
204                 $hint->hint = '';
205                 $hint->id = $DB->insert_record('question_hints', $hint);
206             }
208             $hint->hint = $this->import_or_save_files($formdata->hint[$i],
209                     $context, 'question', 'hint', $hint->id);
210             $hint->hintformat = $formdata->hint[$i]['format'];
211             if ($withparts) {
212                 $hint->clearwrong = $clearwrong;
213                 $hint->shownumcorrect = $shownumcorrect;
214                 $hint->options = $statewhichincorrect;
215             }
216             $DB->update_record('question_hints', $hint);
217         }
219         // Delete any remaining old hints.
220         $fs = get_file_storage();
221         foreach ($oldhints as $oldhint) {
222             $fs->delete_area_files($context->id, 'question', 'hint', $oldhint->id);
223             $DB->delete_records('question_hints', array('id' => $oldhint->id));
224         }
225     }
227     protected function make_hint($hint) {
228         return question_hint_ddmarker::load_from_record($hint);
229     }
230     protected function make_choice($dragdata) {
231         return new qtype_ddmarker_drag_item($dragdata->label, $dragdata->no, $dragdata->infinite, $dragdata->noofdrags);
232     }
234     protected function make_place($dropdata) {
235         return new qtype_ddmarker_drop_zone($dropdata->no, $dropdata->shape, $dropdata->coords);
236     }
238     protected function initialise_combined_feedback(question_definition $question,
239                                                                 $questiondata, $withparts = false) {
240         parent::initialise_combined_feedback($question, $questiondata, $withparts);
241         $question->showmisplaced = $questiondata->options->showmisplaced;
242     }
244     public function move_files($questionid, $oldcontextid, $newcontextid) {
245         global $DB;
246         $fs = get_file_storage();
248         parent::move_files($questionid, $oldcontextid, $newcontextid);
249         $fs->move_area_files_to_new_context($oldcontextid,
250                                     $newcontextid, 'qtype_ddmarker', 'bgimage', $questionid);
252         $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid);
253         $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
254     }
256     /**
257      * Delete all the files belonging to this question.
258      * @param int $questionid the question being deleted.
259      * @param int $contextid the context the question is in.
260      */
262     protected function delete_files($questionid, $contextid) {
263         global $DB;
264         $fs = get_file_storage();
266         parent::delete_files($questionid, $contextid);
268         $this->delete_files_in_combined_feedback($questionid, $contextid);
269         $this->delete_files_in_hints($questionid, $contextid);
270     }
272     public function export_to_xml($question, qformat_xml $format, $extra = null) {
273         $fs = get_file_storage();
274         $contextid = $question->contextid;
275         $output = '';
277         if ($question->options->shuffleanswers) {
278             $output .= "    <shuffleanswers/>\n";
279         }
280         if ($question->options->showmisplaced) {
281             $output .= "    <showmisplaced/>\n";
282         }
283         $output .= $format->write_combined_feedback($question->options,
284                                                     $question->id,
285                                                     $question->contextid);
286         $files = $fs->get_area_files($contextid, 'qtype_ddmarker', 'bgimage', $question->id);
287         $output .= "    ".$this->write_files($files, 2)."\n";;
289         foreach ($question->options->drags as $drag) {
290             $files =
291                     $fs->get_area_files($contextid, 'qtype_ddmarker', 'dragimage', $drag->id);
292             $output .= "    <drag>\n";
293             $output .= "      <no>{$drag->no}</no>\n";
294             $output .= $format->writetext($drag->label, 3);
295             if ($drag->infinite) {
296                 $output .= "      <infinite/>\n";
297             }
298             $output .= "      <noofdrags>{$drag->noofdrags}</noofdrags>\n";
299             $output .= "    </drag>\n";
300         }
301         foreach ($question->options->drops as $drop) {
302             $output .= "    <drop>\n";
303             $output .= "      <no>{$drop->no}</no>\n";
304             $output .= "      <shape>{$drop->shape}</shape>\n";
305             $output .= "      <coords>{$drop->coords}</coords>\n";
306             $output .= "      <choice>{$drop->choice}</choice>\n";
307             $output .= "    </drop>\n";
308         }
310         return $output;
311     }
313     public function import_from_xml($data, $question, qformat_xml $format, $extra=null) {
314         if (!isset($data['@']['type']) || $data['@']['type'] != 'ddmarker') {
315             return false;
316         }
318         $question = $format->import_headers($data);
319         $question->qtype = 'ddmarker';
321         $question->shuffleanswers = array_key_exists('shuffleanswers',
322                                                     $format->getpath($data, array('#'), array()));
323         $question->showmisplaced = array_key_exists('showmisplaced',
324                                                     $format->getpath($data, array('#'), array()));
326         $filexml = $format->getpath($data, array('#', 'file'), array());
327         $question->bgimage = $format->import_files_as_draft($filexml);
328         $drags = $data['#']['drag'];
329         $question->drags = array();
331         foreach ($drags as $dragxml) {
332             $dragno = $format->getpath($dragxml, array('#', 'no', 0, '#'), 0);
333             $dragindex = $dragno - 1;
334             $question->drags[$dragindex] = array();
335             $question->drags[$dragindex]['label'] =
336                         $format->getpath($dragxml, array('#', 'text', 0, '#'), '', true);
337             if (array_key_exists('infinite', $dragxml['#'])) {
338                 $question->drags[$dragindex]['noofdrags'] = 0; // Means infinite in the form.
339             } else {
340                 // Defaults to 1 if 'noofdrags' not set.
341                 $question->drags[$dragindex]['noofdrags'] = $format->getpath($dragxml, array('#', 'noofdrags', 0, '#'), 1);
342             }
343         }
345         $drops = $data['#']['drop'];
346         $question->drops = array();
347         foreach ($drops as $dropxml) {
348             $dropno = $format->getpath($dropxml, array('#', 'no', 0, '#'), 0);
349             $dropindex = $dropno - 1;
350             $question->drops[$dropindex] = array();
351             $question->drops[$dropindex]['choice'] =
352                         $format->getpath($dropxml, array('#', 'choice', 0, '#'), 0);
353             $question->drops[$dropindex]['shape'] =
354                         $format->getpath($dropxml, array('#', 'shape', 0, '#'), '');
355             $question->drops[$dropindex]['coords'] =
356                         $format->getpath($dropxml, array('#', 'coords', 0, '#'), '');
357         }
359         $format->import_combined_feedback($question, $data, true);
360         $format->import_hints($question, $data, true, true,
361                 $format->get_format($question->questiontextformat));
363         return $question;
364     }
366     public function get_random_guess_score($questiondata) {
367         return null;
368     }