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   * Upgrade library code for the match question type.
  19   *
  20   * @package   qtype_match
  21   * @copyright 2010 The Open University
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  
  29  /**
  30   * Class for converting attempt data for match questions when upgrading
  31   * attempts to the new question engine.
  32   *
  33   * This class is used by the code in question/engine/upgrade/upgradelib.php.
  34   *
  35   * @copyright 2010 The Open University
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class qtype_match_qe2_attempt_updater extends question_qtype_attempt_updater {
  39      protected $stems;
  40      protected $choices;
  41      protected $right;
  42      protected $stemorder;
  43      protected $choiceorder;
  44      protected $flippedchoiceorder;
  45  
  46      public function question_summary() {
  47          $this->stems = array();
  48          $this->choices = array();
  49          $this->right = array();
  50  
  51          foreach ($this->question->options->subquestions as $matchsub) {
  52              $ans = $matchsub->answertext;
  53              $key = array_search($matchsub->answertext, $this->choices);
  54              if ($key === false) {
  55                  $key = $matchsub->id;
  56                  $this->choices[$key] = $matchsub->answertext;
  57              }
  58  
  59              if ($matchsub->questiontext !== '') {
  60                  $this->stems[$matchsub->id] = $this->to_text($matchsub->questiontext);
  61                  $this->right[$matchsub->id] = $key;
  62              }
  63          }
  64  
  65          return $this->to_text($this->question->questiontext) . ' {' .
  66                  implode('; ', $this->stems) . '} -> {' . implode('; ', $this->choices) . '}';
  67      }
  68  
  69      public function right_answer() {
  70          $answer = array();
  71          foreach ($this->stems as $key => $stem) {
  72              $answer[$stem] = $this->choices[$this->right[$key]];
  73          }
  74          return $this->make_summary($answer);
  75      }
  76  
  77      protected function explode_answer($answer) {
  78          if (!$answer) {
  79              return array();
  80          }
  81          $bits = explode(',', $answer);
  82          $selections = array();
  83          foreach ($bits as $bit) {
  84              list($stem, $choice) = explode('-', $bit);
  85              $selections[$stem] = $choice;
  86          }
  87          return $selections;
  88      }
  89  
  90      protected function make_summary($pairs) {
  91          $bits = array();
  92          foreach ($pairs as $stem => $answer) {
  93              $bits[] = $stem . ' -> ' . $answer;
  94          }
  95          return implode('; ', $bits);
  96      }
  97  
  98      protected function lookup_choice($choice) {
  99          foreach ($this->question->options->subquestions as $matchsub) {
 100              if ($matchsub->id == $choice) {
 101                  if (array_key_exists($matchsub->id, $this->choices)) {
 102                      return $matchsub->id;
 103                  } else {
 104                      return array_search($matchsub->answertext, $this->choices);
 105                  }
 106              }
 107          }
 108          return null;
 109      }
 110  
 111      public function response_summary($state) {
 112          $choices = $this->explode_answer($state->answer);
 113          if (empty($choices)) {
 114              return null;
 115          }
 116  
 117          $pairs = array();
 118          foreach ($choices as $stemid => $choicekey) {
 119              if (array_key_exists($stemid, $this->stems) && $choices[$stemid]) {
 120                  $choiceid = $this->lookup_choice($choicekey);
 121                  if ($choiceid) {
 122                      $pairs[$this->stems[$stemid]] = $this->choices[$choiceid];
 123                  } else {
 124                      $this->logger->log_assumption("Dealing with a place where the
 125                              student selected a choice that was later deleted for
 126                              match question {$this->question->id}");
 127                      $pairs[$this->stems[$stemid]] = '[CHOICE THAT WAS LATER DELETED]';
 128                  }
 129              }
 130          }
 131  
 132          if ($pairs) {
 133              return $this->make_summary($pairs);
 134          } else {
 135              return '';
 136          }
 137      }
 138  
 139      public function was_answered($state) {
 140          $choices = $this->explode_answer($state->answer);
 141          foreach ($choices as $choice) {
 142              if ($choice) {
 143                  return true;
 144              }
 145          }
 146          return false;
 147      }
 148  
 149      public function set_first_step_data_elements($state, &$data) {
 150          $choices = $this->explode_answer($state->answer);
 151          foreach ($choices as $key => $notused) {
 152              if (array_key_exists($key, $this->stems)) {
 153                  $this->stemorder[] = $key;
 154              }
 155          }
 156  
 157          $this->choiceorder = array_keys($this->choices);
 158          shuffle($this->choiceorder);
 159          $this->flippedchoiceorder = array_combine(
 160                  array_values($this->choiceorder), array_keys($this->choiceorder));
 161  
 162          $data['_stemorder'] = implode(',', $this->stemorder);
 163          $data['_choiceorder'] = implode(',', $this->choiceorder);
 164      }
 165  
 166      public function supply_missing_first_step_data(&$data) {
 167          throw new coding_exception('qtype_match_updater::supply_missing_first_step_data ' .
 168                  'not tested');
 169          $data['_stemorder'] = array_keys($this->stems);
 170          $data['_choiceorder'] = shuffle(array_keys($this->choices));
 171      }
 172  
 173      public function set_data_elements_for_step($state, &$data) {
 174          $choices = $this->explode_answer($state->answer);
 175  
 176          foreach ($this->stemorder as $i => $key) {
 177              if (empty($choices[$key])) {
 178                  $data['sub' . $i] = 0;
 179                  continue;
 180              }
 181              $choice = $this->lookup_choice($choices[$key]);
 182  
 183              if (array_key_exists($choice, $this->flippedchoiceorder)) {
 184                  $data['sub' . $i] = $this->flippedchoiceorder[$choice] + 1;
 185              } else {
 186                  $data['sub' . $i] = 0;
 187              }
 188          }
 189      }
 190  }