Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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 file defines the question attempt step class, and a few related classes.
      19   *
      20   * @package    moodlecore
      21   * @subpackage questionengine
      22   * @copyright  2009 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   * Stores one step in a {@link question_attempt}.
      32   *
      33   * The most important attributes of a step are the state, which is one of the
      34   * {@link question_state} constants, the fraction, which may be null, or a
      35   * number bewteen the attempt's minfraction and maxfraction, and the array of submitted
      36   * data, about which more later.
      37   *
      38   * A step also tracks the time it was created, and the user responsible for
      39   * creating it.
      40   *
      41   * The submitted data is basically just an array of name => value pairs, with
      42   * certain conventions about the to divide the variables into four = two times two
      43   * categories.
      44   *
      45   * Variables may either belong to the behaviour, in which case the
      46   * name starts with a '-', or they may belong to the question type in which case
      47   * they name does not start with a '-'.
      48   *
      49   * Second, variables may either be ones that came form the original request, in
      50   * which case the name does not start with an _, or they are cached values that
      51   * were created during processing, in which case the name does start with an _.
      52   *
      53   * That is, each name will start with one of '', '_'. '-' or '-_'. The remainder
      54   * of the name should match the regex [a-z][a-z0-9]*.
      55   *
      56   * These variables can be accessed with {@link get_behaviour_var()} and {@link get_qt_var()},
      57   * - to be clear, ->get_behaviour_var('x') gets the variable with name '-x' -
      58   * and values whose names start with '_' can be set using {@link set_behaviour_var()}
      59   * and {@link set_qt_var()}. There are some other methods like {@link has_behaviour_var()}
      60   * to check wether a varaible with a particular name is set, and {@link get_behaviour_data()}
      61   * to get all the behaviour data as an associative array.
      62   *
      63   * @copyright  2009 The Open University
      64   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      65   */
      66  class question_attempt_step {
      67      /**
      68       * @var integer if this attempts is stored in the question_attempts table,
      69       * the id of that row.
      70       */
      71      private $id = null;
      72  
      73      /**
      74       * @var question_state one of the {@link question_state} constants.
      75       * The state after this step.
      76       */
      77      private $state;
      78  
      79      /**
      80       * @var null|number the fraction (grade on a scale of
      81       * minfraction .. maxfraction, normally 0..1) or null.
      82       */
      83      private $fraction = null;
      84  
      85      /** @var integer the timestamp when this step was created. */
      86      private $timecreated;
      87  
      88      /** @var integer the id of the user resonsible for creating this step. */
      89      private $userid;
      90  
      91      /** @var array name => value pairs. The submitted data. */
      92      private $data;
      93  
      94      /** @var array name => array of {@link stored_file}s. Caches the contents of file areas. */
      95      private $files = array();
      96  
      97      /** @var stdClass User information. */
      98      private $user = null;
      99  
     100      /**
     101       * You should not need to call this constructor in your own code. Steps are
     102       * normally created by {@link question_attempt} methods like
     103       * {@link question_attempt::process_action()}.
     104       * @param array $data the submitted data that defines this step.
     105       * @param int $timestamp the time to record for the action. (If not given, use now.)
     106       * @param int $userid the user to attribute the aciton to. (If not given, use the current user.)
     107       * @param int $existingstepid if this step is going to replace an existing step
     108       *      (for example, during a regrade) this is the id of the previous step we are replacing.
     109       */
     110      public function __construct($data = array(), $timecreated = null, $userid = null,
     111              $existingstepid = null) {
     112          global $USER;
     113  
     114          if (!is_array($data)) {
     115              throw new coding_exception('$data must be an array when constructing a question_attempt_step.');
     116          }
     117          $this->state = question_state::$unprocessed;
     118          $this->data = $data;
     119          if (is_null($timecreated)) {
     120              $this->timecreated = time();
     121          } else {
     122              $this->timecreated = $timecreated;
     123          }
     124          if (is_null($userid)) {
     125              $this->userid = $USER->id;
     126          } else {
     127              $this->userid = $userid;
     128          }
     129  
     130          if (!is_null($existingstepid)) {
     131              $this->id = $existingstepid;
     132          }
     133      }
     134  
     135      /**
     136       * @return int|null The id of this step in the database. null if this step
     137       * is not stored in the database.
     138       */
     139      public function get_id() {
     140          return $this->id;
     141      }
     142  
     143      /** @return question_state The state after this step. */
     144      public function get_state() {
     145          return $this->state;
     146      }
     147  
     148      /**
     149       * Set the state. Normally only called by behaviours.
     150       * @param question_state $state one of the {@link question_state} constants.
     151       */
     152      public function set_state($state) {
     153          $this->state = $state;
     154      }
     155  
     156      /**
     157       * @return null|number the fraction (grade on a scale of
     158       * minfraction .. maxfraction, normally 0..1),
     159       * or null if this step has not been marked.
     160       */
     161      public function get_fraction() {
     162          return $this->fraction;
     163      }
     164  
     165      /**
     166       * Set the fraction. Normally only called by behaviours.
     167       * @param null|number $fraction the fraction to set.
     168       */
     169      public function set_fraction($fraction) {
     170          $this->fraction = $fraction;
     171      }
     172  
     173      /** @return int the id of the user resonsible for creating this step. */
     174      public function get_user_id() {
     175          return $this->userid;
     176      }
     177  
     178      /**
     179       * Update full user information for step.
     180       *
     181       * @param stdClass $user Full user object.
     182       * @throws coding_exception
     183       */
     184      public function add_full_user_object(stdClass $user): void {
     185          if ($user->id != $this->userid) {
     186              throw new coding_exception('Wrong user passed to add_full_user_object');
     187          }
     188          $this->user = $user;
     189      }
     190  
     191      /**
     192       * Return the full user object.
     193       *
     194       * @return stdClass Get full user object.
     195       */
     196      public function get_user(): stdClass {
     197          return $this->user;
     198      }
     199  
     200      /**
     201       * Get full name of user who did action.
     202       *
     203       * @return string full name of user.
     204       */
     205      public function get_user_fullname(): string {
     206          return fullname($this->user);
     207      }
     208  
     209      /** @return int the timestamp when this step was created. */
     210      public function get_timecreated() {
     211          return $this->timecreated;
     212      }
     213  
     214      /**
     215       * @param string $name the name of a question type variable to look for in the submitted data.
     216       * @return bool whether a variable with this name exists in the question type data.
     217       */
     218      public function has_qt_var($name) {
     219          return array_key_exists($name, $this->data);
     220      }
     221  
     222      /**
     223       * @param string $name the name of a question type variable to look for in the submitted data.
     224       * @return string the requested variable, or null if the variable is not set.
     225       */
     226      public function get_qt_var($name) {
     227          if (!$this->has_qt_var($name)) {
     228              return null;
     229          }
     230          return $this->data[$name];
     231      }
     232  
     233      /**
     234       * Set a cached question type variable.
     235       * @param string $name the name of the variable to set. Must match _[a-z][a-z0-9]*.
     236       * @param string $value the value to set.
     237       */
     238      public function set_qt_var($name, $value) {
     239          if ($name[0] != '_') {
     240              throw new coding_exception('Cannot set question type data ' . $name .
     241                      ' on an attempt step. You can only set variables with names begining with _.');
     242          }
     243          $this->data[$name] = $value;
     244      }
     245  
     246      /**
     247       * Get the latest set of files for a particular question type variable of
     248       * type question_attempt::PARAM_FILES.
     249       *
     250       * @param string $name the name of the associated variable.
     251       * @return array of {@link stored_files}.
     252       */
     253      public function get_qt_files($name, $contextid) {
     254          if (array_key_exists($name, $this->files)) {
     255              return $this->files[$name];
     256          }
     257  
     258          if (!$this->has_qt_var($name)) {
     259              $this->files[$name] = array();
     260              return array();
     261          }
     262  
     263          $fs = get_file_storage();
     264          $this->files[$name] = $fs->get_area_files($contextid, 'question',
     265                  'response_' . $name, $this->id, 'sortorder', false);
     266  
     267          return $this->files[$name];
     268      }
     269  
     270      /**
     271       * Prepare a draft file are for the files belonging the a response variable
     272       * of this step.
     273       *
     274       * @param string $name the variable name the files belong to.
     275       * @param int $contextid the id of the context the quba belongs to.
     276       * @return int the draft itemid.
     277       */
     278      public function prepare_response_files_draft_itemid($name, $contextid) {
     279          list($draftid, $notused) = $this->prepare_response_files_draft_itemid_with_text(
     280                  $name, $contextid, null);
     281          return $draftid;
     282      }
     283  
     284      /**
     285       * Prepare a draft file are for the files belonging the a response variable
     286       * of this step, while rewriting the URLs in some text.
     287       *
     288       * @param string $name the variable name the files belong to.
     289       * @param int $contextid the id of the context the quba belongs to.
     290       * @param string $text the text to update the URLs in.
     291       * @return array(int, string) the draft itemid and the text with URLs rewritten.
     292       */
     293      public function prepare_response_files_draft_itemid_with_text($name, $contextid, $text) {
     294          $draftid = 0; // Will be filled in by file_prepare_draft_area.
     295          $newtext = file_prepare_draft_area($draftid, $contextid, 'question',
     296                  'response_' . $name, $this->id, null, $text);
     297          return array($draftid, $newtext);
     298      }
     299  
     300      /**
     301       * Rewrite the @@PLUGINFILE@@ tokens in a response variable from this step
     302       * that contains links to file. Normally you should probably call
     303       * {@link question_attempt::rewrite_response_pluginfile_urls()} instead of
     304       * calling this method directly.
     305       *
     306       * @param string $text the text to update the URLs in.
     307       * @param int $contextid the id of the context the quba belongs to.
     308       * @param string $name the variable name the files belong to.
     309       * @param array $extra extra file path components.
     310       * @return string the rewritten text.
     311       */
     312      public function rewrite_response_pluginfile_urls($text, $contextid, $name, $extras) {
     313          return question_rewrite_question_urls($text, 'pluginfile.php', $contextid,
     314                  'question', 'response_' . $name, $extras, $this->id);
     315      }
     316  
     317      /**
     318       * Get all the question type variables.
     319       * @param array name => value pairs.
     320       */
     321      public function get_qt_data() {
     322          $result = array();
     323          foreach ($this->data as $name => $value) {
     324              if ($name[0] != '-' && $name[0] != ':') {
     325                  $result[$name] = $value;
     326              }
     327          }
     328          return $result;
     329      }
     330  
     331      /**
     332       * @param string $name the name of a behaviour variable to look for in the submitted data.
     333       * @return bool whether a variable with this name exists in the question type data.
     334       */
     335      public function has_behaviour_var($name) {
     336          return array_key_exists('-' . $name, $this->data);
     337      }
     338  
     339      /**
     340       * @param string $name the name of a behaviour variable to look for in the submitted data.
     341       * @return string the requested variable, or null if the variable is not set.
     342       */
     343      public function get_behaviour_var($name) {
     344          if (!$this->has_behaviour_var($name)) {
     345              return null;
     346          }
     347          return $this->data['-' . $name];
     348      }
     349  
     350      /**
     351       * Set a cached behaviour variable.
     352       * @param string $name the name of the variable to set. Must match _[a-z][a-z0-9]*.
     353       * @param string $value the value to set.
     354       */
     355      public function set_behaviour_var($name, $value) {
     356          if ($name[0] != '_') {
     357              throw new coding_exception('Cannot set question type data ' . $name .
     358                      ' on an attempt step. You can only set variables with names begining with _.');
     359          }
     360          return $this->data['-' . $name] = $value;
     361      }
     362  
     363      /**
     364       * Get all the behaviour variables.
     365       * @param array name => value pairs.
     366       */
     367      public function get_behaviour_data() {
     368          $result = array();
     369          foreach ($this->data as $name => $value) {
     370              if ($name[0] == '-') {
     371                  $result[substr($name, 1)] = $value;
     372              }
     373          }
     374          return $result;
     375      }
     376  
     377      /**
     378       * Get all the submitted data, but not the cached data. behaviour
     379       * variables have the - at the start of their name. This is only really
     380       * intended for use by {@link question_attempt::regrade()}, it should not
     381       * be considered part of the public API.
     382       * @param array name => value pairs.
     383       */
     384      public function get_submitted_data() {
     385          $result = array();
     386          foreach ($this->data as $name => $value) {
     387              if ($name[0] == '_' || ($name[0] == '-' && $name[1] == '_')) {
     388                  continue;
     389              }
     390              $result[$name] = $value;
     391          }
     392          return $result;
     393      }
     394  
     395      /**
     396       * Get all the data. behaviour variables have the - at the start of
     397       * their name. This is only intended for internal use, for example by
     398       * {@link question_engine_data_mapper::insert_question_attempt_step()},
     399       * however, it can occasionally be useful in test code. It should not be
     400       * considered part of the public API of this class.
     401       * @param array name => value pairs.
     402       */
     403      public function get_all_data() {
     404          return $this->data;
     405      }
     406  
     407      /**
     408       * Set a metadata variable.
     409       *
     410       * Do not call this method directly from  your code. It is for internal
     411       * use only. You should call {@link question_usage::set_question_attempt_metadata()}.
     412       *
     413       * @param string $name the name of the variable to set. [a-z][a-z0-9]*.
     414       * @param string $value the value to set.
     415       */
     416      public function set_metadata_var($name, $value) {
     417          $this->data[':_' . $name] = $value;
     418      }
     419  
     420      /**
     421       * Whether this step has a metadata variable.
     422       *
     423       * Do not call this method directly from  your code. It is for internal
     424       * use only. You should call {@link question_usage::get_question_attempt_metadata()}.
     425       *
     426       * @param string $name the name of the variable to set. [a-z][a-z0-9]*.
     427       * @return bool the value to set previously, or null if this variable was never set.
     428       */
     429      public function has_metadata_var($name) {
     430          return isset($this->data[':_' . $name]);
     431      }
     432  
     433      /**
     434       * Get a metadata variable.
     435       *
     436       * Do not call this method directly from  your code. It is for internal
     437       * use only. You should call {@link question_usage::get_question_attempt_metadata()}.
     438       *
     439       * @param string $name the name of the variable to set. [a-z][a-z0-9]*.
     440       * @return string the value to set previously, or null if this variable was never set.
     441       */
     442      public function get_metadata_var($name) {
     443          if (!$this->has_metadata_var($name)) {
     444              return null;
     445          }
     446          return $this->data[':_' . $name];
     447      }
     448  
     449      /**
     450       * Create a question_attempt_step from records loaded from the database.
     451       * @param Iterator $records Raw records loaded from the database.
     452       * @param int $stepid The id of the records to extract.
     453       * @param string $qtype The question type of which this is an attempt.
     454       *      If not given, each record must include a qtype field.
     455       * @return question_attempt_step The newly constructed question_attempt_step.
     456       */
     457      public static function load_from_records($records, $attemptstepid, $qtype = null) {
     458          $currentrec = $records->current();
     459          while ($currentrec->attemptstepid != $attemptstepid) {
     460              $records->next();
     461              if (!$records->valid()) {
     462                  throw new coding_exception('Question attempt step ' . $attemptstepid .
     463                          ' not found in the database.');
     464              }
     465              $currentrec = $records->current();
     466          }
     467  
     468          $record = $currentrec;
     469          $contextid = null;
     470          $data = array();
     471          while ($currentrec && $currentrec->attemptstepid == $attemptstepid) {
     472              if (!is_null($currentrec->name)) {
     473                  $data[$currentrec->name] = $currentrec->value;
     474              }
     475              $records->next();
     476              if ($records->valid()) {
     477                  $currentrec = $records->current();
     478              } else {
     479                  $currentrec = false;
     480              }
     481          }
     482  
     483          $step = new question_attempt_step_read_only($data, $record->timecreated, $record->userid);
     484          $step->state = question_state::get($record->state);
     485          $step->id = $record->attemptstepid;
     486          if (!is_null($record->fraction)) {
     487              $step->fraction = $record->fraction + 0;
     488          }
     489  
     490          // This next chunk of code requires getting $contextid and $qtype here.
     491          // Somehow, we need to get that information to this point by modifying
     492          // all the paths by which this method can be called.
     493          // Can we only return files when it's possible? Should there be some kind of warning?
     494          if (is_null($qtype)) {
     495              $qtype = $record->qtype;
     496          }
     497          foreach (question_bank::get_qtype($qtype)->response_file_areas() as $area) {
     498              if (empty($step->data[$area])) {
     499                  continue;
     500              }
     501  
     502              $step->data[$area] = new question_file_loader($step, $area, $step->data[$area], $record->contextid);
     503          }
     504  
     505          return $step;
     506      }
     507  }
     508  
     509  
     510  /**
     511   * A subclass of {@link question_attempt_step} used when processing a new submission.
     512   *
     513   * When we are processing some new submitted data, which may or may not lead to
     514   * a new step being added to the {@link question_usage_by_activity} we create an
     515   * instance of this class. which is then passed to the question behaviour and question
     516   * type for processing. At the end of processing we then may, or may not, keep it.
     517   *
     518   * @copyright  2010 The Open University
     519   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     520   */
     521  class question_attempt_pending_step extends question_attempt_step {
     522      /** @var string the new response summary, if there is one. */
     523      protected $newresponsesummary = null;
     524  
     525      /** @var int the new variant number, if there is one. */
     526      protected $newvariant = null;
     527  
     528      /**
     529       * If as a result of processing this step, the response summary for the
     530       * question attempt should changed, you should call this method to set the
     531       * new summary.
     532       * @param string $responsesummary the new response summary.
     533       */
     534      public function set_new_response_summary($responsesummary) {
     535          $this->newresponsesummary = $responsesummary;
     536      }
     537  
     538      /**
     539       * Get the new response summary, if there is one.
     540       * @return string the new response summary, or null if it has not changed.
     541       */
     542      public function get_new_response_summary() {
     543          return $this->newresponsesummary;
     544      }
     545  
     546      /**
     547       * Whether this processing this step has changed the response summary.
     548       * @return bool true if there is a new response summary.
     549       */
     550      public function response_summary_changed() {
     551          return !is_null($this->newresponsesummary);
     552      }
     553  
     554      /**
     555       * If as a result of processing this step, you identify that this variant of the
     556       * question is actually identical to the another one, you may change the
     557       * variant number recorded, in order to give better statistics. For an example
     558       * see qbehaviour_opaque.
     559       * @param int $variant the new variant number.
     560       */
     561      public function set_new_variant_number($variant) {
     562          $this->newvariant = $variant;
     563      }
     564  
     565      /**
     566       * Get the new variant number, if there is one.
     567       * @return int the new variant number, or null if it has not changed.
     568       */
     569      public function get_new_variant_number() {
     570          return $this->newvariant;
     571      }
     572  
     573      /**
     574       * Whether this processing this step has changed the variant number.
     575       * @return bool true if there is a new variant number.
     576       */
     577      public function variant_number_changed() {
     578          return !is_null($this->newvariant);
     579      }
     580  }
     581  
     582  
     583  /**
     584   * A subclass of {@link question_attempt_step} that cannot be modified.
     585   *
     586   * @copyright  2009 The Open University
     587   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     588   */
     589  class question_attempt_step_read_only extends question_attempt_step {
     590      public function set_state($state) {
     591          throw new coding_exception('Cannot modify a question_attempt_step_read_only.');
     592      }
     593      public function set_fraction($fraction) {
     594          throw new coding_exception('Cannot modify a question_attempt_step_read_only.');
     595      }
     596      public function set_qt_var($name, $value) {
     597          throw new coding_exception('Cannot modify a question_attempt_step_read_only.');
     598      }
     599      public function set_behaviour_var($name, $value) {
     600          throw new coding_exception('Cannot modify a question_attempt_step_read_only.');
     601      }
     602  }
     603  
     604  
     605  /**
     606   * A null {@link question_attempt_step} returned from
     607   * {@link question_attempt::get_last_step()} etc. when a an attempt has just been
     608   * created and there is no actual step.
     609   *
     610   * @copyright  2009 The Open University
     611   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     612   */
     613  class question_null_step {
     614      public function get_state() {
     615          return question_state::$notstarted;
     616      }
     617  
     618      public function set_state($state) {
     619          throw new coding_exception('This question has not been started.');
     620      }
     621  
     622      public function get_fraction() {
     623          return null;
     624      }
     625  }
     626  
     627  
     628  /**
     629   * This is an adapter class that wraps a {@link question_attempt_step} and
     630   * modifies the get/set_*_data methods so that they operate only on the parts
     631   * that belong to a particular subquestion, as indicated by an extra prefix.
     632   *
     633   * @copyright  2010 The Open University
     634   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     635   */
     636  class question_attempt_step_subquestion_adapter extends question_attempt_step {
     637      /** @var question_attempt_step the step we are wrapping. */
     638      protected $realstep;
     639      /** @var string the exta prefix on fields we work with. */
     640      protected $extraprefix;
     641  
     642      /**
     643       * Constructor.
     644       * @param question_attempt_step $realqas the step to wrap. (Can be null if you
     645       *      just want to call add/remove.prefix.)
     646       * @param unknown_type $extraprefix the extra prefix that is used for date fields.
     647       */
     648      public function __construct($realqas, $extraprefix) {
     649          $this->realqas = $realqas;
     650          $this->extraprefix = $extraprefix;
     651      }
     652  
     653      /**
     654       * Add the extra prefix to a field name.
     655       * @param string $field the plain field name.
     656       * @return string the field name with the extra bit of prefix added.
     657       */
     658      public function add_prefix($field) {
     659          if (substr($field, 0, 2) === '!_') {
     660              return '-_' . $this->extraprefix . substr($field, 2);
     661          } else if (substr($field, 0, 1) === '-') {
     662              return '-' . $this->extraprefix . substr($field, 1);
     663          } else if (substr($field, 0, 1) === '_') {
     664              return '_' . $this->extraprefix . substr($field, 1);
     665          } else {
     666              return $this->extraprefix . $field;
     667          }
     668      }
     669  
     670      /**
     671       * Remove the extra prefix from a field name if it is present.
     672       * @param string $field the extended field name.
     673       * @return string the field name with the extra bit of prefix removed, or
     674       * null if the extre prefix was not present.
     675       */
     676      public function remove_prefix($field) {
     677          if (preg_match('~^(-?_?)' . preg_quote($this->extraprefix, '~') . '(.*)$~', $field, $matches)) {
     678              return $matches[1] . $matches[2];
     679          } else {
     680              return null;
     681          }
     682      }
     683  
     684      /**
     685       * Filter some data to keep only those entries where the key contains
     686       * extraprefix, and remove the extra prefix from the reutrned arrary.
     687       * @param array $data some of the data stored in this step.
     688       * @return array the data with the keys ajusted using {@link remove_prefix()}.
     689       */
     690      public function filter_array($data) {
     691          $result = array();
     692          foreach ($data as $fullname => $value) {
     693              if ($name = $this->remove_prefix($fullname)) {
     694                  $result[$name] = $value;
     695              }
     696          }
     697          return $result;
     698      }
     699  
     700      public function get_state() {
     701          return $this->realqas->get_state();
     702      }
     703  
     704      public function set_state($state) {
     705          throw new coding_exception('Cannot modify a question_attempt_step_subquestion_adapter.');
     706      }
     707  
     708      public function get_fraction() {
     709          return $this->realqas->get_fraction();
     710      }
     711  
     712      public function set_fraction($fraction) {
     713          throw new coding_exception('Cannot modify a question_attempt_step_subquestion_adapter.');
     714      }
     715  
     716      public function get_user_id() {
     717          return $this->realqas->get_user_id;
     718      }
     719  
     720      public function get_timecreated() {
     721          return $this->realqas->get_timecreated();
     722      }
     723  
     724      public function has_qt_var($name) {
     725          return $this->realqas->has_qt_var($this->add_prefix($name));
     726      }
     727  
     728      public function get_qt_var($name) {
     729          return $this->realqas->get_qt_var($this->add_prefix($name));
     730      }
     731  
     732      public function set_qt_var($name, $value) {
     733          return $this->realqas->set_qt_var($this->add_prefix($name), $value);
     734      }
     735  
     736      public function get_qt_data() {
     737          return $this->filter_array($this->realqas->get_qt_data());
     738      }
     739  
     740      public function has_behaviour_var($name) {
     741          return $this->realqas->has_im_var($this->add_prefix($name));
     742      }
     743  
     744      public function get_behaviour_var($name) {
     745          return $this->realqas->get_im_var($this->add_prefix($name));
     746      }
     747  
     748      public function set_behaviour_var($name, $value) {
     749          return $this->realqas->set_im_var($this->add_prefix($name), $value);
     750      }
     751  
     752      public function get_behaviour_data() {
     753          return $this->filter_array($this->realqas->get_behaviour_data());
     754      }
     755  
     756      public function get_submitted_data() {
     757          return $this->filter_array($this->realqas->get_submitted_data());
     758      }
     759  
     760      public function get_all_data() {
     761          return $this->filter_array($this->realqas->get_all_data());
     762      }
     763  
     764      public function get_qt_files($name, $contextid) {
     765          throw new coding_exception('No attempt has yet been made to implement files support in ' .
     766                  'question_attempt_step_subquestion_adapter.');
     767      }
     768  
     769      public function prepare_response_files_draft_itemid($name, $contextid) {
     770          throw new coding_exception('No attempt has yet been made to implement files support in ' .
     771                  'question_attempt_step_subquestion_adapter.');
     772      }
     773  
     774      public function prepare_response_files_draft_itemid_with_text($name, $contextid, $text) {
     775          throw new coding_exception('No attempt has yet been made to implement files support in ' .
     776                  'question_attempt_step_subquestion_adapter.');
     777      }
     778  
     779      public function rewrite_response_pluginfile_urls($text, $contextid, $name, $extras) {
     780          throw new coding_exception('No attempt has yet been made to implement files support in ' .
     781                  'question_attempt_step_subquestion_adapter.');
     782      }
     783  }