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