NOBUG: Changed to _sql() variant to properly use sql_compare_text(). Kudos to aparup...
[moodle.git] / backup / moodle2 / restore_qtype_plugin.class.php
CommitLineData
41941110
EL
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * @package moodlecore
20 * @subpackage backup-moodle2
21 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25/**
26 * Class extending standard restore_plugin in order to implement some
27 * helper methods related with the questions (qtype plugin)
28 *
29 * TODO: Finish phpdocs
30 */
31abstract class restore_qtype_plugin extends restore_plugin {
32
33 /**
34 * Add to $paths the restore_path_elements needed
35 * to handle question_answers for a given question
36 * Used by various qtypes (calculated, essay, multianswer,
37 * multichoice, numerical, shortanswer, truefalse)
38 */
39 protected function add_question_question_answers(&$paths) {
40 // Check $paths is one array
41 if (!is_array($paths)) {
42 throw new restore_step_exception('paths_must_be_array', $paths);
43 }
44
45 $elename = 'question_answer';
46 $elepath = $this->get_pathfor('/answers/answer'); // we used get_recommended_name() so this works
47 $paths[] = new restore_path_element($elename, $elepath);
48 }
49
50 /**
51 * Add to $paths the restore_path_elements needed
52 * to handle question_numerical_units for a given question
53 * Used by various qtypes (calculated, numerical)
54 */
55 protected function add_question_numerical_units(&$paths) {
56 // Check $paths is one array
57 if (!is_array($paths)) {
58 throw new restore_step_exception('paths_must_be_array', $paths);
59 }
60
61 $elename = 'question_numerical_unit';
62 $elepath = $this->get_pathfor('/numerical_units/numerical_unit'); // we used get_recommended_name() so this works
63 $paths[] = new restore_path_element($elename, $elepath);
64 }
65
66 /**
67 * Add to $paths the restore_path_elements needed
68 * to handle question_numerical_options for a given question
69 * Used by various qtypes (calculated, numerical)
70 */
71 protected function add_question_numerical_options(&$paths) {
72 // Check $paths is one array
73 if (!is_array($paths)) {
74 throw new restore_step_exception('paths_must_be_array', $paths);
75 }
76
77 $elename = 'question_numerical_option';
78 $elepath = $this->get_pathfor('/numerical_options/numerical_option'); // we used get_recommended_name() so this works
79 $paths[] = new restore_path_element($elename, $elepath);
80 }
81
82 /**
83 * Add to $paths the restore_path_elements needed
84 * to handle question_datasets (defs and items) for a given question
85 * Used by various qtypes (calculated, numerical)
86 */
87 protected function add_question_datasets(&$paths) {
88 // Check $paths is one array
89 if (!is_array($paths)) {
90 throw new restore_step_exception('paths_must_be_array', $paths);
91 }
92
93 $elename = 'question_dataset_definition';
94 $elepath = $this->get_pathfor('/dataset_definitions/dataset_definition'); // we used get_recommended_name() so this works
95 $paths[] = new restore_path_element($elename, $elepath);
96
97 $elename = 'question_dataset_item';
98 $elepath = $this->get_pathfor('/dataset_definitions/dataset_definition/dataset_items/dataset_item');
99 $paths[] = new restore_path_element($elename, $elepath);
100 }
101
102 /**
103 * Processes the answer element (question answers). Common for various qtypes.
104 * It handles both creation (if the question is being created) and mapping
105 * (if the question already existed and is being reused)
106 */
107 public function process_question_answer($data) {
108 global $DB;
109
110 $data = (object)$data;
111 $oldid = $data->id;
112
113 // Detect if the question is created or mapped
114 $oldquestionid = $this->get_old_parentid('question');
115 $newquestionid = $this->get_new_parentid('question');
116 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
117
118 // If the question has been created by restore, we need to create its question_answers too
119 if ($questioncreated) {
120 // Adjust some columns
121 $data->question = $newquestionid;
122 $data->answer = $data->answertext;
123 // Insert record
124 $newitemid = $DB->insert_record('question_answers', $data);
125
126 // The question existed, we need to map the existing question_answers
127 } else {
128 // Look in question_answers by answertext matching
77151075
EL
129 $sql = 'SELECT id
130 FROM {question_answers}
131 WHERE question = ?
132 AND ' . $DB->sql_compare_text('answer', 255) . ' = ' . $DB->sql_compare_text('?', 255);
133 $params = array($newquestionid, $data->answertext);
134 $newitemid = $DB->get_field_sql($sql, $params);
41941110
EL
135 // If we haven't found the newitemid, something has gone really wrong, question in DB
136 // is missing answers, exception
137 if (!$newitemid) {
138 $info = new stdClass();
139 $info->filequestionid = $oldquestionid;
140 $info->dbquestionid = $newquestionid;
141 $info->answer = $data->answertext;
142 throw restore_step_exception('error_question_answers_missing_in_db', $info);
143 }
144 }
145 // Create mapping (we'll use this intensively when restoring question_states. And also answerfeedback files)
146 $this->set_mapping('question_answer', $oldid, $newitemid);
147 }
148
149 /**
150 * Processes the numerical_unit element (question numerical units). Common for various qtypes.
151 * It handles both creation (if the question is being created) and mapping
152 * (if the question already existed and is being reused)
153 */
154 public function process_question_numerical_unit($data) {
155 global $DB;
156
157 $data = (object)$data;
158 $oldid = $data->id;
159
160 // Detect if the question is created or mapped
161 $oldquestionid = $this->get_old_parentid('question');
162 $newquestionid = $this->get_new_parentid('question');
163 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
164
165 // If the question has been created by restore, we need to create its question_numerical_units too
166 if ($questioncreated) {
167 // Adjust some columns
168 $data->question = $newquestionid;
169 // Insert record
170 $newitemid = $DB->insert_record('question_numerical_units', $data);
171 }
172 }
173
174 /**
175 * Processes the numerical_option element (question numerical options). Common for various qtypes.
176 * It handles both creation (if the question is being created) and mapping
177 * (if the question already existed and is being reused)
178 */
179 public function process_question_numerical_option($data) {
180 global $DB;
181
182 $data = (object)$data;
183 $oldid = $data->id;
184
185 // Detect if the question is created or mapped
186 $oldquestionid = $this->get_old_parentid('question');
187 $newquestionid = $this->get_new_parentid('question');
188 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
189
190 // If the question has been created by restore, we need to create its question_numerical_options too
191 if ($questioncreated) {
192 // Adjust some columns
193 $data->question = $newquestionid;
194 // Insert record
195 $newitemid = $DB->insert_record('question_numerical_options', $data);
196 // Create mapping (not needed, no files nor childs nor states here)
197 //$this->set_mapping('question_numerical_option', $oldid, $newitemid);
198 }
199 }
200
201 /**
202 * Processes the dataset_definition element (question dataset definitions). Common for various qtypes.
203 * It handles both creation (if the question is being created) and mapping
204 * (if the question already existed and is being reused)
205 */
206 public function process_question_dataset_definition($data) {
207 global $DB;
208
209 $data = (object)$data;
210 $oldid = $data->id;
211
212 // Detect if the question is created or mapped
213 $oldquestionid = $this->get_old_parentid('question');
214 $newquestionid = $this->get_new_parentid('question');
215 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
216
217 // If the question is mapped, nothing to do
218 if (!$questioncreated) {
219 return;
220 }
221
222 // Arrived here, let's see if the question_dataset_definition already exists in category or no
223 // (by category, name, type and enough items). Only for "shared" definitions (category != 0).
224 // If exists, reuse it, else, create it as "not shared" (category = 0)
225 $data->category = $this->get_mappingid('question_category', $data->category);
226 // If category is shared, look for definitions
227 $founddefid = null;
228 if ($data->category) {
229 $candidatedefs = $DB->get_records_sql("SELECT id, itemcount
230 FROM {question_dataset_definitions}
231 WHERE category = ?
232 AND name = ?
233 AND type = ?", array($data->category, $data->name, $data->type));
234 foreach ($candidatedefs as $candidatedef) {
235 if ($candidatedef->itemcount >= $data->itemcount) { // Check it has enough items
236 $founddefid = $candidatedef->id;
237 break; // end loop, shared definition match found
238 }
239 }
240 // If there were candidates but none fulfilled the itemcount condition, create definition as not shared
241 if ($candidatedefs && !$founddefid) {
242 $data->category = 0;
243 }
244 }
245 // If haven't found any shared definition match, let's create it
246 if (!$founddefid) {
247 $newitemid = $DB->insert_record('question_dataset_definitions', $data);
248 // Set mapping, so dataset items will know if they must be created
249 $this->set_mapping('question_dataset_definition', $oldid, $newitemid);
250
251 // If we have found one shared definition match, use it
252 } else {
253 $newitemid = $founddefid;
254 // Set mapping to 0, so dataset items will know they don't need to be created
255 $this->set_mapping('question_dataset_definition', $oldid, 0);
256 }
257
258 // Arrived here, we have one $newitemid (create or reused). Create the question_datasets record
259 $questiondataset = new stdClass();
260 $questiondataset->question = $newquestionid;
261 $questiondataset->datasetdefinition = $newitemid;
262 $DB->insert_record('question_datasets', $questiondataset);
263 }
264
265 /**
266 * Processes the dataset_item element (question dataset items). Common for various qtypes.
267 * It handles both creation (if the question is being created) and mapping
268 * (if the question already existed and is being reused)
269 */
270 public function process_question_dataset_item($data) {
271 global $DB;
272
273 $data = (object)$data;
274 $oldid = $data->id;
275
276 // Detect if the question is created or mapped
277 $oldquestionid = $this->get_old_parentid('question');
278 $newquestionid = $this->get_new_parentid('question');
279 $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
280
281 // If the question is mapped, nothing to do
282 if (!$questioncreated) {
283 return;
284 }
285
286 // Detect if the question_dataset_definition is being created
287 $newdefinitionid = $this->get_new_parentid('question_dataset_definition');
288
289 // If the definition is reused, nothing to do
290 if (!$newdefinitionid) {
291 return;
292 }
293
294 // let's create the question_dataset_items
295 $data->definition = $newdefinitionid;
296 $data->itemnumber = $data->number;
297 $DB->insert_record('question_dataset_items', $data);
298 }
299
300 /**
301 * Decode one question_states for this qtype (default impl)
302 */
303 public function recode_state_answer($state) {
304 // By default, return answer unmodified, qtypes needing recode will override this
305 return $state->answer;
306 }
9f68f2d5
EL
307
308 /**
309 * Return the contents of the questions stuff that must be processed by the links decoder
310 *
311 * Only common stuff to all plugins, in this case:
312 * - question: text and feedback
313 * - question_answers: text and feedbak
314 *
315 * Note each qtype will have, if needed, its own define_decode_contents method
316 */
317 static public function define_plugin_decode_contents() {
318
319 $contents = array();
320
321 $contents[] = new restore_decode_content('question', array('questiontext', 'generalfeedback'), 'question_created');
322 $contents[] = new restore_decode_content('question_answers', array('answer', 'feedback'), 'question_answer');
323
324 return $contents;
325 }
41941110 326}