[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/question/type/numerical/ -> questiontype.php (source)

   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  /**
  19   * Question type class for the numerical question type.
  20   *
  21   * @package    qtype
  22   * @subpackage numerical
  23   * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  require_once($CFG->libdir . '/questionlib.php');
  31  require_once($CFG->dirroot . '/question/type/numerical/question.php');
  32  
  33  
  34  /**
  35   * The numerical question type class.
  36   *
  37   * This class contains some special features in order to make the
  38   * question type embeddable within a multianswer (cloze) question
  39   *
  40   * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class qtype_numerical extends question_type {
  44      const UNITINPUT = 0;
  45      const UNITRADIO = 1;
  46      const UNITSELECT = 2;
  47  
  48      const UNITNONE = 3;
  49      const UNITGRADED = 1;
  50      const UNITOPTIONAL = 0;
  51  
  52      const UNITGRADEDOUTOFMARK = 1;
  53      const UNITGRADEDOUTOFMAX = 2;
  54  
  55      public function get_question_options($question) {
  56          global $CFG, $DB, $OUTPUT;
  57          parent::get_question_options($question);
  58          // Get the question answers and their respective tolerances
  59          // Note: question_numerical is an extension of the answer table rather than
  60          //       the question table as is usually the case for qtype
  61          //       specific tables.
  62          if (!$question->options->answers = $DB->get_records_sql(
  63                                  "SELECT a.*, n.tolerance " .
  64                                  "FROM {question_answers} a, " .
  65                                  "     {question_numerical} n " .
  66                                  "WHERE a.question = ? " .
  67                                  "    AND   a.id = n.answer " .
  68                                  "ORDER BY a.id ASC", array($question->id))) {
  69              echo $OUTPUT->notification('Error: Missing question answer for numerical question ' .
  70                      $question->id . '!');
  71              return false;
  72          }
  73  
  74          $question->hints = $DB->get_records('question_hints',
  75                  array('questionid' => $question->id), 'id ASC');
  76  
  77          $this->get_numerical_units($question);
  78          // Get_numerical_options() need to know if there are units
  79          // to set correctly default values.
  80          $this->get_numerical_options($question);
  81  
  82          // If units are defined we strip off the default unit from the answer, if
  83          // it is present. (Required for compatibility with the old code and DB).
  84          if ($defaultunit = $this->get_default_numerical_unit($question)) {
  85              foreach ($question->options->answers as $key => $val) {
  86                  $answer = trim($val->answer);
  87                  $length = strlen($defaultunit->unit);
  88                  if ($length && substr($answer, -$length) == $defaultunit->unit) {
  89                      $question->options->answers[$key]->answer =
  90                              substr($answer, 0, strlen($answer)-$length);
  91                  }
  92              }
  93          }
  94  
  95          return true;
  96      }
  97  
  98      public function get_numerical_units(&$question) {
  99          global $DB;
 100  
 101          if ($units = $DB->get_records('question_numerical_units',
 102                  array('question' => $question->id), 'id ASC')) {
 103              $units = array_values($units);
 104          } else {
 105              $units = array();
 106          }
 107          foreach ($units as $key => $unit) {
 108              $units[$key]->multiplier = clean_param($unit->multiplier, PARAM_FLOAT);
 109          }
 110          $question->options->units = $units;
 111          return true;
 112      }
 113  
 114      public function get_default_numerical_unit($question) {
 115          if (isset($question->options->units[0])) {
 116              foreach ($question->options->units as $unit) {
 117                  if (abs($unit->multiplier - 1.0) < '1.0e-' . ini_get('precision')) {
 118                      return $unit;
 119                  }
 120              }
 121          }
 122          return false;
 123      }
 124  
 125      public function get_numerical_options($question) {
 126          global $DB;
 127          if (!$options = $DB->get_record('question_numerical_options',
 128                  array('question' => $question->id))) {
 129              // Old question, set defaults.
 130              $question->options->unitgradingtype = 0;
 131              $question->options->unitpenalty = 0.1;
 132              if ($defaultunit = $this->get_default_numerical_unit($question)) {
 133                  $question->options->showunits = self::UNITINPUT;
 134              } else {
 135                  $question->options->showunits = self::UNITNONE;
 136              }
 137              $question->options->unitsleft = 0;
 138  
 139          } else {
 140              $question->options->unitgradingtype = $options->unitgradingtype;
 141              $question->options->unitpenalty = $options->unitpenalty;
 142              $question->options->showunits = $options->showunits;
 143              $question->options->unitsleft = $options->unitsleft;
 144          }
 145  
 146          return true;
 147      }
 148  
 149      /**
 150       * Save the units and the answers associated with this question.
 151       */
 152      public function save_question_options($question) {
 153          global $DB;
 154          $context = $question->context;
 155  
 156          // Get old versions of the objects.
 157          $oldanswers = $DB->get_records('question_answers',
 158                  array('question' => $question->id), 'id ASC');
 159          $oldoptions = $DB->get_records('question_numerical',
 160                  array('question' => $question->id), 'answer ASC');
 161  
 162          // Save the units.
 163          $result = $this->save_units($question);
 164          if (isset($result->error)) {
 165              return $result;
 166          } else {
 167              $units = $result->units;
 168          }
 169  
 170          // Insert all the new answers.
 171          foreach ($question->answer as $key => $answerdata) {
 172              // Check for, and ingore, completely blank answer from the form.
 173              if (trim($answerdata) == '' && $question->fraction[$key] == 0 &&
 174                      html_is_blank($question->feedback[$key]['text'])) {
 175                  continue;
 176              }
 177  
 178              // Update an existing answer if possible.
 179              $answer = array_shift($oldanswers);
 180              if (!$answer) {
 181                  $answer = new stdClass();
 182                  $answer->question = $question->id;
 183                  $answer->answer = '';
 184                  $answer->feedback = '';
 185                  $answer->id = $DB->insert_record('question_answers', $answer);
 186              }
 187  
 188              if (trim($answerdata) === '*') {
 189                  $answer->answer = '*';
 190              } else {
 191                  $answer->answer = $this->apply_unit($answerdata, $units,
 192                          !empty($question->unitsleft));
 193                  if ($answer->answer === false) {
 194                      $result->notice = get_string('invalidnumericanswer', 'qtype_numerical');
 195                  }
 196              }
 197              $answer->fraction = $question->fraction[$key];
 198              $answer->feedback = $this->import_or_save_files($question->feedback[$key],
 199                      $context, 'question', 'answerfeedback', $answer->id);
 200              $answer->feedbackformat = $question->feedback[$key]['format'];
 201              $DB->update_record('question_answers', $answer);
 202  
 203              // Set up the options object.
 204              if (!$options = array_shift($oldoptions)) {
 205                  $options = new stdClass();
 206              }
 207              $options->question = $question->id;
 208              $options->answer   = $answer->id;
 209              if (trim($question->tolerance[$key]) == '') {
 210                  $options->tolerance = '';
 211              } else {
 212                  $options->tolerance = $this->apply_unit($question->tolerance[$key],
 213                          $units, !empty($question->unitsleft));
 214                  if ($options->tolerance === false) {
 215                      $result->notice = get_string('invalidnumerictolerance', 'qtype_numerical');
 216                  }
 217              }
 218              if (isset($options->id)) {
 219                  $DB->update_record('question_numerical', $options);
 220              } else {
 221                  $DB->insert_record('question_numerical', $options);
 222              }
 223          }
 224  
 225          // Delete any left over old answer records.
 226          $fs = get_file_storage();
 227          foreach ($oldanswers as $oldanswer) {
 228              $fs->delete_area_files($context->id, 'question', 'answerfeedback', $oldanswer->id);
 229              $DB->delete_records('question_answers', array('id' => $oldanswer->id));
 230          }
 231          foreach ($oldoptions as $oldoption) {
 232              $DB->delete_records('question_numerical', array('id' => $oldoption->id));
 233          }
 234  
 235          $result = $this->save_unit_options($question);
 236          if (!empty($result->error) || !empty($result->notice)) {
 237              return $result;
 238          }
 239  
 240          $this->save_hints($question);
 241  
 242          return true;
 243      }
 244  
 245      /**
 246       * The numerical options control the display and the grading of the unit
 247       * part of the numerical question and related types (calculateds)
 248       * Questions previous to 2.0 do not have this table as multianswer questions
 249       * in all versions including 2.0. The default values are set to give the same grade
 250       * as old question.
 251       *
 252       */
 253      public function save_unit_options($question) {
 254          global $DB;
 255          $result = new stdClass();
 256  
 257          $update = true;
 258          $options = $DB->get_record('question_numerical_options',
 259                  array('question' => $question->id));
 260          if (!$options) {
 261              $options = new stdClass();
 262              $options->question = $question->id;
 263              $options->id = $DB->insert_record('question_numerical_options', $options);
 264          }
 265  
 266          if (isset($question->unitpenalty)) {
 267              $options->unitpenalty = $question->unitpenalty;
 268          } else {
 269              // Either an old question or a close question type.
 270              $options->unitpenalty = 1;
 271          }
 272  
 273          $options->unitgradingtype = 0;
 274          if (isset($question->unitrole)) {
 275              // Saving the editing form.
 276              $options->showunits = $question->unitrole;
 277              if ($question->unitrole == self::UNITGRADED) {
 278                  $options->unitgradingtype = $question->unitgradingtypes;
 279                  $options->showunits = $question->multichoicedisplay;
 280              }
 281  
 282          } else if (isset($question->showunits)) {
 283              // Updated import, e.g. Moodle XML.
 284              $options->showunits = $question->showunits;
 285              if (isset($question->unitgradingtype)) {
 286                  $options->unitgradingtype = $question->unitgradingtype;
 287              }
 288          } else {
 289              // Legacy import.
 290              if ($defaultunit = $this->get_default_numerical_unit($question)) {
 291                  $options->showunits = self::UNITINPUT;
 292              } else {
 293                  $options->showunits = self::UNITNONE;
 294              }
 295          }
 296  
 297          $options->unitsleft = !empty($question->unitsleft);
 298  
 299          $DB->update_record('question_numerical_options', $options);
 300  
 301          // Report any problems.
 302          if (!empty($result->notice)) {
 303              return $result;
 304          }
 305  
 306          return true;
 307      }
 308  
 309      public function save_units($question) {
 310          global $DB;
 311          $result = new stdClass();
 312  
 313          // Delete the units previously saved for this question.
 314          $DB->delete_records('question_numerical_units', array('question' => $question->id));
 315  
 316          // Nothing to do.
 317          if (!isset($question->multiplier)) {
 318              $result->units = array();
 319              return $result;
 320          }
 321  
 322          // Save the new units.
 323          $units = array();
 324          $unitalreadyinsert = array();
 325          foreach ($question->multiplier as $i => $multiplier) {
 326              // Discard any unit which doesn't specify the unit or the multiplier.
 327              if (!empty($question->multiplier[$i]) && !empty($question->unit[$i]) &&
 328                      !array_key_exists($question->unit[$i], $unitalreadyinsert)) {
 329                  $unitalreadyinsert[$question->unit[$i]] = 1;
 330                  $units[$i] = new stdClass();
 331                  $units[$i]->question = $question->id;
 332                  $units[$i]->multiplier = $this->apply_unit($question->multiplier[$i],
 333                          array(), false);
 334                  $units[$i]->unit = $question->unit[$i];
 335                  $DB->insert_record('question_numerical_units', $units[$i]);
 336              }
 337          }
 338          unset($question->multiplier, $question->unit);
 339  
 340          $result->units = &$units;
 341          return $result;
 342      }
 343  
 344      protected function initialise_question_instance(question_definition $question, $questiondata) {
 345          parent::initialise_question_instance($question, $questiondata);
 346          $this->initialise_numerical_answers($question, $questiondata);
 347          $question->unitdisplay = $questiondata->options->showunits;
 348          $question->unitgradingtype = $questiondata->options->unitgradingtype;
 349          $question->unitpenalty = $questiondata->options->unitpenalty;
 350          $question->ap = $this->make_answer_processor($questiondata->options->units,
 351                  $questiondata->options->unitsleft);
 352      }
 353  
 354      public function initialise_numerical_answers(question_definition $question, $questiondata) {
 355          $question->answers = array();
 356          if (empty($questiondata->options->answers)) {
 357              return;
 358          }
 359          foreach ($questiondata->options->answers as $a) {
 360              $question->answers[$a->id] = new qtype_numerical_answer($a->id, $a->answer,
 361                      $a->fraction, $a->feedback, $a->feedbackformat, $a->tolerance);
 362          }
 363      }
 364  
 365      public function make_answer_processor($units, $unitsleft) {
 366          if (empty($units)) {
 367              return new qtype_numerical_answer_processor(array());
 368          }
 369  
 370          $cleanedunits = array();
 371          foreach ($units as $unit) {
 372              $cleanedunits[$unit->unit] = $unit->multiplier;
 373          }
 374  
 375          return new qtype_numerical_answer_processor($cleanedunits, $unitsleft);
 376      }
 377  
 378      public function delete_question($questionid, $contextid) {
 379          global $DB;
 380          $DB->delete_records('question_numerical', array('question' => $questionid));
 381          $DB->delete_records('question_numerical_options', array('question' => $questionid));
 382          $DB->delete_records('question_numerical_units', array('question' => $questionid));
 383  
 384          parent::delete_question($questionid, $contextid);
 385      }
 386  
 387      public function get_random_guess_score($questiondata) {
 388          foreach ($questiondata->options->answers as $aid => $answer) {
 389              if ('*' == trim($answer->answer)) {
 390                  return max($answer->fraction - $questiondata->options->unitpenalty, 0);
 391              }
 392          }
 393          return 0;
 394      }
 395  
 396      /**
 397       * Add a unit to a response for display.
 398       * @param object $questiondata the data defining the quetsion.
 399       * @param string $answer a response.
 400       * @param object $unit a unit. If null, {@link get_default_numerical_unit()}
 401       * is used.
 402       */
 403      public function add_unit($questiondata, $answer, $unit = null) {
 404          if (is_null($unit)) {
 405              $unit = $this->get_default_numerical_unit($questiondata);
 406          }
 407  
 408          if (!$unit) {
 409              return $answer;
 410          }
 411  
 412          if (!empty($questiondata->options->unitsleft)) {
 413              return $unit->unit . ' ' . $answer;
 414          } else {
 415              return $answer . ' ' . $unit->unit;
 416          }
 417      }
 418  
 419      public function get_possible_responses($questiondata) {
 420          $responses = array();
 421  
 422          $unit = $this->get_default_numerical_unit($questiondata);
 423  
 424          $starfound = false;
 425          foreach ($questiondata->options->answers as $aid => $answer) {
 426              $responseclass = $answer->answer;
 427  
 428              if ($responseclass === '*') {
 429                  $starfound = true;
 430              } else {
 431                  $responseclass = $this->add_unit($questiondata, $responseclass, $unit);
 432  
 433                  $ans = new qtype_numerical_answer($answer->id, $answer->answer, $answer->fraction,
 434                          $answer->feedback, $answer->feedbackformat, $answer->tolerance);
 435                  list($min, $max) = $ans->get_tolerance_interval();
 436                  $responseclass .= " ({$min}..{$max})";
 437              }
 438  
 439              $responses[$aid] = new question_possible_response($responseclass,
 440                      $answer->fraction);
 441          }
 442  
 443          if (!$starfound) {
 444              $responses[0] = new question_possible_response(
 445                      get_string('didnotmatchanyanswer', 'question'), 0);
 446          }
 447  
 448          $responses[null] = question_possible_response::no_response();
 449  
 450          return array($questiondata->id => $responses);
 451      }
 452  
 453      /**
 454       * Checks if the $rawresponse has a unit and applys it if appropriate.
 455       *
 456       * @param string $rawresponse  The response string to be converted to a float.
 457       * @param array $units         An array with the defined units, where the
 458       *                             unit is the key and the multiplier the value.
 459       * @return float               The rawresponse with the unit taken into
 460       *                             account as a float.
 461       */
 462      public function apply_unit($rawresponse, $units, $unitsleft) {
 463          $ap = $this->make_answer_processor($units, $unitsleft);
 464          list($value, $unit, $multiplier) = $ap->apply_units($rawresponse);
 465          if (!is_null($multiplier)) {
 466              $value *= $multiplier;
 467          }
 468          return $value;
 469      }
 470  
 471      public function move_files($questionid, $oldcontextid, $newcontextid) {
 472          $fs = get_file_storage();
 473  
 474          parent::move_files($questionid, $oldcontextid, $newcontextid);
 475          $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid);
 476          $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
 477      }
 478  
 479      protected function delete_files($questionid, $contextid) {
 480          $fs = get_file_storage();
 481  
 482          parent::delete_files($questionid, $contextid);
 483          $this->delete_files_in_answers($questionid, $contextid);
 484          $this->delete_files_in_hints($questionid, $contextid);
 485      }
 486  }
 487  
 488  
 489  /**
 490   * This class processes numbers with units.
 491   *
 492   * @copyright 2010 The Open University
 493   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 494   */
 495  class qtype_numerical_answer_processor {
 496      /** @var array unit name => multiplier. */
 497      protected $units;
 498      /** @var string character used as decimal point. */
 499      protected $decsep;
 500      /** @var string character used as thousands separator. */
 501      protected $thousandssep;
 502      /** @var boolean whether the units come before or after the number. */
 503      protected $unitsbefore;
 504  
 505      protected $regex = null;
 506  
 507      public function __construct($units, $unitsbefore = false, $decsep = null,
 508              $thousandssep = null) {
 509          if (is_null($decsep)) {
 510              $decsep = get_string('decsep', 'langconfig');
 511          }
 512          $this->decsep = $decsep;
 513  
 514          if (is_null($thousandssep)) {
 515              $thousandssep = get_string('thousandssep', 'langconfig');
 516          }
 517          $this->thousandssep = $thousandssep;
 518  
 519          $this->units = $units;
 520          $this->unitsbefore = $unitsbefore;
 521      }
 522  
 523      /**
 524       * Set the decimal point and thousands separator character that should be used.
 525       * @param string $decsep
 526       * @param string $thousandssep
 527       */
 528      public function set_characters($decsep, $thousandssep) {
 529          $this->decsep = $decsep;
 530          $this->thousandssep = $thousandssep;
 531          $this->regex = null;
 532      }
 533  
 534      /** @return string the decimal point character used. */
 535      public function get_point() {
 536          return $this->decsep;
 537      }
 538  
 539      /** @return string the thousands separator character used. */
 540      public function get_separator() {
 541          return $this->thousandssep;
 542      }
 543  
 544      /**
 545       * @return book If the student's response contains a '.' or a ',' that
 546       * matches the thousands separator in the current locale. In this case, the
 547       * parsing in apply_unit can give a result that the student did not expect.
 548       */
 549      public function contains_thousands_seaparator($value) {
 550          if (!in_array($this->thousandssep, array('.', ','))) {
 551              return false;
 552          }
 553  
 554          return strpos($value, $this->thousandssep) !== false;
 555      }
 556  
 557      /**
 558       * Create the regular expression that {@link parse_response()} requires.
 559       * @return string
 560       */
 561      protected function build_regex() {
 562          if (!is_null($this->regex)) {
 563              return $this->regex;
 564          }
 565  
 566          $decsep = preg_quote($this->decsep, '/');
 567          $thousandssep = preg_quote($this->thousandssep, '/');
 568          $beforepointre = '([+-]?[' . $thousandssep . '\d]*)';
 569          $decimalsre = $decsep . '(\d*)';
 570          $exponentre = '(?:e|E|(?:x|\*|×)10(?:\^|\*\*))([+-]?\d+)';
 571  
 572          $numberbit = "{$beforepointre}(?:{$decimalsre})?(?:{$exponentre})?";
 573  
 574          if ($this->unitsbefore) {
 575              $this->regex = "/{$numberbit}$/";
 576          } else {
 577              $this->regex = "/^{$numberbit}/";
 578          }
 579          return $this->regex;
 580      }
 581  
 582      /**
 583       * This method can be used for more locale-strict parsing of repsonses. At the
 584       * moment we don't use it, and instead use the more lax parsing in apply_units.
 585       * This is just a note that this funciton was used in the past, so if you are
 586       * intersted, look through version control history.
 587       *
 588       * Take a string which is a number with or without a decimal point and exponent,
 589       * and possibly followed by one of the units, and split it into bits.
 590       * @param string $response a value, optionally with a unit.
 591       * @return array four strings (some of which may be blank) the digits before
 592       * and after the decimal point, the exponent, and the unit. All four will be
 593       * null if the response cannot be parsed.
 594       */
 595      protected function parse_response($response) {
 596          if (!preg_match($this->build_regex(), $response, $matches)) {
 597              return array(null, null, null, null);
 598          }
 599  
 600          $matches += array('', '', '', ''); // Fill in any missing matches.
 601          list($matchedpart, $beforepoint, $decimals, $exponent) = $matches;
 602  
 603          // Strip out thousands separators.
 604          $beforepoint = str_replace($this->thousandssep, '', $beforepoint);
 605  
 606          // Must be either something before, or something after the decimal point.
 607          // (The only way to do this in the regex would make it much more complicated.)
 608          if ($beforepoint === '' && $decimals === '') {
 609              return array(null, null, null, null);
 610          }
 611  
 612          if ($this->unitsbefore) {
 613              $unit = substr($response, 0, -strlen($matchedpart));
 614          } else {
 615              $unit = substr($response, strlen($matchedpart));
 616          }
 617          $unit = trim($unit);
 618  
 619          return array($beforepoint, $decimals, $exponent, $unit);
 620      }
 621  
 622      /**
 623       * Takes a number in almost any localised form, and possibly with a unit
 624       * after it. It separates off the unit, if present, and converts to the
 625       * default unit, by using the given unit multiplier.
 626       *
 627       * @param string $response a value, optionally with a unit.
 628       * @return array(numeric, sting) the value with the unit stripped, and normalised
 629       *      by the unit multiplier, if any, and the unit string, for reference.
 630       */
 631      public function apply_units($response, $separateunit = null) {
 632          // Strip spaces (which may be thousands separators) and change other forms
 633          // of writing e to e.
 634          $response = str_replace(' ', '', $response);
 635          $response = preg_replace('~(?:e|E|(?:x|\*|×)10(?:\^|\*\*))([+-]?\d+)~', 'e$1', $response);
 636  
 637          // If a . is present or there are multiple , (i.e. 2,456,789 ) assume ,
 638          // is a thouseands separator, and strip it, else assume it is a decimal
 639          // separator, and change it to ..
 640          if (strpos($response, '.') !== false || substr_count($response, ',') > 1) {
 641              $response = str_replace(',', '', $response);
 642          } else {
 643              $response = str_replace(',', '.', $response);
 644          }
 645  
 646          $regex = '[+-]?(?:\d+(?:\\.\d*)?|\\.\d+)(?:e[-+]?\d+)?';
 647          if ($this->unitsbefore) {
 648              $regex = "/{$regex}$/";
 649          } else {
 650              $regex = "/^{$regex}/";
 651          }
 652          if (!preg_match($regex, $response, $matches)) {
 653              return array(null, null, null);
 654          }
 655  
 656          $numberstring = $matches[0];
 657          if ($this->unitsbefore) {
 658              // Substr returns false when it means '', so cast back to string.
 659              $unit = (string) substr($response, 0, -strlen($numberstring));
 660          } else {
 661              $unit = (string) substr($response, strlen($numberstring));
 662          }
 663  
 664          if (!is_null($separateunit)) {
 665              $unit = $separateunit;
 666          }
 667  
 668          if ($this->is_known_unit($unit)) {
 669              $multiplier = 1 / $this->units[$unit];
 670          } else {
 671              $multiplier = null;
 672          }
 673  
 674          return array($numberstring + 0, $unit, $multiplier); // The + 0 is to convert to number.
 675      }
 676  
 677      /**
 678       * @return string the default unit.
 679       */
 680      public function get_default_unit() {
 681          reset($this->units);
 682          return key($this->units);
 683      }
 684  
 685      /**
 686       * @param string $answer a response.
 687       * @param string $unit a unit.
 688       */
 689      public function add_unit($answer, $unit = null) {
 690          if (is_null($unit)) {
 691              $unit = $this->get_default_unit();
 692          }
 693  
 694          if (!$unit) {
 695              return $answer;
 696          }
 697  
 698          if ($this->unitsbefore) {
 699              return $unit . ' ' . $answer;
 700          } else {
 701              return $answer . ' ' . $unit;
 702          }
 703      }
 704  
 705      /**
 706       * Is this unit recognised.
 707       * @param string $unit the unit
 708       * @return bool whether this is a unit we recognise.
 709       */
 710      public function is_known_unit($unit) {
 711          return array_key_exists($unit, $this->units);
 712      }
 713  
 714      /**
 715       * Whether the units go before or after the number.
 716       * @return true = before, false = after.
 717       */
 718      public function are_units_before() {
 719          return $this->unitsbefore;
 720      }
 721  
 722      /**
 723       * Get the units as an array suitably for passing to html_writer::select.
 724       * @return array of unit choices.
 725       */
 726      public function get_unit_options() {
 727          $options = array();
 728          foreach ($this->units as $unit => $notused) {
 729              $options[$unit] = $unit;
 730          }
 731          return $options;
 732      }
 733  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1