Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Question type class for the randomsamatch question type.
  19   *
  20   * @package    qtype_randomsamatch
  21   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once($CFG->dirroot . '/question/type/questiontypebase.php');
  29  require_once($CFG->dirroot . '/question/type/questionbase.php');
  30  require_once($CFG->dirroot . '/question/type/numerical/question.php');
  31  
  32  /**
  33   * The randomsamatch question type class.
  34   *
  35   * TODO: Make sure short answer questions chosen by a randomsamatch question
  36   * can not also be used by a random question
  37   *
  38   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class qtype_randomsamatch extends question_type {
  42      /**
  43       * Cache of available shortanswer question ids from a particular category.
  44       * @var array two-dimensional array. The first key is a category id, the
  45       * second key is wether subcategories should be included.
  46       */
  47      private $availablesaquestionsbycategory = array();
  48      const MAX_SUBQUESTIONS = 10;
  49  
  50      public function is_usable_by_random() {
  51          return false;
  52      }
  53  
  54      public function get_question_options($question) {
  55          global $DB;
  56          parent::get_question_options($question);
  57          $question->options = $DB->get_record('qtype_randomsamatch_options',
  58                  array('questionid' => $question->id));
  59  
  60          return true;
  61  
  62      }
  63  
  64      public function save_question_options($question) {
  65          global $DB;
  66  
  67          if (2 > $question->choose) {
  68              $result = new stdClass();
  69              $result->error = "At least two shortanswer questions need to be chosen!";
  70              return $result;
  71          }
  72  
  73          $context = $question->context;
  74  
  75          // Save the question options.
  76          $options = $DB->get_record('qtype_randomsamatch_options', array('questionid' => $question->id));
  77          if (!$options) {
  78              $options = new stdClass();
  79              $options->questionid = $question->id;
  80              $options->correctfeedback = '';
  81              $options->partiallycorrectfeedback = '';
  82              $options->incorrectfeedback = '';
  83              $options->id = $DB->insert_record('qtype_randomsamatch_options', $options);
  84          }
  85  
  86          $options->choose = $question->choose;
  87          $options->subcats = $question->subcats;
  88          $options = $this->save_combined_feedback_helper($options, $question, $context, true);
  89          $DB->update_record('qtype_randomsamatch_options', $options);
  90  
  91          $this->save_hints($question, true);
  92  
  93          return true;
  94      }
  95  
  96      protected function make_hint($hint) {
  97          return question_hint_with_parts::load_from_record($hint);
  98      }
  99  
 100      public function delete_question($questionid, $contextid) {
 101          global $DB;
 102          $DB->delete_records('qtype_randomsamatch_options', array('questionid' => $questionid));
 103  
 104          parent::delete_question($questionid, $contextid);
 105      }
 106  
 107      public function move_files($questionid, $oldcontextid, $newcontextid) {
 108          parent::move_files($questionid, $oldcontextid, $newcontextid);
 109  
 110          $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid);
 111          $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
 112      }
 113  
 114      protected function delete_files($questionid, $contextid) {
 115          parent::delete_files($questionid, $contextid);
 116  
 117          $this->delete_files_in_combined_feedback($questionid, $contextid);
 118          $this->delete_files_in_hints($questionid, $contextid);
 119      }
 120  
 121      protected function initialise_question_instance(question_definition $question, $questiondata) {
 122          parent::initialise_question_instance($question, $questiondata);
 123          $availablesaquestions = $this->get_available_saquestions_from_category(
 124                  $question->category, $questiondata->options->subcats);
 125          $question->shufflestems = false;
 126          $question->stems = array();
 127          $question->choices = array();
 128          $question->right = array();
 129          $this->initialise_combined_feedback($question, $questiondata);
 130          $question->questionsloader = new qtype_randomsamatch_question_loader(
 131                  $availablesaquestions, $questiondata->options->choose);
 132      }
 133  
 134      public function can_analyse_responses() {
 135          return false;
 136      }
 137  
 138      /**
 139       * Get all the usable shortanswer questions from a particular question category.
 140       *
 141       * @param integer $categoryid the id of a question category.
 142       * @param bool $subcategories whether to include questions from subcategories.
 143       * @return array of question records.
 144       */
 145      public function get_available_saquestions_from_category($categoryid, $subcategories) {
 146          if (isset($this->availablesaquestionsbycategory[$categoryid][$subcategories])) {
 147              return $this->availablesaquestionsbycategory[$categoryid][$subcategories];
 148          }
 149  
 150          if ($subcategories) {
 151              $categoryids = question_categorylist($categoryid);
 152          } else {
 153              $categoryids = array($categoryid);
 154          }
 155  
 156          $questionids = question_bank::get_finder()->get_questions_from_categories(
 157                  $categoryids, "qtype = 'shortanswer'");
 158          $this->availablesaquestionsbycategory[$categoryid][$subcategories] = $questionids;
 159          return $questionids;
 160      }
 161  
 162      /**
 163       * @param object $question
 164       * @return mixed either a integer score out of 1 that the average random
 165       * guess by a student might give or an empty string which means will not
 166       * calculate.
 167       */
 168      public function get_random_guess_score($question) {
 169          return 1/$question->options->choose;
 170      }
 171  
 172      /**
 173       * Defines the table which extends the question table. This allows the base questiontype
 174       * to automatically save, backup and restore the extra fields.
 175       *
 176       * @return an array with the table name (first) and then the column names (apart from id and questionid)
 177       */
 178      public function extra_question_fields() {
 179          return array('qtype_randomsamatch_options',
 180                       'choose',        // Number of shortanswer questions to choose.
 181                       'subcats',       // Questions can be choosen from subcategories.
 182                       );
 183      }
 184  
 185      /**
 186       * Imports the question from Moodle XML format.
 187       *
 188       * @param array $xml structure containing the XML data
 189       * @param object $fromform question object to fill: ignored by this function (assumed to be null)
 190       * @param qformat_xml $format format class exporting the question
 191       * @param object $extra extra information (not required for importing this question in this format)
 192       * @return object question object
 193       */
 194      public function import_from_xml($xml, $fromform, qformat_xml $format, $extra=null) {
 195          // Return if data type is not our own one.
 196          if (!isset($xml['@']['type']) || $xml['@']['type'] != $this->name()) {
 197              return false;
 198          }
 199  
 200          // Import the common question headers and set the corresponding field.
 201          $fromform = $format->import_headers($xml);
 202          $fromform->qtype = $this->name();
 203          $format->import_combined_feedback($fromform, $xml, true);
 204          $format->import_hints($fromform, $xml, true);
 205  
 206          $extras = $this->extra_question_fields();
 207          array_shift($extras);
 208          foreach ($extras as $extra) {
 209              $fromform->$extra = $format->getpath($xml, array('#', $extra, 0, '#'), '', true);
 210          }
 211  
 212          return $fromform;
 213      }
 214  
 215      /**
 216       * Exports the question to Moodle XML format.
 217       *
 218       * @param object $question question to be exported into XML format
 219       * @param qformat_xml $format format class exporting the question
 220       * @param object $extra extra information (not required for exporting this question in this format)
 221       * @return string containing the question data in XML format
 222       */
 223      public function export_to_xml($question, qformat_xml $format, $extra=null) {
 224          $expout = '';
 225          $expout .= $format->write_combined_feedback($question->options,
 226                                                      $question->id,
 227                                                      $question->contextid);
 228          $extraquestionfields = $this->extra_question_fields();
 229          array_shift($extraquestionfields);
 230          foreach ($extraquestionfields as $extra) {
 231              $expout .= "    <{$extra}>" . $question->options->$extra . "</{$extra}>\n";
 232          }
 233          return $expout;
 234      }
 235  }