Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 402] [Versions 310 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          $this->wizarddisplay = optional_param('analyzequestion', false, PARAM_BOOL);
 321          if ($this->maxnumber != -1) {
 322              $this->noofitems = $this->maxnumber;
 323          } else {
 324              $this->noofitems = 0;
 325          }
 326          if (!empty($this->datasetdefs)) {// So there are some datadefs.
 327              // We put them on the page.
 328              $key = 0;
 329              $mform->addElement('header', 'additemhdr',
 330                      get_string('wildcardparam', 'qtype_calculatedsimple'));
 331              $idx = 1;
 332              if (!empty($this->datasetdefs)) {// Unnecessary test.
 333                  $j = (($this->noofitems) * count($this->datasetdefs))+1;//
 334                  foreach ($this->datasetdefs as $defkey => $datasetdef) {
 335                      $mform->addElement('static', "na[{$j}]",
 336                              get_string('param', 'qtype_calculated', $datasetdef->name));
 337                      $this->qtypeobj->custom_generator_tools_part($mform, $idx, $j);
 338                      $mform->addElement('hidden', "datasetdef[{$idx}]");
 339                      $mform->setType("datasetdef[{$idx}]", PARAM_RAW);
 340                      $mform->addElement('hidden', "defoptions[{$idx}]");
 341                      $mform->setType("defoptions[{$idx}]", PARAM_RAW);
 342                      $idx++;
 343                      $mform->addElement('static', "divider[{$j}]", '', '<hr />');
 344                      $j++;
 345                  }
 346              }
 347              // This should be done before the elements are created and stored as $this->formdata.
 348              // Fill out all data sets and also the fields for the next item to add.
 349              /*Here we do already the values error analysis so that
 350               * we could force all wild cards values display if there is an error in values.
 351               * as using a , in a number */
 352              $this->numbererrors = array();
 353              if (!empty($this->datasetdefs)) {
 354                  $j = $this->noofitems * count($this->datasetdefs);
 355                  for ($itemnumber = $this->noofitems; $itemnumber >= 1; $itemnumber--) {
 356                      $data = array();
 357                      $numbererrors = '';
 358                      $comment = new stdClass();
 359                      $comment->stranswers = array();
 360                      $comment->outsidelimit = false;
 361                      $comment->answers = array();
 362  
 363                      foreach ($this->datasetdefs as $defid => $datasetdef) {
 364                          if (isset($datasetdef->items[$itemnumber])) {
 365                              $this->formdata["definition[{$j}]"] = $defid;
 366                              $this->formdata["itemid[{$j}]"] =
 367                                      $datasetdef->items[$itemnumber]->id;
 368                              $data[$datasetdef->name] = $datasetdef->items[$itemnumber]->value;
 369                              $this->formdata["number[{$j}]"] = $number =
 370                                      $datasetdef->items[$itemnumber]->value;
 371                              if (! is_numeric($number)) {
 372                                  $a = new stdClass();
 373                                  $a->name = '{'.$datasetdef->name.'}';
 374                                  $a->value = $datasetdef->items[$itemnumber]->value;
 375                                  if (stristr($number, ',')) {
 376                                      $this->numbererrors["number[{$j}]"] =
 377                                              get_string('nocommaallowed', 'qtype_calculated');
 378                                      $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 379  
 380                                  } else {
 381                                      $this->numbererrors["number[{$j}]"] =
 382                                              get_string('notvalidnumber', 'qtype_calculated', $a);
 383                                      $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 384                                  }
 385                              } else if (stristr($number, 'x')) { // Hexa will pass the test.
 386                                  $a = new stdClass();
 387                                  $a->name = '{'.$datasetdef->name.'}';
 388                                  $a->value = $datasetdef->items[$itemnumber]->value;
 389                                  $this->numbererrors['number['.$j.']'] =
 390                                          get_string('hexanotallowed', 'qtype_calculated', $a);
 391                                  $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 392                              } else if (is_nan($number)) {
 393                                  $a = new stdClass();
 394                                  $a->name = '{'.$datasetdef->name.'}';
 395                                  $a->value = $datasetdef->items[$itemnumber]->value;
 396                                  $this->numbererrors["number[{$j}]"] =
 397                                          get_string('notvalidnumber', 'qtype_calculated', $a);
 398                                  $numbererrors .= $this->numbererrors['number['.$j.']']."<br />";
 399                              }
 400                          }
 401                          $j--;
 402                      }
 403                      if ($this->noofitems != 0) {
 404                          if (empty($numbererrors)) {
 405                              if (!isset($this->question->id)) {
 406                                  $this->question->id = 0;
 407                              }
 408                              $this->question->questiontext = !empty($this->question->questiontext) ?
 409                                      $this->question->questiontext : '';
 410                              $comment = $this->qtypeobj->comment_on_datasetitems(
 411                                      $this->qtypeobj, $this->question->id,
 412                                      $this->question->questiontext, $this->nonemptyanswer,
 413                                      $data, $itemnumber);
 414                              if ($comment->outsidelimit) {
 415                                  $this->outsidelimit = $comment->outsidelimit;
 416                              }
 417                              $totalcomment = '';
 418  
 419                              foreach ($this->nonemptyanswer as $key => $answer) {
 420                                  $totalcomment .= $comment->stranswers[$key].'<br/>';
 421                              }
 422  
 423                              $this->formdata['answercomment['.$itemnumber.']'] = $totalcomment;
 424                          }
 425                      }
 426                  }
 427                  $this->formdata['selectdelete'] = '1';
 428                  $this->formdata['selectadd'] = '1';
 429                  $j = $this->noofitems * count($this->datasetdefs)+1;
 430                  $data = array(); // Data for comment_on_datasetitems later.
 431                  $idx = 1;
 432                  foreach ($this->datasetdefs as $defid => $datasetdef) {
 433                      $this->formdata["datasetdef[{$idx}]"] = $defid;
 434                      $idx++;
 435                  }
 436                  $this->formdata = $this->qtypeobj->custom_generator_set_data(
 437                          $this->datasetdefs, $this->formdata);
 438              }
 439  
 440              $addoptions = Array();
 441              $addoptions['1'] = '1';
 442              for ($i = 10; $i <= 100; $i += 10) {
 443                  $addoptions["{$i}"] = "{$i}";
 444              }
 445              $showoptions = Array();
 446              $showoptions['1'] = '1';
 447              $showoptions['2'] = '2';
 448              $showoptions['5'] = '5';
 449              for ($i = 10; $i <= 100; $i += 10) {
 450                  $showoptions["{$i}"] = "{$i}";
 451              }
 452              $mform->closeHeaderBefore('additemhdr');
 453              $addgrp = array();
 454              $addgrp[] = $mform->createElement('submit', 'addbutton',
 455                      get_string('generatenewitemsset', 'qtype_calculatedsimple'));
 456              $addgrp[] = $mform->createElement('select', "selectadd", '', $addoptions);
 457              $addgrp[] = $mform->createElement('static', "stat", '',
 458                      get_string('newsetwildcardvalues', 'qtype_calculatedsimple'));
 459              $mform->addGroup($addgrp, 'addgrp', '', '   ', false);
 460              $mform->registerNoSubmitButton('addbutton');
 461              $mform->closeHeaderBefore('addgrp');
 462              $addgrp1 = array();
 463              $addgrp1[] = $mform->createElement('submit', 'showbutton',
 464                      get_string('showitems', 'qtype_calculatedsimple'));
 465              $addgrp1[] = $mform->createElement('select', "selectshow", '', $showoptions);
 466              $addgrp1[] = $mform->createElement('static', "stat", '',
 467                      get_string('setwildcardvalues', 'qtype_calculatedsimple'));
 468              $mform->addGroup($addgrp1, 'addgrp1', '', '   ', false);
 469              $mform->registerNoSubmitButton('showbutton');
 470              $mform->closeHeaderBefore('addgrp1');
 471              $mform->addElement('static', "divideradd", '', '');
 472              if ($this->noofitems == 0) {
 473                  $mform->addElement('static', 'warningnoitems', '', '<span class="error">' .
 474                          get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple') .
 475                          '</span>');
 476                  $mform->closeHeaderBefore('warningnoitems');
 477              } else {
 478                  $mform->addElement('header', 'additemhdr1',
 479                          get_string('wildcardvalues', 'qtype_calculatedsimple'));
 480                  $mform->closeHeaderBefore('additemhdr1');
 481                  if (!empty($this->numbererrors) || $this->outsidelimit) {
 482                      $mform->addElement('static', "alert", '', '<span class="error">' .
 483                              get_string('useadvance', 'qtype_calculatedsimple').'</span>');
 484                  }
 485  
 486                  $mform->addElement('submit', 'updatedatasets',
 487                          get_string('updatewildcardvalues', 'qtype_calculatedsimple'));
 488                  $mform->registerNoSubmitButton('updatedatasets');
 489                  $mform->setAdvanced("updatedatasets", true);
 490  
 491                  // ...--------------------------------------------------------------.
 492                  $j = $this->noofitems * count($this->datasetdefs);
 493                  $k = optional_param('selectshow', 1, PARAM_INT);
 494  
 495                  for ($i = $this->noofitems; $i >= 1; $i--) {
 496                      foreach ($this->datasetdefs as $defkey => $datasetdef) {
 497                          if ($k > 0 ||  $this->outsidelimit || !empty($this->numbererrors)) {
 498                              $mform->addElement('float', "number[{$j}]", get_string(
 499                                      'wildcard', 'qtype_calculatedsimple', $datasetdef->name));
 500                              $mform->setAdvanced("number[{$j}]", true);
 501                              if (!empty($this->numbererrors['number['.$j.']'])) {
 502                                  $mform->addElement('static', "numbercomment[{$j}]", '',
 503                                          '<span class="error">' .
 504                                          $this->numbererrors['number['.$j.']'] . '</span>');
 505                                  $mform->setAdvanced("numbercomment[{$j}]", true);
 506                              }
 507                          } else {
 508                              $mform->addElement('hidden', "number[{$j}]", '');
 509                              $mform->setType("number[{$j}]", PARAM_LOCALISEDFLOAT); // Localisation handling has to be done manually.
 510                              if (isset($this->formdata["number[{$j}]"])) {
 511                                  $this->formdata["number[{$j}]"] = format_float($this->formdata["number[{$j}]"], -1);
 512                              }
 513                          }
 514  
 515                          $mform->addElement('hidden', "itemid[{$j}]");
 516                          $mform->setType("itemid[{$j}]", PARAM_INT);
 517  
 518                          $mform->addElement('hidden', "definition[{$j}]");
 519                          $mform->setType("definition[{$j}]", PARAM_NOTAGS);
 520  
 521                          $j--;
 522                      }
 523                      if (!empty($strquestionlabel) && ($k > 0 ||  $this->outsidelimit ||
 524                              !empty($this->numbererrors))) {
 525                          $mform->addElement('static', "answercomment[{$i}]", "<b>" .
 526                                  get_string('setno', 'qtype_calculatedsimple', $i) .
 527                                  "</b>&nbsp;&nbsp;" . $strquestionlabel);
 528  
 529                      }
 530                      if ($k > 0 ||  $this->outsidelimit || !empty($this->numbererrors)) {
 531                          $mform->addElement('static', "divider1[{$j}]", '', '<hr />');
 532  
 533                      }
 534                      $k--;
 535                  }
 536              }
 537          } else {
 538              $mform->addElement('static', 'warningnowildcards', '', '<span class="error">' .
 539                      get_string('atleastonewildcard', 'qtype_calculatedsimple') . '</span>');
 540              $mform->closeHeaderBefore('warningnowildcards');
 541          }
 542  
 543          // ...----------------------------------------------------------------------.
 544          // Non standard name for button element needed so not using add_action_buttons.
 545          // Hidden elements.
 546  
 547          $mform->addElement('hidden', 'id');
 548          $mform->setType('id', PARAM_INT);
 549  
 550          $mform->addElement('hidden', 'courseid');
 551          $mform->setType('courseid', PARAM_INT);
 552          $mform->setDefault('courseid', 0);
 553  
 554          $mform->addElement('hidden', 'cmid');
 555          $mform->setType('cmid', PARAM_INT);
 556          $mform->setDefault('cmid', 0);
 557          if (!empty($this->question->id)) {
 558              if ($this->question->formoptions->cansaveasnew) {
 559                  $mform->addElement('header', 'additemhdr',
 560                          get_string('converttocalculated', 'qtype_calculatedsimple'));
 561                  $mform->closeHeaderBefore('additemhdr');
 562  
 563                  $mform->addElement('checkbox', 'convert', '',
 564                          get_string('willconverttocalculated', 'qtype_calculatedsimple'));
 565                  $mform->setDefault('convert', 0);
 566  
 567              }
 568          }
 569      }
 570  
 571      protected function can_preview() {
 572          return empty($this->question->beingcopied) && !empty($this->question->id) &&
 573                  $this->question->formoptions->canedit && $this->noofitems > 0;
 574      }
 575  
 576      public function data_preprocessing($question) {
 577          $question = parent::data_preprocessing($question);
 578          $question = $this->data_preprocessing_answers($question);
 579          $question = $this->data_preprocessing_hints($question);
 580          $question = $this->data_preprocessing_units($question);
 581          $question = $this->data_preprocessing_unit_options($question);
 582  
 583          // This is a bit ugly, but it loads all the dataset values.
 584          $question = (object)((array)$question + $this->formdata);
 585  
 586          return $question;
 587      }
 588  
 589      public function qtype() {
 590          return 'calculatedsimple';
 591      }
 592  
 593      public function validation($data, $files) {
 594          $errors = parent::validation($data, $files);
 595  
 596          if (array_key_exists('number', $data)) {
 597              $numbers = $data['number'];
 598          } else {
 599              $numbers = array();
 600          }
 601          foreach ($numbers as $key => $number) {
 602              if (! is_numeric($number)) {
 603                  if (stristr($number, ',')) {
 604                      $errors['number['.$key.']'] = get_string('nocommaallowed', 'qtype_calculated');
 605                  } else {
 606                      $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
 607                  }
 608              } else if (stristr($number, 'x')) {
 609                  $a = new stdClass();
 610                  $a->name = '';
 611                  $a->value = $number;
 612                  $errors['number['.$key.']'] = get_string('hexanotallowed', 'qtype_calculated', $a);
 613              } else if (is_nan($number)) {
 614                  $errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
 615              }
 616          }
 617  
 618          if (empty($data['definition'])) {
 619              $errors['selectadd'] = get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple');
 620          }
 621  
 622          return $errors;
 623      }
 624  }