Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Defines the editing form for the calculated simplequestion type.
  19   *
  20   * @package    qtype
  21   * @subpackage calculatedsimple
  22   * @copyright  2007 Jamie Pratt me@jamiep.org
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->dirroot . '/question/type/calculated/edit_calculated_form.php');
  30  
  31  
  32  /**
  33   * Editing form for the calculated simplequestion type.
  34   *
  35   * @copyright  2007 Jamie Pratt me@jamiep.org
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class qtype_calculatedsimple_edit_form extends qtype_calculated_edit_form {
  39      /**
  40       * Handle to the question type for this question.
  41       *
  42       * @var question_calculatedsimple_qtype
  43       */
  44      public $qtypeobj;
  45  
  46      public $wildcarddisplay;
  47  
  48      public $questiondisplay;
  49  
  50      public $datasetdefs;
  51  
  52      public $reload = false;
  53  
  54      public $maxnumber = -1;
  55  
  56      public $regenerate = true;
  57  
  58      public $noofitems;
  59  
  60      public $outsidelimit = false;
  61  
  62      public $commentanswer = array();
  63  
  64      public $answer = array();
  65  
  66      public $nonemptyanswer = array();
  67  
  68      public $numbererrors = array();
  69  
  70      public $formdata = array();
  71  
  72      public function __construct($submiturl, $question, $category, $contexts, $formeditable = true) {
  73          $this->regenerate = true;
  74          $this->question = $question;
  75  
  76          $this->qtypeobj = question_bank::get_qtype($this->question->qtype);
  77          // Get the dataset definitions for this question.
  78          // Coming here everytime even when using a NoSubmitButton.
  79          // This will only set the values to the actual question database content
  80          // which is not what we want, so this should be removed from here.
  81          // Get priority to paramdatasets.
  82  
  83          $this->reload = optional_param('reload', false, PARAM_BOOL);
  84          if (!$this->reload) { // Use database data as this is first pass
  85              // Question->id == 0 so no stored datasets.
  86              if (!empty($question->id)) {
  87  
  88                  $this->datasetdefs = $this->qtypeobj->get_dataset_definitions(
  89                          $question->id, array());
  90  
  91                  if (!empty($this->datasetdefs)) {
  92                      foreach ($this->datasetdefs as $defid => $datasetdef) {
  93                          // First get the items in case their number does not correspond to itemcount.
  94                          if (isset($datasetdef->id)) {
  95                              $this->datasetdefs[$defid]->items =
  96                                      $this->qtypeobj->get_database_dataset_items($datasetdef->id);
  97                              if ($this->datasetdefs[$defid]->items != '') {
  98                                  $datasetdef->itemcount = count($this->datasetdefs[$defid]->items);
  99                              } else {
 100                                  $datasetdef->itemcount = 0;
 101                              }
 102                          }
 103                          // Get maxnumber.
 104                          if ($this->maxnumber == -1 || $datasetdef->itemcount < $this->maxnumber) {
 105                              $this->maxnumber = $datasetdef->itemcount;
 106                          }
 107                      }
 108                  }
 109  
 110                  $i = 0;
 111                  foreach ($this->question->options->answers as $answer) {
 112                       $this->answer[$i] = $answer;
 113                       $i++;
 114                  }
 115                  $this->nonemptyanswer = $this->answer;
 116              }
 117              $datasettoremove = false;
 118              $newdatasetvalues = false;
 119              $newdataset = false;
 120          } else {
 121              // Handle reload to get values from the form-elements
 122              // answers, datasetdefs and data_items. In any case the validation
 123              // step will warn the user of any error in settings the values.
 124              // Verification for the specific dataset values as the other parameters
 125              // units, feeedback etc are handled elsewhere.
 126              // Handle request buttons :
 127              //    'analyzequestion' (Identify the wild cards {x..} present in answers).
 128              //    'addbutton' (create new set of datatitems).
 129              //    'updatedatasets' is handled automatically on each reload.
 130              // The analyzequestion is done every time on reload
 131              // to detect any new wild cards so that the current display reflects
 132              // the mandatory (i.e. in answers) datasets.
 133              // To implement : don't do any changes if the question is used in a quiz.
 134              // If new datadef, new properties should erase items.
 135              // most of the data.
 136              $datasettoremove = false;
 137              $newdatasetvalues = false;
 138              $newdataset = false;
 139              $dummyform = new stdClass();
 140              $mandatorydatasets = array();
 141              // Should not test on adding a new answer.
 142              // Should test if there are already olddatasets or if the 'analyzequestion'.
 143              // submit button has been clicked.
 144              if (optional_param_array('datasetdef', false, PARAM_BOOL) ||
 145                      optional_param('analyzequestion', false, PARAM_BOOL)) {
 146  
 147                  if ($dummyform->answer = optional_param_array('answer', '', PARAM_NOTAGS)) {
 148                      // There is always at least one answer...
 149                      $fraction = optional_param_array('fraction', '', PARAM_FLOAT);
 150                      $tolerance = optional_param_array('tolerance', '', PARAM_LOCALISEDFLOAT);
 151                      $tolerancetype = optional_param_array('tolerancetype', '', PARAM_FLOAT);
 152                      $correctanswerlength = optional_param_array('correctanswerlength', '', PARAM_INT);
 153                      $correctanswerformat = optional_param_array('correctanswerformat', '', PARAM_INT);
 154  
 155                      foreach ($dummyform->answer as $key => $answer) {
 156                          if (trim($answer) != '') {  // Just look for non-empty.
 157                              $this->answer[$key] = new stdClass();
 158                              $this->answer[$key]->answer = $answer;
 159                              $this->answer[$key]->fraction = $fraction[$key];
 160                              $this->answer[$key]->tolerance = $tolerance[$key];
 161                              $this->answer[$key]->tolerancetype = $tolerancetype[$key];
 162                              $this->answer[$key]->correctanswerlength = $correctanswerlength[$key];
 163                              $this->answer[$key]->correctanswerformat = $correctanswerformat[$key];
 164                              $this->nonemptyanswer[]= $this->answer[$key];
 165                              $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
 166                          }
 167                      }
 168                  }
 169                  $this->datasetdefs = array();
 170                  // Rebuild datasetdefs from old values.
 171                  if ($olddef = optional_param_array('datasetdef', '', PARAM_RAW)) {
 172                      $calcmin = optional_param_array('calcmin', '', PARAM_LOCALISEDFLOAT);
 173                      $calclength = optional_param_array('calclength', '', PARAM_INT);
 174                      $calcmax = optional_param_array('calcmax', '', PARAM_LOCALISEDFLOAT);
 175                      $oldoptions  = optional_param_array('defoptions', '', PARAM_RAW);
 176                      $newdatasetvalues = false;
 177                      $sizeofolddef = count($olddef);
 178                      for ($key = 1; $key <= $sizeofolddef; $key++) {
 179                          $def = $olddef[$key];
 180                          $this->datasetdefs[$def]= new stdClass();
 181                          $this->datasetdefs[$def]->type = 1;
 182                          $this->datasetdefs[$def]->category = 0;
 183                          $this->datasetdefs[$def]->options = $oldoptions[$key];
 184                          $this->datasetdefs[$def]->calcmin = $calcmin[$key];
 185                          $this->datasetdefs[$def]->calcmax = $calcmax[$key];
 186                          $this->datasetdefs[$def]->calclength = $calclength[$key];
 187                          // Then compare with new values.
 188                          if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
 189                                  $this->datasetdefs[$def]->options, $regs)) {
 190                              if ($this->datasetdefs[$def]->calcmin != $regs[2]||
 191                                      $this->datasetdefs[$def]->calcmax != $regs[3] ||
 192                                      $this->datasetdefs[$def]->calclength != $regs[4]) {
 193                                  $newdatasetvalues = true;
 194                              }
 195                          }
 196                          $this->datasetdefs[$def]->options = "uniform:" .
 197                                  $this->datasetdefs[$def]->calcmin . ":" .
 198                                  $this->datasetdefs[$def]->calcmax . ":" .
 199                                  $this->datasetdefs[$def]->calclength;
 200                      }
 201                  }
 202                  // Detect new datasets.
 203                  $newdataset = false;
 204                  foreach ($mandatorydatasets as $datasetname) {
 205                      if (!isset($this->datasetdefs["1-0-{$datasetname}"])) {
 206                          $key = "1-0-{$datasetname}";
 207                          $this->datasetdefs[$key] = new stdClass();
 208                          $this->datasetdefs[$key]->type = 1;
 209                          $this->datasetdefs[$key]->category = 0;
 210                          $this->datasetdefs[$key]->name = $datasetname;
 211                          $this->datasetdefs[$key]->options = "uniform:1.0:10.0:1";
 212                          $newdataset = true;
 213                      } else {
 214                          $this->datasetdefs["1-0-{$datasetname}"]->name = $datasetname;
 215                      }
 216                  }
 217                  // Remove obsolete datasets.
 218                  $datasettoremove = false;
 219                  foreach ($this->datasetdefs as $defkey => $datasetdef) {
 220                      if (!isset($datasetdef->name)) {
 221                          $datasettoremove = true;
 222                          unset($this->datasetdefs[$defkey]);
 223                      }
 224                  }
 225              }
 226          } // Handle reload.
 227          // Create items if  $newdataset and noofitems > 0 and !$newdatasetvalues.
 228          // Eliminate any items if $newdatasetvalues.
 229          // Eliminate any items if $datasettoremove, $newdataset, $newdatasetvalues.
 230          if ($datasettoremove ||$newdataset ||$newdatasetvalues) {
 231              foreach ($this->datasetdefs as $defkey => $datasetdef) {
 232                  $datasetdef->itemcount = 0;
 233                  unset($datasetdef->items);
 234              }
 235          }
 236          $maxnumber = -1;
 237          if (optional_param('addbutton', false, PARAM_BOOL)) {
 238              $maxnumber = optional_param('selectadd', '', PARAM_INT); // FIXME: sloppy coding.
 239              foreach ($this->datasetdefs as $defid => $datasetdef) {
 240                  $datasetdef->itemcount = $maxnumber;
 241                  unset($datasetdef->items);
 242                  for ($numberadded = 1; $numberadded <= $maxnumber; $numberadded++) {
 243                      $datasetitem = new stdClass();
 244                      $datasetitem->itemnumber = $numberadded;
 245                      $datasetitem->id = 0;
 246                      $datasetitem->value = $this->qtypeobj->generate_dataset_item(
 247                              $datasetdef->options);
 248                      $this->datasetdefs[$defid]->items[$numberadded] = $datasetitem;
 249                  }
 250              }
 251              $this->maxnumber = $maxnumber;
 252          } else {
 253              // Handle reload dataset items.
 254              if (optional_param_array('definition', '', PARAM_NOTAGS) &&
 255                      !($datasettoremove ||$newdataset ||$newdatasetvalues)) {
 256                  $i = 1;
 257                  $fromformdefinition = optional_param_array('definition', '', PARAM_NOTAGS);
 258                  $fromformnumber = optional_param_array('number', '', PARAM_LOCALISEDFLOAT);
 259                  $fromformitemid = optional_param_array('itemid', '', PARAM_INT);
 260                  ksort($fromformdefinition);
 261  
 262                  foreach ($fromformdefinition as $key => $defid) {
 263                      $addeditem = new stdClass();
 264                      $addeditem->id = $fromformitemid[$i];
 265                      $addeditem->value = $fromformnumber[$i];
 266                      $addeditem->itemnumber = ceil($i / count($this->datasetdefs));
 267                      $this->datasetdefs[$defid]->items[$addeditem->itemnumber] = $addeditem;
 268                      $this->datasetdefs[$defid]->itemcount = $i;
 269                      $i++;
 270                  }
 271              }
 272              if (isset($addeditem->itemnumber) && $this->maxnumber < $addeditem->itemnumber) {
 273                  $this->maxnumber = $addeditem->itemnumber;
 274                  if (!empty($this->datasetdefs)) {
 275                      foreach ($this->datasetdefs as $datasetdef) {
 276                              $datasetdef->itemcount = $this->maxnumber;
 277                      }
 278                  }
 279              }
 280          }
 281  
 282          parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
 283      }
 284  
 285      /**
 286       * Add question-type specific form fields.
 287       *
 288       * @param MoodleQuickForm $mform the form being built.
 289       */
 290      protected function definition_inner($mform) {
 291          $strquestionlabel = $this->qtypeobj->comment_header($this->nonemptyanswer);
 292          $label = get_string("sharedwildcards", "qtype_calculated");
 293          $mform->addElement('hidden', 'synchronize', 0);
 294          $mform->addElement('hidden', 'initialcategory', 1);
 295          $mform->setType('synchronize', PARAM_BOOL);
 296          $mform->setType('initialcategory', PARAM_INT);
 297          $mform->addElement('hidden', 'reload', 1);
 298          $mform->setType('reload', PARAM_INT);
 299          $addfieldsname = 'updatequestion value';
 300          $addstring = get_string("updatecategory", "qtype_calculated");
 301          $mform->registerNoSubmitButton($addfieldsname);
 302  
 303          $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'),
 304                  question_bank::fraction_options(), 1, 1);
 305  
 306          $this->add_unit_options($mform, $this);
 307          $this->add_unit_fields($mform, $this);
 308          $this->add_interactive_settings();
 309  
 310          $label = "<div class='mdl-align'></div><div class='mdl-align'>" .
 311                  get_string('wildcardrole', 'qtype_calculatedsimple') . "</div>";
 312          $mform->addElement('html', "<div class='mdl-align'>&nbsp;</div>");
 313          // Explaining the role of datasets so other strings can be shortened.
 314          $mform->addElement('html', $label);
 315  
 316          $mform->addElement('submit', 'analyzequestion',
 317                  get_string('findwildcards', 'qtype_calculatedsimple'));
 318          $mform->registerNoSubmitButton('analyzequestion');
 319          $mform->closeHeaderBefore('analyzequestion');
 320          if ($this->maxnumber != -1) {
 321              $this->noofitems = $this->maxnumber;
 322          } else {
 323              $this->noofitems = 0;
 324          }
 325          if (!empty($this->datasetdefs)) {// So there are some datadefs.
 326              // We put them on the page.
 327              $key = 0;
 328              $mform->addElement('header', 'additemhdr',
 329                      get_string('wildcardparam', 'qtype_calculatedsimple'));
 330              $idx = 1;
 331              if (!empty($this->datasetdefs)) {// Unnecessary test.
 332                  $j = (($this->noofitems) * count($this->datasetdefs))+1;//
 333                  foreach ($this->datasetdefs as $defkey => $datasetdef) {
 334                      $mform->addElement('static', "na[{$j}]",
 335                              get_string('param', 'qtype_calculated', $datasetdef->name));
 336                      $this->qtypeobj->custom_generator_tools_part($mform, $idx, $j);
 337                      $mform->addElement('hidden', "datasetdef[{$idx}]");
 338                      $mform->setType("datasetdef[{$idx}]", PARAM_RAW);
 339                      $mform->addElement('hidden', "defoptions[{$idx}]");
 340                      $mform->setType("defoptions[{$idx}]", PARAM_RAW);
 341                      $idx++;
 342                      $mform->addElement('static', "divider[{$j}]", '', '<hr />');
 343                      $j++;
 344                  }
 345              }
 346              // This should be done before the elements are created and stored as $this->formdata.
 347              // Fill out all data sets and also the fields for the next item to add.
 348              /*Here we do already the values error analysis so that
 349               * we could force all wild cards values display if there is an error in values.
 350               * as using a , in a number */
 351              $this->numbererrors = array();
 352              if (!empty($this->datasetdefs)) {
 353                  $j = $this->noofitems * count($this->datasetdefs);
 354                  for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--) {
 355                      $data = array();
 356                      $numbererrors = '';
 357                      $comment = new stdClass();
 358                      $comment->stranswers = array();
 359                      $comment->outsidelimit = false;
 360                      $comment->answers = array();
 361  
 362                      foreach ($this->datasetdefs as $defid => $datasetdef) {
 363                          if (isset($datasetdef->items[$itemnumber])) {
 364                              $this->formdata["definition[{$j}]"] = $defid;
 365                              $this->formdata["itemid[{$j}]"] =
 366                                      $datasetdef->items[$itemnumber]->id;
 367                              $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
 368                              $this->formdata["number[{$j}]"] = $number =
 369                                      $datasetdef->items[$itemnumber]->value;
 370                              if (! is_numeric($number)) {
 371                                  $a = new stdClass();
 372                                  $a->name = '{'.$datasetdef->name.'}';
 373                                  $a->value = $datasetdef->items[$itemnumber]->value;
 374                                  if (stristr($number, ',')) {
 375                                      $this->numbererrors["number[{$j}]"] =
 376                                              get_string('nocommaallowed', 'qtype_calculated');
 377                                      $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 378  
 379                                  } else {
 380                                      $this->numbererrors["number[{$j}]"] =
 381                                              get_string('notvalidnumber', 'qtype_calculated', $a);
 382                                      $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 383                                  }
 384                              } else if (stristr($number, 'x')) { // Hexa will pass the test.
 385                                  $a = new stdClass();
 386                                  $a->name = '{'.$datasetdef->name.'}';
 387                                  $a->value = $datasetdef->items[$itemnumber]->value;
 388                                  $this->numbererrors['number['.$j.']'] =
 389                                          get_string('hexanotallowed', 'qtype_calculated', $a);
 390                                  $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 391                              } else if (is_nan($number)) {
 392                                  $a = new stdClass();
 393                                  $a->name = '{'.$datasetdef->name.'}';
 394                                  $a->value = $datasetdef->items[$itemnumber]->value;
 395                                  $this->numbererrors["number[{$j}]"] =
 396                                          get_string('notvalidnumber', 'qtype_calculated', $a);
 397                                  $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 398                              }
 399                          }
 400                          $j--;
 401                      }
 402                      if ($this->noofitems != 0) {
 403                          if (empty($numbererrors)) {
 404                              if (!isset($this->question->id)) {
 405                                  $this->question->id = 0;
 406                              }
 407                              $this->question->questiontext = !empty($this->question->questiontext) ?
 408                                      $this->question->questiontext : '';
 409                              $comment = $this->qtypeobj->comment_on_datasetitems(
 410                                      $this->qtypeobj, $this->question->id,
 411                                      $this->question->questiontext, $this->nonemptyanswer,
 412                                      $data, $itemnumber);
 413                              if ($comment->outsidelimit) {
 414                                  $this->outsidelimit = $comment->outsidelimit;
 415                              }
 416                              $totalcomment = '';
 417  
 418                              foreach ($this->nonemptyanswer as $key => $answer) {
 419                                  $totalcomment .= $comment->stranswers[$key].'<br/>';
 420                              }
 421  
 422                              $this->formdata['answercomment['.$itemnumber.']'] = $totalcomment;
 423                          }
 424                      }
 425                  }
 426                  $this->formdata['selectdelete'] = '1';
 427                  $this->formdata['selectadd'] = '1';
 428                  $j = $this->noofitems * count($this->datasetdefs)+1;
 429                  $data = array(); // Data for comment_on_datasetitems later.
 430                  $idx = 1;
 431                  foreach ($this->datasetdefs as $defid => $datasetdef) {
 432                      $this->formdata["datasetdef[{$idx}]"] = $defid;
 433                      $idx++;
 434                  }
 435                  $this->formdata = $this->qtypeobj->custom_generator_set_data(
 436                          $this->datasetdefs, $this->formdata);
 437              }
 438  
 439              $addoptions = Array();
 440              $addoptions['1'] = '1';
 441              for ($i = 10; $i <= 100; $i += 10) {
 442                  $addoptions["{$i}"] = "{$i}";
 443              }
 444              $showoptions = Array();
 445              $showoptions['1'] = '1';
 446              $showoptions['2'] = '2';
 447              $showoptions['5'] = '5';
 448              for ($i = 10; $i <= 100; $i += 10) {
 449                  $showoptions["{$i}"] = "{$i}";
 450              }
 451              $mform->closeHeaderBefore('additemhdr');
 452              $addgrp = array();
 453              $addgrp[] = $mform->createElement('submit', 'addbutton',
 454                      get_string('generatenewitemsset', 'qtype_calculatedsimple'));
 455              $addgrp[] = $mform->createElement('select', "selectadd", '', $addoptions);
 456              $addgrp[] = $mform->createElement('static', "stat", '',
 457                      get_string('newsetwildcardvalues', 'qtype_calculatedsimple'));
 458              $mform->addGroup($addgrp, 'addgrp', '', '   ', false);
 459              $mform->registerNoSubmitButton('addbutton');
 460              $mform->closeHeaderBefore('addgrp');
 461              $addgrp1 = array();
 462              $addgrp1[] = $mform->createElement('submit', 'showbutton',
 463                      get_string('showitems', 'qtype_calculatedsimple'));
 464              $addgrp1[] = $mform->createElement('select', "selectshow", '', $showoptions);
 465              $addgrp1[] = $mform->createElement('static', "stat", '',
 466                      get_string('setwildcardvalues', 'qtype_calculatedsimple'));
 467              $mform->addGroup($addgrp1, 'addgrp1', '', '   ', false);
 468              $mform->registerNoSubmitButton('showbutton');
 469              $mform->closeHeaderBefore('addgrp1');
 470              $mform->addElement('static', "divideradd", '', '');
 471              if ($this->noofitems == 0) {
 472                  $mform->addElement('static', 'warningnoitems', '', '<span class="error">' .
 473                          get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple') .
 474                          '</span>');
 475                  $mform->closeHeaderBefore('warningnoitems');
 476              } else {
 477                  $mform->addElement('header', 'additemhdr1',
 478                          get_string('wildcardvalues', 'qtype_calculatedsimple'));
 479                  $mform->closeHeaderBefore('additemhdr1');
 480                  if (!empty($this->numbererrors) || $this->outsidelimit) {
 481                      $mform->addElement('static', "alert", '', '<span class="error">' .
 482                              get_string('useadvance', 'qtype_calculatedsimple').'</span>');
 483                  }
 484  
 485                  $mform->addElement('submit', 'updatedatasets',
 486                          get_string('updatewildcardvalues', 'qtype_calculatedsimple'));
 487                  $mform->registerNoSubmitButton('updatedatasets');
 488                  $mform->setAdvanced("updatedatasets", true);
 489  
 490                  // ...--------------------------------------------------------------.
 491                  $j = $this->noofitems * count($this->datasetdefs);
 492                  $k = optional_param('selectshow', 1, PARAM_INT);
 493  
 494                  for ($i = $this->noofitems; $i >= 1; $i--) {
 495                      foreach ($this->datasetdefs as $defkey => $datasetdef) {
 496                          if ($k > 0 ||  $this->outsidelimit || !empty($this->numbererrors)) {
 497                              $mform->addElement('float', "number[{$j}]", get_string(
 498                                      'wildcard', 'qtype_calculatedsimple', $datasetdef->name));
 499                              $mform->setAdvanced("number[{$j}]", true);
 500                              if (!empty($this->numbererrors['number['.$j.']'])) {
 501                                  $mform->addElement('static', "numbercomment[{$j}]", '',
 502                                          '<span class="error">' .
 503                                          $this->numbererrors['number['.$j.']'] . '</span>');
 504                                  $mform->setAdvanced("numbercomment[{$j}]", true);
 505                              }
 506                          } else {
 507                              $mform->addElement('hidden', "number[{$j}]", '');
 508                              $mform->setType("number[{$j}]", PARAM_LOCALISEDFLOAT); // Localisation handling has to be done manually.
 509                              if (isset($this->formdata["number[{$j}]"])) {
 510                                  $this->formdata["number[{$j}]"] = format_float($this->formdata["number[{$j}]"], -1);
 511                              }
 512                          }
 513  
 514                          $mform->addElement('hidden', "itemid[{$j}]");
 515                          $mform->setType("itemid[{$j}]", PARAM_INT);
 516  
 517                          $mform->addElement('hidden', "definition[{$j}]");
 518                          $mform->setType("definition[{$j}]", PARAM_NOTAGS);
 519  
 520                          $j--;
 521                      }
 522                      if (!empty($strquestionlabel) && ($k > 0 ||  $this->outsidelimit ||
 523                              !empty($this->numbererrors))) {
 524                          $mform->addElement('static', "answercomment[{$i}]", "<b>" .
 525                                  get_string('setno', 'qtype_calculatedsimple', $i) .
 526                                  "</b>&nbsp;&nbsp;" . $strquestionlabel);
 527  
 528                      }
 529                      if ($k > 0 ||  $this->outsidelimit || !empty($this->numbererrors)) {
 530                          $mform->addElement('static', "divider1[{$j}]", '', '<hr />');
 531  
 532                      }
 533                      $k--;
 534                  }
 535              }
 536          } else {
 537              $mform->addElement('static', 'warningnowildcards', '', '<span class="error">' .
 538                      get_string('atleastonewildcard', 'qtype_calculatedsimple') . '</span>');
 539              $mform->closeHeaderBefore('warningnowildcards');
 540          }
 541  
 542          // ...----------------------------------------------------------------------.
 543          // Non standard name for button element needed so not using add_action_buttons.
 544          // Hidden elements.
 545  
 546          $mform->addElement('hidden', 'id');
 547          $mform->setType('id', PARAM_INT);
 548  
 549          $mform->addElement('hidden', 'courseid');
 550          $mform->setType('courseid', PARAM_INT);
 551          $mform->setDefault('courseid', 0);
 552  
 553          $mform->addElement('hidden', 'cmid');
 554          $mform->setType('cmid', PARAM_INT);
 555          $mform->setDefault('cmid', 0);
 556          if (!empty($this->question->id)) {
 557              if ($this->question->formoptions->cansaveasnew) {
 558                  $mform->addElement('header', 'additemhdr',
 559                          get_string('converttocalculated', 'qtype_calculatedsimple'));
 560                  $mform->closeHeaderBefore('additemhdr');
 561  
 562                  $mform->addElement('checkbox', 'convert', '',
 563                          get_string('willconverttocalculated', 'qtype_calculatedsimple'));
 564                  $mform->setDefault('convert', 0);
 565  
 566              }
 567          }
 568      }
 569  
 570      protected function can_preview() {
 571          return empty($this->question->beingcopied) && !empty($this->question->id) &&
 572                  $this->question->formoptions->canedit && $this->noofitems > 0;
 573      }
 574  
 575      public function data_preprocessing($question) {
 576          $question = parent::data_preprocessing($question);
 577          $question = $this->data_preprocessing_answers($question);
 578          $question = $this->data_preprocessing_hints($question);
 579          $question = $this->data_preprocessing_units($question);
 580          $question = $this->data_preprocessing_unit_options($question);
 581  
 582          // This is a bit ugly, but it loads all the dataset values.
 583          $question = (object)((array)$question + $this->formdata);
 584  
 585          return $question;
 586      }
 587  
 588      public function qtype() {
 589          return 'calculatedsimple';
 590      }
 591  
 592      public function validation($data, $files) {
 593          $errors = parent::validation($data, $files);
 594  
 595          if (array_key_exists('number', $data)) {
 596              $numbers = $data['number'];
 597          } else {
 598              $numbers = array();
 599          }
 600          foreach ($numbers as $key => $number) {
 601              if (! is_numeric($number)) {
 602                  if (stristr($number, ',')) {
 603                      $errors['number['.$key.']'] = get_string('nocommaallowed', 'qtype_calculated');
 604                  } else {
 605                      $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
 606                  }
 607              } else if (stristr($number, 'x')) {
 608                  $a = new stdClass();
 609                  $a->name = '';
 610                  $a->value = $number;
 611                  $errors['number['.$key.']'] = get_string('hexanotallowed', 'qtype_calculated', $a);
 612              } else if (is_nan($number)) {
 613                  $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
 614              }
 615          }
 616  
 617          if (empty($data['definition'])) {
 618              $errors['selectadd'] = get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple');
 619          }
 620  
 621          return $errors;
 622      }
 623  }