Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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   * This defines the states a question can be in.
  19   *
  20   * @package    moodlecore
  21   * @subpackage questionengine
  22   * @copyright  2010 The Open University
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  
  30  /**
  31   * An enumeration representing the states a question can be in after a
  32   * {@link question_attempt_step}.
  33   *
  34   * There are also some useful methods for testing and manipulating states.
  35   *
  36   * @copyright  2009 The Open University
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  abstract class question_state {
  40      /**#@+
  41       * Specific question_state instances.
  42       */
  43      public static $notstarted;
  44      public static $unprocessed;
  45      public static $todo;
  46      public static $invalid;
  47      public static $complete;
  48      public static $needsgrading;
  49      public static $finished;
  50      public static $gaveup;
  51      public static $gradedwrong;
  52      public static $gradedpartial;
  53      public static $gradedright;
  54      public static $manfinished;
  55      public static $mangaveup;
  56      public static $mangrwrong;
  57      public static $mangrpartial;
  58      public static $mangrright;
  59      /**#@+-*/
  60  
  61      protected function __construct() {
  62      }
  63  
  64      public static function init() {
  65          $us = new ReflectionClass('question_state');
  66          foreach ($us->getStaticProperties() as $name => $notused) {
  67              $class = 'question_state_' . $name;
  68              $states[$name] = new $class();
  69              self::$$name = $states[$name];
  70          }
  71      }
  72  
  73      /**
  74       * Get all the states in an array.
  75       *
  76       * @return question_state[] of question_state objects.
  77       */
  78      public static function get_all() {
  79          $states = array();
  80          $us = new ReflectionClass('question_state');
  81          foreach ($us->getStaticProperties() as $name => $notused) {
  82              $states[] = self::$$name;
  83          }
  84          return $states;
  85      }
  86  
  87      /**
  88       * Get all the states in an array.
  89       * @param string $summarystate one of the four summary states
  90       * inprogress, needsgrading, manuallygraded or autograded.
  91       * @return array of the corresponding states.
  92       */
  93      public static function get_all_for_summary_state($summarystate) {
  94          $states = array();
  95          foreach (self::get_all() as $state) {
  96              if ($state->get_summary_state() == $summarystate) {
  97                  $states[] = $state;
  98              }
  99          }
 100          if (empty($states)) {
 101              throw new coding_exception('unknown summary state ' . $summarystate);
 102          }
 103          return $states;
 104      }
 105  
 106      /**
 107       * @return string convert this state to a string.
 108       */
 109      public function __toString() {
 110          return substr(get_class($this), 15);
 111      }
 112  
 113      /**
 114       * Get the instance of this class for a given state name.
 115       *
 116       * @param string $name a state name.
 117       * @return question_state|null the state with that name. (Null only in an exceptional case.)
 118       */
 119      public static function get(string $name): ?question_state {
 120          // In the past, there was a bug where null states got stored
 121          // in the database as an empty string, which was wrong because
 122          // the state column should be NOT NULL.
 123          // That is no longer possible, but we need to avoid exceptions
 124          // for people with old bad data in their database.
 125          if ($name === '') {
 126              debugging('Attempt to create a state from an empty string. ' .
 127                  'This is probably a sign of bad data in your database. See MDL-80127.');
 128              return null;
 129          }
 130  
 131          return self::$$name;
 132      }
 133  
 134      /**
 135       * Is this state one of the ones that mean the question attempt is in progress?
 136       * That is, started, but no finished.
 137       * @return bool
 138       */
 139      public function is_active() {
 140          return false;
 141      }
 142  
 143      /**
 144       * Is this state one of the ones that mean the question attempt is finished?
 145       * That is, no further interaction possible, apart from manual grading.
 146       * @return bool
 147       */
 148      public function is_finished() {
 149          return true;
 150      }
 151  
 152      /**
 153       * Is this state one of the ones that mean the question attempt has been graded?
 154       * @return bool
 155       */
 156      public function is_graded() {
 157          return false;
 158      }
 159  
 160      /**
 161       * Is this state one of the ones that mean the question attempt has been graded?
 162       * @return bool
 163       */
 164      public function is_correct() {
 165          return false;
 166      }
 167  
 168      /**
 169       * Is this state one of the ones that mean the question attempt has been graded?
 170       * @return bool
 171       */
 172      public function is_partially_correct() {
 173          return false;
 174      }
 175  
 176      /**
 177       * Is this state one of the ones that mean the question attempt has been graded?
 178       * @return bool
 179       */
 180      public function is_incorrect() {
 181          return false;
 182      }
 183  
 184      /**
 185       * Is this state one of the ones that mean the question attempt has been graded?
 186       * @return bool
 187       */
 188      public function is_gave_up() {
 189          return false;
 190      }
 191  
 192      /**
 193       * Is this state one of the ones that mean the question attempt has had a manual comment added?
 194       * @return bool
 195       */
 196      public function is_commented() {
 197          return false;
 198      }
 199  
 200      /**
 201       * Each state can be categorised into one of four categories:
 202       * inprogress, needsgrading, manuallygraded or autograded.
 203       * @return string which category this state falls into.
 204       */
 205      public function get_summary_state() {
 206          if (!$this->is_finished()) {
 207              return 'inprogress';
 208          } else if ($this == self::$needsgrading) {
 209              return 'needsgrading';
 210          } else if ($this->is_commented()) {
 211              return 'manuallygraded';
 212          } else {
 213              return 'autograded';
 214          }
 215      }
 216  
 217      /**
 218       * Return the appropriate graded state based on a fraction. That is 0 or less
 219       * is $graded_incorrect, 1 is $graded_correct, otherwise it is $graded_partcorrect.
 220       * Appropriate allowance is made for rounding float values.
 221       *
 222       * @param number $fraction the grade, on the fraction scale.
 223       * @return question_state one of the state constants.
 224       */
 225      public static function graded_state_for_fraction($fraction) {
 226          if ($fraction < 0.000001) {
 227              return self::$gradedwrong;
 228          } else if ($fraction > 0.999999) {
 229              return self::$gradedright;
 230          } else {
 231              return self::$gradedpartial;
 232          }
 233      }
 234  
 235      /**
 236       * Return the appropriate manually graded state based on a fraction. That is 0 or less
 237       * is $manually_graded_incorrect, 1 is $manually_graded_correct, otherwise it is
 238       * $manually_graded_partcorrect. Appropriate allowance is made for rounding float values.
 239       *
 240       * @param number $fraction the grade, on the fraction scale.
 241       * @return int one of the state constants.
 242       */
 243      public static function manually_graded_state_for_fraction($fraction) {
 244          if (is_null($fraction)) {
 245              return self::$needsgrading;
 246          } else if ($fraction < 0.000001) {
 247              return self::$mangrwrong;
 248          } else if ($fraction > 0.999999) {
 249              return self::$mangrright;
 250          } else {
 251              return self::$mangrpartial;
 252          }
 253      }
 254  
 255      /**
 256       * Compute an appropriate state to move to after a manual comment has been
 257       * added to this state.
 258       * @param number $fraction the manual grade (if any) on the fraction scale.
 259       * @return int the new state.
 260       */
 261      public function corresponding_commented_state($fraction) {
 262          throw new coding_exception('Unexpected question state.');
 263      }
 264  
 265      /**
 266       * Return an appropriate CSS class name ''/'correct'/'partiallycorrect'/'incorrect',
 267       * for a state.
 268       * @return string
 269       */
 270      public function get_feedback_class() {
 271          return '';
 272      }
 273  
 274      /**
 275       * Return the name of an appropriate string to look up in the question
 276       * language pack for a state. This is used, for example, by
 277       * {@link question_behaviour::get_state_string()}. However, behaviours
 278       * sometimes change this default string for soemthing more specific.
 279       *
 280       * @param bool $showcorrectness Whether right/partial/wrong states should
 281       * be distinguised, or just treated as 'complete'.
 282       * @return string the name of a string that can be looked up in the 'question'
 283       *      lang pack, or used as a CSS class name, etc.
 284       */
 285      public abstract function get_state_class($showcorrectness);
 286  
 287      /**
 288       * The result of doing get_string on the result of {@link get_state_class()}.
 289       *
 290       * @param bool $showcorrectness Whether right/partial/wrong states should
 291       * be distinguised.
 292       * @return string a string from the lang pack that can be used in the UI.
 293       */
 294      public function default_string($showcorrectness) {
 295          return get_string($this->get_state_class($showcorrectness), 'question');
 296      }
 297  }
 298  
 299  
 300  /**#@+
 301   * Specific question_state subclasses.
 302   *
 303   * @copyright  2009 The Open University
 304   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 305   */
 306  class question_state_notstarted extends question_state {
 307      public function is_finished() {
 308          return false;
 309      }
 310      public function get_state_class($showcorrectness) {
 311          throw new coding_exception('Unexpected question state.');
 312      }
 313  }
 314  class question_state_unprocessed extends question_state {
 315      public function is_finished() {
 316          return false;
 317      }
 318      public function get_state_class($showcorrectness) {
 319          throw new coding_exception('Unexpected question state.');
 320      }
 321  }
 322  class question_state_todo extends question_state {
 323      public function is_active() {
 324          return true;
 325      }
 326      public function is_finished() {
 327          return false;
 328      }
 329      public function get_state_class($showcorrectness) {
 330          return 'notyetanswered';
 331      }
 332  }
 333  class question_state_invalid extends question_state {
 334      public function is_active() {
 335          return true;
 336      }
 337      public function is_finished() {
 338          return false;
 339      }
 340      public function get_state_class($showcorrectness) {
 341          return 'invalidanswer';
 342      }
 343  }
 344  class question_state_complete extends question_state {
 345      public function is_active() {
 346          return true;
 347      }
 348      public function is_finished() {
 349          return false;
 350      }
 351      public function get_state_class($showcorrectness) {
 352          return 'answersaved';
 353      }
 354  }
 355  class question_state_needsgrading extends question_state {
 356      public function get_state_class($showcorrectness) {
 357          if ($showcorrectness) {
 358              return 'requiresgrading';
 359          } else {
 360              return 'complete';
 361          }
 362      }
 363      public function corresponding_commented_state($fraction) {
 364          return self::manually_graded_state_for_fraction($fraction);
 365      }
 366  }
 367  class question_state_finished extends question_state {
 368      public function get_state_class($showcorrectness) {
 369          return 'complete';
 370      }
 371      public function corresponding_commented_state($fraction) {
 372          return self::$manfinished;
 373      }
 374  }
 375  class question_state_gaveup extends question_state {
 376      public function is_gave_up() {
 377          return true;
 378      }
 379      public function get_feedback_class() {
 380          return 'incorrect';
 381      }
 382      public function get_state_class($showcorrectness) {
 383          return 'notanswered';
 384      }
 385      public function corresponding_commented_state($fraction) {
 386          if (is_null($fraction)) {
 387              return self::$mangaveup;
 388          } else {
 389              return self::manually_graded_state_for_fraction($fraction);
 390          }
 391      }
 392  }
 393  abstract class question_state_graded extends question_state {
 394      public function is_graded() {
 395          return true;
 396      }
 397      public function get_state_class($showcorrectness) {
 398          if ($showcorrectness) {
 399              return $this->get_feedback_class();
 400          } else {
 401              return 'complete';
 402          }
 403      }
 404      public function corresponding_commented_state($fraction) {
 405          return self::manually_graded_state_for_fraction($fraction);
 406      }
 407  }
 408  class question_state_gradedwrong extends question_state_graded {
 409      public function is_incorrect() {
 410          return true;
 411      }
 412      public function get_feedback_class() {
 413          return 'incorrect';
 414      }
 415  }
 416  class question_state_gradedpartial extends question_state_graded {
 417      public function is_graded() {
 418          return true;
 419      }
 420      public function is_partially_correct() {
 421          return true;
 422      }
 423      public function get_feedback_class() {
 424          return 'partiallycorrect';
 425      }
 426  }
 427  class question_state_gradedright extends question_state_graded {
 428      public function is_graded() {
 429          return true;
 430      }
 431      public function is_correct() {
 432          return true;
 433      }
 434      public function get_feedback_class() {
 435          return 'correct';
 436      }
 437  }
 438  class question_state_manfinished extends question_state_finished {
 439      public function is_commented() {
 440          return true;
 441      }
 442  }
 443  class question_state_mangaveup extends question_state_gaveup {
 444      public function is_commented() {
 445          return true;
 446      }
 447  }
 448  abstract class question_state_manuallygraded extends question_state_graded {
 449      public function is_commented() {
 450          return true;
 451      }
 452  }
 453  class question_state_mangrwrong extends question_state_manuallygraded {
 454      public function is_incorrect() {
 455          return false;
 456      }
 457      public function get_feedback_class() {
 458          return 'incorrect';
 459      }
 460  }
 461  class question_state_mangrpartial extends question_state_manuallygraded {
 462      public function is_partially_correct() {
 463          return true;
 464      }
 465      public function get_feedback_class() {
 466          return 'partiallycorrect';
 467      }
 468  }
 469  class question_state_mangrright extends question_state_manuallygraded {
 470      public function is_correct() {
 471          return true;
 472      }
 473      public function get_feedback_class() {
 474          return 'correct';
 475      }
 476  }
 477  /**#@-*/
 478  question_state::init();