Merge branch 'MDL-70106-icon-cache-310' of https://github.com/Peterburnett/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     /** @var stdClass the question category. */
55     protected $category;
57     /** @var context the context of the question category. */
58     protected $categorycontext;
60     public $datasetdefs;
62     public $maxnumber = -1;
64     public $regenerate;
66     public $noofitems;
68     public $outsidelimit = false;
70     public $commentanswers = array();
72     /**
73      * Add question-type specific form fields.
74      *
75      * @param MoodleQuickForm $mform the form being built.
76      */
77     public function __construct($submiturl, $question, $regenerate) {
78         global $SESSION, $CFG, $DB;
79         $this->regenerate = $regenerate;
80         $this->question = $question;
81         $this->qtypeobj = question_bank::get_qtype($this->question->qtype);
82         // Validate the question category.
83         if (!$category = $DB->get_record('question_categories',
84                 array('id' => $question->category))) {
85             print_error('categorydoesnotexist', 'question', $returnurl);
86         }
87         $this->category = $category;
88         $this->categorycontext = context::instance_by_id($category->contextid);
89         // Get the dataset defintions for this question.
90         if (empty($question->id)) {
91             $this->datasetdefs = $this->qtypeobj->get_dataset_definitions(
92                     $question->id, $SESSION->calculated->definitionform->dataset);
93         } else {
94             if (empty($question->options)) {
95                 $this->get_question_options($question);
96             }
97             $this->datasetdefs = $this->qtypeobj->get_dataset_definitions(
98                     $question->id, array());
99         }
101         foreach ($this->datasetdefs as $datasetdef) {
102             // Get maxnumber.
103             if ($this->maxnumber == -1 || $datasetdef->itemcount < $this->maxnumber) {
104                 $this->maxnumber = $datasetdef->itemcount;
105             }
106         }
107         foreach ($this->datasetdefs as $defid => $datasetdef) {
108             if (isset($datasetdef->id)) {
109                 $this->datasetdefs[$defid]->items =
110                         $this->qtypeobj->get_database_dataset_items($datasetdef->id);
111             }
112         }
113         parent::__construct($submiturl);
114     }
116     protected function definition() {
117         global $PAGE;
119         $labelsharedwildcard = get_string("sharedwildcard", "qtype_calculated");
120         $mform = $this->_form;
121         $mform->setDisableShortforms();
123         $strquestionlabel = $this->qtypeobj->comment_header($this->question);
124         if ($this->maxnumber != -1 ) {
125             $this->noofitems = $this->maxnumber;
126         } else {
127             $this->noofitems = 0;
128         }
129         $label = get_string("sharedwildcards", "qtype_calculated");
131         $html2 = $this->qtypeobj->print_dataset_definitions_category_shared(
132                 $this->question, $this->datasetdefs);
133         $mform->addElement('static', 'listcategory', $label, $html2);
134         // ...----------------------------------------------------------------------.
135         $mform->addElement('submit', 'updatedatasets',
136                 get_string('updatedatasetparam', 'qtype_calculated'));
137         $mform->registerNoSubmitButton('updatedatasets');
138         $mform->addElement('header', 'additemhdr',
139                 get_string('itemtoadd', 'qtype_calculated'));
140         $idx = 1;
141         $data = array();
142         $j = (($this->noofitems) * count($this->datasetdefs))+1;
143         foreach ($this->datasetdefs as $defkey => $datasetdef) {
144             if ($datasetdef->category |= 0 ) {
145                 $name = get_string('sharedwildcard', 'qtype_calculated', $datasetdef->name);
146             } else {
147                 $name = get_string('wildcard', 'qtype_calculated', $datasetdef->name);
148             }
149             $mform->addElement('float', "number[{$j}]", $name);
150             $this->qtypeobj->custom_generator_tools_part($mform, $idx, $j);
151             $idx++;
152             $mform->addElement('hidden', "definition[{$j}]");
153             $mform->setType("definition[{$j}]", PARAM_RAW);
154             $mform->addElement('hidden', "itemid[{$j}]");
155             $mform->setType("itemid[{$j}]", PARAM_RAW);
156             $mform->addElement('static', "divider[{$j}]", '', '<hr />');
157             $mform->setType("divider[{$j}]", PARAM_RAW);
158             $j++;
159         }
161         $mform->addElement('header', 'updateanswershdr',
162                 get_string('answerstoleranceparam', 'qtype_calculated'));
163         $mform->addElement('submit', 'updateanswers',
164                 get_string('updatetolerancesparam', 'qtype_calculated'));
165         $mform->setAdvanced('updateanswers', true);
166         $mform->registerNoSubmitButton('updateanswers');
168         $answers = fullclone($this->question->options->answers);
169         $key1 =1;
170         foreach ($answers as $key => $answer) {
171             $ans = shorten_text($answer->answer, 17, true);
172             if ($ans === '*') {
173                 $mform->addElement('static',
174                         'answercomment[' . ($this->noofitems+$key1) . ']', $ans);
175                 $mform->addElement('hidden', 'tolerance['.$key.']', '');
176                 $mform->setType('tolerance['.$key.']', PARAM_FLOAT); // No need to worry about localisation, as the value of this field will not be shown to users anymore.
177                 $mform->setAdvanced('tolerance['.$key.']', true);
178                 $mform->addElement('hidden', 'tolerancetype['.$key.']', '');
179                 $mform->setType('tolerancetype['.$key.']', PARAM_RAW);
180                 $mform->setAdvanced('tolerancetype['.$key.']', true);
181                 $mform->addElement('hidden', 'correctanswerlength['.$key.']', '');
182                 $mform->setType('correctanswerlength['.$key.']', PARAM_RAW);
183                 $mform->setAdvanced('correctanswerlength['.$key.']', true);
184                 $mform->addElement('hidden', 'correctanswerformat['.$key.']', '');
185                 $mform->setType('correctanswerformat['.$key.']', PARAM_RAW);
186                 $mform->setAdvanced('correctanswerformat['.$key.']', true);
187             } else if ( $ans !== '' ) {
188                 $mform->addElement('static', 'answercomment[' . ($this->noofitems+$key1) . ']',
189                         $ans);
190                 $mform->addElement('float', 'tolerance['.$key.']',
191                         get_string('tolerance', 'qtype_calculated'));
192                 $mform->setAdvanced('tolerance['.$key.']', true);
193                 $mform->addElement('select', 'tolerancetype['.$key.']',
194                         get_string('tolerancetype', 'qtype_numerical'),
195                         $this->qtypeobj->tolerance_types());
196                 $mform->setAdvanced('tolerancetype['.$key.']', true);
198                 $mform->addElement('select', 'correctanswerlength['.$key.']',
199                         get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
200                 $mform->setAdvanced('correctanswerlength['.$key.']', true);
202                 $answerlengthformats = array(
203                     '1' => get_string('decimalformat', 'qtype_numerical'),
204                     '2' => get_string('significantfiguresformat', 'qtype_calculated')
205                 );
206                 $mform->addElement('select', 'correctanswerformat['.$key.']',
207                         get_string('correctanswershowsformat', 'qtype_calculated'),
208                         $answerlengthformats);
209                 $mform->setAdvanced('correctanswerformat['.$key.']', true);
210                 $mform->addElement('static', 'dividertolerance', '', '<hr />');
211                 $mform->setAdvanced('dividertolerance', true);
212             }
213             $key1++;
214         }
216         $addremoveoptions = array();
217         $addremoveoptions['1']='1';
218         for ($i=10; $i<=100; $i+=10) {
219              $addremoveoptions["{$i}"] = "{$i}";
220         }
221         $showoptions = Array();
222         $showoptions['1']='1';
223         $showoptions['2']='2';
224         $showoptions['5']='5';
225         for ($i=10; $i<=100; $i+=10) {
226              $showoptions["{$i}"] = "{$i}";
227         }
228         $mform->addElement('header', 'addhdr', get_string('add', 'moodle'));
229         $mform->closeHeaderBefore('addhdr');
231         if ($this->qtypeobj->supports_dataset_item_generation()) {
232             $radiogrp = array();
233             $radiogrp[] =& $mform->createElement('radio', 'nextpageparam[forceregeneration]',
234                     null, get_string('reuseifpossible', 'qtype_calculated'), 0);
235             $radiogrp[] =& $mform->createElement('radio', 'nextpageparam[forceregeneration]',
236                     null, get_string('forceregenerationshared', 'qtype_calculated'), 1);
237             $radiogrp[] =& $mform->createElement('radio', 'nextpageparam[forceregeneration]',
238                     null, get_string('forceregenerationall', 'qtype_calculated'), 2);
239             $mform->addGroup($radiogrp, 'forceregenerationgrp',
240                     get_string('nextitemtoadd', 'qtype_calculated'), "<br/>", false);
241         }
243         $mform->addElement('submit', 'getnextbutton', get_string('getnextnow', 'qtype_calculated'));
244         $mform->addElement('static', "dividera", '', '<hr />');
245         $addgrp = array();
246         $addgrp[] =& $mform->createElement('submit', 'addbutton', get_string('add', 'moodle'));
247         $addgrp[] =& $mform->createElement('select', "selectadd",
248                 get_string('additem', 'qtype_calculated'), $addremoveoptions);
249         $addgrp[] = & $mform->createElement('static', "stat", "Items",
250                 get_string('newsetwildcardvalues', 'qtype_calculatedsimple'));
251         $mform->addGroup($addgrp, 'addgrp', get_string('additem', 'qtype_calculated'), ' ', false);
252         $mform->addElement('static', "divideradd", '', '');
253         if ($this->noofitems > 0) {
254             $mform->addElement('header', 'deleteitemhdr', get_string('delete', 'moodle'));
255             $deletegrp = array();
256             $deletegrp[] = $mform->createElement('submit', 'deletebutton',
257                     get_string('delete', 'moodle'));
258             $deletegrp[] = $mform->createElement('select', 'selectdelete',
259                     get_string('deleteitem', 'qtype_calculated')."1", $addremoveoptions);
260             $deletegrp[] = $mform->createElement('static', "stat", "Items",
261                     get_string('setwildcardvalues', 'qtype_calculatedsimple'));
262             $mform->addGroup($deletegrp, 'deletegrp', '', '   ', false);
263         } else {
264             $mform->addElement('static', 'warning', '', '<span class="error">' .
265                     get_string('youmustaddatleastoneitem', 'qtype_calculated').'</span>');
266         }
268         $addgrp1 = array();
269         $addgrp1[] = $mform->createElement('submit', 'showbutton',
270                 get_string('showitems', 'qtype_calculated'));
271         $addgrp1[] = $mform->createElement('select', "selectshow", '' , $showoptions);
272         $addgrp1[] = $mform->createElement('static', "stat", '',
273                 get_string('setwildcardvalues', 'qtype_calculated'));
274         $mform->addGroup($addgrp1, 'addgrp1', '', '   ', false);
275         $mform->registerNoSubmitButton('showbutton');
276         $mform->closeHeaderBefore('addgrp1');
277         // ...----------------------------------------------------------------------.
278         $j = $this->noofitems * count($this->datasetdefs);
279         $k = optional_param('selectshow', 1, PARAM_INT);
280         for ($i = $this->noofitems; $i >= 1; $i--) {
281             if ($k > 0) {
282                 $mform->addElement('header', 'setnoheader' . $i, "<b>" .
283                         get_string('setno', 'qtype_calculated', $i)."</b>&nbsp;&nbsp;");
284             }
285             foreach ($this->datasetdefs as $defkey => $datasetdef) {
286                 if ($k > 0) {
287                     if ($datasetdef->category == 0 ) {
288                         $mform->addElement('float', "number[{$j}]",
289                                 get_string('wildcard', 'qtype_calculated', $datasetdef->name));
290                     } else {
291                         $mform->addElement('float', "number[{$j}]", get_string(
292                                 'sharedwildcard', 'qtype_calculated', $datasetdef->name));
293                     }
295                 } else {
296                     $mform->addElement('hidden', "number[{$j}]" , '');
297                     $mform->setType("number[{$j}]", PARAM_LOCALISEDFLOAT); // Localisation handling has to be done manually.
298                 }
299                 $mform->addElement('hidden', "itemid[{$j}]");
300                 $mform->setType("itemid[{$j}]", PARAM_INT);
302                 $mform->addElement('hidden', "definition[{$j}]");
303                 $mform->setType("definition[{$j}]", PARAM_NOTAGS);
304                 $data[$datasetdef->name] =$datasetdef->items[$i]->value;
306                 $j--;
307             }
308             if ('' != $strquestionlabel && ($k > 0 )) {
309                 // ... $this->outsidelimit || !empty($this->numbererrors ).
310                 $repeated[] = $mform->addElement('static', "answercomment[{$i}]", $strquestionlabel);
311                 // Decode equations in question text.
312                 $qtext = $this->qtypeobj->substitute_variables(
313                         $this->question->questiontext, $data);
314                 $textequations = $this->qtypeobj->find_formulas($qtext);
315                 if ($textequations != '' && count($textequations) > 0 ) {
316                     $mform->addElement('static', "divider1[{$j}]", '',
317                             'Formulas {=..} in question text');
318                     foreach ($textequations as $key => $equation) {
319                         if ($formulaerrors = qtype_calculated_find_formula_errors($equation)) {
320                             $str = $formulaerrors;
321                         } else {
322                             eval('$str = '.$equation.';');
323                         }
324                         $equation = shorten_text($equation, 17, true);
325                         $mform->addElement('static', "textequation", "{={$equation}}", "=".$str);
326                     }
327                 }
329             }
330             $k--;
332         }
333         $mform->addElement('static', 'outsidelimit', '', '');
335         // Submit buttons.
336         if ($this->noofitems > 0) {
337             $buttonarray = array();
338             $buttonarray[] = $mform->createElement(
339                     'submit', 'savechanges', get_string('savechanges'));
341             $previewlink = $PAGE->get_renderer('core_question')->question_preview_link(
342                         $this->question->id, $this->categorycontext, true);
343             $buttonarray[] = $mform->createElement('static', 'previewlink', '', $previewlink);
345             $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
346             $mform->closeHeaderBefore('buttonar');
347         }
349         $this->add_hidden_fields();
351         $mform->addElement('hidden', 'category');
352         $mform->setType('category', PARAM_SEQUENCE);
354         $mform->addElement('hidden', 'wizard', 'datasetitems');
355         $mform->setType('wizard', PARAM_ALPHA);
356     }
358     public function set_data($question) {
359         $formdata = array();
360         $fromform = new stdClass();
361         if (isset($question->options)) {
362             $answers = $question->options->answers;
363             if (count($answers)) {
364                 if (optional_param('updateanswers', false, PARAM_BOOL) ||
365                         optional_param('updatedatasets', false, PARAM_BOOL)) {
366                     foreach ($answers as $key => $answer) {
367                         $fromform->tolerance[$key]= $this->_form->getElementValue(
368                                 'tolerance['.$key.']');
369                         $answer->tolerance = $fromform->tolerance[$key];
370                         $fromform->tolerancetype[$key]= $this->_form->getElementValue(
371                                 'tolerancetype['.$key.']');
372                         if (is_array($fromform->tolerancetype[$key])) {
373                             $fromform->tolerancetype[$key]= $fromform->tolerancetype[$key][0];
374                         }
375                         $answer->tolerancetype = $fromform->tolerancetype[$key];
376                         $fromform->correctanswerlength[$key]= $this->_form->getElementValue(
377                                 'correctanswerlength['.$key.']');
378                         if (is_array($fromform->correctanswerlength[$key])) {
379                             $fromform->correctanswerlength[$key] =
380                                     $fromform->correctanswerlength[$key][0];
381                         }
382                         $answer->correctanswerlength = $fromform->correctanswerlength[$key];
383                         $fromform->correctanswerformat[$key] = $this->_form->getElementValue(
384                                 'correctanswerformat['.$key.']');
385                         if (is_array($fromform->correctanswerformat[$key])) {
386                             $fromform->correctanswerformat[$key] =
387                                     $fromform->correctanswerformat[$key][0];
388                         }
389                         $answer->correctanswerformat = $fromform->correctanswerformat[$key];
390                     }
391                     $this->qtypeobj->save_question_calculated($question, $fromform);
393                 } else {
394                     foreach ($answers as $key => $answer) {
395                         $formdata['tolerance['.$key.']'] = $answer->tolerance;
396                         $formdata['tolerancetype['.$key.']'] = $answer->tolerancetype;
397                         $formdata['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
398                         $formdata['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
399                     }
400                 }
401             }
402         }
403         // Fill out all data sets and also the fields for the next item to add.
404         $j = $this->noofitems * count($this->datasetdefs);
405         for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--) {
406             $data = array();
407             foreach ($this->datasetdefs as $defid => $datasetdef) {
408                 if (isset($datasetdef->items[$itemnumber])) {
409                     $value = $datasetdef->items[$itemnumber]->value;
410                     if ($this->_form->getElementType("number[{$j}]") == 'hidden') {
411                         // Some of the number elements are from the float type and some are from the hidden type.
412                         // We need to manually handle localised floats for hidden elements.
413                         $value = format_float($value, -1);
414                     }
415                     $formdata["number[{$j}]"] = $value;
416                     $formdata["definition[{$j}]"] = $defid;
417                     $formdata["itemid[{$j}]"] = $datasetdef->items[$itemnumber]->id;
418                     $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
419                 }
420                 $j--;
421             }
422             $comment = $this->qtypeobj->comment_on_datasetitems($this->qtypeobj, $question->id,
423                     $question->questiontext, $answers, $data, $itemnumber);
424             if ($comment->outsidelimit) {
425                 $this->outsidelimit=$comment->outsidelimit;
426             }
427             $totalcomment='';
428             foreach ($question->options->answers as $key => $answer) {
429                 $totalcomment .= $comment->stranswers[$key].'<br/>';
430             }
431             $formdata['answercomment['.$itemnumber.']'] = $totalcomment;
432         }
434         $formdata['nextpageparam[forceregeneration]'] = $this->regenerate;
435         $formdata['selectdelete'] = '1';
436         $formdata['selectadd'] = '1';
437         $j = $this->noofitems * count($this->datasetdefs)+1;
438         $data = array(); // Data for comment_on_datasetitems later.
439         // Dataset generation defaults.
440         if ($this->qtypeobj->supports_dataset_item_generation()) {
441             $itemnumber = $this->noofitems+1;
442             foreach ($this->datasetdefs as $defid => $datasetdef) {
443                 if (!optional_param('updatedatasets', false, PARAM_BOOL) &&
444                         !optional_param('updateanswers', false, PARAM_BOOL)) {
445                     $value = $this->qtypeobj->generate_dataset_item($datasetdef->options);
446                 } else {
447                     $value = $this->_form->getElementValue("number[{$j}]");
448                 }
449                 if ($this->_form->getElementType("number[{$j}]") == 'hidden') {
450                     // Some of the number elements are from the float type and some are from the hidden type.
451                     // We need to manually handle localised floats for hidden elements.
452                     $value = format_float($value, -1);
453                 }
454                 $formdata["number[{$j}]"] = $value;
455                 $formdata["definition[{$j}]"] = $defid;
456                 $formdata["itemid[{$j}]"] = isset($datasetdef->items[$itemnumber]) ?
457                         $datasetdef->items[$itemnumber]->id : 0;
458                 $data[$datasetdef->name] = $formdata["number[{$j}]"];
459                 $j++;
460             }
461         }
463         // Existing records override generated data depending on radio element.
464         $j = $this->noofitems * count($this->datasetdefs) + 1;
465         if (!$this->regenerate && !optional_param('updatedatasets', false, PARAM_BOOL) &&
466                 !optional_param('updateanswers', false, PARAM_BOOL)) {
467             $itemnumber = $this->noofitems + 1;
468             foreach ($this->datasetdefs as $defid => $datasetdef) {
469                 if (isset($datasetdef->items[$itemnumber])) {
470                     $formdata["number[{$j}]"] = $datasetdef->items[$itemnumber]->value;
471                     $formdata["definition[{$j}]"] = $defid;
472                     $formdata["itemid[{$j}]"] = $datasetdef->items[$itemnumber]->id;
473                     $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
474                 }
475                 $j++;
476             }
477         }
479         $comment = $this->qtypeobj->comment_on_datasetitems($this->qtypeobj, $question->id,
480                 $question->questiontext, $answers, $data, ($this->noofitems + 1));
481         if (isset($comment->outsidelimit) && $comment->outsidelimit) {
482             $this->outsidelimit=$comment->outsidelimit;
483         }
484         $key1 = 1;
485         foreach ($question->options->answers as $key => $answer) {
486             $formdata['answercomment['.($this->noofitems+$key1).']'] = $comment->stranswers[$key];
487             $key1++;
488         }
490         if ($this->outsidelimit) {
491             $formdata['outsidelimit']= '<span class="error">' .
492                     get_string('oneanswertrueansweroutsidelimits', 'qtype_calculated') . '</span>';
493         }
494         $formdata = $this->qtypeobj->custom_generator_set_data($this->datasetdefs, $formdata);
496         parent::set_data((object)($formdata + (array)$question));
497     }
499     public function validation($data, $files) {
500         $errors = array();
501         if (isset($data['savechanges']) && ($this->noofitems==0) ) {
502             $errors['warning'] = get_string('warning', 'mnet');
503         }
504         if ($this->outsidelimit) {
505             $errors['outsidelimits'] =
506                     get_string('oneanswertrueansweroutsidelimits', 'qtype_calculated');
507         }
508         $numbers = $data['number'];
509         foreach ($numbers as $key => $number) {
510             if (! is_numeric($number)) {
511                 if (stristr($number, ',')) {
512                     $errors['number['.$key.']'] = get_string('nocommaallowed', 'qtype_calculated');
513                 } else {
514                     $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
515                 }
516             } else if (stristr($number, 'x')) {
517                 $a = new stdClass();
518                 $a->name = '';
519                 $a->value = $number;
520                 $errors['number['.$key.']'] = get_string('hexanotallowed', 'qtype_calculated', $a);
521             } else if (is_nan($number)) {
522                 $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
523             }
524         }
525         return $errors;
526     }