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