See Release Notes
Long Term Support Release
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 28 /** 29 * Restore plugin class that provides the necessary information 30 * needed to restore one match 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_match_plugin extends restore_qtype_plugin { 36 37 /** 38 * A simple answer, questiontext to id cache for a match answers. 39 * @var array 40 */ 41 private $questionsubcache = array(); 42 43 /** 44 * The id of the current question in the questionsubcache. 45 * @var int 46 */ 47 private $questionsubcacheid = null; 48 49 50 /** 51 * Returns the paths to be handled by the plugin at question level. 52 */ 53 protected function define_question_plugin_structure() { 54 55 $paths = array(); 56 57 // Add own qtype stuff. 58 $elename = 'matchoptions'; 59 // We used get_recommended_name() so this works. 60 $elepath = $this->get_pathfor('/matchoptions'); 61 $paths[] = new restore_path_element($elename, $elepath); 62 63 $elename = 'match'; 64 // We used get_recommended_name() so this works. 65 $elepath = $this->get_pathfor('/matches/match'); 66 $paths[] = new restore_path_element($elename, $elepath); 67 68 return $paths; 69 } 70 71 /** 72 * Process the qtype/matchoptions element 73 */ 74 public function process_matchoptions($data) { 75 global $DB; 76 77 $data = (object)$data; 78 $oldid = $data->id; 79 80 // Detect if the question is created or mapped. 81 $oldquestionid = $this->get_old_parentid('question'); 82 $newquestionid = $this->get_new_parentid('question'); 83 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false; 84 85 // If the question has been created by restore, we need to create its qtype_match_options too. 86 if ($questioncreated) { 87 // Fill in some field that were added in 2.1, and so which may be missing 88 // from backups made in older versions of Moodle. 89 if (!isset($data->correctfeedback)) { 90 $data->correctfeedback = ''; 91 $data->correctfeedbackformat = FORMAT_HTML; 92 } 93 if (!isset($data->partiallycorrectfeedback)) { 94 $data->partiallycorrectfeedback = ''; 95 $data->partiallycorrectfeedbackformat = FORMAT_HTML; 96 } 97 if (!isset($data->incorrectfeedback)) { 98 $data->incorrectfeedback = ''; 99 $data->incorrectfeedbackformat = FORMAT_HTML; 100 } 101 if (!isset($data->shownumcorrect)) { 102 $data->shownumcorrect = 0; 103 } 104 105 // Adjust some columns. 106 $data->questionid = $newquestionid; 107 108 // It is possible for old backup files to contain unique key violations. 109 // We need to check to avoid that. 110 if (!$DB->record_exists('qtype_match_options', array('questionid' => $data->questionid))) { 111 $newitemid = $DB->insert_record('qtype_match_options', $data); 112 $this->set_mapping('qtype_match_options', $oldid, $newitemid); 113 } 114 } 115 } 116 117 /** 118 * Process the qtype/matches/match element 119 */ 120 public function process_match($data) { 121 global $DB; 122 123 $data = (object)$data; 124 $oldid = $data->id; 125 126 // Detect if the question is created or mapped. 127 $oldquestionid = $this->get_old_parentid('question'); 128 $newquestionid = $this->get_new_parentid('question'); 129 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false; 130 131 if ($questioncreated) { 132 // If the question has been created by restore, we need to create its 133 // qtype_match_subquestions too. 134 135 // Adjust some columns. 136 $data->questionid = $newquestionid; 137 // Insert record. 138 $newitemid = $DB->insert_record('qtype_match_subquestions', $data); 139 // Create mapping (there are files and states based on this). 140 if (isset($data->code)) { 141 $this->set_mapping('qtype_match_subquestion_codes', $data->code, $newitemid); 142 } 143 144 } else { 145 // Match questions require mapping of qtype_match_subquestions, because 146 // they are used by question_states->answer. 147 148 // Have we cached the current question? 149 if ($this->questionsubcacheid !== $newquestionid) { 150 // The question changed, purge and start again! 151 $this->questionsubcache = array(); 152 153 $params = array('question' => $newquestionid); 154 $potentialsubs = $DB->get_records('qtype_match_subquestions', 155 array('questionid' => $newquestionid), '', 'id, questiontext, answertext'); 156 157 $this->questionsubcacheid = $newquestionid; 158 // Cache all cleaned answers and questiontext. 159 foreach ($potentialsubs as $potentialsub) { 160 // Clean in the same way than {@link xml_writer::xml_safe_utf8()}. 161 $cleanquestion = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', 162 '', $potentialsub->questiontext); // Clean CTRL chars. 163 $cleanquestion = preg_replace("/\r\n|\r/", "\n", $cleanquestion); // Normalize line ending. 164 165 $cleananswer = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', 166 '', $potentialsub->answertext); // Clean CTRL chars. 167 $cleananswer = preg_replace("/\r\n|\r/", "\n", $cleananswer); // Normalize line ending. 168 169 $this->questionsubcache[$cleanquestion][$cleananswer] = $potentialsub->id; 170 } 171 } 172 173 if (!isset($this->questionsubcache[$data->questiontext][$data->answertext])) { 174 throw new restore_step_exception('error_qtype_match_subquestion_missing_in_db', $data); 175 } 176 $newitemid = $this->questionsubcache[$data->questiontext][$data->answertext]; 177 } 178 179 // Found one. Let's create the mapping. 180 $this->set_mapping('qtype_match_subquestions', $oldid, $newitemid); 181 } 182 183 public function recode_response($questionid, $sequencenumber, array $response) { 184 if (array_key_exists('_stemorder', $response)) { 185 $response['_stemorder'] = $this->recode_match_sub_order($response['_stemorder']); 186 } 187 if (array_key_exists('_choiceorder', $response)) { 188 $response['_choiceorder'] = $this->recode_match_sub_order($response['_choiceorder']); 189 } 190 return $response; 191 } 192 193 /** 194 * Given one question_states record, return the answer 195 * recoded pointing to all the restored stuff for match questions. 196 * 197 * answer is one comma separated list of hypen separated pairs 198 * containing question_match_sub->id and question_match_sub->code, which 199 * has been remapped to be qtype_match_subquestions->id, since code no longer exists. 200 */ 201 public function recode_legacy_state_answer($state) { 202 $answer = $state->answer; 203 $resultarr = array(); 204 foreach (explode(',', $answer) as $pair) { 205 $pairarr = explode('-', $pair); 206 $id = $pairarr[0]; 207 $code = $pairarr[1]; 208 $newid = $this->get_mappingid('qtype_match_subquestions', $id); 209 if ($code) { 210 $newcode = $this->get_mappingid('qtype_match_subquestion_codes', $code); 211 } else { 212 $newcode = $code; 213 } 214 $resultarr[] = $newid . '-' . $newcode; 215 } 216 return implode(',', $resultarr); 217 } 218 219 /** 220 * Recode the choice order as stored in the response. 221 * @param string $order the original order. 222 * @return string the recoded order. 223 */ 224 protected function recode_match_sub_order($order) { 225 $neworder = array(); 226 foreach (explode(',', $order) as $id) { 227 if ($newid = $this->get_mappingid('qtype_match_subquestions', $id)) { 228 $neworder[] = $newid; 229 } 230 } 231 return implode(',', $neworder); 232 } 233 234 /** 235 * Return the contents of this qtype to be processed by the links decoder. 236 */ 237 public static function define_decode_contents() { 238 239 $contents = array(); 240 241 $contents[] = new restore_decode_content('qtype_match_subquestions', 242 array('questiontext'), 'qtype_match_subquestions'); 243 244 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 245 $contents[] = new restore_decode_content('qtype_match_options', $fields, 'qtype_match_options'); 246 247 return $contents; 248 } 249 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body