missing global $CFG ; line 577
[moodle.git] / question / type / calculated / questiontype.php
1 <?php  // $Id$
3 /////////////////
4 // CALCULATED ///
5 /////////////////
7 /// QUESTION TYPE CLASS //////////////////
9 require_once("$CFG->dirroot/question/type/datasetdependent/abstractqtype.php");
12 class question_calculated_qtype extends question_dataset_dependent_questiontype {
14     // Used by the function custom_generator_tools:
15     var $calcgenerateidhasbeenadded = false;
17     function name() {
18         return 'calculated';
19     }
21     function get_question_options(&$question) {
22         // First get the datasets and default options
23          global $CFG;
24         if (!$question->options->answers = get_records_sql(
25                                 "SELECT a.*, c.tolerance, c.tolerancetype, c.correctanswerlength, c.correctanswerformat " .
26                                 "FROM {$CFG->prefix}question_answers a, " .
27                                 "     {$CFG->prefix}question_calculated c " .
28                                 "WHERE a.question = $question->id " .
29                                 "AND   a.id = c.answer ".
30                                 "ORDER BY a.id ASC")) {
31             notify('Error: Missing question answer!');
32             return false;
33         }
35 /*
36        if(false === parent::get_question_options($question)) {
37             return false;
38         }
40         if (!$options = get_records('question_calculated', 'question', $question->id)) {
41             notify("No options were found for calculated question
42              #{$question->id}! Proceeding with defaults.");
43         //     $options = new Array(); 
44             $options= new stdClass;
45             $options->tolerance           = 0.01;
46             $options->tolerancetype       = 1; // relative
47             $options->correctanswerlength = 2;
48             $options->correctanswerformat = 1; // decimals
49         } 
51         // For historic reasons we also need these fields in the answer objects.
52         // This should eventually be removed and related code changed to use
53         // the values in $question->options instead.
54          foreach ($question->options->answers as $key => $answer) {
55             $answer = &$question->options->answers[$key]; // for PHP 4.x
56            $answer->calcid              = $options->id;
57             $answer->tolerance           = $options->tolerance;
58             $answer->tolerancetype       = $options->tolerancetype;
59             $answer->correctanswerlength = $options->correctanswerlength;
60             $answer->correctanswerformat = $options->correctanswerformat;
61         }*/
63         $virtualqtype = $this->get_virtual_qtype();
64         $virtualqtype->get_numerical_units($question);
65        
66         if( isset($question->export_process)&&$question->export_process){
67             $question->options->datasets = $this->get_datasets_for_export($question);
68         }   
69         return true;
70     }
71     
72     function get_datasets_for_export(&$question){
73         $datasetdefs = array();
74         if (!empty($question->id)) {
75             global $CFG;
76             $sql = "SELECT i.*
77                     FROM {$CFG->prefix}question_datasets d,
78                          {$CFG->prefix}question_dataset_definitions i
79                     WHERE d.question = '$question->id'
80                     AND   d.datasetdefinition = i.id
81                    ";
82             if ($records = get_records_sql($sql)) {                
83                 foreach ($records as $r) {
84                     $def = $r ;
85                     if ($def->category=='0'){
86                         $def->status='private';
87                     } else {   
88                         $def->status='shared';
89                     }   
90                     $def->type ='calculated' ;
91                     list($distribution, $min, $max,$dec) = explode(':', $def->options, 4);
92                     $def->distribution=$distribution;
93                     $def->minimum=$min;
94                     $def->maximum=$max;
95                     $def->decimals=$dec ;                                         
96                      if ($def->itemcount > 0 ) {
97                         // get the datasetitems
98                         $def->items = array();
99                         $sql1= (" SELECT itemnumber, definition, id, value
100                         FROM {$CFG->prefix}question_dataset_items 
101                         WHERE definition = '$def->id' order by itemnumber ASC ");
102                         if ($items = get_records_sql($sql1)){
103                             $n = 0;
104                             foreach( $items as $ii){
105                                 $n++;
106                                 $def->items[$n] = new stdClass;
107                                 $def->items[$n]->itemnumber=$ii->itemnumber;
108                                 $def->items[$n]->value=$ii->value;
109                            }
110                            $def->number_of_items=$n ;
111                         }
112                     }
113                     $datasetdefs["1-$r->category-$r->name"] = $def;                                                                                               
114                 }
115             }
116         }
117         return $datasetdefs ;
118     } 
119       
120     function save_question_options($question) {
121         //$options = $question->subtypeoptions;
122         // Get old answers:
123         global $CFG;
124                 
125         // Get old versions of the objects
126         if (!$oldanswers = get_records('question_answers', 'question', $question->id, 'id ASC')) {
127             $oldanswers = array();
128         }
130         if (!$oldoptions = get_records('question_calculated', 'question', $question->id, 'answer ASC')) {
131             $oldoptions = array();
132         }
133                 // Save the units.
134         // Save units
135         $virtualqtype = $this->get_virtual_qtype();
136         $result = $virtualqtype->save_numerical_units($question);
137         if (isset($result->error)) {
138             return $result;
139         } else {
140             $units = &$result->units;
141         }
142         // Insert all the new answers
143         foreach ($question->answers as $key => $dataanswer) {
144             if (  trim($dataanswer) != '' ) { 
145                 $answer = new stdClass;
146                 $answer->question = $question->id;
147                 $answer->answer = trim($dataanswer);
148                 $answer->fraction = $question->fraction[$key];
149                 $answer->feedback = trim($question->feedback[$key]);
150  
151                 if ($oldanswer = array_shift($oldanswers)) {  // Existing answer, so reuse it
152                     $answer->id = $oldanswer->id;
153                     if (! update_record("question_answers", $answer)) {
154                         $result->error = "Could not update question answer! (id=$answer->id)";
155                         return $result;
156                     }
157                 } else { // This is a completely new answer
158                     if (! $answer->id = insert_record("question_answers", $answer)) {
159                         $result->error = "Could not insert question answer!";
160                         return $result;
161                     }
162                 }
164                 // Set up the options object
165                 if (!$options = array_shift($oldoptions)) {
166                     $options = new stdClass;
167                 }
168                 $options->question  = $question->id;
169                 $options->answer    = $answer->id;
170                 $options->tolerance = trim($question->tolerance[$key]);
171                 $options->tolerancetype  = trim($question->tolerancetype[$key]);
172                 $options->correctanswerlength  = trim($question->correctanswerlength[$key]);
173                 $options->correctanswerformat  = trim($question->correctanswerformat[$key]);
174                 
175                 // Save options
176                 if (isset($options->id)) { // reusing existing record
177                     if (! update_record('question_calculated', $options)) {
178                         $result->error = "Could not update question calculated options! (id=$options->id)";
179                         return $result;
180                     }
181                 } else { // new options
182                     if (! insert_record('question_calculated', $options)) {
183                         $result->error = "Could not insert question  calculated options!";
184                         return $result;
185                     }
186                 }
187             }
188         }
189         // delete old answer records
190         if (!empty($oldanswers)) {
191             foreach($oldanswers as $oa) {
192                 delete_records('question_answers', 'id', $oa->id);
193             }
194         }
196         // delete old answer records
197         if (!empty($oldoptions)) {
198             foreach($oldoptions as $oo) {
199                 delete_records('question_calculated', 'id', $oo->id);
200             }
201         }
204         if( isset($question->import_process)&&$question->import_process){
205             $this->import_datasets($question);
206          }   
207         // Report any problems.
208         if (!empty($result->notice)) {
209             return $result;
210         }
211         return true;
212     }
214     function import_datasets($question){
215         $n = count($question->dataset);
216         foreach ($question->dataset as $dataset) {
217             // name, type, option, 
218             $datasetdef = new stdClass();
219             $datasetdef->name = $dataset->name;
220             $datasetdef->type = 1 ;
221             $datasetdef->options =  $dataset->distribution.':'.$dataset->min.':'.$dataset->max.':'.$dataset->length;
222             $datasetdef->itemcount=$dataset->itemcount;                       
223             if ( $dataset->status =='private'){
224                 $datasetdef->category = 0;
225                 $todo='create' ;
226             }else if ($dataset->status =='shared' ){
227                 if ($sharedatasetdefs = get_records_select(
228                         'question_dataset_definitions',
229                         "type = '1'
230                         AND name = '$dataset->name'
231                         AND category = '$question->category'
232                         ORDER BY id DESC;"
233                        )) { // so there is at least one
234                     $sharedatasetdef = array_shift($sharedatasetdefs);
235                     if ( $sharedatasetdef->options ==  $datasetdef->options ){// identical so use it
236                         $todo='useit' ;
237                         $datasetdef =$sharedatasetdef ;
238                     } else { // different so create a private one
239                         $datasetdef->category = 0;
240                         $todo='create' ;
241                     }    
242                 }else { // no so create one
243                     $datasetdef->category =$question->category ;
244                     $todo='create' ;
245                }     
246             }         
247             if (  $todo=='create'){
248                 if (!$datasetdef->id = insert_record(
249                     'question_dataset_definitions', $datasetdef)) {
250                     error("Unable to create dataset $defid");
251                 } 
252            }  
253            // Create relation to the dataset:
254            $questiondataset = new stdClass;
255            $questiondataset->question = $question->id;
256            $questiondataset->datasetdefinition = $datasetdef->id;
257             if (!insert_record('question_datasets',
258                                $questiondataset)) {
259                 error("Unable to create relation to dataset $dataset->name $todo");
260             }
261             if ($todo=='create'){ // add the items
262                 foreach ($dataset->datasetitem as $dataitem ){
263                     $datasetitem = new stdClass;
264                     $datasetitem->definition=$datasetdef->id ;
265                     $datasetitem->itemnumber = $dataitem->itemnumber ;
266                     $datasetitem->value = $dataitem->value ;
267                     if (!insert_record('question_dataset_items', $datasetitem)) {
268                         error("Unable to insert dataset item $item->itemnumber with $item->value for $datasetdef->name");
269                     }
270                 }     
271             }                                
272         }
273     }
274          
275     function create_runtime_question($question, $form) {
276         $question = parent::create_runtime_question($question, $form);
277         $question->options->answers = array();
278         foreach ($form->answers as $key => $answer) {
279             $a->answer              = trim($form->answer[$key]);
280             $a->fraction              = $form->fraction[$key];//new
281            $a->tolerance           = $form->tolerance[$key];
282             $a->tolerancetype       = $form->tolerancetype[$key];
283             $a->correctanswerlength = $form->correctanswerlength[$key];
284             $a->correctanswerformat = $form->correctanswerformat[$key];
285             $question->options->answers[] = clone($a);
286         }
288         return $question;
289     }
291     function validate_form($form) {
292         switch($form->wizardpage) {
293             case 'question':
294                 $calculatedmessages = array();
295                 if (empty($form->name)) {
296                     $calculatedmessages[] = get_string('missingname', 'quiz');
297                 }
298                 if (empty($form->questiontext)) {
299                     $calculatedmessages[] = get_string('missingquestiontext', 'quiz');
300                 }
301                 // Verify formulas
302                 foreach ($form->answers as $key => $answer) {
303                     if ('' === trim($answer)) {
304                         $calculatedmessages[] =
305                             get_string('missingformula', 'quiz');
306                     }
307                     if ($formulaerrors =
308                      qtype_calculated_find_formula_errors($answer)) {
309                         $calculatedmessages[] = $formulaerrors;
310                     }
311                     if (! isset($form->tolerance[$key])) {
312                         $form->tolerance[$key] = 0.0;
313                     }
314                     if (! is_numeric($form->tolerance[$key])) {
315                         $calculatedmessages[] =
316                             get_string('tolerancemustbenumeric', 'quiz');
317                     }
318                 }
320                 if (!empty($calculatedmessages)) {
321                     $errorstring = "The following errors were found:<br />";
322                     foreach ($calculatedmessages as $msg) {
323                         $errorstring .= $msg . '<br />';
324                     }
325                     error($errorstring);
326                 }
328                 break;
329             default:
330                 return parent::validate_form($form);
331                 break;
332         }
333         return true;
334     }
336     /**
337     * Deletes question from the question-type specific tables
338     *
339     * @return boolean Success/Failure
340     * @param object $question  The question being deleted
341     */
342     function delete_question($questionid) {
343         delete_records("question_calculated", "question", $questionid);
344         delete_records("question_numerical_units", "question", $questionid);
345         if ($datasets = get_records('question_datasets', 'question', $questionid)) {
346             foreach ($datasets as $dataset) {
347                 delete_records('question_dataset_definitions', 'id', $dataset->datasetdefinition);
348                 delete_records('question_dataset_items', 'definition', $dataset->datasetdefinition);
349             }
350         }
351         delete_records("question_datasets", "question", $questionid);
352         return true;
353     }
355     function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
356         // Substitute variables in questiontext before giving the data to the
357         // virtual type for printing
358         $virtualqtype = $this->get_virtual_qtype();
359         if($unit = $virtualqtype->get_default_numerical_unit($question)){
360              $unit = $unit->unit;
361         } else {
362             $unit = '';
363         }          
364         // We modify the question to look like a numerical question
365         $numericalquestion = fullclone($question);
366         foreach ($numericalquestion->options->answers as $key => $answer) {
367           $answer = fullclone($numericalquestion->options->answers[$key]);
368             $correctanswer = qtype_calculated_calculate_answer(
369                  $answer->answer, $state->options->dataset, $answer->tolerance,
370                  $answer->tolerancetype, $answer->correctanswerlength,
371                  $answer->correctanswerformat, $unit);
372            $numericalquestion->options->answers[$key]->answer = $correctanswer->answer;
373         }
374         $numericalquestion->questiontext = parent::substitute_variables(
375         $numericalquestion->questiontext, $state->options->dataset);
376         //evaluate the equations i.e {=5+4)   
377         $qtext = "";
378         $qtextremaining = $numericalquestion->questiontext ;
379         while  (ereg('\{=([^[:space:]}]*)}', $qtextremaining, $regs1)) {
380             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
381             $qtext =$qtext.$qtextsplits[0];
382             $qtextremaining = $qtextsplits[1];
383             if (empty($regs1[1])) {
384                     $str = '';
385                 } else {
386                     if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
387                         $str=$formulaerrors ;
388                     }else {  
389                         eval('$str = '.$regs1[1].';');            
390                     }
391                 }
392                 $qtext = $qtext.$str ; 
393         } 
394         $numericalquestion->questiontext = $qtext.$qtextremaining ; // end replace equations
395         $virtualqtype->print_question_formulation_and_controls($numericalquestion, $state, $cmoptions, $options);
396     }
397     function grade_responses(&$question, &$state, $cmoptions) {
398         // Forward the grading to the virtual qtype 
399         // We modify the question to look like a numerical question
400         $numericalquestion = fullclone($question);
401        foreach ($numericalquestion->options->answers as $key => $answer) {
402             $answer = $numericalquestion->options->answers[$key]->answer; // for PHP 4.x
403           $numericalquestion->options->answers[$key]->answer = $this->substitute_variables($answer,
404              $state->options->dataset);
405        }
406          $virtualqtype = $this->get_virtual_qtype();
407         return $virtualqtype->grade_responses($numericalquestion, $state, $cmoptions) ;
408     }
410     function response_summary($question, $state, $length=80) {
411         // The actual response is the bit after the hyphen
412         return substr($state->answer, strpos($state->answer, '-')+1, $length);
413     }
415     // ULPGC ecastro
416     function check_response(&$question, &$state) {
417         // Forward the checking to the virtual qtype
418         // We modify the question to look like a numerical question
419         $numericalquestion = clone($question);
420         $numericalquestion->options = clone($question->options);
421         foreach ($question->options->answers as $key => $answer) {
422             $numericalquestion->options->answers[$key] = clone($answer);
423         }
424         foreach ($numericalquestion->options->answers as $key => $answer) {
425             $answer = &$numericalquestion->options->answers[$key]; // for PHP 4.x
426             $answer->answer = $this->substitute_variables($answer->answer,
427              $state->options->dataset);
428         }
429         $virtualqtype = $this->get_virtual_qtype();
430         return $virtualqtype->check_response($numericalquestion, $state) ;
431     }
433     // ULPGC ecastro
434     function get_actual_response(&$question, &$state) {
435         // Substitute variables in questiontext before giving the data to the
436         // virtual type
437         $virtualqtype = $this->get_virtual_qtype();
438         $unit = $virtualqtype->get_default_numerical_unit($question);
440         // We modify the question to look like a numerical question
441         $numericalquestion = clone($question);
442         $numericalquestion->options = clone($question->options);
443         foreach ($question->options->answers as $key => $answer) {
444             $numericalquestion->options->answers[$key] = clone($answer);
445         }
446         foreach ($numericalquestion->options->answers as $key => $answer) {
447             $answer = &$numericalquestion->options->answers[$key]; // for PHP 4.x
448             $answer->answer = $this->substitute_variables($answer->answer,
449              $state->options->dataset);
450             // apply_unit
451         }
452         $numericalquestion->questiontext = $this->substitute_variables(
453                                   $numericalquestion->questiontext, $state->options->dataset);
454         $responses = $virtualqtype->get_all_responses($numericalquestion, $state);
455         $response = reset($responses->responses);
456         $correct = $response->answer.' : ';
458         $responses = $virtualqtype->get_actual_response($numericalquestion, $state);
460         foreach ($responses as $key=>$response){
461             $responses[$key] = $correct.$response;
462         }
464         return $responses;
465     }
467     function create_virtual_qtype() {
468         global $CFG;
469         require_once("$CFG->dirroot/question/type/numerical/questiontype.php");
470         return new question_numerical_qtype();
471     }
473     function supports_dataset_item_generation() {
474     // Calcualted support generation of randomly distributed number data
475         return true;
476     }
477     function custom_generator_tools_part(&$mform, $idx, $j){
479         $minmaxgrp = array();
480         $minmaxgrp[] =& $mform->createElement('text', "calcmin[$idx]", get_string('calcmin', 'qtype_datasetdependent'), 'size="3"');
481         $minmaxgrp[] =& $mform->createElement('text', "calcmax[$idx]", get_string('calcmax', 'qtype_datasetdependent'), 'size="3"');
482         $mform->addGroup($minmaxgrp, 'minmaxgrp', get_string('minmax', 'qtype_datasetdependent'), ' - ', false);
483         $mform->setType('calcmin', PARAM_NUMBER);
484         $mform->setType('calcmax', PARAM_NUMBER);
486         $precisionoptions = range(0, 10);
487         $mform->addElement('select', "calclength[$idx]", get_string('calclength', 'qtype_datasetdependent'), $precisionoptions);
489         $distriboptions = array('uniform' => get_string('uniform', 'qtype_datasetdependent'), 'loguniform' => get_string('loguniform', 'qtype_datasetdependent'));
490         $mform->addElement('select', "calcdistribution[$idx]", get_string('calcdistribution', 'qtype_datasetdependent'), $distriboptions);
493     }
495     function custom_generator_set_data($datasetdefs, $formdata){
496         $idx = 1;
497         foreach ($datasetdefs as $datasetdef){
498             if (ereg('^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$', $datasetdef->options, $regs)) {
499                 $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name";
500                 $formdata["calcdistribution[$idx]"] = $regs[1];
501                 $formdata["calcmin[$idx]"] = $regs[2];
502                 $formdata["calcmax[$idx]"] = $regs[3];
503                 $formdata["calclength[$idx]"] = $regs[4];
504             }
505             $idx++;
506         }
507         return $formdata;
508     }
510     function custom_generator_tools($datasetdef) {
511         if (ereg('^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$',
512                 $datasetdef->options, $regs)) {
513             $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name";
514             for ($i = 0 ; $i<10 ; ++$i) {
515                 $lengthoptions[$i] = get_string(($regs[1] == 'uniform'
516                                                 ? 'decimals'
517                                                 : 'significantfigures'), 'quiz', $i);
518             }
519             return '<input type="submit" onclick="'
520                     . "getElementById('addform').regenerateddefid.value='$defid'; return true;"
521                     .'" value="'. get_string('generatevalue', 'quiz') . '"/><br/>'
522                     . '<input type="text" size="3" name="calcmin[]" '
523                     . " value=\"$regs[2]\"/> &amp; <input name=\"calcmax[]\" "
524                     . ' type="text" size="3" value="' . $regs[3] .'"/> '
525                     . choose_from_menu($lengthoptions, 'calclength[]',
526                                        $regs[4], // Selected
527                                        '', '', '', true) . '<br/>'
528                     . choose_from_menu(array('uniform' => get_string('uniform', 'quiz'),
529                                              'loguniform' => get_string('loguniform', 'quiz')),
530                                        'calcdistribution[]',
531                                        $regs[1], // Selected
532                                        '', '', '', true);
533         } else {
534             return '';
535         }
536     }
539     function update_dataset_options($datasetdefs, $form) {
540         // Do we have informatin about new options???
541         if (empty($form->definition) || empty($form->calcmin)
542                 || empty($form->calcmax) || empty($form->calclength)
543                 || empty($form->calcdistribution)) {
544             // I guess not
546         } else {
547             // Looks like we just could have some new information here
548             $uniquedefs = array_values(array_unique($form->definition));
549             foreach ($uniquedefs as $key => $defid) {
550                 if (isset($datasetdefs[$defid])
551                         && is_numeric($form->calcmin[$key+1])
552                         && is_numeric($form->calcmax[$key+1])
553                         && is_numeric($form->calclength[$key+1])) {
554                     switch     ($form->calcdistribution[$key+1]) {
555                         case 'uniform': case 'loguniform':
556                             $datasetdefs[$defid]->options =
557                                     $form->calcdistribution[$key+1] . ':'
558                                     . $form->calcmin[$key+1] . ':'
559                                     . $form->calcmax[$key+1] . ':'
560                                     . $form->calclength[$key+1];
561                             break;
562                         default:
563                             notify("Unexpected distribution ".$form->calcdistribution[$key+1]);
564                     }
565                 }
566             }
567         }
569         // Look for empty options, on which we set default values
570         foreach ($datasetdefs as $defid => $def) {
571             if (empty($def->options)) {
572                 $datasetdefs[$defid]->options = 'uniform:1.0:10.0:1';
573             }
574         }
575         return $datasetdefs;
576     }
578     function save_dataset_items($question, $fromform){
579         global $CFG ;
580         // max datasets = 100 items
581         $max100 = 100 ;
582         $regenerate = optional_param('forceregeneration', 0, PARAM_BOOL);
583         // echo "<pre>"; print_r($fromform);
584         if (empty($question->options)) {
585             $this->get_question_options($question);
586         }
587         //get the old datasets for this question
588         $datasetdefs = $this->get_dataset_definitions($question->id, array());
589         // Handle generator options...
590         $olddatasetdefs = fullclone($datasetdefs);
591         $datasetdefs = $this->update_dataset_options($datasetdefs, $fromform);
592         $maxnumber = -1;
593         foreach ($datasetdefs as $defid => $datasetdef) {
594             if (isset($datasetdef->id)
595              && $datasetdef->options != $olddatasetdefs[$defid]->options) {
596                 // Save the new value for options
597                 update_record('question_dataset_definitions', $datasetdef);
599             }
600             // Get maxnumber
601             if ($maxnumber == -1 || $datasetdef->itemcount < $maxnumber) {
602                 $maxnumber = $datasetdef->itemcount;
603             }
604         }
605         // Handle adding and removing of dataset items
606         $i = 1;
607         ksort($fromform->definition);
608         foreach ($fromform->definition as $key => $defid) {
609             //if the delete button has not been pressed then skip the datasetitems
610             //in the 'add item' part of the form.
611             if ((!isset($fromform->addbutton)) && ($i > (count($datasetdefs)*$maxnumber))) {
612                 break;
613             }
614             $addeditem = new stdClass();
615             $addeditem->definition = $datasetdefs[$defid]->id;
616             $addeditem->value = $fromform->number[$i];
617             $addeditem->itemnumber = ceil($i / count($datasetdefs));
619             if ($fromform->itemid[$i]) {
620                 // Reuse any previously used record
621                 $addeditem->id = $fromform->itemid[$i];
622                 if (!update_record('question_dataset_items', $addeditem)) {
623                     error("Error: Unable to update dataset item");
624                 }
625             } else {
626                 if (!insert_record('question_dataset_items', $addeditem)) {
627                     error("Error: Unable to insert dataset item");
628                 }
629             }
631             $i++;
632         }
633         if ($maxnumber < $addeditem->itemnumber){
634             $maxnumber = $addeditem->itemnumber;
635             foreach ($datasetdefs as $key => $newdef) {
636                 if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
637                     $newdef->itemcount = $maxnumber;
638                     // Save the new value for options
639                     update_record('question_dataset_definitions', $newdef);
640                 }
641             }
642         }
643         // adding supplementary items
644         $numbertoadd =0;
645         if (isset($fromform->addbutton) && $fromform->selectadd > 1 && $maxnumber < $max100 ) {
646             $numbertoadd =$fromform->selectadd-1 ;
647             if ( $max100 - $maxnumber < $numbertoadd ) {
648                 $numbertoadd = $max100 - $maxnumber ;
649             }
650             //add the other items.
651             // Generate a new dataset item (or reuse an old one)
652             foreach ($datasetdefs as $defid => $datasetdef) {
653                 if (isset($datasetdef->id)) {
654                     $datasetdefs[$defid]->items = get_records_sql( // Use number as key!!
655                           " SELECT itemnumber, definition, id, value
656                             FROM {$CFG->prefix}question_dataset_items
657                             WHERE definition = $datasetdef->id ORDER BY itemnumber");
658                 }
659                 // echo "<pre>"; print_r($datasetdefs[$defid]->items);
660                 for ($numberadded =$maxnumber+1 ; $numberadded <= $maxnumber+$numbertoadd ; $numberadded++){
661                     if (isset($datasetdefs[$defid]->items[$numberadded]) && ! $regenerate ){
662                         //  echo "<p>Reuse an previously used record".$numberadded."id".$datasetdef->id."</p>";
663                     } else {
664                         $datasetitem = new stdClass;
665                         $datasetitem->definition = $datasetdef->id ;
666                         $datasetitem->itemnumber = $numberadded;
667                         if ($this->supports_dataset_item_generation()) {
668                             $datasetitem->value = $this->generate_dataset_item($datasetdef->options);
669                         } else {
670                             $datasetitem->value = '';
671                         }
672                         //pp  echo "<pre>"; print_r( $datasetitem );
673                         if (!insert_record('question_dataset_items', $datasetitem)) {
674                             error("Error: Unable to insert new dataset item");
675                         }                        
676                     }
677                 }//for number added
678             }// datasetsdefs end
679             $maxnumber += $numbertoadd ;
680             foreach ($datasetdefs as $key => $newdef) {
681                 if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
682                     $newdef->itemcount = $maxnumber;
683                     // Save the new value for options
684                     update_record('question_dataset_definitions', $newdef);
685                 }
686             }
687         }        
689         if (isset($fromform->deletebutton))  {
690             if(isset($fromform->selectdelete)) $newmaxnumber = $maxnumber-$fromform->selectdelete ;
691             else $newmaxnumber = $maxnumber-1 ;
692             if ($newmaxnumber < 0 ) $newmaxnumber = 0 ;
693             foreach ($datasetdefs as $datasetdef) {
694                 if ($datasetdef->itemcount == $maxnumber) {
695                     $datasetdef->itemcount= $newmaxnumber ;        
696                     if (!update_record('question_dataset_definitions',
697                                        $datasetdef)) {
698                          error("Error: Unable to update itemcount");
699                     }
700                 }
701             }
702        }
703     }
704     function generate_dataset_item($options) {
705         if (!ereg('^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$',
706                 $options, $regs)) {
707             // Unknown options...
708             return false;
709         }
710         if ($regs[1] == 'uniform') {
711             $nbr = $regs[2] + ($regs[3]-$regs[2])*mt_rand()/mt_getrandmax();
712             return round($nbr, $regs[4]);
714         } else if ($regs[1] == 'loguniform') {
715             $log0 = log(abs($regs[2])); // It would have worked the other way to
716             $nbr = exp($log0 + (log(abs($regs[3])) - $log0)*mt_rand()/mt_getrandmax());
718             // Reformat according to the precision $regs[4]:
720             // Determine the format 0.[1-9][0-9]* for the nbr...
721             $p10 = 0;
722             while ($nbr < 1) {
723                 --$p10;
724                 $nbr *= 10;
725             }
726             while ($nbr >= 1) {
727                 ++$p10;
728                 $nbr /= 10;
729             }
730             // ... and have the nbr rounded off to the correct length
731             $nbr = round($nbr, $regs[4]);
733             // Have the nbr written on a suitable format,
734             // Either scientific or plain numeric
735             if (-2 > $p10 || 4 < $p10) {
736                 // Use scientific format:
737                 $eX = 'e'.--$p10;
738                 $nbr *= 10;
739                 if (1 == $regs[4]) {
740                     $nbr = $nbr.$eX;
741                 } else {
742                     // Attach additional zeros at the end of $nbr,
743                     $nbr .= (1==strlen($nbr) ? '.' : '')
744                             . '00000000000000000000000000000000000000000x';
745                     $nbr = substr($nbr, 0, $regs[4] +1).$eX;
746                 }
747             } else {
748                 // Stick to plain numeric format
749                 $nbr *= "1e$p10";
750                 if (0.1 <= $nbr / "1e$regs[4]") {
751                     $nbr = $nbr;
752                 } else {
753                     // Could be an idea to add some zeros here
754                     $nbr .= (ereg('^[0-9]*$', $nbr) ? '.' : '')
755                             . '00000000000000000000000000000000000000000x';
756                     $oklen = $regs[4] + ($p10 < 1 ? 2-$p10 : 1);
757                     $nbr = substr($nbr, 0, $oklen);
758                 }
759             }
761             // The larger of the values decide the sign in case the
762             // have equal different signs (which they really must not have)
763             if ($regs[2] + $regs[3] > 0) {
764                 return $nbr;
765             } else {
766                 return -$nbr;
767             }
769         } else {
770             error("The distribution $regs[1] caused problems");
771         }
772         return '';
773     }
775     function comment_header($question) {
776         //$this->get_question_options($question);
777         $strheader = '';
778         $delimiter = '';
780         $answers = $question->options->answers;
782         foreach ($answers as $answer) {
783             if (is_string($answer)) {
784                 $strheader .= $delimiter.$answer;
785             } else {
786                 $strheader .= $delimiter.$answer->answer;
787             }
788             $delimiter = '<br/><br/>';
789         }
790         return $strheader;
791     }
793     function comment_on_datasetitems($question, $data, $number) {
794         /// Find a default unit:
795         if (!empty($question->id) && $unit = get_record('question_numerical_units',
796                 'question', $question->id, 'multiplier', 1.0)) {
797             $unit = $unit->unit;
798         } else {
799             $unit = '';
800         }
802         $answers = $question->options->answers;
803         $stranswers = '';
804         $strmin = get_string('min', 'quiz');
805         $strmax = get_string('max', 'quiz');
806         $errors = '';
807         $delimiter = ': ';
808         $virtualqtype = $this->get_virtual_qtype();
809         foreach ($answers as $answer) {
810             $formula = $answer->answer;
811             foreach ($data as $name => $value) {
812                 $formula = str_replace('{'.$name.'}', $value, $formula);
813             }
814             $calculated = qtype_calculated_calculate_answer(
815                     $answer->answer, $data, $answer->tolerance,
816                     $answer->tolerancetype, $answer->correctanswerlength,
817                     $answer->correctanswerformat, $unit);
818             $calculated->tolerance = $answer->tolerance;
819             $calculated->tolerancetype = $answer->tolerancetype;
820             $calculated->correctanswerlength = $answer->correctanswerlength;
821             $calculated->correctanswerformat = $answer->correctanswerformat;
822             $virtualqtype->get_tolerance_interval($calculated);
823             if ($calculated->min === '') {
824                 // This should mean that something is wrong
825                 $stranswers .= " -$calculated->answer".'<br/><br/>';                
826             } else {
827                 $stranswers .= $formula.' = '.$calculated->answer.'<br/>' ;
828                 $stranswers .= $strmin. $delimiter.$calculated->min.'---';
829                 $stranswers .= $strmax.$delimiter.$calculated->max;
830                 $stranswers .='<br/>';
831             }
832         }
833         return "$stranswers";
834     }
836     function tolerance_types() {
837         return array('1'  => get_string('relative', 'quiz'),
838                      '2'  => get_string('nominal', 'quiz'),
839                      '3'  => get_string('geometric', 'quiz'));
840     }
842     function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false) {
843     // Takes datasets from the parent implementation but
844     // filters options that are currently not accepted by calculated
845     // It also determines a default selection...
846     //$renameabledatasets not implemented anmywhere 
847         list($options, $selected) = parent::dataset_options($form, $name,'','qtype_calculated');
848   //  list($options, $selected) = $this->dataset_optionsa($form, $name);
850         foreach ($options as $key => $whatever) {
851             if (!ereg('^'.LITERAL.'-', $key) && $key != '0') {
852                 unset($options[$key]);
853             }
854         }
855         if (!$selected) {
856             if ($mandatory){             
857             $selected = LITERAL . "-0-$name"; // Default
858             }else {
859                 $selected = "0"; // Default
860             }    
861         }
862         return array($options, $selected);
863     }
865     function construct_dataset_menus($form, $mandatorydatasets,
866                                      $optionaldatasets) {
867         $datasetmenus = array();
868         foreach ($mandatorydatasets as $datasetname) {
869             if (!isset($datasetmenus[$datasetname])) {
870                 list($options, $selected) =
871                         $this->dataset_options($form, $datasetname);
872                 unset($options['0']); // Mandatory...
873                 $datasetmenus[$datasetname] = choose_from_menu ($options,
874                         'dataset[]', $selected, '', '', "0", true);
875             }
876         }
877         foreach ($optionaldatasets as $datasetname) {
878             if (!isset($datasetmenus[$datasetname])) {
879                 list($options, $selected) =
880                         $this->dataset_options($form, $datasetname);
881                 $datasetmenus[$datasetname] = choose_from_menu ($options,
882                         'dataset[]', $selected, '', '', "0", true);
883             }
884         }
885         return $datasetmenus;
886     }
888     function get_correct_responses(&$question, &$state) {
889         $virtualqtype = $this->get_virtual_qtype();
890         if($unit = $virtualqtype->get_default_numerical_unit($question)){
891              $unit = $unit->unit;
892         } else {
893             $unit = '';
894         }          
895         foreach ($question->options->answers as $answer) {
896             if (((int) $answer->fraction) === 1) {
897                 $answernumerical = qtype_calculated_calculate_answer(
898                  $answer->answer, $state->options->dataset, $answer->tolerance,
899                  $answer->tolerancetype, $answer->correctanswerlength,
900                  $answer->correctanswerformat, $unit);
901                 return array('' => $answernumerical->answer);
902             }
903         }
904         return null;
905     }
907     function substitute_variables($str, $dataset) {
908         $formula = parent::substitute_variables($str, $dataset);
909         if ($error = qtype_calculated_find_formula_errors($formula)) {
910             return $error;
911         }
912         /// Calculate the correct answer
913         if (empty($formula)) {
914             $str = '';
915         } else {
916             eval('$str = '.$formula.';');            
917         }
918         return $str;
919     }
920     
921     /**
922     * This function retrieve the item count of the available category shareable
923     * wild cards that is added as a comment displayed when a wild card with 
924     * the same name is displayed in datasetdefinitions_form.php
925     */ 
926     function get_dataset_definitions_category($form) {
927         global $CFG;
928         $datasetdefs = array();
929         $lnamemax = 30;
930         if (!empty($form->category)) {            
931             $sql = "SELECT i.*,d.*
932                     FROM {$CFG->prefix}question_datasets d,
933                          {$CFG->prefix}question_dataset_definitions i    
934                   WHERE i.id = d.datasetdefinition
935                     AND i.category = '$form->category'
936                     ;
937                    ";
938              if ($records = get_records_sql($sql)) {
939                    foreach ($records as $r) {
940                        if ( !isset ($datasetdefs["$r->name"])) $datasetdefs["$r->name"] = $r->itemcount;
941                     }
942                 }
943         }  
944         return  $datasetdefs ;
945     }  
947     /**
948     * This function build a table showing the available category shareable
949     * wild cards, their name, their definition (Min, Max, Decimal) , the item count
950     * and the name of the question where they are used.
951     * This table is intended to be add before the question text to help the user use 
952     * these wild cards
953     */                          
954         
955     function print_dataset_definitions_category($form) {
956         global $CFG;
957         $datasetdefs = array();
958         $lnamemax = 22;
959         $namestr =get_string('name', 'quiz');
960         $minstr=get_string('min', 'quiz');
961         $maxstr=get_string('max', 'quiz');
962         $rangeofvaluestr=get_string('minmax','qtype_datasetdependent');
963         $questionusingstr = get_string('usedinquestion','qtype_calculated');
964         $wildcardstr =  get_string('wildcard', 'qtype_calculated');
965         $itemscountstr = get_string('itemscount','qtype_datasetdependent');
966        $text ='';
967         if (!empty($form->category)) {           
968             $sql = "SELECT i.*,d.*
969                     FROM {$CFG->prefix}question_datasets d,
970                          {$CFG->prefix}question_dataset_definitions i    
971                     WHERE i.id = d.datasetdefinition
972                     AND i.category = '$form->category'; 
973                     " ;
974             if ($records = get_records_sql($sql)) {
975                 foreach ($records as $r) {
976                     $sql1 = "SELECT q.*
977                         FROM  {$CFG->prefix}question q                    
978                              WHERE q.id = $r->question                    
979                     ";                  
980                     if ( !isset ($datasetdefs["$r->type-$r->category-$r->name"])){
981                         $datasetdefs["$r->type-$r->category-$r->name"]= $r;
982                     }
983                     if ($questionb = get_records_sql($sql1)) {
984                         $datasetdefs["$r->type-$r->category-$r->name"]->questions[$r->question]->name =$questionb[$r->question]->name ;
985                     }
986                 }
987             }
988         }
989         if (!empty ($datasetdefs)){
990             
991             $text ="<table width=\"100%\" border=\"1\"><tr><th  style=\"white-space:nowrap;\" class=\"header\" scope=\"col\" >$namestr</th><th   style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$rangeofvaluestr</th><th  style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$itemscountstr</th><th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$questionusingstr</th></tr>";  
992             foreach ($datasetdefs as $datasetdef){
993                 list($distribution, $min, $max,$dec) = explode(':', $datasetdef->options, 4);
994                 $text .="<tr><td valign=\"top\" align=\"center\"> $datasetdef->name </td><td align=\"center\" valign=\"top\"> $min <strong>-</strong> $max </td><td align=\"right\" valign=\"top\">$datasetdef->itemcount&nbsp;&nbsp;</td><td align=\"left\">";
995                 foreach ($datasetdef->questions as $qu) {
996                     //limit the name length displayed
997                     if (!empty($qu->name)) {
998                         $qu->name = (strlen($qu->name) > $lnamemax) ?
999                         substr($qu->name, 0, $lnamemax).'...' : $qu->name;
1000                     } else {
1001                         $qu->name = '';
1002                     }
1003                     $text .=" &nbsp;&nbsp; $qu->name <br/>";  
1004                 }  
1005                 $text .="</td></tr>";
1006             }
1007             $text .="</table>";
1008         }else{
1009              $text .=get_string('nosharedwildcard', 'qtype_calculated'); 
1010         }
1011         return  $text ;
1012     }    
1015 /// BACKUP FUNCTIONS ////////////////////////////
1017     /*
1018      * Backup the data in the question
1019      *
1020      * This is used in question/backuplib.php
1021      */
1022     function backup($bf,$preferences,$question,$level=6) {
1024         $status = true;
1026         $calculateds = get_records("question_calculated","question",$question,"id");
1027         //If there are calculated-s
1028         if ($calculateds) {
1029             //Iterate over each calculateds
1030             foreach ($calculateds as $calculated) {
1031                 $status = $status &&fwrite ($bf,start_tag("CALCULATED",$level,true));
1032                 //Print calculated contents
1033                 fwrite ($bf,full_tag("ANSWER",$level+1,false,$calculated->answer));
1034                 fwrite ($bf,full_tag("TOLERANCE",$level+1,false,$calculated->tolerance));
1035                 fwrite ($bf,full_tag("TOLERANCETYPE",$level+1,false,$calculated->tolerancetype));
1036                 fwrite ($bf,full_tag("CORRECTANSWERLENGTH",$level+1,false,$calculated->correctanswerlength));
1037                 fwrite ($bf,full_tag("CORRECTANSWERFORMAT",$level+1,false,$calculated->correctanswerformat));
1038                 //Now backup numerical_units
1039                 $status = question_backup_numerical_units($bf,$preferences,$question,7);
1040                 //Now backup required dataset definitions and items...
1041                 $status = question_backup_datasets($bf,$preferences,$question,7);
1042                 //End calculated data
1043                 $status = $status &&fwrite ($bf,end_tag("CALCULATED",$level,true));
1044             }
1045             //Now print question_answers
1046             $status = question_backup_answers($bf,$preferences,$question);
1047         }
1048         return $status;
1049     }
1051 /// RESTORE FUNCTIONS /////////////////
1053     /*
1054      * Restores the data in the question
1055      *
1056      * This is used in question/restorelib.php
1057      */
1058     function restore($old_question_id,$new_question_id,$info,$restore) {
1060         $status = true;
1062         //Get the calculated-s array
1063         $calculateds = $info['#']['CALCULATED'];
1065         //Iterate over calculateds
1066         for($i = 0; $i < sizeof($calculateds); $i++) {
1067             $cal_info = $calculateds[$i];
1068             //traverse_xmlize($cal_info);                                                                 //Debug
1069             //print_object ($GLOBALS['traverse_array']);                                                  //Debug
1070             //$GLOBALS['traverse_array']="";                                                              //Debug
1072             //Now, build the question_calculated record structure
1073             $calculated->question = $new_question_id;
1074             $calculated->answer = backup_todb($cal_info['#']['ANSWER']['0']['#']);
1075             $calculated->tolerance = backup_todb($cal_info['#']['TOLERANCE']['0']['#']);
1076             $calculated->tolerancetype = backup_todb($cal_info['#']['TOLERANCETYPE']['0']['#']);
1077             $calculated->correctanswerlength = backup_todb($cal_info['#']['CORRECTANSWERLENGTH']['0']['#']);
1078             $calculated->correctanswerformat = backup_todb($cal_info['#']['CORRECTANSWERFORMAT']['0']['#']);
1080             ////We have to recode the answer field
1081             $answer = backup_getid($restore->backup_unique_code,"question_answers",$calculated->answer);
1082             if ($answer) {
1083                 $calculated->answer = $answer->new_id;
1084             }
1086             //The structure is equal to the db, so insert the question_calculated
1087             $newid = insert_record ("question_calculated",$calculated);
1089             //Do some output
1090             if (($i+1) % 50 == 0) {
1091                 if (!defined('RESTORE_SILENTLY')) {
1092                     echo ".";
1093                     if (($i+1) % 1000 == 0) {
1094                         echo "<br />";
1095                     }
1096                 }
1097                 backup_flush(300);
1098             }
1100             //Now restore numerical_units
1101             $status = question_restore_numerical_units ($old_question_id,$new_question_id,$cal_info,$restore);
1103             //Now restore dataset_definitions
1104             if ($status && $newid) {
1105                 $status = question_restore_dataset_definitions ($old_question_id,$new_question_id,$cal_info,$restore);
1106             }
1108             if (!$newid) {
1109                 $status = false;
1110             }
1111         }
1113         return $status;
1114     }
1116 //// END OF CLASS ////
1118 //////////////////////////////////////////////////////////////////////////
1119 //// INITIATION - Without this line the question type is not in use... ///
1120 //////////////////////////////////////////////////////////////////////////
1121 question_register_questiontype(new question_calculated_qtype());
1123 function qtype_calculated_calculate_answer($formula, $individualdata,
1124         $tolerance, $tolerancetype, $answerlength, $answerformat='1', $unit='') {
1125 /// The return value has these properties:
1126 /// ->answer    the correct answer
1127 /// ->min       the lower bound for an acceptable response
1128 /// ->max       the upper bound for an accetpable response
1130     /// Exchange formula variables with the correct values...
1131     global $QTYPES;
1132     $answer = $QTYPES['calculated']->substitute_variables($formula, $individualdata);
1133     if ('1' == $answerformat) { /* Answer is to have $answerlength decimals */
1134         /*** Adjust to the correct number of decimals ***/
1136         $calculated->answer = round($answer, $answerlength);
1138         if ($answerlength) {
1139             /* Try to include missing zeros at the end */
1141             if (ereg('^(.*\\.)(.*)$', $calculated->answer, $regs)) {
1142                 $calculated->answer = $regs[1] . substr(
1143                         $regs[2] . '00000000000000000000000000000000000000000x',
1144                         0, $answerlength)
1145                         . $unit;
1146             } else {
1147                 $calculated->answer .=
1148                         substr('.00000000000000000000000000000000000000000x',
1149                         0, $answerlength + 1) . $unit;
1150             }
1151         } else {
1152             /* Attach unit */
1153             $calculated->answer .= $unit;
1154         }
1156     } else if ($answer) { // Significant figures does only apply if the result is non-zero
1158         // Convert to positive answer...
1159         if ($answer < 0) {
1160             $answer = -$answer;
1161             $sign = '-';
1162         } else {
1163             $sign = '';
1164         }
1166         // Determine the format 0.[1-9][0-9]* for the answer...
1167         $p10 = 0;
1168         while ($answer < 1) {
1169             --$p10;
1170             $answer *= 10;
1171         }
1172         while ($answer >= 1) {
1173             ++$p10;
1174             $answer /= 10;
1175         }
1176         // ... and have the answer rounded of to the correct length
1177         $answer = round($answer, $answerlength);
1179         // Have the answer written on a suitable format,
1180         // Either scientific or plain numeric
1181         if (-2 > $p10 || 4 < $p10) {
1182             // Use scientific format:
1183             $eX = 'e'.--$p10;
1184             $answer *= 10;
1185             if (1 == $answerlength) {
1186                 $calculated->answer = $sign.$answer.$eX.$unit;
1187             } else {
1188                 // Attach additional zeros at the end of $answer,
1189                 $answer .= (1==strlen($answer) ? '.' : '')
1190                         . '00000000000000000000000000000000000000000x';
1191                 $calculated->answer = $sign
1192                         .substr($answer, 0, $answerlength +1).$eX.$unit;
1193             }
1194         } else {
1195             // Stick to plain numeric format
1196             $answer *= "1e$p10";
1197             if (0.1 <= $answer / "1e$answerlength") {
1198                 $calculated->answer = $sign.$answer.$unit;
1199             } else {
1200                 // Could be an idea to add some zeros here
1201                 $answer .= (ereg('^[0-9]*$', $answer) ? '.' : '')
1202                         . '00000000000000000000000000000000000000000x';
1203                 $oklen = $answerlength + ($p10 < 1 ? 2-$p10 : 1);
1204                 $calculated->answer = $sign.substr($answer, 0, $oklen).$unit;
1205             }
1206         }
1208     } else {
1209         $calculated->answer = 0.0;
1210     }
1212     /// Return the result
1213     return $calculated;
1217 function qtype_calculated_find_formula_errors($formula) {
1218 /// Validates the formula submitted from the question edit page.
1219 /// Returns false if everything is alright.
1220 /// Otherwise it constructs an error message
1221     // Strip away dataset names
1222     while (ereg('\\{[[:alpha:]][^>} <{"\']*\\}', $formula, $regs)) {
1223         $formula = str_replace($regs[0], '1', $formula);
1224     }
1226     // Strip away empty space and lowercase it
1227     $formula = strtolower(str_replace(' ', '', $formula));
1229     $safeoperatorchar = '-+/*%>:^~<?=&|!'; /* */
1230     $operatorornumber = "[$safeoperatorchar.0-9eE]";
1233     while (ereg("(^|[$safeoperatorchar,(])([a-z0-9_]*)\\(($operatorornumber+(,$operatorornumber+((,$operatorornumber+)+)?)?)?\\)",
1234             $formula, $regs)) {
1236         switch ($regs[2]) {
1237             // Simple parenthesis
1238             case '':
1239                 if ($regs[4] || strlen($regs[3])==0) {
1240                     return get_string('illegalformulasyntax', 'quiz', $regs[0]);
1241                 }
1242                 break;
1244             // Zero argument functions
1245             case 'pi':
1246                 if ($regs[3]) {
1247                     return get_string('functiontakesnoargs', 'quiz', $regs[2]);
1248                 }
1249                 break;
1251             // Single argument functions (the most common case)
1252             case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh':
1253             case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos':
1254             case 'cosh': case 'decbin': case 'decoct': case 'deg2rad':
1255             case 'exp': case 'expm1': case 'floor': case 'is_finite':
1256             case 'is_infinite': case 'is_nan': case 'log10': case 'log1p':
1257             case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt':
1258             case 'tan': case 'tanh':
1259                 if ($regs[4] || empty($regs[3])) {
1260                     return get_string('functiontakesonearg','quiz',$regs[2]);
1261                 }
1262                 break;
1264             // Functions that take one or two arguments
1265             case 'log': case 'round':
1266                 if ($regs[5] || empty($regs[3])) {
1267                     return get_string('functiontakesoneortwoargs','quiz',$regs[2]);
1268                 }
1269                 break;
1271             // Functions that must have two arguments
1272             case 'atan2': case 'fmod': case 'pow':
1273                 if ($regs[5] || empty($regs[4])) {
1274                     return get_string('functiontakestwoargs', 'quiz', $regs[2]);
1275                 }
1276                 break;
1278             // Functions that take two or more arguments
1279             case 'min': case 'max':
1280                 if (empty($regs[4])) {
1281                     return get_string('functiontakesatleasttwo','quiz',$regs[2]);
1282                 }
1283                 break;
1285             default:
1286                 return get_string('unsupportedformulafunction','quiz',$regs[2]);
1287         }
1289         // Exchange the function call with '1' and then chack for
1290         // another function call...
1291         if ($regs[1]) {
1292             // The function call is proceeded by an operator
1293             $formula = str_replace($regs[0], $regs[1] . '1', $formula);
1294         } else {
1295             // The function call starts the formula
1296             $formula = ereg_replace("^$regs[2]\\([^)]*\\)", '1', $formula);
1297         }
1298     }
1300     if (ereg("[^$safeoperatorchar.0-9eE]+", $formula, $regs)) {
1301         return get_string('illegalformulasyntax', 'quiz', $regs[0]);
1302     } else {
1303         // Formula just might be valid
1304         return false;
1305     }
1309 function dump($obj) {
1310     echo "<pre>\n";
1311     var_dump($obj);
1312     echo "</pre><br />\n";
1315 ?>