Merge branch 'MDL-31680' of https://github.com/ppichet/moodle
[moodle.git] / question / type / calculated / datasetitems_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 the calculated question data set items.
19  *
20  * @package    qtype
21  * @subpackage calculated
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();
29 require_once($CFG->dirroot . '/question/type/edit_question_form.php');
32 /**
33  * Calculated question data set items editing form definition.
34  *
35  * @copyright  2007 Jamie Pratt me@jamiep.org
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class question_dataset_dependent_items_form extends question_wizard_form {
39     /**
40      * Question object with options and answers already loaded by get_question_options
41      * Be careful how you use this it is needed sometimes to set up the structure of the
42      * form in definition_inner but data is always loaded into the form with set_defaults.
43      *
44      * @var object
45      */
46     public $question;
47     /**
48      * Reference to question type object
49      *
50      * @var question_dataset_dependent_questiontype
51      */
52     public $qtypeobj;
54     public $datasetdefs;
56     public $maxnumber = -1;
58     public $regenerate;
60     public $noofitems;
62     public $outsidelimit = false;
64     public $commentanswers = array();
66     /**
67      * Add question-type specific form fields.
68      *
69      * @param MoodleQuickForm $mform the form being built.
70      */
71     public function __construct($submiturl, $question, $regenerate) {
72         global $SESSION, $CFG, $DB;
73         $this->regenerate = $regenerate;
74         $this->question = $question;
75         $this->qtypeobj = question_bank::get_qtype($this->question->qtype);
76         // Validate the question category.
77         if (!$category = $DB->get_record('question_categories',
78                 array('id' => $question->category))) {
79             print_error('categorydoesnotexist', 'question', $returnurl);
80         }
81         $this->category = $category;
82         $this->categorycontext = context::instance_by_id($category->contextid);
83         // Get the dataset defintions for this question.
84         if (empty($question->id)) {
85             $this->datasetdefs = $this->qtypeobj->get_dataset_definitions(
86                     $question->id, $SESSION->calculated->definitionform->dataset);
87         } else {
88             if (empty($question->options)) {
89                 $this->get_question_options($question);
90             }
91             $this->datasetdefs = $this->qtypeobj->get_dataset_definitions(
92                     $question->id, array());
93         }
95         foreach ($this->datasetdefs as $datasetdef) {
96             // Get maxnumber.
97             if ($this->maxnumber == -1 || $datasetdef->itemcount < $this->maxnumber) {
98                 $this->maxnumber = $datasetdef->itemcount;
99             }
100         }
101         foreach ($this->datasetdefs as $defid => $datasetdef) {
102             if (isset($datasetdef->id)) {
103                 $this->datasetdefs[$defid]->items =
104                         $this->qtypeobj->get_database_dataset_items($datasetdef->id);
105             }
106         }
107         parent::__construct($submiturl);
108     }
110     protected function definition() {
111         $labelsharedwildcard = get_string("sharedwildcard", "qtype_calculated");
112         $mform =& $this->_form;
113         $mform->setDisableShortforms();
115         $strquestionlabel = $this->qtypeobj->comment_header($this->question);
116         if ($this->maxnumber != -1 ) {
117             $this->noofitems = $this->maxnumber;
118         } else {
119             $this->noofitems = 0;
120         }
121         $label = get_string("sharedwildcards", "qtype_calculated");
123         $html2 = $this->qtypeobj->print_dataset_definitions_category_shared(
124                 $this->question, $this->datasetdefs);
125         $mform->addElement('static', 'listcategory', $label, $html2);
126         // ...----------------------------------------------------------------------.
127         $mform->addElement('submit', 'updatedatasets',
128                 get_string('updatedatasetparam', 'qtype_calculated'));
129         $mform->registerNoSubmitButton('updatedatasets');
130         $mform->addElement('header', 'additemhdr',
131                 get_string('itemtoadd', 'qtype_calculated'));
132         $idx = 1;
133         $data = array();
134         $j = (($this->noofitems) * count($this->datasetdefs))+1;
135         foreach ($this->datasetdefs as $defkey => $datasetdef) {
136             if ($datasetdef->category |= 0 ) {
137                 $name = get_string('sharedwildcard', 'qtype_calculated', $datasetdef->name);
138             } else {
139                 $name = get_string('wildcard', 'qtype_calculated', $datasetdef->name);
140             }
141             $mform->addElement('text', "number[$j]", $name);
142             $mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
143             $this->qtypeobj->custom_generator_tools_part($mform, $idx, $j);
144             $idx++;
145             $mform->addElement('hidden', "definition[$j]");
146             $mform->setType("definition[$j]", PARAM_RAW);
147             $mform->addElement('hidden', "itemid[$j]");
148             $mform->setType("itemid[$j]", PARAM_RAW);
149             $mform->addElement('static', "divider[$j]", '', '<hr />');
150             $mform->setType("divider[$j]", PARAM_RAW);
151             $j++;
152         }
154         $mform->addElement('header', 'updateanswershdr',
155                 get_string('answerstoleranceparam', 'qtype_calculated'));
156         $mform->addElement('submit', 'updateanswers',
157                 get_string('updatetolerancesparam', 'qtype_calculated'));
158         $mform->setAdvanced('updateanswers', true);
159         $mform->registerNoSubmitButton('updateanswers');
161         $answers = fullclone($this->question->options->answers);
162         $key1 =1;
163         foreach ($answers as $key => $answer) {
164             $ans = shorten_text($answer->answer, 17, true);
165             if ($ans === '*') {
166                 $mform->addElement('static',
167                         'answercomment[' . ($this->noofitems+$key1) . ']', $ans);
168                 $mform->addElement('hidden', 'tolerance['.$key.']', '');
169                 $mform->setType('tolerance['.$key.']', PARAM_RAW);
170                 $mform->setAdvanced('tolerance['.$key.']', true);
171                 $mform->addElement('hidden', 'tolerancetype['.$key.']', '');
172                 $mform->setType('tolerancetype['.$key.']', PARAM_RAW);
173                 $mform->setAdvanced('tolerancetype['.$key.']', true);
174                 $mform->addElement('hidden', 'correctanswerlength['.$key.']', '');
175                 $mform->setType('correctanswerlength['.$key.']', PARAM_RAW);
176                 $mform->setAdvanced('correctanswerlength['.$key.']', true);
177                 $mform->addElement('hidden', 'correctanswerformat['.$key.']', '');
178                 $mform->setType('correctanswerformat['.$key.']', PARAM_RAW);
179                 $mform->setAdvanced('correctanswerformat['.$key.']', true);
180             } else if ( $ans !== '' ) {
181                 $mform->addElement('static', 'answercomment[' . ($this->noofitems+$key1) . ']',
182                         $ans);
183                 $mform->addElement('text', 'tolerance['.$key.']',
184                         get_string('tolerance', 'qtype_calculated'));
185                 $mform->setType('tolerance['.$key.']', PARAM_RAW);
186                 $mform->setAdvanced('tolerance['.$key.']', true);
187                 $mform->addElement('select', 'tolerancetype['.$key.']',
188                         get_string('tolerancetype', 'qtype_numerical'),
189                         $this->qtypeobj->tolerance_types());
190                 $mform->setAdvanced('tolerancetype['.$key.']', true);
192                 $mform->addElement('select', 'correctanswerlength['.$key.']',
193                         get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
194                 $mform->setAdvanced('correctanswerlength['.$key.']', true);
196                 $answerlengthformats = array(
197                     '1' => get_string('decimalformat', 'qtype_numerical'),
198                     '2' => get_string('significantfiguresformat', 'qtype_calculated')
199                 );
200                 $mform->addElement('select', 'correctanswerformat['.$key.']',
201                         get_string('correctanswershowsformat', 'qtype_calculated'),
202                         $answerlengthformats);
203                 $mform->setAdvanced('correctanswerformat['.$key.']', true);
204                 $mform->addElement('static', 'dividertolerance', '', '<hr />');
205                 $mform->setAdvanced('dividertolerance', true);
206             }
207             $key1++;
208         }
210         $addremoveoptions = array();
211         $addremoveoptions['1']='1';
212         for ($i=10; $i<=100; $i+=10) {
213              $addremoveoptions["$i"]="$i";
214         }
215         $showoptions = Array();
216         $showoptions['1']='1';
217         $showoptions['2']='2';
218         $showoptions['5']='5';
219         for ($i=10; $i<=100; $i+=10) {
220              $showoptions["$i"]="$i";
221         }
222         $mform->addElement('header', 'addhdr', get_string('add', 'moodle'));
223         $mform->closeHeaderBefore('addhdr');
225         if ($this->qtypeobj->supports_dataset_item_generation()) {
226             $radiogrp = array();
227             $radiogrp[] =& $mform->createElement('radio', 'nextpageparam[forceregeneration]',
228                     null, get_string('reuseifpossible', 'qtype_calculated'), 0);
229             $radiogrp[] =& $mform->createElement('radio', 'nextpageparam[forceregeneration]',
230                     null, get_string('forceregenerationshared', 'qtype_calculated'), 1);
231             $radiogrp[] =& $mform->createElement('radio', 'nextpageparam[forceregeneration]',
232                     null, get_string('forceregenerationall', 'qtype_calculated'), 2);
233             $mform->addGroup($radiogrp, 'forceregenerationgrp',
234                     get_string('nextitemtoadd', 'qtype_calculated'), "<br/>", false);
235         }
237         $mform->addElement('submit', 'getnextbutton', get_string('getnextnow', 'qtype_calculated'));
238         $mform->addElement('static', "dividera", '', '<hr />');
239         $addgrp = array();
240         $addgrp[] =& $mform->createElement('submit', 'addbutton', get_string('add', 'moodle'));
241         $addgrp[] =& $mform->createElement('select', "selectadd",
242                 get_string('additem', 'qtype_calculated'), $addremoveoptions);
243         $addgrp[] = & $mform->createElement('static', "stat", "Items",
244                 get_string('newsetwildcardvalues', 'qtype_calculatedsimple'));
245         $mform->addGroup($addgrp, 'addgrp', get_string('additem', 'qtype_calculated'), ' ', false);
246         $mform->addElement('static', "divideradd", '', '');
247         if ($this->noofitems > 0) {
248             $mform->addElement('header', 'deleteitemhdr', get_string('delete', 'moodle'));
249             $deletegrp = array();
250             $deletegrp[] = $mform->createElement('submit', 'deletebutton',
251                     get_string('delete', 'moodle'));
252             $deletegrp[] = $mform->createElement('select', 'selectdelete',
253                     get_string('deleteitem', 'qtype_calculated')."1", $addremoveoptions);
254             $deletegrp[] = $mform->createElement('static', "stat", "Items",
255                     get_string('setwildcardvalues', 'qtype_calculatedsimple'));
256             $mform->addGroup($deletegrp, 'deletegrp', '', '   ', false);
257         } else {
258             $mform->addElement('static', 'warning', '', '<span class="error">' .
259                     get_string('youmustaddatleastoneitem', 'qtype_calculated').'</span>');
260         }
262         $addgrp1 = array();
263         $addgrp1[] = $mform->createElement('submit', 'showbutton',
264                 get_string('showitems', 'qtype_calculated'));
265         $addgrp1[] = $mform->createElement('select', "selectshow", '' , $showoptions);
266         $addgrp1[] = $mform->createElement('static', "stat", '',
267                 get_string('setwildcardvalues', 'qtype_calculated'));
268         $mform->addGroup($addgrp1, 'addgrp1', '', '   ', false);
269         $mform->registerNoSubmitButton('showbutton');
270         $mform->closeHeaderBefore('addgrp1');
271         // ...----------------------------------------------------------------------.
272         $j = $this->noofitems * count($this->datasetdefs);
273         $k = optional_param('selectshow', 1, PARAM_INT);
274         for ($i = $this->noofitems; $i >= 1; $i--) {
275             if ($k > 0) {
276                 $mform->addElement('header', 'setnoheader' . $i, "<b>" .
277                         get_string('setno', 'qtype_calculated', $i)."</b>&nbsp;&nbsp;");
278             }
279             foreach ($this->datasetdefs as $defkey => $datasetdef) {
280                 if ($k > 0) {
281                     if ($datasetdef->category == 0 ) {
282                         $mform->addElement('text', "number[$j]",
283                                 get_string('wildcard', 'qtype_calculated', $datasetdef->name));
284                     } else {
285                         $mform->addElement('text', "number[$j]", get_string(
286                                 'sharedwildcard', 'qtype_calculated', $datasetdef->name));
287                     }
289                 } else {
290                     $mform->addElement('hidden', "number[$j]" , '');
291                 }
292                 $mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
293                 $mform->addElement('hidden', "itemid[$j]");
294                 $mform->setType("itemid[$j]", PARAM_INT);
296                 $mform->addElement('hidden', "definition[$j]");
297                 $mform->setType("definition[$j]", PARAM_NOTAGS);
298                 $data[$datasetdef->name] =$datasetdef->items[$i]->value;
300                 $j--;
301             }
302             if ('' != $strquestionlabel && ($k > 0 )) {
303                 // ... $this->outsidelimit || !empty($this->numbererrors ).
304                 $repeated[] = $mform->addElement('static', "answercomment[$i]", $strquestionlabel);
305                 // Decode equations in question text.
306                 $qtext = $this->qtypeobj->substitute_variables(
307                         $this->question->questiontext, $data);
308                 $textequations = $this->qtypeobj->find_math_equations($qtext);
309                 if ($textequations != '' && count($textequations) > 0 ) {
310                     $mform->addElement('static', "divider1[$j]", '',
311                             'Formulas {=..} in question text');
312                     foreach ($textequations as $key => $equation) {
313                         if ($formulaerrors = qtype_calculated_find_formula_errors($equation)) {
314                             $str = $formulaerrors;
315                         } else {
316                             eval('$str = '.$equation.';');
317                         }
318                         $equation = shorten_text($equation, 17, true);
319                         $mform->addElement('static', "textequation", "{=$equation}", "=".$str);
320                     }
321                 }
323             }
324             $k--;
326         }
327         $mform->addElement('static', 'outsidelimit', '', '');
328         // ...----------------------------------------------------------------------
329         // Non standard name for button element needed so not using add_action_buttons.
330         if (!($this->noofitems==0) ) {
331             $mform->addElement('submit', 'savechanges', get_string('savechanges'));
332             $mform->closeHeaderBefore('savechanges');
333         }
335         $this->add_hidden_fields();
337         $mform->addElement('hidden', 'category');
338         $mform->setType('category', PARAM_SEQUENCE);
340         $mform->addElement('hidden', 'wizard', 'datasetitems');
341         $mform->setType('wizard', PARAM_ALPHA);
342     }
344     public function set_data($question) {
345         $formdata = array();
346         $fromform = new stdClass();
347         if (isset($question->options)) {
348             $answers = $question->options->answers;
349             if (count($answers)) {
350                 if (optional_param('updateanswers', false, PARAM_BOOL) ||
351                         optional_param('updatedatasets', false, PARAM_BOOL)) {
352                     foreach ($answers as $key => $answer) {
353                         $fromform->tolerance[$key]= $this->_form->getElementValue(
354                                 'tolerance['.$key.']');
355                         $answer->tolerance = $fromform->tolerance[$key];
356                         $fromform->tolerancetype[$key]= $this->_form->getElementValue(
357                                 'tolerancetype['.$key.']');
358                         if (is_array($fromform->tolerancetype[$key])) {
359                             $fromform->tolerancetype[$key]= $fromform->tolerancetype[$key][0];
360                         }
361                         $answer->tolerancetype = $fromform->tolerancetype[$key];
362                         $fromform->correctanswerlength[$key]= $this->_form->getElementValue(
363                                 'correctanswerlength['.$key.']');
364                         if (is_array($fromform->correctanswerlength[$key])) {
365                             $fromform->correctanswerlength[$key] =
366                                     $fromform->correctanswerlength[$key][0];
367                         }
368                         $answer->correctanswerlength = $fromform->correctanswerlength[$key];
369                         $fromform->correctanswerformat[$key] = $this->_form->getElementValue(
370                                 'correctanswerformat['.$key.']');
371                         if (is_array($fromform->correctanswerformat[$key])) {
372                             $fromform->correctanswerformat[$key] =
373                                     $fromform->correctanswerformat[$key][0];
374                         }
375                         $answer->correctanswerformat = $fromform->correctanswerformat[$key];
376                     }
377                     $this->qtypeobj->save_question_calculated($question, $fromform);
379                 } else {
380                     foreach ($answers as $key => $answer) {
381                         $formdata['tolerance['.$key.']'] = $answer->tolerance;
382                         $formdata['tolerancetype['.$key.']'] = $answer->tolerancetype;
383                         $formdata['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
384                         $formdata['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
385                     }
386                 }
387             }
388         }
389         // Fill out all data sets and also the fields for the next item to add.
390         $j = $this->noofitems * count($this->datasetdefs);
391         for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--) {
392             $data = array();
393             foreach ($this->datasetdefs as $defid => $datasetdef) {
394                 if (isset($datasetdef->items[$itemnumber])) {
395                     $formdata["number[$j]"] = $datasetdef->items[$itemnumber]->value;
396                     $formdata["definition[$j]"] = $defid;
397                     $formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id;
398                     $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
399                 }
400                 $j--;
401             }
402             $comment = $this->qtypeobj->comment_on_datasetitems($this->qtypeobj, $question->id,
403                     $question->questiontext, $answers, $data, $itemnumber);
404             if ($comment->outsidelimit) {
405                 $this->outsidelimit=$comment->outsidelimit;
406             }
407             $totalcomment='';
408             foreach ($question->options->answers as $key => $answer) {
409                 $totalcomment .= $comment->stranswers[$key].'<br/>';
410             }
411             $formdata['answercomment['.$itemnumber.']'] = $totalcomment;
412         }
414         $formdata['nextpageparam[forceregeneration]'] = $this->regenerate;
415         $formdata['selectdelete'] = '1';
416         $formdata['selectadd'] = '1';
417         $j = $this->noofitems * count($this->datasetdefs)+1;
418         $data = array(); // Data for comment_on_datasetitems later.
419         // Dataset generation defaults.
420         if ($this->qtypeobj->supports_dataset_item_generation()) {
421             $itemnumber = $this->noofitems+1;
422             foreach ($this->datasetdefs as $defid => $datasetdef) {
423                 if (!optional_param('updatedatasets', false, PARAM_BOOL) &&
424                         !optional_param('updateanswers', false, PARAM_BOOL)) {
425                     $formdata["number[$j]"] = $this->qtypeobj->generate_dataset_item(
426                             $datasetdef->options);
427                 } else {
428                     $formdata["number[$j]"] = $this->_form->getElementValue("number[$j]");
429                 }
430                 $formdata["definition[$j]"] = $defid;
431                 $formdata["itemid[$j]"] = isset($datasetdef->items[$itemnumber]) ?
432                         $datasetdef->items[$itemnumber]->id : 0;
433                 $data[$datasetdef->name] = $formdata["number[$j]"];
434                 $j++;
435             }
436         }
438         // Existing records override generated data depending on radio element.
439         $j = $this->noofitems * count($this->datasetdefs) + 1;
440         if (!$this->regenerate && !optional_param('updatedatasets', false, PARAM_BOOL) &&
441                 !optional_param('updateanswers', false, PARAM_BOOL)) {
442             $idx = 1;
443             $itemnumber = $this->noofitems + 1;
444             foreach ($this->datasetdefs as $defid => $datasetdef) {
445                 if (isset($datasetdef->items[$itemnumber])) {
446                     $formdata["number[$j]"] = $datasetdef->items[$itemnumber]->value;
447                     $formdata["definition[$j]"] = $defid;
448                     $formdata["itemid[$j]"] = $datasetdef->items[$itemnumber]->id;
449                     $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
450                 }
451                 $j++;
452             }
453         }
455         $comment = $this->qtypeobj->comment_on_datasetitems($this->qtypeobj, $question->id,
456                 $question->questiontext, $answers, $data, ($this->noofitems + 1));
457         if (isset($comment->outsidelimit) && $comment->outsidelimit) {
458             $this->outsidelimit=$comment->outsidelimit;
459         }
460         $key1 = 1;
461         foreach ($question->options->answers as $key => $answer) {
462             $formdata['answercomment['.($this->noofitems+$key1).']'] = $comment->stranswers[$key];
463             $key1++;
464         }
466         if ($this->outsidelimit) {
467             $formdata['outsidelimit']= '<span class="error">' .
468                     get_string('oneanswertrueansweroutsidelimits', 'qtype_calculated') . '</span>';
469         }
470         $formdata = $this->qtypeobj->custom_generator_set_data($this->datasetdefs, $formdata);
472         parent::set_data((object)($formdata + (array)$question));
473     }
475     public function validation($data, $files) {
476         $errors = array();
477         if (isset($data['savechanges']) && ($this->noofitems==0) ) {
478             $errors['warning'] = get_string('warning', 'mnet');
479         }
480         if ($this->outsidelimit) {
481             $errors['outsidelimits'] =
482                     get_string('oneanswertrueansweroutsidelimits', 'qtype_calculated');
483         }
484         $numbers = $data['number'];
485         foreach ($numbers as $key => $number) {
486             if (! is_numeric($number)) {
487                 if (stristr($number, ',')) {
488                     $errors['number['.$key.']'] = get_string('nocommaallowed', 'qtype_calculated');
489                 } else {
490                     $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
491                 }
492             } else if (stristr($number, 'x')) {
493                 $a = new stdClass();
494                 $a->name = '';
495                 $a->value = $number;
496                 $errors['number['.$key.']'] = get_string('hexanotallowed', 'qtype_calculated', $a);
497             } else if (is_nan($number)) {
498                 $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
499             }
500         }
501         return $errors;
502     }