Merge branch 'MDL-38792' of https://github.com/ppichet/moodle
[moodle.git] / question / type / calculatedmulti / edit_calculatedmulti_form.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  * Defines the editing form for calculated multiple-choice questions.
19  *
20  * @package    qtype
21  * @subpackage calculatedmulti
22  * @copyright  2007 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();
30 /**
31  * Calculated multiple-choice question editing form.
32  *
33  * @copyright  2007 Jamie Pratt me@jamiep.org
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class qtype_calculatedmulti_edit_form extends question_edit_form {
37     /**
38      * Handle to the question type for this question.
39      *
40      * @var question_calculatedmulti_qtype
41      */
42     public $qtypeobj;
43     public $questiondisplay;
44     public $initialname = '';
45     public $reload = false;
47     public function __construct($submiturl, $question, $category,
48             $contexts, $formeditable = true) {
49         $this->question = $question;
50         $this->qtypeobj = question_bank::get_qtype('calculatedmulti');
51         $this->reload = optional_param('reload', false, PARAM_BOOL);
52         if (!$this->reload) {
53             // Use database data as this is first pass.
54             if (isset($this->question->id)) {
55                 // Remove prefix #{..}# if exists.
56                 $this->initialname = $question->name;
57                 $regs= array();
58                 if (preg_match('~#\{([^[:space:]]*)#~', $question->name , $regs)) {
59                     $question->name = str_replace($regs[0], '', $question->name);
60                 };
61             }
62         }
63         parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
64     }
66     public function get_per_answer_fields($mform, $label, $gradeoptions,
67             &$repeatedoptions, &$answersoption) {
68         $repeated = array();
69         $answeroptions = array();
70         $answeroptions[] = $mform->createElement('text', 'answer',
71                 $label, array('size' => 50));
72         $answeroptions[] = $mform->createElement('select', 'fraction',
73                 get_string('grade'), $gradeoptions);
74         $repeated[] = $mform->createElement('group', 'answeroptions',
75                  $label, $answeroptions, null, false);
77         // Added answeroptions help button in definition_inner() after called to add_per_answer_fields.
79         $repeatedoptions['answer']['type'] = PARAM_RAW;
80         $repeatedoptions['fraction']['default'] = 0;
81         $answersoption = 'answers';
83         $mform->setType('answer', PARAM_NOTAGS);
85         $repeated[] = $mform->createElement('hidden', 'tolerance');
86         $repeated[] = $mform->createElement('hidden', 'tolerancetype', 1);
87         $repeatedoptions['tolerance']['type'] = PARAM_FLOAT;
88         $repeatedoptions['tolerance']['default'] = 0.01;
89         $repeatedoptions['tolerancetype']['type'] = PARAM_INT;
91         // Create display group.
92         $answerdisplay = array();
93         $answerdisplay[] =  $mform->createElement('select', 'correctanswerlength',
94                 get_string('answerdisplay', 'qtype_calculated'), range(0, 9));
95         $repeatedoptions['correctanswerlength']['default'] = 2;
97         $answerlengthformats = array(
98             '1' => get_string('decimalformat', 'qtype_numerical'),
99             '2' => get_string('significantfiguresformat', 'qtype_calculated')
100         );
101         $answerdisplay[] = $mform->createElement('select', 'correctanswerformat',
102                 get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
103         $repeated[] = $mform->createElement('group', 'answerdisplay',
104                  get_string('answerdisplay', 'qtype_calculated'), $answerdisplay, null, false);
106         // Add feedback.
107         $repeated[] = $mform->createElement('editor', 'feedback',
108                 get_string('feedback', 'question'), null, $this->editoroptions);
110         return $repeated;
111     }
113     protected function definition_inner($mform) {
115         $label = get_string('sharedwildcards', 'qtype_calculated');
116         $mform->addElement('hidden', 'initialcategory', 1);
117         $mform->addElement('hidden', 'reload', 1);
118         $mform->setType('initialcategory', PARAM_INT);
119         $mform->setType('reload', PARAM_BOOL);
121         $html2 = '';
122         $mform->insertElementBefore(
123                 $mform->createElement('static', 'listcategory', $label, $html2), 'name');
124         if (isset($this->question->id)) {
125             $mform->insertElementBefore($mform->createElement('static', 'initialname',
126                     get_string('questionstoredname', 'qtype_calculated'),
127                     $this->initialname), 'name');
128         };
129         $addfieldsname = 'updatecategory';
130         $addstring = get_string('updatecategory', 'qtype_calculated');
131         $mform->registerNoSubmitButton($addfieldsname);
132         $this->editasmultichoice = 1;
134         $mform->insertElementBefore(
135                 $mform->createElement('submit', $addfieldsname, $addstring), 'listcategory');
136         $mform->registerNoSubmitButton('createoptionbutton');
137         $mform->addElement('hidden', 'multichoice', $this->editasmultichoice);
138         $mform->setType('multichoice', PARAM_INT);
140         $menu = array(get_string('answersingleno', 'qtype_multichoice'),
141                 get_string('answersingleyes', 'qtype_multichoice'));
142         $mform->addElement('select', 'single',
143                 get_string('answerhowmany', 'qtype_multichoice'), $menu);
144         $mform->setDefault('single', 1);
146         $mform->addElement('advcheckbox', 'shuffleanswers',
147                 get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0, 1));
148         $mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
149         $mform->setDefault('shuffleanswers', 1);
151         $numberingoptions = question_bank::get_qtype('multichoice')->get_numbering_styles();
152         $mform->addElement('select', 'answernumbering',
153                 get_string('answernumbering', 'qtype_multichoice'), $numberingoptions);
154         $mform->setDefault('answernumbering', 'abc');
156         $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
157                 question_bank::fraction_options_full(), max(5, QUESTION_NUMANS_START));
158         $mform->addHelpButton('answeroptions[0]', 'answeroptions', 'qtype_calculatedmulti');
160         $repeated = array();
161         $nounits = optional_param('nounits', 1, PARAM_INT);
162         $mform->addElement('hidden', 'nounits', $nounits);
163         $mform->setType('nounits', PARAM_INT);
164         $mform->setConstants(array('nounits'=>$nounits));
165         for ($i = 0; $i < $nounits; $i++) {
166             $mform->addElement('hidden', 'unit'."[$i]",
167                     optional_param("unit[$i]", '', PARAM_NOTAGS));
168             $mform->setType('unit'."[$i]", PARAM_NOTAGS);
169             $mform->addElement('hidden', 'multiplier'."[$i]",
170                     optional_param("multiplier[$i]", '', PARAM_FLOAT));
171             $mform->setType("multiplier[$i]", PARAM_FLOAT);
172         }
174         $this->add_combined_feedback_fields(true);
175         $mform->disabledIf('shownumcorrect', 'single', 'eq', 1);
177         $this->add_interactive_settings(true, true);
179         // Hidden elements.
180         $mform->addElement('hidden', 'synchronize', '');
181         $mform->setType('synchronize', PARAM_INT);
182         if (isset($this->question->options) && isset($this->question->options->synchronize)) {
183             $mform->setDefault('synchronize', $this->question->options->synchronize);
184         } else {
185             $mform->setDefault('synchronize', 0);
186         }
187         $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
188         $mform->setType('wizard', PARAM_ALPHA);
189     }
191     public function data_preprocessing($question) {
192         $question = parent::data_preprocessing($question);
193         $question = $this->data_preprocessing_answers($question, false);
194         $question = $this->data_preprocessing_combined_feedback($question, true);
195         $question = $this->data_preprocessing_hints($question, true, true);
197         if (isset($question->options)) {
198             $question->synchronize     = $question->options->synchronize;
199             $question->single          = $question->options->single;
200             $question->answernumbering = $question->options->answernumbering;
201             $question->shuffleanswers  = $question->options->shuffleanswers;
202         }
204         return $question;
205     }
207     protected function data_preprocessing_answers($question, $withanswerfiles = false) {
208         $question = parent::data_preprocessing_answers($question, $withanswerfiles);
209         if (empty($question->options->answers)) {
210             return $question;
211         }
213         $key = 0;
214         foreach ($question->options->answers as $answer) {
215             // See comment in the parent method about this hack.
216             unset($this->_form->_defaultValues["tolerance[$key]"]);
217             unset($this->_form->_defaultValues["tolerancetype[$key]"]);
218             unset($this->_form->_defaultValues["correctanswerlength[$key]"]);
219             unset($this->_form->_defaultValues["correctanswerformat[$key]"]);
221             $question->tolerance[$key]           = $answer->tolerance;
222             $question->tolerancetype[$key]       = $answer->tolerancetype;
223             $question->correctanswerlength[$key] = $answer->correctanswerlength;
224             $question->correctanswerformat[$key] = $answer->correctanswerformat;
225             $key++;
226         }
228         return $question;
229     }
231     public function validation($data, $files) {
232         $errors = parent::validation($data, $files);
234         // Verifying for errors in {=...} in question text.
235         $qtext = '';
236         $qtextremaining = $data['questiontext']['text'];
237         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
238         foreach ($possibledatasets as $name => $value) {
239             $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
240         }
242         while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
243             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
244             $qtext = $qtext.$qtextsplits[0];
245             $qtextremaining = $qtextsplits[1];
246             if (!empty($regs1[1]) && $formulaerrors =
247                     qtype_calculated_find_formula_errors($regs1[1])) {
248                 if (!isset($errors['questiontext'])) {
249                     $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
250                 } else {
251                     $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
252                 }
253             }
254         }
255         $answers = $data['answer'];
256         $answercount = 0;
257         $maxgrade = false;
258         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
259         $mandatorydatasets = array();
260         foreach ($answers as $key => $answer) {
261             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
262         }
263         if (count($mandatorydatasets) == 0) {
264             foreach ($answers as $key => $answer) {
265                 $errors['answeroptions['.$key.']'] =
266                         get_string('atleastonewildcard', 'qtype_calculated');
267             }
268         }
269         $totalfraction = 0;
270         $maxfraction = -1;
271         foreach ($answers as $key => $answer) {
272             $trimmedanswer = trim($answer);
273             $fraction = (float) $data['fraction'][$key];
274             if (empty($trimmedanswer) && $trimmedanswer != '0' && empty($fraction)) {
275                 continue;
276             }
277             if (empty($trimmedanswer)) {
278                 $errors['answeroptions['.$key.']'] = get_string('errgradesetanswerblank', 'qtype_multichoice');
279             }
280             if ($trimmedanswer != '' || $answercount == 0) {
281                 // Verifying for errors in {=...} in answer text.
282                 $qanswer = '';
283                 $qanswerremaining =  $trimmedanswer;
284                 $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
285                 foreach ($possibledatasets as $name => $value) {
286                     $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
287                 }
289                 while (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
290                     $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
291                     $qanswer = $qanswer . $qanswersplits[0];
292                     $qanswerremaining = $qanswersplits[1];
293                     if (!empty($regs1[1]) && $formulaerrors =
294                             qtype_calculated_find_formula_errors($regs1[1])) {
295                         if (!isset($errors['answeroptions['.$key.']'])) {
296                             $errors['answeroptions['.$key.']'] = $formulaerrors.':'.$regs1[1];
297                         } else {
298                             $errors['answeroptions['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
299                         }
300                     }
301                 }
302             }
303             if ($trimmedanswer != '') {
304                 if ('2' == $data['correctanswerformat'][$key] &&
305                         '0' == $data['correctanswerlength'][$key]) {
306                     $errors['correctanswerlength['.$key.']'] =
307                             get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
308                 }
309                 if (!is_numeric($data['tolerance'][$key])) {
310                     $errors['tolerance['.$key.']'] =
311                             get_string('xmustbenumeric', 'qtype_numerical',
312                                 get_string('acceptederror', 'qtype_numerical'));
313                 }
314                 if ($data['fraction'][$key] > 0) {
315                     $totalfraction += $data['fraction'][$key];
316                 }
317                 if ($data['fraction'][$key] > $maxfraction) {
318                     $maxfraction = $data['fraction'][$key];
319                 }
321                 $answercount++;
322             }
323         }
324         if ($answercount == 0) {
325             $errors['answeroptions[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
326             $errors['answeroptions[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
327         } else if ($answercount == 1) {
328             $errors['answeroptions[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
330         }
331         // Perform sanity checks on fractional grades.
332         if ($data['single']== 1 ) {
333             if ($maxfraction != 1) {
334                 $errors['answeroptions[0]'] = get_string('errfractionsnomax', 'qtype_multichoice',
335                         $maxfraction * 100);
336             }
337         } else {
338             $totalfraction = round($totalfraction, 2);
339             if ($totalfraction != 1) {
340                 $totalfraction = $totalfraction * 100;
341                 $errors['answeroptions[0]'] =
342                         get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
343             }
344         }
345         return $errors;
346     }
348     public function qtype() {
349         return 'calculatedmulti';
350     }