Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
use mod_quiz\question\display_options;
18
 
19
/**
20
 * Structure step to restore one quiz activity
21
 *
22
 * @package    mod_quiz
23
 * @subpackage backup-moodle2
24
 * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
25
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
27
class restore_quiz_activity_structure_step extends restore_questions_activity_structure_step {
28
 
29
    /**
30
     * @var bool tracks whether the quiz contains at least one section. Before
31
     * Moodle 2.9 quiz sections did not exist, so if the file being restored
32
     * did not contain any, we need to create one in {@link after_execute()}.
33
     */
34
    protected $sectioncreated = false;
35
 
36
    /** @var stdClass|null $currentquizattempt Track the current quiz attempt being restored. */
37
    protected $currentquizattempt = null;
38
 
39
    /**
40
     * @var bool when restoring old quizzes (2.8 or before) this records the
41
     * shufflequestionsoption quiz option which has moved to the quiz_sections table.
42
     */
43
    protected $legacyshufflequestionsoption = false;
44
 
45
    /** @var stdClass */
46
    protected $oldquizlayout;
47
 
48
    /**
49
     * @var array Track old question ids that need to be removed at the end of the restore.
50
     */
51
    protected $oldquestionids = [];
52
 
53
    protected function define_structure() {
54
 
55
        $paths = [];
56
        $userinfo = $this->get_setting_value('userinfo');
57
 
58
        $quiz = new restore_path_element('quiz', '/activity/quiz');
59
        $paths[] = $quiz;
60
 
61
        // A chance for access subplugings to set up their quiz data.
62
        $this->add_subplugin_structure('quizaccess', $quiz);
63
 
64
        $paths[] = new restore_path_element('quiz_grade_item', '/activity/quiz/quiz_grade_items/quiz_grade_item');
65
        $quizquestioninstance = new restore_path_element('quiz_question_instance',
66
            '/activity/quiz/question_instances/question_instance');
67
        $paths[] = $quizquestioninstance;
68
        if ($this->task->get_old_moduleversion() < 2021091700) {
69
            $paths[] = new restore_path_element('quiz_slot_tags',
70
                '/activity/quiz/question_instances/question_instance/tags/tag');
71
        } else {
72
            $this->add_question_references($quizquestioninstance, $paths);
73
            $this->add_question_set_references($quizquestioninstance, $paths);
74
        }
75
        $paths[] = new restore_path_element('quiz_section', '/activity/quiz/sections/section');
76
        $paths[] = new restore_path_element('quiz_feedback', '/activity/quiz/feedbacks/feedback');
77
        $paths[] = new restore_path_element('quiz_override', '/activity/quiz/overrides/override');
78
 
79
        if ($userinfo) {
80
            $paths[] = new restore_path_element('quiz_grade', '/activity/quiz/grades/grade');
81
 
82
            if ($this->task->get_old_moduleversion() > 2011010100) {
83
                // Restoring from a version 2.1 dev or later.
84
                // Process the new-style attempt data.
85
                $quizattempt = new restore_path_element('quiz_attempt',
86
                        '/activity/quiz/attempts/attempt');
87
                $paths[] = $quizattempt;
88
 
89
                // Add states and sessions.
90
                $this->add_question_usages($quizattempt, $paths);
91
 
92
                // A chance for access subplugings to set up their attempt data.
93
                $this->add_subplugin_structure('quizaccess', $quizattempt);
94
 
95
            } else {
96
                // Restoring from a version 2.0.x+ or earlier.
97
                // Upgrade the legacy attempt data.
98
                $quizattempt = new restore_path_element('quiz_attempt_legacy',
99
                        '/activity/quiz/attempts/attempt',
100
                        true);
101
                $paths[] = $quizattempt;
102
                $this->add_legacy_question_attempt_data($quizattempt, $paths);
103
            }
104
        }
105
 
106
        // Return the paths wrapped into standard activity structure.
107
        return $this->prepare_activity_structure($paths);
108
    }
109
 
110
    /**
111
     * Process the quiz data.
112
     *
113
     * @param stdClass|array $data
114
     */
115
    protected function process_quiz($data) {
116
        global $CFG, $DB, $USER;
117
 
118
        $data = (object)$data;
119
        $oldid = $data->id;
120
        $data->course = $this->get_courseid();
121
 
122
        // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
123
        // See MDL-9367.
124
 
125
        $data->timeopen = $this->apply_date_offset($data->timeopen);
126
        $data->timeclose = $this->apply_date_offset($data->timeclose);
127
 
128
        if (property_exists($data, 'questions')) {
129
            // Needed by {@link process_quiz_attempt_legacy}, in which case it will be present.
130
            $this->oldquizlayout = $data->questions;
131
        }
132
 
133
        // The setting quiz->attempts can come both in data->attempts and
134
        // data->attempts_number, handle both. MDL-26229.
135
        if (isset($data->attempts_number)) {
136
            $data->attempts = $data->attempts_number;
137
            unset($data->attempts_number);
138
        }
139
 
140
        // The old optionflags and penaltyscheme from 2.0 need to be mapped to
141
        // the new preferredbehaviour. See MDL-20636.
142
        if (!isset($data->preferredbehaviour)) {
143
            if (empty($data->optionflags)) {
144
                $data->preferredbehaviour = 'deferredfeedback';
145
            } else if (empty($data->penaltyscheme)) {
146
                $data->preferredbehaviour = 'adaptivenopenalty';
147
            } else {
148
                $data->preferredbehaviour = 'adaptive';
149
            }
150
            unset($data->optionflags);
151
            unset($data->penaltyscheme);
152
        }
153
 
154
        // The old review column from 2.0 need to be split into the seven new
155
        // review columns. See MDL-20636.
156
        if (isset($data->review)) {
157
            require_once($CFG->dirroot . '/mod/quiz/locallib.php');
158
 
159
            if (!defined('QUIZ_OLD_IMMEDIATELY')) {
160
                define('QUIZ_OLD_IMMEDIATELY', 0x3c003f);
161
                define('QUIZ_OLD_OPEN',        0x3c00fc0);
162
                define('QUIZ_OLD_CLOSED',      0x3c03f000);
163
 
164
                define('QUIZ_OLD_RESPONSES',        1*0x1041);
165
                define('QUIZ_OLD_SCORES',           2*0x1041);
166
                define('QUIZ_OLD_FEEDBACK',         4*0x1041);
167
                define('QUIZ_OLD_ANSWERS',          8*0x1041);
168
                define('QUIZ_OLD_SOLUTIONS',       16*0x1041);
169
                define('QUIZ_OLD_GENERALFEEDBACK', 32*0x1041);
170
                define('QUIZ_OLD_OVERALLFEEDBACK',  1*0x4440000);
171
            }
172
 
173
            $oldreview = $data->review;
174
 
175
            $data->reviewattempt =
176
                    display_options::DURING |
177
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_RESPONSES ?
178
                            display_options::IMMEDIATELY_AFTER : 0) |
179
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_RESPONSES ?
180
                            display_options::LATER_WHILE_OPEN : 0) |
181
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_RESPONSES ?
182
                            display_options::AFTER_CLOSE : 0);
183
 
184
            $data->reviewcorrectness =
185
                    display_options::DURING |
186
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ?
187
                            display_options::IMMEDIATELY_AFTER : 0) |
188
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ?
189
                            display_options::LATER_WHILE_OPEN : 0) |
190
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ?
191
                            display_options::AFTER_CLOSE : 0);
192
 
193
            $data->reviewmarks =
194
                    display_options::DURING |
195
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ?
196
                            display_options::IMMEDIATELY_AFTER : 0) |
197
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ?
198
                            display_options::LATER_WHILE_OPEN : 0) |
199
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ?
200
                            display_options::AFTER_CLOSE : 0);
201
 
202
            $data->reviewspecificfeedback =
203
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ?
204
                            display_options::DURING : 0) |
205
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ?
206
                            display_options::IMMEDIATELY_AFTER : 0) |
207
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_FEEDBACK ?
208
                            display_options::LATER_WHILE_OPEN : 0) |
209
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_FEEDBACK ?
210
                            display_options::AFTER_CLOSE : 0);
211
 
212
            $data->reviewgeneralfeedback =
213
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ?
214
                            display_options::DURING : 0) |
215
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ?
216
                            display_options::IMMEDIATELY_AFTER : 0) |
217
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_GENERALFEEDBACK ?
218
                            display_options::LATER_WHILE_OPEN : 0) |
219
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_GENERALFEEDBACK ?
220
                            display_options::AFTER_CLOSE : 0);
221
 
222
            $data->reviewrightanswer =
223
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ?
224
                            display_options::DURING : 0) |
225
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ?
226
                            display_options::IMMEDIATELY_AFTER : 0) |
227
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_ANSWERS ?
228
                            display_options::LATER_WHILE_OPEN : 0) |
229
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_ANSWERS ?
230
                            display_options::AFTER_CLOSE : 0);
231
 
232
            $data->reviewoverallfeedback =
233
 
234
                    ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_OVERALLFEEDBACK ?
235
                            display_options::IMMEDIATELY_AFTER : 0) |
236
                    ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_OVERALLFEEDBACK ?
237
                            display_options::LATER_WHILE_OPEN : 0) |
238
                    ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_OVERALLFEEDBACK ?
239
                            display_options::AFTER_CLOSE : 0);
240
        }
241
 
242
        // New setting in 4.3 needs to be set if not in the backup.
243
        if (!isset($data->reviewmaxmarks)) {
244
            $data->reviewmaxmarks =
245
                    display_options::DURING |
246
                    display_options::IMMEDIATELY_AFTER |
247
                    display_options::LATER_WHILE_OPEN |
248
                    display_options::AFTER_CLOSE;
249
        }
250
 
251
        // The old popup column from from <= 2.1 need to be mapped to
252
        // the new browsersecurity. See MDL-29627.
253
        if (!isset($data->browsersecurity)) {
254
            if (empty($data->popup)) {
255
                $data->browsersecurity = '-';
256
            } else if ($data->popup == 1) {
257
                $data->browsersecurity = 'securewindow';
258
            } else if ($data->popup == 2) {
259
                // Since 3.9 quizaccess_safebrowser replaced with a new quizaccess_seb.
260
                $data->browsersecurity = '-';
261
                $addsebrule = true;
262
            } else {
263
                $data->preferredbehaviour = '-';
264
            }
265
            unset($data->popup);
266
        } else if ($data->browsersecurity == 'safebrowser') {
267
            // Since 3.9 quizaccess_safebrowser replaced with a new quizaccess_seb.
268
            $data->browsersecurity = '-';
269
            $addsebrule = true;
270
        }
271
 
272
        if (!isset($data->overduehandling)) {
273
            $data->overduehandling = get_config('quiz', 'overduehandling');
274
        }
275
 
276
        // Old shufflequestions setting is now stored in quiz sections,
277
        // so save it here if necessary so it is available when we need it.
278
        $this->legacyshufflequestionsoption = !empty($data->shufflequestions);
279
 
280
        // Insert the quiz record.
281
        $newitemid = $DB->insert_record('quiz', $data);
282
        // Immediately after inserting "activity" record, call this.
283
        $this->apply_activity_instance($newitemid);
284
 
285
        // Process Safe Exam Browser settings for backups taken in Moodle < 3.9.
286
        if (!empty($addsebrule)) {
287
            $sebsettings = new stdClass();
288
 
289
            $sebsettings->quizid = $newitemid;
290
            $sebsettings->cmid = $this->task->get_moduleid();
291
            $sebsettings->templateid = 0;
292
            $sebsettings->requiresafeexambrowser = \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG;
293
            $sebsettings->showsebtaskbar = null;
294
            $sebsettings->showwificontrol = null;
295
            $sebsettings->showreloadbutton = null;
296
            $sebsettings->showtime = null;
297
            $sebsettings->showkeyboardlayout = null;
298
            $sebsettings->allowuserquitseb = null;
299
            $sebsettings->quitpassword = null;
300
            $sebsettings->linkquitseb = null;
301
            $sebsettings->userconfirmquit = null;
302
            $sebsettings->enableaudiocontrol = null;
303
            $sebsettings->muteonstartup = null;
1441 ariadna 304
            $sebsettings->allowcapturecamera = null;
305
            $sebsettings->allowcapturemicrophone = null;
1 efrain 306
            $sebsettings->allowspellchecking = null;
307
            $sebsettings->allowreloadinexam = null;
308
            $sebsettings->activateurlfiltering = null;
309
            $sebsettings->filterembeddedcontent = null;
310
            $sebsettings->expressionsallowed = null;
311
            $sebsettings->regexallowed = null;
312
            $sebsettings->expressionsblocked = null;
313
            $sebsettings->regexblocked = null;
314
            $sebsettings->allowedbrowserexamkeys = null;
315
            $sebsettings->showsebdownloadlink = 1;
316
            $sebsettings->usermodified = $USER->id;
317
            $sebsettings->timecreated = time();
318
            $sebsettings->timemodified = time();
319
 
320
            $DB->insert_record('quizaccess_seb_quizsettings', $sebsettings);
321
        }
322
 
323
        // If we are dealing with a backup from < 4.0 then we need to move completionpass to core.
324
        if (!empty($data->completionpass)) {
325
            $params = ['id' => $this->task->get_moduleid()];
326
            $DB->set_field('course_modules', 'completionpassgrade', $data->completionpass, $params);
327
        }
328
    }
329
 
330
    /**
331
     * Process a quiz grade items.
332
     *
333
     * @param stdClass|array $data
334
     */
335
    protected function process_quiz_grade_item($data): void {
336
        global $DB;
337
 
338
        $data = (object) $data;
339
        $data->quizid = $this->get_new_parentid('quiz');
340
        $oldid = $data->id;
341
        $newitemid = $DB->insert_record('quiz_grade_items', $data);
342
        $this->set_mapping('quiz_grade_item', $oldid, $newitemid, true);
343
    }
344
 
345
    /**
346
     * Process the data for pre 4.0 quiz data where the question_references and question_set_references table introduced.
347
     *
348
     * @param stdClass|array $data
349
     */
350
    protected function process_quiz_question_legacy_instance($data) {
351
        global $DB;
352
 
353
        $questionid = $this->get_mappingid('question', $data->questionid);
354
        $sql = 'SELECT qbe.id as questionbankentryid,
355
                       qc.contextid as questioncontextid,
356
                       qc.id as category,
357
                       qv.version,
358
                       q.qtype,
359
                       q.id as questionid
360
                  FROM {question} q
361
                  JOIN {question_versions} qv ON qv.questionid = q.id
362
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
363
                  JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
364
                 WHERE q.id = ?';
365
        $question = $DB->get_record_sql($sql, [$questionid]);
366
        $module = $DB->get_record('quiz', ['id' => $data->quizid]);
367
 
368
        if ($question->qtype === 'random') {
369
            // Set reference data.
370
            $questionsetreference = new stdClass();
371
            $questionsetreference->usingcontextid = context_module::instance(get_coursemodule_from_instance(
372
                "quiz", $module->id, $module->course)->id)->id;
373
            $questionsetreference->component = 'mod_quiz';
374
            $questionsetreference->questionarea = 'slot';
375
            $questionsetreference->itemid = $data->id;
376
            // If, in the orginal quiz that was backed up, this random question was pointing to a
377
            // category in the quiz question bank, then (for reasons explained in {@see restore_move_module_questions_categories})
378
            // right now, $question->questioncontextid will incorrectly point to the course contextid.
379
            // This will get fixed up later in restore_move_module_questions_categories
380
            // as part of moving the question categories to the right place.
381
            $questionsetreference->questionscontextid = $question->questioncontextid;
382
            $filtercondition = new stdClass();
383
            $filtercondition->questioncategoryid = $question->category;
384
            $filtercondition->includingsubcategories = $data->includingsubcategories ?? false;
385
            $questionsetreference->filtercondition = json_encode($filtercondition);
386
            $DB->insert_record('question_set_references', $questionsetreference);
387
            $this->oldquestionids[$question->questionid] = 1;
388
        } else {
389
            // Reference data.
390
            $questionreference = new \stdClass();
391
            $questionreference->usingcontextid = context_module::instance(get_coursemodule_from_instance(
392
                "quiz", $module->id, $module->course)->id)->id;
393
            $questionreference->component = 'mod_quiz';
394
            $questionreference->questionarea = 'slot';
395
            $questionreference->itemid = $data->id;
396
            $questionreference->questionbankentryid = $question->questionbankentryid;
397
            $questionreference->version = null; // Default to Always latest.
398
            $DB->insert_record('question_references', $questionreference);
399
        }
400
    }
401
 
402
    /**
403
     * Process quiz slots.
404
     *
405
     * @param stdClass|array $data
406
     */
407
    protected function process_quiz_question_instance($data) {
408
        global $DB;
409
 
410
        $data = (object)$data;
411
        $oldid = $data->id;
412
 
413
        // Backwards compatibility for old field names (MDL-43670).
414
        if (!isset($data->questionid) && isset($data->question)) {
415
            $data->questionid = $data->question;
416
        }
417
        if (!isset($data->maxmark) && isset($data->grade)) {
418
            $data->maxmark = $data->grade;
419
        }
420
 
421
        if (!property_exists($data, 'slot')) {
422
            $page = 1;
423
            $slot = 1;
424
            foreach (explode(',', $this->oldquizlayout) as $item) {
425
                if ($item == 0) {
426
                    $page += 1;
427
                    continue;
428
                }
429
                if (isset($data->questionid) && $item == $data->questionid) {
430
                    $data->slot = $slot;
431
                    $data->page = $page;
432
                    break;
433
                }
434
                $slot += 1;
435
            }
436
        }
437
 
438
        if (!property_exists($data, 'slot')) {
439
            // There was a question_instance in the backup file for a question
440
            // that was not actually in the quiz. Drop it.
441
            $this->log('question ' . $data->questionid . ' was associated with quiz ' .
442
                    $this->get_new_parentid('quiz') . ' but not actually used. ' .
443
                    'The instance has been ignored.', backup::LOG_INFO);
444
            return;
445
        }
446
 
447
        if (isset($data->quizgradeitemid)) {
448
            $data->quizgradeitemid = $this->get_mappingid('quiz_grade_item', $data->quizgradeitemid);
449
        }
450
 
451
        $data->quizid = $this->get_new_parentid('quiz');
452
 
453
        $newitemid = $DB->insert_record('quiz_slots', $data);
454
        // Add mapping, restore of slot tags (for random questions) need it.
455
        $this->set_mapping('quiz_question_instance', $oldid, $newitemid);
456
 
457
        if ($this->task->get_old_moduleversion() < 2022020300) {
458
            $data->id = $newitemid;
459
            $this->process_quiz_question_legacy_instance($data);
460
        }
461
    }
462
 
463
    /**
464
     * Process a quiz_slot_tags to restore the tags to the new structure.
465
     *
466
     * @param stdClass|array $data The quiz_slot_tags data
467
     */
468
    protected function process_quiz_slot_tags($data) {
469
        global $DB;
470
 
471
        $data = (object) $data;
472
        $slotid = $this->get_new_parentid('quiz_question_instance');
473
 
474
        if ($this->task->is_samesite() && $tag = core_tag_tag::get($data->tagid, 'id, name')) {
475
            $data->tagname = $tag->name;
476
        } else if ($tag = core_tag_tag::get_by_name(0, $data->tagname, 'id, name')) {
477
            $data->tagid = $tag->id;
478
        } else {
479
            $data->tagid = null;
480
            $data->tagname = $tag->name;
481
        }
482
 
483
        $tagstring = "{$data->tagid},{$data->tagname}";
484
        $setreferencedata = $DB->get_record('question_set_references',
485
            ['itemid' => $slotid, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
486
        $filtercondition = json_decode($setreferencedata->filtercondition);
487
        $filtercondition->tags[] = $tagstring;
488
        $setreferencedata->filtercondition = json_encode($filtercondition);
489
        $DB->update_record('question_set_references', $setreferencedata);
490
    }
491
 
492
    protected function process_quiz_section($data) {
493
        global $DB;
494
 
495
        $data = (object) $data;
496
        $data->quizid = $this->get_new_parentid('quiz');
497
        $oldid = $data->id;
498
        $newitemid = $DB->insert_record('quiz_sections', $data);
499
        $this->sectioncreated = true;
500
        $this->set_mapping('quiz_section', $oldid, $newitemid, true);
501
    }
502
 
503
    protected function process_quiz_feedback($data) {
504
        global $DB;
505
 
506
        $data = (object)$data;
507
        $oldid = $data->id;
508
 
509
        $data->quizid = $this->get_new_parentid('quiz');
510
 
511
        $newitemid = $DB->insert_record('quiz_feedback', $data);
512
        $this->set_mapping('quiz_feedback', $oldid, $newitemid, true); // Has related files.
513
    }
514
 
515
    protected function process_quiz_override($data) {
516
        global $DB;
517
 
518
        $data = (object)$data;
519
        $oldid = $data->id;
520
 
521
        // Based on userinfo, we'll restore user overides or no.
522
        $userinfo = $this->get_setting_value('userinfo');
523
 
524
        // Skip user overrides if we are not restoring userinfo.
525
        if (!$userinfo && !is_null($data->userid)) {
526
            return;
527
        }
528
 
529
        $data->quiz = $this->get_new_parentid('quiz');
530
 
531
        if ($data->userid !== null) {
532
            $data->userid = $this->get_mappingid('user', $data->userid);
533
        }
534
 
535
        if ($data->groupid !== null) {
536
            $data->groupid = $this->get_mappingid('group', $data->groupid);
537
        }
538
 
539
        // Skip if there is no user and no group data.
540
        if (empty($data->userid) && empty($data->groupid)) {
541
            return;
542
        }
543
 
544
        $data->timeopen = $this->apply_date_offset($data->timeopen);
545
        $data->timeclose = $this->apply_date_offset($data->timeclose);
546
 
547
        $newitemid = $DB->insert_record('quiz_overrides', $data);
548
 
549
        // Add mapping, restore of logs needs it.
550
        $this->set_mapping('quiz_override', $oldid, $newitemid);
551
    }
552
 
553
    protected function process_quiz_grade($data) {
554
        global $DB;
555
 
556
        $data = (object)$data;
557
        $oldid = $data->id;
558
 
559
        $data->quiz = $this->get_new_parentid('quiz');
560
 
561
        $data->userid = $this->get_mappingid('user', $data->userid);
562
        $data->grade = $data->gradeval;
563
 
564
        $DB->insert_record('quiz_grades', $data);
565
    }
566
 
567
    protected function process_quiz_attempt($data) {
568
        $data = (object)$data;
569
 
570
        $data->quiz = $this->get_new_parentid('quiz');
571
        $data->attempt = $data->attemptnum;
572
 
573
        // Get user mapping, return early if no mapping found for the quiz attempt.
574
        $olduserid = $data->userid;
575
        $data->userid = $this->get_mappingid('user', $olduserid, 0);
576
        if ($data->userid === 0) {
577
            $this->log('Mapped user ID not found for user ' . $olduserid . ', quiz ' . $this->get_new_parentid('quiz') .
578
                ', attempt ' . $data->attempt . '. Skipping quiz attempt', backup::LOG_INFO);
579
 
580
            $this->currentquizattempt = null;
581
            return;
582
        }
583
 
584
        if (!empty($data->timecheckstate)) {
585
            $data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
586
        } else {
587
            $data->timecheckstate = 0;
588
        }
589
 
590
        if (!isset($data->gradednotificationsenttime)) {
591
            // For attempts restored from old Moodle sites before this field
592
            // existed, we never want to send emails.
593
            $data->gradednotificationsenttime = $data->timefinish;
594
        }
595
 
596
        // Deals with up-grading pre-2.3 back-ups to 2.3+.
597
        if (!isset($data->state)) {
598
            if ($data->timefinish > 0) {
599
                $data->state = 'finished';
600
            } else {
601
                $data->state = 'inprogress';
602
            }
603
        }
604
 
605
        // The data is actually inserted into the database later in inform_new_usage_id.
606
        $this->currentquizattempt = clone($data);
607
    }
608
 
609
    protected function process_quiz_attempt_legacy($data) {
610
        global $DB;
611
 
612
        $this->process_quiz_attempt($data);
613
 
614
        $quiz = $DB->get_record('quiz', ['id' => $this->get_new_parentid('quiz')]);
615
        $quiz->oldquestions = $this->oldquizlayout;
616
        $this->process_legacy_quiz_attempt_data($data, $quiz);
617
    }
618
 
619
    protected function inform_new_usage_id($newusageid) {
620
        global $DB;
621
 
622
        $data = $this->currentquizattempt;
623
        if ($data === null) {
624
            return;
625
        }
626
 
627
        $oldid = $data->id;
628
        $data->uniqueid = $newusageid;
629
 
630
        $newitemid = $DB->insert_record('quiz_attempts', $data);
631
 
632
        // Save quiz_attempt->id mapping, because logs use it.
633
        $this->set_mapping('quiz_attempt', $oldid, $newitemid, false);
634
    }
635
 
636
    protected function after_execute() {
637
        global $DB;
638
 
639
        parent::after_execute();
640
        // Add quiz related files, no need to match by itemname (just internally handled context).
641
        $this->add_related_files('mod_quiz', 'intro', null);
642
        // Add feedback related files, matching by itemname = 'quiz_feedback'.
643
        $this->add_related_files('mod_quiz', 'feedback', 'quiz_feedback');
644
 
645
        if (!$this->sectioncreated) {
646
            $DB->insert_record('quiz_sections', [
647
                    'quizid' => $this->get_new_parentid('quiz'),
648
                    'firstslot' => 1, 'heading' => '',
649
                    'shufflequestions' => $this->legacyshufflequestionsoption]);
650
        }
651
    }
652
 
653
    protected function after_restore() {
654
        parent::after_restore();
655
        // Delete old random questions that have been converted to set references.
656
        foreach (array_keys($this->oldquestionids) as $oldquestionid) {
657
            question_delete_question($oldquestionid);
658
        }
659
    }
660
}