MDL-51855 mod_question: fix offset error during upgrade
[moodle.git] / question / type / multianswer / backup / moodle2 / restore_qtype_multianswer_plugin.class.php
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/>.
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  */
25 defined('MOODLE_INTERNAL') || die();
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 {
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();
43         // This qtype uses question_answers, add them.
44         $this->add_question_question_answers($paths);
46         // Add own qtype stuff.
47         $elename = 'multianswer';
48         $elepath = $this->get_pathfor('/multianswer');
49         $paths[] = new restore_path_element($elename, $elepath);
51         return $paths; // And we return the interesting paths.
52     }
54     /**
55      * Process the qtype/multianswer element
56      */
57     public function process_multianswer($data) {
58         global $DB;
60         $data = (object)$data;
61         $oldid = $data->id;
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;
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     }
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 = explode(',', $rec->sequence);
106             foreach ($sequencearr as $key => $question) {
107                 $sequencearr[$key] = $this->get_mappingid('question', $question);
108             }
109             $sequence = implode(',', $sequencearr);
110             $DB->set_field('question_multianswer', 'sequence', $sequence,
111                     array('id' => $rec->id));
112             if (!empty($sequence)) {
113                 // Get relevant data indexed by positionkey from the multianswers table.
114                 $wrappedquestions = $DB->get_records_list('question', 'id',
115                     explode(',', $sequence), 'id ASC');
116                 foreach ($wrappedquestions as $wrapped) {
117                     if ($wrapped->qtype == 'multichoice') {
118                         question_bank::get_qtype($wrapped->qtype)->get_question_options($wrapped);
119                         if (isset($wrapped->options->shuffleanswers)) {
120                             preg_match('/'.ANSWER_REGEX.'/s', $wrapped->questiontext, $answerregs);
121                             if (isset($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]) &&
122                                     $answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE] !== '') {
123                                 $wrapped->options->shuffleanswers = 0;
124                                 $DB->set_field_select('qtype_multichoice_options', 'shuffleanswers', '0', "id =:select",
125                                     array('select' => $wrapped->options->id) );
126                             }
127                         }
128                     }
129                 }
130             }
131         }
132         $rs->close();
133     }
135     public function recode_response($questionid, $sequencenumber, array $response) {
136         global $DB;
138         $qtypes = $DB->get_records_menu('question', array('parent' => $questionid),
139                 '', 'id, qtype');
141         $sequence = $DB->get_field('question_multianswer', 'sequence',
142                 array('question' => $questionid));
144         $fakestep = new question_attempt_step_read_only($response);
146         foreach (explode(',', $sequence) as $key => $subqid) {
147             $i = $key + 1;
149             $substep = new question_attempt_step_subquestion_adapter($fakestep, 'sub' . $i . '_');
150             $recodedresponse = $this->step->questions_recode_response_data($qtypes[$subqid],
151                     $subqid, $sequencenumber, $substep->get_all_data());
153             foreach ($recodedresponse as $name => $value) {
154                 $response[$substep->add_prefix($name)] = $value;
155             }
156         }
158         return $response;
159     }
161     /**
162      * Given one question_states record, return the answer
163      * recoded pointing to all the restored stuff for multianswer questions
164      *
165      * answer is one comma separated list of hypen separated pairs
166      * containing sequence (pointing to questions sequence in question_multianswer)
167      * and mixed answers. We'll delegate
168      * the recoding of answers to the proper qtype
169      */
170     public function recode_legacy_state_answer($state) {
171         global $DB;
172         $answer = $state->answer;
173         $resultarr = array();
174         // Get sequence of questions.
175         $sequence = $DB->get_field('question_multianswer', 'sequence',
176                 array('question' => $state->question));
177         $sequencearr = explode(',', $sequence);
178         // Let's process each pair.
179         foreach (explode(',', $answer) as $pair) {
180             $pairarr = explode('-', $pair);
181             $sequenceid = $pairarr[0];
182             $subanswer = $pairarr[1];
183             // Calculate the questionid based on sequenceid.
184             // Note it is already one *new* questionid that doesn't need mapping.
185             $questionid = $sequencearr[$sequenceid - 1];
186             // Fetch qtype of the question (needed for delegation).
187             $questionqtype = $DB->get_field('question', 'qtype', array('id' => $questionid));
188             // Delegate subanswer recode to proper qtype, faking one question_states record.
189             $substate = new stdClass();
190             $substate->question = $questionid;
191             $substate->answer = $subanswer;
192             $newanswer = $this->step->restore_recode_legacy_answer($substate, $questionqtype);
193             $resultarr[] = implode('-', array($sequenceid, $newanswer));
194         }
195         return implode(',', $resultarr);
196     }