Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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   * Question type class for the simple calculated question type.
  19   *
  20   * @package    qtype
  21   * @subpackage calculatedsimple
  22   * @copyright  2009 Pierre Pichet
  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/questiontype.php');
  30  
  31  
  32  /**
  33   * The simple calculated question type.
  34   *
  35   * @copyright  2009 Pierre Pichet
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class qtype_calculatedsimple extends qtype_calculated {
  39  
  40      // Used by the function custom_generator_tools.
  41      public $wizard_pages_number = 1;
  42  
  43      public function save_question_options($question) {
  44          global $CFG, $DB;
  45          $context = $question->context;
  46  
  47          // Make it impossible to save bad formulas anywhere.
  48          $this->validate_question_data($question);
  49  
  50          // Get old versions of the objects.
  51          if (!$oldanswers = $DB->get_records('question_answers',
  52                  array('question' => $question->id), 'id ASC')) {
  53              $oldanswers = array();
  54          }
  55  
  56          if (!$oldoptions = $DB->get_records('question_calculated',
  57                  array('question' => $question->id), 'answer ASC')) {
  58              $oldoptions = array();
  59          }
  60  
  61          // Save the units.
  62          $virtualqtype = $this->get_virtual_qtype();
  63          $result = $virtualqtype->save_units($question);
  64          if (isset($result->error)) {
  65              return $result;
  66          } else {
  67              $units = &$result->units;
  68          }
  69          // Insert all the new answers.
  70          foreach ($question->answer as $key => $answerdata) {
  71              if (is_array($answerdata)) {
  72                  $answerdata = $answerdata['text'];
  73              }
  74              if (trim($answerdata) == '') {
  75                  continue;
  76              }
  77  
  78              // Update an existing answer if possible.
  79              $answer = array_shift($oldanswers);
  80              if (!$answer) {
  81                  $answer = new stdClass();
  82                  $answer->question = $question->id;
  83                  $answer->answer   = '';
  84                  $answer->feedback = '';
  85                  $answer->id       = $DB->insert_record('question_answers', $answer);
  86              }
  87  
  88              $answer->answer   = trim($answerdata);
  89              $answer->fraction = $question->fraction[$key];
  90              $answer->feedback = $this->import_or_save_files($question->feedback[$key],
  91                      $context, 'question', 'answerfeedback', $answer->id);
  92              $answer->feedbackformat = $question->feedback[$key]['format'];
  93  
  94              $DB->update_record("question_answers", $answer);
  95  
  96              // Set up the options object.
  97              if (!$options = array_shift($oldoptions)) {
  98                  $options = new stdClass();
  99              }
 100              $options->question            = $question->id;
 101              $options->answer              = $answer->id;
 102              $options->tolerance           = trim($question->tolerance[$key]);
 103              $options->tolerancetype       = trim($question->tolerancetype[$key]);
 104              $options->correctanswerlength = trim($question->correctanswerlength[$key]);
 105              $options->correctanswerformat = trim($question->correctanswerformat[$key]);
 106  
 107              // Save options.
 108              if (isset($options->id)) {
 109                  // Reusing existing record.
 110                  $DB->update_record('question_calculated', $options);
 111              } else {
 112                  // New options.
 113                  $DB->insert_record('question_calculated', $options);
 114              }
 115          }
 116  
 117          // Delete old answer records.
 118          if (!empty($oldanswers)) {
 119              foreach ($oldanswers as $oa) {
 120                  $DB->delete_records('question_answers', array('id' => $oa->id));
 121              }
 122          }
 123  
 124          // Delete old answer records.
 125          if (!empty($oldoptions)) {
 126              foreach ($oldoptions as $oo) {
 127                  $DB->delete_records('question_calculated', array('id' => $oo->id));
 128              }
 129          }
 130  
 131          if (isset($question->import_process) && $question->import_process) {
 132              $this->import_datasets($question);
 133          } else {
 134              // Save datasets and datatitems from form i.e in question.
 135              $question->dataset = $question->datasetdef;
 136  
 137              // Save datasets.
 138              $datasetdefinitions = $this->get_dataset_definitions($question->id, $question->dataset);
 139              $tmpdatasets = array_flip($question->dataset);
 140              $defids = array_keys($datasetdefinitions);
 141              $datasetdefs = array();
 142              foreach ($defids as $defid) {
 143                  $datasetdef = &$datasetdefinitions[$defid];
 144                  if (isset($datasetdef->id)) {
 145                      if (!isset($tmpdatasets[$defid])) {
 146                          // This dataset is not used any more, delete it.
 147                          $DB->delete_records('question_datasets', array('question' => $question->id,
 148                                  'datasetdefinition' => $datasetdef->id));
 149                          $DB->delete_records('question_dataset_definitions',
 150                                  array('id' => $datasetdef->id));
 151                          $DB->delete_records('question_dataset_items',
 152                                  array('definition' => $datasetdef->id));
 153                      }
 154                      // This has already been saved or just got deleted.
 155                      unset($datasetdefinitions[$defid]);
 156                      continue;
 157                  }
 158                  $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
 159                  $datasetdefs[] = clone($datasetdef);
 160                  $questiondataset = new stdClass();
 161                  $questiondataset->question = $question->id;
 162                  $questiondataset->datasetdefinition = $datasetdef->id;
 163                  $DB->insert_record('question_datasets', $questiondataset);
 164                  unset($datasetdefinitions[$defid]);
 165              }
 166              // Remove local obsolete datasets as well as relations
 167              // to datasets in other categories.
 168              if (!empty($datasetdefinitions)) {
 169                  foreach ($datasetdefinitions as $def) {
 170                      $DB->delete_records('question_datasets', array('question' => $question->id,
 171                              'datasetdefinition' => $def->id));
 172                      if ($def->category == 0) { // Question local dataset.
 173                          $DB->delete_records('question_dataset_definitions',
 174                                  array('id' => $def->id));
 175                          $DB->delete_records('question_dataset_items',
 176                                  array('definition' => $def->id));
 177                      }
 178                  }
 179              }
 180              $datasetdefs = $this->get_dataset_definitions($question->id, $question->dataset);
 181              // Handle adding and removing of dataset items.
 182              $i = 1;
 183              ksort($question->definition);
 184              foreach ($question->definition as $key => $defid) {
 185                  $addeditem = new stdClass();
 186                  $addeditem->definition = $datasetdefs[$defid]->id;
 187                  $addeditem->value = $question->number[$i];
 188                  $addeditem->itemnumber = ceil($i / count($datasetdefs));
 189                  if (empty($question->makecopy) && $question->itemid[$i]) {
 190                      // Reuse any previously used record.
 191                      $addeditem->id = $question->itemid[$i];
 192                      $DB->update_record('question_dataset_items', $addeditem);
 193                  } else {
 194                      $DB->insert_record('question_dataset_items', $addeditem);
 195                  }
 196                  $i++;
 197              }
 198              $maxnumber = -1;
 199              if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber) {
 200                  $maxnumber = $addeditem->itemnumber;
 201                  foreach ($datasetdefs as $key => $newdef) {
 202                      if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
 203                          $newdef->itemcount = $maxnumber;
 204                          // Save the new value for options.
 205                          $DB->update_record('question_dataset_definitions', $newdef);
 206                      }
 207                  }
 208              }
 209          }
 210  
 211          $this->save_hints($question);
 212  
 213          // Report any problems.
 214          if (!empty($question->makecopy) && !empty($question->convert)) {
 215              $DB->set_field('question', 'qtype', 'calculated', array('id' => $question->id));
 216          }
 217  
 218          $result = $virtualqtype->save_unit_options($question);
 219          if (isset($result->error)) {
 220              return $result;
 221          }
 222  
 223          if (!empty($result->notice)) {
 224              return $result;
 225          }
 226  
 227          return true;
 228      }
 229  
 230      public function finished_edit_wizard($form) {
 231          return true;
 232      }
 233  
 234      public function wizard_pages_number() {
 235          return 1;
 236      }
 237  
 238      public function custom_generator_tools_part($mform, $idx, $j) {
 239  
 240          $minmaxgrp = array();
 241          $minmaxgrp[] = $mform->createElement('float', "calcmin[{$idx}]",
 242                  get_string('calcmin', 'qtype_calculated'));
 243          $minmaxgrp[] = $mform->createElement('float', "calcmax[{$idx}]",
 244                  get_string('calcmax', 'qtype_calculated'));
 245          $mform->addGroup($minmaxgrp, 'minmaxgrp',
 246                  get_string('minmax', 'qtype_calculated'), ' - ', false);
 247  
 248          $precisionoptions = range(0, 10);
 249          $mform->addElement('select', "calclength[{$idx}]",
 250                  get_string('calclength', 'qtype_calculated'), $precisionoptions);
 251  
 252          $distriboptions = array('uniform' => get_string('uniform', 'qtype_calculated'),
 253                  'loguniform' => get_string('loguniform', 'qtype_calculated'));
 254          $mform->addElement('hidden', "calcdistribution[{$idx}]", 'uniform');
 255          $mform->setType("calcdistribution[{$idx}]", PARAM_INT);
 256      }
 257  
 258      public function comment_header($answers) {
 259          $strheader = "";
 260          $delimiter = '';
 261  
 262          foreach ($answers as $key => $answer) {
 263              $ans = shorten_text($answer->answer, 17, true);
 264              $strheader .= $delimiter.$ans;
 265              $delimiter = '<br/><br/><br/>';
 266          }
 267          return $strheader;
 268      }
 269  
 270      public function tolerance_types() {
 271          return array(
 272              '1'  => get_string('relative', 'qtype_numerical'),
 273              '2'  => get_string('nominal', 'qtype_numerical'),
 274          );
 275      }
 276  
 277      public function dataset_options($form, $name, $mandatory = true, $renameabledatasets = false) {
 278          // Takes datasets from the parent implementation but
 279          // filters options that are currently not accepted by calculated.
 280          // It also determines a default selection
 281          // $renameabledatasets not implemented anywhere.
 282          list($options, $selected) = $this->dataset_options_from_database(
 283                  $form, $name, '', 'qtype_calculated');
 284  
 285          foreach ($options as $key => $whatever) {
 286              if (!preg_match('~^1-~', $key) && $key != '0') {
 287                  unset($options[$key]);
 288              }
 289          }
 290          if (!$selected) {
 291              if ($mandatory) {
 292                  $selected =  "1-0-{$name}"; // Default.
 293              } else {
 294                  $selected = "0"; // Default.
 295              }
 296          }
 297          return array($options, $selected);
 298      }
 299  }