curl now mandatory
[moodle.git] / question / type / calculatedmulti / questiontype.php
CommitLineData
2d279432
PP
1<?php
2
3/////////////////
4// CALCULATED ///
5/////////////////
6
7/// QUESTION TYPE CLASS //////////////////
8
9
10
11class question_calculatedmulti_qtype extends question_calculated_qtype {
12
13 // Used by the function custom_generator_tools:
14 var $calcgenerateidhasbeenadded = false;
15 public $virtualqtype = false;
16
17 function name() {
18 return 'calculatedmulti';
19 }
20
21 function has_wildcards_in_responses($question, $subqid) {
22 return true;
23 }
24
25 function requires_qtypes() {
26 return array('multichoice');
27 }
2d279432 28
2d279432
PP
29
30 function save_question_options($question) {
31 //$options = $question->subtypeoptions;
32 // Get old answers:
33 global $CFG, $DB, $QTYPES ;
34 if (isset($question->answer) && !isset($question->answers)) {
35 $question->answers = $question->answer;
36 }
37 // calculated options
38 $update = true ;
39 $options = $DB->get_record("question_calculated_options", array("question" => $question->id));
40 if (!$options) {
41 $update = false;
42 $options = new stdClass;
43 $options->question = $question->id;
44 }
45 $options->synchronize = $question->synchronize;
2d279432
PP
46 $options->single = $question->single;
47 $options->answernumbering = $question->answernumbering;
48 $options->shuffleanswers = $question->shuffleanswers;
49 $options->correctfeedback = trim($question->correctfeedback);
50 $options->partiallycorrectfeedback = trim($question->partiallycorrectfeedback);
51 $options->incorrectfeedback = trim($question->incorrectfeedback);
52 if ($update) {
53 if (!$DB->update_record("question_calculated_options", $options)) {
54 $result->error = "Could not update calculated question options! (id=$options->id)";
55 return $result;
56 }
57 } else {
58 if (!$DB->insert_record("question_calculated_options", $options)) {
59 $result->error = "Could not insert calculated question options!";
60 return $result;
61 }
62 }
63
64 // Get old versions of the objects
65 if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) {
66 $oldanswers = array();
67 }
68
69 if (!$oldoptions = $DB->get_records('question_calculated', array('question' => $question->id), 'answer ASC')) {
70 $oldoptions = array();
71 }
72
73 // Save the units.
74 $virtualqtype = $this->get_virtual_qtype( $question);
75 // $result = $virtualqtype->save_numerical_units($question);
76 if (isset($result->error)) {
77 return $result;
78 } else {
79 $units = &$result->units;
80 }
81 // Insert all the new answers
82 if (isset($question->answer) && !isset($question->answers)) {
83 $question->answers=$question->answer;
84 }
85 foreach ($question->answers as $key => $dataanswer) {
86 if ( trim($dataanswer) != '' ) {
87 $answer = new stdClass;
88 $answer->question = $question->id;
89 $answer->answer = trim($dataanswer);
90 $answer->fraction = $question->fraction[$key];
91 $answer->feedback = trim($question->feedback[$key]);
92
93 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
94 $answer->id = $oldanswer->id;
95 $DB->update_record("question_answers", $answer);
96 } else { // This is a completely new answer
97 $answer->id = $DB->insert_record("question_answers", $answer);
98 }
99
100 // Set up the options object
101 if (!$options = array_shift($oldoptions)) {
102 $options = new stdClass;
103 }
104 $options->question = $question->id;
105 $options->answer = $answer->id;
106 $options->tolerance = trim($question->tolerance[$key]);
107 $options->tolerancetype = trim($question->tolerancetype[$key]);
108 $options->correctanswerlength = trim($question->correctanswerlength[$key]);
109 $options->correctanswerformat = trim($question->correctanswerformat[$key]);
110
111 // Save options
112 if (isset($options->id)) { // reusing existing record
113 $DB->update_record('question_calculated', $options);
114 } else { // new options
115 $DB->insert_record('question_calculated', $options);
116 }
117 }
118 }
119 // delete old answer records
120 if (!empty($oldanswers)) {
121 foreach($oldanswers as $oa) {
122 $DB->delete_records('question_answers', array('id' => $oa->id));
123 }
124 }
125
126 // delete old answer records
127 if (!empty($oldoptions)) {
128 foreach($oldoptions as $oo) {
129 $DB->delete_records('question_calculated', array('id' => $oo->id));
130 }
131 }
132 // $result = $QTYPES['numerical']->save_numerical_options($question);
133 // if (isset($result->error)) {
134 // return $result;
135 // }
136
137
138 if( isset($question->import_process)&&$question->import_process){
139 $this->import_datasets($question);
140 }
141 // Report any problems.
142 if (!empty($result->notice)) {
143 return $result;
144 }
145 return true;
146 }
147
148 function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
149 // Find out how many datasets are available
d90b016b 150 global $CFG, $DB, $QTYPES, $OUTPUT ;
2d279432
PP
151 if(!$maxnumber = (int)$DB->get_field_sql(
152 "SELECT MIN(a.itemcount)
153 FROM {question_dataset_definitions} a,
154 {question_datasets} b
155 WHERE b.question = ?
156 AND a.id = b.datasetdefinition", array($question->id))) {
157 print_error('cannotgetdsforquestion', 'question', '', $question->id);
158 }
159 $sql = "SELECT i.*
160 FROM {question_datasets} d,
161 {question_dataset_definitions} i
162 WHERE d.question = ?
163 AND d.datasetdefinition = i.id
164 AND i.category != 0
165 ";
166 if (!$question->options->synchronize || !$records = $DB->get_records_sql($sql, array($question->id))) {
167 $synchronize_calculated = false ;
168 }else {
d90b016b
PP
169 // i.e records is true so test coherence
170 $coherence = true ;
171 $a = new stdClass ;
172 $a->qid = $question->id ;
173 $a->qcat = $question->category ;
174 foreach($records as $def ){
175 if ($def->category != $question->category){
176 $a->name = $def->name;
177 $a->sharedcat = $def->category ;
178 $coherence = false ;
179 break;
180 }
181 }
182 if(!$coherence){
183 echo $OUTPUT->notification(get_string('nocoherencequestionsdatyasetcategory','qtype_calculated',$a));
184 }
185
2d279432
PP
186 $synchronize_calculated = true ;
187 }
188
189 // Choose a random dataset
d90b016b
PP
190 // maxnumber sould not be breater than 100
191 if ($maxnumber > CALCULATEDQUESTIONMAXITEMNUMBER ){
192 $maxnumber = CALCULATEDQUESTIONMAXITEMNUMBER ;
193 }
2d279432
PP
194 if ( $synchronize_calculated === false ) {
195 $state->options->datasetitem = rand(1, $maxnumber);
196 }else{
197 $state->options->datasetitem = intval( $maxnumber * substr($attempt->timestart,-2) /100 ) ;
198 if ($state->options->datasetitem < 1) {
199 $state->options->datasetitem =1 ;
200 } else if ($state->options->datasetitem > $maxnumber){
201 $state->options->datasetitem = $maxnumber ;
202 }
203
204 };
205 $state->options->dataset =
206 $this->pick_question_dataset($question,$state->options->datasetitem);
207 // create an array of answerids ??? why so complicated ???
208 $answerids = array_values(array_map(create_function('$val',
209 'return $val->id;'), $question->options->answers));
210 // Shuffle the answers if required
211 if (!empty($cmoptions->shuffleanswers) and !empty($question->options->shuffleanswers)) {
212 $answerids = swapshuffle($answerids);
213 }
214 $state->options->order = $answerids;
215 // Create empty responses
216 if ($question->options->single) {
217 $state->responses = array('' => '');
218 } else {
219 $state->responses = array();
220 }
221 return true;
222
223 }
224
225 function save_session_and_responses(&$question, &$state) {
226 global $DB;
227 $responses = 'dataset'.$state->options->datasetitem.'-' ;
228 $responses .= implode(',', $state->options->order) . ':';
229 $responses .= implode(',', $state->responses);
230
231 // Set the legacy answer field
232 if (!$DB->set_field('question_states', 'answer', $responses, array('id'=> $state->id))) {
233 return false;
234 }
235 return true;
236 }
237
238 function create_runtime_question($question, $form) {
78a6d0ba 239 $question = default_questiontype::create_runtime_question($question, $form);
2d279432
PP
240 $question->options->answers = array();
241 foreach ($form->answers as $key => $answer) {
242 $a->answer = trim($form->answer[$key]);
243 $a->fraction = $form->fraction[$key];//new
244 $a->tolerance = $form->tolerance[$key];
245 $a->tolerancetype = $form->tolerancetype[$key];
246 $a->correctanswerlength = $form->correctanswerlength[$key];
247 $a->correctanswerformat = $form->correctanswerformat[$key];
248 $question->options->answers[] = clone($a);
249 }
250
251 return $question;
252 }
253
2d279432 254
2d279432 255
2d279432
PP
256
257
2d279432
PP
258 function convert_answers (&$question, &$state){
259 foreach ($question->options->answers as $key => $answer) {
260 $answer->answer = $this->substitute_variables($answer->answer, $state->options->dataset);
261 //evaluate the equations i.e {=5+4)
262 $qtext = "";
263 $qtextremaining = $answer->answer ;
264 // while (preg_match('~\{(=)|%[[:digit]]\.=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
265 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
266
267 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
268 $qtext =$qtext.$qtextsplits[0];
269 $qtextremaining = $qtextsplits[1];
270 if (empty($regs1[1])) {
271 $str = '';
272 } else {
273 if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
274 $str=$formulaerrors ;
275 }else {
276 eval('$str = '.$regs1[1].';');
277 $texteval= qtype_calculated_calculate_answer(
278 $str, $state->options->dataset, $answer->tolerance,
279 $answer->tolerancetype, $answer->correctanswerlength,
280 $answer->correctanswerformat, '');
281 $str = $texteval->answer;
282 }
283 }
34e24cd3 284 $qtext = $qtext.$str ;
2d279432
PP
285 }
286 $answer->answer = $qtext.$qtextremaining ; ;
287 }
288 }
289
290 function get_default_numerical_unit($question,$virtualqtype){
291 $unit = '';
292 return $unit ;
293 }
294 function grade_responses(&$question, &$state, $cmoptions) {
295 // Forward the grading to the virtual qtype
296 // We modify the question to look like a multichoice question
297 // for grading nothing to do
298/* $numericalquestion = fullclone($question);
299 foreach ($numericalquestion->options->answers as $key => $answer) {
300 $answer = $numericalquestion->options->answers[$key]->answer; // for PHP 4.x
301 $numericalquestion->options->answers[$key]->answer = $this->substitute_variables_and_eval($answer,
302 $state->options->dataset);
303 }*/
304 $virtualqtype = $this->get_virtual_qtype( $question);
305 return $virtualqtype->grade_responses($question, $state, $cmoptions) ;
306 }
307
2d279432 308
2d279432
PP
309
310 // ULPGC ecastro
311 function get_actual_response(&$question, &$state) {
312 // Substitute variables in questiontext before giving the data to the
313 // virtual type
314 $virtualqtype = $this->get_virtual_qtype( $question);
315 $unit = '' ;//$virtualqtype->get_default_numerical_unit($question);
316
317 // We modify the question to look like a multichoice question
318 $numericalquestion = clone($question);
319 $this->convert_answers ($numericalquestion, $state);
320 $this->convert_questiontext ($numericalquestion, $state);
321 /* $numericalquestion->questiontext = $this->substitute_variables_and_eval(
322 $numericalquestion->questiontext, $state->options->dataset);*/
323 $responses = $virtualqtype->get_all_responses($numericalquestion, $state);
324 $response = reset($responses->responses);
325 $correct = $response->answer.' : ';
326
327 $responses = $virtualqtype->get_actual_response($numericalquestion, $state);
328
329 foreach ($responses as $key=>$response){
330 $responses[$key] = $correct.$response;
331 }
332
333 return $responses;
334 }
335
336 function create_virtual_qtype() {
337 global $CFG;
338 require_once("$CFG->dirroot/question/type/multichoice/questiontype.php");
339 return new question_multichoice_qtype();
340 }
341
342
343 function comment_header($question) {
344 //$this->get_question_options($question);
345 $strheader = '';
346 $delimiter = '';
347
348 $answers = $question->options->answers;
349
350 foreach ($answers as $key => $answer) {
351 if (is_string($answer)) {
352 $strheader .= $delimiter.$answer;
353 } else {
354 $strheader .= $delimiter.$answer->answer;
355 }
356 $delimiter = '<br/>';
357 }
358 return $strheader;
359 }
360
361 function comment_on_datasetitems($qtypeobj,$questionid,$questiontext, $answers,$data, $number) { //multichoice_
362 global $DB;
363 $comment = new stdClass;
364 $comment->stranswers = array();
365 $comment->outsidelimit = false ;
366 $comment->answers = array();
367 /// Find a default unit:
368 /* if (!empty($questionid) && $unit = $DB->get_record('question_numerical_units', array('question'=> $questionid, 'multiplier' => 1.0))) {
369 $unit = $unit->unit;
370 } else {
371 $unit = '';
372 }*/
373
374 $answers = fullclone($answers);
375 $strmin = get_string('min', 'quiz');
376 $strmax = get_string('max', 'quiz');
377 $errors = '';
378 $delimiter = ': ';
379 foreach ($answers as $key => $answer) {
380 $answer->answer = $this->substitute_variables($answer->answer, $data);
381 //evaluate the equations i.e {=5+4)
382 $qtext = "";
383 $qtextremaining = $answer->answer ;
384 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
385 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
386 $qtext =$qtext.$qtextsplits[0];
387 $qtextremaining = $qtextsplits[1];
388 if (empty($regs1[1])) {
389 $str = '';
390 } else {
391 if( $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])){
392 $str=$formulaerrors ;
393 }else {
394 eval('$str = '.$regs1[1].';');
395 }
396 }
397 $qtext = $qtext.$str ;
398 }
399 $answer->answer = $qtext.$qtextremaining ; ;
400 $comment->stranswers[$key]= $answer->answer ;
401
402
403 /* $formula = $this->substitute_variables($answer->answer,$data);
404 $formattedanswer = qtype_calculated_calculate_answer(
405 $answer->answer, $data, $answer->tolerance,
406 $answer->tolerancetype, $answer->correctanswerlength,
407 $answer->correctanswerformat, $unit);
408 if ( $formula === '*'){
409 $answer->min = ' ';
410 $formattedanswer->answer = $answer->answer ;
411 }else {
412 eval('$answer->answer = '.$formula.';') ;
413 $virtualqtype->get_tolerance_interval($answer);
414 }
415 if ($answer->min === '') {
416 // This should mean that something is wrong
417 $comment->stranswers[$key] = " $formattedanswer->answer".'<br/><br/>';
418 } else if ($formula === '*'){
419 $comment->stranswers[$key] = $formula.' = '.get_string('anyvalue','qtype_calculated').'<br/><br/><br/>';
420 }else{
421 $comment->stranswers[$key]= $formula.' = '.$formattedanswer->answer.'<br/>' ;
422 $comment->stranswers[$key] .= $strmin. $delimiter.$answer->min.'---';
423 $comment->stranswers[$key] .= $strmax.$delimiter.$answer->max;
424 $comment->stranswers[$key] .='<br/>';
425 $correcttrue->correct = $formattedanswer->answer ;
426 $correcttrue->true = $answer->answer ;
427 if ($formattedanswer->answer < $answer->min || $formattedanswer->answer > $answer->max){
428 $comment->outsidelimit = true ;
429 $comment->answers[$key] = $key;
430 $comment->stranswers[$key] .=get_string('trueansweroutsidelimits','qtype_calculated',$correcttrue);//<span class="error">ERROR True answer '..' outside limits</span>';
431 }else {
432 $comment->stranswers[$key] .=get_string('trueanswerinsidelimits','qtype_calculated',$correcttrue);//' True answer :'.$calculated->trueanswer.' inside limits';
433 }
434 $comment->stranswers[$key] .='';
435 }*/
436 }
437 return fullclone($comment);
438 }
439
440
441
442
443
444 function get_correct_responses1(&$question, &$state) {
445 $virtualqtype = $this->get_virtual_qtype( $question);
446 /* if ($question->options->multichoice != 1 ) {
447 if($unit = $virtualqtype->get_default_numerical_unit($question)){
448 $unit = $unit->unit;
449 } else {
450 $unit = '';
451 }
452 foreach ($question->options->answers as $answer) {
453 if (((int) $answer->fraction) === 1) {
454 $answernumerical = qtype_calculated_calculate_answer(
455 $answer->answer, $state->options->dataset, $answer->tolerance,
456 $answer->tolerancetype, $answer->correctanswerlength,
457 $answer->correctanswerformat, ''); // remove unit
458 $correct = array('' => $answernumerical->answer);
459 $correct['answer']= $correct[''];
460 if (isset($correct['']) && $correct[''] != '*' && $unit ) {
461 $correct[''] .= ' '.$unit;
462 $correct['unit']= $unit;
463 }
464 return $correct;
465 }
466 }
467 }else{**/
468 return $virtualqtype->get_correct_responses($question, $state) ;
469 // }
470 return null;
471 }
472
2d279432
PP
473 function get_virtual_qtype() {
474 global $QTYPES;
475 // if ( isset($question->options->multichoice) && $question->options->multichoice == '1'){
476 $this->virtualqtype =& $QTYPES['multichoice'];
477 // }else {
478 // $this->virtualqtype =& $QTYPES['numerical'];
479 // }
480 return $this->virtualqtype;
481 }
482
483
2d279432
PP
484 /**
485 * Runs all the code required to set up and save an essay question for testing purposes.
486 * Alternate DB table prefix may be used to facilitate data deletion.
487 */
488 function generate_test($name, $courseid = null) {
489 global $DB;
490 list($form, $question) = parent::generate_test($name, $courseid);
491 $form->feedback = 1;
492 $form->multiplier = array(1, 1);
493 $form->shuffleanswers = 1;
494 $form->noanswers = 1;
495 $form->qtype ='calculatedmulti';
496 $question->qtype ='calculatedmulti';
497 $form->answers = array('{a} + {b}');
498 $form->fraction = array(1);
499 $form->tolerance = array(0.01);
500 $form->tolerancetype = array(1);
501 $form->correctanswerlength = array(2);
502 $form->correctanswerformat = array(1);
503 $form->questiontext = "What is {a} + {b}?";
504
505 if ($courseid) {
506 $course = $DB->get_record('course', array('id'=> $courseid));
507 }
508
509 $new_question = $this->save_question($question, $form, $course);
510
511 $dataset_form = new stdClass();
512 $dataset_form->nextpageparam["forceregeneration"]= 1;
513 $dataset_form->calcmin = array(1 => 1.0, 2 => 1.0);
514 $dataset_form->calcmax = array(1 => 10.0, 2 => 10.0);
515 $dataset_form->calclength = array(1 => 1, 2 => 1);
516 $dataset_form->number = array(1 => 5.4 , 2 => 4.9);
517 $dataset_form->itemid = array(1 => '' , 2 => '');
518 $dataset_form->calcdistribution = array(1 => 'uniform', 2 => 'uniform');
519 $dataset_form->definition = array(1 => "1-0-a",
520 2 => "1-0-b");
521 $dataset_form->nextpageparam = array('forceregeneration' => false);
522 $dataset_form->addbutton = 1;
523 $dataset_form->selectadd = 1;
524 $dataset_form->courseid = $courseid;
525 $dataset_form->cmid = 0;
526 $dataset_form->id = $new_question->id;
527 $this->save_dataset_items($new_question, $dataset_form);
528
529 return $new_question;
530 }
531}
532//// END OF CLASS ////
533
534//////////////////////////////////////////////////////////////////////////
535//// INITIATION - Without this line the question type is not in use... ///
536//////////////////////////////////////////////////////////////////////////
537question_register_questiontype(new question_calculatedmulti_qtype());
34e24cd3
PP
538
539if ( ! defined ("CALCULATEDMULTI")) {
540 define("CALCULATEDMULTI", "calculatedmulti");
541}