Merge branch 'MDL-70065_310' of https://github.com/timhunt/moodle into MOODLE_310_STABLE
[moodle.git] / question / type / calculatedsimple / 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 simple calculated question type.
19  *
20  * @package    qtype
21  * @subpackage calculatedsimple
22  * @copyright  2009 Pierre Pichet
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/calculated/questiontype.php');
32 /**
33  * The simple calculated question type.
34  *
35  * @copyright  2009 Pierre Pichet
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class qtype_calculatedsimple extends qtype_calculated {
40     // Used by the function custom_generator_tools.
41     public $wizard_pages_number = 1;
43     public function save_question_options($question) {
44         global $CFG, $DB;
45         $context = $question->context;
47         // Make it impossible to save bad formulas anywhere.
48         $this->validate_question_data($question);
50         // Get old versions of the objects.
51         if (!$oldanswers = $DB->get_records('question_answers',
52                 array('question' => $question->id), 'id ASC')) {
53             $oldanswers = array();
54         }
56         if (!$oldoptions = $DB->get_records('question_calculated',
57                 array('question' => $question->id), 'answer ASC')) {
58             $oldoptions = array();
59         }
61         // Save the units.
62         $virtualqtype = $this->get_virtual_qtype();
63         $result = $virtualqtype->save_units($question);
64         if (isset($result->error)) {
65             return $result;
66         } else {
67             $units = &$result->units;
68         }
69         // Insert all the new answers.
70         foreach ($question->answer as $key => $answerdata) {
71             if (is_array($answerdata)) {
72                 $answerdata = $answerdata['text'];
73             }
74             if (trim($answerdata) == '') {
75                 continue;
76             }
78             // Update an existing answer if possible.
79             $answer = array_shift($oldanswers);
80             if (!$answer) {
81                 $answer = new stdClass();
82                 $answer->question = $question->id;
83                 $answer->answer   = '';
84                 $answer->feedback = '';
85                 $answer->id       = $DB->insert_record('question_answers', $answer);
86             }
88             $answer->answer   = trim($answerdata);
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             // Set up the options object.
97             if (!$options = array_shift($oldoptions)) {
98                 $options = new stdClass();
99             }
100             $options->question            = $question->id;
101             $options->answer              = $answer->id;
102             $options->tolerance           = trim($question->tolerance[$key]);
103             $options->tolerancetype       = trim($question->tolerancetype[$key]);
104             $options->correctanswerlength = trim($question->correctanswerlength[$key]);
105             $options->correctanswerformat = trim($question->correctanswerformat[$key]);
107             // Save options.
108             if (isset($options->id)) {
109                 // Reusing existing record.
110                 $DB->update_record('question_calculated', $options);
111             } else {
112                 // New options.
113                 $DB->insert_record('question_calculated', $options);
114             }
115         }
117         // Delete old answer records.
118         if (!empty($oldanswers)) {
119             foreach ($oldanswers as $oa) {
120                 $DB->delete_records('question_answers', array('id' => $oa->id));
121             }
122         }
124         // Delete old answer records.
125         if (!empty($oldoptions)) {
126             foreach ($oldoptions as $oo) {
127                 $DB->delete_records('question_calculated', array('id' => $oo->id));
128             }
129         }
131         if (isset($question->import_process) && $question->import_process) {
132             $this->import_datasets($question);
133         } else {
134             // Save datasets and datatitems from form i.e in question.
135             $question->dataset = $question->datasetdef;
137             // Save datasets.
138             $datasetdefinitions = $this->get_dataset_definitions($question->id, $question->dataset);
139             $tmpdatasets = array_flip($question->dataset);
140             $defids = array_keys($datasetdefinitions);
141             $datasetdefs = array();
142             foreach ($defids as $defid) {
143                 $datasetdef = &$datasetdefinitions[$defid];
144                 if (isset($datasetdef->id)) {
145                     if (!isset($tmpdatasets[$defid])) {
146                         // This dataset is not used any more, delete it.
147                         $DB->delete_records('question_datasets', array('question' => $question->id,
148                                 'datasetdefinition' => $datasetdef->id));
149                         $DB->delete_records('question_dataset_definitions',
150                                 array('id' => $datasetdef->id));
151                         $DB->delete_records('question_dataset_items',
152                                 array('definition' => $datasetdef->id));
153                     }
154                     // This has already been saved or just got deleted.
155                     unset($datasetdefinitions[$defid]);
156                     continue;
157                 }
158                 $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
159                 $datasetdefs[] = clone($datasetdef);
160                 $questiondataset = new stdClass();
161                 $questiondataset->question = $question->id;
162                 $questiondataset->datasetdefinition = $datasetdef->id;
163                 $DB->insert_record('question_datasets', $questiondataset);
164                 unset($datasetdefinitions[$defid]);
165             }
166             // Remove local obsolete datasets as well as relations
167             // to datasets in other categories.
168             if (!empty($datasetdefinitions)) {
169                 foreach ($datasetdefinitions as $def) {
170                     $DB->delete_records('question_datasets', array('question' => $question->id,
171                             'datasetdefinition' => $def->id));
172                     if ($def->category == 0) { // Question local dataset.
173                         $DB->delete_records('question_dataset_definitions',
174                                 array('id' => $def->id));
175                         $DB->delete_records('question_dataset_items',
176                                 array('definition' => $def->id));
177                     }
178                 }
179             }
180             $datasetdefs = $this->get_dataset_definitions($question->id, $question->dataset);
181             // Handle adding and removing of dataset items.
182             $i = 1;
183             ksort($question->definition);
184             foreach ($question->definition as $key => $defid) {
185                 $addeditem = new stdClass();
186                 $addeditem->definition = $datasetdefs[$defid]->id;
187                 $addeditem->value = $question->number[$i];
188                 $addeditem->itemnumber = ceil($i / count($datasetdefs));
189                 if (empty($question->makecopy) && $question->itemid[$i]) {
190                     // Reuse any previously used record.
191                     $addeditem->id = $question->itemid[$i];
192                     $DB->update_record('question_dataset_items', $addeditem);
193                 } else {
194                     $DB->insert_record('question_dataset_items', $addeditem);
195                 }
196                 $i++;
197             }
198             $maxnumber = -1;
199             if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber) {
200                 $maxnumber = $addeditem->itemnumber;
201                 foreach ($datasetdefs as $key => $newdef) {
202                     if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
203                         $newdef->itemcount = $maxnumber;
204                         // Save the new value for options.
205                         $DB->update_record('question_dataset_definitions', $newdef);
206                     }
207                 }
208             }
209         }
211         $this->save_hints($question);
213         // Report any problems.
214         if (!empty($question->makecopy) && !empty($question->convert)) {
215             $DB->set_field('question', 'qtype', 'calculated', array('id' => $question->id));
216         }
218         $result = $virtualqtype->save_unit_options($question);
219         if (isset($result->error)) {
220             return $result;
221         }
223         if (!empty($result->notice)) {
224             return $result;
225         }
227         return true;
228     }
230     public function finished_edit_wizard($form) {
231         return true;
232     }
234     public function wizard_pages_number() {
235         return 1;
236     }
238     public function custom_generator_tools_part($mform, $idx, $j) {
240         $minmaxgrp = array();
241         $minmaxgrp[] = $mform->createElement('float', "calcmin[{$idx}]",
242                 get_string('calcmin', 'qtype_calculated'));
243         $minmaxgrp[] = $mform->createElement('float', "calcmax[{$idx}]",
244                 get_string('calcmax', 'qtype_calculated'));
245         $mform->addGroup($minmaxgrp, 'minmaxgrp',
246                 get_string('minmax', 'qtype_calculated'), ' - ', false);
248         $precisionoptions = range(0, 10);
249         $mform->addElement('select', "calclength[{$idx}]",
250                 get_string('calclength', 'qtype_calculated'), $precisionoptions);
252         $distriboptions = array('uniform' => get_string('uniform', 'qtype_calculated'),
253                 'loguniform' => get_string('loguniform', 'qtype_calculated'));
254         $mform->addElement('hidden', "calcdistribution[{$idx}]", 'uniform');
255         $mform->setType("calcdistribution[{$idx}]", PARAM_INT);
256     }
258     public function comment_header($answers) {
259         $strheader = "";
260         $delimiter = '';
262         foreach ($answers as $key => $answer) {
263             $ans = shorten_text($answer->answer, 17, true);
264             $strheader .= $delimiter.$ans;
265             $delimiter = '<br/><br/><br/>';
266         }
267         return $strheader;
268     }
270     public function tolerance_types() {
271         return array(
272             '1'  => get_string('relative', 'qtype_numerical'),
273             '2'  => get_string('nominal', 'qtype_numerical'),
274         );
275     }
277     public function dataset_options($form, $name, $mandatory = true, $renameabledatasets = false) {
278         // Takes datasets from the parent implementation but
279         // filters options that are currently not accepted by calculated.
280         // It also determines a default selection
281         // $renameabledatasets not implemented anywhere.
282         list($options, $selected) = $this->dataset_options_from_database(
283                 $form, $name, '', 'qtype_calculated');
285         foreach ($options as $key => $whatever) {
286             if (!preg_match('~^1-~', $key) && $key != '0') {
287                 unset($options[$key]);
288             }
289         }
290         if (!$selected) {
291             if ($mandatory) {
292                 $selected =  "1-0-{$name}"; // Default.
293             } else {
294                 $selected = "0"; // Default.
295             }
296         }
297         return array($options, $selected);
298     }