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