Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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 matching question type.
  19   *
  20   * @package   qtype_match
  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->libdir . '/questionlib.php');
  29  require_once($CFG->dirroot . '/question/engine/lib.php');
  30  
  31  
  32  /**
  33   * The matching question type class.
  34   *
  35   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class qtype_match extends question_type {
  39  
  40      public function get_question_options($question) {
  41          global $DB;
  42          parent::get_question_options($question);
  43          $question->options = $DB->get_record('qtype_match_options',
  44                  array('questionid' => $question->id));
  45          $question->options->subquestions = $DB->get_records('qtype_match_subquestions',
  46                  array('questionid' => $question->id), 'id ASC');
  47          return true;
  48      }
  49  
  50      public function save_question_options($question) {
  51          global $DB;
  52          $context = $question->context;
  53          $result = new stdClass();
  54  
  55          $oldsubquestions = $DB->get_records('qtype_match_subquestions',
  56                  array('questionid' => $question->id), 'id ASC');
  57  
  58          // Insert all the new question & answer pairs.
  59          foreach ($question->subquestions as $key => $questiontext) {
  60              if ($questiontext['text'] == '' && trim($question->subanswers[$key]) == '') {
  61                  continue;
  62              }
  63              if ($questiontext['text'] != '' && trim($question->subanswers[$key]) == '') {
  64                  $result->notice = get_string('nomatchinganswer', 'qtype_match', $questiontext);
  65              }
  66  
  67              // Update an existing subquestion if possible.
  68              $subquestion = array_shift($oldsubquestions);
  69              if (!$subquestion) {
  70                  $subquestion = new stdClass();
  71                  $subquestion->questionid = $question->id;
  72                  $subquestion->questiontext = '';
  73                  $subquestion->answertext = '';
  74                  $subquestion->id = $DB->insert_record('qtype_match_subquestions', $subquestion);
  75              }
  76  
  77              $subquestion->questiontext = $this->import_or_save_files($questiontext,
  78                      $context, 'qtype_match', 'subquestion', $subquestion->id);
  79              $subquestion->questiontextformat = $questiontext['format'];
  80              $subquestion->answertext = trim($question->subanswers[$key]);
  81  
  82              $DB->update_record('qtype_match_subquestions', $subquestion);
  83          }
  84  
  85          // Delete old subquestions records.
  86          $fs = get_file_storage();
  87          foreach ($oldsubquestions as $oldsub) {
  88              $fs->delete_area_files($context->id, 'qtype_match', 'subquestion', $oldsub->id);
  89              $DB->delete_records('qtype_match_subquestions', array('id' => $oldsub->id));
  90          }
  91  
  92          // Save the question options.
  93          $options = $DB->get_record('qtype_match_options', array('questionid' => $question->id));
  94          if (!$options) {
  95              $options = new stdClass();
  96              $options->questionid = $question->id;
  97              $options->correctfeedback = '';
  98              $options->partiallycorrectfeedback = '';
  99              $options->incorrectfeedback = '';
 100              $options->id = $DB->insert_record('qtype_match_options', $options);
 101          }
 102  
 103          $options->shuffleanswers = $question->shuffleanswers;
 104          $options = $this->save_combined_feedback_helper($options, $question, $context, true);
 105          $DB->update_record('qtype_match_options', $options);
 106  
 107          $this->save_hints($question, true);
 108  
 109          if (!empty($result->notice)) {
 110              return $result;
 111          }
 112  
 113          return true;
 114      }
 115  
 116      protected function initialise_question_instance(question_definition $question, $questiondata) {
 117          parent::initialise_question_instance($question, $questiondata);
 118  
 119          $question->shufflestems = $questiondata->options->shuffleanswers;
 120          $this->initialise_combined_feedback($question, $questiondata, true);
 121  
 122          $question->stems = array();
 123          $question->choices = array();
 124          $question->right = array();
 125  
 126          foreach ($questiondata->options->subquestions as $matchsub) {
 127              $key = array_search($matchsub->answertext, $question->choices, true);
 128              if ($key === false) {
 129                  $key = $matchsub->id;
 130                  $question->choices[$key] = $matchsub->answertext;
 131              }
 132  
 133              if ($matchsub->questiontext !== '') {
 134                  $question->stems[$matchsub->id] = $matchsub->questiontext;
 135                  $question->stemformat[$matchsub->id] = $matchsub->questiontextformat;
 136                  $question->right[$matchsub->id] = $key;
 137              }
 138          }
 139      }
 140  
 141      protected function make_hint($hint) {
 142          return question_hint_with_parts::load_from_record($hint);
 143      }
 144  
 145      public function delete_question($questionid, $contextid) {
 146          global $DB;
 147          $DB->delete_records('qtype_match_options', array('questionid' => $questionid));
 148          $DB->delete_records('qtype_match_subquestions', array('questionid' => $questionid));
 149  
 150          parent::delete_question($questionid, $contextid);
 151      }
 152  
 153      public function get_random_guess_score($questiondata) {
 154          $q = $this->make_question($questiondata);
 155          return 1 / count($q->choices);
 156      }
 157  
 158      public function get_possible_responses($questiondata) {
 159          $subqs = array();
 160  
 161          $q = $this->make_question($questiondata);
 162  
 163          foreach ($q->stems as $stemid => $stem) {
 164  
 165              $responses = array();
 166              foreach ($q->choices as $choiceid => $choice) {
 167                  $responses[$choiceid] = new question_possible_response(
 168                          $q->html_to_text($stem, $q->stemformat[$stemid]) . ': ' . $choice,
 169                          ($choiceid == $q->right[$stemid]) / count($q->stems));
 170              }
 171              $responses[null] = question_possible_response::no_response();
 172  
 173              $subqs[$stemid] = $responses;
 174          }
 175  
 176          return $subqs;
 177      }
 178  
 179      public function move_files($questionid, $oldcontextid, $newcontextid) {
 180          global $DB;
 181          $fs = get_file_storage();
 182  
 183          parent::move_files($questionid, $oldcontextid, $newcontextid);
 184  
 185          $subquestionids = $DB->get_records_menu('qtype_match_subquestions',
 186                  array('questionid' => $questionid), 'id', 'id,1');
 187          foreach ($subquestionids as $subquestionid => $notused) {
 188              $fs->move_area_files_to_new_context($oldcontextid,
 189                      $newcontextid, 'qtype_match', 'subquestion', $subquestionid);
 190          }
 191  
 192          $this->move_files_in_combined_feedback($questionid, $oldcontextid, $newcontextid);
 193          $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid);
 194      }
 195  
 196      protected function delete_files($questionid, $contextid) {
 197          global $DB;
 198          $fs = get_file_storage();
 199  
 200          parent::delete_files($questionid, $contextid);
 201  
 202          $subquestionids = $DB->get_records_menu('qtype_match_subquestions',
 203                  array('questionid' => $questionid), 'id', 'id,1');
 204          foreach ($subquestionids as $subquestionid => $notused) {
 205              $fs->delete_area_files($contextid, 'qtype_match', 'subquestion', $subquestionid);
 206          }
 207  
 208          $this->delete_files_in_combined_feedback($questionid, $contextid);
 209          $this->delete_files_in_hints($questionid, $contextid);
 210      }
 211  }