Proyectos de Subversion Moodle

Rev

| 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;
304
            $sebsettings->allowspellchecking = null;
305
            $sebsettings->allowreloadinexam = null;
306
            $sebsettings->activateurlfiltering = null;
307
            $sebsettings->filterembeddedcontent = null;
308
            $sebsettings->expressionsallowed = null;
309
            $sebsettings->regexallowed = null;
310
            $sebsettings->expressionsblocked = null;
311
            $sebsettings->regexblocked = null;
312
            $sebsettings->allowedbrowserexamkeys = null;
313
            $sebsettings->showsebdownloadlink = 1;
314
            $sebsettings->usermodified = $USER->id;
315
            $sebsettings->timecreated = time();
316
            $sebsettings->timemodified = time();
317
 
318
            $DB->insert_record('quizaccess_seb_quizsettings', $sebsettings);
319
        }
320
 
321
        // If we are dealing with a backup from < 4.0 then we need to move completionpass to core.
322
        if (!empty($data->completionpass)) {
323
            $params = ['id' => $this->task->get_moduleid()];
324
            $DB->set_field('course_modules', 'completionpassgrade', $data->completionpass, $params);
325
        }
326
    }
327
 
328
    /**
329
     * Process a quiz grade items.
330
     *
331
     * @param stdClass|array $data
332
     */
333
    protected function process_quiz_grade_item($data): void {
334
        global $DB;
335
 
336
        $data = (object) $data;
337
        $data->quizid = $this->get_new_parentid('quiz');
338
        $oldid = $data->id;
339
        $newitemid = $DB->insert_record('quiz_grade_items', $data);
340
        $this->set_mapping('quiz_grade_item', $oldid, $newitemid, true);
341
    }
342
 
343
    /**
344
     * Process the data for pre 4.0 quiz data where the question_references and question_set_references table introduced.
345
     *
346
     * @param stdClass|array $data
347
     */
348
    protected function process_quiz_question_legacy_instance($data) {
349
        global $DB;
350
 
351
        $questionid = $this->get_mappingid('question', $data->questionid);
352
        $sql = 'SELECT qbe.id as questionbankentryid,
353
                       qc.contextid as questioncontextid,
354
                       qc.id as category,
355
                       qv.version,
356
                       q.qtype,
357
                       q.id as questionid
358
                  FROM {question} q
359
                  JOIN {question_versions} qv ON qv.questionid = q.id
360
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
361
                  JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
362
                 WHERE q.id = ?';
363
        $question = $DB->get_record_sql($sql, [$questionid]);
364
        $module = $DB->get_record('quiz', ['id' => $data->quizid]);
365
 
366
        if ($question->qtype === 'random') {
367
            // Set reference data.
368
            $questionsetreference = new stdClass();
369
            $questionsetreference->usingcontextid = context_module::instance(get_coursemodule_from_instance(
370
                "quiz", $module->id, $module->course)->id)->id;
371
            $questionsetreference->component = 'mod_quiz';
372
            $questionsetreference->questionarea = 'slot';
373
            $questionsetreference->itemid = $data->id;
374
            // If, in the orginal quiz that was backed up, this random question was pointing to a
375
            // category in the quiz question bank, then (for reasons explained in {@see restore_move_module_questions_categories})
376
            // right now, $question->questioncontextid will incorrectly point to the course contextid.
377
            // This will get fixed up later in restore_move_module_questions_categories
378
            // as part of moving the question categories to the right place.
379
            $questionsetreference->questionscontextid = $question->questioncontextid;
380
            $filtercondition = new stdClass();
381
            $filtercondition->questioncategoryid = $question->category;
382
            $filtercondition->includingsubcategories = $data->includingsubcategories ?? false;
383
            $questionsetreference->filtercondition = json_encode($filtercondition);
384
            $DB->insert_record('question_set_references', $questionsetreference);
385
            $this->oldquestionids[$question->questionid] = 1;
386
        } else {
387
            // Reference data.
388
            $questionreference = new \stdClass();
389
            $questionreference->usingcontextid = context_module::instance(get_coursemodule_from_instance(
390
                "quiz", $module->id, $module->course)->id)->id;
391
            $questionreference->component = 'mod_quiz';
392
            $questionreference->questionarea = 'slot';
393
            $questionreference->itemid = $data->id;
394
            $questionreference->questionbankentryid = $question->questionbankentryid;
395
            $questionreference->version = null; // Default to Always latest.
396
            $DB->insert_record('question_references', $questionreference);
397
        }
398
    }
399
 
400
    /**
401
     * Process quiz slots.
402
     *
403
     * @param stdClass|array $data
404
     */
405
    protected function process_quiz_question_instance($data) {
406
        global $DB;
407
 
408
        $data = (object)$data;
409
        $oldid = $data->id;
410
 
411
        // Backwards compatibility for old field names (MDL-43670).
412
        if (!isset($data->questionid) && isset($data->question)) {
413
            $data->questionid = $data->question;
414
        }
415
        if (!isset($data->maxmark) && isset($data->grade)) {
416
            $data->maxmark = $data->grade;
417
        }
418
 
419
        if (!property_exists($data, 'slot')) {
420
            $page = 1;
421
            $slot = 1;
422
            foreach (explode(',', $this->oldquizlayout) as $item) {
423
                if ($item == 0) {
424
                    $page += 1;
425
                    continue;
426
                }
427
                if (isset($data->questionid) && $item == $data->questionid) {
428
                    $data->slot = $slot;
429
                    $data->page = $page;
430
                    break;
431
                }
432
                $slot += 1;
433
            }
434
        }
435
 
436
        if (!property_exists($data, 'slot')) {
437
            // There was a question_instance in the backup file for a question
438
            // that was not actually in the quiz. Drop it.
439
            $this->log('question ' . $data->questionid . ' was associated with quiz ' .
440
                    $this->get_new_parentid('quiz') . ' but not actually used. ' .
441
                    'The instance has been ignored.', backup::LOG_INFO);
442
            return;
443
        }
444
 
445
        if (isset($data->quizgradeitemid)) {
446
            $data->quizgradeitemid = $this->get_mappingid('quiz_grade_item', $data->quizgradeitemid);
447
        }
448
 
449
        $data->quizid = $this->get_new_parentid('quiz');
450
 
451
        $newitemid = $DB->insert_record('quiz_slots', $data);
452
        // Add mapping, restore of slot tags (for random questions) need it.
453
        $this->set_mapping('quiz_question_instance', $oldid, $newitemid);
454
 
455
        if ($this->task->get_old_moduleversion() < 2022020300) {
456
            $data->id = $newitemid;
457
            $this->process_quiz_question_legacy_instance($data);
458
        }
459
    }
460
 
461
    /**
462
     * Process a quiz_slot_tags to restore the tags to the new structure.
463
     *
464
     * @param stdClass|array $data The quiz_slot_tags data
465
     */
466
    protected function process_quiz_slot_tags($data) {
467
        global $DB;
468
 
469
        $data = (object) $data;
470
        $slotid = $this->get_new_parentid('quiz_question_instance');
471
 
472
        if ($this->task->is_samesite() && $tag = core_tag_tag::get($data->tagid, 'id, name')) {
473
            $data->tagname = $tag->name;
474
        } else if ($tag = core_tag_tag::get_by_name(0, $data->tagname, 'id, name')) {
475
            $data->tagid = $tag->id;
476
        } else {
477
            $data->tagid = null;
478
            $data->tagname = $tag->name;
479
        }
480
 
481
        $tagstring = "{$data->tagid},{$data->tagname}";
482
        $setreferencedata = $DB->get_record('question_set_references',
483
            ['itemid' => $slotid, 'component' => 'mod_quiz', 'questionarea' => 'slot']);
484
        $filtercondition = json_decode($setreferencedata->filtercondition);
485
        $filtercondition->tags[] = $tagstring;
486
        $setreferencedata->filtercondition = json_encode($filtercondition);
487
        $DB->update_record('question_set_references', $setreferencedata);
488
    }
489
 
490
    protected function process_quiz_section($data) {
491
        global $DB;
492
 
493
        $data = (object) $data;
494
        $data->quizid = $this->get_new_parentid('quiz');
495
        $oldid = $data->id;
496
        $newitemid = $DB->insert_record('quiz_sections', $data);
497
        $this->sectioncreated = true;
498
        $this->set_mapping('quiz_section', $oldid, $newitemid, true);
499
    }
500
 
501
    protected function process_quiz_feedback($data) {
502
        global $DB;
503
 
504
        $data = (object)$data;
505
        $oldid = $data->id;
506
 
507
        $data->quizid = $this->get_new_parentid('quiz');
508
 
509
        $newitemid = $DB->insert_record('quiz_feedback', $data);
510
        $this->set_mapping('quiz_feedback', $oldid, $newitemid, true); // Has related files.
511
    }
512
 
513
    protected function process_quiz_override($data) {
514
        global $DB;
515
 
516
        $data = (object)$data;
517
        $oldid = $data->id;
518
 
519
        // Based on userinfo, we'll restore user overides or no.
520
        $userinfo = $this->get_setting_value('userinfo');
521
 
522
        // Skip user overrides if we are not restoring userinfo.
523
        if (!$userinfo && !is_null($data->userid)) {
524
            return;
525
        }
526
 
527
        $data->quiz = $this->get_new_parentid('quiz');
528
 
529
        if ($data->userid !== null) {
530
            $data->userid = $this->get_mappingid('user', $data->userid);
531
        }
532
 
533
        if ($data->groupid !== null) {
534
            $data->groupid = $this->get_mappingid('group', $data->groupid);
535
        }
536
 
537
        // Skip if there is no user and no group data.
538
        if (empty($data->userid) && empty($data->groupid)) {
539
            return;
540
        }
541
 
542
        $data->timeopen = $this->apply_date_offset($data->timeopen);
543
        $data->timeclose = $this->apply_date_offset($data->timeclose);
544
 
545
        $newitemid = $DB->insert_record('quiz_overrides', $data);
546
 
547
        // Add mapping, restore of logs needs it.
548
        $this->set_mapping('quiz_override', $oldid, $newitemid);
549
    }
550
 
551
    protected function process_quiz_grade($data) {
552
        global $DB;
553
 
554
        $data = (object)$data;
555
        $oldid = $data->id;
556
 
557
        $data->quiz = $this->get_new_parentid('quiz');
558
 
559
        $data->userid = $this->get_mappingid('user', $data->userid);
560
        $data->grade = $data->gradeval;
561
 
562
        $DB->insert_record('quiz_grades', $data);
563
    }
564
 
565
    protected function process_quiz_attempt($data) {
566
        $data = (object)$data;
567
 
568
        $data->quiz = $this->get_new_parentid('quiz');
569
        $data->attempt = $data->attemptnum;
570
 
571
        // Get user mapping, return early if no mapping found for the quiz attempt.
572
        $olduserid = $data->userid;
573
        $data->userid = $this->get_mappingid('user', $olduserid, 0);
574
        if ($data->userid === 0) {
575
            $this->log('Mapped user ID not found for user ' . $olduserid . ', quiz ' . $this->get_new_parentid('quiz') .
576
                ', attempt ' . $data->attempt . '. Skipping quiz attempt', backup::LOG_INFO);
577
 
578
            $this->currentquizattempt = null;
579
            return;
580
        }
581
 
582
        if (!empty($data->timecheckstate)) {
583
            $data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
584
        } else {
585
            $data->timecheckstate = 0;
586
        }
587
 
588
        if (!isset($data->gradednotificationsenttime)) {
589
            // For attempts restored from old Moodle sites before this field
590
            // existed, we never want to send emails.
591
            $data->gradednotificationsenttime = $data->timefinish;
592
        }
593
 
594
        // Deals with up-grading pre-2.3 back-ups to 2.3+.
595
        if (!isset($data->state)) {
596
            if ($data->timefinish > 0) {
597
                $data->state = 'finished';
598
            } else {
599
                $data->state = 'inprogress';
600
            }
601
        }
602
 
603
        // The data is actually inserted into the database later in inform_new_usage_id.
604
        $this->currentquizattempt = clone($data);
605
    }
606
 
607
    protected function process_quiz_attempt_legacy($data) {
608
        global $DB;
609
 
610
        $this->process_quiz_attempt($data);
611
 
612
        $quiz = $DB->get_record('quiz', ['id' => $this->get_new_parentid('quiz')]);
613
        $quiz->oldquestions = $this->oldquizlayout;
614
        $this->process_legacy_quiz_attempt_data($data, $quiz);
615
    }
616
 
617
    protected function inform_new_usage_id($newusageid) {
618
        global $DB;
619
 
620
        $data = $this->currentquizattempt;
621
        if ($data === null) {
622
            return;
623
        }
624
 
625
        $oldid = $data->id;
626
        $data->uniqueid = $newusageid;
627
 
628
        $newitemid = $DB->insert_record('quiz_attempts', $data);
629
 
630
        // Save quiz_attempt->id mapping, because logs use it.
631
        $this->set_mapping('quiz_attempt', $oldid, $newitemid, false);
632
    }
633
 
634
    protected function after_execute() {
635
        global $DB;
636
 
637
        parent::after_execute();
638
        // Add quiz related files, no need to match by itemname (just internally handled context).
639
        $this->add_related_files('mod_quiz', 'intro', null);
640
        // Add feedback related files, matching by itemname = 'quiz_feedback'.
641
        $this->add_related_files('mod_quiz', 'feedback', 'quiz_feedback');
642
 
643
        if (!$this->sectioncreated) {
644
            $DB->insert_record('quiz_sections', [
645
                    'quizid' => $this->get_new_parentid('quiz'),
646
                    'firstslot' => 1, 'heading' => '',
647
                    'shufflequestions' => $this->legacyshufflequestionsoption]);
648
        }
649
    }
650
 
651
    protected function after_restore() {
652
        parent::after_restore();
653
        // Delete old random questions that have been converted to set references.
654
        foreach (array_keys($this->oldquestionids) as $oldquestionid) {
655
            question_delete_question($oldquestionid);
656
        }
657
    }
658
}