1cd3abb17e9baeffc9eb948599509712835c4af4
[moodle.git] / mod / quiz / backup / moodle2 / restore_quiz_stepslib.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    mod_quiz
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();
28 /**
29  * Structure step to restore one quiz activity
30  *
31  * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
32  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33  */
34 class restore_quiz_activity_structure_step extends restore_questions_activity_structure_step {
36     protected function define_structure() {
38         $paths = array();
39         $userinfo = $this->get_setting_value('userinfo');
41         $quiz = new restore_path_element('quiz', '/activity/quiz');
42         $paths[] = $quiz;
44         // A chance for access subplugings to set up their quiz data.
45         $this->add_subplugin_structure('quizaccess', $quiz);
47         $paths[] = new restore_path_element('quiz_question_instance',
48                 '/activity/quiz/question_instances/question_instance');
49         $paths[] = new restore_path_element('quiz_feedback', '/activity/quiz/feedbacks/feedback');
50         $paths[] = new restore_path_element('quiz_override', '/activity/quiz/overrides/override');
52         if ($userinfo) {
53             $paths[] = new restore_path_element('quiz_grade', '/activity/quiz/grades/grade');
55             if ($this->task->get_old_moduleversion() > 2011010100) {
56                 // Restoring from a version 2.1 dev or later.
57                 // Process the new-style attempt data.
58                 $quizattempt = new restore_path_element('quiz_attempt',
59                         '/activity/quiz/attempts/attempt');
60                 $paths[] = $quizattempt;
62                 // Add states and sessions.
63                 $this->add_question_usages($quizattempt, $paths);
65                 // A chance for access subplugings to set up their attempt data.
66                 $this->add_subplugin_structure('quizaccess', $quizattempt);
68             } else {
69                 // Restoring from a version 2.0.x+ or earlier.
70                 // Upgrade the legacy attempt data.
71                 $quizattempt = new restore_path_element('quiz_attempt_legacy',
72                         '/activity/quiz/attempts/attempt',
73                         true);
74                 $paths[] = $quizattempt;
75                 $this->add_legacy_question_attempt_data($quizattempt, $paths);
76             }
77         }
79         // Return the paths wrapped into standard activity structure.
80         return $this->prepare_activity_structure($paths);
81     }
83     protected function process_quiz($data) {
84         global $CFG, $DB;
86         $data = (object)$data;
87         $oldid = $data->id;
88         $data->course = $this->get_courseid();
90         $data->timeopen = $this->apply_date_offset($data->timeopen);
91         $data->timeclose = $this->apply_date_offset($data->timeclose);
92         $data->timecreated = $this->apply_date_offset($data->timecreated);
93         $data->timemodified = $this->apply_date_offset($data->timemodified);
95         if (property_exists($data, 'questions')) {
96             // Needed by {@link process_quiz_attempt_legacy}, in which case it will be present.
97             $this->oldquizlayout = $data->questions;
98         }
100         // The setting quiz->attempts can come both in data->attempts and
101         // data->attempts_number, handle both. MDL-26229.
102         if (isset($data->attempts_number)) {
103             $data->attempts = $data->attempts_number;
104             unset($data->attempts_number);
105         }
107         // The old optionflags and penaltyscheme from 2.0 need to be mapped to
108         // the new preferredbehaviour. See MDL-20636.
109         if (!isset($data->preferredbehaviour)) {
110             if (empty($data->optionflags)) {
111                 $data->preferredbehaviour = 'deferredfeedback';
112             } else if (empty($data->penaltyscheme)) {
113                 $data->preferredbehaviour = 'adaptivenopenalty';
114             } else {
115                 $data->preferredbehaviour = 'adaptive';
116             }
117             unset($data->optionflags);
118             unset($data->penaltyscheme);
119         }
121         // The old review column from 2.0 need to be split into the seven new
122         // review columns. See MDL-20636.
123         if (isset($data->review)) {
124             require_once($CFG->dirroot . '/mod/quiz/locallib.php');
126             if (!defined('QUIZ_OLD_IMMEDIATELY')) {
127                 define('QUIZ_OLD_IMMEDIATELY', 0x3c003f);
128                 define('QUIZ_OLD_OPEN',        0x3c00fc0);
129                 define('QUIZ_OLD_CLOSED',      0x3c03f000);
131                 define('QUIZ_OLD_RESPONSES',        1*0x1041);
132                 define('QUIZ_OLD_SCORES',           2*0x1041);
133                 define('QUIZ_OLD_FEEDBACK',         4*0x1041);
134                 define('QUIZ_OLD_ANSWERS',          8*0x1041);
135                 define('QUIZ_OLD_SOLUTIONS',       16*0x1041);
136                 define('QUIZ_OLD_GENERALFEEDBACK', 32*0x1041);
137                 define('QUIZ_OLD_OVERALLFEEDBACK',  1*0x4440000);
138             }
140             $oldreview = $data->review;
142             $data->reviewattempt =
143                     mod_quiz_display_options::DURING |
144                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_RESPONSES ?
145                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
146                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_RESPONSES ?
147                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
148                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_RESPONSES ?
149                             mod_quiz_display_options::AFTER_CLOSE : 0);
151             $data->reviewcorrectness =
152                     mod_quiz_display_options::DURING |
153                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ?
154                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
155                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ?
156                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
157                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ?
158                             mod_quiz_display_options::AFTER_CLOSE : 0);
160             $data->reviewmarks =
161                     mod_quiz_display_options::DURING |
162                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ?
163                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
164                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ?
165                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
166                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ?
167                             mod_quiz_display_options::AFTER_CLOSE : 0);
169             $data->reviewspecificfeedback =
170                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ?
171                             mod_quiz_display_options::DURING : 0) |
172                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ?
173                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
174                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_FEEDBACK ?
175                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
176                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_FEEDBACK ?
177                             mod_quiz_display_options::AFTER_CLOSE : 0);
179             $data->reviewgeneralfeedback =
180                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ?
181                             mod_quiz_display_options::DURING : 0) |
182                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ?
183                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
184                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_GENERALFEEDBACK ?
185                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
186                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_GENERALFEEDBACK ?
187                             mod_quiz_display_options::AFTER_CLOSE : 0);
189             $data->reviewrightanswer =
190                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ?
191                             mod_quiz_display_options::DURING : 0) |
192                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ?
193                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
194                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_ANSWERS ?
195                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
196                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_ANSWERS ?
197                             mod_quiz_display_options::AFTER_CLOSE : 0);
199             $data->reviewoverallfeedback =
200                     0 |
201                     ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_OVERALLFEEDBACK ?
202                             mod_quiz_display_options::IMMEDIATELY_AFTER : 0) |
203                     ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_OVERALLFEEDBACK ?
204                             mod_quiz_display_options::LATER_WHILE_OPEN : 0) |
205                     ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_OVERALLFEEDBACK ?
206                             mod_quiz_display_options::AFTER_CLOSE : 0);
207         }
209         // The old popup column from from <= 2.1 need to be mapped to
210         // the new browsersecurity. See MDL-29627.
211         if (!isset($data->browsersecurity)) {
212             if (empty($data->popup)) {
213                 $data->browsersecurity = '-';
214             } else if ($data->popup == 1) {
215                 $data->browsersecurity = 'securewindow';
216             } else if ($data->popup == 2) {
217                 $data->browsersecurity = 'safebrowser';
218             } else {
219                 $data->preferredbehaviour = '-';
220             }
221             unset($data->popup);
222         }
224         if (!isset($data->overduehandling)) {
225             $data->overduehandling = get_config('quiz', 'overduehandling');
226         }
228         // Insert the quiz record.
229         $newitemid = $DB->insert_record('quiz', $data);
230         // Immediately after inserting "activity" record, call this.
231         $this->apply_activity_instance($newitemid);
232     }
234     protected function process_quiz_question_instance($data) {
235         global $DB;
237         $data = (object)$data;
239         // Backwards compatibility for old field names (MDL-43670).
240         if (!isset($data->questionid) && isset($data->question)) {
241             $data->questionid = $data->question;
242         }
243         if (!isset($data->maxmark) && isset($data->grade)) {
244             $data->maxmark = $data->grade;
245         }
247         if (!property_exists($data, 'slot')) {
248             $page = 1;
249             $slot = 1;
250             foreach (explode(',', $this->oldquizlayout) as $item) {
251                 if ($item == 0) {
252                     $page += 1;
253                     continue;
254                 }
255                 if ($item == $data->questionid) {
256                     $data->slot = $slot;
257                     $data->page = $page;
258                     break;
259                 }
260                 $slot += 1;
261             }
262         }
264         if (!property_exists($data, 'slot')) {
265             // There was a question_instance in the backup file for a question
266             // that was not acutally in the quiz. Drop it.
267             $this->log('question ' . $data->questionid . ' was associated with quiz ' .
268                     $quiz->id . ' but not actually used. ' .
269                     'The instance has been ignored.', backup::LOG_INFO);
270             return;
271         }
273         $data->quizid = $this->get_new_parentid('quiz');
274         $data->questionid = $this->get_mappingid('question', $data->questionid);
276         $DB->insert_record('quiz_slots', $data);
277     }
279     protected function process_quiz_feedback($data) {
280         global $DB;
282         $data = (object)$data;
283         $oldid = $data->id;
285         $data->quizid = $this->get_new_parentid('quiz');
287         $newitemid = $DB->insert_record('quiz_feedback', $data);
288         $this->set_mapping('quiz_feedback', $oldid, $newitemid, true); // Has related files.
289     }
291     protected function process_quiz_override($data) {
292         global $DB;
294         $data = (object)$data;
295         $oldid = $data->id;
297         // Based on userinfo, we'll restore user overides or no.
298         $userinfo = $this->get_setting_value('userinfo');
300         // Skip user overrides if we are not restoring userinfo.
301         if (!$userinfo && !is_null($data->userid)) {
302             return;
303         }
305         $data->quiz = $this->get_new_parentid('quiz');
307         $data->userid = $this->get_mappingid('user', $data->userid);
308         $data->groupid = $this->get_mappingid('group', $data->groupid);
310         $data->timeopen = $this->apply_date_offset($data->timeopen);
311         $data->timeclose = $this->apply_date_offset($data->timeclose);
313         $newitemid = $DB->insert_record('quiz_overrides', $data);
315         // Add mapping, restore of logs needs it.
316         $this->set_mapping('quiz_override', $oldid, $newitemid);
317     }
319     protected function process_quiz_grade($data) {
320         global $DB;
322         $data = (object)$data;
323         $oldid = $data->id;
325         $data->quiz = $this->get_new_parentid('quiz');
327         $data->userid = $this->get_mappingid('user', $data->userid);
328         $data->grade = $data->gradeval;
330         $data->timemodified = $this->apply_date_offset($data->timemodified);
332         $DB->insert_record('quiz_grades', $data);
333     }
335     protected function process_quiz_attempt($data) {
336         $data = (object)$data;
338         $data->quiz = $this->get_new_parentid('quiz');
339         $data->attempt = $data->attemptnum;
341         $data->userid = $this->get_mappingid('user', $data->userid);
343         $data->timestart = $this->apply_date_offset($data->timestart);
344         $data->timefinish = $this->apply_date_offset($data->timefinish);
345         $data->timemodified = $this->apply_date_offset($data->timemodified);
346         if (!empty($data->timecheckstate)) {
347             $data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
348         } else {
349             $data->timecheckstate = 0;
350         }
352         // Deals with up-grading pre-2.3 back-ups to 2.3+.
353         if (!isset($data->state)) {
354             if ($data->timefinish > 0) {
355                 $data->state = 'finished';
356             } else {
357                 $data->state = 'inprogress';
358             }
359         }
361         // The data is actually inserted into the database later in inform_new_usage_id.
362         $this->currentquizattempt = clone($data);
363     }
365     protected function process_quiz_attempt_legacy($data) {
366         global $DB;
368         $this->process_quiz_attempt($data);
370         $quiz = $DB->get_record('quiz', array('id' => $this->get_new_parentid('quiz')));
371         $quiz->oldquestions = $this->oldquizlayout;
372         $this->process_legacy_quiz_attempt_data($data, $quiz);
373     }
375     protected function inform_new_usage_id($newusageid) {
376         global $DB;
378         $data = $this->currentquizattempt;
380         $oldid = $data->id;
381         $data->uniqueid = $newusageid;
383         $newitemid = $DB->insert_record('quiz_attempts', $data);
385         // Save quiz_attempt->id mapping, because logs use it.
386         $this->set_mapping('quiz_attempt', $oldid, $newitemid, false);
387     }
389     protected function after_execute() {
390         parent::after_execute();
391         // Add quiz related files, no need to match by itemname (just internally handled context).
392         $this->add_related_files('mod_quiz', 'intro', null);
393         // Add feedback related files, matching by itemname = 'quiz_feedback'.
394         $this->add_related_files('mod_quiz', 'feedback', 'quiz_feedback');
395     }