Search moodle.org's
Developer Documentation

See Release Notes

  • 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 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   * @package    moodlecore
  19   * @subpackage backup-moodle2
  20   * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once($CFG->dirroot . '/question/type/multianswer/questiontype.php');
  28  /**
  29   * restore plugin class that provides the necessary information
  30   * needed to restore one multianswer qtype plugin
  31   *
  32   * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   */
  35  class restore_qtype_multianswer_plugin extends restore_qtype_plugin {
  36  
  37      /**
  38       * Returns the paths to be handled by the plugin at question level
  39       */
  40      protected function define_question_plugin_structure() {
  41          $paths = array();
  42  
  43          // This qtype uses question_answers, add them.
  44          $this->add_question_question_answers($paths);
  45  
  46          // Add own qtype stuff.
  47          $elename = 'multianswer';
  48          $elepath = $this->get_pathfor('/multianswer');
  49          $paths[] = new restore_path_element($elename, $elepath);
  50  
  51          return $paths; // And we return the interesting paths.
  52      }
  53  
  54      /**
  55       * Process the qtype/multianswer element
  56       */
  57      public function process_multianswer($data) {
  58          global $DB;
  59  
  60          $data = (object)$data;
  61          $oldid = $data->id;
  62  
  63          // Detect if the question is created or mapped.
  64          $oldquestionid   = $this->get_old_parentid('question');
  65          $newquestionid   = $this->get_new_parentid('question');
  66          $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
  67  
  68          // If the question has been created by restore, we need to create its
  69          // question_multianswer too.
  70          if ($questioncreated) {
  71              // Adjust some columns.
  72              $data->question = $newquestionid;
  73              // Note: multianswer->sequence is a list of question->id values. We aren't
  74              // recoding them here (because some questions can be missing yet). Instead
  75              // we'll perform the recode in the {@link after_execute} method of the plugin
  76              // that gets executed once all questions have been created.
  77              // Insert record.
  78              $newitemid = $DB->insert_record('question_multianswer', $data);
  79              // Create mapping (need it for after_execute recode of sequence).
  80              $this->set_mapping('question_multianswer', $oldid, $newitemid);
  81          }
  82      }
  83  
  84      /**
  85       * This method is executed once the whole restore_structure_step
  86       * this step is part of ({@link restore_create_categories_and_questions})
  87       * has ended processing the whole xml structure. Its name is:
  88       * "after_execute_" + connectionpoint ("question")
  89       *
  90       * For multianswer qtype we use it to restore the sequence column,
  91       * containing one list of question ids
  92       */
  93      public function after_execute_question() {
  94          global $DB;
  95          // Now that all the questions have been restored, let's process
  96          // the created question_multianswer sequences (list of question ids).
  97          $rs = $DB->get_recordset_sql("
  98                  SELECT qma.id, qma.sequence
  99                    FROM {question_multianswer} qma
 100                    JOIN {backup_ids_temp} bi ON bi.newitemid = qma.question
 101                   WHERE bi.backupid = ?
 102                     AND bi.itemname = 'question_created'",
 103                  array($this->get_restoreid()));
 104          foreach ($rs as $rec) {
 105              $sequencearr = preg_split('/,/', $rec->sequence, -1, PREG_SPLIT_NO_EMPTY);
 106              if (substr_count($rec->sequence, ',') + 1 != count($sequencearr)) {
 107                  $this->task->log('Invalid sequence found in restored multianswer question ' . $rec->id, backup::LOG_WARNING);
 108              }
 109  
 110              foreach ($sequencearr as $key => $question) {
 111                  $sequencearr[$key] = $this->get_mappingid('question', $question);
 112              }
 113              $sequence = implode(',', array_filter($sequencearr));
 114              $DB->set_field('question_multianswer', 'sequence', $sequence,
 115                      array('id' => $rec->id));
 116              if (!empty($sequence)) {
 117                  // Get relevant data indexed by positionkey from the multianswers table.
 118                  $wrappedquestions = $DB->get_records_list('question', 'id',
 119                      explode(',', $sequence), 'id ASC');
 120                  foreach ($wrappedquestions as $wrapped) {
 121                      if ($wrapped->qtype == 'multichoice') {
 122                          question_bank::get_qtype($wrapped->qtype)->get_question_options($wrapped);
 123                          if (isset($wrapped->options->shuffleanswers)) {
 124                              preg_match('/'.ANSWER_REGEX.'/s', $wrapped->questiontext, $answerregs);
 125                              if (isset($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]) &&
 126                                      $answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE] !== '') {
 127                                  $wrapped->options->shuffleanswers = 0;
 128                                  $DB->set_field_select('qtype_multichoice_options', 'shuffleanswers', '0', "id =:select",
 129                                      array('select' => $wrapped->options->id) );
 130                              }
 131                          }
 132                      }
 133                  }
 134              }
 135          }
 136          $rs->close();
 137      }
 138  
 139      public function recode_response($questionid, $sequencenumber, array $response) {
 140          global $DB;
 141  
 142          $qtypes = $DB->get_records_menu('question', array('parent' => $questionid),
 143                  '', 'id, qtype');
 144  
 145          $sequence = $DB->get_field('question_multianswer', 'sequence',
 146                  array('question' => $questionid));
 147  
 148          $fakestep = new question_attempt_step_read_only($response);
 149  
 150          foreach (explode(',', $sequence) as $key => $subqid) {
 151              $i = $key + 1;
 152  
 153              $substep = new question_attempt_step_subquestion_adapter($fakestep, 'sub' . $i . '_');
 154              $recodedresponse = $this->step->questions_recode_response_data($qtypes[$subqid],
 155                      $subqid, $sequencenumber, $substep->get_all_data());
 156  
 157              foreach ($recodedresponse as $name => $value) {
 158                  $response[$substep->add_prefix($name)] = $value;
 159              }
 160          }
 161  
 162          return $response;
 163      }
 164  
 165      /**
 166       * Given one question_states record, return the answer
 167       * recoded pointing to all the restored stuff for multianswer questions
 168       *
 169       * answer is one comma separated list of hypen separated pairs
 170       * containing sequence (pointing to questions sequence in question_multianswer)
 171       * and mixed answers. We'll delegate
 172       * the recoding of answers to the proper qtype
 173       */
 174      public function recode_legacy_state_answer($state) {
 175          global $DB;
 176          $answer = $state->answer;
 177          $resultarr = array();
 178          // Get sequence of questions.
 179          $sequence = $DB->get_field('question_multianswer', 'sequence',
 180                  array('question' => $state->question));
 181          $sequencearr = explode(',', $sequence);
 182          // Let's process each pair.
 183          foreach (explode(',', $answer) as $pair) {
 184              $pairarr = explode('-', $pair);
 185              $sequenceid = $pairarr[0];
 186              $subanswer = $pairarr[1];
 187              // Calculate the questionid based on sequenceid.
 188              // Note it is already one *new* questionid that doesn't need mapping.
 189              $questionid = $sequencearr[$sequenceid - 1];
 190              // Fetch qtype of the question (needed for delegation).
 191              $questionqtype = $DB->get_field('question', 'qtype', array('id' => $questionid));
 192              // Delegate subanswer recode to proper qtype, faking one question_states record.
 193              $substate = new stdClass();
 194              $substate->question = $questionid;
 195              $substate->answer = $subanswer;
 196              $newanswer = $this->step->restore_recode_legacy_answer($substate, $questionqtype);
 197              $resultarr[] = implode('-', array($sequenceid, $newanswer));
 198          }
 199          return implode(',', $resultarr);
 200      }
 201  
 202  }