| 1 | 
           efrain | 
           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 calculated question type.
  | 
        
        
            | 
            | 
           19 | 
            *
  | 
        
        
            | 
            | 
           20 | 
            * @package    qtype
  | 
        
        
            | 
            | 
           21 | 
            * @subpackage calculated
  | 
        
        
            | 
            | 
           22 | 
            * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
  | 
        
        
            | 
            | 
           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/questiontypebase.php');
  | 
        
        
            | 
            | 
           30 | 
           require_once($CFG->dirroot . '/question/type/questionbase.php');
  | 
        
        
            | 
            | 
           31 | 
           require_once($CFG->dirroot . '/question/type/numerical/question.php');
  | 
        
        
            | 
            | 
           32 | 
              | 
        
        
            | 
            | 
           33 | 
              | 
        
        
            | 
            | 
           34 | 
           /**
  | 
        
        
            | 
            | 
           35 | 
            * The calculated question type.
  | 
        
        
            | 
            | 
           36 | 
            *
  | 
        
        
            | 
            | 
           37 | 
            * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
  | 
        
        
            | 
            | 
           38 | 
            * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  | 
        
        
            | 
            | 
           39 | 
            */
  | 
        
        
            | 
            | 
           40 | 
           class qtype_calculated extends question_type {
  | 
        
        
            | 
            | 
           41 | 
               /**
  | 
        
        
           | 11 | 
           efrain | 
           42 | 
                * @var string a placeholder is a letter, followed by zero or more alphanum chars (as well as space, - and _ for readability).
  | 
        
        
           | 1 | 
           efrain | 
           43 | 
                */
  | 
        
        
           | 11 | 
           efrain | 
           44 | 
               const PLACEHOLDER_REGEX_PART = '[[:alpha:]][[:alpha:][:digit:]\-_\s]*';
  | 
        
        
           | 1 | 
           efrain | 
           45 | 
              | 
        
        
            | 
            | 
           46 | 
               /**
  | 
        
        
            | 
            | 
           47 | 
                * @var string REGEXP for a placeholder, wrapped in its {...} delimiters, with capturing brackets around the name.
  | 
        
        
            | 
            | 
           48 | 
                */
  | 
        
        
            | 
            | 
           49 | 
               const PLACEHODLER_REGEX = '~\{(' . self::PLACEHOLDER_REGEX_PART . ')\}~';
  | 
        
        
            | 
            | 
           50 | 
              | 
        
        
            | 
            | 
           51 | 
               /**
  | 
        
        
            | 
            | 
           52 | 
                * @var string Regular expression that finds the formulas in content, with capturing brackets to get the forumlas.
  | 
        
        
            | 
            | 
           53 | 
                */
  | 
        
        
            | 
            | 
           54 | 
               const FORMULAS_IN_TEXT_REGEX = '~\{=([^{}]*(?:\{' . self::PLACEHOLDER_REGEX_PART . '\}[^{}]*)*)\}~';
  | 
        
        
            | 
            | 
           55 | 
              | 
        
        
            | 
            | 
           56 | 
               const MAX_DATASET_ITEMS = 100;
  | 
        
        
            | 
            | 
           57 | 
              | 
        
        
            | 
            | 
           58 | 
               public $wizardpagesnumber = 3;
  | 
        
        
            | 
            | 
           59 | 
              | 
        
        
            | 
            | 
           60 | 
               public function get_question_options($question) {
  | 
        
        
            | 
            | 
           61 | 
                   // First get the datasets and default options.
  | 
        
        
            | 
            | 
           62 | 
                   // The code is used for calculated, calculatedsimple and calculatedmulti qtypes.
  | 
        
        
            | 
            | 
           63 | 
                   global $CFG, $DB, $OUTPUT;
  | 
        
        
            | 
            | 
           64 | 
                   parent::get_question_options($question);
  | 
        
        
            | 
            | 
           65 | 
                   if (!$question->options = $DB->get_record('question_calculated_options',
  | 
        
        
            | 
            | 
           66 | 
                           ['question' => $question->id])) {
  | 
        
        
            | 
            | 
           67 | 
                       $question->options = new stdClass();
  | 
        
        
            | 
            | 
           68 | 
                       $question->options->synchronize = 0;
  | 
        
        
            | 
            | 
           69 | 
                       $question->options->single = 0;
  | 
        
        
            | 
            | 
           70 | 
                       $question->options->answernumbering = 'abc';
  | 
        
        
            | 
            | 
           71 | 
                       $question->options->shuffleanswers = 0;
  | 
        
        
            | 
            | 
           72 | 
                       $question->options->correctfeedback = '';
  | 
        
        
            | 
            | 
           73 | 
                       $question->options->partiallycorrectfeedback = '';
  | 
        
        
            | 
            | 
           74 | 
                       $question->options->incorrectfeedback = '';
  | 
        
        
            | 
            | 
           75 | 
                       $question->options->correctfeedbackformat = 0;
  | 
        
        
            | 
            | 
           76 | 
                       $question->options->partiallycorrectfeedbackformat = 0;
  | 
        
        
            | 
            | 
           77 | 
                       $question->options->incorrectfeedbackformat = 0;
  | 
        
        
            | 
            | 
           78 | 
                   }
  | 
        
        
            | 
            | 
           79 | 
              | 
        
        
            | 
            | 
           80 | 
                   if (!$question->options->answers = $DB->get_records_sql("
  | 
        
        
            | 
            | 
           81 | 
                       SELECT a.*, c.tolerance, c.tolerancetype, c.correctanswerlength, c.correctanswerformat
  | 
        
        
            | 
            | 
           82 | 
                       FROM {question_answers} a,
  | 
        
        
            | 
            | 
           83 | 
                            {question_calculated} c
  | 
        
        
            | 
            | 
           84 | 
                       WHERE a.question = ?
  | 
        
        
            | 
            | 
           85 | 
                       AND   a.id = c.answer
  | 
        
        
            | 
            | 
           86 | 
                       ORDER BY a.id ASC", [$question->id])) {
  | 
        
        
            | 
            | 
           87 | 
                           return false;
  | 
        
        
            | 
            | 
           88 | 
                   }
  | 
        
        
            | 
            | 
           89 | 
              | 
        
        
            | 
            | 
           90 | 
                   if ($this->get_virtual_qtype()->name() == 'numerical') {
  | 
        
        
            | 
            | 
           91 | 
                       $this->get_virtual_qtype()->get_numerical_units($question);
  | 
        
        
            | 
            | 
           92 | 
                       $this->get_virtual_qtype()->get_numerical_options($question);
  | 
        
        
            | 
            | 
           93 | 
                   }
  | 
        
        
            | 
            | 
           94 | 
              | 
        
        
            | 
            | 
           95 | 
                   $question->hints = $DB->get_records('question_hints',
  | 
        
        
            | 
            | 
           96 | 
                           ['questionid' => $question->id], 'id ASC');
  | 
        
        
            | 
            | 
           97 | 
              | 
        
        
            | 
            | 
           98 | 
                   if (isset($question->export_process)&&$question->export_process) {
  | 
        
        
            | 
            | 
           99 | 
                       $question->options->datasets = $this->get_datasets_for_export($question);
  | 
        
        
            | 
            | 
           100 | 
                   }
  | 
        
        
            | 
            | 
           101 | 
                   return true;
  | 
        
        
            | 
            | 
           102 | 
               }
  | 
        
        
            | 
            | 
           103 | 
              | 
        
        
            | 
            | 
           104 | 
               public function get_datasets_for_export($question) {
  | 
        
        
            | 
            | 
           105 | 
                   global $DB, $CFG;
  | 
        
        
            | 
            | 
           106 | 
                   $datasetdefs = [];
  | 
        
        
            | 
            | 
           107 | 
                   if (!empty($question->id)) {
  | 
        
        
            | 
            | 
           108 | 
                       $sql = "SELECT i.*
  | 
        
        
            | 
            | 
           109 | 
                                 FROM {question_datasets} d, {question_dataset_definitions} i
  | 
        
        
            | 
            | 
           110 | 
                                WHERE d.question = ? AND d.datasetdefinition = i.id";
  | 
        
        
            | 
            | 
           111 | 
                       if ($records = $DB->get_records_sql($sql, [$question->id])) {
  | 
        
        
            | 
            | 
           112 | 
                           foreach ($records as $r) {
  | 
        
        
            | 
            | 
           113 | 
                               $def = $r;
  | 
        
        
            | 
            | 
           114 | 
                               if ($def->category == '0') {
  | 
        
        
            | 
            | 
           115 | 
                                   $def->status = 'private';
  | 
        
        
            | 
            | 
           116 | 
                               } else {
  | 
        
        
            | 
            | 
           117 | 
                                   $def->status = 'shared';
  | 
        
        
            | 
            | 
           118 | 
                               }
  | 
        
        
            | 
            | 
           119 | 
                               $def->type = 'calculated';
  | 
        
        
            | 
            | 
           120 | 
                               list($distribution, $min, $max, $dec) = explode(':', $def->options, 4);
  | 
        
        
            | 
            | 
           121 | 
                               $def->distribution = $distribution;
  | 
        
        
            | 
            | 
           122 | 
                               $def->minimum = $min;
  | 
        
        
            | 
            | 
           123 | 
                               $def->maximum = $max;
  | 
        
        
            | 
            | 
           124 | 
                               $def->decimals = $dec;
  | 
        
        
            | 
            | 
           125 | 
                               if ($def->itemcount > 0) {
  | 
        
        
            | 
            | 
           126 | 
                                   // Get the datasetitems.
  | 
        
        
            | 
            | 
           127 | 
                                   $def->items = [];
  | 
        
        
            | 
            | 
           128 | 
                                   if ($items = $this->get_database_dataset_items($def->id)) {
  | 
        
        
            | 
            | 
           129 | 
                                       $n = 0;
  | 
        
        
            | 
            | 
           130 | 
                                       foreach ($items as $ii) {
  | 
        
        
            | 
            | 
           131 | 
                                           $n++;
  | 
        
        
            | 
            | 
           132 | 
                                           $def->items[$n] = new stdClass();
  | 
        
        
            | 
            | 
           133 | 
                                           $def->items[$n]->itemnumber = $ii->itemnumber;
  | 
        
        
            | 
            | 
           134 | 
                                           $def->items[$n]->value = $ii->value;
  | 
        
        
            | 
            | 
           135 | 
                                       }
  | 
        
        
            | 
            | 
           136 | 
                                       $def->number_of_items = $n;
  | 
        
        
            | 
            | 
           137 | 
                                   }
  | 
        
        
            | 
            | 
           138 | 
                               }
  | 
        
        
            | 
            | 
           139 | 
                               $datasetdefs["1-{$r->category}-{$r->name}"] = $def;
  | 
        
        
            | 
            | 
           140 | 
                           }
  | 
        
        
            | 
            | 
           141 | 
                       }
  | 
        
        
            | 
            | 
           142 | 
                   }
  | 
        
        
            | 
            | 
           143 | 
                   return $datasetdefs;
  | 
        
        
            | 
            | 
           144 | 
               }
  | 
        
        
            | 
            | 
           145 | 
              | 
        
        
            | 
            | 
           146 | 
               public function save_question_options($question) {
  | 
        
        
            | 
            | 
           147 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           148 | 
              | 
        
        
            | 
            | 
           149 | 
                   // Make it impossible to save bad formulas anywhere.
  | 
        
        
            | 
            | 
           150 | 
                   $this->validate_question_data($question);
  | 
        
        
            | 
            | 
           151 | 
              | 
        
        
            | 
            | 
           152 | 
                   // The code is used for calculated, calculatedsimple and calculatedmulti qtypes.
  | 
        
        
            | 
            | 
           153 | 
                   $context = $question->context;
  | 
        
        
            | 
            | 
           154 | 
              | 
        
        
            | 
            | 
           155 | 
                   // Calculated options.
  | 
        
        
            | 
            | 
           156 | 
                   $update = true;
  | 
        
        
            | 
            | 
           157 | 
                   $options = $DB->get_record('question_calculated_options',
  | 
        
        
            | 
            | 
           158 | 
                           ['question' => $question->id]);
  | 
        
        
            | 
            | 
           159 | 
                   if (!$options) {
  | 
        
        
            | 
            | 
           160 | 
                       $update = false;
  | 
        
        
            | 
            | 
           161 | 
                       $options = new stdClass();
  | 
        
        
            | 
            | 
           162 | 
                       $options->question = $question->id;
  | 
        
        
            | 
            | 
           163 | 
                   }
  | 
        
        
            | 
            | 
           164 | 
                   // As used only by calculated.
  | 
        
        
            | 
            | 
           165 | 
                   if (isset($question->synchronize)) {
  | 
        
        
            | 
            | 
           166 | 
                       $options->synchronize = $question->synchronize;
  | 
        
        
            | 
            | 
           167 | 
                   } else {
  | 
        
        
            | 
            | 
           168 | 
                       $options->synchronize = 0;
  | 
        
        
            | 
            | 
           169 | 
                   }
  | 
        
        
            | 
            | 
           170 | 
                   $options->single = 0;
  | 
        
        
            | 
            | 
           171 | 
                   $options->answernumbering = $question->answernumbering;
  | 
        
        
            | 
            | 
           172 | 
                   $options->shuffleanswers = $question->shuffleanswers;
  | 
        
        
            | 
            | 
           173 | 
              | 
        
        
            | 
            | 
           174 | 
                   foreach (['correctfeedback', 'partiallycorrectfeedback',
  | 
        
        
            | 
            | 
           175 | 
                           'incorrectfeedback'] as $feedbackname) {
  | 
        
        
            | 
            | 
           176 | 
                       $options->$feedbackname = '';
  | 
        
        
            | 
            | 
           177 | 
                       $feedbackformat = $feedbackname . 'format';
  | 
        
        
            | 
            | 
           178 | 
                       $options->$feedbackformat = 0;
  | 
        
        
            | 
            | 
           179 | 
                   }
  | 
        
        
            | 
            | 
           180 | 
              | 
        
        
            | 
            | 
           181 | 
                   if ($update) {
  | 
        
        
            | 
            | 
           182 | 
                       $DB->update_record('question_calculated_options', $options);
  | 
        
        
            | 
            | 
           183 | 
                   } else {
  | 
        
        
            | 
            | 
           184 | 
                       $DB->insert_record('question_calculated_options', $options);
  | 
        
        
            | 
            | 
           185 | 
                   }
  | 
        
        
            | 
            | 
           186 | 
              | 
        
        
            | 
            | 
           187 | 
                   // Get old versions of the objects.
  | 
        
        
            | 
            | 
           188 | 
                   $oldanswers = $DB->get_records('question_answers',
  | 
        
        
            | 
            | 
           189 | 
                           ['question' => $question->id], 'id ASC');
  | 
        
        
            | 
            | 
           190 | 
              | 
        
        
            | 
            | 
           191 | 
                   $oldoptions = $DB->get_records('question_calculated',
  | 
        
        
            | 
            | 
           192 | 
                           ['question' => $question->id], 'answer ASC');
  | 
        
        
            | 
            | 
           193 | 
              | 
        
        
            | 
            | 
           194 | 
                   // Save the units.
  | 
        
        
            | 
            | 
           195 | 
                   $virtualqtype = $this->get_virtual_qtype();
  | 
        
        
            | 
            | 
           196 | 
              | 
        
        
            | 
            | 
           197 | 
                   $result = $virtualqtype->save_units($question);
  | 
        
        
            | 
            | 
           198 | 
                   if (isset($result->error)) {
  | 
        
        
            | 
            | 
           199 | 
                       return $result;
  | 
        
        
            | 
            | 
           200 | 
                   } else {
  | 
        
        
            | 
            | 
           201 | 
                       $units = $result->units;
  | 
        
        
            | 
            | 
           202 | 
                   }
  | 
        
        
            | 
            | 
           203 | 
              | 
        
        
            | 
            | 
           204 | 
                   foreach ($question->answer as $key => $answerdata) {
  | 
        
        
            | 
            | 
           205 | 
                       if (trim($answerdata) == '') {
  | 
        
        
            | 
            | 
           206 | 
                           continue;
  | 
        
        
            | 
            | 
           207 | 
                       }
  | 
        
        
            | 
            | 
           208 | 
              | 
        
        
            | 
            | 
           209 | 
                       // Update an existing answer if possible.
  | 
        
        
            | 
            | 
           210 | 
                       $answer = array_shift($oldanswers);
  | 
        
        
            | 
            | 
           211 | 
                       if (!$answer) {
  | 
        
        
            | 
            | 
           212 | 
                           $answer = new stdClass();
  | 
        
        
            | 
            | 
           213 | 
                           $answer->question = $question->id;
  | 
        
        
            | 
            | 
           214 | 
                           $answer->answer   = '';
  | 
        
        
            | 
            | 
           215 | 
                           $answer->feedback = '';
  | 
        
        
            | 
            | 
           216 | 
                           $answer->id       = $DB->insert_record('question_answers', $answer);
  | 
        
        
            | 
            | 
           217 | 
                       }
  | 
        
        
            | 
            | 
           218 | 
              | 
        
        
            | 
            | 
           219 | 
                       $answer->answer   = trim($answerdata);
  | 
        
        
            | 
            | 
           220 | 
                       $answer->fraction = $question->fraction[$key];
  | 
        
        
            | 
            | 
           221 | 
                       $answer->feedback = $this->import_or_save_files($question->feedback[$key],
  | 
        
        
            | 
            | 
           222 | 
                               $context, 'question', 'answerfeedback', $answer->id);
  | 
        
        
            | 
            | 
           223 | 
                       $answer->feedbackformat = $question->feedback[$key]['format'];
  | 
        
        
            | 
            | 
           224 | 
              | 
        
        
            | 
            | 
           225 | 
                       $DB->update_record("question_answers", $answer);
  | 
        
        
            | 
            | 
           226 | 
              | 
        
        
            | 
            | 
           227 | 
                       // Set up the options object.
  | 
        
        
            | 
            | 
           228 | 
                       if (!$options = array_shift($oldoptions)) {
  | 
        
        
            | 
            | 
           229 | 
                           $options = new stdClass();
  | 
        
        
            | 
            | 
           230 | 
                       }
  | 
        
        
            | 
            | 
           231 | 
                       $options->question            = $question->id;
  | 
        
        
            | 
            | 
           232 | 
                       $options->answer              = $answer->id;
  | 
        
        
            | 
            | 
           233 | 
                       $options->tolerance           = trim($question->tolerance[$key]);
  | 
        
        
            | 
            | 
           234 | 
                       $options->tolerancetype       = trim($question->tolerancetype[$key]);
  | 
        
        
            | 
            | 
           235 | 
                       $options->correctanswerlength = trim($question->correctanswerlength[$key]);
  | 
        
        
            | 
            | 
           236 | 
                       $options->correctanswerformat = trim($question->correctanswerformat[$key]);
  | 
        
        
            | 
            | 
           237 | 
              | 
        
        
            | 
            | 
           238 | 
                       // Save options.
  | 
        
        
            | 
            | 
           239 | 
                       if (isset($options->id)) {
  | 
        
        
            | 
            | 
           240 | 
                           // Reusing existing record.
  | 
        
        
            | 
            | 
           241 | 
                           $DB->update_record('question_calculated', $options);
  | 
        
        
            | 
            | 
           242 | 
                       } else {
  | 
        
        
            | 
            | 
           243 | 
                           // New options.
  | 
        
        
            | 
            | 
           244 | 
                           $DB->insert_record('question_calculated', $options);
  | 
        
        
            | 
            | 
           245 | 
                       }
  | 
        
        
            | 
            | 
           246 | 
                   }
  | 
        
        
            | 
            | 
           247 | 
              | 
        
        
            | 
            | 
           248 | 
                   // Delete old answer records.
  | 
        
        
            | 
            | 
           249 | 
                   if (!empty($oldanswers)) {
  | 
        
        
            | 
            | 
           250 | 
                       foreach ($oldanswers as $oa) {
  | 
        
        
            | 
            | 
           251 | 
                           $DB->delete_records('question_answers', ['id' => $oa->id]);
  | 
        
        
            | 
            | 
           252 | 
                       }
  | 
        
        
            | 
            | 
           253 | 
                   }
  | 
        
        
            | 
            | 
           254 | 
              | 
        
        
            | 
            | 
           255 | 
                   // Delete old answer records.
  | 
        
        
            | 
            | 
           256 | 
                   if (!empty($oldoptions)) {
  | 
        
        
            | 
            | 
           257 | 
                       foreach ($oldoptions as $oo) {
  | 
        
        
            | 
            | 
           258 | 
                           $DB->delete_records('question_calculated', ['id' => $oo->id]);
  | 
        
        
            | 
            | 
           259 | 
                       }
  | 
        
        
            | 
            | 
           260 | 
                   }
  | 
        
        
            | 
            | 
           261 | 
              | 
        
        
            | 
            | 
           262 | 
                   $result = $virtualqtype->save_unit_options($question);
  | 
        
        
            | 
            | 
           263 | 
                   if (isset($result->error)) {
  | 
        
        
            | 
            | 
           264 | 
                       return $result;
  | 
        
        
            | 
            | 
           265 | 
                   }
  | 
        
        
            | 
            | 
           266 | 
              | 
        
        
            | 
            | 
           267 | 
                   $this->save_hints($question);
  | 
        
        
            | 
            | 
           268 | 
              | 
        
        
            | 
            | 
           269 | 
                   if (isset($question->import_process)&&$question->import_process) {
  | 
        
        
            | 
            | 
           270 | 
                       $this->import_datasets($question);
  | 
        
        
            | 
            | 
           271 | 
                   }
  | 
        
        
            | 
            | 
           272 | 
                   // Report any problems.
  | 
        
        
            | 
            | 
           273 | 
                   if (!empty($result->notice)) {
  | 
        
        
            | 
            | 
           274 | 
                       return $result;
  | 
        
        
            | 
            | 
           275 | 
                   }
  | 
        
        
            | 
            | 
           276 | 
                   return true;
  | 
        
        
            | 
            | 
           277 | 
               }
  | 
        
        
            | 
            | 
           278 | 
              | 
        
        
            | 
            | 
           279 | 
               public function import_datasets($question) {
  | 
        
        
            | 
            | 
           280 | 
                   global $DB;
  | 
        
        
            | 
            | 
           281 | 
                   $n = count($question->dataset);
  | 
        
        
            | 
            | 
           282 | 
                   foreach ($question->dataset as $dataset) {
  | 
        
        
            | 
            | 
           283 | 
                       // Name, type, option.
  | 
        
        
            | 
            | 
           284 | 
                       $datasetdef = new stdClass();
  | 
        
        
            | 
            | 
           285 | 
                       $datasetdef->name = $dataset->name;
  | 
        
        
            | 
            | 
           286 | 
                       $datasetdef->type = 1;
  | 
        
        
            | 
            | 
           287 | 
                       $datasetdef->options = $dataset->distribution . ':' . $dataset->min . ':' .
  | 
        
        
            | 
            | 
           288 | 
                               $dataset->max . ':' . $dataset->length;
  | 
        
        
            | 
            | 
           289 | 
                       $datasetdef->itemcount = $dataset->itemcount;
  | 
        
        
            | 
            | 
           290 | 
                       if ($dataset->status == 'private') {
  | 
        
        
            | 
            | 
           291 | 
                           $datasetdef->category = 0;
  | 
        
        
            | 
            | 
           292 | 
                           $todo = 'create';
  | 
        
        
            | 
            | 
           293 | 
                       } else if ($dataset->status == 'shared') {
  | 
        
        
            | 
            | 
           294 | 
                           if ($sharedatasetdefs = $DB->get_records_select(
  | 
        
        
            | 
            | 
           295 | 
                               'question_dataset_definitions',
  | 
        
        
            | 
            | 
           296 | 
                               "type = '1'
  | 
        
        
            | 
            | 
           297 | 
                               AND " . $DB->sql_equal('name', '?') . "
  | 
        
        
            | 
            | 
           298 | 
                               AND category = ?
  | 
        
        
            | 
            | 
           299 | 
                               ORDER BY id DESC ", [$dataset->name, $question->category]
  | 
        
        
            | 
            | 
           300 | 
                           )) { // So there is at least one.
  | 
        
        
            | 
            | 
           301 | 
                               $sharedatasetdef = array_shift($sharedatasetdefs);
  | 
        
        
            | 
            | 
           302 | 
                               if ($sharedatasetdef->options == $datasetdef->options) {// Identical so use it.
  | 
        
        
            | 
            | 
           303 | 
                                   $todo = 'useit';
  | 
        
        
            | 
            | 
           304 | 
                                   $datasetdef = $sharedatasetdef;
  | 
        
        
            | 
            | 
           305 | 
                               } else { // Different so create a private one.
  | 
        
        
            | 
            | 
           306 | 
                                   $datasetdef->category = 0;
  | 
        
        
            | 
            | 
           307 | 
                                   $todo = 'create';
  | 
        
        
            | 
            | 
           308 | 
                               }
  | 
        
        
            | 
            | 
           309 | 
                           } else { // No so create one.
  | 
        
        
            | 
            | 
           310 | 
                               $datasetdef->category = $question->category;
  | 
        
        
            | 
            | 
           311 | 
                               $todo = 'create';
  | 
        
        
            | 
            | 
           312 | 
                           }
  | 
        
        
            | 
            | 
           313 | 
                       }
  | 
        
        
            | 
            | 
           314 | 
                       if ($todo == 'create') {
  | 
        
        
            | 
            | 
           315 | 
                           $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
  | 
        
        
            | 
            | 
           316 | 
                       }
  | 
        
        
            | 
            | 
           317 | 
                       // Create relation to the dataset.
  | 
        
        
            | 
            | 
           318 | 
                       $questiondataset = new stdClass();
  | 
        
        
            | 
            | 
           319 | 
                       $questiondataset->question = $question->id;
  | 
        
        
            | 
            | 
           320 | 
                       $questiondataset->datasetdefinition = $datasetdef->id;
  | 
        
        
            | 
            | 
           321 | 
                       $DB->insert_record('question_datasets', $questiondataset);
  | 
        
        
            | 
            | 
           322 | 
                       if ($todo == 'create') {
  | 
        
        
            | 
            | 
           323 | 
                           // Add the items.
  | 
        
        
            | 
            | 
           324 | 
                           foreach ($dataset->datasetitem as $dataitem) {
  | 
        
        
            | 
            | 
           325 | 
                               $datasetitem = new stdClass();
  | 
        
        
            | 
            | 
           326 | 
                               $datasetitem->definition = $datasetdef->id;
  | 
        
        
            | 
            | 
           327 | 
                               $datasetitem->itemnumber = $dataitem->itemnumber;
  | 
        
        
            | 
            | 
           328 | 
                               $datasetitem->value = $dataitem->value;
  | 
        
        
            | 
            | 
           329 | 
                               $DB->insert_record('question_dataset_items', $datasetitem);
  | 
        
        
            | 
            | 
           330 | 
                           }
  | 
        
        
            | 
            | 
           331 | 
                       }
  | 
        
        
            | 
            | 
           332 | 
                   }
  | 
        
        
            | 
            | 
           333 | 
               }
  | 
        
        
            | 
            | 
           334 | 
              | 
        
        
            | 
            | 
           335 | 
               /**
  | 
        
        
            | 
            | 
           336 | 
                * Initializes calculated answers for a given question.
  | 
        
        
            | 
            | 
           337 | 
                *
  | 
        
        
            | 
            | 
           338 | 
                * @param question_definition $question The question definition object.
  | 
        
        
            | 
            | 
           339 | 
                * @param stdClass $questiondata The question data object.
  | 
        
        
            | 
            | 
           340 | 
                */
  | 
        
        
            | 
            | 
           341 | 
               protected function initialise_calculated_answers(question_definition $question, stdClass $questiondata) {
  | 
        
        
            | 
            | 
           342 | 
                   $question->answers = [];
  | 
        
        
            | 
            | 
           343 | 
                   if (empty($questiondata->options->answers)) {
  | 
        
        
            | 
            | 
           344 | 
                       return;
  | 
        
        
            | 
            | 
           345 | 
                   }
  | 
        
        
            | 
            | 
           346 | 
                   foreach ($questiondata->options->answers as $a) {
  | 
        
        
            | 
            | 
           347 | 
                       $question->answers[$a->id] = new \qtype_calculated\qtype_calculated_answer($a->id, $a->answer,
  | 
        
        
            | 
            | 
           348 | 
                               $a->fraction, $a->feedback, $a->feedbackformat, $a->tolerance);
  | 
        
        
            | 
            | 
           349 | 
                   }
  | 
        
        
            | 
            | 
           350 | 
               }
  | 
        
        
            | 
            | 
           351 | 
              | 
        
        
            | 
            | 
           352 | 
               protected function initialise_question_instance(question_definition $question, $questiondata) {
  | 
        
        
            | 
            | 
           353 | 
                   parent::initialise_question_instance($question, $questiondata);
  | 
        
        
            | 
            | 
           354 | 
                   $this->initialise_calculated_answers($question, $questiondata);
  | 
        
        
            | 
            | 
           355 | 
              | 
        
        
            | 
            | 
           356 | 
                   foreach ($questiondata->options->answers as $a) {
  | 
        
        
            | 
            | 
           357 | 
                       $question->answers[$a->id]->tolerancetype = $a->tolerancetype;
  | 
        
        
            | 
            | 
           358 | 
                       $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength;
  | 
        
        
            | 
            | 
           359 | 
                       $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat;
  | 
        
        
            | 
            | 
           360 | 
                   }
  | 
        
        
            | 
            | 
           361 | 
              | 
        
        
            | 
            | 
           362 | 
                   $question->synchronised = $questiondata->options->synchronize;
  | 
        
        
            | 
            | 
           363 | 
              | 
        
        
            | 
            | 
           364 | 
                   $question->unitdisplay = $questiondata->options->showunits;
  | 
        
        
            | 
            | 
           365 | 
                   $question->unitgradingtype = $questiondata->options->unitgradingtype;
  | 
        
        
            | 
            | 
           366 | 
                   $question->unitpenalty = $questiondata->options->unitpenalty;
  | 
        
        
            | 
            | 
           367 | 
                   $question->ap = question_bank::get_qtype(
  | 
        
        
            | 
            | 
           368 | 
                           'numerical')->make_answer_processor(
  | 
        
        
            | 
            | 
           369 | 
                           $questiondata->options->units, $questiondata->options->unitsleft);
  | 
        
        
            | 
            | 
           370 | 
              | 
        
        
            | 
            | 
           371 | 
                   $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
  | 
        
        
            | 
            | 
           372 | 
               }
  | 
        
        
            | 
            | 
           373 | 
              | 
        
        
            | 
            | 
           374 | 
               public function finished_edit_wizard($form) {
  | 
        
        
            | 
            | 
           375 | 
                   return isset($form->savechanges);
  | 
        
        
            | 
            | 
           376 | 
               }
  | 
        
        
            | 
            | 
           377 | 
               public function wizardpagesnumber() {
  | 
        
        
            | 
            | 
           378 | 
                   return 3;
  | 
        
        
            | 
            | 
           379 | 
               }
  | 
        
        
            | 
            | 
           380 | 
               // This gets called by editquestion.php after the standard question is saved.
  | 
        
        
            | 
            | 
           381 | 
               public function print_next_wizard_page($question, $form, $course) {
  | 
        
        
            | 
            | 
           382 | 
                   global $CFG, $SESSION, $COURSE;
  | 
        
        
            | 
            | 
           383 | 
              | 
        
        
            | 
            | 
           384 | 
                   // Catch invalid navigation & reloads.
  | 
        
        
            | 
            | 
           385 | 
                   if (empty($question->id) && empty($SESSION->calculated)) {
  | 
        
        
            | 
            | 
           386 | 
                       redirect('edit.php?courseid='.$COURSE->id, 'The page you are loading has expired.', 3);
  | 
        
        
            | 
            | 
           387 | 
                   }
  | 
        
        
            | 
            | 
           388 | 
              | 
        
        
            | 
            | 
           389 | 
                   // See where we're coming from.
  | 
        
        
            | 
            | 
           390 | 
                   switch($form->wizardpage) {
  | 
        
        
            | 
            | 
           391 | 
                       case 'question':
  | 
        
        
            | 
            | 
           392 | 
                           require("{$CFG->dirroot}/question/type/calculated/datasetdefinitions.php");
  | 
        
        
            | 
            | 
           393 | 
                           break;
  | 
        
        
            | 
            | 
           394 | 
                       case 'datasetdefinitions':
  | 
        
        
            | 
            | 
           395 | 
                       case 'datasetitems':
  | 
        
        
            | 
            | 
           396 | 
                           require("{$CFG->dirroot}/question/type/calculated/datasetitems.php");
  | 
        
        
            | 
            | 
           397 | 
                           break;
  | 
        
        
            | 
            | 
           398 | 
                       default:
  | 
        
        
            | 
            | 
           399 | 
                           throw new \moodle_exception('invalidwizardpage', 'question');
  | 
        
        
            | 
            | 
           400 | 
                           break;
  | 
        
        
            | 
            | 
           401 | 
                   }
  | 
        
        
            | 
            | 
           402 | 
               }
  | 
        
        
            | 
            | 
           403 | 
              | 
        
        
            | 
            | 
           404 | 
               // This gets called by question2.php after the standard question is saved.
  | 
        
        
            | 
            | 
           405 | 
               public function &next_wizard_form($submiturl, $question, $wizardnow) {
  | 
        
        
            | 
            | 
           406 | 
                   global $CFG, $SESSION, $COURSE;
  | 
        
        
            | 
            | 
           407 | 
              | 
        
        
            | 
            | 
           408 | 
                   // Catch invalid navigation & reloads.
  | 
        
        
            | 
            | 
           409 | 
                   if (empty($question->id) && empty($SESSION->calculated)) {
  | 
        
        
            | 
            | 
           410 | 
                       redirect('edit.php?courseid=' . $COURSE->id,
  | 
        
        
            | 
            | 
           411 | 
                               'The page you are loading has expired. Cannot get next wizard form.', 3);
  | 
        
        
            | 
            | 
           412 | 
                   }
  | 
        
        
            | 
            | 
           413 | 
                   if (empty($question->id)) {
  | 
        
        
            | 
            | 
           414 | 
                       $question = $SESSION->calculated->questionform;
  | 
        
        
            | 
            | 
           415 | 
                   }
  | 
        
        
            | 
            | 
           416 | 
              | 
        
        
            | 
            | 
           417 | 
                   // See where we're coming from.
  | 
        
        
            | 
            | 
           418 | 
                   switch($wizardnow) {
  | 
        
        
            | 
            | 
           419 | 
                       case 'datasetdefinitions':
  | 
        
        
            | 
            | 
           420 | 
                           require("{$CFG->dirroot}/question/type/calculated/datasetdefinitions_form.php");
  | 
        
        
            | 
            | 
           421 | 
                           $mform = new question_dataset_dependent_definitions_form(
  | 
        
        
            | 
            | 
           422 | 
                                   "{$submiturl}?wizardnow=datasetdefinitions", $question);
  | 
        
        
            | 
            | 
           423 | 
                           break;
  | 
        
        
            | 
            | 
           424 | 
                       case 'datasetitems':
  | 
        
        
            | 
            | 
           425 | 
                           require("{$CFG->dirroot}/question/type/calculated/datasetitems_form.php");
  | 
        
        
            | 
            | 
           426 | 
                           $regenerate = optional_param('forceregeneration', false, PARAM_BOOL);
  | 
        
        
            | 
            | 
           427 | 
                           $mform = new question_dataset_dependent_items_form(
  | 
        
        
            | 
            | 
           428 | 
                                   "{$submiturl}?wizardnow=datasetitems", $question, $regenerate);
  | 
        
        
            | 
            | 
           429 | 
                           break;
  | 
        
        
            | 
            | 
           430 | 
                       default:
  | 
        
        
            | 
            | 
           431 | 
                           throw new \moodle_exception('invalidwizardpage', 'question');
  | 
        
        
            | 
            | 
           432 | 
                           break;
  | 
        
        
            | 
            | 
           433 | 
                   }
  | 
        
        
            | 
            | 
           434 | 
              | 
        
        
            | 
            | 
           435 | 
                   return $mform;
  | 
        
        
            | 
            | 
           436 | 
               }
  | 
        
        
            | 
            | 
           437 | 
              | 
        
        
            | 
            | 
           438 | 
               /**
  | 
        
        
            | 
            | 
           439 | 
                * This method should be overriden if you want to include a special heading or some other
  | 
        
        
            | 
            | 
           440 | 
                * html on a question editing page besides the question editing form.
  | 
        
        
            | 
            | 
           441 | 
                *
  | 
        
        
            | 
            | 
           442 | 
                * @param question_edit_form $mform a child of question_edit_form
  | 
        
        
            | 
            | 
           443 | 
                * @param object $question
  | 
        
        
            | 
            | 
           444 | 
                * @param string $wizardnow is '' for first page.
  | 
        
        
            | 
            | 
           445 | 
                */
  | 
        
        
            | 
            | 
           446 | 
               public function display_question_editing_page($mform, $question, $wizardnow) {
  | 
        
        
            | 
            | 
           447 | 
                   global $OUTPUT;
  | 
        
        
            | 
            | 
           448 | 
                   switch ($wizardnow) {
  | 
        
        
            | 
            | 
           449 | 
                       case '':
  | 
        
        
            | 
            | 
           450 | 
                           // On the first page, the default display is fine.
  | 
        
        
            | 
            | 
           451 | 
                           parent::display_question_editing_page($mform, $question, $wizardnow);
  | 
        
        
            | 
            | 
           452 | 
                           return;
  | 
        
        
            | 
            | 
           453 | 
              | 
        
        
            | 
            | 
           454 | 
                       case 'datasetdefinitions':
  | 
        
        
            | 
            | 
           455 | 
                           echo $OUTPUT->heading_with_help(
  | 
        
        
            | 
            | 
           456 | 
                                   get_string('choosedatasetproperties', 'qtype_calculated'),
  | 
        
        
            | 
            | 
           457 | 
                                   'questiondatasets', 'qtype_calculated');
  | 
        
        
            | 
            | 
           458 | 
                           break;
  | 
        
        
            | 
            | 
           459 | 
              | 
        
        
            | 
            | 
           460 | 
                       case 'datasetitems':
  | 
        
        
            | 
            | 
           461 | 
                           echo $OUTPUT->heading_with_help(get_string('editdatasets', 'qtype_calculated'),
  | 
        
        
            | 
            | 
           462 | 
                                   'questiondatasets', 'qtype_calculated');
  | 
        
        
            | 
            | 
           463 | 
                           break;
  | 
        
        
            | 
            | 
           464 | 
                   }
  | 
        
        
            | 
            | 
           465 | 
              | 
        
        
            | 
            | 
           466 | 
                   $mform->display();
  | 
        
        
            | 
            | 
           467 | 
               }
  | 
        
        
            | 
            | 
           468 | 
              | 
        
        
            | 
            | 
           469 | 
               /**
  | 
        
        
            | 
            | 
           470 | 
                * Verify that the equations in part of the question are OK.
  | 
        
        
            | 
            | 
           471 | 
                * We throw an exception here because this should have already been validated
  | 
        
        
            | 
            | 
           472 | 
                * by the form. This is just a last line of defence to prevent a question
  | 
        
        
            | 
            | 
           473 | 
                * being stored in the database if it has bad formulas. This saves us from,
  | 
        
        
            | 
            | 
           474 | 
                * for example, malicious imports.
  | 
        
        
            | 
            | 
           475 | 
                * @param string $text containing equations.
  | 
        
        
            | 
            | 
           476 | 
                */
  | 
        
        
            | 
            | 
           477 | 
               protected function validate_text($text) {
  | 
        
        
            | 
            | 
           478 | 
                   $error = qtype_calculated_find_formula_errors_in_text($text);
  | 
        
        
            | 
            | 
           479 | 
                   if ($error) {
  | 
        
        
            | 
            | 
           480 | 
                       throw new coding_exception($error);
  | 
        
        
            | 
            | 
           481 | 
                   }
  | 
        
        
            | 
            | 
           482 | 
               }
  | 
        
        
            | 
            | 
           483 | 
              | 
        
        
            | 
            | 
           484 | 
               /**
  | 
        
        
            | 
            | 
           485 | 
                * Verify that an answer is OK.
  | 
        
        
            | 
            | 
           486 | 
                * We throw an exception here because this should have already been validated
  | 
        
        
            | 
            | 
           487 | 
                * by the form. This is just a last line of defence to prevent a question
  | 
        
        
            | 
            | 
           488 | 
                * being stored in the database if it has bad formulas. This saves us from,
  | 
        
        
            | 
            | 
           489 | 
                * for example, malicious imports.
  | 
        
        
            | 
            | 
           490 | 
                * @param string $text containing equations.
  | 
        
        
            | 
            | 
           491 | 
                */
  | 
        
        
            | 
            | 
           492 | 
               protected function validate_answer($answer) {
  | 
        
        
            | 
            | 
           493 | 
                   $error = qtype_calculated_find_formula_errors($answer);
  | 
        
        
            | 
            | 
           494 | 
                   if ($error) {
  | 
        
        
            | 
            | 
           495 | 
                       throw new coding_exception($error);
  | 
        
        
            | 
            | 
           496 | 
                   }
  | 
        
        
            | 
            | 
           497 | 
               }
  | 
        
        
            | 
            | 
           498 | 
              | 
        
        
            | 
            | 
           499 | 
               /**
  | 
        
        
            | 
            | 
           500 | 
                * Validate data before save.
  | 
        
        
            | 
            | 
           501 | 
                * @param stdClass $question data from the form / import file.
  | 
        
        
            | 
            | 
           502 | 
                */
  | 
        
        
            | 
            | 
           503 | 
               protected function validate_question_data($question) {
  | 
        
        
            | 
            | 
           504 | 
                   $this->validate_text($question->questiontext); // Yes, really no ['text'].
  | 
        
        
            | 
            | 
           505 | 
              | 
        
        
            | 
            | 
           506 | 
                   if (isset($question->generalfeedback['text'])) {
  | 
        
        
            | 
            | 
           507 | 
                       $this->validate_text($question->generalfeedback['text']);
  | 
        
        
            | 
            | 
           508 | 
                   } else if (isset($question->generalfeedback)) {
  | 
        
        
            | 
            | 
           509 | 
                       $this->validate_text($question->generalfeedback); // Because question import is weird.
  | 
        
        
            | 
            | 
           510 | 
                   }
  | 
        
        
            | 
            | 
           511 | 
              | 
        
        
            | 
            | 
           512 | 
                   foreach ($question->answer as $key => $answer) {
  | 
        
        
            | 
            | 
           513 | 
                       $this->validate_answer($answer);
  | 
        
        
            | 
            | 
           514 | 
                       $this->validate_text($question->feedback[$key]['text']);
  | 
        
        
            | 
            | 
           515 | 
                   }
  | 
        
        
            | 
            | 
           516 | 
               }
  | 
        
        
            | 
            | 
           517 | 
              | 
        
        
            | 
            | 
           518 | 
               /**
  | 
        
        
            | 
            | 
           519 | 
                * Remove prefix #{..}# if exists.
  | 
        
        
            | 
            | 
           520 | 
                * @param $name a question name,
  | 
        
        
            | 
            | 
           521 | 
                * @return string the cleaned up question name.
  | 
        
        
            | 
            | 
           522 | 
                */
  | 
        
        
            | 
            | 
           523 | 
               public function clean_technical_prefix_from_question_name($name) {
  | 
        
        
            | 
            | 
           524 | 
                   return preg_replace('~#\{([^[:space:]]*)#~', '', $name);
  | 
        
        
            | 
            | 
           525 | 
               }
  | 
        
        
            | 
            | 
           526 | 
              | 
        
        
            | 
            | 
           527 | 
               /**
  | 
        
        
            | 
            | 
           528 | 
                * This method prepare the $datasets in a format similar to dadatesetdefinitions_form.php
  | 
        
        
            | 
            | 
           529 | 
                * so that they can be saved
  | 
        
        
            | 
            | 
           530 | 
                * using the function save_dataset_definitions($form)
  | 
        
        
            | 
            | 
           531 | 
                * when creating a new calculated question or
  | 
        
        
            | 
            | 
           532 | 
                * when editing an already existing calculated question
  | 
        
        
            | 
            | 
           533 | 
                * or by  function save_as_new_dataset_definitions($form, $initialid)
  | 
        
        
            | 
            | 
           534 | 
                * when saving as new an already existing calculated question.
  | 
        
        
            | 
            | 
           535 | 
                *
  | 
        
        
            | 
            | 
           536 | 
                * @param object $form
  | 
        
        
            | 
            | 
           537 | 
                * @param int $questionfromid default = '0'
  | 
        
        
            | 
            | 
           538 | 
                */
  | 
        
        
            | 
            | 
           539 | 
               public function preparedatasets($form, $questionfromid = '0') {
  | 
        
        
            | 
            | 
           540 | 
              | 
        
        
            | 
            | 
           541 | 
                   // The dataset names present in the edit_question_form and edit_calculated_form
  | 
        
        
            | 
            | 
           542 | 
                   // are retrieved.
  | 
        
        
            | 
            | 
           543 | 
                   $possibledatasets = $this->find_dataset_names($form->questiontext);
  | 
        
        
            | 
            | 
           544 | 
                   $mandatorydatasets = [];
  | 
        
        
            | 
            | 
           545 | 
                   foreach ($form->answer as $key => $answer) {
  | 
        
        
            | 
            | 
           546 | 
                       $mandatorydatasets += $this->find_dataset_names($answer);
  | 
        
        
            | 
            | 
           547 | 
                   }
  | 
        
        
            | 
            | 
           548 | 
                   // If there are identical datasetdefs already saved in the original question
  | 
        
        
            | 
            | 
           549 | 
                   // either when editing a question or saving as new,
  | 
        
        
            | 
            | 
           550 | 
                   // they are retrieved using $questionfromid.
  | 
        
        
            | 
            | 
           551 | 
                   if ($questionfromid != '0') {
  | 
        
        
            | 
            | 
           552 | 
                       $form->id = $questionfromid;
  | 
        
        
            | 
            | 
           553 | 
                   }
  | 
        
        
            | 
            | 
           554 | 
                   $datasets = [];
  | 
        
        
            | 
            | 
           555 | 
                   $key = 0;
  | 
        
        
            | 
            | 
           556 | 
                   // Always prepare the mandatorydatasets present in the answers.
  | 
        
        
            | 
            | 
           557 | 
                   // The $options are not used here.
  | 
        
        
            | 
            | 
           558 | 
                   foreach ($mandatorydatasets as $datasetname) {
  | 
        
        
            | 
            | 
           559 | 
                       if (!isset($datasets[$datasetname])) {
  | 
        
        
            | 
            | 
           560 | 
                           list($options, $selected) =
  | 
        
        
            | 
            | 
           561 | 
                               $this->dataset_options($form, $datasetname);
  | 
        
        
            | 
            | 
           562 | 
                           $datasets[$datasetname] = '';
  | 
        
        
            | 
            | 
           563 | 
                           $form->dataset[$key] = $selected;
  | 
        
        
            | 
            | 
           564 | 
                           $key++;
  | 
        
        
            | 
            | 
           565 | 
                       }
  | 
        
        
            | 
            | 
           566 | 
                   }
  | 
        
        
            | 
            | 
           567 | 
                   // Do not prepare possibledatasets when creating a question.
  | 
        
        
            | 
            | 
           568 | 
                   // They will defined and stored with datasetdefinitions_form.php.
  | 
        
        
            | 
            | 
           569 | 
                   // The $options are not used here.
  | 
        
        
            | 
            | 
           570 | 
                   if ($questionfromid != '0') {
  | 
        
        
            | 
            | 
           571 | 
              | 
        
        
            | 
            | 
           572 | 
                       foreach ($possibledatasets as $datasetname) {
  | 
        
        
            | 
            | 
           573 | 
                           if (!isset($datasets[$datasetname])) {
  | 
        
        
            | 
            | 
           574 | 
                               list($options, $selected) =
  | 
        
        
            | 
            | 
           575 | 
                                   $this->dataset_options($form, $datasetname, false);
  | 
        
        
            | 
            | 
           576 | 
                               $datasets[$datasetname] = '';
  | 
        
        
            | 
            | 
           577 | 
                               $form->dataset[$key] = $selected;
  | 
        
        
            | 
            | 
           578 | 
                               $key++;
  | 
        
        
            | 
            | 
           579 | 
                           }
  | 
        
        
            | 
            | 
           580 | 
                       }
  | 
        
        
            | 
            | 
           581 | 
                   }
  | 
        
        
            | 
            | 
           582 | 
                   return $datasets;
  | 
        
        
            | 
            | 
           583 | 
               }
  | 
        
        
            | 
            | 
           584 | 
               public function addnamecategory(&$question) {
  | 
        
        
            | 
            | 
           585 | 
                   global $DB;
  | 
        
        
            | 
            | 
           586 | 
                   $categorydatasetdefs = $DB->get_records_sql(
  | 
        
        
            | 
            | 
           587 | 
                       "SELECT  a.*
  | 
        
        
            | 
            | 
           588 | 
                          FROM {question_datasets} b, {question_dataset_definitions} a
  | 
        
        
            | 
            | 
           589 | 
                         WHERE a.id = b.datasetdefinition
  | 
        
        
            | 
            | 
           590 | 
                           AND a.type = '1'
  | 
        
        
            | 
            | 
           591 | 
                           AND a.category != 0
  | 
        
        
            | 
            | 
           592 | 
                           AND b.question = ?
  | 
        
        
            | 
            | 
           593 | 
                      ORDER BY a.name ", [$question->id]);
  | 
        
        
            | 
            | 
           594 | 
                   $questionname = $this->clean_technical_prefix_from_question_name($question->name);
  | 
        
        
            | 
            | 
           595 | 
              | 
        
        
            | 
            | 
           596 | 
                   if (!empty($categorydatasetdefs)) {
  | 
        
        
            | 
            | 
           597 | 
                       // There is at least one with the same name.
  | 
        
        
            | 
            | 
           598 | 
                       $questionname = '#' . $questionname;
  | 
        
        
            | 
            | 
           599 | 
                       foreach ($categorydatasetdefs as $def) {
  | 
        
        
            | 
            | 
           600 | 
                           if (strlen($def->name) + strlen($questionname) < 250) {
  | 
        
        
            | 
            | 
           601 | 
                               $questionname = '{' . $def->name . '}' . $questionname;
  | 
        
        
            | 
            | 
           602 | 
                           }
  | 
        
        
            | 
            | 
           603 | 
                       }
  | 
        
        
            | 
            | 
           604 | 
                       $questionname = '#' . $questionname;
  | 
        
        
            | 
            | 
           605 | 
                   }
  | 
        
        
            | 
            | 
           606 | 
                   $DB->set_field('question', 'name', $questionname, ['id' => $question->id]);
  | 
        
        
            | 
            | 
           607 | 
               }
  | 
        
        
            | 
            | 
           608 | 
              | 
        
        
            | 
            | 
           609 | 
               /**
  | 
        
        
            | 
            | 
           610 | 
                * this version save the available data at the different steps of the question editing process
  | 
        
        
            | 
            | 
           611 | 
                * without using global $SESSION as storage between steps
  | 
        
        
            | 
            | 
           612 | 
                * at the first step $wizardnow = 'question'
  | 
        
        
            | 
            | 
           613 | 
                *  when creating a new question
  | 
        
        
            | 
            | 
           614 | 
                *  when modifying a question
  | 
        
        
            | 
            | 
           615 | 
                *  when copying as a new question
  | 
        
        
            | 
            | 
           616 | 
                *  the general parameters and answers are saved using parent::save_question
  | 
        
        
            | 
            | 
           617 | 
                *  then the datasets are prepared and saved
  | 
        
        
            | 
            | 
           618 | 
                * at the second step $wizardnow = 'datasetdefinitions'
  | 
        
        
            | 
            | 
           619 | 
                *  the datadefs final type are defined as private, category or not a datadef
  | 
        
        
            | 
            | 
           620 | 
                * at the third step $wizardnow = 'datasetitems'
  | 
        
        
            | 
            | 
           621 | 
                *  the datadefs parameters and the data items are created or defined
  | 
        
        
            | 
            | 
           622 | 
                *
  | 
        
        
            | 
            | 
           623 | 
                * @param object question
  | 
        
        
            | 
            | 
           624 | 
                * @param object $form
  | 
        
        
            | 
            | 
           625 | 
                * @param int $course
  | 
        
        
            | 
            | 
           626 | 
                * @param PARAM_ALPHA $wizardnow should be added as we are coming from question2.php
  | 
        
        
            | 
            | 
           627 | 
                */
  | 
        
        
            | 
            | 
           628 | 
               public function save_question($question, $form) {
  | 
        
        
            | 
            | 
           629 | 
                   global $DB;
  | 
        
        
            | 
            | 
           630 | 
              | 
        
        
            | 
            | 
           631 | 
                   if ($this->wizardpagesnumber() == 1 || $question->qtype == 'calculatedsimple') {
  | 
        
        
            | 
            | 
           632 | 
                       $question = parent::save_question($question, $form);
  | 
        
        
            | 
            | 
           633 | 
                       return $question;
  | 
        
        
            | 
            | 
           634 | 
                   }
  | 
        
        
            | 
            | 
           635 | 
              | 
        
        
            | 
            | 
           636 | 
                   $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA);
  | 
        
        
            | 
            | 
           637 | 
                   $id = optional_param('id', 0, PARAM_INT); // Question id.
  | 
        
        
            | 
            | 
           638 | 
                   // In case 'question':
  | 
        
        
            | 
            | 
           639 | 
                   // For a new question $form->id is empty
  | 
        
        
            | 
            | 
           640 | 
                   // when saving as new question.
  | 
        
        
            | 
            | 
           641 | 
                   // The $question->id = 0, $form is $data from question2.php
  | 
        
        
            | 
            | 
           642 | 
                   // and $data->makecopy is defined as $data->id is the initial question id.
  | 
        
        
            | 
            | 
           643 | 
                   // Edit case. If it is a new question we don't necessarily need to
  | 
        
        
            | 
            | 
           644 | 
                   // return a valid question object.
  | 
        
        
            | 
            | 
           645 | 
              | 
        
        
            | 
            | 
           646 | 
                   // See where we're coming from.
  | 
        
        
            | 
            | 
           647 | 
                   switch($wizardnow) {
  | 
        
        
            | 
            | 
           648 | 
                       case '' :
  | 
        
        
            | 
            | 
           649 | 
                       case 'question': // Coming from the first page, creating the second.
  | 
        
        
            | 
            | 
           650 | 
                           if (empty($form->id)) { // Or a new question $form->id is empty.
  | 
        
        
            | 
            | 
           651 | 
                               $question = parent::save_question($question, $form);
  | 
        
        
            | 
            | 
           652 | 
                               // Prepare the datasets using default $questionfromid.
  | 
        
        
            | 
            | 
           653 | 
                               $this->preparedatasets($form);
  | 
        
        
            | 
            | 
           654 | 
                               $form->id = $question->id;
  | 
        
        
            | 
            | 
           655 | 
                               $this->save_dataset_definitions($form);
  | 
        
        
            | 
            | 
           656 | 
                               if (isset($form->synchronize) && $form->synchronize == 2) {
  | 
        
        
            | 
            | 
           657 | 
                                   $this->addnamecategory($question);
  | 
        
        
            | 
            | 
           658 | 
                               }
  | 
        
        
            | 
            | 
           659 | 
                           } else {
  | 
        
        
            | 
            | 
           660 | 
                               $questionfromid = $form->id;
  | 
        
        
            | 
            | 
           661 | 
                               $question = parent::save_question($question, $form);
  | 
        
        
            | 
            | 
           662 | 
                               // Prepare the datasets.
  | 
        
        
            | 
            | 
           663 | 
                               $this->preparedatasets($form, $questionfromid);
  | 
        
        
            | 
            | 
           664 | 
                               $form->id = $question->id;
  | 
        
        
            | 
            | 
           665 | 
                               $this->save_as_new_dataset_definitions($form, $questionfromid);
  | 
        
        
            | 
            | 
           666 | 
                               if (isset($form->synchronize) && $form->synchronize == 2) {
  | 
        
        
            | 
            | 
           667 | 
                                   $this->addnamecategory($question);
  | 
        
        
            | 
            | 
           668 | 
                               }
  | 
        
        
            | 
            | 
           669 | 
                           }
  | 
        
        
            | 
            | 
           670 | 
                           break;
  | 
        
        
            | 
            | 
           671 | 
                       case 'datasetdefinitions':
  | 
        
        
            | 
            | 
           672 | 
                           // Calculated options.
  | 
        
        
            | 
            | 
           673 | 
                           // It cannot go here without having done the first page,
  | 
        
        
            | 
            | 
           674 | 
                           // so the question_calculated_options should exist.
  | 
        
        
            | 
            | 
           675 | 
                           // We only need to update the synchronize field.
  | 
        
        
            | 
            | 
           676 | 
                           if (isset($form->synchronize)) {
  | 
        
        
            | 
            | 
           677 | 
                               $optionssynchronize = $form->synchronize;
  | 
        
        
            | 
            | 
           678 | 
                           } else {
  | 
        
        
            | 
            | 
           679 | 
                               $optionssynchronize = 0;
  | 
        
        
            | 
            | 
           680 | 
                           }
  | 
        
        
            | 
            | 
           681 | 
                           $DB->set_field('question_calculated_options', 'synchronize', $optionssynchronize,
  | 
        
        
            | 
            | 
           682 | 
                                   ['question' => $question->id]);
  | 
        
        
            | 
            | 
           683 | 
                           if (isset($form->synchronize) && $form->synchronize == 2) {
  | 
        
        
            | 
            | 
           684 | 
                               $this->addnamecategory($question);
  | 
        
        
            | 
            | 
           685 | 
                           }
  | 
        
        
            | 
            | 
           686 | 
              | 
        
        
            | 
            | 
           687 | 
                           $this->save_dataset_definitions($form);
  | 
        
        
            | 
            | 
           688 | 
                           break;
  | 
        
        
            | 
            | 
           689 | 
                       case 'datasetitems':
  | 
        
        
            | 
            | 
           690 | 
                           $this->save_dataset_items($question, $form);
  | 
        
        
            | 
            | 
           691 | 
                           $this->save_question_calculated($question, $form);
  | 
        
        
            | 
            | 
           692 | 
                           break;
  | 
        
        
            | 
            | 
           693 | 
                       default:
  | 
        
        
            | 
            | 
           694 | 
                           throw new \moodle_exception('invalidwizardpage', 'question');
  | 
        
        
            | 
            | 
           695 | 
                           break;
  | 
        
        
            | 
            | 
           696 | 
                   }
  | 
        
        
            | 
            | 
           697 | 
                   return $question;
  | 
        
        
            | 
            | 
           698 | 
               }
  | 
        
        
            | 
            | 
           699 | 
              | 
        
        
            | 
            | 
           700 | 
               public function delete_question($questionid, $contextid) {
  | 
        
        
            | 
            | 
           701 | 
                   global $DB;
  | 
        
        
            | 
            | 
           702 | 
              | 
        
        
            | 
            | 
           703 | 
                   $DB->delete_records('question_calculated', ['question' => $questionid]);
  | 
        
        
            | 
            | 
           704 | 
                   $DB->delete_records('question_calculated_options', ['question' => $questionid]);
  | 
        
        
            | 
            | 
           705 | 
                   $DB->delete_records('question_numerical_units', ['question' => $questionid]);
  | 
        
        
            | 
            | 
           706 | 
                   if ($datasets = $DB->get_records('question_datasets', ['question' => $questionid])) {
  | 
        
        
            | 
            | 
           707 | 
                       foreach ($datasets as $dataset) {
  | 
        
        
            | 
            | 
           708 | 
                           if (!$DB->get_records_select('question_datasets',
  | 
        
        
            | 
            | 
           709 | 
                                   "question != ? AND datasetdefinition = ? ",
  | 
        
        
            | 
            | 
           710 | 
                                   [$questionid, $dataset->datasetdefinition])) {
  | 
        
        
            | 
            | 
           711 | 
                               $DB->delete_records('question_dataset_definitions',
  | 
        
        
            | 
            | 
           712 | 
                                       ['id' => $dataset->datasetdefinition]);
  | 
        
        
            | 
            | 
           713 | 
                               $DB->delete_records('question_dataset_items',
  | 
        
        
            | 
            | 
           714 | 
                                       ['definition' => $dataset->datasetdefinition]);
  | 
        
        
            | 
            | 
           715 | 
                           }
  | 
        
        
            | 
            | 
           716 | 
                       }
  | 
        
        
            | 
            | 
           717 | 
                   }
  | 
        
        
            | 
            | 
           718 | 
                   $DB->delete_records('question_datasets', ['question' => $questionid]);
  | 
        
        
            | 
            | 
           719 | 
              | 
        
        
            | 
            | 
           720 | 
                   parent::delete_question($questionid, $contextid);
  | 
        
        
            | 
            | 
           721 | 
               }
  | 
        
        
            | 
            | 
           722 | 
              | 
        
        
            | 
            | 
           723 | 
               public function get_random_guess_score($questiondata) {
  | 
        
        
            | 
            | 
           724 | 
                   foreach ($questiondata->options->answers as $aid => $answer) {
  | 
        
        
            | 
            | 
           725 | 
                       if ('*' == trim($answer->answer)) {
  | 
        
        
            | 
            | 
           726 | 
                           return max($answer->fraction - $questiondata->options->unitpenalty, 0);
  | 
        
        
            | 
            | 
           727 | 
                       }
  | 
        
        
            | 
            | 
           728 | 
                   }
  | 
        
        
            | 
            | 
           729 | 
                   return 0;
  | 
        
        
            | 
            | 
           730 | 
               }
  | 
        
        
            | 
            | 
           731 | 
              | 
        
        
            | 
            | 
           732 | 
               public function supports_dataset_item_generation() {
  | 
        
        
            | 
            | 
           733 | 
                   // Calculated support generation of randomly distributed number data.
  | 
        
        
            | 
            | 
           734 | 
                   return true;
  | 
        
        
            | 
            | 
           735 | 
               }
  | 
        
        
            | 
            | 
           736 | 
              | 
        
        
            | 
            | 
           737 | 
               public function custom_generator_tools_part($mform, $idx, $j) {
  | 
        
        
            | 
            | 
           738 | 
              | 
        
        
            | 
            | 
           739 | 
                   $minmaxgrp = [];
  | 
        
        
            | 
            | 
           740 | 
                   $minmaxgrp[] = $mform->createElement('float', "calcmin[{$idx}]",
  | 
        
        
            | 
            | 
           741 | 
                           get_string('calcmin', 'qtype_calculated'));
  | 
        
        
            | 
            | 
           742 | 
                   $minmaxgrp[] = $mform->createElement('float', "calcmax[{$idx}]",
  | 
        
        
            | 
            | 
           743 | 
                           get_string('calcmax', 'qtype_calculated'));
  | 
        
        
            | 
            | 
           744 | 
                   $mform->addGroup($minmaxgrp, 'minmaxgrp',
  | 
        
        
            | 
            | 
           745 | 
                           get_string('minmax', 'qtype_calculated'), ' - ', false);
  | 
        
        
            | 
            | 
           746 | 
              | 
        
        
            | 
            | 
           747 | 
                   $precisionoptions = range(0, 10);
  | 
        
        
            | 
            | 
           748 | 
                   $mform->addElement('select', "calclength[{$idx}]",
  | 
        
        
            | 
            | 
           749 | 
                           get_string('calclength', 'qtype_calculated'), $precisionoptions);
  | 
        
        
            | 
            | 
           750 | 
              | 
        
        
            | 
            | 
           751 | 
                   $distriboptions = ['uniform' => get_string('uniform', 'qtype_calculated'),
  | 
        
        
            | 
            | 
           752 | 
                           'loguniform' => get_string('loguniform', 'qtype_calculated')];
  | 
        
        
            | 
            | 
           753 | 
                   $mform->addElement('select', "calcdistribution[{$idx}]",
  | 
        
        
            | 
            | 
           754 | 
                           get_string('calcdistribution', 'qtype_calculated'), $distriboptions);
  | 
        
        
            | 
            | 
           755 | 
               }
  | 
        
        
            | 
            | 
           756 | 
              | 
        
        
            | 
            | 
           757 | 
               public function custom_generator_set_data($datasetdefs, $formdata) {
  | 
        
        
            | 
            | 
           758 | 
                   $idx = 1;
  | 
        
        
            | 
            | 
           759 | 
                   foreach ($datasetdefs as $datasetdef) {
  | 
        
        
            | 
            | 
           760 | 
                       if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
  | 
        
        
            | 
            | 
           761 | 
                               $datasetdef->options, $regs)) {
  | 
        
        
            | 
            | 
           762 | 
                           $formdata["calcdistribution[{$idx}]"] = $regs[1];
  | 
        
        
            | 
            | 
           763 | 
                           $formdata["calcmin[{$idx}]"] = $regs[2];
  | 
        
        
            | 
            | 
           764 | 
                           $formdata["calcmax[{$idx}]"] = $regs[3];
  | 
        
        
            | 
            | 
           765 | 
                           $formdata["calclength[{$idx}]"] = $regs[4];
  | 
        
        
            | 
            | 
           766 | 
                       }
  | 
        
        
            | 
            | 
           767 | 
                       $idx++;
  | 
        
        
            | 
            | 
           768 | 
                   }
  | 
        
        
            | 
            | 
           769 | 
                   return $formdata;
  | 
        
        
            | 
            | 
           770 | 
               }
  | 
        
        
            | 
            | 
           771 | 
              | 
        
        
            | 
            | 
           772 | 
               public function custom_generator_tools($datasetdef) {
  | 
        
        
            | 
            | 
           773 | 
                   global $OUTPUT;
  | 
        
        
            | 
            | 
           774 | 
                   if (preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
  | 
        
        
            | 
            | 
           775 | 
                           $datasetdef->options, $regs)) {
  | 
        
        
            | 
            | 
           776 | 
                       $defid = "{$datasetdef->type}-{$datasetdef->category}-{$datasetdef->name}";
  | 
        
        
            | 
            | 
           777 | 
                       for ($i = 0; $i < 10; ++$i) {
  | 
        
        
            | 
            | 
           778 | 
                           $lengthoptions[$i] = get_string(($regs[1] == 'uniform'
  | 
        
        
            | 
            | 
           779 | 
                               ? 'decimals'
  | 
        
        
            | 
            | 
           780 | 
                               : 'significantfigures'), 'qtype_calculated', $i);
  | 
        
        
            | 
            | 
           781 | 
                       }
  | 
        
        
            | 
            | 
           782 | 
                       $menu1 = html_writer::label(get_string('lengthoption', 'qtype_calculated'),
  | 
        
        
            | 
            | 
           783 | 
                           'menucalclength', false, ['class' => 'accesshide']);
  | 
        
        
           | 1441 | 
           ariadna | 
           784 | 
                       $menu1 .= html_writer::select($lengthoptions, 'calclength[]', $regs[4], null, ['class' => 'form-select']);
  | 
        
        
           | 1 | 
           efrain | 
           785 | 
              | 
        
        
            | 
            | 
           786 | 
                       $options = ['uniform' => get_string('uniformbit', 'qtype_calculated'),
  | 
        
        
            | 
            | 
           787 | 
                           'loguniform' => get_string('loguniformbit', 'qtype_calculated')];
  | 
        
        
            | 
            | 
           788 | 
                       $menu2 = html_writer::label(get_string('distributionoption', 'qtype_calculated'),
  | 
        
        
            | 
            | 
           789 | 
                           'menucalcdistribution', false, ['class' => 'accesshide']);
  | 
        
        
           | 1441 | 
           ariadna | 
           790 | 
                       $menu2 .= html_writer::select($options, 'calcdistribution[]', $regs[1], null, ['class' => 'form-select']);
  | 
        
        
           | 1 | 
           efrain | 
           791 | 
                       return '<input type="submit" class="btn btn-secondary" onclick="'
  | 
        
        
            | 
            | 
           792 | 
                           . "getElementById('addform').regenerateddefid.value='{$defid}'; return true;"
  | 
        
        
            | 
            | 
           793 | 
                           .'" value="'. get_string('generatevalue', 'qtype_calculated') . '"/><br/>'
  | 
        
        
            | 
            | 
           794 | 
                           . '<input type="text" class="form-control" size="3" name="calcmin[]" '
  | 
        
        
            | 
            | 
           795 | 
                           . " value=\"{$regs[2]}\"/> & <input name=\"calcmax[]\" "
  | 
        
        
            | 
            | 
           796 | 
                           . ' type="text" class="form-control" size="3" value="' . $regs[3] .'"/> '
  | 
        
        
            | 
            | 
           797 | 
                           . $menu1 . '<br/>'
  | 
        
        
            | 
            | 
           798 | 
                           . $menu2;
  | 
        
        
            | 
            | 
           799 | 
                   } else {
  | 
        
        
            | 
            | 
           800 | 
                       return '';
  | 
        
        
            | 
            | 
           801 | 
                   }
  | 
        
        
            | 
            | 
           802 | 
               }
  | 
        
        
            | 
            | 
           803 | 
              | 
        
        
            | 
            | 
           804 | 
              | 
        
        
            | 
            | 
           805 | 
               public function update_dataset_options($datasetdefs, $form) {
  | 
        
        
            | 
            | 
           806 | 
                   global $OUTPUT;
  | 
        
        
            | 
            | 
           807 | 
                   // Do we have information about new options ?
  | 
        
        
            | 
            | 
           808 | 
                   if (empty($form->definition) || empty($form->calcmin)
  | 
        
        
            | 
            | 
           809 | 
                           ||empty($form->calcmax) || empty($form->calclength)
  | 
        
        
            | 
            | 
           810 | 
                           || empty($form->calcdistribution)) {
  | 
        
        
            | 
            | 
           811 | 
                       // I guess not.
  | 
        
        
            | 
            | 
           812 | 
              | 
        
        
            | 
            | 
           813 | 
                   } else {
  | 
        
        
            | 
            | 
           814 | 
                       // Looks like we just could have some new information here.
  | 
        
        
            | 
            | 
           815 | 
                       $uniquedefs = array_values(array_unique($form->definition));
  | 
        
        
            | 
            | 
           816 | 
                       foreach ($uniquedefs as $key => $defid) {
  | 
        
        
            | 
            | 
           817 | 
                           if (isset($datasetdefs[$defid])
  | 
        
        
            | 
            | 
           818 | 
                                   && is_numeric($form->calcmin[$key + 1])
  | 
        
        
            | 
            | 
           819 | 
                                   && is_numeric($form->calcmax[$key + 1])
  | 
        
        
            | 
            | 
           820 | 
                                   && is_numeric($form->calclength[$key + 1])) {
  | 
        
        
            | 
            | 
           821 | 
                               switch     ($form->calcdistribution[$key + 1]) {
  | 
        
        
            | 
            | 
           822 | 
                                   case 'uniform': case 'loguniform':
  | 
        
        
            | 
            | 
           823 | 
                                           $datasetdefs[$defid]->options =
  | 
        
        
            | 
            | 
           824 | 
                                           $form->calcdistribution[$key + 1] . ':'
  | 
        
        
            | 
            | 
           825 | 
                                           . $form->calcmin[$key + 1] . ':'
  | 
        
        
            | 
            | 
           826 | 
                                           . $form->calcmax[$key + 1] . ':'
  | 
        
        
            | 
            | 
           827 | 
                                           . $form->calclength[$key + 1];
  | 
        
        
            | 
            | 
           828 | 
                                       break;
  | 
        
        
            | 
            | 
           829 | 
                                   default:
  | 
        
        
            | 
            | 
           830 | 
                                       echo $OUTPUT->notification(
  | 
        
        
            | 
            | 
           831 | 
                                               "Unexpected distribution ".$form->calcdistribution[$key + 1]);
  | 
        
        
            | 
            | 
           832 | 
                               }
  | 
        
        
            | 
            | 
           833 | 
                           }
  | 
        
        
            | 
            | 
           834 | 
                       }
  | 
        
        
            | 
            | 
           835 | 
                   }
  | 
        
        
            | 
            | 
           836 | 
              | 
        
        
            | 
            | 
           837 | 
                   // Look for empty options, on which we set default values.
  | 
        
        
            | 
            | 
           838 | 
                   foreach ($datasetdefs as $defid => $def) {
  | 
        
        
            | 
            | 
           839 | 
                       if (empty($def->options)) {
  | 
        
        
            | 
            | 
           840 | 
                           $datasetdefs[$defid]->options = 'uniform:1.0:10.0:1';
  | 
        
        
            | 
            | 
           841 | 
                       }
  | 
        
        
            | 
            | 
           842 | 
                   }
  | 
        
        
            | 
            | 
           843 | 
                   return $datasetdefs;
  | 
        
        
            | 
            | 
           844 | 
               }
  | 
        
        
            | 
            | 
           845 | 
              | 
        
        
            | 
            | 
           846 | 
               public function save_question_calculated($question, $fromform) {
  | 
        
        
            | 
            | 
           847 | 
                   global $DB;
  | 
        
        
            | 
            | 
           848 | 
              | 
        
        
            | 
            | 
           849 | 
                   foreach ($question->options->answers as $key => $answer) {
  | 
        
        
            | 
            | 
           850 | 
                       if ($options = $DB->get_record('question_calculated', ['answer' => $key])) {
  | 
        
        
            | 
            | 
           851 | 
                           $options->tolerance = trim($fromform->tolerance[$key]);
  | 
        
        
            | 
            | 
           852 | 
                           $options->tolerancetype  = trim($fromform->tolerancetype[$key]);
  | 
        
        
            | 
            | 
           853 | 
                           $options->correctanswerlength  = trim($fromform->correctanswerlength[$key]);
  | 
        
        
            | 
            | 
           854 | 
                           $options->correctanswerformat  = trim($fromform->correctanswerformat[$key]);
  | 
        
        
            | 
            | 
           855 | 
                           $DB->update_record('question_calculated', $options);
  | 
        
        
            | 
            | 
           856 | 
                       }
  | 
        
        
            | 
            | 
           857 | 
                   }
  | 
        
        
            | 
            | 
           858 | 
               }
  | 
        
        
            | 
            | 
           859 | 
              | 
        
        
            | 
            | 
           860 | 
               /**
  | 
        
        
            | 
            | 
           861 | 
                * This function get the dataset items using id as unique parameter and return an
  | 
        
        
            | 
            | 
           862 | 
                * array with itemnumber as index sorted ascendant
  | 
        
        
            | 
            | 
           863 | 
                * If the multiple records with the same itemnumber exist, only the newest one
  | 
        
        
            | 
            | 
           864 | 
                * i.e with the greatest id is used, the others are ignored but not deleted.
  | 
        
        
            | 
            | 
           865 | 
                * MDL-19210
  | 
        
        
            | 
            | 
           866 | 
                */
  | 
        
        
            | 
            | 
           867 | 
               public function get_database_dataset_items($definition) {
  | 
        
        
            | 
            | 
           868 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           869 | 
                   $databasedataitems = $DB->get_records_sql( // Hint: Use the number as a key.
  | 
        
        
            | 
            | 
           870 | 
                       " SELECT id , itemnumber, definition,  value
  | 
        
        
            | 
            | 
           871 | 
                       FROM {question_dataset_items}
  | 
        
        
            | 
            | 
           872 | 
                       WHERE definition = $definition order by id DESC ", [$definition]);
  | 
        
        
            | 
            | 
           873 | 
                   $dataitems = [];
  | 
        
        
            | 
            | 
           874 | 
                   foreach ($databasedataitems as $id => $dataitem) {
  | 
        
        
            | 
            | 
           875 | 
                       if (!isset($dataitems[$dataitem->itemnumber])) {
  | 
        
        
            | 
            | 
           876 | 
                           $dataitems[$dataitem->itemnumber] = $dataitem;
  | 
        
        
            | 
            | 
           877 | 
                       }
  | 
        
        
            | 
            | 
           878 | 
                   }
  | 
        
        
            | 
            | 
           879 | 
                   ksort($dataitems);
  | 
        
        
            | 
            | 
           880 | 
                   return $dataitems;
  | 
        
        
            | 
            | 
           881 | 
               }
  | 
        
        
            | 
            | 
           882 | 
              | 
        
        
            | 
            | 
           883 | 
               public function save_dataset_items($question, $fromform) {
  | 
        
        
            | 
            | 
           884 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           885 | 
                   $synchronize = false;
  | 
        
        
            | 
            | 
           886 | 
                   if (isset($fromform->nextpageparam['forceregeneration'])) {
  | 
        
        
            | 
            | 
           887 | 
                       $regenerate = $fromform->nextpageparam['forceregeneration'];
  | 
        
        
            | 
            | 
           888 | 
                   } else {
  | 
        
        
            | 
            | 
           889 | 
                       $regenerate = 0;
  | 
        
        
            | 
            | 
           890 | 
                   }
  | 
        
        
            | 
            | 
           891 | 
                   if (empty($question->options)) {
  | 
        
        
            | 
            | 
           892 | 
                       $this->get_question_options($question);
  | 
        
        
            | 
            | 
           893 | 
                   }
  | 
        
        
            | 
            | 
           894 | 
                   if (!empty($question->options->synchronize)) {
  | 
        
        
            | 
            | 
           895 | 
                       $synchronize = true;
  | 
        
        
            | 
            | 
           896 | 
                   }
  | 
        
        
            | 
            | 
           897 | 
              | 
        
        
            | 
            | 
           898 | 
                   // Get the old datasets for this question.
  | 
        
        
            | 
            | 
           899 | 
                   $datasetdefs = $this->get_dataset_definitions($question->id, []);
  | 
        
        
            | 
            | 
           900 | 
                   // Handle generator options...
  | 
        
        
            | 
            | 
           901 | 
                   $olddatasetdefs = fullclone($datasetdefs);
  | 
        
        
            | 
            | 
           902 | 
                   $datasetdefs = $this->update_dataset_options($datasetdefs, $fromform);
  | 
        
        
            | 
            | 
           903 | 
                   $maxnumber = -1;
  | 
        
        
            | 
            | 
           904 | 
                   foreach ($datasetdefs as $defid => $datasetdef) {
  | 
        
        
            | 
            | 
           905 | 
                       if (isset($datasetdef->id)
  | 
        
        
            | 
            | 
           906 | 
                               && $datasetdef->options != $olddatasetdefs[$defid]->options) {
  | 
        
        
            | 
            | 
           907 | 
                           // Save the new value for options.
  | 
        
        
            | 
            | 
           908 | 
                           $DB->update_record('question_dataset_definitions', $datasetdef);
  | 
        
        
            | 
            | 
           909 | 
              | 
        
        
            | 
            | 
           910 | 
                       }
  | 
        
        
            | 
            | 
           911 | 
                       // Get maxnumber.
  | 
        
        
            | 
            | 
           912 | 
                       if ($maxnumber == -1 || $datasetdef->itemcount < $maxnumber) {
  | 
        
        
            | 
            | 
           913 | 
                           $maxnumber = $datasetdef->itemcount;
  | 
        
        
            | 
            | 
           914 | 
                       }
  | 
        
        
            | 
            | 
           915 | 
                   }
  | 
        
        
            | 
            | 
           916 | 
                   // Handle adding and removing of dataset items.
  | 
        
        
            | 
            | 
           917 | 
                   $i = 1;
  | 
        
        
            | 
            | 
           918 | 
                   if ($maxnumber > self::MAX_DATASET_ITEMS) {
  | 
        
        
            | 
            | 
           919 | 
                       $maxnumber = self::MAX_DATASET_ITEMS;
  | 
        
        
            | 
            | 
           920 | 
                   }
  | 
        
        
            | 
            | 
           921 | 
              | 
        
        
            | 
            | 
           922 | 
                   ksort($fromform->definition);
  | 
        
        
            | 
            | 
           923 | 
                   foreach ($fromform->definition as $key => $defid) {
  | 
        
        
            | 
            | 
           924 | 
                       // If the delete button has not been pressed then skip the datasetitems
  | 
        
        
            | 
            | 
           925 | 
                       // in the 'add item' part of the form.
  | 
        
        
            | 
            | 
           926 | 
                       if ($i > count($datasetdefs) * $maxnumber) {
  | 
        
        
            | 
            | 
           927 | 
                           break;
  | 
        
        
            | 
            | 
           928 | 
                       }
  | 
        
        
            | 
            | 
           929 | 
                       $addeditem = new stdClass();
  | 
        
        
            | 
            | 
           930 | 
                       $addeditem->definition = $datasetdefs[$defid]->id;
  | 
        
        
            | 
            | 
           931 | 
                       $addeditem->value = $fromform->number[$i];
  | 
        
        
            | 
            | 
           932 | 
                       $addeditem->itemnumber = ceil($i / count($datasetdefs));
  | 
        
        
            | 
            | 
           933 | 
              | 
        
        
            | 
            | 
           934 | 
                       if ($fromform->itemid[$i]) {
  | 
        
        
            | 
            | 
           935 | 
                           // Reuse any previously used record.
  | 
        
        
            | 
            | 
           936 | 
                           $addeditem->id = $fromform->itemid[$i];
  | 
        
        
            | 
            | 
           937 | 
                           $DB->update_record('question_dataset_items', $addeditem);
  | 
        
        
            | 
            | 
           938 | 
                       } else {
  | 
        
        
            | 
            | 
           939 | 
                           $DB->insert_record('question_dataset_items', $addeditem);
  | 
        
        
            | 
            | 
           940 | 
                       }
  | 
        
        
            | 
            | 
           941 | 
              | 
        
        
            | 
            | 
           942 | 
                       $i++;
  | 
        
        
            | 
            | 
           943 | 
                   }
  | 
        
        
            | 
            | 
           944 | 
                   if (isset($addeditem->itemnumber) && $maxnumber < $addeditem->itemnumber
  | 
        
        
            | 
            | 
           945 | 
                           && $addeditem->itemnumber < self::MAX_DATASET_ITEMS) {
  | 
        
        
            | 
            | 
           946 | 
                       $maxnumber = $addeditem->itemnumber;
  | 
        
        
            | 
            | 
           947 | 
                       foreach ($datasetdefs as $key => $newdef) {
  | 
        
        
            | 
            | 
           948 | 
                           if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
  | 
        
        
            | 
            | 
           949 | 
                               $newdef->itemcount = $maxnumber;
  | 
        
        
            | 
            | 
           950 | 
                               // Save the new value for options.
  | 
        
        
            | 
            | 
           951 | 
                               $DB->update_record('question_dataset_definitions', $newdef);
  | 
        
        
            | 
            | 
           952 | 
                           }
  | 
        
        
            | 
            | 
           953 | 
                       }
  | 
        
        
            | 
            | 
           954 | 
                   }
  | 
        
        
            | 
            | 
           955 | 
                   // Adding supplementary items.
  | 
        
        
            | 
            | 
           956 | 
                   $numbertoadd = 0;
  | 
        
        
            | 
            | 
           957 | 
                   if (isset($fromform->addbutton) && $fromform->selectadd > 0 &&
  | 
        
        
            | 
            | 
           958 | 
                           $maxnumber < self::MAX_DATASET_ITEMS) {
  | 
        
        
            | 
            | 
           959 | 
                       $numbertoadd = $fromform->selectadd;
  | 
        
        
            | 
            | 
           960 | 
                       if (self::MAX_DATASET_ITEMS - $maxnumber < $numbertoadd) {
  | 
        
        
            | 
            | 
           961 | 
                           $numbertoadd = self::MAX_DATASET_ITEMS - $maxnumber;
  | 
        
        
            | 
            | 
           962 | 
                       }
  | 
        
        
            | 
            | 
           963 | 
                       // Add the other items.
  | 
        
        
            | 
            | 
           964 | 
                       // Generate a new dataset item (or reuse an old one).
  | 
        
        
            | 
            | 
           965 | 
                       foreach ($datasetdefs as $defid => $datasetdef) {
  | 
        
        
            | 
            | 
           966 | 
                           // In case that for category datasets some new items has been added,
  | 
        
        
            | 
            | 
           967 | 
                           // get actual values.
  | 
        
        
            | 
            | 
           968 | 
                           // Fix regenerate for this datadefs.
  | 
        
        
            | 
            | 
           969 | 
                           $defregenerate = 0;
  | 
        
        
            | 
            | 
           970 | 
                           if ($synchronize &&
  | 
        
        
            | 
            | 
           971 | 
                                   !empty ($fromform->nextpageparam["datasetregenerate[{$datasetdef->name}"])) {
  | 
        
        
            | 
            | 
           972 | 
                               $defregenerate = 1;
  | 
        
        
            | 
            | 
           973 | 
                           } else if (!$synchronize &&
  | 
        
        
            | 
            | 
           974 | 
                                   (($regenerate == 1 && $datasetdef->category == 0) ||$regenerate == 2)) {
  | 
        
        
            | 
            | 
           975 | 
                               $defregenerate = 1;
  | 
        
        
            | 
            | 
           976 | 
                           }
  | 
        
        
            | 
            | 
           977 | 
                           if (isset($datasetdef->id)) {
  | 
        
        
            | 
            | 
           978 | 
                               $datasetdefs[$defid]->items =
  | 
        
        
            | 
            | 
           979 | 
                                       $this->get_database_dataset_items($datasetdef->id);
  | 
        
        
            | 
            | 
           980 | 
                           }
  | 
        
        
            | 
            | 
           981 | 
                           for ($numberadded = $maxnumber + 1; $numberadded <= $maxnumber + $numbertoadd; $numberadded++) {
  | 
        
        
            | 
            | 
           982 | 
                               if (isset($datasetdefs[$defid]->items[$numberadded])) {
  | 
        
        
            | 
            | 
           983 | 
                                   // In case of regenerate it modifies the already existing record.
  | 
        
        
            | 
            | 
           984 | 
                                   if ($defregenerate) {
  | 
        
        
            | 
            | 
           985 | 
                                       $datasetitem = new stdClass();
  | 
        
        
            | 
            | 
           986 | 
                                       $datasetitem->id = $datasetdefs[$defid]->items[$numberadded]->id;
  | 
        
        
            | 
            | 
           987 | 
                                       $datasetitem->definition = $datasetdef->id;
  | 
        
        
            | 
            | 
           988 | 
                                       $datasetitem->itemnumber = $numberadded;
  | 
        
        
            | 
            | 
           989 | 
                                       $datasetitem->value =
  | 
        
        
            | 
            | 
           990 | 
                                               $this->generate_dataset_item($datasetdef->options);
  | 
        
        
            | 
            | 
           991 | 
                                       $DB->update_record('question_dataset_items', $datasetitem);
  | 
        
        
            | 
            | 
           992 | 
                                   }
  | 
        
        
            | 
            | 
           993 | 
                                   // If not regenerate do nothing as there is already a record.
  | 
        
        
            | 
            | 
           994 | 
                               } else {
  | 
        
        
            | 
            | 
           995 | 
                                   $datasetitem = new stdClass();
  | 
        
        
            | 
            | 
           996 | 
                                   $datasetitem->definition = $datasetdef->id;
  | 
        
        
            | 
            | 
           997 | 
                                   $datasetitem->itemnumber = $numberadded;
  | 
        
        
            | 
            | 
           998 | 
                                   if ($this->supports_dataset_item_generation()) {
  | 
        
        
            | 
            | 
           999 | 
                                       $datasetitem->value =
  | 
        
        
            | 
            | 
           1000 | 
                                               $this->generate_dataset_item($datasetdef->options);
  | 
        
        
            | 
            | 
           1001 | 
                                   } else {
  | 
        
        
            | 
            | 
           1002 | 
                                       $datasetitem->value = '';
  | 
        
        
            | 
            | 
           1003 | 
                                   }
  | 
        
        
            | 
            | 
           1004 | 
                                   $DB->insert_record('question_dataset_items', $datasetitem);
  | 
        
        
            | 
            | 
           1005 | 
                               }
  | 
        
        
            | 
            | 
           1006 | 
                           }// For number added.
  | 
        
        
            | 
            | 
           1007 | 
                       }// Datasetsdefs end.
  | 
        
        
            | 
            | 
           1008 | 
                       $maxnumber += $numbertoadd;
  | 
        
        
            | 
            | 
           1009 | 
                       foreach ($datasetdefs as $key => $newdef) {
  | 
        
        
            | 
            | 
           1010 | 
                           if (isset($newdef->id) && $newdef->itemcount <= $maxnumber) {
  | 
        
        
            | 
            | 
           1011 | 
                               $newdef->itemcount = $maxnumber;
  | 
        
        
            | 
            | 
           1012 | 
                               // Save the new value for options.
  | 
        
        
            | 
            | 
           1013 | 
                               $DB->update_record('question_dataset_definitions', $newdef);
  | 
        
        
            | 
            | 
           1014 | 
                           }
  | 
        
        
            | 
            | 
           1015 | 
                       }
  | 
        
        
            | 
            | 
           1016 | 
                   }
  | 
        
        
            | 
            | 
           1017 | 
              | 
        
        
            | 
            | 
           1018 | 
                   if (isset($fromform->deletebutton)) {
  | 
        
        
            | 
            | 
           1019 | 
                       if (isset($fromform->selectdelete)) {
  | 
        
        
            | 
            | 
           1020 | 
                           $newmaxnumber = $maxnumber - $fromform->selectdelete;
  | 
        
        
            | 
            | 
           1021 | 
                       } else {
  | 
        
        
            | 
            | 
           1022 | 
                           $newmaxnumber = $maxnumber - 1;
  | 
        
        
            | 
            | 
           1023 | 
                       }
  | 
        
        
            | 
            | 
           1024 | 
                       if ($newmaxnumber < 0) {
  | 
        
        
            | 
            | 
           1025 | 
                           $newmaxnumber = 0;
  | 
        
        
            | 
            | 
           1026 | 
                       }
  | 
        
        
            | 
            | 
           1027 | 
                       foreach ($datasetdefs as $datasetdef) {
  | 
        
        
            | 
            | 
           1028 | 
                           if ($datasetdef->itemcount == $maxnumber) {
  | 
        
        
            | 
            | 
           1029 | 
                               $datasetdef->itemcount = $newmaxnumber;
  | 
        
        
            | 
            | 
           1030 | 
                               $DB->update_record('question_dataset_definitions', $datasetdef);
  | 
        
        
            | 
            | 
           1031 | 
                           }
  | 
        
        
            | 
            | 
           1032 | 
                       }
  | 
        
        
            | 
            | 
           1033 | 
                   }
  | 
        
        
            | 
            | 
           1034 | 
               }
  | 
        
        
            | 
            | 
           1035 | 
               public function generate_dataset_item($options) {
  | 
        
        
            | 
            | 
           1036 | 
                   if (!preg_match('~^(uniform|loguniform):([^:]*):([^:]*):([0-9]*)$~',
  | 
        
        
            | 
            | 
           1037 | 
                           $options, $regs)) {
  | 
        
        
            | 
            | 
           1038 | 
                       // Unknown options...
  | 
        
        
            | 
            | 
           1039 | 
                       return false;
  | 
        
        
            | 
            | 
           1040 | 
                   }
  | 
        
        
            | 
            | 
           1041 | 
                   if ($regs[1] == 'uniform') {
  | 
        
        
            | 
            | 
           1042 | 
                       $nbr = $regs[2] + ($regs[3] - $regs[2]) * mt_rand() / mt_getrandmax();
  | 
        
        
            | 
            | 
           1043 | 
                       return sprintf("%.".$regs[4].'f', $nbr);
  | 
        
        
            | 
            | 
           1044 | 
              | 
        
        
            | 
            | 
           1045 | 
                   } else if ($regs[1] == 'loguniform') {
  | 
        
        
            | 
            | 
           1046 | 
                       $log0 = log(abs($regs[2])); // It would have worked the other way to.
  | 
        
        
            | 
            | 
           1047 | 
                       $nbr = exp($log0 + (log(abs($regs[3])) - $log0) * mt_rand() / mt_getrandmax());
  | 
        
        
            | 
            | 
           1048 | 
                       return sprintf("%.".$regs[4].'f', $nbr);
  | 
        
        
            | 
            | 
           1049 | 
              | 
        
        
            | 
            | 
           1050 | 
                   } else {
  | 
        
        
            | 
            | 
           1051 | 
                       throw new \moodle_exception('disterror', 'question', '', $regs[1]);
  | 
        
        
            | 
            | 
           1052 | 
                   }
  | 
        
        
            | 
            | 
           1053 | 
                   return '';
  | 
        
        
            | 
            | 
           1054 | 
               }
  | 
        
        
            | 
            | 
           1055 | 
              | 
        
        
            | 
            | 
           1056 | 
               public function comment_header($question) {
  | 
        
        
            | 
            | 
           1057 | 
                   $strheader = '';
  | 
        
        
            | 
            | 
           1058 | 
                   $delimiter = '';
  | 
        
        
            | 
            | 
           1059 | 
              | 
        
        
            | 
            | 
           1060 | 
                   $answers = $question->options->answers;
  | 
        
        
            | 
            | 
           1061 | 
              | 
        
        
            | 
            | 
           1062 | 
                   foreach ($answers as $key => $answer) {
  | 
        
        
            | 
            | 
           1063 | 
                       $ans = shorten_text($answer->answer, 17, true);
  | 
        
        
            | 
            | 
           1064 | 
                       $strheader .= $delimiter.$ans;
  | 
        
        
            | 
            | 
           1065 | 
                       $delimiter = '<br/><br/><br/>';
  | 
        
        
            | 
            | 
           1066 | 
                   }
  | 
        
        
            | 
            | 
           1067 | 
                   return $strheader;
  | 
        
        
            | 
            | 
           1068 | 
               }
  | 
        
        
            | 
            | 
           1069 | 
              | 
        
        
            | 
            | 
           1070 | 
               public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext,
  | 
        
        
            | 
            | 
           1071 | 
                       $answers, $data, $number) {
  | 
        
        
            | 
            | 
           1072 | 
                   global $DB;
  | 
        
        
            | 
            | 
           1073 | 
                   $comment = new stdClass();
  | 
        
        
            | 
            | 
           1074 | 
                   $comment->stranswers = [];
  | 
        
        
            | 
            | 
           1075 | 
                   $comment->outsidelimit = false;
  | 
        
        
            | 
            | 
           1076 | 
                   $comment->answers = [];
  | 
        
        
            | 
            | 
           1077 | 
                   // Find a default unit.
  | 
        
        
            | 
            | 
           1078 | 
                   $unit = '';
  | 
        
        
            | 
            | 
           1079 | 
                   if (!empty($questionid)) {
  | 
        
        
            | 
            | 
           1080 | 
                       $units = $DB->get_records('question_numerical_units',
  | 
        
        
            | 
            | 
           1081 | 
                           ['question' => $questionid, 'multiplier' => 1.0],
  | 
        
        
            | 
            | 
           1082 | 
                           'id ASC', '*', 0, 1);
  | 
        
        
            | 
            | 
           1083 | 
                       if ($units) {
  | 
        
        
            | 
            | 
           1084 | 
                           $unit = reset($units);
  | 
        
        
            | 
            | 
           1085 | 
                           $unit = $unit->unit;
  | 
        
        
            | 
            | 
           1086 | 
                       }
  | 
        
        
            | 
            | 
           1087 | 
                   }
  | 
        
        
            | 
            | 
           1088 | 
              | 
        
        
            | 
            | 
           1089 | 
                   $answers = fullclone($answers);
  | 
        
        
            | 
            | 
           1090 | 
                   $delimiter = ': ';
  | 
        
        
            | 
            | 
           1091 | 
                   $virtualqtype = $qtypeobj->get_virtual_qtype();
  | 
        
        
            | 
            | 
           1092 | 
                   foreach ($answers as $key => $answer) {
  | 
        
        
            | 
            | 
           1093 | 
                       $error = qtype_calculated_find_formula_errors($answer->answer);
  | 
        
        
            | 
            | 
           1094 | 
                       if ($error) {
  | 
        
        
            | 
            | 
           1095 | 
                           $comment->stranswers[$key] = $error;
  | 
        
        
            | 
            | 
           1096 | 
                           continue;
  | 
        
        
            | 
            | 
           1097 | 
                       }
  | 
        
        
            | 
            | 
           1098 | 
                       $formula = $this->substitute_variables($answer->answer, $data);
  | 
        
        
            | 
            | 
           1099 | 
                       $formattedanswer = qtype_calculated_calculate_answer(
  | 
        
        
            | 
            | 
           1100 | 
                           $answer->answer, $data, $answer->tolerance,
  | 
        
        
            | 
            | 
           1101 | 
                           $answer->tolerancetype, $answer->correctanswerlength,
  | 
        
        
            | 
            | 
           1102 | 
                           $answer->correctanswerformat, $unit);
  | 
        
        
            | 
            | 
           1103 | 
                       if ($formula === '*') {
  | 
        
        
            | 
            | 
           1104 | 
                           $answer->min = ' ';
  | 
        
        
            | 
            | 
           1105 | 
                           $formattedanswer->answer = $answer->answer;
  | 
        
        
            | 
            | 
           1106 | 
                       } else {
  | 
        
        
            | 
            | 
           1107 | 
                           eval('$ansvalue = '.$formula.';');
  | 
        
        
            | 
            | 
           1108 | 
                           $ans = new qtype_numerical_answer(0, $ansvalue, 0, '', 0, $answer->tolerance);
  | 
        
        
            | 
            | 
           1109 | 
                           $ans->tolerancetype = $answer->tolerancetype;
  | 
        
        
            | 
            | 
           1110 | 
                           list($answer->min, $answer->max) = $ans->get_tolerance_interval($answer);
  | 
        
        
            | 
            | 
           1111 | 
                       }
  | 
        
        
            | 
            | 
           1112 | 
                       if ($answer->min === '') {
  | 
        
        
            | 
            | 
           1113 | 
                           // This should mean that something is wrong.
  | 
        
        
            | 
            | 
           1114 | 
                           $comment->stranswers[$key] = " {$formattedanswer->answer}".'<br/><br/>';
  | 
        
        
            | 
            | 
           1115 | 
                       } else if ($formula === '*') {
  | 
        
        
            | 
            | 
           1116 | 
                           $comment->stranswers[$key] = $formula . ' = ' .
  | 
        
        
            | 
            | 
           1117 | 
                                   get_string('anyvalue', 'qtype_calculated') . '<br/><br/><br/>';
  | 
        
        
            | 
            | 
           1118 | 
                       } else {
  | 
        
        
            | 
            | 
           1119 | 
                           $formula = shorten_text($formula, 57, true);
  | 
        
        
            | 
            | 
           1120 | 
                           $comment->stranswers[$key] = $formula . ' = ' . $formattedanswer->answer . '<br/>';
  | 
        
        
            | 
            | 
           1121 | 
                           $correcttrue = new stdClass();
  | 
        
        
            | 
            | 
           1122 | 
                           $correcttrue->correct = $formattedanswer->answer;
  | 
        
        
            | 
            | 
           1123 | 
                           $correcttrue->true = '';
  | 
        
        
            | 
            | 
           1124 | 
                           if ((float) $formattedanswer->answer < $answer->min ||
  | 
        
        
            | 
            | 
           1125 | 
                               (float) $formattedanswer->answer > $answer->max) {
  | 
        
        
            | 
            | 
           1126 | 
                               $comment->outsidelimit = true;
  | 
        
        
            | 
            | 
           1127 | 
                               $comment->answers[$key] = $key;
  | 
        
        
            | 
            | 
           1128 | 
                               $comment->stranswers[$key] .=
  | 
        
        
            | 
            | 
           1129 | 
                                       get_string('trueansweroutsidelimits', 'qtype_calculated', $correcttrue);
  | 
        
        
            | 
            | 
           1130 | 
                           } else {
  | 
        
        
            | 
            | 
           1131 | 
                               $comment->stranswers[$key] .=
  | 
        
        
            | 
            | 
           1132 | 
                                       get_string('trueanswerinsidelimits', 'qtype_calculated', $correcttrue);
  | 
        
        
            | 
            | 
           1133 | 
                           }
  | 
        
        
            | 
            | 
           1134 | 
                           $comment->stranswers[$key] .= '<br/>';
  | 
        
        
            | 
            | 
           1135 | 
                           $comment->stranswers[$key] .= get_string('min', 'qtype_calculated') .
  | 
        
        
            | 
            | 
           1136 | 
                                   $delimiter . $answer->min . ' --- ';
  | 
        
        
            | 
            | 
           1137 | 
                           $comment->stranswers[$key] .= get_string('max', 'qtype_calculated') .
  | 
        
        
            | 
            | 
           1138 | 
                                   $delimiter . $answer->max;
  | 
        
        
            | 
            | 
           1139 | 
                       }
  | 
        
        
            | 
            | 
           1140 | 
                   }
  | 
        
        
            | 
            | 
           1141 | 
                   return fullclone($comment);
  | 
        
        
            | 
            | 
           1142 | 
               }
  | 
        
        
            | 
            | 
           1143 | 
              | 
        
        
            | 
            | 
           1144 | 
               public function tolerance_types() {
  | 
        
        
            | 
            | 
           1145 | 
                   return [
  | 
        
        
            | 
            | 
           1146 | 
                       '1' => get_string('relative', 'qtype_numerical'),
  | 
        
        
            | 
            | 
           1147 | 
                       '2' => get_string('nominal', 'qtype_numerical'),
  | 
        
        
            | 
            | 
           1148 | 
                       '3' => get_string('geometric', 'qtype_numerical'),
  | 
        
        
            | 
            | 
           1149 | 
                   ];
  | 
        
        
            | 
            | 
           1150 | 
               }
  | 
        
        
            | 
            | 
           1151 | 
              | 
        
        
            | 
            | 
           1152 | 
               public function dataset_options($form, $name, $mandatory = true,
  | 
        
        
            | 
            | 
           1153 | 
                       $renameabledatasets = false) {
  | 
        
        
            | 
            | 
           1154 | 
                   // Takes datasets from the parent implementation but
  | 
        
        
            | 
            | 
           1155 | 
                   // filters options that are currently not accepted by calculated.
  | 
        
        
            | 
            | 
           1156 | 
                   // It also determines a default selection.
  | 
        
        
            | 
            | 
           1157 | 
                   // Param $renameabledatasets not implemented anywhere.
  | 
        
        
            | 
            | 
           1158 | 
              | 
        
        
            | 
            | 
           1159 | 
                   list($options, $selected) = $this->dataset_options_from_database(
  | 
        
        
            | 
            | 
           1160 | 
                           $form, $name, '', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1161 | 
              | 
        
        
            | 
            | 
           1162 | 
                   foreach ($options as $key => $whatever) {
  | 
        
        
            | 
            | 
           1163 | 
                       if (!preg_match('~^1-~', $key) && $key != '0') {
  | 
        
        
            | 
            | 
           1164 | 
                           unset($options[$key]);
  | 
        
        
            | 
            | 
           1165 | 
                       }
  | 
        
        
            | 
            | 
           1166 | 
                   }
  | 
        
        
            | 
            | 
           1167 | 
                   if (!$selected) {
  | 
        
        
            | 
            | 
           1168 | 
                       if ($mandatory) {
  | 
        
        
            | 
            | 
           1169 | 
                           $selected = "1-0-{$name}"; // Default.
  | 
        
        
            | 
            | 
           1170 | 
                       } else {
  | 
        
        
            | 
            | 
           1171 | 
                           $selected = '0'; // Default.
  | 
        
        
            | 
            | 
           1172 | 
                       }
  | 
        
        
            | 
            | 
           1173 | 
                   }
  | 
        
        
            | 
            | 
           1174 | 
                   return [$options, $selected];
  | 
        
        
            | 
            | 
           1175 | 
               }
  | 
        
        
            | 
            | 
           1176 | 
              | 
        
        
            | 
            | 
           1177 | 
               public function construct_dataset_menus($form, $mandatorydatasets,
  | 
        
        
            | 
            | 
           1178 | 
                       $optionaldatasets) {
  | 
        
        
            | 
            | 
           1179 | 
                   global $OUTPUT;
  | 
        
        
            | 
            | 
           1180 | 
                   $datasetmenus = [];
  | 
        
        
            | 
            | 
           1181 | 
                   foreach ($mandatorydatasets as $datasetname) {
  | 
        
        
            | 
            | 
           1182 | 
                       if (!isset($datasetmenus[$datasetname])) {
  | 
        
        
            | 
            | 
           1183 | 
                           list($options, $selected) =
  | 
        
        
            | 
            | 
           1184 | 
                               $this->dataset_options($form, $datasetname);
  | 
        
        
            | 
            | 
           1185 | 
                           unset($options['0']); // Mandatory...
  | 
        
        
            | 
            | 
           1186 | 
                           $datasetmenus[$datasetname] = html_writer::select(
  | 
        
        
            | 
            | 
           1187 | 
                                   $options, 'dataset[]', $selected, null);
  | 
        
        
            | 
            | 
           1188 | 
                       }
  | 
        
        
            | 
            | 
           1189 | 
                   }
  | 
        
        
            | 
            | 
           1190 | 
                   foreach ($optionaldatasets as $datasetname) {
  | 
        
        
            | 
            | 
           1191 | 
                       if (!isset($datasetmenus[$datasetname])) {
  | 
        
        
            | 
            | 
           1192 | 
                           list($options, $selected) =
  | 
        
        
            | 
            | 
           1193 | 
                               $this->dataset_options($form, $datasetname);
  | 
        
        
            | 
            | 
           1194 | 
                           $datasetmenus[$datasetname] = html_writer::select(
  | 
        
        
            | 
            | 
           1195 | 
                                   $options, 'dataset[]', $selected, null);
  | 
        
        
            | 
            | 
           1196 | 
                       }
  | 
        
        
            | 
            | 
           1197 | 
                   }
  | 
        
        
            | 
            | 
           1198 | 
                   return $datasetmenus;
  | 
        
        
            | 
            | 
           1199 | 
               }
  | 
        
        
            | 
            | 
           1200 | 
              | 
        
        
            | 
            | 
           1201 | 
               public function substitute_variables($str, $dataset) {
  | 
        
        
            | 
            | 
           1202 | 
                   global $OUTPUT;
  | 
        
        
            | 
            | 
           1203 | 
                   // Testing for wrong numerical values.
  | 
        
        
            | 
            | 
           1204 | 
                   // All calculations used this function so testing here should be OK.
  | 
        
        
            | 
            | 
           1205 | 
              | 
        
        
            | 
            | 
           1206 | 
                   foreach ($dataset as $name => $value) {
  | 
        
        
            | 
            | 
           1207 | 
                       $val = $value;
  | 
        
        
            | 
            | 
           1208 | 
                       if (! is_numeric($val)) {
  | 
        
        
            | 
            | 
           1209 | 
                           $a = new stdClass();
  | 
        
        
            | 
            | 
           1210 | 
                           $a->name = '{'.$name.'}';
  | 
        
        
            | 
            | 
           1211 | 
                           $a->value = $value;
  | 
        
        
            | 
            | 
           1212 | 
                           echo $OUTPUT->notification(get_string('notvalidnumber', 'qtype_calculated', $a));
  | 
        
        
            | 
            | 
           1213 | 
                           $val = 1.0;
  | 
        
        
            | 
            | 
           1214 | 
                       }
  | 
        
        
            | 
            | 
           1215 | 
                       if ($val <= 0) { // MDL-36025 Use parentheses for "-0" .
  | 
        
        
            | 
            | 
           1216 | 
                           $str = str_replace('{'.$name.'}', '('.$val.')', $str);
  | 
        
        
            | 
            | 
           1217 | 
                       } else {
  | 
        
        
            | 
            | 
           1218 | 
                           $str = str_replace('{'.$name.'}', $val, $str);
  | 
        
        
            | 
            | 
           1219 | 
                       }
  | 
        
        
            | 
            | 
           1220 | 
                   }
  | 
        
        
            | 
            | 
           1221 | 
                   return $str;
  | 
        
        
            | 
            | 
           1222 | 
               }
  | 
        
        
            | 
            | 
           1223 | 
              | 
        
        
            | 
            | 
           1224 | 
               public function evaluate_equations($str, $dataset) {
  | 
        
        
            | 
            | 
           1225 | 
                   $formula = $this->substitute_variables($str, $dataset);
  | 
        
        
            | 
            | 
           1226 | 
                   if ($error = qtype_calculated_find_formula_errors($formula)) {
  | 
        
        
            | 
            | 
           1227 | 
                       return $error;
  | 
        
        
            | 
            | 
           1228 | 
                   }
  | 
        
        
            | 
            | 
           1229 | 
                   return $str;
  | 
        
        
            | 
            | 
           1230 | 
               }
  | 
        
        
            | 
            | 
           1231 | 
              | 
        
        
            | 
            | 
           1232 | 
               public function substitute_variables_and_eval($str, $dataset) {
  | 
        
        
            | 
            | 
           1233 | 
                   $formula = $this->substitute_variables($str, $dataset);
  | 
        
        
            | 
            | 
           1234 | 
                   if ($error = qtype_calculated_find_formula_errors($formula)) {
  | 
        
        
            | 
            | 
           1235 | 
                       return $error;
  | 
        
        
            | 
            | 
           1236 | 
                   }
  | 
        
        
            | 
            | 
           1237 | 
                   // Calculate the correct answer.
  | 
        
        
            | 
            | 
           1238 | 
                   if (empty($formula)) {
  | 
        
        
            | 
            | 
           1239 | 
                       $str = '';
  | 
        
        
            | 
            | 
           1240 | 
                   } else if ($formula === '*') {
  | 
        
        
            | 
            | 
           1241 | 
                       $str = '*';
  | 
        
        
            | 
            | 
           1242 | 
                   } else {
  | 
        
        
            | 
            | 
           1243 | 
                       $str = null;
  | 
        
        
            | 
            | 
           1244 | 
                       eval('$str = '.$formula.';');
  | 
        
        
            | 
            | 
           1245 | 
                   }
  | 
        
        
            | 
            | 
           1246 | 
                   return $str;
  | 
        
        
            | 
            | 
           1247 | 
               }
  | 
        
        
            | 
            | 
           1248 | 
              | 
        
        
            | 
            | 
           1249 | 
               public function get_dataset_definitions($questionid, $newdatasets) {
  | 
        
        
            | 
            | 
           1250 | 
                   global $DB;
  | 
        
        
            | 
            | 
           1251 | 
                   // Get the existing datasets for this question.
  | 
        
        
            | 
            | 
           1252 | 
                   $datasetdefs = [];
  | 
        
        
            | 
            | 
           1253 | 
                   if (!empty($questionid)) {
  | 
        
        
            | 
            | 
           1254 | 
                       global $CFG;
  | 
        
        
            | 
            | 
           1255 | 
                       $sql = "SELECT i.*
  | 
        
        
            | 
            | 
           1256 | 
                                 FROM {question_datasets} d, {question_dataset_definitions} i
  | 
        
        
            | 
            | 
           1257 | 
                                WHERE d.question = ? AND d.datasetdefinition = i.id
  | 
        
        
            | 
            | 
           1258 | 
                             ORDER BY i.id";
  | 
        
        
            | 
            | 
           1259 | 
                       if ($records = $DB->get_records_sql($sql, [$questionid])) {
  | 
        
        
            | 
            | 
           1260 | 
                           foreach ($records as $r) {
  | 
        
        
            | 
            | 
           1261 | 
                               $datasetdefs["{$r->type}-{$r->category}-{$r->name}"] = $r;
  | 
        
        
            | 
            | 
           1262 | 
                           }
  | 
        
        
            | 
            | 
           1263 | 
                       }
  | 
        
        
            | 
            | 
           1264 | 
                   }
  | 
        
        
            | 
            | 
           1265 | 
              | 
        
        
            | 
            | 
           1266 | 
                   foreach ($newdatasets as $dataset) {
  | 
        
        
            | 
            | 
           1267 | 
                       if (!$dataset) {
  | 
        
        
            | 
            | 
           1268 | 
                           continue; // The no dataset case...
  | 
        
        
            | 
            | 
           1269 | 
                       }
  | 
        
        
            | 
            | 
           1270 | 
              | 
        
        
            | 
            | 
           1271 | 
                       if (!isset($datasetdefs[$dataset])) {
  | 
        
        
            | 
            | 
           1272 | 
                           // Make new datasetdef.
  | 
        
        
            | 
            | 
           1273 | 
                           list($type, $category, $name) = explode('-', $dataset, 3);
  | 
        
        
            | 
            | 
           1274 | 
                           $datasetdef = new stdClass();
  | 
        
        
            | 
            | 
           1275 | 
                           $datasetdef->type = $type;
  | 
        
        
            | 
            | 
           1276 | 
                           $datasetdef->name = $name;
  | 
        
        
            | 
            | 
           1277 | 
                           $datasetdef->category  = $category;
  | 
        
        
            | 
            | 
           1278 | 
                           $datasetdef->itemcount = 0;
  | 
        
        
            | 
            | 
           1279 | 
                           $datasetdef->options   = 'uniform:1.0:10.0:1';
  | 
        
        
            | 
            | 
           1280 | 
                           $datasetdefs[$dataset] = clone($datasetdef);
  | 
        
        
            | 
            | 
           1281 | 
                       }
  | 
        
        
            | 
            | 
           1282 | 
                   }
  | 
        
        
            | 
            | 
           1283 | 
                   return $datasetdefs;
  | 
        
        
            | 
            | 
           1284 | 
               }
  | 
        
        
            | 
            | 
           1285 | 
              | 
        
        
            | 
            | 
           1286 | 
               public function save_dataset_definitions($form) {
  | 
        
        
            | 
            | 
           1287 | 
                   global $DB;
  | 
        
        
            | 
            | 
           1288 | 
                   // Save synchronize.
  | 
        
        
            | 
            | 
           1289 | 
              | 
        
        
            | 
            | 
           1290 | 
                   if (empty($form->dataset)) {
  | 
        
        
            | 
            | 
           1291 | 
                       $form->dataset = [];
  | 
        
        
            | 
            | 
           1292 | 
                   }
  | 
        
        
            | 
            | 
           1293 | 
                   // Save datasets.
  | 
        
        
            | 
            | 
           1294 | 
                   $datasetdefinitions = $this->get_dataset_definitions($form->id, $form->dataset);
  | 
        
        
            | 
            | 
           1295 | 
                   $tmpdatasets = array_flip($form->dataset);
  | 
        
        
            | 
            | 
           1296 | 
                   $defids = array_keys($datasetdefinitions);
  | 
        
        
            | 
            | 
           1297 | 
                   foreach ($defids as $defid) {
  | 
        
        
            | 
            | 
           1298 | 
                       $datasetdef = &$datasetdefinitions[$defid];
  | 
        
        
            | 
            | 
           1299 | 
                       if (isset($datasetdef->id)) {
  | 
        
        
            | 
            | 
           1300 | 
                           if (!isset($tmpdatasets[$defid])) {
  | 
        
        
            | 
            | 
           1301 | 
                               // This dataset is not used any more, delete it.
  | 
        
        
            | 
            | 
           1302 | 
                               $DB->delete_records('question_datasets',
  | 
        
        
            | 
            | 
           1303 | 
                                       ['question' => $form->id, 'datasetdefinition' => $datasetdef->id]);
  | 
        
        
            | 
            | 
           1304 | 
                               if ($datasetdef->category == 0) {
  | 
        
        
            | 
            | 
           1305 | 
                                   // Question local dataset.
  | 
        
        
            | 
            | 
           1306 | 
                                   $DB->delete_records('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1307 | 
                                           ['id' => $datasetdef->id]);
  | 
        
        
            | 
            | 
           1308 | 
                                   $DB->delete_records('question_dataset_items',
  | 
        
        
            | 
            | 
           1309 | 
                                           ['definition' => $datasetdef->id]);
  | 
        
        
            | 
            | 
           1310 | 
                               }
  | 
        
        
            | 
            | 
           1311 | 
                           }
  | 
        
        
            | 
            | 
           1312 | 
                           // This has already been saved or just got deleted.
  | 
        
        
            | 
            | 
           1313 | 
                           unset($datasetdefinitions[$defid]);
  | 
        
        
            | 
            | 
           1314 | 
                           continue;
  | 
        
        
            | 
            | 
           1315 | 
                       }
  | 
        
        
            | 
            | 
           1316 | 
              | 
        
        
            | 
            | 
           1317 | 
                       $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
  | 
        
        
            | 
            | 
           1318 | 
              | 
        
        
            | 
            | 
           1319 | 
                       if (0 != $datasetdef->category) {
  | 
        
        
            | 
            | 
           1320 | 
                           // We need to look for already existing datasets in the category.
  | 
        
        
            | 
            | 
           1321 | 
                           // First creating the datasetdefinition above
  | 
        
        
            | 
            | 
           1322 | 
                           // then we can manage to automatically take care of some possible realtime concurrence.
  | 
        
        
            | 
            | 
           1323 | 
              | 
        
        
            | 
            | 
           1324 | 
                           if ($olderdatasetdefs = $DB->get_records_select('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1325 | 
                                   'type = ? AND name = ? AND category = ? AND id < ?
  | 
        
        
            | 
            | 
           1326 | 
                                   ORDER BY id DESC',
  | 
        
        
            | 
            | 
           1327 | 
                                   [$datasetdef->type, $datasetdef->name,
  | 
        
        
            | 
            | 
           1328 | 
                                           $datasetdef->category, $datasetdef->id])) {
  | 
        
        
            | 
            | 
           1329 | 
              | 
        
        
            | 
            | 
           1330 | 
                               while ($olderdatasetdef = array_shift($olderdatasetdefs)) {
  | 
        
        
            | 
            | 
           1331 | 
                                   $DB->delete_records('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1332 | 
                                           ['id' => $datasetdef->id]);
  | 
        
        
            | 
            | 
           1333 | 
                                   $datasetdef = $olderdatasetdef;
  | 
        
        
            | 
            | 
           1334 | 
                               }
  | 
        
        
            | 
            | 
           1335 | 
                           }
  | 
        
        
            | 
            | 
           1336 | 
                       }
  | 
        
        
            | 
            | 
           1337 | 
              | 
        
        
            | 
            | 
           1338 | 
                       // Create relation to this dataset.
  | 
        
        
            | 
            | 
           1339 | 
                       $questiondataset = new stdClass();
  | 
        
        
            | 
            | 
           1340 | 
                       $questiondataset->question = $form->id;
  | 
        
        
            | 
            | 
           1341 | 
                       $questiondataset->datasetdefinition = $datasetdef->id;
  | 
        
        
            | 
            | 
           1342 | 
                       $DB->insert_record('question_datasets', $questiondataset);
  | 
        
        
            | 
            | 
           1343 | 
                       unset($datasetdefinitions[$defid]);
  | 
        
        
            | 
            | 
           1344 | 
                   }
  | 
        
        
            | 
            | 
           1345 | 
              | 
        
        
            | 
            | 
           1346 | 
                   // Remove local obsolete datasets as well as relations
  | 
        
        
            | 
            | 
           1347 | 
                   // to datasets in other categories.
  | 
        
        
            | 
            | 
           1348 | 
                   if (!empty($datasetdefinitions)) {
  | 
        
        
            | 
            | 
           1349 | 
                       foreach ($datasetdefinitions as $def) {
  | 
        
        
            | 
            | 
           1350 | 
                           $DB->delete_records('question_datasets',
  | 
        
        
            | 
            | 
           1351 | 
                                   ['question' => $form->id, 'datasetdefinition' => $def->id]);
  | 
        
        
            | 
            | 
           1352 | 
              | 
        
        
            | 
            | 
           1353 | 
                           if ($def->category == 0) { // Question local dataset.
  | 
        
        
            | 
            | 
           1354 | 
                               $DB->delete_records('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1355 | 
                                       ['id' => $def->id]);
  | 
        
        
            | 
            | 
           1356 | 
                               $DB->delete_records('question_dataset_items',
  | 
        
        
            | 
            | 
           1357 | 
                                       ['definition' => $def->id]);
  | 
        
        
            | 
            | 
           1358 | 
                           }
  | 
        
        
            | 
            | 
           1359 | 
                       }
  | 
        
        
            | 
            | 
           1360 | 
                   }
  | 
        
        
            | 
            | 
           1361 | 
               }
  | 
        
        
            | 
            | 
           1362 | 
               /** This function create a copy of the datasets (definition and dataitems)
  | 
        
        
            | 
            | 
           1363 | 
                * from the preceding question if they remain in the new question
  | 
        
        
            | 
            | 
           1364 | 
                * otherwise its create the datasets that have been added as in the
  | 
        
        
            | 
            | 
           1365 | 
                * save_dataset_definitions()
  | 
        
        
            | 
            | 
           1366 | 
                */
  | 
        
        
            | 
            | 
           1367 | 
               public function save_as_new_dataset_definitions($form, $initialid) {
  | 
        
        
            | 
            | 
           1368 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           1369 | 
                   // Get the datasets from the intial question.
  | 
        
        
            | 
            | 
           1370 | 
                   $datasetdefinitions = $this->get_dataset_definitions($initialid, $form->dataset);
  | 
        
        
            | 
            | 
           1371 | 
                   // Param $tmpdatasets contains those of the new question.
  | 
        
        
            | 
            | 
           1372 | 
                   $tmpdatasets = array_flip($form->dataset);
  | 
        
        
            | 
            | 
           1373 | 
                   $defids = array_keys($datasetdefinitions);// New datasets.
  | 
        
        
            | 
            | 
           1374 | 
                   foreach ($defids as $defid) {
  | 
        
        
            | 
            | 
           1375 | 
                       $datasetdef = &$datasetdefinitions[$defid];
  | 
        
        
            | 
            | 
           1376 | 
                       if (isset($datasetdef->id)) {
  | 
        
        
            | 
            | 
           1377 | 
                           // This dataset exist in the initial question.
  | 
        
        
            | 
            | 
           1378 | 
                           if (!isset($tmpdatasets[$defid])) {
  | 
        
        
            | 
            | 
           1379 | 
                               // Do not exist in the new question so ignore.
  | 
        
        
            | 
            | 
           1380 | 
                               unset($datasetdefinitions[$defid]);
  | 
        
        
            | 
            | 
           1381 | 
                               continue;
  | 
        
        
            | 
            | 
           1382 | 
                           }
  | 
        
        
            | 
            | 
           1383 | 
                           // Create a copy but not for category one.
  | 
        
        
            | 
            | 
           1384 | 
                           if (0 == $datasetdef->category) {
  | 
        
        
            | 
            | 
           1385 | 
                               $olddatasetid = $datasetdef->id;
  | 
        
        
            | 
            | 
           1386 | 
                               $olditemcount = $datasetdef->itemcount;
  | 
        
        
            | 
            | 
           1387 | 
                               $datasetdef->itemcount = 0;
  | 
        
        
            | 
            | 
           1388 | 
                               $datasetdef->id = $DB->insert_record('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1389 | 
                                       $datasetdef);
  | 
        
        
            | 
            | 
           1390 | 
                               // Copy the dataitems.
  | 
        
        
            | 
            | 
           1391 | 
                               $olditems = $this->get_database_dataset_items($olddatasetid);
  | 
        
        
            | 
            | 
           1392 | 
                               if (count($olditems) > 0) {
  | 
        
        
            | 
            | 
           1393 | 
                                   $itemcount = 0;
  | 
        
        
            | 
            | 
           1394 | 
                                   foreach ($olditems as $item) {
  | 
        
        
            | 
            | 
           1395 | 
                                       $item->definition = $datasetdef->id;
  | 
        
        
            | 
            | 
           1396 | 
                                       $DB->insert_record('question_dataset_items', $item);
  | 
        
        
            | 
            | 
           1397 | 
                                       $itemcount++;
  | 
        
        
            | 
            | 
           1398 | 
                                   }
  | 
        
        
            | 
            | 
           1399 | 
                                   // Update item count to olditemcount if
  | 
        
        
            | 
            | 
           1400 | 
                                   // at least this number of items has been recover from the database.
  | 
        
        
            | 
            | 
           1401 | 
                                   if ($olditemcount <= $itemcount) {
  | 
        
        
            | 
            | 
           1402 | 
                                       $datasetdef->itemcount = $olditemcount;
  | 
        
        
            | 
            | 
           1403 | 
                                   } else {
  | 
        
        
            | 
            | 
           1404 | 
                                       $datasetdef->itemcount = $itemcount;
  | 
        
        
            | 
            | 
           1405 | 
                                   }
  | 
        
        
            | 
            | 
           1406 | 
                                   $DB->update_record('question_dataset_definitions', $datasetdef);
  | 
        
        
            | 
            | 
           1407 | 
                               } // End of  copy the dataitems.
  | 
        
        
            | 
            | 
           1408 | 
                           }// End of  copy the datasetdef.
  | 
        
        
            | 
            | 
           1409 | 
                           // Create relation to the new question with this
  | 
        
        
            | 
            | 
           1410 | 
                           // copy as new datasetdef from the initial question.
  | 
        
        
            | 
            | 
           1411 | 
                           $questiondataset = new stdClass();
  | 
        
        
            | 
            | 
           1412 | 
                           $questiondataset->question = $form->id;
  | 
        
        
            | 
            | 
           1413 | 
                           $questiondataset->datasetdefinition = $datasetdef->id;
  | 
        
        
            | 
            | 
           1414 | 
                           $DB->insert_record('question_datasets', $questiondataset);
  | 
        
        
            | 
            | 
           1415 | 
                           unset($datasetdefinitions[$defid]);
  | 
        
        
            | 
            | 
           1416 | 
                           continue;
  | 
        
        
            | 
            | 
           1417 | 
                       }// End of datasetdefs from the initial question.
  | 
        
        
            | 
            | 
           1418 | 
                       // Really new one code similar to save_dataset_definitions().
  | 
        
        
            | 
            | 
           1419 | 
                       $datasetdef->id = $DB->insert_record('question_dataset_definitions', $datasetdef);
  | 
        
        
            | 
            | 
           1420 | 
              | 
        
        
            | 
            | 
           1421 | 
                       if (0 != $datasetdef->category) {
  | 
        
        
            | 
            | 
           1422 | 
                           // We need to look for already existing
  | 
        
        
            | 
            | 
           1423 | 
                           // datasets in the category.
  | 
        
        
            | 
            | 
           1424 | 
                           // By first creating the datasetdefinition above we
  | 
        
        
            | 
            | 
           1425 | 
                           // can manage to automatically take care of
  | 
        
        
            | 
            | 
           1426 | 
                           // some possible realtime concurrence.
  | 
        
        
            | 
            | 
           1427 | 
                           if ($olderdatasetdefs = $DB->get_records_select('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1428 | 
                                   "type = ? AND " . $DB->sql_equal('name', '?') . " AND category = ? AND id < ?
  | 
        
        
            | 
            | 
           1429 | 
                                   ORDER BY id DESC",
  | 
        
        
            | 
            | 
           1430 | 
                                   [$datasetdef->type, $datasetdef->name,
  | 
        
        
            | 
            | 
           1431 | 
                                           $datasetdef->category, $datasetdef->id])) {
  | 
        
        
            | 
            | 
           1432 | 
              | 
        
        
            | 
            | 
           1433 | 
                               while ($olderdatasetdef = array_shift($olderdatasetdefs)) {
  | 
        
        
            | 
            | 
           1434 | 
                                   $DB->delete_records('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1435 | 
                                           ['id' => $datasetdef->id]);
  | 
        
        
            | 
            | 
           1436 | 
                                   $datasetdef = $olderdatasetdef;
  | 
        
        
            | 
            | 
           1437 | 
                               }
  | 
        
        
            | 
            | 
           1438 | 
                           }
  | 
        
        
            | 
            | 
           1439 | 
                       }
  | 
        
        
            | 
            | 
           1440 | 
              | 
        
        
            | 
            | 
           1441 | 
                       // Create relation to this dataset.
  | 
        
        
            | 
            | 
           1442 | 
                       $questiondataset = new stdClass();
  | 
        
        
            | 
            | 
           1443 | 
                       $questiondataset->question = $form->id;
  | 
        
        
            | 
            | 
           1444 | 
                       $questiondataset->datasetdefinition = $datasetdef->id;
  | 
        
        
            | 
            | 
           1445 | 
                       $DB->insert_record('question_datasets', $questiondataset);
  | 
        
        
            | 
            | 
           1446 | 
                       unset($datasetdefinitions[$defid]);
  | 
        
        
            | 
            | 
           1447 | 
                   }
  | 
        
        
            | 
            | 
           1448 | 
              | 
        
        
            | 
            | 
           1449 | 
                   // Remove local obsolete datasets as well as relations
  | 
        
        
            | 
            | 
           1450 | 
                   // to datasets in other categories.
  | 
        
        
            | 
            | 
           1451 | 
                   if (!empty($datasetdefinitions)) {
  | 
        
        
            | 
            | 
           1452 | 
                       foreach ($datasetdefinitions as $def) {
  | 
        
        
            | 
            | 
           1453 | 
                           $DB->delete_records('question_datasets',
  | 
        
        
            | 
            | 
           1454 | 
                                   ['question' => $form->id, 'datasetdefinition' => $def->id]);
  | 
        
        
            | 
            | 
           1455 | 
              | 
        
        
            | 
            | 
           1456 | 
                           if ($def->category == 0) { // Question local dataset.
  | 
        
        
            | 
            | 
           1457 | 
                               $DB->delete_records('question_dataset_definitions',
  | 
        
        
            | 
            | 
           1458 | 
                                       ['id' => $def->id]);
  | 
        
        
            | 
            | 
           1459 | 
                               $DB->delete_records('question_dataset_items',
  | 
        
        
            | 
            | 
           1460 | 
                                       ['definition' => $def->id]);
  | 
        
        
            | 
            | 
           1461 | 
                           }
  | 
        
        
            | 
            | 
           1462 | 
                       }
  | 
        
        
            | 
            | 
           1463 | 
                   }
  | 
        
        
            | 
            | 
           1464 | 
               }
  | 
        
        
            | 
            | 
           1465 | 
              | 
        
        
            | 
            | 
           1466 | 
               // Dataset functionality.
  | 
        
        
            | 
            | 
           1467 | 
               public function pick_question_dataset($question, $datasetitem) {
  | 
        
        
            | 
            | 
           1468 | 
                   // Select a dataset in the following format:
  | 
        
        
            | 
            | 
           1469 | 
                   // an array indexed by the variable names (d.name) pointing to the value
  | 
        
        
            | 
            | 
           1470 | 
                   // to be substituted.
  | 
        
        
            | 
            | 
           1471 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           1472 | 
                   if (!$dataitems = $DB->get_records_sql(
  | 
        
        
            | 
            | 
           1473 | 
                           "SELECT i.id, d.name, i.value
  | 
        
        
            | 
            | 
           1474 | 
                              FROM {question_dataset_definitions} d,
  | 
        
        
            | 
            | 
           1475 | 
                                   {question_dataset_items} i,
  | 
        
        
            | 
            | 
           1476 | 
                                   {question_datasets} q
  | 
        
        
            | 
            | 
           1477 | 
                             WHERE q.question = ?
  | 
        
        
            | 
            | 
           1478 | 
                               AND q.datasetdefinition = d.id
  | 
        
        
            | 
            | 
           1479 | 
                               AND d.id = i.definition
  | 
        
        
            | 
            | 
           1480 | 
                               AND i.itemnumber = ?
  | 
        
        
            | 
            | 
           1481 | 
                          ORDER BY i.id DESC ", [$question->id, $datasetitem])) {
  | 
        
        
            | 
            | 
           1482 | 
                       $a = new stdClass();
  | 
        
        
            | 
            | 
           1483 | 
                       $a->id = $question->id;
  | 
        
        
            | 
            | 
           1484 | 
                       $a->item = $datasetitem;
  | 
        
        
            | 
            | 
           1485 | 
                       throw new \moodle_exception('cannotgetdsfordependent', 'question', '', $a);
  | 
        
        
            | 
            | 
           1486 | 
                   }
  | 
        
        
            | 
            | 
           1487 | 
                   $dataset = [];
  | 
        
        
            | 
            | 
           1488 | 
                   foreach ($dataitems as $id => $dataitem) {
  | 
        
        
            | 
            | 
           1489 | 
                       if (!isset($dataset[$dataitem->name])) {
  | 
        
        
            | 
            | 
           1490 | 
                           $dataset[$dataitem->name] = $dataitem->value;
  | 
        
        
            | 
            | 
           1491 | 
                       }
  | 
        
        
            | 
            | 
           1492 | 
                   }
  | 
        
        
            | 
            | 
           1493 | 
                   return $dataset;
  | 
        
        
            | 
            | 
           1494 | 
               }
  | 
        
        
            | 
            | 
           1495 | 
              | 
        
        
            | 
            | 
           1496 | 
               public function dataset_options_from_database($form, $name, $prefix = '',
  | 
        
        
            | 
            | 
           1497 | 
                       $langfile = 'qtype_calculated') {
  | 
        
        
            | 
            | 
           1498 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           1499 | 
                   $type = 1; // Only type = 1 (i.e. old 'LITERAL') has ever been used.
  | 
        
        
            | 
            | 
           1500 | 
                   // First options - it is not a dataset...
  | 
        
        
            | 
            | 
           1501 | 
                   $options['0'] = get_string($prefix.'nodataset', $langfile);
  | 
        
        
            | 
            | 
           1502 | 
                   // New question no local.
  | 
        
        
            | 
            | 
           1503 | 
                   if (!isset($form->id) || $form->id == 0) {
  | 
        
        
            | 
            | 
           1504 | 
                       $key = "{$type}-0-{$name}";
  | 
        
        
            | 
            | 
           1505 | 
                       $options[$key] = get_string($prefix."newlocal{$type}", $langfile);
  | 
        
        
            | 
            | 
           1506 | 
                       $currentdatasetdef = new stdClass();
  | 
        
        
            | 
            | 
           1507 | 
                       $currentdatasetdef->type = '0';
  | 
        
        
            | 
            | 
           1508 | 
                   } else {
  | 
        
        
            | 
            | 
           1509 | 
                       // Construct question local options.
  | 
        
        
            | 
            | 
           1510 | 
                       $sql = "SELECT a.*
  | 
        
        
            | 
            | 
           1511 | 
                           FROM {question_dataset_definitions} a, {question_datasets} b
  | 
        
        
            | 
            | 
           1512 | 
                          WHERE a.id = b.datasetdefinition AND a.type = '1' AND b.question = ? AND " . $DB->sql_equal('a.name', '?');
  | 
        
        
            | 
            | 
           1513 | 
                       $currentdatasetdef = $DB->get_record_sql($sql, [$form->id, $name]);
  | 
        
        
            | 
            | 
           1514 | 
                       if (!$currentdatasetdef) {
  | 
        
        
            | 
            | 
           1515 | 
                           $currentdatasetdef = new stdClass();
  | 
        
        
            | 
            | 
           1516 | 
                           $currentdatasetdef->type = '0';
  | 
        
        
            | 
            | 
           1517 | 
                       }
  | 
        
        
            | 
            | 
           1518 | 
                       $key = "{$type}-0-{$name}";
  | 
        
        
            | 
            | 
           1519 | 
                       if ($currentdatasetdef->type == $type
  | 
        
        
            | 
            | 
           1520 | 
                               && $currentdatasetdef->category == 0) {
  | 
        
        
            | 
            | 
           1521 | 
                           $options[$key] = get_string($prefix."keptlocal{$type}", $langfile);
  | 
        
        
            | 
            | 
           1522 | 
                       } else {
  | 
        
        
            | 
            | 
           1523 | 
                           $options[$key] = get_string($prefix."newlocal{$type}", $langfile);
  | 
        
        
            | 
            | 
           1524 | 
                       }
  | 
        
        
            | 
            | 
           1525 | 
                   }
  | 
        
        
            | 
            | 
           1526 | 
                   // Construct question category options.
  | 
        
        
            | 
            | 
           1527 | 
                   $categorydatasetdefs = $DB->get_records_sql(
  | 
        
        
            | 
            | 
           1528 | 
                       "SELECT b.question, a.*
  | 
        
        
            | 
            | 
           1529 | 
                       FROM {question_datasets} b,
  | 
        
        
            | 
            | 
           1530 | 
                       {question_dataset_definitions} a
  | 
        
        
            | 
            | 
           1531 | 
                       WHERE a.id = b.datasetdefinition
  | 
        
        
            | 
            | 
           1532 | 
                       AND a.type = '1'
  | 
        
        
            | 
            | 
           1533 | 
                       AND a.category = ?
  | 
        
        
            | 
            | 
           1534 | 
                       AND " . $DB->sql_equal('a.name', '?'), [$form->category, $name]);
  | 
        
        
            | 
            | 
           1535 | 
                   $type = 1;
  | 
        
        
            | 
            | 
           1536 | 
                   $key = "{$type}-{$form->category}-{$name}";
  | 
        
        
            | 
            | 
           1537 | 
                   if (!empty($categorydatasetdefs)) {
  | 
        
        
            | 
            | 
           1538 | 
                       // There is at least one with the same name.
  | 
        
        
            | 
            | 
           1539 | 
                       if (isset($form->id) && isset($categorydatasetdefs[$form->id])) {
  | 
        
        
            | 
            | 
           1540 | 
                           // It is already used by this question.
  | 
        
        
            | 
            | 
           1541 | 
                           $options[$key] = get_string($prefix."keptcategory{$type}", $langfile);
  | 
        
        
            | 
            | 
           1542 | 
                       } else {
  | 
        
        
            | 
            | 
           1543 | 
                           $options[$key] = get_string($prefix."existingcategory{$type}", $langfile);
  | 
        
        
            | 
            | 
           1544 | 
                       }
  | 
        
        
            | 
            | 
           1545 | 
                   } else {
  | 
        
        
            | 
            | 
           1546 | 
                       $options[$key] = get_string($prefix."newcategory{$type}", $langfile);
  | 
        
        
            | 
            | 
           1547 | 
                   }
  | 
        
        
            | 
            | 
           1548 | 
                   // All done!
  | 
        
        
            | 
            | 
           1549 | 
                   return [$options, $currentdatasetdef->type
  | 
        
        
            | 
            | 
           1550 | 
                       ? "{$currentdatasetdef->type}-{$currentdatasetdef->category}-{$name}"
  | 
        
        
            | 
            | 
           1551 | 
                       : ''];
  | 
        
        
            | 
            | 
           1552 | 
               }
  | 
        
        
            | 
            | 
           1553 | 
              | 
        
        
            | 
            | 
           1554 | 
               /**
  | 
        
        
            | 
            | 
           1555 | 
                * Find the names of all datasets mentioned in a piece of question content like the question text.
  | 
        
        
            | 
            | 
           1556 | 
                * @param $text the text to analyse.
  | 
        
        
            | 
            | 
           1557 | 
                * @return array with dataset name for both key and value.
  | 
        
        
            | 
            | 
           1558 | 
                */
  | 
        
        
            | 
            | 
           1559 | 
               public function find_dataset_names($text) {
  | 
        
        
            | 
            | 
           1560 | 
                   preg_match_all(self::PLACEHODLER_REGEX, $text, $matches);
  | 
        
        
            | 
            | 
           1561 | 
                   return array_combine($matches[1], $matches[1]);
  | 
        
        
            | 
            | 
           1562 | 
               }
  | 
        
        
            | 
            | 
           1563 | 
              | 
        
        
            | 
            | 
           1564 | 
               /**
  | 
        
        
            | 
            | 
           1565 | 
                * Find all the formulas in a bit of text.
  | 
        
        
            | 
            | 
           1566 | 
                *
  | 
        
        
            | 
            | 
           1567 | 
                * For example, called with "What is {a} plus {b}? (Hint, it is not {={a}*{b}}.)" this
  | 
        
        
            | 
            | 
           1568 | 
                * returns ['{a}*{b}'].
  | 
        
        
            | 
            | 
           1569 | 
                *
  | 
        
        
            | 
            | 
           1570 | 
                * @param $text text to analyse.
  | 
        
        
            | 
            | 
           1571 | 
                * @return array where they keys an values are the formulas.
  | 
        
        
            | 
            | 
           1572 | 
                */
  | 
        
        
            | 
            | 
           1573 | 
               public function find_formulas($text) {
  | 
        
        
            | 
            | 
           1574 | 
                   preg_match_all(self::FORMULAS_IN_TEXT_REGEX, $text, $matches);
  | 
        
        
            | 
            | 
           1575 | 
                   return array_combine($matches[1], $matches[1]);
  | 
        
        
            | 
            | 
           1576 | 
               }
  | 
        
        
            | 
            | 
           1577 | 
              | 
        
        
            | 
            | 
           1578 | 
               /**
  | 
        
        
            | 
            | 
           1579 | 
                * This function retrieve the item count of the available category shareable
  | 
        
        
            | 
            | 
           1580 | 
                * wild cards that is added as a comment displayed when a wild card with
  | 
        
        
            | 
            | 
           1581 | 
                * the same name is displayed in datasetdefinitions_form.php
  | 
        
        
            | 
            | 
           1582 | 
                */
  | 
        
        
            | 
            | 
           1583 | 
               public function get_dataset_definitions_category($form) {
  | 
        
        
            | 
            | 
           1584 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           1585 | 
                   $datasetdefs = [];
  | 
        
        
            | 
            | 
           1586 | 
                   $lnamemax = 30;
  | 
        
        
            | 
            | 
           1587 | 
                   if (!empty($form->category)) {
  | 
        
        
            | 
            | 
           1588 | 
                       $sql = "SELECT i.*, d.*
  | 
        
        
            | 
            | 
           1589 | 
                                 FROM {question_datasets} d, {question_dataset_definitions} i
  | 
        
        
            | 
            | 
           1590 | 
                                WHERE i.id = d.datasetdefinition AND i.category = ?";
  | 
        
        
            | 
            | 
           1591 | 
                       if ($records = $DB->get_records_sql($sql, [$form->category])) {
  | 
        
        
            | 
            | 
           1592 | 
                           foreach ($records as $r) {
  | 
        
        
            | 
            | 
           1593 | 
                               if (!isset ($datasetdefs["{$r->name}"])) {
  | 
        
        
            | 
            | 
           1594 | 
                                   $datasetdefs["{$r->name}"] = $r->itemcount;
  | 
        
        
            | 
            | 
           1595 | 
                               }
  | 
        
        
            | 
            | 
           1596 | 
                           }
  | 
        
        
            | 
            | 
           1597 | 
                       }
  | 
        
        
            | 
            | 
           1598 | 
                   }
  | 
        
        
            | 
            | 
           1599 | 
                   return $datasetdefs;
  | 
        
        
            | 
            | 
           1600 | 
               }
  | 
        
        
            | 
            | 
           1601 | 
              | 
        
        
            | 
            | 
           1602 | 
               /**
  | 
        
        
            | 
            | 
           1603 | 
                * This function build a table showing the available category shareable
  | 
        
        
            | 
            | 
           1604 | 
                * wild cards, their name, their definition (Min, Max, Decimal) , the item count
  | 
        
        
            | 
            | 
           1605 | 
                * and the name of the question where they are used.
  | 
        
        
            | 
            | 
           1606 | 
                * This table is intended to be add before the question text to help the user use
  | 
        
        
            | 
            | 
           1607 | 
                * these wild cards
  | 
        
        
            | 
            | 
           1608 | 
                */
  | 
        
        
            | 
            | 
           1609 | 
               public function print_dataset_definitions_category($form) {
  | 
        
        
            | 
            | 
           1610 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           1611 | 
                   $datasetdefs = [];
  | 
        
        
            | 
            | 
           1612 | 
                   $lnamemax = 22;
  | 
        
        
            | 
            | 
           1613 | 
                   $namestr          = get_string('name');
  | 
        
        
            | 
            | 
           1614 | 
                   $rangeofvaluestr  = get_string('minmax', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1615 | 
                   $questionusingstr = get_string('usedinquestion', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1616 | 
                   $itemscountstr    = get_string('itemscount', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1617 | 
                   $text = '';
  | 
        
        
            | 
            | 
           1618 | 
                   if (!empty($form->category)) {
  | 
        
        
            | 
            | 
           1619 | 
                       list($category) = explode(',', $form->category);
  | 
        
        
            | 
            | 
           1620 | 
                       $sql = "SELECT i.*, d.*
  | 
        
        
            | 
            | 
           1621 | 
                           FROM {question_datasets} d,
  | 
        
        
            | 
            | 
           1622 | 
                   {question_dataset_definitions} i
  | 
        
        
            | 
            | 
           1623 | 
                   WHERE i.id = d.datasetdefinition
  | 
        
        
            | 
            | 
           1624 | 
                   AND i.category = ?";
  | 
        
        
            | 
            | 
           1625 | 
                       if ($records = $DB->get_records_sql($sql, [$category])) {
  | 
        
        
            | 
            | 
           1626 | 
                           foreach ($records as $r) {
  | 
        
        
            | 
            | 
           1627 | 
                               $sql1 = "SELECT q.*
  | 
        
        
            | 
            | 
           1628 | 
                                          FROM {question} q
  | 
        
        
            | 
            | 
           1629 | 
                                         WHERE q.id = ?";
  | 
        
        
            | 
            | 
           1630 | 
                               if (!isset ($datasetdefs["{$r->type}-{$r->category}-{$r->name}"])) {
  | 
        
        
            | 
            | 
           1631 | 
                                   $datasetdefs["{$r->type}-{$r->category}-{$r->name}"] = $r;
  | 
        
        
            | 
            | 
           1632 | 
                               }
  | 
        
        
            | 
            | 
           1633 | 
                               if ($questionb = $DB->get_records_sql($sql1, [$r->question])) {
  | 
        
        
            | 
            | 
           1634 | 
                                   if (!isset ($datasetdefs["{$r->type}-{$r->category}-{$r->name}"]->questions[$r->question])) {
  | 
        
        
            | 
            | 
           1635 | 
                                       $datasetdefs["{$r->type}-{$r->category}-{$r->name}"]->questions[$r->question] = new stdClass();
  | 
        
        
            | 
            | 
           1636 | 
                                   }
  | 
        
        
            | 
            | 
           1637 | 
                                   $datasetdefs["{$r->type}-{$r->category}-{$r->name}"]->questions[$r->question]->name =
  | 
        
        
            | 
            | 
           1638 | 
                                       $questionb[$r->question]->name;
  | 
        
        
            | 
            | 
           1639 | 
                               }
  | 
        
        
            | 
            | 
           1640 | 
                           }
  | 
        
        
            | 
            | 
           1641 | 
                       }
  | 
        
        
            | 
            | 
           1642 | 
                   }
  | 
        
        
            | 
            | 
           1643 | 
                   if (!empty ($datasetdefs)) {
  | 
        
        
            | 
            | 
           1644 | 
              | 
        
        
            | 
            | 
           1645 | 
                       $text = "<table width=\"100%\" border=\"1\"><tr>
  | 
        
        
            | 
            | 
           1646 | 
                               <th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1647 | 
                                       scope=\"col\">{$namestr}</th>
  | 
        
        
            | 
            | 
           1648 | 
                               <th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1649 | 
                                       scope=\"col\">{$rangeofvaluestr}</th>
  | 
        
        
            | 
            | 
           1650 | 
                               <th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1651 | 
                                       scope=\"col\">{$itemscountstr}</th>
  | 
        
        
            | 
            | 
           1652 | 
                               <th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1653 | 
                                       scope=\"col\">{$questionusingstr}</th>
  | 
        
        
            | 
            | 
           1654 | 
                               </tr>";
  | 
        
        
            | 
            | 
           1655 | 
                       foreach ($datasetdefs as $datasetdef) {
  | 
        
        
            | 
            | 
           1656 | 
                           list($distribution, $min, $max, $dec) = explode(':', $datasetdef->options, 4);
  | 
        
        
            | 
            | 
           1657 | 
                           $text .= "<tr>
  | 
        
        
            | 
            | 
           1658 | 
                                   <td valign=\"top\" align=\"center\">{$datasetdef->name}</td>
  | 
        
        
            | 
            | 
           1659 | 
                                   <td align=\"center\" valign=\"top\">{$min} <strong>-</strong> $max</td>
  | 
        
        
            | 
            | 
           1660 | 
                                   <td align=\"right\" valign=\"top\">{$datasetdef->itemcount}  </td>
  | 
        
        
            | 
            | 
           1661 | 
                                   <td align=\"left\">";
  | 
        
        
            | 
            | 
           1662 | 
                           foreach ($datasetdef->questions as $qu) {
  | 
        
        
            | 
            | 
           1663 | 
                               // Limit the name length displayed.
  | 
        
        
            | 
            | 
           1664 | 
                               $questionname = $this->get_short_question_name($qu->name, $lnamemax);
  | 
        
        
            | 
            | 
           1665 | 
                               $text .= "    {$questionname} <br/>";
  | 
        
        
            | 
            | 
           1666 | 
                           }
  | 
        
        
            | 
            | 
           1667 | 
                           $text .= "</td></tr>";
  | 
        
        
            | 
            | 
           1668 | 
                       }
  | 
        
        
            | 
            | 
           1669 | 
                       $text .= "</table>";
  | 
        
        
            | 
            | 
           1670 | 
                   } else {
  | 
        
        
            | 
            | 
           1671 | 
                       $text .= get_string('nosharedwildcard', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1672 | 
                   }
  | 
        
        
            | 
            | 
           1673 | 
                   return $text;
  | 
        
        
            | 
            | 
           1674 | 
               }
  | 
        
        
            | 
            | 
           1675 | 
              | 
        
        
            | 
            | 
           1676 | 
               /**
  | 
        
        
            | 
            | 
           1677 | 
                * This function shortens a question name if it exceeds the character limit.
  | 
        
        
            | 
            | 
           1678 | 
                *
  | 
        
        
            | 
            | 
           1679 | 
                * @param string $stringtoshorten the string to be shortened.
  | 
        
        
            | 
            | 
           1680 | 
                * @param int $characterlimit the character limit.
  | 
        
        
            | 
            | 
           1681 | 
                * @return string
  | 
        
        
            | 
            | 
           1682 | 
                */
  | 
        
        
            | 
            | 
           1683 | 
               public function get_short_question_name($stringtoshorten, $characterlimit) {
  | 
        
        
            | 
            | 
           1684 | 
                   if (!empty($stringtoshorten)) {
  | 
        
        
            | 
            | 
           1685 | 
                       $returnstring = format_string($stringtoshorten);
  | 
        
        
            | 
            | 
           1686 | 
                       if (strlen($returnstring) > $characterlimit) {
  | 
        
        
            | 
            | 
           1687 | 
                           $returnstring = shorten_text($returnstring, $characterlimit, true);
  | 
        
        
            | 
            | 
           1688 | 
                       }
  | 
        
        
            | 
            | 
           1689 | 
                       return $returnstring;
  | 
        
        
            | 
            | 
           1690 | 
                   } else {
  | 
        
        
            | 
            | 
           1691 | 
                       return '';
  | 
        
        
            | 
            | 
           1692 | 
                   }
  | 
        
        
            | 
            | 
           1693 | 
               }
  | 
        
        
            | 
            | 
           1694 | 
              | 
        
        
            | 
            | 
           1695 | 
               /**
  | 
        
        
            | 
            | 
           1696 | 
                * This function build a table showing the available category shareable
  | 
        
        
            | 
            | 
           1697 | 
                * wild cards, their name, their definition (Min, Max, Decimal) , the item count
  | 
        
        
            | 
            | 
           1698 | 
                * and the name of the question where they are used.
  | 
        
        
            | 
            | 
           1699 | 
                * This table is intended to be add before the question text to help the user use
  | 
        
        
            | 
            | 
           1700 | 
                * these wild cards
  | 
        
        
            | 
            | 
           1701 | 
                */
  | 
        
        
            | 
            | 
           1702 | 
              | 
        
        
            | 
            | 
           1703 | 
               public function print_dataset_definitions_category_shared($question, $datasetdefsq) {
  | 
        
        
            | 
            | 
           1704 | 
                   global $CFG, $DB;
  | 
        
        
            | 
            | 
           1705 | 
                   $datasetdefs = [];
  | 
        
        
            | 
            | 
           1706 | 
                   $lnamemax = 22;
  | 
        
        
            | 
            | 
           1707 | 
                   $namestr          = get_string('name', 'quiz');
  | 
        
        
            | 
            | 
           1708 | 
                   $rangeofvaluestr  = get_string('minmax', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1709 | 
                   $questionusingstr = get_string('usedinquestion', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1710 | 
                   $itemscountstr    = get_string('itemscount', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1711 | 
                   $text = '';
  | 
        
        
            | 
            | 
           1712 | 
                   if (!empty($question->category)) {
  | 
        
        
            | 
            | 
           1713 | 
                       list($category) = explode(',', $question->category);
  | 
        
        
            | 
            | 
           1714 | 
                       $sql = "SELECT i.*, d.*
  | 
        
        
            | 
            | 
           1715 | 
                                 FROM {question_datasets} d, {question_dataset_definitions} i
  | 
        
        
            | 
            | 
           1716 | 
                                WHERE i.id = d.datasetdefinition AND i.category = ?";
  | 
        
        
            | 
            | 
           1717 | 
                       if ($records = $DB->get_records_sql($sql, [$category])) {
  | 
        
        
            | 
            | 
           1718 | 
                           foreach ($records as $r) {
  | 
        
        
            | 
            | 
           1719 | 
                               $key = "{$r->type}-{$r->category}-{$r->name}";
  | 
        
        
            | 
            | 
           1720 | 
                               $sql1 = "SELECT q.*
  | 
        
        
            | 
            | 
           1721 | 
                                          FROM {question} q
  | 
        
        
            | 
            | 
           1722 | 
                                         WHERE q.id = ?";
  | 
        
        
            | 
            | 
           1723 | 
                               if (!isset($datasetdefs[$key])) {
  | 
        
        
            | 
            | 
           1724 | 
                                   $datasetdefs[$key] = $r;
  | 
        
        
            | 
            | 
           1725 | 
                               }
  | 
        
        
            | 
            | 
           1726 | 
                               if ($questionb = $DB->get_records_sql($sql1, [$r->question])) {
  | 
        
        
            | 
            | 
           1727 | 
                                   $datasetdefs[$key]->questions[$r->question] = new stdClass();
  | 
        
        
            | 
            | 
           1728 | 
                                   $datasetdefs[$key]->questions[$r->question]->name =
  | 
        
        
            | 
            | 
           1729 | 
                                           $questionb[$r->question]->name;
  | 
        
        
            | 
            | 
           1730 | 
                                   $datasetdefs[$key]->questions[$r->question]->id =
  | 
        
        
            | 
            | 
           1731 | 
                                           $questionb[$r->question]->id;
  | 
        
        
            | 
            | 
           1732 | 
                               }
  | 
        
        
            | 
            | 
           1733 | 
                           }
  | 
        
        
            | 
            | 
           1734 | 
                       }
  | 
        
        
            | 
            | 
           1735 | 
                   }
  | 
        
        
            | 
            | 
           1736 | 
                   if (!empty ($datasetdefs)) {
  | 
        
        
            | 
            | 
           1737 | 
              | 
        
        
            | 
            | 
           1738 | 
                       $text  = "<table width=\"100%\" border=\"1\"><tr>
  | 
        
        
            | 
            | 
           1739 | 
                               <th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1740 | 
                                       scope=\"col\">{$namestr}</th>";
  | 
        
        
            | 
            | 
           1741 | 
                       $text .= "<th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1742 | 
                               scope=\"col\">{$itemscountstr}</th>";
  | 
        
        
            | 
            | 
           1743 | 
                       $text .= "<th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1744 | 
                               scope=\"col\">  {$questionusingstr}   </th>";
  | 
        
        
            | 
            | 
           1745 | 
                       $text .= "<th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1746 | 
                               scope=\"col\">Quiz</th>";
  | 
        
        
            | 
            | 
           1747 | 
                       $text .= "<th style=\"white-space:nowrap;\" class=\"header\"
  | 
        
        
            | 
            | 
           1748 | 
                               scope=\"col\">Attempts</th></tr>";
  | 
        
        
            | 
            | 
           1749 | 
                       foreach ($datasetdefs as $datasetdef) {
  | 
        
        
            | 
            | 
           1750 | 
                           list($distribution, $min, $max, $dec) = explode(':', $datasetdef->options, 4);
  | 
        
        
            | 
            | 
           1751 | 
                           $count = count($datasetdef->questions);
  | 
        
        
            | 
            | 
           1752 | 
                           $text .= "<tr>
  | 
        
        
            | 
            | 
           1753 | 
                                   <td style=\"white-space:nowrap;\" valign=\"top\"
  | 
        
        
            | 
            | 
           1754 | 
                                           align=\"center\" rowspan=\"{$count}\"> {$datasetdef->name} </td>
  | 
        
        
            | 
            | 
           1755 | 
                                   <td align=\"right\" valign=\"top\"
  | 
        
        
            | 
            | 
           1756 | 
                                           rowspan=\"{$count}\">{$datasetdef->itemcount}</td>";
  | 
        
        
            | 
            | 
           1757 | 
                           $line = 0;
  | 
        
        
            | 
            | 
           1758 | 
                           foreach ($datasetdef->questions as $qu) {
  | 
        
        
            | 
            | 
           1759 | 
                               // Limit the name length displayed.
  | 
        
        
            | 
            | 
           1760 | 
                               $questionname = $this->get_short_question_name($qu->name, $lnamemax);
  | 
        
        
            | 
            | 
           1761 | 
                               if ($line) {
  | 
        
        
            | 
            | 
           1762 | 
                                   $text .= "<tr>";
  | 
        
        
            | 
            | 
           1763 | 
                               }
  | 
        
        
            | 
            | 
           1764 | 
                               $line++;
  | 
        
        
            | 
            | 
           1765 | 
                               $text .= "<td align=\"left\" style=\"white-space:nowrap;\">{$questionname}</td>";
  | 
        
        
            | 
            | 
           1766 | 
                               // TODO MDL-43779 should not have quiz-specific code here.
  | 
        
        
            | 
            | 
           1767 | 
                               $sql = 'SELECT COUNT(*) FROM (' . qbank_usage\helper::get_question_bank_usage_sql() . ') questioncount';
  | 
        
        
            | 
            | 
           1768 | 
                               $nbofquiz = $DB->count_records_sql($sql, [$qu->id, 'mod_quiz', 'slot']);
  | 
        
        
            | 
            | 
           1769 | 
                               $sql = 'SELECT COUNT(*) FROM (' . qbank_usage\helper::get_question_attempt_usage_sql() . ') attemptcount';
  | 
        
        
            | 
            | 
           1770 | 
                               $nbofattempts = $DB->count_records_sql($sql, [$qu->id]);
  | 
        
        
            | 
            | 
           1771 | 
                               if ($nbofquiz > 0) {
  | 
        
        
            | 
            | 
           1772 | 
                                   $text .= "<td align=\"center\">{$nbofquiz}</td>";
  | 
        
        
            | 
            | 
           1773 | 
                                   $text .= "<td align=\"center\">{$nbofattempts}";
  | 
        
        
            | 
            | 
           1774 | 
                               } else {
  | 
        
        
            | 
            | 
           1775 | 
                                   $text .= "<td align=\"center\">0</td>";
  | 
        
        
            | 
            | 
           1776 | 
                                   $text .= "<td align=\"left\"><br/>";
  | 
        
        
            | 
            | 
           1777 | 
                               }
  | 
        
        
            | 
            | 
           1778 | 
              | 
        
        
            | 
            | 
           1779 | 
                               $text .= "</td></tr>";
  | 
        
        
            | 
            | 
           1780 | 
                           }
  | 
        
        
            | 
            | 
           1781 | 
                       }
  | 
        
        
            | 
            | 
           1782 | 
                       $text .= "</table>";
  | 
        
        
            | 
            | 
           1783 | 
                   } else {
  | 
        
        
            | 
            | 
           1784 | 
                       $text .= get_string('nosharedwildcard', 'qtype_calculated');
  | 
        
        
            | 
            | 
           1785 | 
                   }
  | 
        
        
            | 
            | 
           1786 | 
                   return $text;
  | 
        
        
            | 
            | 
           1787 | 
               }
  | 
        
        
            | 
            | 
           1788 | 
              | 
        
        
            | 
            | 
           1789 | 
               public function get_virtual_qtype() {
  | 
        
        
            | 
            | 
           1790 | 
                   return question_bank::get_qtype('numerical');
  | 
        
        
            | 
            | 
           1791 | 
               }
  | 
        
        
            | 
            | 
           1792 | 
              | 
        
        
            | 
            | 
           1793 | 
               public function get_possible_responses($questiondata) {
  | 
        
        
            | 
            | 
           1794 | 
                   $responses = [];
  | 
        
        
            | 
            | 
           1795 | 
              | 
        
        
            | 
            | 
           1796 | 
                   $virtualqtype = $this->get_virtual_qtype();
  | 
        
        
            | 
            | 
           1797 | 
                   $unit = $virtualqtype->get_default_numerical_unit($questiondata);
  | 
        
        
            | 
            | 
           1798 | 
              | 
        
        
            | 
            | 
           1799 | 
                   $tolerancetypes = $this->tolerance_types();
  | 
        
        
            | 
            | 
           1800 | 
              | 
        
        
            | 
            | 
           1801 | 
                   $starfound = false;
  | 
        
        
            | 
            | 
           1802 | 
                   foreach ($questiondata->options->answers as $aid => $answer) {
  | 
        
        
            | 
            | 
           1803 | 
                       $responseclass = $answer->answer;
  | 
        
        
            | 
            | 
           1804 | 
              | 
        
        
            | 
            | 
           1805 | 
                       if ($responseclass === '*') {
  | 
        
        
            | 
            | 
           1806 | 
                           $starfound = true;
  | 
        
        
            | 
            | 
           1807 | 
                       } else {
  | 
        
        
            | 
            | 
           1808 | 
                           $a = new stdClass();
  | 
        
        
            | 
            | 
           1809 | 
                           $a->answer = $virtualqtype->add_unit($questiondata, $responseclass, $unit);
  | 
        
        
            | 
            | 
           1810 | 
                           $a->tolerance = $answer->tolerance;
  | 
        
        
            | 
            | 
           1811 | 
                           $a->tolerancetype = $tolerancetypes[$answer->tolerancetype];
  | 
        
        
            | 
            | 
           1812 | 
              | 
        
        
            | 
            | 
           1813 | 
                           $responseclass = get_string('answerwithtolerance', 'qtype_calculated', $a);
  | 
        
        
            | 
            | 
           1814 | 
                       }
  | 
        
        
            | 
            | 
           1815 | 
              | 
        
        
            | 
            | 
           1816 | 
                       $responses[$aid] = new question_possible_response($responseclass,
  | 
        
        
            | 
            | 
           1817 | 
                               $answer->fraction);
  | 
        
        
            | 
            | 
           1818 | 
                   }
  | 
        
        
            | 
            | 
           1819 | 
              | 
        
        
            | 
            | 
           1820 | 
                   if (!$starfound) {
  | 
        
        
            | 
            | 
           1821 | 
                       $responses[0] = new question_possible_response(
  | 
        
        
            | 
            | 
           1822 | 
                       get_string('didnotmatchanyanswer', 'question'), 0);
  | 
        
        
            | 
            | 
           1823 | 
                   }
  | 
        
        
            | 
            | 
           1824 | 
              | 
        
        
            | 
            | 
           1825 | 
                   $responses[null] = question_possible_response::no_response();
  | 
        
        
            | 
            | 
           1826 | 
              | 
        
        
            | 
            | 
           1827 | 
                   return [$questiondata->id => $responses];
  | 
        
        
            | 
            | 
           1828 | 
               }
  | 
        
        
            | 
            | 
           1829 | 
              | 
        
        
            | 
            | 
           1830 | 
               public function move_files($questionid, $oldcontextid, $newcontextid) {
  | 
        
        
            | 
            | 
           1831 | 
                   $fs = get_file_storage();
  | 
        
        
            | 
            | 
           1832 | 
              | 
        
        
            | 
            | 
           1833 | 
                   parent::move_files($questionid, $oldcontextid, $newcontextid);
  | 
        
        
            | 
            | 
           1834 | 
                   $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid);
  | 
        
        
            | 
            | 
           1835 | 
                   $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
  | 
        
        
            | 
            | 
           1836 | 
               }
  | 
        
        
            | 
            | 
           1837 | 
              | 
        
        
            | 
            | 
           1838 | 
               protected function delete_files($questionid, $contextid) {
  | 
        
        
            | 
            | 
           1839 | 
                   $fs = get_file_storage();
  | 
        
        
            | 
            | 
           1840 | 
              | 
        
        
            | 
            | 
           1841 | 
                   parent::delete_files($questionid, $contextid);
  | 
        
        
            | 
            | 
           1842 | 
                   $this->delete_files_in_answers($questionid, $contextid);
  | 
        
        
            | 
            | 
           1843 | 
                   $this->delete_files_in_hints($questionid, $contextid);
  | 
        
        
            | 
            | 
           1844 | 
               }
  | 
        
        
            | 
            | 
           1845 | 
           }
  | 
        
        
            | 
            | 
           1846 | 
              | 
        
        
            | 
            | 
           1847 | 
              | 
        
        
            | 
            | 
           1848 | 
           function qtype_calculated_calculate_answer($formula, $individualdata,
  | 
        
        
            | 
            | 
           1849 | 
               $tolerance, $tolerancetype, $answerlength, $answerformat = '1', $unit = '') {
  | 
        
        
            | 
            | 
           1850 | 
               // The return value has these properties: .
  | 
        
        
            | 
            | 
           1851 | 
               // ->answer    the correct answer
  | 
        
        
            | 
            | 
           1852 | 
               // ->min       the lower bound for an acceptable response
  | 
        
        
            | 
            | 
           1853 | 
               // ->max       the upper bound for an accetpable response.
  | 
        
        
            | 
            | 
           1854 | 
               $calculated = new stdClass();
  | 
        
        
            | 
            | 
           1855 | 
               // Exchange formula variables with the correct values...
  | 
        
        
            | 
            | 
           1856 | 
               $answer = question_bank::get_qtype('calculated')->substitute_variables_and_eval(
  | 
        
        
            | 
            | 
           1857 | 
                       $formula, $individualdata);
  | 
        
        
            | 
            | 
           1858 | 
               if (!is_numeric($answer)) {
  | 
        
        
            | 
            | 
           1859 | 
                   // Something went wrong, so just return NaN.
  | 
        
        
            | 
            | 
           1860 | 
                   $calculated->answer = NAN;
  | 
        
        
            | 
            | 
           1861 | 
                   return $calculated;
  | 
        
        
            | 
            | 
           1862 | 
               } else if (is_nan($answer) || is_infinite($answer)) {
  | 
        
        
            | 
            | 
           1863 | 
                   $calculated->answer = $answer;
  | 
        
        
            | 
            | 
           1864 | 
                   return $calculated;
  | 
        
        
            | 
            | 
           1865 | 
               }
  | 
        
        
            | 
            | 
           1866 | 
               if ('1' == $answerformat) { // Answer is to have $answerlength decimals.
  | 
        
        
            | 
            | 
           1867 | 
                   // Decimal places.
  | 
        
        
            | 
            | 
           1868 | 
                   $calculated->answer = sprintf('%.' . $answerlength . 'F', $answer);
  | 
        
        
            | 
            | 
           1869 | 
              | 
        
        
            | 
            | 
           1870 | 
               } else if ($answer) { // Significant figures does only apply if the result is non-zero.
  | 
        
        
            | 
            | 
           1871 | 
              | 
        
        
            | 
            | 
           1872 | 
                   // Convert to positive answer...
  | 
        
        
            | 
            | 
           1873 | 
                   if ($answer < 0) {
  | 
        
        
            | 
            | 
           1874 | 
                       $answer = -$answer;
  | 
        
        
            | 
            | 
           1875 | 
                       $sign = '-';
  | 
        
        
            | 
            | 
           1876 | 
                   } else {
  | 
        
        
            | 
            | 
           1877 | 
                       $sign = '';
  | 
        
        
            | 
            | 
           1878 | 
                   }
  | 
        
        
            | 
            | 
           1879 | 
              | 
        
        
            | 
            | 
           1880 | 
                   // Determine the format 0.[1-9][0-9]* for the answer...
  | 
        
        
            | 
            | 
           1881 | 
                   $p10 = 0;
  | 
        
        
            | 
            | 
           1882 | 
                   while ($answer < 1) {
  | 
        
        
            | 
            | 
           1883 | 
                       --$p10;
  | 
        
        
            | 
            | 
           1884 | 
                       $answer *= 10;
  | 
        
        
            | 
            | 
           1885 | 
                   }
  | 
        
        
            | 
            | 
           1886 | 
                   while ($answer >= 1) {
  | 
        
        
            | 
            | 
           1887 | 
                       ++$p10;
  | 
        
        
            | 
            | 
           1888 | 
                       $answer /= 10;
  | 
        
        
            | 
            | 
           1889 | 
                   }
  | 
        
        
            | 
            | 
           1890 | 
                   // ... and have the answer rounded of to the correct length.
  | 
        
        
            | 
            | 
           1891 | 
                   $answer = round($answer, $answerlength);
  | 
        
        
            | 
            | 
           1892 | 
              | 
        
        
            | 
            | 
           1893 | 
                   // If we rounded up to 1.0, place the answer back into 0.[1-9][0-9]* format.
  | 
        
        
            | 
            | 
           1894 | 
                   if ($answer >= 1) {
  | 
        
        
            | 
            | 
           1895 | 
                       ++$p10;
  | 
        
        
            | 
            | 
           1896 | 
                       $answer /= 10;
  | 
        
        
            | 
            | 
           1897 | 
                   }
  | 
        
        
            | 
            | 
           1898 | 
              | 
        
        
            | 
            | 
           1899 | 
                   // Have the answer written on a suitable format:
  | 
        
        
            | 
            | 
           1900 | 
                   // either scientific or plain numeric.
  | 
        
        
            | 
            | 
           1901 | 
                   if (-2 > $p10 || 4 < $p10) {
  | 
        
        
            | 
            | 
           1902 | 
                       // Use scientific format.
  | 
        
        
            | 
            | 
           1903 | 
                       $exponent = 'e'.--$p10;
  | 
        
        
            | 
            | 
           1904 | 
                       $answer *= 10;
  | 
        
        
            | 
            | 
           1905 | 
                       if (1 == $answerlength) {
  | 
        
        
            | 
            | 
           1906 | 
                           $calculated->answer = $sign.$answer.$exponent;
  | 
        
        
            | 
            | 
           1907 | 
                       } else {
  | 
        
        
            | 
            | 
           1908 | 
                           // Attach additional zeros at the end of $answer.
  | 
        
        
            | 
            | 
           1909 | 
                           $answer .= (1 == strlen($answer) ? '.' : '')
  | 
        
        
            | 
            | 
           1910 | 
                               . '00000000000000000000000000000000000000000x';
  | 
        
        
            | 
            | 
           1911 | 
                           $calculated->answer = $sign
  | 
        
        
            | 
            | 
           1912 | 
                               .substr($answer, 0, $answerlength + 1).$exponent;
  | 
        
        
            | 
            | 
           1913 | 
                       }
  | 
        
        
            | 
            | 
           1914 | 
                   } else {
  | 
        
        
            | 
            | 
           1915 | 
                       // Stick to plain numeric format.
  | 
        
        
            | 
            | 
           1916 | 
                       $answer *= "1e{$p10}";
  | 
        
        
            | 
            | 
           1917 | 
                       if (0.1 <= $answer / "1e{$answerlength}") {
  | 
        
        
            | 
            | 
           1918 | 
                           $calculated->answer = $sign.$answer;
  | 
        
        
            | 
            | 
           1919 | 
                       } else {
  | 
        
        
            | 
            | 
           1920 | 
                           // Could be an idea to add some zeros here.
  | 
        
        
            | 
            | 
           1921 | 
                           $answer .= (preg_match('~^[0-9]*$~', $answer) ? '.' : '')
  | 
        
        
            | 
            | 
           1922 | 
                               . '00000000000000000000000000000000000000000x';
  | 
        
        
            | 
            | 
           1923 | 
                           $oklen = $answerlength + ($p10 < 1 ? 2 - $p10 : 1);
  | 
        
        
            | 
            | 
           1924 | 
                           $calculated->answer = $sign.substr($answer, 0, $oklen);
  | 
        
        
            | 
            | 
           1925 | 
                       }
  | 
        
        
            | 
            | 
           1926 | 
                   }
  | 
        
        
            | 
            | 
           1927 | 
              | 
        
        
            | 
            | 
           1928 | 
               } else {
  | 
        
        
            | 
            | 
           1929 | 
                   $calculated->answer = 0.0;
  | 
        
        
            | 
            | 
           1930 | 
               }
  | 
        
        
            | 
            | 
           1931 | 
               if ($unit != '') {
  | 
        
        
            | 
            | 
           1932 | 
                       $calculated->answer = $calculated->answer . ' ' . $unit;
  | 
        
        
            | 
            | 
           1933 | 
               }
  | 
        
        
            | 
            | 
           1934 | 
              | 
        
        
            | 
            | 
           1935 | 
               // Return the result.
  | 
        
        
            | 
            | 
           1936 | 
               return $calculated;
  | 
        
        
            | 
            | 
           1937 | 
           }
  | 
        
        
            | 
            | 
           1938 | 
              | 
        
        
            | 
            | 
           1939 | 
              | 
        
        
            | 
            | 
           1940 | 
           /**
  | 
        
        
            | 
            | 
           1941 | 
            * Validate a forumula.
  | 
        
        
            | 
            | 
           1942 | 
            * @param string $formula the formula to validate.
  | 
        
        
            | 
            | 
           1943 | 
            * @return string|boolean false if there are no problems. Otherwise a string error message.
  | 
        
        
            | 
            | 
           1944 | 
            */
  | 
        
        
            | 
            | 
           1945 | 
           function qtype_calculated_find_formula_errors($formula) {
  | 
        
        
            | 
            | 
           1946 | 
               foreach (['//', '/*', '#', '<?', '?>'] as $commentstart) {
  | 
        
        
            | 
            | 
           1947 | 
                   if (strpos($formula, $commentstart) !== false) {
  | 
        
        
            | 
            | 
           1948 | 
                       return get_string('illegalformulasyntax', 'qtype_calculated', $commentstart);
  | 
        
        
            | 
            | 
           1949 | 
                   }
  | 
        
        
            | 
            | 
           1950 | 
               }
  | 
        
        
            | 
            | 
           1951 | 
              | 
        
        
            | 
            | 
           1952 | 
               // Validates the formula submitted from the question edit page.
  | 
        
        
            | 
            | 
           1953 | 
               // Returns false if everything is alright
  | 
        
        
            | 
            | 
           1954 | 
               // otherwise it constructs an error message.
  | 
        
        
           | 11 | 
           efrain | 
           1955 | 
               // Strip away dataset names. Use 1.0 to remove valid names, so illegal names can be identified later.
  | 
        
        
           | 1 | 
           efrain | 
           1956 | 
               $formula = preg_replace(qtype_calculated::PLACEHODLER_REGEX, '1.0', $formula);
  | 
        
        
            | 
            | 
           1957 | 
              | 
        
        
            | 
            | 
           1958 | 
               // Strip away empty space and lowercase it.
  | 
        
        
            | 
            | 
           1959 | 
               $formula = strtolower(str_replace(' ', '', $formula));
  | 
        
        
            | 
            | 
           1960 | 
              | 
        
        
           | 11 | 
           efrain | 
           1961 | 
               // Only mathematical operators are supported. Bitwise operators are not safe.
  | 
        
        
            | 
            | 
           1962 | 
               // Note: In this context, ^ is a bitwise operator (exponents are represented by **).
  | 
        
        
            | 
            | 
           1963 | 
               $safeoperatorchar = '-+/*%>:\~<?=!';
  | 
        
        
           | 1 | 
           efrain | 
           1964 | 
               $operatorornumber = "[{$safeoperatorchar}.0-9eE]";
  | 
        
        
            | 
            | 
           1965 | 
              | 
        
        
           | 11 | 
           efrain | 
           1966 | 
               // Validate mathematical functions in formula.
  | 
        
        
           | 1 | 
           efrain | 
           1967 | 
               while (preg_match("~(^|[{$safeoperatorchar},(])([a-z0-9_]*)" .
  | 
        
        
            | 
            | 
           1968 | 
                       "\\(({$operatorornumber}+(,{$operatorornumber}+((,{$operatorornumber}+)+)?)?)?\\)~",
  | 
        
        
            | 
            | 
           1969 | 
                       $formula, $regs)) {
  | 
        
        
            | 
            | 
           1970 | 
                   switch ($regs[2]) {
  | 
        
        
            | 
            | 
           1971 | 
                       // Simple parenthesis.
  | 
        
        
            | 
            | 
           1972 | 
                       case '':
  | 
        
        
            | 
            | 
           1973 | 
                           if ((isset($regs[4]) && $regs[4]) || strlen($regs[3]) == 0) {
  | 
        
        
            | 
            | 
           1974 | 
                               return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
  | 
        
        
            | 
            | 
           1975 | 
                           }
  | 
        
        
            | 
            | 
           1976 | 
                           break;
  | 
        
        
            | 
            | 
           1977 | 
              | 
        
        
            | 
            | 
           1978 | 
                           // Zero argument functions.
  | 
        
        
            | 
            | 
           1979 | 
                       case 'pi':
  | 
        
        
            | 
            | 
           1980 | 
                           if (array_key_exists(3, $regs)) {
  | 
        
        
            | 
            | 
           1981 | 
                               return get_string('functiontakesnoargs', 'qtype_calculated', $regs[2]);
  | 
        
        
            | 
            | 
           1982 | 
                           }
  | 
        
        
            | 
            | 
           1983 | 
                           break;
  | 
        
        
            | 
            | 
           1984 | 
              | 
        
        
            | 
            | 
           1985 | 
                       // Single argument functions (the most common case).
  | 
        
        
            | 
            | 
           1986 | 
                       case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh':
  | 
        
        
            | 
            | 
           1987 | 
                       case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos':
  | 
        
        
            | 
            | 
           1988 | 
                       case 'cosh': case 'decbin': case 'decoct': case 'deg2rad':
  | 
        
        
            | 
            | 
           1989 | 
                       case 'exp': case 'expm1': case 'floor': case 'is_finite':
  | 
        
        
            | 
            | 
           1990 | 
                       case 'is_infinite': case 'is_nan': case 'log10': case 'log1p':
  | 
        
        
            | 
            | 
           1991 | 
                       case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt':
  | 
        
        
            | 
            | 
           1992 | 
                       case 'tan': case 'tanh':
  | 
        
        
            | 
            | 
           1993 | 
                           if (!empty($regs[4]) || empty($regs[3])) {
  | 
        
        
            | 
            | 
           1994 | 
                               return get_string('functiontakesonearg', 'qtype_calculated', $regs[2]);
  | 
        
        
            | 
            | 
           1995 | 
                           }
  | 
        
        
            | 
            | 
           1996 | 
                           break;
  | 
        
        
            | 
            | 
           1997 | 
              | 
        
        
            | 
            | 
           1998 | 
                           // Functions that take one or two arguments.
  | 
        
        
            | 
            | 
           1999 | 
                       case 'log': case 'round':
  | 
        
        
            | 
            | 
           2000 | 
                               if (!empty($regs[5]) || empty($regs[3])) {
  | 
        
        
            | 
            | 
           2001 | 
                                   return get_string('functiontakesoneortwoargs', 'qtype_calculated', $regs[2]);
  | 
        
        
            | 
            | 
           2002 | 
                               }
  | 
        
        
            | 
            | 
           2003 | 
                           break;
  | 
        
        
            | 
            | 
           2004 | 
              | 
        
        
            | 
            | 
           2005 | 
                           // Functions that must have two arguments.
  | 
        
        
            | 
            | 
           2006 | 
                       case 'atan2': case 'fmod': case 'pow':
  | 
        
        
            | 
            | 
           2007 | 
                                   if (!empty($regs[5]) || empty($regs[4])) {
  | 
        
        
            | 
            | 
           2008 | 
                                       return get_string('functiontakestwoargs', 'qtype_calculated', $regs[2]);
  | 
        
        
            | 
            | 
           2009 | 
                                   }
  | 
        
        
            | 
            | 
           2010 | 
                           break;
  | 
        
        
            | 
            | 
           2011 | 
              | 
        
        
            | 
            | 
           2012 | 
                           // Functions that take two or more arguments.
  | 
        
        
            | 
            | 
           2013 | 
                       case 'min': case 'max':
  | 
        
        
            | 
            | 
           2014 | 
                               if (empty($regs[4])) {
  | 
        
        
            | 
            | 
           2015 | 
                                   return get_string('functiontakesatleasttwo', 'qtype_calculated', $regs[2]);
  | 
        
        
            | 
            | 
           2016 | 
                               }
  | 
        
        
            | 
            | 
           2017 | 
                           break;
  | 
        
        
            | 
            | 
           2018 | 
              | 
        
        
            | 
            | 
           2019 | 
                       default:
  | 
        
        
            | 
            | 
           2020 | 
                           return get_string('unsupportedformulafunction', 'qtype_calculated', $regs[2]);
  | 
        
        
            | 
            | 
           2021 | 
                   }
  | 
        
        
            | 
            | 
           2022 | 
              | 
        
        
            | 
            | 
           2023 | 
                   // Exchange the function call with '1.0' and then check for
  | 
        
        
            | 
            | 
           2024 | 
                   // another function call...
  | 
        
        
            | 
            | 
           2025 | 
                   if ($regs[1]) {
  | 
        
        
            | 
            | 
           2026 | 
                       // The function call is proceeded by an operator.
  | 
        
        
            | 
            | 
           2027 | 
                       $formula = str_replace($regs[0], $regs[1] . '1.0', $formula);
  | 
        
        
            | 
            | 
           2028 | 
                   } else {
  | 
        
        
            | 
            | 
           2029 | 
                       // The function call starts the formula.
  | 
        
        
            | 
            | 
           2030 | 
                       $formula = preg_replace('~^' . preg_quote($regs[2], '~') . '\([^)]*\)~', '1.0', $formula);
  | 
        
        
            | 
            | 
           2031 | 
                   }
  | 
        
        
            | 
            | 
           2032 | 
               }
  | 
        
        
            | 
            | 
           2033 | 
              | 
        
        
            | 
            | 
           2034 | 
               if (preg_match("~[^{$safeoperatorchar}.0-9eE]+~", $formula, $regs)) {
  | 
        
        
            | 
            | 
           2035 | 
                   return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
  | 
        
        
            | 
            | 
           2036 | 
               } else {
  | 
        
        
            | 
            | 
           2037 | 
                   // Formula just might be valid.
  | 
        
        
            | 
            | 
           2038 | 
                   return false;
  | 
        
        
            | 
            | 
           2039 | 
               }
  | 
        
        
            | 
            | 
           2040 | 
           }
  | 
        
        
            | 
            | 
           2041 | 
              | 
        
        
            | 
            | 
           2042 | 
           /**
  | 
        
        
            | 
            | 
           2043 | 
            * Validate all the forumulas in a bit of text.
  | 
        
        
            | 
            | 
           2044 | 
            * @param string $text the text in which to validate the formulas.
  | 
        
        
            | 
            | 
           2045 | 
            * @return string|boolean false if there are no problems. Otherwise a string error message.
  | 
        
        
            | 
            | 
           2046 | 
            */
  | 
        
        
            | 
            | 
           2047 | 
           function qtype_calculated_find_formula_errors_in_text($text) {
  | 
        
        
            | 
            | 
           2048 | 
               $formulas = question_bank::get_qtype('calculated')->find_formulas($text);
  | 
        
        
            | 
            | 
           2049 | 
              | 
        
        
            | 
            | 
           2050 | 
               $errors = [];
  | 
        
        
            | 
            | 
           2051 | 
               foreach ($formulas as $match) {
  | 
        
        
            | 
            | 
           2052 | 
                   $error = qtype_calculated_find_formula_errors($match);
  | 
        
        
            | 
            | 
           2053 | 
                   if ($error) {
  | 
        
        
            | 
            | 
           2054 | 
                       $errors[] = $error;
  | 
        
        
            | 
            | 
           2055 | 
                   }
  | 
        
        
            | 
            | 
           2056 | 
               }
  | 
        
        
            | 
            | 
           2057 | 
              | 
        
        
            | 
            | 
           2058 | 
               if ($errors) {
  | 
        
        
            | 
            | 
           2059 | 
                   return implode(' ', $errors);
  | 
        
        
            | 
            | 
           2060 | 
               }
  | 
        
        
            | 
            | 
           2061 | 
              | 
        
        
            | 
            | 
           2062 | 
               return false;
  | 
        
        
            | 
            | 
           2063 | 
           }
  |