09e6b0ec5c962eccec03848fd1afeab4a5db86f1
[moodle.git] / question / type / calculated / edit_calculated_form.php
1 <?php  // $Id$
2 /**
3  * Defines the editing form for the calculated question type.
4  *
5  * @copyright &copy; 2007 Jamie Pratt
6  * @author Jamie Pratt me@jamiep.org
7  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8  * @package questionbank
9  * @subpackage questiontypes
10  */
12 /**
13  * calculated editing form definition.
14  */
15 class question_edit_calculated_form extends question_edit_form {
16     /**
17      * Handle to the question type for this question.
18      *
19      * @var question_calculated_qtype
20      */
21     var $qtypeobj;
23     /**
24      * Get the list of form elements to repeat, one for each answer.
25      * @param object $mform the form being built.
26      * @param $label the label to use for each option.
27      * @param $gradeoptions the possible grades for each answer.
28      * @param $repeatedoptions reference to array of repeated options to fill
29      * @param $answersoption reference to return the name of $question->options field holding an array of answers
30      * @return array of form fields.
31      */
32  /*   function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
33         $repeated = array();
34         $repeated[] =& $mform->createElement('header', 'answerhdr', $label);
35         $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50));
36         $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions);
37         $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'),
38                                 array('course' => $this->coursefilesid));
39         $repeatedoptions['answer']['type'] = PARAM_RAW;
40         $repeatedoptions['fraction']['default'] = 0;
41         $answersoption = 'answers';
42         return $repeated;
43     }*/
44     function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
45    //     $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption);
46            $repeated = array();
47         $repeated[] =& $mform->createElement('header', 'answerhdr', $label);
48      //   if ($this->editasmultichoice == 1){
49         $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50));
50         $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions);
51         $repeated[] =& $mform->createElement('htmleditor', 'feedback', get_string('feedback', 'quiz'),
52                                 array('course' => $this->coursefilesid));
53         $repeatedoptions['answer']['type'] = PARAM_RAW;
54         $repeatedoptions['fraction']['default'] = 0;
55         $answersoption = 'answers';
57         $mform->setType('answer', PARAM_NOTAGS);
59         $addrepeated = array();
60         if ($this->editasmultichoice == 1){
61             $addrepeated[] =& $mform->createElement('hidden', 'tolerance');
62             $addrepeated[] =& $mform->createElement('hidden', 'tolerancetype',1);
63         }else {
64             $addrepeated[] =& $mform->createElement('text', 'tolerance', get_string('tolerance', 'qtype_calculated'));
65             $addrepeated[] =& $mform->createElement('select', 'tolerancetype', get_string('tolerancetype', 'quiz'), $this->qtypeobj->tolerance_types());
66         }        
67         $repeatedoptions['tolerance']['type'] = PARAM_NUMBER;
68         $repeatedoptions['tolerance']['default'] = 0.01;
70         $addrepeated[] =&  $mform->createElement('select', 'correctanswerlength', get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
71         $repeatedoptions['correctanswerlength']['default'] = 2;
73         $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz'));
74         $addrepeated[] =&  $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
75         array_splice($repeated, 3, 0, $addrepeated);
76         if ($this->editasmultichoice == 1){
77              $repeated[1]->setLabel('...<strong>{={x}+..}</strong>...');       
78         }else {
79             $repeated[1]->setLabel(get_string('correctanswerformula', 'quiz').'=');
81         }
83         return $repeated;
84     }
86     /**
87      * Add question-type specific form fields.
88      *
89      * @param MoodleQuickForm $mform the form being built.
90      */
91     function definition_inner(&$mform) {
92         global $QTYPES;
93         $this->qtypeobj =& $QTYPES[$this->qtype()];
94       // echo code left for testing period 
95       //  echo "<p>question ".optional_param('multichoice', '', PARAM_RAW)." optional<pre>";print_r($this->question);echo "</pre></p>";
96         $label = get_string("sharedwildcards", "qtype_datasetdependent");
97         $mform->addElement('hidden', 'initialcategory', 1);
98         $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question);
99         $mform->insertElementBefore($mform->createElement('static','listcategory',$label,$html2),'name');
100         $addfieldsname='updatecategory';
101         $addstring=get_string("updatecategory", "qtype_calculated");
102                 $mform->registerNoSubmitButton($addfieldsname);
103         $this->editasmultichoice =  0 ;
104         if ( isset($this->question->options->multichoice) && $this->question->options->multichoice == '1'){
105             $this->editasmultichoice = 1 ;   
106         }else {
107             if ( !isset($this->question->id ) && 1 ==  optional_param('multichoice', '', PARAM_RAW )){
108                 $this->editasmultichoice = 1 ;
109             }else {
110                 $this->editasmultichoice = 0 ;
111             }
112             if ( !isset($this->question->id ) && '' != optional_param('createoptionbutton', '', PARAM_RAW) && 1 ==  optional_param('multichoice', '', PARAM_RAW)){
113                 $this->editasmultichoice = 1 ;
114             }
115             if ( !isset($this->question->id )== 0 && '' != optional_param('createoptionbutton', '', PARAM_RAW) && 0 ==  optional_param('multichoice', '', PARAM_RAW)){
116                 $this->editasmultichoice = 0 ;
117             }
118         }
119             
120    /*      if ( '' !=  optional_param('changetomultichoice', '', PARAM_RAW)){
121            $this->editasmultichoice = 1 ;            
122         }
123          if ( '' !=  optional_param('changetocalculated', '', PARAM_RAW)){
124             $this->editasmultichoice = 0 ;            
125        }*/
127         $mform->insertElementBefore(    $mform->createElement('submit', $addfieldsname, $addstring),'listcategory');
128         $mform->registerNoSubmitButton('createoptionbutton');
129                                             
130         if(!isset($this->question->id ) ){
131             $mform->addElement('header', 'choicehdr',get_string('Choosingcreationmode', 'qtype_calculated'));
132             $createoptions = Array();
133                                                     
134             $createoptions['0']=get_string('Regularcalculated', 'qtype_calculated');
135             $createoptions['1']=get_string('Multiplechoicecalculated', 'qtype_calculated');
136             $addgrp1 = array();
137             $addgrp1[] =& $mform->createElement('submit', 'createoptionbutton', get_string('Createas', 'qtype_calculatedsimple'));
138             $addgrp1[] =& $mform->createElement('select', "multichoice",'' , $createoptions);
139             $mform->addGroup($addgrp1, 'addgrp1', '', '   ', false);
140         }else {
141             $mform->addElement('hidden', 'multichoice',$this->editasmultichoice);
142         }
144         if ($this->editasmultichoice == 1){
145             $mform->addElement('header', 'choicehdr',get_string('multichoicecalculatedquestion', 'qtype_calculated'));
146             $menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice'));
147             $mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu);
148             $mform->setDefault('single', 1);
149     
150             $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0,1));
151             $mform->setHelpButton('shuffleanswers', array('multichoiceshuffle', get_string('shuffleanswers','qtype_multichoice'), 'qtype_multichoice'));
152             $mform->setDefault('shuffleanswers', 1);
153     
154             $numberingoptions = $QTYPES['multichoice']->get_numbering_styles();
155             $menu = array();
156             foreach ($numberingoptions as $numberingoption) {
157                 $menu[$numberingoption] = get_string('answernumbering' . $numberingoption, 'qtype_multichoice');
158             }
159             $mform->addElement('select', 'answernumbering', get_string('answernumbering', 'qtype_multichoice'), $menu);
160             $mform->setDefault('answernumbering', 'abc');
161         }else { //editing as regular
162             $mform->addElement('header', 'choicehdr', get_string('regularcalculatedquestion', 'qtype_calculated'));
163             $mform->addElement('hidden','single', '1');
164             $mform->addElement('hidden','shuffleanswers', '1');
165             $mform->addElement('hidden','answernumbering', 'abc');            
166         }
168         $creategrades = get_grade_options();
169         if ($this->editasmultichoice == 1){
170             $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
171                 $creategrades->gradeoptionsfull, max(5, QUESTION_NUMANS_START));
172         }else{
173             $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'),
174                 $creategrades->gradeoptions, 1, 1);
175         }
176             
178         $repeated = array();
179         if ($this->editasmultichoice == 1){
180             $nounits = optional_param('nounits', 1, PARAM_INT);
181             $mform->addElement('hidden', 'nounits', $nounits);
182             $mform->setConstants(array('nounits'=>$nounits));
183             for ($i=0; $i< $nounits; $i++) {
184                 $mform->addElement('hidden','unit'."[$i]", optional_param('unit'."[$i]", '', PARAM_NOTAGS));  
185                 $mform->addElement('hidden', 'multiplier'."[$i]", optional_param('multiplier'."[$i]", '', PARAM_NUMBER)); 
186             }  
188         }else {
189             $repeated[] =& $mform->createElement('header', 'unithdr', get_string('unithdr', 'qtype_numerical', '{no}'));    
190             $repeated[] =& $mform->createElement('text', 'unit', get_string('unit', 'quiz'));
191             $repeated[] =& $mform->createElement('text', 'multiplier', get_string('multiplier', 'quiz'));
192         
193         $mform->setType('unit', PARAM_NOTAGS);
195         $mform->setType('multiplier', PARAM_NUMBER);
197         if (isset($this->question->options)){
198             $countunits = count($this->question->options->units);
199         } else {
200             $countunits = 0;
201         }
202         if ($this->question->formoptions->repeatelements){
203             $repeatsatstart = $countunits + 1;
204         } else {
205             $repeatsatstart = $countunits;
206         }
207         $this->repeat_elements($repeated, $repeatsatstart, array(), 'nounits', 'addunits', 2, get_string('addmoreunitblanks', 'qtype_calculated', '{no}'));
209         }
210         if ($mform->elementExists('multiplier[0]')){
211             $firstunit =& $mform->getElement('multiplier[0]');
212             $firstunit->freeze();
213             $firstunit->setValue('1.0');
214             $firstunit->setPersistantFreeze(true);
215         }
216         if ($this->editasmultichoice == 1){
217             $mform->setType('addunits','hidden');
218             $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice'));
220             foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
221                 $mform->addElement('htmleditor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'),
222                                     array('course' => $this->coursefilesid));
223                 $mform->setType($feedbackname, PARAM_RAW);
224             }
225         }else {
226             foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
227                 $mform->addElement('hidden', $feedbackname);
228                 $mform->setType($feedbackname, PARAM_RAW);
229             }
230         }
231         //hidden elements
232         $mform->addElement('hidden', 'synchronize', '');
233         if (isset($this->question->options)&& isset($this->question->options->synchronize) ){
234             $mform->setDefault("synchronize", $this->question->options->synchronize);
235         } else {
236             $mform->setDefault("synchronize", 0 );
237         }
238         $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
239         $mform->setType('wizard', PARAM_ALPHA);
242     }
244     function set_data($question) {
245         if (isset($this->editasmultichoice)){
246              $default_values['multichoice']= 1 ; //$this->editasmultichoice ;
247         }
248         if (isset($question->options)){
249             $answers = $question->options->answers;
250             if (count($answers)) {
251                 $key = 0;
252                 foreach ($answers as $answer){
253                     $default_values['answer['.$key.']'] = $answer->answer;
254                     $default_values['fraction['.$key.']'] = $answer->fraction;
255                     $default_values['tolerance['.$key.']'] = $answer->tolerance;
256                     $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype;
257                     $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
258                     $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
259                     $default_values['feedback['.$key.']'] = $answer->feedback;
260                     $key++;
261                 }
262             }
263             if (isset($question->options->units)){
264                 $units  = array_values($question->options->units);
265                 // make sure the default unit is at index 0
266                 usort($units, create_function('$a, $b',
267                 'if (1.0 === (float)$a->multiplier) { return -1; } else '.
268                 'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
269                 if (count($units)) {
270                     $key = 0;
271                     foreach ($units as $unit){
272                         $default_values['unit['.$key.']'] = $unit->unit;
273                         $default_values['multiplier['.$key.']'] = $unit->multiplier;
274                         $key++;
275                     }
276                 }
277             }
278         }
279         if (isset($question->options->single)){
280         $default_values['single'] =  $question->options->single;
281         $default_values['answernumbering'] =  $question->options->answernumbering;
282         $default_values['shuffleanswers'] =  $question->options->shuffleanswers;
283         $default_values['correctfeedback'] =  $question->options->correctfeedback;
284         $default_values['partiallycorrectfeedback'] =  $question->options->partiallycorrectfeedback;
285         $default_values['incorrectfeedback'] =  $question->options->incorrectfeedback;
286     }
287         $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated');
288         $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated');
289         /* set the wild cards category display given that on loading the category element is
290         unselected when processing this function but have a valid value when processing the
291         update category button. The value can be obtain by
292          $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0];
293          but is coded using existing functions
294         */
295          $qu = new stdClass;
296          $el = new stdClass;
297          /* no need to call elementExists() here */
298          if ($this->_form->elementExists('category')){
299             $el=$this->_form->getElement('category');
300          } else {
301             $el=$this->_form->getElement('categorymoveto');
302          }
303          if($value =$el->getSelected()) {
304             $qu->category =$value[0];
305         }else {
306             $qu->category=$question->category;// on load  $question->category is set by question.php
307         }
308         $html2 = $this->qtypeobj->print_dataset_definitions_category($qu);
309         $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2 ;
310         $question = (object)((array)$question + $default_values);
312         parent::set_data($question);
313     }
315     function qtype() {
316         return 'calculated';
317     }
319     function validation($data, $files) {
320               // echo code left for testing period 
322               //  echo "<p>question <pre>";print_r($this->question);echo "</pre></p>";
323               //  echo "<p>data <pre>";print_r($data);echo "</pre></p>";
325         $errors = parent::validation($data, $files);
326         //verifying for errors in {=...} in question text;
327         $qtext = "";
328         $qtextremaining = $data['questiontext'] ;
329         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']);
330             foreach ($possibledatasets as $name => $value) {
331             $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
332         }
333     //     echo "numericalquestion qtextremaining <pre>";print_r($possibledatasets);
334         while  (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
335             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
336             $qtext =$qtext.$qtextsplits[0];
337             $qtextremaining = $qtextsplits[1];
338             if (!empty($regs1[1]) && $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
339                 if(!isset($errors['questiontext'])){
340                     $errors['questiontext'] = $formulaerrors.':'.$regs1[1] ;
341                 }else {
342                     $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
343                 }
344             }
345         }
346         $answers = $data['answer'];
347         $answercount = 0;
348         $maxgrade = false;
349         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']);
350         $mandatorydatasets = array();
351         foreach ($answers as $key => $answer){
352             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
353         }
354         if ( count($mandatorydatasets )==0){
355           //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
356             foreach ($answers as $key => $answer){
357                 $errors['answer['.$key.']'] = get_string('atleastonewildcard', 'qtype_datasetdependent');
358             }
359         }
360         if ($data['multichoice']== 1 ){
361             foreach ($answers as $key => $answer){
362                 $trimmedanswer = trim($answer);
363                 if (($trimmedanswer!='')||$answercount==0){    
364                     //verifying for errors in {=...} in answer text;
365                     $qanswer = "";
366                     $qanswerremaining =  $trimmedanswer ;
367                     $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
368                         foreach ($possibledatasets as $name => $value) {
369                         $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
370                     }
371                 //     echo "numericalquestion qanswerremaining <pre>";print_r($possibledatasets);
372                     while  (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
373                         $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
374                         $qanswer =$qanswer.$qanswersplits[0];
375                         $qanswerremaining = $qanswersplits[1];
376                         if (!empty($regs1[1]) && $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
377                             if(!isset($errors['answer['.$key.']'])){
378                                 $errors['answer['.$key.']'] = $formulaerrors.':'.$regs1[1] ;
379                             }else {
380                                 $errors['answer['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
381                             }
382                         }
383                     }
384                 }
385                 if ($trimmedanswer!=''){
386                     if ('2' == $data['correctanswerformat'][$key]
387                             && '0' == $data['correctanswerlength'][$key]) {
388                         $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
389                     }
390                     if (!is_numeric($data['tolerance'][$key])){
391                         $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
392                     }
393                     if ($data['fraction'][$key] == 1) {
394                        $maxgrade = true;
395                     }
396     
397                     $answercount++;
398                 }
399                 //check grades
400                 if ($answer != '') {
401                     if ($data['fraction'][$key] > 0) {
402                         $totalfraction += $data['fraction'][$key];
403                     }
404                     if ($data['fraction'][$key] > $maxfraction) {
405                         $maxfraction = $data['fraction'][$key];
406                     }
407                 }        
408             }
409             if ($answercount==0){
410                 $errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
411                 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
412             } elseif ($answercount==1){
413                 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
414     
415             }
417             /// Perform sanity checks on fractional grades
418             if ($data['single']) {
419                 if ($maxfraction != 1) {
420                     $maxfraction = $maxfraction * 100;
421                     $errors['fraction[0]'] = get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
422                 }
423             } else {
424                 $totalfraction = round($totalfraction,2);
425                 if ($totalfraction != 1) {
426                     $totalfraction = $totalfraction * 100;
427                     $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
428                 }
429             }
430         }else{ // regular calculated
431             foreach ($answers as $key => $answer){
432                 //check no of choices
433                 // the * for everykind of answer not actually implemented
434                 $trimmedanswer = trim($answer);
435                 if (($trimmedanswer!='')||$answercount==0){
436                     $eqerror = qtype_calculated_find_formula_errors($trimmedanswer);
437                     if (FALSE !== $eqerror){
438                         $errors['answer['.$key.']'] = $eqerror;
439                     }
440                 }
441                 if ($trimmedanswer!=''){
442                     if ('2' == $data['correctanswerformat'][$key]
443                             && '0' == $data['correctanswerlength'][$key]) {
444                         $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
445                     }
446                     if (!is_numeric($data['tolerance'][$key])){
447                         $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
448                     }
449                     if ($data['fraction'][$key] == 1) {
450                        $maxgrade = true;
451                     }
452     
453                     $answercount++;
454                 }
455                 //check grades
456     
457                 //TODO how should grade checking work here??
458                 /*if ($answer != '') {
459                     if ($data['fraction'][$key] > 0) {
460                         $totalfraction += $data['fraction'][$key];
461                     }
462                     if ($data['fraction'][$key] > $maxfraction) {
463                         $maxfraction = $data['fraction'][$key];
464                     }
465                 }*/
466             }
467         
468             //grade checking :
469             /// Perform sanity checks on fractional grades
470             /*if ( ) {
471                 if ($maxfraction != 1) {
472                     $maxfraction = $maxfraction * 100;
473                     $errors['fraction[0]'] = get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
474                 }
475             } else {
476                 $totalfraction = round($totalfraction,2);
477                 if ($totalfraction != 1) {
478                     $totalfraction = $totalfraction * 100;
479                     $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
480                 }
481             }*/
482             $units  = $data['unit'];
483             if (count($units)) {
484                 foreach ($units as $key => $unit){
485                     if (is_numeric($unit)){
486                         $errors['unit['.$key.']'] = get_string('mustnotbenumeric', 'qtype_calculated');
487                     }
488                     $trimmedunit = trim($unit);
489                     $trimmedmultiplier = trim($data['multiplier'][$key]);
490                     if (!empty($trimmedunit)){
491                         if (empty($trimmedmultiplier)){
492                             $errors['multiplier['.$key.']'] = get_string('youmustenteramultiplierhere', 'qtype_calculated');
493                         }
494                         if (!is_numeric($trimmedmultiplier)){
495                             $errors['multiplier['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
496                         }
497     
498                     }
499                 }
500             }
501             if ($answercount==0){
502                 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
503             }
504             if ($maxgrade == false) {
505                 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
506             }
507         }
509         return $errors;
510     }
512 ?>