Typos in comments
[moodle.git] / question / type / calculated / questiontype.php
CommitLineData
aeb15530 1<?php
3f76dd52 2
516cf3eb 3/////////////////
a2156789 4// CALCULATED ///
516cf3eb 5/////////////////
6
7/// QUESTION TYPE CLASS //////////////////
8
516cf3eb 9
fd6b864f 10
fbe2cfea 11class question_calculated_qtype extends default_questiontype {
516cf3eb 12
13 // Used by the function custom_generator_tools:
14 var $calcgenerateidhasbeenadded = false;
fbe2cfea 15 public $virtualqtype = false;
516cf3eb 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
ab8b5142 31 // the code is used for calculated, calculatedsimple and calculatedmulti qtypes
c5da9906 32 global $CFG, $DB, $OUTPUT, $QTYPES;
28a27ef1 33 if (!$question->options = $DB->get_record('question_calculated_options', array('question' => $question->id))) {
34 // echo $OUTPUT->notification('Error: Missing question options for calculated question'.$question->id.'!');
35 // return false;
ab8b5142 36 $question->options->synchronize = 0;
ab8b5142
PP
37 $question->options->single = 0; //$question->single;
38 $question->options->answernumbering = 'abc';
39 $question->options->shuffleanswers = 0 ;
40 $question->options->correctfeedback = '';
41 $question->options->partiallycorrectfeedback = '';
42 $question->options->incorrectfeedback = '';
aeb15530 43
28a27ef1 44 }
45
f34488b2 46 if (!$question->options->answers = $DB->get_records_sql(
a6d46515 47 "SELECT a.*, c.tolerance, c.tolerancetype, c.correctanswerlength, c.correctanswerformat " .
f34488b2 48 "FROM {question_answers} a, " .
49 " {question_calculated} c " .
50 "WHERE a.question = ? " .
a6d46515 51 "AND a.id = c.answer ".
f34488b2 52 "ORDER BY a.id ASC", array($question->id))) {
fef8f84e 53 echo $OUTPUT->notification('Error: Missing question answer for calculated question ' . $question->id . '!');
516cf3eb 54 return false;
55 }
56
ab8b5142 57 if ( $this->get_virtual_qtype() == $QTYPES['numerical']){
ab8b5142 58 $QTYPES['numerical']->get_numerical_units($question);
cf146692 59 $QTYPES['numerical']->get_numerical_options($question);
a6d46515 60 }
61
c9e4ba36 62 if( isset($question->export_process)&&$question->export_process){
63 $question->options->datasets = $this->get_datasets_for_export($question);
f34488b2 64 }
516cf3eb 65 return true;
66 }
f34488b2 67
c9e4ba36 68 function get_datasets_for_export(&$question){
f34488b2 69 global $DB;
3f76dd52 70 $datasetdefs = array();
c9e4ba36 71 if (!empty($question->id)) {
3f76dd52 72 global $CFG;
73 $sql = "SELECT i.*
f34488b2 74 FROM {question_datasets} d,
75 {question_dataset_definitions} i
76 WHERE d.question = ?
3f76dd52 77 AND d.datasetdefinition = i.id
78 ";
f34488b2 79 if ($records = $DB->get_records_sql($sql, array($question->id))) {
3f76dd52 80 foreach ($records as $r) {
81 $def = $r ;
82 if ($def->category=='0'){
83 $def->status='private';
f34488b2 84 } else {
3f76dd52 85 $def->status='shared';
f34488b2 86 }
3f76dd52 87 $def->type ='calculated' ;
88 list($distribution, $min, $max,$dec) = explode(':', $def->options, 4);
89 $def->distribution=$distribution;
90 $def->minimum=$min;
91 $def->maximum=$max;
f34488b2 92 $def->decimals=$dec ;
3f76dd52 93 if ($def->itemcount > 0 ) {
94 // get the datasetitems
95 $def->items = array();
a2155a7b 96 if ($items = $this->get_database_dataset_items($def->id)){
3f76dd52 97 $n = 0;
98 foreach( $items as $ii){
99 $n++;
100 $def->items[$n] = new stdClass;
101 $def->items[$n]->itemnumber=$ii->itemnumber;
102 $def->items[$n]->value=$ii->value;
103 }
104 $def->number_of_items=$n ;
105 }
106 }
f34488b2 107 $datasetdefs["1-$r->category-$r->name"] = $def;
3f76dd52 108 }
109 }
110 }
111 return $datasetdefs ;
f34488b2 112 }
113
516cf3eb 114 function save_question_options($question) {
115 //$options = $question->subtypeoptions;
116 // Get old answers:
ab8b5142 117 // the code is used for calculated, calculatedsimple and calculatedmulti qtypes
824f0182 118 global $CFG, $DB, $QTYPES ;
f7fa6874 119 if (isset($question->answer) && !isset($question->answers)) {
2aef1fe5 120 $question->answers = $question->answer;
121 }
1ee53ca9 122 // calculated options
aeb15530 123 $update = true ;
1ee53ca9 124 $options = $DB->get_record("question_calculated_options", array("question" => $question->id));
125 if (!$options) {
126 $update = false;
127 $options = new stdClass;
128 $options->question = $question->id;
129 }
ab8b5142 130 // as used only by calculated
1ee53ca9 131 $options->synchronize = $question->synchronize;
ab8b5142
PP
132 $options->single = 0; //$question->single;
133 $options->answernumbering = $question->answernumbering;
28a27ef1 134 $options->shuffleanswers = $question->shuffleanswers;
135 $options->correctfeedback = trim($question->correctfeedback);
136 $options->partiallycorrectfeedback = trim($question->partiallycorrectfeedback);
137 $options->incorrectfeedback = trim($question->incorrectfeedback);
1ee53ca9 138 if ($update) {
139 if (!$DB->update_record("question_calculated_options", $options)) {
140 $result->error = "Could not update calculated question options! (id=$options->id)";
141 return $result;
142 }
143 } else {
144 if (!$DB->insert_record("question_calculated_options", $options)) {
145 $result->error = "Could not insert calculated question options!";
146 return $result;
147 }
148 }
2aef1fe5 149
a6d46515 150 // Get old versions of the objects
f34488b2 151 if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) {
516cf3eb 152 $oldanswers = array();
153 }
154
f34488b2 155 if (!$oldoptions = $DB->get_records('question_calculated', array('question' => $question->id), 'answer ASC')) {
a6d46515 156 $oldoptions = array();
157 }
2aef1fe5 158
159 // Save the units.
ab8b5142 160 $virtualqtype = $this->get_virtual_qtype();
a6d46515 161 $result = $virtualqtype->save_numerical_units($question);
162 if (isset($result->error)) {
163 return $result;
164 } else {
165 $units = &$result->units;
166 }
167 // Insert all the new answers
0a6555f8 168 if (isset($question->answer) && !isset($question->answers)) {
169 $question->answers=$question->answer;
170 }
a6d46515 171 foreach ($question->answers as $key => $dataanswer) {
f34488b2 172 if ( trim($dataanswer) != '' ) {
a6d46515 173 $answer = new stdClass;
174 $answer->question = $question->id;
175 $answer->answer = trim($dataanswer);
176 $answer->fraction = $question->fraction[$key];
177 $answer->feedback = trim($question->feedback[$key]);
f34488b2 178
a6d46515 179 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
180 $answer->id = $oldanswer->id;
0bcf8b6f 181 $DB->update_record("question_answers", $answer);
a6d46515 182 } else { // This is a completely new answer
0bcf8b6f 183 $answer->id = $DB->insert_record("question_answers", $answer);
516cf3eb 184 }
a6d46515 185
186 // Set up the options object
187 if (!$options = array_shift($oldoptions)) {
188 $options = new stdClass;
516cf3eb 189 }
a6d46515 190 $options->question = $question->id;
191 $options->answer = $answer->id;
192 $options->tolerance = trim($question->tolerance[$key]);
193 $options->tolerancetype = trim($question->tolerancetype[$key]);
194 $options->correctanswerlength = trim($question->correctanswerlength[$key]);
195 $options->correctanswerformat = trim($question->correctanswerformat[$key]);
f34488b2 196
a6d46515 197 // Save options
198 if (isset($options->id)) { // reusing existing record
0bcf8b6f 199 $DB->update_record('question_calculated', $options);
a6d46515 200 } else { // new options
0bcf8b6f 201 $DB->insert_record('question_calculated', $options);
516cf3eb 202 }
203 }
204 }
a6d46515 205 // delete old answer records
206 if (!empty($oldanswers)) {
207 foreach($oldanswers as $oa) {
f34488b2 208 $DB->delete_records('question_answers', array('id' => $oa->id));
516cf3eb 209 }
a6d46515 210 }
211
212 // delete old answer records
213 if (!empty($oldoptions)) {
214 foreach($oldoptions as $oo) {
f34488b2 215 $DB->delete_records('question_calculated', array('id' => $oo->id));
516cf3eb 216 }
217 }
824f0182 218 $result = $QTYPES['numerical']->save_numerical_options($question);
219 if (isset($result->error)) {
220 return $result;
221 }
516cf3eb 222
a6d46515 223
3f76dd52 224 if( isset($question->import_process)&&$question->import_process){
225 $this->import_datasets($question);
f34488b2 226 }
a6d46515 227 // Report any problems.
228 if (!empty($result->notice)) {
229 return $result;
230 }
516cf3eb 231 return true;
232 }
233
3f76dd52 234 function import_datasets($question){
f34488b2 235 global $DB;
3f76dd52 236 $n = count($question->dataset);
237 foreach ($question->dataset as $dataset) {
f34488b2 238 // name, type, option,
3f76dd52 239 $datasetdef = new stdClass();
240 $datasetdef->name = $dataset->name;
241 $datasetdef->type = 1 ;
242 $datasetdef->options = $dataset->distribution.':'.$dataset->min.':'.$dataset->max.':'.$dataset->length;
f34488b2 243 $datasetdef->itemcount=$dataset->itemcount;
3f76dd52 244 if ( $dataset->status =='private'){
245 $datasetdef->category = 0;
246 $todo='create' ;
247 }else if ($dataset->status =='shared' ){
f34488b2 248 if ($sharedatasetdefs = $DB->get_records_select(
3f76dd52 249 'question_dataset_definitions',
250 "type = '1'
f34488b2 251 AND name = ?
252 AND category = ?
9e459630 253 ORDER BY id DESC ", array($dataset->name, $question->category)
3f76dd52 254 )) { // so there is at least one
255 $sharedatasetdef = array_shift($sharedatasetdefs);
256 if ( $sharedatasetdef->options == $datasetdef->options ){// identical so use it
257 $todo='useit' ;
258 $datasetdef =$sharedatasetdef ;
259 } else { // different so create a private one
260 $datasetdef->category = 0;
261 $todo='create' ;
f34488b2 262 }
cb024555 263 }else { // no so create one
264 $datasetdef->category =$question->category ;
265 $todo='create' ;
f34488b2 266 }
267 }
3f76dd52 268 if ( $todo=='create'){
bb4b6010 269 $datasetdef->id = $DB->insert_record( 'question_dataset_definitions', $datasetdef);
f34488b2 270 }
3f76dd52 271 // Create relation to the dataset:
272 $questiondataset = new stdClass;
273 $questiondataset->question = $question->id;
274 $questiondataset->datasetdefinition = $datasetdef->id;
bb4b6010 275 $DB->insert_record('question_datasets', $questiondataset);
3f76dd52 276 if ($todo=='create'){ // add the items
277 foreach ($dataset->datasetitem as $dataitem ){
278 $datasetitem = new stdClass;
279 $datasetitem->definition=$datasetdef->id ;
280 $datasetitem->itemnumber = $dataitem->itemnumber ;
281 $datasetitem->value = $dataitem->value ;
bb4b6010 282 $DB->insert_record('question_dataset_items', $datasetitem);
f34488b2 283 }
284 }
3f76dd52 285 }
286 }
f34488b2 287
fbe2cfea 288 function restore_session_and_responses(&$question, &$state) {
fef8f84e 289 global $OUTPUT;
6dbcacee 290 if (!preg_match('~^dataset([0-9]+)[^-]*-(.*)$~',
fbe2cfea 291 $state->responses[''], $regs)) {
fef8f84e 292 echo $OUTPUT->notification("Wrongly formatted raw response answer " .
fbe2cfea 293 "{$state->responses['']}! Could not restore session for " .
294 " question #{$question->id}.");
295 $state->options->datasetitem = 1;
296 $state->options->dataset = array();
297 $state->responses = array('' => '');
298 return false;
299 }
300
301 // Restore the chosen dataset
302 $state->options->datasetitem = $regs[1];
303 $state->options->dataset =
304 $this->pick_question_dataset($question,$state->options->datasetitem);
305 $state->responses = array('' => $regs[2]);
ab8b5142
PP
306 $virtualqtype = $this->get_virtual_qtype();
307 return $virtualqtype->restore_session_and_responses($question, $state);
fbe2cfea 308 }
309
310 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
311 // Find out how many datasets are available
28a27ef1 312 global $CFG, $DB, $QTYPES;
fbe2cfea 313 if(!$maxnumber = (int)$DB->get_field_sql(
314 "SELECT MIN(a.itemcount)
315 FROM {question_dataset_definitions} a,
316 {question_datasets} b
317 WHERE b.question = ?
318 AND a.id = b.datasetdefinition", array($question->id))) {
319 print_error('cannotgetdsforquestion', 'question', '', $question->id);
320 }
1ee53ca9 321 $sql = "SELECT i.*
322 FROM {question_datasets} d,
323 {question_dataset_definitions} i
324 WHERE d.question = ?
aeb15530
PS
325 AND d.datasetdefinition = i.id
326 AND i.category != 0
1ee53ca9 327 ";
328 if (!$question->options->synchronize || !$records = $DB->get_records_sql($sql, array($question->id))) {
aeb15530 329 $synchronize_calculated = false ;
1ee53ca9 330 }else {
aeb15530
PS
331 $synchronize_calculated = true ;
332 }
fbe2cfea 333
334 // Choose a random dataset
1ee53ca9 335 if ( $synchronize_calculated === false ) {
f96e83d4 336 $state->options->datasetitem = rand(1, $maxnumber);
337 }else{
aeb15530 338 $state->options->datasetitem = intval( $maxnumber * substr($attempt->timestart,-2) /100 ) ;
1ee53ca9 339 if ($state->options->datasetitem < 1) {
340 $state->options->datasetitem =1 ;
341 } else if ($state->options->datasetitem > $maxnumber){
342 $state->options->datasetitem = $maxnumber ;
f96e83d4 343 }
aeb15530
PS
344
345 };
fbe2cfea 346 $state->options->dataset =
347 $this->pick_question_dataset($question,$state->options->datasetitem);
ab8b5142 348 $virtualqtype = $this->get_virtual_qtype( );
aeb15530 349 return $virtualqtype->create_session_and_responses($question, $state, $cmoptions, $attempt);
c5da9906 350 }
aeb15530 351
ab8b5142 352
aeb15530 353
fbe2cfea 354 function save_session_and_responses(&$question, &$state) {
355 global $DB;
aeb15530 356 $responses = 'dataset'.$state->options->datasetitem.'-' ;
aeb15530 357 // regular numeric type
c5da9906 358 if(isset($state->responses['unit']) && isset($question->options->units[$state->responses['unit']])){
359 $responses .= $state->responses['answer'].'|||||'.$question->options->units[$state->responses['unit']]->unit;
360 }else if(isset($state->responses['unit'])){
361 $responses .= $state->responses['answer'].'|||||'.$state->responses['unit'] ;
362 }else {
363 $responses .= $state->responses['answer'].'|||||';
364 }
365
aeb15530 366 // Set the legacy answer field
fbe2cfea 367 if (!$DB->set_field('question_states', 'answer', $responses, array('id'=> $state->id))) {
368 return false;
369 }
370 return true;
371 }
372
516cf3eb 373 function create_runtime_question($question, $form) {
374 $question = parent::create_runtime_question($question, $form);
375 $question->options->answers = array();
376 foreach ($form->answers as $key => $answer) {
377 $a->answer = trim($form->answer[$key]);
a6d46515 378 $a->fraction = $form->fraction[$key];//new
379 $a->tolerance = $form->tolerance[$key];
516cf3eb 380 $a->tolerancetype = $form->tolerancetype[$key];
381 $a->correctanswerlength = $form->correctanswerlength[$key];
382 $a->correctanswerformat = $form->correctanswerformat[$key];
383 $question->options->answers[] = clone($a);
384 }
385
386 return $question;
387 }
388
389 function validate_form($form) {
390 switch($form->wizardpage) {
391 case 'question':
1d9ed698 392 $calculatedmessages = array();
516cf3eb 393 if (empty($form->name)) {
1d9ed698 394 $calculatedmessages[] = get_string('missingname', 'quiz');
516cf3eb 395 }
396 if (empty($form->questiontext)) {
1d9ed698 397 $calculatedmessages[] = get_string('missingquestiontext', 'quiz');
516cf3eb 398 }
399 // Verify formulas
400 foreach ($form->answers as $key => $answer) {
401 if ('' === trim($answer)) {
1d9ed698 402 $calculatedmessages[] =
403 get_string('missingformula', 'quiz');
516cf3eb 404 }
1d9ed698 405 if ($formulaerrors =
406 qtype_calculated_find_formula_errors($answer)) {
407 $calculatedmessages[] = $formulaerrors;
516cf3eb 408 }
409 if (! isset($form->tolerance[$key])) {
410 $form->tolerance[$key] = 0.0;
411 }
412 if (! is_numeric($form->tolerance[$key])) {
1d9ed698 413 $calculatedmessages[] =
414 get_string('tolerancemustbenumeric', 'quiz');
516cf3eb 415 }
416 }
417
1d9ed698 418 if (!empty($calculatedmessages)) {
419 $errorstring = "The following errors were found:<br />";
420 foreach ($calculatedmessages as $msg) {
421 $errorstring .= $msg . '<br />';
422 }
423 print_error($errorstring);
424 }
425
516cf3eb 426 break;
427 default:
428 return parent::validate_form($form);
429 break;
430 }
431 return true;
432 }
fbe2cfea 433 function finished_edit_wizard(&$form) {
434 return isset($form->backtoquiz);
435 }
436 // This gets called by editquestion.php after the standard question is saved
437 function print_next_wizard_page(&$question, &$form, $course) {
438 global $CFG, $USER, $SESSION, $COURSE;
439
440 // Catch invalid navigation & reloads
441 if (empty($question->id) && empty($SESSION->calculated)) {
442 redirect('edit.php?courseid='.$COURSE->id, 'The page you are loading has expired.', 3);
443 }
444
445 // See where we're coming from
446 switch($form->wizardpage) {
447 case 'question':
448 require("$CFG->dirroot/question/type/calculated/datasetdefinitions.php");
449 break;
450 case 'datasetdefinitions':
451 case 'datasetitems':
452 require("$CFG->dirroot/question/type/calculated/datasetitems.php");
453 break;
454 default:
455 print_error('invalidwizardpage', 'question');
456 break;
457 }
458 }
459
460 // This gets called by question2.php after the standard question is saved
461 function &next_wizard_form($submiturl, $question, $wizardnow){
462 global $CFG, $SESSION, $COURSE;
463
464 // Catch invalid navigation & reloads
465 if (empty($question->id) && empty($SESSION->calculated)) {
466 redirect('edit.php?courseid='.$COURSE->id, 'The page you are loading has expired. Cannot get next wizard form.', 3);
467 }
468 if (empty($question->id)){
469 $question =& $SESSION->calculated->questionform;
470 }
471
472 // See where we're coming from
473 switch($wizardnow) {
474 case 'datasetdefinitions':
475 require("$CFG->dirroot/question/type/calculated/datasetdefinitions_form.php");
476 $mform =& new question_dataset_dependent_definitions_form("$submiturl?wizardnow=datasetdefinitions", $question);
477 break;
478 case 'datasetitems':
479 require("$CFG->dirroot/question/type/calculated/datasetitems_form.php");
480 $regenerate = optional_param('forceregeneration', 0, PARAM_BOOL);
481 $mform =& new question_dataset_dependent_items_form("$submiturl?wizardnow=datasetitems", $question, $regenerate);
482 break;
483 default:
484 print_error('invalidwizardpage', 'question');
485 break;
486 }
487
488 return $mform;
489 }
516cf3eb 490
fbe2cfea 491 /**
492 * This method should be overriden if you want to include a special heading or some other
493 * html on a question editing page besides the question editing form.
494 *
495 * @param question_edit_form $mform a child of question_edit_form
496 * @param object $question
497 * @param string $wizardnow is '' for first page.
498 */
499 function display_question_editing_page(&$mform, $question, $wizardnow){
7b07a7d4 500 global $OUTPUT ;
fbe2cfea 501 switch ($wizardnow){
502 case '':
503 //on first page default display is fine
504 parent::display_question_editing_page($mform, $question, $wizardnow);
505 return;
506 break;
507 case 'datasetdefinitions':
efeedf04 508 echo $OUTPUT->heading_with_help(get_string("choosedatasetproperties", "qtype_calculated"), 'questiondatasets', 'qtype_calculated');
fbe2cfea 509 break;
510 case 'datasetitems':
efeedf04 511 echo $OUTPUT->heading_with_help(get_string("editdatasets", "qtype_calculated"), 'questiondatasets', 'qtype_calculated');
fbe2cfea 512 break;
513 }
514
515
516 $mform->display();
517
518 }
519
520 /**
521 * This method prepare the $datasets in a format similar to dadatesetdefinitions_form.php
522 * so that they can be saved
523 * using the function save_dataset_definitions($form)
524 * when creating a new calculated question or
525 * whenediting an already existing calculated question
526 * or by function save_as_new_dataset_definitions($form, $initialid)
527 * when saving as new an already existing calculated question
528 *
529 * @param object $form
530 * @param int $questionfromid default = '0'
531 */
532 function preparedatasets(&$form , $questionfromid='0'){
533 // the dataset names present in the edit_question_form and edit_calculated_form are retrieved
534 $possibledatasets = $this->find_dataset_names($form->questiontext);
535 $mandatorydatasets = array();
536 foreach ($form->answers as $answer) {
537 $mandatorydatasets += $this->find_dataset_names($answer);
538 }
539 // if there are identical datasetdefs already saved in the original question.
540 // either when editing a question or saving as new
541 // they are retrieved using $questionfromid
542 if ($questionfromid!='0'){
543 $form->id = $questionfromid ;
544 }
545 $datasets = array();
546 $key = 0 ;
547 // always prepare the mandatorydatasets present in the answers
548 // the $options are not used here
549 foreach ($mandatorydatasets as $datasetname) {
550 if (!isset($datasets[$datasetname])) {
551 list($options, $selected) =
552 $this->dataset_options($form, $datasetname);
553 $datasets[$datasetname]='';
554 $form->dataset[$key]=$selected ;
555 $key++;
556 }
557 }
558 // do not prepare possibledatasets when creating a question
559 // they will defined and stored with datasetdefinitions_form.php
560 // the $options are not used here
561 if ($questionfromid!='0'){
562
563 foreach ($possibledatasets as $datasetname) {
564 if (!isset($datasets[$datasetname])) {
565 list($options, $selected) =
566 $this->dataset_options($form, $datasetname,false);
567 $datasets[$datasetname]='';
568 $form->dataset[$key]=$selected ;
569 $key++;
570 }
571 }
572 }
573 return $datasets ;
574 }
575
576 /**
577 * this version save the available data at the different steps of the question editing process
578 * without using global $SESSION as storage between steps
579 * at the first step $wizardnow = 'question'
580 * when creating a new question
581 * when modifying a question
582 * when copying as a new question
583 * the general parameters and answers are saved using parent::save_question
584 * then the datasets are prepared and saved
585 * at the second step $wizardnow = 'datasetdefinitions'
586 * the datadefs final type are defined as private, category or not a datadef
587 * at the third step $wizardnow = 'datasetitems'
588 * the datadefs parameters and the data items are created or defined
589 *
590 * @param object question
591 * @param object $form
592 * @param int $course
593 * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php
594 */
595 function save_question($question, $form, $course) {
1ee53ca9 596 global $DB;
fbe2cfea 597 $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA);
598 $id = optional_param('id', 0, PARAM_INT); // question id
599 // in case 'question'
600 // for a new question $form->id is empty
601 // when saving as new question
602 // $question->id = 0, $form is $data from question2.php
603 // and $data->makecopy is defined as $data->id is the initial question id
604 // edit case. If it is a new question we don't necessarily need to
605 // return a valid question object
606
607 // See where we're coming from
608 switch($wizardnow) {
609 case '' :
610 case 'question': // coming from the first page, creating the second
611 if (empty($form->id)) { // for a new question $form->id is empty
612 $question = parent::save_question($question, $form, $course);
613 //prepare the datasets using default $questionfromid
614 $this->preparedatasets($form);
615 $form->id = $question->id;
616 $this->save_dataset_definitions($form);
617 } else if (!empty($form->makecopy)){
618 $questionfromid = $form->id ;
619 $question = parent::save_question($question, $form, $course);
620 //prepare the datasets
621 $this->preparedatasets($form,$questionfromid);
622 $form->id = $question->id;
623 $this->save_as_new_dataset_definitions($form,$questionfromid );
624 } else {// editing a question
625 $question = parent::save_question($question, $form, $course);
626 //prepare the datasets
627 $this->preparedatasets($form,$question->id);
628 $form->id = $question->id;
629 $this->save_dataset_definitions($form);
630 }
631 break;
632 case 'datasetdefinitions':
28a27ef1 633 // calculated options
634 // it cannot go here without having done the first page
635 // so the question_calculated_options should exist
636 // only need to update the synchronize field
ab8b5142 637 if(isset($form->synchronize) && $form->synchronize == 1 ){
28a27ef1 638 $options_synchronize = 1 ;
639 }else {
640 $options_synchronize = 0 ;
641 }
642 if (!$DB->set_field('question_calculated_options', 'synchronize', $options_synchronize, array("question" => $question->id))) {
643 return false;
644 }
fbe2cfea 645
646 $this->save_dataset_definitions($form);
647 break;
648 case 'datasetitems':
649 $this->save_dataset_items($question, $form);
450f1127 650 $this->save_question_calculated($question, $form);
fbe2cfea 651 break;
652 default:
653 print_error('invalidwizardpage', 'question');
654 break;
655 }
656 return $question;
657 }
516cf3eb 658 /**
659 * Deletes question from the question-type specific tables
660 *
661 * @return boolean Success/Failure
662 * @param object $question The question being deleted
663 */
90c3f310 664 function delete_question($questionid) {
f34488b2 665 global $DB;
ab8b5142 666
f34488b2 667 $DB->delete_records("question_calculated", array("question" => $questionid));
ab8b5142 668 $DB->delete_records("question_calculated_options", array("question" => $questionid));
f34488b2 669 $DB->delete_records("question_numerical_units", array("question" => $questionid));
670 if ($datasets = $DB->get_records('question_datasets', array('question' => $questionid))) {
90c3f310 671 foreach ($datasets as $dataset) {
57db70af 672 if (!$DB->get_records_select(
673 'question_datasets',
674 "question != ?
9e459630 675 AND datasetdefinition = ? ", array($questionid, $dataset->datasetdefinition))){
636bbc87 676 $DB->delete_records('question_dataset_definitions', array('id' => $dataset->datasetdefinition));
677 $DB->delete_records('question_dataset_items', array('definition' => $dataset->datasetdefinition));
678 }
90c3f310 679 }
680 }
f34488b2 681 $DB->delete_records("question_datasets", array("question" => $questionid));
516cf3eb 682 return true;
683 }
c5da9906 684 function test_response(&$question, &$state, $answer) {
ab8b5142 685 $virtualqtype = $this->get_virtual_qtype();
aeb15530
PS
686 return $virtualqtype->test_response($question, $state, $answer);
687
c5da9906 688 }
689 function compare_responses(&$question, $state, $teststate) {
ab8b5142
PP
690
691 $virtualqtype = $this->get_virtual_qtype();
c5da9906 692 return $virtualqtype->compare_responses($question, $state, $teststate);
693 }
516cf3eb 694
ab8b5142
PP
695 function convert_answers (&$question, &$state){
696 foreach ($question->options->answers as $key => $answer) {
697 $answer = fullclone($question->options->answers[$key]);
698 $question->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer->answer,
699 $state->options->dataset);
700 }
701 }
702 function convert_questiontext (&$question, &$state){
703 $tolerancemax =0.01;
704 $tolerancetypemax = 1 ;
705 $correctanswerlengthmax = 2 ;
706 $correctanswerformatmax = 1 ;
707 $tolerancemaxset = false ;
708 foreach ($question->options->answers as $key => $answer) {
709 if($answer->fraction == 1.0 && !$tolerancemaxset){
710 $tolerancemax = $answer->tolerance;
711 $tolerancetypemax = $answer->tolerancetype ;
712 $correctanswerlengthmax = $answer->correctanswerlength;
713 $correctanswerformatmax =$answer->correctanswerformat;
714 $tolerancemaxset = true ;
715 }
716 }
717 $question->questiontext = $this->substitute_variables(
718 $question->questiontext, $state->options->dataset);
719 //evaluate the equations i.e {=5+4)
720 $qtext = "";
721 $qtextremaining = $question->questiontext ;
722 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
723 // while (preg_match('~\{=|%=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
724 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
725 $qtext =$qtext.$qtextsplits[0];
726 $qtextremaining = $qtextsplits[1];
727 if (empty($regs1[1])) {
728 $str = '';
729 } else {
730 if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
731 $str=$formulaerrors ;
732 }else {
733 eval('$str = '.$regs1[1].';');
734 $texteval= qtype_calculated_calculate_answer(
735 $str, $state->options->dataset, $tolerancemax,
736 $tolerancetypemax, $correctanswerlengthmax,
737 $correctanswerformatmax, '');
738 $str = $texteval->answer;
739
740 ;
741 }
742 }
743 $qtext = $qtext.$str ;
744 }
745 $question->questiontext = $qtext.$qtextremaining ; // end replace equations
746 }
747
748 function get_default_numerical_unit($question,$virtualqtype){
28a27ef1 749 if($unit = $virtualqtype->get_default_numerical_unit($question)){
750 $unit = $unit->unit;
751 } else {
752 $unit = '';
753 }
ab8b5142
PP
754 return $unit ;
755
756 }
757 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
758 // Substitute variables in questiontext before giving the data to the
759 // virtual type for printing
760 $virtualqtype = $this->get_virtual_qtype();
761 // why $unit as it is not use
762 $unit = $this-> get_default_numerical_unit($question,$virtualqtype);
516cf3eb 763 // We modify the question to look like a numerical question
a6d46515 764 $numericalquestion = fullclone($question);
ab8b5142
PP
765 $this->convert_answers($numericalquestion, $state);
766 $this->convert_questiontext($numericalquestion, $state);
767 /* $tolerancemax =0.01;
768 $tolerancetypemax = 1 ;
769 $correctanswerlengthmax = 2 ;
770 $correctanswerformatmax = 1 ;
771 $tolerancemaxset = false ;
516cf3eb 772 foreach ($numericalquestion->options->answers as $key => $answer) {
ab8b5142
PP
773 if($answer->fraction == 1.0 && !$tolerancemaxset){
774 $tolerancemax = $answer->tolerance;
775 $tolerancetypemax = $answer->tolerancetype ;
776 $correctanswerlengthmax = $answer->correctanswerlength;
777 $correctanswerformatmax =$answer->correctanswerformat;
778 $tolerancemaxset = true ;
779 }
516cf3eb 780 }
ab8b5142 781
fbe2cfea 782 $numericalquestion->questiontext = $this->substitute_variables(
92186abc 783 $numericalquestion->questiontext, $state->options->dataset);
f34488b2 784 //evaluate the equations i.e {=5+4)
fd6b864f 785 $qtext = "";
786 $qtextremaining = $numericalquestion->questiontext ;
6dbcacee 787 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
ab8b5142 788 // while (preg_match('~\{=|%=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
fd6b864f 789 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
790 $qtext =$qtext.$qtextsplits[0];
791 $qtextremaining = $qtextsplits[1];
792 if (empty($regs1[1])) {
793 $str = '';
794 } else {
795 if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
796 $str=$formulaerrors ;
f34488b2 797 }else {
ab8b5142
PP
798 eval('$str = '.$regs1[1].';');
799 $texteval= qtype_calculated_calculate_answer(
800 $str, $state->options->dataset, $tolerancemax,
801 $tolerancetypemax, $correctanswerlengthmax,
802 $correctanswerformatmax, '');
803 $str = $texteval->answer;
804
805 ;
fd6b864f 806 }
807 }
f34488b2 808 $qtext = $qtext.$str ;
809 }
fd6b864f 810 $numericalquestion->questiontext = $qtext.$qtextremaining ; // end replace equations
ab8b5142
PP
811 */
812
60b5ecd3 813 $virtualqtype->print_question_formulation_and_controls($numericalquestion, $state, $cmoptions, $options);
516cf3eb 814 }
516cf3eb 815 function grade_responses(&$question, &$state, $cmoptions) {
f34488b2 816 // Forward the grading to the virtual qtype
516cf3eb 817 // We modify the question to look like a numerical question
a6d46515 818 $numericalquestion = fullclone($question);
a6d46515 819 foreach ($numericalquestion->options->answers as $key => $answer) {
820 $answer = $numericalquestion->options->answers[$key]->answer; // for PHP 4.x
fbe2cfea 821 $numericalquestion->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer,
516cf3eb 822 $state->options->dataset);
a6d46515 823 }
ab8b5142 824 $virtualqtype = $this->get_virtual_qtype();
a6d46515 825 return $virtualqtype->grade_responses($numericalquestion, $state, $cmoptions) ;
516cf3eb 826 }
827
31d21f22 828
516cf3eb 829 // ULPGC ecastro
830 function check_response(&$question, &$state) {
831 // Forward the checking to the virtual qtype
832 // We modify the question to look like a numerical question
833 $numericalquestion = clone($question);
834 $numericalquestion->options = clone($question->options);
835 foreach ($question->options->answers as $key => $answer) {
836 $numericalquestion->options->answers[$key] = clone($answer);
837 }
838 foreach ($numericalquestion->options->answers as $key => $answer) {
839 $answer = &$numericalquestion->options->answers[$key]; // for PHP 4.x
fbe2cfea 840 $answer->answer = $this->substitute_variables_and_eval($answer->answer,
516cf3eb 841 $state->options->dataset);
842 }
ab8b5142 843 $virtualqtype = $this->get_virtual_qtype();
a6d46515 844 return $virtualqtype->check_response($numericalquestion, $state) ;
516cf3eb 845 }
846
847 // ULPGC ecastro
848 function get_actual_response(&$question, &$state) {
849 // Substitute variables in questiontext before giving the data to the
850 // virtual type
ab8b5142 851 $virtualqtype = $this->get_virtual_qtype();
516cf3eb 852 $unit = $virtualqtype->get_default_numerical_unit($question);
853
854 // We modify the question to look like a numerical question
855 $numericalquestion = clone($question);
856 $numericalquestion->options = clone($question->options);
857 foreach ($question->options->answers as $key => $answer) {
858 $numericalquestion->options->answers[$key] = clone($answer);
859 }
860 foreach ($numericalquestion->options->answers as $key => $answer) {
861 $answer = &$numericalquestion->options->answers[$key]; // for PHP 4.x
fbe2cfea 862 $answer->answer = $this->substitute_variables_and_eval($answer->answer,
516cf3eb 863 $state->options->dataset);
864 // apply_unit
865 }
fbe2cfea 866 $numericalquestion->questiontext = $this->substitute_variables_and_eval(
516cf3eb 867 $numericalquestion->questiontext, $state->options->dataset);
868 $responses = $virtualqtype->get_all_responses($numericalquestion, $state);
869 $response = reset($responses->responses);
870 $correct = $response->answer.' : ';
871
872 $responses = $virtualqtype->get_actual_response($numericalquestion, $state);
873
874 foreach ($responses as $key=>$response){
875 $responses[$key] = $correct.$response;
876 }
877
878 return $responses;
879 }
880
881 function create_virtual_qtype() {
882 global $CFG;
28a27ef1 883 require_once("$CFG->dirroot/question/type/numerical/questiontype.php");
884 return new question_numerical_qtype();
516cf3eb 885 }
886
887 function supports_dataset_item_generation() {
888 // Calcualted support generation of randomly distributed number data
889 return true;
890 }
60b5ecd3 891 function custom_generator_tools_part(&$mform, $idx, $j){
892
893 $minmaxgrp = array();
5940864e
PP
894 $minmaxgrp[] =& $mform->createElement('text', "calcmin[$idx]", get_string('calcmin', 'qtype_calculated'));
895 $minmaxgrp[] =& $mform->createElement('text', "calcmax[$idx]", get_string('calcmax', 'qtype_calculated'));
896 $mform->addGroup($minmaxgrp, 'minmaxgrp', get_string('minmax', 'qtype_calculated'), ' - ', false);
87cd4f54 897 $mform->setType("calcmin[$idx]", PARAM_NUMBER);
898 $mform->setType("calcmax[$idx]", PARAM_NUMBER);
60b5ecd3 899
900 $precisionoptions = range(0, 10);
5940864e 901 $mform->addElement('select', "calclength[$idx]", get_string('calclength', 'qtype_calculated'), $precisionoptions);
60b5ecd3 902
5940864e
PP
903 $distriboptions = array('uniform' => get_string('uniform', 'qtype_calculated'), 'loguniform' => get_string('loguniform', 'qtype_calculated'));
904 $mform->addElement('select', "calcdistribution[$idx]", get_string('calcdistribution', 'qtype_calculated'), $distriboptions);
60b5ecd3 905
906
60b5ecd3 907 }
908
909 function custom_generator_set_data($datasetdefs, $formdata){
910 $idx = 1;
911 foreach ($datasetdefs as $datasetdef){
6dbcacee 912 if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~', $datasetdef->options, $regs)) {
60b5ecd3 913 $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name";
914 $formdata["calcdistribution[$idx]"] = $regs[1];
915 $formdata["calcmin[$idx]"] = $regs[2];
916 $formdata["calcmax[$idx]"] = $regs[3];
917 $formdata["calclength[$idx]"] = $regs[4];
918 }
919 $idx++;
920 }
921 return $formdata;
922 }
516cf3eb 923
924 function custom_generator_tools($datasetdef) {
44c64ad8 925 global $OUTPUT;
6dbcacee 926 if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
516cf3eb 927 $datasetdef->options, $regs)) {
928 $defid = "$datasetdef->type-$datasetdef->category-$datasetdef->name";
929 for ($i = 0 ; $i<10 ; ++$i) {
930 $lengthoptions[$i] = get_string(($regs[1] == 'uniform'
931 ? 'decimals'
932 : 'significantfigures'), 'quiz', $i);
933 }
c5dbaba9
PS
934 $menu1 = html_writer::select($lengthoptions, 'calclength[]', $regs[4], null);
935
936 $options = array('uniform' => get_string('uniform', 'quiz'), 'loguniform' => get_string('loguniform', 'quiz'));
937 $menu2 = html_writer::select($options, 'calcdistribution[]', $regs[1], null);
09275894 938 return '<input type="submit" onclick="'
d2ce367f 939 . "getElementById('addform').regenerateddefid.value='$defid'; return true;"
516cf3eb 940 .'" value="'. get_string('generatevalue', 'quiz') . '"/><br/>'
941 . '<input type="text" size="3" name="calcmin[]" '
942 . " value=\"$regs[2]\"/> &amp; <input name=\"calcmax[]\" "
943 . ' type="text" size="3" value="' . $regs[3] .'"/> '
44c64ad8 944 . $menu1 . '<br/>'
945 . $menu2;
516cf3eb 946 } else {
947 return '';
948 }
949 }
950
60b5ecd3 951
516cf3eb 952 function update_dataset_options($datasetdefs, $form) {
953 // Do we have informatin about new options???
954 if (empty($form->definition) || empty($form->calcmin)
955 || empty($form->calcmax) || empty($form->calclength)
956 || empty($form->calcdistribution)) {
a8d2a373 957 // I guess not
516cf3eb 958
959 } else {
960 // Looks like we just could have some new information here
60b5ecd3 961 $uniquedefs = array_values(array_unique($form->definition));
962 foreach ($uniquedefs as $key => $defid) {
516cf3eb 963 if (isset($datasetdefs[$defid])
60b5ecd3 964 && is_numeric($form->calcmin[$key+1])
965 && is_numeric($form->calcmax[$key+1])
966 && is_numeric($form->calclength[$key+1])) {
967 switch ($form->calcdistribution[$key+1]) {
516cf3eb 968 case 'uniform': case 'loguniform':
969 $datasetdefs[$defid]->options =
60b5ecd3 970 $form->calcdistribution[$key+1] . ':'
971 . $form->calcmin[$key+1] . ':'
972 . $form->calcmax[$key+1] . ':'
973 . $form->calclength[$key+1];
516cf3eb 974 break;
975 default:
fef8f84e 976 echo $OUTPUT->notification("Unexpected distribution ".$form->calcdistribution[$key+1]);
516cf3eb 977 }
978 }
979 }
980 }
981
982 // Look for empty options, on which we set default values
983 foreach ($datasetdefs as $defid => $def) {
984 if (empty($def->options)) {
985 $datasetdefs[$defid]->options = 'uniform:1.0:10.0:1';
986 }
987 }
988 return $datasetdefs;
989 }
990
450f1127 991 function save_question_calculated($question, $fromform){
992 global $DB;
993
994 foreach ($question->options->answers as $key => $answer) {
995 if ($options = $DB->get_record('question_calculated', array('answer' => $key))) {
996 $options->tolerance = trim($fromform->tolerance[$key]);
997 $options->tolerancetype = trim($fromform->tolerancetype[$key]);
998 $options->correctanswerlength = trim($fromform->correctanswerlength[$key]);
999 $options->correctanswerformat = trim($fromform->correctanswerformat[$key]);
0bcf8b6f 1000 $DB->update_record('question_calculated', $options);
450f1127 1001 }
1002 }
1003 }
1004
ab8b5142
PP
1005 /**
1006 * This function get the dataset items using id as unique parameter and return an
1007 * array with itemnumber as index sorted ascendant
1008 * If the multiple records with the same itemnumber exist, only the newest one
1009 * i.e with the greatest id is used, the others are ignored but not deleted.
1010 * MDL-19210
1011 */
a2155a7b 1012 function get_database_dataset_items($definition){
ab8b5142
PP
1013 global $CFG, $DB;
1014 $databasedataitems = $DB->get_records_sql( // Use number as key!!
1015 " SELECT id , itemnumber, definition, value
1016 FROM {question_dataset_items}
1017 WHERE definition = $definition order by id DESC ", array($definition));
1018 $dataitems = Array();
1019 foreach($databasedataitems as $id => $dataitem ){
1020 if (!isset($dataitems[$dataitem->itemnumber])){
1021 $dataitems[$dataitem->itemnumber] = $dataitem ;
1022 }else {
1023 // deleting the unused records could be added here
1024 }
1025 }
1026 ksort($dataitems);
1027 return $dataitems ;
a2155a7b 1028 }
aeb15530 1029
60b5ecd3 1030 function save_dataset_items($question, $fromform){
f34488b2 1031 global $CFG, $DB;
451373ed 1032 // max datasets = 100 items
c31f631b 1033 $max100 = 100 ;
f34488b2 1034 if(isset($fromform->nextpageparam["forceregeneration"])) {
d650e1a3 1035 $regenerate = $fromform->nextpageparam["forceregeneration"];
1036 }else{
1037 $regenerate = 0 ;
1038 }
60b5ecd3 1039 if (empty($question->options)) {
1040 $this->get_question_options($question);
1041 }
1042 //get the old datasets for this question
1043 $datasetdefs = $this->get_dataset_definitions($question->id, array());
1044 // Handle generator options...
1045 $olddatasetdefs = fullclone($datasetdefs);
1046 $datasetdefs = $this->update_dataset_options($datasetdefs, $fromform);
1047 $maxnumber = -1;
1048 foreach ($datasetdefs as $defid => $datasetdef) {
1049 if (isset($datasetdef->id)
1050 && $datasetdef->options != $olddatasetdefs[$defid]->options) {
1051 // Save the new value for options
f34488b2 1052 $DB->update_record('question_dataset_definitions', $datasetdef);
60b5ecd3 1053
1054 }
1055 // Get maxnumber
1056 if ($maxnumber == -1 || $datasetdef->itemcount < $maxnumber) {
1057 $maxnumber = $datasetdef->itemcount;
1058 }
1059 }
1060 // Handle adding and removing of dataset items
1061 $i = 1;
a8d2a373 1062 ksort($fromform->definition);
60b5ecd3 1063 foreach ($fromform->definition as $key => $defid) {
1064 //if the delete button has not been pressed then skip the datasetitems
1065 //in the 'add item' part of the form.
1066 if ((!isset($fromform->addbutton)) && ($i > (count($datasetdefs)*$maxnumber))) {
1067 break;
1068 }
1069 $addeditem = new stdClass();
1070 $addeditem->definition = $datasetdefs[$defid]->id;
1071 $addeditem->value = $fromform->number[$i];
1072 $addeditem->itemnumber = ceil($i / count($datasetdefs));
1073
1074 if ($fromform->itemid[$i]) {
1075 // Reuse any previously used record
1076 $addeditem->id = $fromform->itemid[$i];
bb4b6010 1077 $DB->update_record('question_dataset_items', $addeditem);
60b5ecd3 1078 } else {
bb4b6010 1079 $DB->insert_record('question_dataset_items', $addeditem);
60b5ecd3 1080 }
1081
1082 $i++;
1083 }
450f1127 1084 if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber){
60b5ecd3 1085 $maxnumber = $addeditem->itemnumber;
1086 foreach ($datasetdefs as $key => $newdef) {
1087 if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
1088 $newdef->itemcount = $maxnumber;
1089 // Save the new value for options
f34488b2 1090 $DB->update_record('question_dataset_definitions', $newdef);
60b5ecd3 1091 }
1092 }
1093 }
451373ed 1094 // adding supplementary items
1095 $numbertoadd =0;
1096 if (isset($fromform->addbutton) && $fromform->selectadd > 1 && $maxnumber < $max100 ) {
c31f631b 1097 $numbertoadd =$fromform->selectadd-1 ;
1098 if ( $max100 - $maxnumber < $numbertoadd ) {
1099 $numbertoadd = $max100 - $maxnumber ;
1100 }
451373ed 1101 //add the other items.
1102 // Generate a new dataset item (or reuse an old one)
1103 foreach ($datasetdefs as $defid => $datasetdef) {
a2155a7b 1104 // in case that for category datasets some new items has been added
1105 // get actual values
451373ed 1106 if (isset($datasetdef->id)) {
a2155a7b 1107 $datasetdefs[$defid]->items = $this->get_database_dataset_items($datasetdef->id);
451373ed 1108 }
c31f631b 1109 for ($numberadded =$maxnumber+1 ; $numberadded <= $maxnumber+$numbertoadd ; $numberadded++){
ad3a4aa6 1110 if (isset($datasetdefs[$defid]->items[$numberadded]) ){
1111 // in case of regenerate it modifies the already existing record
1112 if ( $regenerate ) {
1113 $datasetitem = new stdClass;
1114 $datasetitem->id = $datasetdefs[$defid]->items[$numberadded]->id;
1115 $datasetitem->definition = $datasetdef->id ;
1116 $datasetitem->itemnumber = $numberadded;
1117 $datasetitem->value = $this->generate_dataset_item($datasetdef->options);
bb4b6010 1118 $DB->update_record('question_dataset_items', $datasetitem);
ad3a4aa6 1119 }
1120 //if not regenerate do nothing as there is already a record
451373ed 1121 } else {
1122 $datasetitem = new stdClass;
1123 $datasetitem->definition = $datasetdef->id ;
1124 $datasetitem->itemnumber = $numberadded;
1125 if ($this->supports_dataset_item_generation()) {
1126 $datasetitem->value = $this->generate_dataset_item($datasetdef->options);
1127 } else {
1128 $datasetitem->value = '';
1129 }
bb4b6010 1130 $DB->insert_record('question_dataset_items', $datasetitem);
c31f631b 1131 }
1132 }//for number added
1133 }// datasetsdefs end
1134 $maxnumber += $numbertoadd ;
1135 foreach ($datasetdefs as $key => $newdef) {
451373ed 1136 if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
1137 $newdef->itemcount = $maxnumber;
1138 // Save the new value for options
f34488b2 1139 $DB->update_record('question_dataset_definitions', $newdef);
451373ed 1140 }
1141 }
f34488b2 1142 }
451373ed 1143
60b5ecd3 1144 if (isset($fromform->deletebutton)) {
451373ed 1145 if(isset($fromform->selectdelete)) $newmaxnumber = $maxnumber-$fromform->selectdelete ;
1146 else $newmaxnumber = $maxnumber-1 ;
1147 if ($newmaxnumber < 0 ) $newmaxnumber = 0 ;
60b5ecd3 1148 foreach ($datasetdefs as $datasetdef) {
1149 if ($datasetdef->itemcount == $maxnumber) {
f34488b2 1150 $datasetdef->itemcount= $newmaxnumber ;
bb4b6010 1151 $DB->update_record('question_dataset_definitions', $datasetdef);
60b5ecd3 1152 }
1153 }
451373ed 1154 }
60b5ecd3 1155 }
516cf3eb 1156 function generate_dataset_item($options) {
6dbcacee 1157 if (!preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
516cf3eb 1158 $options, $regs)) {
1159 // Unknown options...
1160 return false;
1161 }
1162 if ($regs[1] == 'uniform') {
1163 $nbr = $regs[2] + ($regs[3]-$regs[2])*mt_rand()/mt_getrandmax();
87cd4f54 1164 return sprintf("%.".$regs[4]."f",$nbr);
516cf3eb 1165
1166 } else if ($regs[1] == 'loguniform') {
1167 $log0 = log(abs($regs[2])); // It would have worked the other way to
f34488b2 1168 $nbr = exp($log0 + (log(abs($regs[3])) - $log0)*mt_rand()/mt_getrandmax());
87cd4f54 1169 return sprintf("%.".$regs[4]."f",$nbr);
f34488b2 1170
516cf3eb 1171 } else {
0b4f4187 1172 print_error('disterror', 'question', '', $regs[1]);
516cf3eb 1173 }
1174 return '';
1175 }
1176
1177 function comment_header($question) {
1178 //$this->get_question_options($question);
28a27ef1 1179 $strheader = '';
516cf3eb 1180 $delimiter = '';
60b5ecd3 1181
1182 $answers = $question->options->answers;
1183
450f1127 1184 foreach ($answers as $key => $answer) {
516cf3eb 1185 if (is_string($answer)) {
1186 $strheader .= $delimiter.$answer;
1187 } else {
1188 $strheader .= $delimiter.$answer->answer;
1189 }
28a27ef1 1190 $delimiter = '<br/><br/><br/>';
516cf3eb 1191 }
1192 return $strheader;
1193 }
1194
ab8b5142 1195 function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$data, $number) {
28a27ef1 1196 global $DB, $QTYPES;
450f1127 1197 $comment = new stdClass;
1198 $comment->stranswers = array();
1199 $comment->outsidelimit = false ;
1200 $comment->answers = array();
516cf3eb 1201 /// Find a default unit:
450f1127 1202 if (!empty($questionid) && $unit = $DB->get_record('question_numerical_units', array('question'=> $questionid, 'multiplier' => 1.0))) {
516cf3eb 1203 $unit = $unit->unit;
1204 } else {
1205 $unit = '';
1206 }
1207
450f1127 1208 $answers = fullclone($answers);
516cf3eb 1209 $strmin = get_string('min', 'quiz');
1210 $strmax = get_string('max', 'quiz');
1211 $errors = '';
1212 $delimiter = ': ';
ab8b5142 1213 $virtualqtype = $qtypeobj->get_virtual_qtype();//& $QTYPES['numerical'];
450f1127 1214 foreach ($answers as $key => $answer) {
fbe2cfea 1215 $formula = $this->substitute_variables($answer->answer,$data);
1d9ed698 1216 $formattedanswer = qtype_calculated_calculate_answer(
516cf3eb 1217 $answer->answer, $data, $answer->tolerance,
1218 $answer->tolerancetype, $answer->correctanswerlength,
1219 $answer->correctanswerformat, $unit);
078cc3f0 1220 if ( $formula === '*'){
450f1127 1221 $answer->min = ' ';
1222 $formattedanswer->answer = $answer->answer ;
078cc3f0 1223 }else {
1224 eval('$answer->answer = '.$formula.';') ;
1225 $virtualqtype->get_tolerance_interval($answer);
aeb15530 1226 }
1d9ed698 1227 if ($answer->min === '') {
516cf3eb 1228 // This should mean that something is wrong
450f1127 1229 $comment->stranswers[$key] = " $formattedanswer->answer".'<br/><br/>';
1230 } else if ($formula === '*'){
1231 $comment->stranswers[$key] = $formula.' = '.get_string('anyvalue','qtype_calculated').'<br/><br/><br/>';
1232 }else{
1233 $comment->stranswers[$key]= $formula.' = '.$formattedanswer->answer.'<br/>' ;
1d9ed698 1234 $correcttrue->correct = $formattedanswer->answer ;
1235 $correcttrue->true = $answer->answer ;
0d03d6be 1236 if ($formattedanswer->answer < $answer->min || $formattedanswer->answer > $answer->max){
450f1127 1237 $comment->outsidelimit = true ;
1238 $comment->answers[$key] = $key;
1239 $comment->stranswers[$key] .=get_string('trueansweroutsidelimits','qtype_calculated',$correcttrue);//<span class="error">ERROR True answer '..' outside limits</span>';
1d9ed698 1240 }else {
450f1127 1241 $comment->stranswers[$key] .=get_string('trueanswerinsidelimits','qtype_calculated',$correcttrue);//' True answer :'.$calculated->trueanswer.' inside limits';
0d03d6be 1242 }
ab8b5142
PP
1243 $comment->stranswers[$key] .='<br/>';
1244 $comment->stranswers[$key] .= $strmin.$delimiter.$answer->min.' --- ';
1245 $comment->stranswers[$key] .= $strmax.$delimiter.$answer->max;
450f1127 1246 $comment->stranswers[$key] .='';
516cf3eb 1247 }
1248 }
450f1127 1249 return fullclone($comment);
516cf3eb 1250 }
ab8b5142 1251 function multichoice_comment_on_datasetitems($questionid,$questiontext, $answers,$data, $number) {
28a27ef1 1252 global $DB;
1253 $comment = new stdClass;
1254 $comment->stranswers = array();
1255 $comment->outsidelimit = false ;
1256 $comment->answers = array();
1257 /// Find a default unit:
1258 if (!empty($questionid) && $unit = $DB->get_record('question_numerical_units', array('question'=> $questionid, 'multiplier' => 1.0))) {
1259 $unit = $unit->unit;
1260 } else {
1261 $unit = '';
1262 }
1263
1264 $answers = fullclone($answers);
1265 $strmin = get_string('min', 'quiz');
1266 $strmax = get_string('max', 'quiz');
1267 $errors = '';
1268 $delimiter = ': ';
1269 foreach ($answers as $key => $answer) {
1270 $answer->answer = $this->substitute_variables($answer->answer, $data);
1271 //evaluate the equations i.e {=5+4)
1272 $qtext = "";
1273 $qtextremaining = $answer->answer ;
1274 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
1275 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
1276 $qtext =$qtext.$qtextsplits[0];
1277 $qtextremaining = $qtextsplits[1];
1278 if (empty($regs1[1])) {
1279 $str = '';
1280 } else {
1281 if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
1282 $str=$formulaerrors ;
1283 }else {
1284 eval('$str = '.$regs1[1].';');
ab8b5142
PP
1285
1286 $texteval= qtype_calculated_calculate_answer(
1287 $str, $data, $answer->tolerance,
1288 $answer->tolerancetype, $answer->correctanswerlength,
1289 $answer->correctanswerformat, '');
1290 $str = $texteval->answer;
1291
28a27ef1 1292 }
1293 }
1294 $qtext = $qtext.$str ;
1295 }
1296 $answer->answer = $qtext.$qtextremaining ; ;
1297 $comment->stranswers[$key]= $answer->answer ;
aeb15530
PS
1298
1299
28a27ef1 1300 /* $formula = $this->substitute_variables($answer->answer,$data);
1301 $formattedanswer = qtype_calculated_calculate_answer(
1302 $answer->answer, $data, $answer->tolerance,
1303 $answer->tolerancetype, $answer->correctanswerlength,
1304 $answer->correctanswerformat, $unit);
1305 if ( $formula === '*'){
1306 $answer->min = ' ';
1307 $formattedanswer->answer = $answer->answer ;
1308 }else {
1309 eval('$answer->answer = '.$formula.';') ;
1310 $virtualqtype->get_tolerance_interval($answer);
aeb15530 1311 }
28a27ef1 1312 if ($answer->min === '') {
1313 // This should mean that something is wrong
1314 $comment->stranswers[$key] = " $formattedanswer->answer".'<br/><br/>';
1315 } else if ($formula === '*'){
1316 $comment->stranswers[$key] = $formula.' = '.get_string('anyvalue','qtype_calculated').'<br/><br/><br/>';
1317 }else{
1318 $comment->stranswers[$key]= $formula.' = '.$formattedanswer->answer.'<br/>' ;
1319 $comment->stranswers[$key] .= $strmin. $delimiter.$answer->min.'---';
1320 $comment->stranswers[$key] .= $strmax.$delimiter.$answer->max;
1321 $comment->stranswers[$key] .='<br/>';
1322 $correcttrue->correct = $formattedanswer->answer ;
1323 $correcttrue->true = $answer->answer ;
1324 if ($formattedanswer->answer < $answer->min || $formattedanswer->answer > $answer->max){
1325 $comment->outsidelimit = true ;
1326 $comment->answers[$key] = $key;
1327 $comment->stranswers[$key] .=get_string('trueansweroutsidelimits','qtype_calculated',$correcttrue);//<span class="error">ERROR True answer '..' outside limits</span>';
1328 }else {
1329 $comment->stranswers[$key] .=get_string('trueanswerinsidelimits','qtype_calculated',$correcttrue);//' True answer :'.$calculated->trueanswer.' inside limits';
1330 }
1331 $comment->stranswers[$key] .='';
1332 }*/
1333 }
1334 return fullclone($comment);
1335 }
516cf3eb 1336
1337 function tolerance_types() {
1338 return array('1' => get_string('relative', 'quiz'),
1339 '2' => get_string('nominal', 'quiz'),
1340 '3' => get_string('geometric', 'quiz'));
1341 }
1342
fd0973cc 1343 function dataset_options($form, $name, $mandatory=true,$renameabledatasets=false) {
516cf3eb 1344 // Takes datasets from the parent implementation but
1345 // filters options that are currently not accepted by calculated
1346 // It also determines a default selection...
f34488b2 1347 //$renameabledatasets not implemented anmywhere
fbe2cfea 1348 list($options, $selected) = $this->dataset_options_from_database($form, $name,'','qtype_calculated');
fd0973cc 1349 // list($options, $selected) = $this->dataset_optionsa($form, $name);
1350
516cf3eb 1351 foreach ($options as $key => $whatever) {
6dbcacee 1352 if (!preg_match('~^1-~', $key) && $key != '0') {
516cf3eb 1353 unset($options[$key]);
1354 }
1355 }
1356 if (!$selected) {
f34488b2 1357 if ($mandatory){
fbe2cfea 1358 $selected = "1-0-$name"; // Default
fd0973cc 1359 }else {
1360 $selected = "0"; // Default
f34488b2 1361 }
516cf3eb 1362 }
1363 return array($options, $selected);
1364 }
1365
1366 function construct_dataset_menus($form, $mandatorydatasets,
1367 $optionaldatasets) {
44c64ad8 1368 global $OUTPUT;
516cf3eb 1369 $datasetmenus = array();
1370 foreach ($mandatorydatasets as $datasetname) {
1371 if (!isset($datasetmenus[$datasetname])) {
1372 list($options, $selected) =
1373 $this->dataset_options($form, $datasetname);
1374 unset($options['0']); // Mandatory...
c5dbaba9 1375 $datasetmenus[$datasetname] = html_writer::select($options, 'dataset[]', $selected, null);
516cf3eb 1376 }
1377 }
1378 foreach ($optionaldatasets as $datasetname) {
1379 if (!isset($datasetmenus[$datasetname])) {
1380 list($options, $selected) =
1381 $this->dataset_options($form, $datasetname);
c5dbaba9 1382 $datasetmenus[$datasetname] = html_writer::select($options, 'dataset[]', $selected, null);
516cf3eb 1383 }
1384 }
1385 return $datasetmenus;
1386 }
1387
20bf2c1a 1388 function print_question_grading_details(&$question, &$state, &$cmoptions, &$options) {
ab8b5142 1389 $virtualqtype = $this->get_virtual_qtype();
20bf2c1a 1390 $virtualqtype->print_question_grading_details($question, $state, $cmoptions, $options) ;
1391 }
1392
516cf3eb 1393 function get_correct_responses(&$question, &$state) {
ab8b5142
PP
1394 // virtual type for printing
1395 $virtualqtype = $this->get_virtual_qtype();
78a6d0ba 1396 $unit = $this->get_default_numerical_unit($question,$virtualqtype);
ab8b5142 1397 // We modify the question to look like a numerical question
ab8b5142 1398 $this->convert_answers($question, $state);
fe7e43f4 1399 return $virtualqtype->get_correct_responses($question, $state) ;
516cf3eb 1400 }
1401
1402 function substitute_variables($str, $dataset) {
ab8b5142 1403 global $OUTPUT ;
1ee53ca9 1404 // testing for wrong numerical values
aeb15530 1405 // all calculations used this function so testing here should be OK
fbe2cfea 1406
1407 foreach ($dataset as $name => $value) {
1ee53ca9 1408 $val = $value ;
1409 if(! is_numeric($val)){
1410 $a = new stdClass;
1411 $a->name = '{'.$name.'}' ;
1412 $a->value = $value ;
1413 echo $OUTPUT->notification(get_string('notvalidnumber','qtype_calculated',$a));
1414 $val = 1.0 ;
aeb15530 1415 }
1ee53ca9 1416 if($val < 0 ){
1417 $str = str_replace('{'.$name.'}', '('.$val.')', $str);
fbe2cfea 1418 } else {
1ee53ca9 1419 $str = str_replace('{'.$name.'}', $val, $str);
fbe2cfea 1420 }
1421 }
1422 return $str;
1423 }
28a27ef1 1424 function evaluate_equations($str, $dataset){
1425 $formula = $this->substitute_variables($str, $dataset) ;
1426 if ($error = qtype_calculated_find_formula_errors($formula)) {
1427 return $error;
1428 }
1429 return $str;
1430 }
aeb15530 1431
fbe2cfea 1432
1433 function substitute_variables_and_eval($str, $dataset) {
1434 $formula = $this->substitute_variables($str, $dataset) ;
1435 if ($error = qtype_calculated_find_formula_errors($formula)) {
516cf3eb 1436 return $error;
1437 }
1438 /// Calculate the correct answer
1439 if (empty($formula)) {
1440 $str = '';
078cc3f0 1441 } else if ($formula === '*'){
1442 $str = '*';
516cf3eb 1443 } else {
f34488b2 1444 eval('$str = '.$formula.';');
516cf3eb 1445 }
1446 return $str;
1447 }
f34488b2 1448
fbe2cfea 1449 function get_dataset_definitions($questionid, $newdatasets) {
1450 global $DB;
1451 //get the existing datasets for this question
1452 $datasetdefs = array();
1453 if (!empty($questionid)) {
1454 global $CFG;
1455 $sql = "SELECT i.*
1456 FROM {question_datasets} d,
1457 {question_dataset_definitions} i
1458 WHERE d.question = ?
1459 AND d.datasetdefinition = i.id
1460 ";
1461 if ($records = $DB->get_records_sql($sql, array($questionid))) {
1462 foreach ($records as $r) {
1463 $datasetdefs["$r->type-$r->category-$r->name"] = $r;
1464 }
1465 }
1466 }
1467
1468 foreach ($newdatasets as $dataset) {
1469 if (!$dataset) {
1470 continue; // The no dataset case...
1471 }
1472
1473 if (!isset($datasetdefs[$dataset])) {
1474 //make new datasetdef
1475 list($type, $category, $name) = explode('-', $dataset, 3);
1476 $datasetdef = new stdClass;
1477 $datasetdef->type = $type;
1478 $datasetdef->name = $name;
1479 $datasetdef->category = $category;
1480 $datasetdef->itemcount = 0;
1481 $datasetdef->options = 'uniform:1.0:10.0:1';
1482 $datasetdefs[$dataset] = clone($datasetdef);
1483 }
1484 }
1485 return $datasetdefs;
1486 }
1487
1488 function save_dataset_definitions($form) {
1489 global $DB;
1ee53ca9 1490 // save synchronize
aeb15530 1491
fbe2cfea 1492 // Save datasets
1493 $datasetdefinitions = $this->get_dataset_definitions($form->id, $form->dataset);
1494 $tmpdatasets = array_flip($form->dataset);
1495 $defids = array_keys($datasetdefinitions);
1496 foreach ($defids as $defid) {
1497 $datasetdef = &$datasetdefinitions[$defid];
1498 if (isset($datasetdef->id)) {
1499 if (!isset($tmpdatasets[$defid])) {
1500 // This dataset is not used any more, delete it
1501 $DB->delete_records('question_datasets', array('question' => $form->id, 'datasetdefinition' => $datasetdef->id));
1502 if ($datasetdef->category == 0) { // Question local dataset
1503 $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id));
1504 $DB->delete_records('question_dataset_items', array('definition' => $datasetdef->id));
1505 }
1506 }
1507 // This has already been saved or just got deleted
1508 unset($datasetdefinitions[$defid]);
1509 continue;
1510 }
1511
bb4b6010 1512 $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
fbe2cfea 1513
1514 if (0 != $datasetdef->category) {
1515 // We need to look for already existing
1516 // datasets in the category.
1517 // By first creating the datasetdefinition above we
1518 // can manage to automatically take care of
1519 // some possible realtime concurrence
1520 if ($olderdatasetdefs = $DB->get_records_select( 'question_dataset_definitions',
1521 "type = ?
1522 AND name = ?
1523 AND category = ?
1524 AND id < ?
1525 ORDER BY id DESC", array($datasetdef->type, $datasetdef->name, $datasetdef->category, $datasetdef->id))) {
1526
1527 while ($olderdatasetdef = array_shift($olderdatasetdefs)) {
1528 $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id));
1529 $datasetdef = $olderdatasetdef;
1530 }
1531 }
1532 }
1533
1534 // Create relation to this dataset:
1535 $questiondataset = new stdClass;
1536 $questiondataset->question = $form->id;
1537 $questiondataset->datasetdefinition = $datasetdef->id;
bb4b6010 1538 $DB->insert_record('question_datasets', $questiondataset);
fbe2cfea 1539 unset($datasetdefinitions[$defid]);
1540 }
1541
1542 // Remove local obsolete datasets as well as relations
1543 // to datasets in other categories:
1544 if (!empty($datasetdefinitions)) {
1545 foreach ($datasetdefinitions as $def) {
1546 $DB->delete_records('question_datasets', array('question' => $form->id, 'datasetdefinition' => $def->id));
1547
1548 if ($def->category == 0) { // Question local dataset
1549 $DB->delete_records('question_dataset_definitions', array('id' => $def->id));
1550 $DB->delete_records('question_dataset_items', array('definition' => $def->id));
1551 }
1552 }
1553 }
1554 }
1555 /** This function create a copy of the datasets ( definition and dataitems)
1556 * from the preceding question if they remain in the new question
1557 * otherwise its create the datasets that have been added as in the
1558 * save_dataset_definitions()
1559 */
1560 function save_as_new_dataset_definitions($form, $initialid) {
1561 global $CFG, $DB;
1562 // Get the datasets from the intial question
1563 $datasetdefinitions = $this->get_dataset_definitions($initialid, $form->dataset);
1564 // $tmpdatasets contains those of the new question
1565 $tmpdatasets = array_flip($form->dataset);
1566 $defids = array_keys($datasetdefinitions);// new datasets
1567 foreach ($defids as $defid) {
1568 $datasetdef = &$datasetdefinitions[$defid];
1569 if (isset($datasetdef->id)) {
1570 // This dataset exist in the initial question
1571 if (!isset($tmpdatasets[$defid])) {
1572 // do not exist in the new question so ignore
1573 unset($datasetdefinitions[$defid]);
1574 continue;
1575 }
1576 // create a copy but not for category one
1577 if (0 == $datasetdef->category) {
1578 $olddatasetid = $datasetdef->id ;
1579 $olditemcount = $datasetdef->itemcount ;
1580 $datasetdef->itemcount =0;
bb4b6010 1581 $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
fbe2cfea 1582 //copy the dataitems
a2155a7b 1583 $olditems = $this->get_database_dataset_items($olddatasetid);
fbe2cfea 1584 if (count($olditems) > 0 ) {
1585 $itemcount = 0;
1586 foreach($olditems as $item ){
1587 $item->definition = $datasetdef->id;
bb4b6010 1588 $DB->insert_record('question_dataset_items', $item);
1589 $itemcount++;
fbe2cfea 1590 }
ed15021f
PP
1591 //update item count to olditemcount if
1592 // at least this number of items has been recover from the database
1593 if( $olditemcount <= $itemcount ) {
1594 $datasetdef->itemcount = $olditemcount;
1595 } else {
1596 $datasetdef->itemcount = $itemcount ;
1597 }
fbe2cfea 1598 $DB->update_record('question_dataset_definitions', $datasetdef);
1599 } // end of copy the dataitems
1600 }// end of copy the datasetdef
1601 // Create relation to the new question with this
1602 // copy as new datasetdef from the initial question
1603 $questiondataset = new stdClass;
1604 $questiondataset->question = $form->id;
1605 $questiondataset->datasetdefinition = $datasetdef->id;
bb4b6010 1606 $DB->insert_record('question_datasets', $questiondataset);
fbe2cfea 1607 unset($datasetdefinitions[$defid]);
1608 continue;
1609 }// end of datasetdefs from the initial question
1610 // really new one code similar to save_dataset_definitions()
bb4b6010 1611 $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
fbe2cfea 1612
1613 if (0 != $datasetdef->category) {
1614 // We need to look for already existing
1615 // datasets in the category.
1616 // By first creating the datasetdefinition above we
1617 // can manage to automatically take care of
1618 // some possible realtime concurrence
1619 if ($olderdatasetdefs = $DB->get_records_select(
1620 'question_dataset_definitions',
1621 "type = ?
1622 AND name = ?
1623 AND category = ?
1624 AND id < ?
1625 ORDER BY id DESC", array($datasetdef->type, $datasetdef->name, $datasetdef->category, $datasetdef->id))) {
1626
1627 while ($olderdatasetdef = array_shift($olderdatasetdefs)) {
1628 $DB->delete_records('question_dataset_definitions', array('id' => $datasetdef->id));
1629 $datasetdef = $olderdatasetdef;
1630 }
1631 }
1632 }
1633
1634 // Create relation to this dataset:
1635 $questiondataset = new stdClass;
1636 $questiondataset->question = $form->id;
1637 $questiondataset->datasetdefinition = $datasetdef->id;
bb4b6010 1638 $DB->insert_record('question_datasets', $questiondataset);
fbe2cfea 1639 unset($datasetdefinitions[$defid]);
1640 }
1641
1642 // Remove local obsolete datasets as well as relations
1643 // to datasets in other categories:
1644 if (!empty($datasetdefinitions)) {
1645 foreach ($datasetdefinitions as $def) {
1646 $DB->delete_records('question_datasets', array('question' => $form->id, 'datasetdefinition' => $def->id));
1647
1648 if ($def->category == 0) { // Question local dataset
1649 $DB->delete_records('question_dataset_definitions', array('id' => $def->id));
1650 $DB->delete_records('question_dataset_items', array('definition' => $def->id));
1651 }
1652 }
1653 }
1654 }
1655
1656/// Dataset functionality
1657 function pick_question_dataset($question, $datasetitem) {
1658 // Select a dataset in the following format:
1659 // An array indexed by the variable names (d.name) pointing to the value
1660 // to be substituted
1661 global $CFG, $DB;
a2155a7b 1662 if (!$dataitems = $DB->get_records_sql(
1663 "SELECT i.id, d.name, i.value
fbe2cfea 1664 FROM {question_dataset_definitions} d,
1665 {question_dataset_items} i,
1666 {question_datasets} q
1667 WHERE q.question = ?
1668 AND q.datasetdefinition = d.id
1669 AND d.id = i.definition
a2155a7b 1670 AND i.itemnumber = ? ORDER by i.id DESC ", array($question->id, $datasetitem))) {
fbe2cfea 1671 print_error('cannotgetdsfordependent', 'question', '', array($question->id, $datasetitem));
1672 }
a2155a7b 1673 $dataset = Array();
1674 foreach($dataitems as $id => $dataitem ){
1675 if (!isset($dataset[$dataitem->name])){
1676 $dataset[$dataitem->name] = $dataitem->value ;
1677 }else {
1678 // deleting the unused records could be added here
1679 }
1680 }
fbe2cfea 1681 return $dataset;
1682 }
aeb15530 1683
fbe2cfea 1684 function dataset_options_from_database($form, $name,$prefix='',$langfile='quiz') {
1685
1686 // First options - it is not a dataset...
1687 $options['0'] = get_string($prefix.'nodataset', $langfile);
1688
1689 // Construct question local options
1690 global $CFG, $DB;
aeb15530 1691 $type = 1 ; // only type = 1 (i.e. old 'LITERAL') has ever been used
cae0b24a 1692 if ( ! $currentdatasetdef = $DB->get_record_sql(
fbe2cfea 1693 "SELECT a.*
1694 FROM {question_dataset_definitions} a,
1695 {question_datasets} b
1696 WHERE a.id = b.datasetdefinition
cae0b24a 1697 AND a.type = '1'
fbe2cfea 1698 AND b.question = ?
cae0b24a 1699 AND a.name = ?", array($form->id, $name))){
1700 $currentdatasetdef->type = '0';
1701 };
1702 $key = "$type-0-$name";
1703 if ($currentdatasetdef->type == $type
1704 and $currentdatasetdef->category == 0) {
1705 $options[$key] = get_string($prefix."keptlocal$type", $langfile);
1706 } else {
1707 $options[$key] = get_string($prefix."newlocal$type", $langfile);
fbe2cfea 1708 }
fbe2cfea 1709 // Construct question category options
1710 $categorydatasetdefs = $DB->get_records_sql(
aeb15530 1711 "SELECT b.question, a.*
cae0b24a 1712 FROM {question_datasets} b,
aeb15530 1713 {question_dataset_definitions} a
fbe2cfea 1714 WHERE a.id = b.datasetdefinition
cae0b24a 1715 AND a.type = '1'
fbe2cfea 1716 AND a.category = ?
1717 AND a.name = ?", array($form->category, $name));
cae0b24a 1718 $type = 1 ;
1719 $key = "$type-$form->category-$name";
cae0b24a 1720 if (!empty($categorydatasetdefs)){ // there is at least one with the same name
1721 if (isset($categorydatasetdefs[$form->id])) {// it is already used by this question
fbe2cfea 1722 $options[$key] = get_string($prefix."keptcategory$type", $langfile);
1723 } else {
1724 $options[$key] = get_string($prefix."existingcategory$type", $langfile);
1725 }
cae0b24a 1726 } else {
1727 $options[$key] = get_string($prefix."newcategory$type", $langfile);
fbe2cfea 1728 }
fbe2cfea 1729 // All done!
1730 return array($options, $currentdatasetdef->type
1731 ? "$currentdatasetdef->type-$currentdatasetdef->category-$name"
1732 : '');
1733 }
1734
1735 function find_dataset_names($text) {
1736 /// Returns the possible dataset names found in the text as an array
1737 /// The array has the dataset name for both key and value
1738 $datasetnames = array();
6dbcacee 1739 while (preg_match('~\\{([[:alpha:]][^>} <{"\']*)\\}~', $text, $regs)) {
fbe2cfea 1740 $datasetnames[$regs[1]] = $regs[1];
1741 $text = str_replace($regs[0], '', $text);
1742 }
1743 return $datasetnames;
1744 }
1745
fd0973cc 1746 /**
1747 * This function retrieve the item count of the available category shareable
f34488b2 1748 * wild cards that is added as a comment displayed when a wild card with
fd0973cc 1749 * the same name is displayed in datasetdefinitions_form.php
f34488b2 1750 */
fd0973cc 1751 function get_dataset_definitions_category($form) {
f34488b2 1752 global $CFG, $DB;
fd0973cc 1753 $datasetdefs = array();
1754 $lnamemax = 30;
f34488b2 1755 if (!empty($form->category)) {
fd0973cc 1756 $sql = "SELECT i.*,d.*
f34488b2 1757 FROM {question_datasets} d,
1758 {question_dataset_definitions} i
fd0973cc 1759 WHERE i.id = d.datasetdefinition
8275477c 1760 AND i.category = ?";
f34488b2 1761 if ($records = $DB->get_records_sql($sql, array($form->category))) {
fd0973cc 1762 foreach ($records as $r) {
1763 if ( !isset ($datasetdefs["$r->name"])) $datasetdefs["$r->name"] = $r->itemcount;
1764 }
1765 }
f34488b2 1766 }
fd0973cc 1767 return $datasetdefs ;
f34488b2 1768 }
fd0973cc 1769
1770 /**
1771 * This function build a table showing the available category shareable
1772 * wild cards, their name, their definition (Min, Max, Decimal) , the item count
1773 * and the name of the question where they are used.
f34488b2 1774 * This table is intended to be add before the question text to help the user use
fd0973cc 1775 * these wild cards
f34488b2 1776 */
1777
fd0973cc 1778 function print_dataset_definitions_category($form) {
f34488b2 1779 global $CFG, $DB;
fd0973cc 1780 $datasetdefs = array();
1781 $lnamemax = 22;
1782 $namestr =get_string('name', 'quiz');
1783 $minstr=get_string('min', 'quiz');
1784 $maxstr=get_string('max', 'quiz');
5940864e 1785 $rangeofvaluestr=get_string('minmax','qtype_calculated');
fd0973cc 1786 $questionusingstr = get_string('usedinquestion','qtype_calculated');
5940864e 1787 $itemscountstr = get_string('itemscount','qtype_calculated');
fd0973cc 1788 $text ='';
f34488b2 1789 if (!empty($form->category)) {
08121fad 1790 list($category) = explode(',', $form->category);
fd0973cc 1791 $sql = "SELECT i.*,d.*
f34488b2 1792 FROM {question_datasets} d,
1793 {question_dataset_definitions} i
fd0973cc 1794 WHERE i.id = d.datasetdefinition
8275477c 1795 AND i.category = ?";
f34488b2 1796 if ($records = $DB->get_records_sql($sql, array($category))) {
fd0973cc 1797 foreach ($records as $r) {
1798 $sql1 = "SELECT q.*
f34488b2 1799 FROM {question} q
8275477c 1800 WHERE q.id = ?";
fd0973cc 1801 if ( !isset ($datasetdefs["$r->type-$r->category-$r->name"])){
1802 $datasetdefs["$r->type-$r->category-$r->name"]= $r;
1803 }
f34488b2 1804 if ($questionb = $DB->get_records_sql($sql1, array($r->question))) {
fd0973cc 1805 $datasetdefs["$r->type-$r->category-$r->name"]->questions[$r->question]->name =$questionb[$r->question]->name ;
1806 }
1807 }
1808 }
1809 }
1810 if (!empty ($datasetdefs)){
f34488b2 1811
1812 $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 1813 foreach ($datasetdefs as $datasetdef){
1814 list($distribution, $min, $max,$dec) = explode(':', $datasetdef->options, 4);
1815 $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\">";
1816 foreach ($datasetdef->questions as $qu) {
1817 //limit the name length displayed
1818 if (!empty($qu->name)) {
1819 $qu->name = (strlen($qu->name) > $lnamemax) ?
1820 substr($qu->name, 0, $lnamemax).'...' : $qu->name;
1821 } else {
1822 $qu->name = '';
1823 }
f34488b2 1824 $text .=" &nbsp;&nbsp; $qu->name <br/>";
1825 }
fd0973cc 1826 $text .="</td></tr>";
1827 }
1828 $text .="</table>";
1829 }else{
f34488b2 1830 $text .=get_string('nosharedwildcard', 'qtype_calculated');
fd0973cc 1831 }
1832 return $text ;
f34488b2 1833 }
ab8b5142
PP
1834
1835 /**
1836 * This function build a table showing the available category shareable
1837 * wild cards, their name, their definition (Min, Max, Decimal) , the item count
1838 * and the name of the question where they are used.
1839 * This table is intended to be add before the question text to help the user use
1840 * these wild cards
1841 */
1842
1843 function print_dataset_definitions_category_shared($question,$datasetdefsq) {
1844 global $CFG, $DB;
1845 $datasetdefs = array();
1846 $lnamemax = 22;
1847 $namestr =get_string('name', 'quiz');
1848 $minstr=get_string('min', 'quiz');
1849 $maxstr=get_string('max', 'quiz');
5940864e 1850 $rangeofvaluestr=get_string('minmax','qtype_calculated');
ab8b5142 1851 $questionusingstr = get_string('usedinquestion','qtype_calculated');
5940864e 1852 $itemscountstr = get_string('itemscount','qtype_calculated');
ab8b5142
PP
1853 $text ='';
1854 if (!empty($question->category)) {
1855 list($category) = explode(',', $question->category);
1856 $sql = "SELECT i.*,d.*
1857 FROM {question_datasets} d,
1858 {question_dataset_definitions} i
1859 WHERE i.id = d.datasetdefinition
8275477c 1860 AND i.category = ?";
ab8b5142
PP
1861 if ($records = $DB->get_records_sql($sql, array($category))) {
1862 foreach ($records as $r) {
1863 $sql1 = "SELECT q.*
1864 FROM {question} q
8275477c 1865 WHERE q.id = ?";
ab8b5142
PP
1866 if ( !isset ($datasetdefs["$r->type-$r->category-$r->name"])){
1867 $datasetdefs["$r->type-$r->category-$r->name"]= $r;
1868 }
1869 if ($questionb = $DB->get_records_sql($sql1, array($r->question))) {
1870 $datasetdefs["$r->type-$r->category-$r->name"]->questions[$r->question]->name =$questionb[$r->question]->name ;
1871 $datasetdefs["$r->type-$r->category-$r->name"]->questions[$r->question]->id =$questionb[$r->question]->id ;
1872 }
1873 }
1874 }
1875 }
1876 if (!empty ($datasetdefs)){
1877
1878 $text ="<table width=\"100%\" border=\"1\"><tr><th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\" >$namestr</th>";
1879 $text .="<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">$itemscountstr</th>";
1880 $text .="<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">&nbsp;&nbsp;$questionusingstr &nbsp;&nbsp; </th>";
1881 $text .="<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">Quiz</th>";
1882 $text .="<th style=\"white-space:nowrap;\" class=\"header\" scope=\"col\">Attempts</th></tr>";
1883 foreach ($datasetdefs as $datasetdef){
1884 list($distribution, $min, $max,$dec) = explode(':', $datasetdef->options, 4);
1885 $count = count($datasetdef->questions);
1886 $text .="<tr><td style=\"white-space:nowrap;\" valign=\"top\" align=\"center\" rowspan=\"$count\"> $datasetdef->name </td><td align=\"right\" valign=\"top\" rowspan=\"$count\" >$datasetdef->itemcount&nbsp;&nbsp;</td>";
1887 //$text .="<td align=\"left\">";
1888 $line = 0 ;
1889 foreach ($datasetdef->questions as $qu) {
1890 //limit the name length displayed
1891 if (!empty($qu->name)) {
1892 $qu->name = (strlen($qu->name) > $lnamemax) ?
1893 substr($qu->name, 0, $lnamemax).'...' : $qu->name;
1894 } else {
1895 $qu->name = '';
1896 }
1897 if( $line ) {
1898 $text .="<tr>";
1899 }
1900 $line++;
1901 $text .="<td align=\"left\" style=\"white-space:nowrap;\" >$qu->name</td>";
1902 $nb_of_quiz = 0;
1903 $nb_of_attempts=0;
1904 $used_in_quiz = false ;
1905 if ($list = $DB->get_records('quiz_question_instances', array( 'question'=> $qu->id))){
1906 $used_in_quiz = true;
1907 foreach($list as $key => $li){
1908 $nb_of_quiz ++;
1909 if($att = $DB->get_records('quiz_attempts',array( 'quiz'=> $li->quiz, 'preview'=> '0'))){
1910 $nb_of_attempts+= count($att);
1911 }
1912 }
1913 }
1914 if($used_in_quiz){
1915 $text .="<td align=\"center\">$nb_of_quiz</td>";
1916 }else {
1917 $text .="<td align=\"center\">0</td>";
1918 }
1919 if($used_in_quiz){
1920 $text .="<td align=\"center\">$nb_of_attempts";
1921 }else {
1922 $text .="<td align=\"left\"><br/>";
1923 }
1924
1925 $text .="</td></tr>";
1926 }
1927 }
1928 $text .="</table>";
1929 }else{
1930 $text .=get_string('nosharedwildcard', 'qtype_calculated');
1931 }
1932 return $text ;
1933 }
1934
1935 function find_math_equations($text) {
1936 /// Returns the possible dataset names found in the text as an array
1937 /// The array has the dataset name for both key and value
1938 $equations = array();
1939 /* $qtext = "";
1940 $qtextremaining = $numericalquestion->questiontext ;
1941 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
1942 // while (preg_match('~\{=|%=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
1943 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
1944 $qtext =$qtext.$qtextsplits[0];
1945 $qtextremaining = $qtextsplits[1];
1946 if (empty($regs1[1])) {
1947 $str = '';
1948 } else {
1949*/
1950 while (preg_match('~\{=([^[:space:]}]*)}~', $text, $regs)) {
1951 $equations[] = $regs[1];
1952 $text = str_replace($regs[0], '', $text);
1953 }
1954 return $equations;
1955 }
1956
1957 function get_virtual_qtype() {
28a27ef1 1958 global $QTYPES;
28a27ef1 1959 $this->virtualqtype =& $QTYPES['numerical'];
fbe2cfea 1960 return $this->virtualqtype;
1961 }
fd0973cc 1962
92186abc 1963
c5d94c41 1964/// BACKUP FUNCTIONS ////////////////////////////
1965
1966 /*
1967 * Backup the data in the question
1968 *
1969 * This is used in question/backuplib.php
1970 */
1971 function backup($bf,$preferences,$question,$level=6) {
f34488b2 1972 global $DB;
c5d94c41 1973 $status = true;
1974
f34488b2 1975 $calculateds = $DB->get_records("question_calculated",array("question" =>$question,"id"));
c5d94c41 1976 //If there are calculated-s
1977 if ($calculateds) {
1978 //Iterate over each calculateds
1979 foreach ($calculateds as $calculated) {
1980 $status = $status &&fwrite ($bf,start_tag("CALCULATED",$level,true));
1981 //Print calculated contents
1982 fwrite ($bf,full_tag("ANSWER",$level+1,false,$calculated->answer));
1983 fwrite ($bf,full_tag("TOLERANCE",$level+1,false,$calculated->tolerance));
1984 fwrite ($bf,full_tag("TOLERANCETYPE",$level+1,false,$calculated->tolerancetype));
1985 fwrite ($bf,full_tag("CORRECTANSWERLENGTH",$level+1,false,$calculated->correctanswerlength));
1986 fwrite ($bf,full_tag("CORRECTANSWERFORMAT",$level+1,false,$calculated->correctanswerformat));
1987 //Now backup numerical_units
1988 $status = question_backup_numerical_units($bf,$preferences,$question,7);
1989 //Now backup required dataset definitions and items...
1990 $status = question_backup_datasets($bf,$preferences,$question,7);
1991 //End calculated data
1992 $status = $status &&fwrite ($bf,end_tag("CALCULATED",$level,true));
1993 }
1ee53ca9 1994 $calculated_options = $DB->get_records("question_calculated_options",array("questionid" => $question),"id");
1995 if ($calculated_options) {
1996 //Iterate over each calculated_option
1997 foreach ($calculated_options as $calculated_option) {
1998 $status = fwrite ($bf,start_tag("CALCULATED_OPTIONS",$level,true));
1999 //Print calculated_option contents
2000 fwrite ($bf,full_tag("SYNCHRONIZE",$level+1,false,$calculated_option->synchronize));
28a27ef1 2001 fwrite ($bf,full_tag("SINGLE",$level+1,false,$calculated_option->single));
2002 fwrite ($bf,full_tag("SHUFFLEANSWERS",$level+1,false,$calculated_option->shuffleanswers));
2003 fwrite ($bf,full_tag("CORRECTFEEDBACK",$level+1,false,$calculated_option->correctfeedback));
2004 fwrite ($bf,full_tag("PARTIALLYCORRECTFEEDBACK",$level+1,false,$calculated_option->partiallycorrectfeedback));
2005 fwrite ($bf,full_tag("INCORRECTFEEDBACK",$level+1,false,$calculated_option->incorrectfeedback));
2006 fwrite ($bf,full_tag("ANSWERNUMBERING",$level+1,false,$calculated_option->answernumbering));
1ee53ca9 2007 $status = fwrite ($bf,end_tag("CALCULATED_OPTIONS",$level,true));
2008 }
1ee53ca9 2009 }
c5da9906 2010 $status = question_backup_numerical_options($bf,$preferences,$question,$level);
2011
c5d94c41 2012 }
2013 return $status;
2014 }
315559d3 2015
2016/// RESTORE FUNCTIONS /////////////////
2017
2018 /*
2019 * Restores the data in the question
2020 *
2021 * This is used in question/restorelib.php
2022 */
2023 function restore($old_question_id,$new_question_id,$info,$restore) {
9db7dab2 2024 global $DB;
315559d3 2025
2026 $status = true;
2027
2028 //Get the calculated-s array
2029 $calculateds = $info['#']['CALCULATED'];
2030
2031 //Iterate over calculateds
2032 for($i = 0; $i < sizeof($calculateds); $i++) {
2033 $cal_info = $calculateds[$i];
2034 //traverse_xmlize($cal_info); //Debug
2035 //print_object ($GLOBALS['traverse_array']); //Debug
2036 //$GLOBALS['traverse_array']=""; //Debug
2037
2038 //Now, build the question_calculated record structure
2039 $calculated->question = $new_question_id;
2040 $calculated->answer = backup_todb($cal_info['#']['ANSWER']['0']['#']);
2041 $calculated->tolerance = backup_todb($cal_info['#']['TOLERANCE']['0']['#']);
2042 $calculated->tolerancetype = backup_todb($cal_info['#']['TOLERANCETYPE']['0']['#']);
2043 $calculated->correctanswerlength = backup_todb($cal_info['#']['CORRECTANSWERLENGTH']['0']['#']);
2044 $calculated->correctanswerformat = backup_todb($cal_info['#']['CORRECTANSWERFORMAT']['0']['#']);
2045
2046 ////We have to recode the answer field
2047 $answer = backup_getid($restore->backup_unique_code,"question_answers",$calculated->answer);
2048 if ($answer) {
2049 $calculated->answer = $answer->new_id;
2050 }
2051
2052 //The structure is equal to the db, so insert the question_calculated
9db7dab2 2053 $newid = $DB->insert_record ("question_calculated",$calculated);
315559d3 2054
2055 //Do some output
2056 if (($i+1) % 50 == 0) {
2057 if (!defined('RESTORE_SILENTLY')) {
2058 echo ".";
2059 if (($i+1) % 1000 == 0) {
2060 echo "<br />";
2061 }
2062 }
2063 backup_flush(300);
2064 }
1ee53ca9 2065 //Get the calculated_options array
2066 // need to check as old questions don't have calculated_options record
2067 if(isset($info['#']['CALCULATED_OPTIONS'])){
2068 $calculatedoptions = $info['#']['CALCULATED_OPTIONS'];
aeb15530 2069
1ee53ca9 2070 //Iterate over calculated_options
28a27ef1 2071 for($i = 0; $i < sizeof($calculatedoptions); $i++){
1ee53ca9 2072 $cal_info = $calculatedoptions[$i];
2073 //traverse_xmlize($cal_info); //Debug
2074 //print_object ($GLOBALS['traverse_array']); //Debug
2075 //$GLOBALS['traverse_array']=""; //Debug
aeb15530 2076
1ee53ca9 2077 //Now, build the question_calculated_options record structure
2078 $calculated_options->questionid = $new_question_id;
2079 $calculated_options->synchronize = backup_todb($cal_info['#']['SYNCHRONIZE']['0']['#']);
28a27ef1 2080 $calculated_options->single = backup_todb($cal_info['#']['SINGLE']['0']['#']);
2081 $calculated_options->shuffleanswers = isset($cal_info['#']['SHUFFLEANSWERS']['0']['#'])?backup_todb($mul_info['#']['SHUFFLEANSWERS']['0']['#']):'';
2082 $calculated_options->correctfeedback = backup_todb($cal_info['#']['CORRECTFEEDBACK']['0']['#']);
2083 $calculated_options->partiallycorrectfeedback = backup_todb($cal_info['#']['PARTIALLYCORRECTFEEDBACK']['0']['#']);
2084 $calculated_options->incorrectfeedback = backup_todb($cal_info['#']['INCORRECTFEEDBACK']['0']['#']);
2085 $calculated_options->answernumbering = backup_todb($cal_info['#']['ANSWERNUMBERING']['0']['#']);
aeb15530 2086
1ee53ca9 2087 //The structure is equal to the db, so insert the question_calculated_options
2088 $newid = $DB->insert_record ("question_calculated_options",$calculated_options);
aeb15530 2089
1ee53ca9 2090 //Do some output
2091 if (($i+1) % 50 == 0) {
2092 if (!defined('RESTORE_SILENTLY')) {
2093 echo ".";
2094 if (($i+1) % 1000 == 0) {
2095 echo "<br />";
2096 }
2097 }
2098 backup_flush(300);
2099 }
2100 }
28a27ef1 2101 }
315559d3 2102 //Now restore numerical_units
2103 $status = question_restore_numerical_units ($old_question_id,$new_question_id,$cal_info,$restore);
c5da9906 2104 $status = question_restore_numerical_options($old_question_id,$new_question_id,$info,$restore);
315559d3 2105 //Now restore dataset_definitions
2106 if ($status && $newid) {
2107 $status = question_restore_dataset_definitions ($old_question_id,$new_question_id,$cal_info,$restore);
2108 }
2109
2110 if (!$newid) {
2111 $status = false;
2112 }
2113 }
2114
2115 return $status;
2116 }
0d03d6be 2117
a2155a7b 2118 /**
0d03d6be 2119 * Runs all the code required to set up and save an essay question for testing purposes.
2120 * Alternate DB table prefix may be used to facilitate data deletion.
2121 */
2122 function generate_test($name, $courseid = null) {
01f3ba13 2123 global $DB;
0d03d6be 2124 list($form, $question) = parent::generate_test($name, $courseid);
2125 $form->feedback = 1;
2126 $form->multiplier = array(1, 1);
2127 $form->shuffleanswers = 1;
2128 $form->noanswers = 1;
2129 $form->qtype ='calculated';
2130 $question->qtype ='calculated';
2131 $form->answers = array('{a} + {b}');
2132 $form->fraction = array(1);
2133 $form->tolerance = array(0.01);
2134 $form->tolerancetype = array(1);
2135 $form->correctanswerlength = array(2);
2136 $form->correctanswerformat = array(1);
2137 $form->questiontext = "What is {a} + {b}?";
2138
2139 if ($courseid) {
c9f4940e 2140 $course = $DB->get_record('course', array('id'=> $courseid));
0d03d6be 2141 }
2142
2143 $new_question = $this->save_question($question, $form, $course);
2144
2145 $dataset_form = new stdClass();
2146 $dataset_form->nextpageparam["forceregeneration"]= 1;
2147 $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0);
2148 $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0);
2149 $dataset_form->calclength = array(1 => 1, 2 => 1);
2150 $dataset_form->number = array(1 => 5.4 , 2 => 4.9);
2151 $dataset_form->itemid = array(1 => '' , 2 => '');
2152 $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform');
2153 $dataset_form->definition = array(1 => "1-0-a",
2154 2 => "1-0-b");
2155 $dataset_form->nextpageparam = array('forceregeneration' => false);
2156 $dataset_form->addbutton = 1;
2157 $dataset_form->selectadd = 1;
2158 $dataset_form->courseid = $courseid;
2159 $dataset_form->cmid = 0;
2160 $dataset_form->id = $new_question->id;
2161 $this->save_dataset_items($new_question, $dataset_form);
2162
2163 return $new_question;
2164 }
516cf3eb 2165}
2166//// END OF CLASS ////
2167
2168//////////////////////////////////////////////////////////////////////////
2169//// INITIATION - Without this line the question type is not in use... ///
2170//////////////////////////////////////////////////////////////////////////
a2156789 2171question_register_questiontype(new question_calculated_qtype());
516cf3eb 2172
7518b645 2173function qtype_calculated_calculate_answer($formula, $individualdata,
516cf3eb 2174 $tolerance, $tolerancetype, $answerlength, $answerformat='1', $unit='') {
2175/// The return value has these properties:
2176/// ->answer the correct answer
2177/// ->min the lower bound for an acceptable response
2178/// ->max the upper bound for an accetpable response
2179
2180 /// Exchange formula variables with the correct values...
f02c6f01 2181 global $QTYPES;
fbe2cfea 2182 $answer = $QTYPES['calculated']->substitute_variables_and_eval($formula, $individualdata);
516cf3eb 2183 if ('1' == $answerformat) { /* Answer is to have $answerlength decimals */
2184 /*** Adjust to the correct number of decimals ***/
1d9ed698 2185 if (stripos($answer,'e')>0 ){
2186 $answerlengthadd = strlen($answer)-stripos($answer,'e');
2187 }else {
2188 $answerlengthadd = 0 ;
2189 }
2190 $calculated->answer = round(floatval($answer), $answerlength+$answerlengthadd);
516cf3eb 2191
2192 if ($answerlength) {
2193 /* Try to include missing zeros at the end */
2194
6dbcacee 2195 if (preg_match('~^(.*\\.)(.*)$~', $calculated->answer, $regs)) {
516cf3eb 2196 $calculated->answer = $regs[1] . substr(
2197 $regs[2] . '00000000000000000000000000000000000000000x',
2198 0, $answerlength)
2199 . $unit;
2200 } else {
2201 $calculated->answer .=
2202 substr('.00000000000000000000000000000000000000000x',
2203 0, $answerlength + 1) . $unit;
2204 }
2205 } else {
2206 /* Attach unit */
2207 $calculated->answer .= $unit;
2208 }
2209
2210 } else if ($answer) { // Significant figures does only apply if the result is non-zero
2211
2212 // Convert to positive answer...
2213 if ($answer < 0) {
2214 $answer = -$answer;
2215 $sign = '-';
2216 } else {
2217 $sign = '';
2218 }
2219
2220 // Determine the format 0.[1-9][0-9]* for the answer...
2221 $p10 = 0;
2222 while ($answer < 1) {
2223 --$p10;
2224 $answer *= 10;
2225 }
2226 while ($answer >= 1) {
2227 ++$p10;
2228 $answer /= 10;
2229 }
2230 // ... and have the answer rounded of to the correct length
2231 $answer = round($answer, $answerlength);
2232
2233 // Have the answer written on a suitable format,
2234 // Either scientific or plain numeric
2235 if (-2 > $p10 || 4 < $p10) {
2236 // Use scientific format:
2237 $eX = 'e'.--$p10;
2238 $answer *= 10;
2239 if (1 == $answerlength) {
2240 $calculated->answer = $sign.$answer.$eX.$unit;
2241 } else {
2242 // Attach additional zeros at the end of $answer,
2243 $answer .= (1==strlen($answer) ? '.' : '')
2244 . '00000000000000000000000000000000000000000x';
2245 $calculated->answer = $sign
2246 .substr($answer, 0, $answerlength +1).$eX.$unit;
2247 }
2248 } else {
2249 // Stick to plain numeric format
2250 $answer *= "1e$p10";
2251 if (0.1 <= $answer / "1e$answerlength") {
2252 $calculated->answer = $sign.$answer.$unit;
2253 } else {
2254 // Could be an idea to add some zeros here
6dbcacee 2255 $answer .= (preg_match('~^[0-9]*$~', $answer) ? '.' : '')
516cf3eb 2256 . '00000000000000000000000000000000000000000x';
2257 $oklen = $answerlength + ($p10 < 1 ? 2-$p10 : 1);
2258 $calculated->answer = $sign.substr($answer, 0, $oklen).$unit;
2259 }
2260 }
2261
2262 } else {
2263 $calculated->answer = 0.0;
2264 }
2265
2266 /// Return the result
2267 return $calculated;
2268}
2269
2270
7518b645 2271function qtype_calculated_find_formula_errors($formula) {
516cf3eb 2272/// Validates the formula submitted from the question edit page.
2273/// Returns false if everything is alright.
2274/// Otherwise it constructs an error message
516cf3eb 2275 // Strip away dataset names
6dbcacee 2276 while (preg_match('~\\{[[:alpha:]][^>} <{"\']*\\}~', $formula, $regs)) {
516cf3eb 2277 $formula = str_replace($regs[0], '1', $formula);
2278 }
2279
2280 // Strip away empty space and lowercase it
2281 $formula = strtolower(str_replace(' ', '', $formula));
2282
86e85775 2283 $safeoperatorchar = '-+/*%>:^\~<?=&|!'; /* */
516cf3eb 2284 $operatorornumber = "[$safeoperatorchar.0-9eE]";
2285
1ee53ca9 2286 while ( preg_match("~(^|[$safeoperatorchar,(])([a-z0-9_]*)\\(($operatorornumber+(,$operatorornumber+((,$operatorornumber+)+)?)?)?\\)~",
516cf3eb 2287 $formula, $regs)) {
2288
2289 switch ($regs[2]) {
2290 // Simple parenthesis
2291 case '':
c9026379 2292 if ($regs[4] || strlen($regs[3])==0) {
516cf3eb 2293 return get_string('illegalformulasyntax', 'quiz', $regs[0]);
2294 }
2295 break;
2296
2297 // Zero argument functions
2298 case 'pi':
2299 if ($regs[3]) {
2300 return get_string('functiontakesnoargs', 'quiz', $regs[2]);
2301 }
2302 break;
2303
2304 // Single argument functions (the most common case)
2305 case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh':
2306 case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos':
2307 case 'cosh': case 'decbin': case 'decoct': case 'deg2rad':
2308 case 'exp': case 'expm1': case 'floor': case 'is_finite':
2309 case 'is_infinite': case 'is_nan': case 'log10': case 'log1p':
2310 case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt':
2311 case 'tan': case 'tanh':
c5da9906 2312 if (!empty($regs[4]) || empty($regs[3])) {
516cf3eb 2313 return get_string('functiontakesonearg','quiz',$regs[2]);
2314 }
2315 break;
2316
2317 // Functions that take one or two arguments
2318 case 'log': case 'round':
c5da9906 2319 if (!empty($regs[5]) || empty($regs[3])) {
516cf3eb 2320 return get_string('functiontakesoneortwoargs','quiz',$regs[2]);
2321 }
2322 break;
2323
2324 // Functions that must have two arguments
2325 case 'atan2': case 'fmod': case 'pow':
c5da9906 2326 if (!empty($regs[5]) || empty($regs[4])) {
516cf3eb 2327 return get_string('functiontakestwoargs', 'quiz', $regs[2]);
2328 }
2329 break;
2330
2331 // Functions that take two or more arguments
2332 case 'min': case 'max':
2333 if (empty($regs[4])) {
2334 return get_string('functiontakesatleasttwo','quiz',$regs[2]);
2335 }
2336 break;
2337
2338 default:
2339 return get_string('unsupportedformulafunction','quiz',$regs[2]);
2340 }
2341
2342 // Exchange the function call with '1' and then chack for
2343 // another function call...
2344 if ($regs[1]) {
2345 // The function call is proceeded by an operator
2346 $formula = str_replace($regs[0], $regs[1] . '1', $formula);
2347 } else {
2348 // The function call starts the formula
6dbcacee 2349 $formula = preg_replace("~^$regs[2]\\([^)]*\\)~", '1', $formula);
516cf3eb 2350 }
2351 }
2352
6dbcacee 2353 if (preg_match("~[^$safeoperatorchar.0-9eE]+~", $formula, $regs)) {
516cf3eb 2354 return get_string('illegalformulasyntax', 'quiz', $regs[0]);
2355 } else {
2356 // Formula just might be valid
2357 return false;
2358 }
fd6b864f 2359
516cf3eb 2360}
2361
2362function dump($obj) {
2363 echo "<pre>\n";
2364 var_dump($obj);
2365 echo "</pre><br />\n";
2366}
2367
aeb15530 2368