Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 /** 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 */ 23 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 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 { 35 36 /** 37 * @var bool tracks whether the quiz contains at least one section. Before 38 * Moodle 2.9 quiz sections did not exist, so if the file being restored 39 * did not contain any, we need to create one in {@link after_execute()}. 40 */ 41 protected $sectioncreated = false; 42 43 /** @var stdClass|null $currentquizattempt Track the current quiz attempt being restored. */ 44 protected $currentquizattempt = null; 45 46 /** 47 * @var bool when restoring old quizzes (2.8 or before) this records the 48 * shufflequestionsoption quiz option which has moved to the quiz_sections table. 49 */ 50 protected $legacyshufflequestionsoption = false; 51 52 protected function define_structure() { 53 54 $paths = array(); 55 $userinfo = $this->get_setting_value('userinfo'); 56 57 $quiz = new restore_path_element('quiz', '/activity/quiz'); 58 $paths[] = $quiz; 59 60 // A chance for access subplugings to set up their quiz data. 61 $this->add_subplugin_structure('quizaccess', $quiz); 62 63 $paths[] = new restore_path_element('quiz_question_instance', 64 '/activity/quiz/question_instances/question_instance'); 65 $paths[] = new restore_path_element('quiz_slot_tags', 66 '/activity/quiz/question_instances/question_instance/tags/tag'); 67 $paths[] = new restore_path_element('quiz_section', '/activity/quiz/sections/section'); 68 $paths[] = new restore_path_element('quiz_feedback', '/activity/quiz/feedbacks/feedback'); 69 $paths[] = new restore_path_element('quiz_override', '/activity/quiz/overrides/override'); 70 71 if ($userinfo) { 72 $paths[] = new restore_path_element('quiz_grade', '/activity/quiz/grades/grade'); 73 74 if ($this->task->get_old_moduleversion() > 2011010100) { 75 // Restoring from a version 2.1 dev or later. 76 // Process the new-style attempt data. 77 $quizattempt = new restore_path_element('quiz_attempt', 78 '/activity/quiz/attempts/attempt'); 79 $paths[] = $quizattempt; 80 81 // Add states and sessions. 82 $this->add_question_usages($quizattempt, $paths); 83 84 // A chance for access subplugings to set up their attempt data. 85 $this->add_subplugin_structure('quizaccess', $quizattempt); 86 87 } else { 88 // Restoring from a version 2.0.x+ or earlier. 89 // Upgrade the legacy attempt data. 90 $quizattempt = new restore_path_element('quiz_attempt_legacy', 91 '/activity/quiz/attempts/attempt', 92 true); 93 $paths[] = $quizattempt; 94 $this->add_legacy_question_attempt_data($quizattempt, $paths); 95 } 96 } 97 98 // Return the paths wrapped into standard activity structure. 99 return $this->prepare_activity_structure($paths); 100 } 101 102 protected function process_quiz($data) { 103 global $CFG, $DB, $USER; 104 105 $data = (object)$data; 106 $oldid = $data->id; 107 $data->course = $this->get_courseid(); 108 109 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. 110 // See MDL-9367. 111 112 $data->timeopen = $this->apply_date_offset($data->timeopen); 113 $data->timeclose = $this->apply_date_offset($data->timeclose); 114 115 if (property_exists($data, 'questions')) { 116 // Needed by {@link process_quiz_attempt_legacy}, in which case it will be present. 117 $this->oldquizlayout = $data->questions; 118 } 119 120 // The setting quiz->attempts can come both in data->attempts and 121 // data->attempts_number, handle both. MDL-26229. 122 if (isset($data->attempts_number)) { 123 $data->attempts = $data->attempts_number; 124 unset($data->attempts_number); 125 } 126 127 // The old optionflags and penaltyscheme from 2.0 need to be mapped to 128 // the new preferredbehaviour. See MDL-20636. 129 if (!isset($data->preferredbehaviour)) { 130 if (empty($data->optionflags)) { 131 $data->preferredbehaviour = 'deferredfeedback'; 132 } else if (empty($data->penaltyscheme)) { 133 $data->preferredbehaviour = 'adaptivenopenalty'; 134 } else { 135 $data->preferredbehaviour = 'adaptive'; 136 } 137 unset($data->optionflags); 138 unset($data->penaltyscheme); 139 } 140 141 // The old review column from 2.0 need to be split into the seven new 142 // review columns. See MDL-20636. 143 if (isset($data->review)) { 144 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 145 146 if (!defined('QUIZ_OLD_IMMEDIATELY')) { 147 define('QUIZ_OLD_IMMEDIATELY', 0x3c003f); 148 define('QUIZ_OLD_OPEN', 0x3c00fc0); 149 define('QUIZ_OLD_CLOSED', 0x3c03f000); 150 151 define('QUIZ_OLD_RESPONSES', 1*0x1041); 152 define('QUIZ_OLD_SCORES', 2*0x1041); 153 define('QUIZ_OLD_FEEDBACK', 4*0x1041); 154 define('QUIZ_OLD_ANSWERS', 8*0x1041); 155 define('QUIZ_OLD_SOLUTIONS', 16*0x1041); 156 define('QUIZ_OLD_GENERALFEEDBACK', 32*0x1041); 157 define('QUIZ_OLD_OVERALLFEEDBACK', 1*0x4440000); 158 } 159 160 $oldreview = $data->review; 161 162 $data->reviewattempt = 163 mod_quiz_display_options::DURING | 164 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_RESPONSES ? 165 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 166 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_RESPONSES ? 167 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 168 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_RESPONSES ? 169 mod_quiz_display_options::AFTER_CLOSE : 0); 170 171 $data->reviewcorrectness = 172 mod_quiz_display_options::DURING | 173 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ? 174 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 175 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ? 176 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 177 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ? 178 mod_quiz_display_options::AFTER_CLOSE : 0); 179 180 $data->reviewmarks = 181 mod_quiz_display_options::DURING | 182 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_SCORES ? 183 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 184 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_SCORES ? 185 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 186 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_SCORES ? 187 mod_quiz_display_options::AFTER_CLOSE : 0); 188 189 $data->reviewspecificfeedback = 190 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ? 191 mod_quiz_display_options::DURING : 0) | 192 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_FEEDBACK ? 193 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 194 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_FEEDBACK ? 195 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 196 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_FEEDBACK ? 197 mod_quiz_display_options::AFTER_CLOSE : 0); 198 199 $data->reviewgeneralfeedback = 200 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ? 201 mod_quiz_display_options::DURING : 0) | 202 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_GENERALFEEDBACK ? 203 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 204 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_GENERALFEEDBACK ? 205 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 206 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_GENERALFEEDBACK ? 207 mod_quiz_display_options::AFTER_CLOSE : 0); 208 209 $data->reviewrightanswer = 210 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ? 211 mod_quiz_display_options::DURING : 0) | 212 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_ANSWERS ? 213 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 214 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_ANSWERS ? 215 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 216 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_ANSWERS ? 217 mod_quiz_display_options::AFTER_CLOSE : 0); 218 219 $data->reviewoverallfeedback = 220 0 | 221 ($oldreview & QUIZ_OLD_IMMEDIATELY & QUIZ_OLD_OVERALLFEEDBACK ? 222 mod_quiz_display_options::IMMEDIATELY_AFTER : 0) | 223 ($oldreview & QUIZ_OLD_OPEN & QUIZ_OLD_OVERALLFEEDBACK ? 224 mod_quiz_display_options::LATER_WHILE_OPEN : 0) | 225 ($oldreview & QUIZ_OLD_CLOSED & QUIZ_OLD_OVERALLFEEDBACK ? 226 mod_quiz_display_options::AFTER_CLOSE : 0); 227 } 228 229 // The old popup column from from <= 2.1 need to be mapped to 230 // the new browsersecurity. See MDL-29627. 231 if (!isset($data->browsersecurity)) { 232 if (empty($data->popup)) { 233 $data->browsersecurity = '-'; 234 } else if ($data->popup == 1) { 235 $data->browsersecurity = 'securewindow'; 236 } else if ($data->popup == 2) { 237 // Since 3.9 quizaccess_safebrowser replaced with a new quizaccess_seb. 238 $data->browsersecurity = '-'; 239 $addsebrule = true; 240 } else { 241 $data->preferredbehaviour = '-'; 242 } 243 unset($data->popup); 244 } else if ($data->browsersecurity == 'safebrowser') { 245 // Since 3.9 quizaccess_safebrowser replaced with a new quizaccess_seb. 246 $data->browsersecurity = '-'; 247 $addsebrule = true; 248 } 249 250 if (!isset($data->overduehandling)) { 251 $data->overduehandling = get_config('quiz', 'overduehandling'); 252 } 253 254 // Old shufflequestions setting is now stored in quiz sections, 255 // so save it here if necessary so it is available when we need it. 256 $this->legacyshufflequestionsoption = !empty($data->shufflequestions); 257 258 // Insert the quiz record. 259 $newitemid = $DB->insert_record('quiz', $data); 260 // Immediately after inserting "activity" record, call this. 261 $this->apply_activity_instance($newitemid); 262 263 // Process Safe Exam Browser settings for backups taken in Moodle < 3.9. 264 if (!empty($addsebrule)) { 265 $sebsettings = new stdClass(); 266 267 $sebsettings->quizid = $newitemid; 268 $sebsettings->cmid = $this->task->get_moduleid(); 269 $sebsettings->templateid = 0; 270 $sebsettings->requiresafeexambrowser = \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG; 271 $sebsettings->showsebtaskbar = null; 272 $sebsettings->showwificontrol = null; 273 $sebsettings->showreloadbutton = null; 274 $sebsettings->showtime = null; 275 $sebsettings->showkeyboardlayout = null; 276 $sebsettings->allowuserquitseb = null; 277 $sebsettings->quitpassword = null; 278 $sebsettings->linkquitseb = null; 279 $sebsettings->userconfirmquit = null; 280 $sebsettings->enableaudiocontrol = null; 281 $sebsettings->muteonstartup = null; 282 $sebsettings->allowspellchecking = null; 283 $sebsettings->allowreloadinexam = null; 284 $sebsettings->activateurlfiltering = null; 285 $sebsettings->filterembeddedcontent = null; 286 $sebsettings->expressionsallowed = null; 287 $sebsettings->regexallowed = null; 288 $sebsettings->expressionsblocked = null; 289 $sebsettings->regexblocked = null; 290 $sebsettings->allowedbrowserexamkeys = null; 291 $sebsettings->showsebdownloadlink = 1; 292 $sebsettings->usermodified = $USER->id; 293 $sebsettings->timecreated = time(); 294 $sebsettings->timemodified = time(); 295 296 $DB->insert_record('quizaccess_seb_quizsettings', $sebsettings); 297 } 298 } 299 300 protected function process_quiz_question_instance($data) { 301 global $CFG, $DB; 302 303 $data = (object)$data; 304 $oldid = $data->id; 305 306 // Backwards compatibility for old field names (MDL-43670). 307 if (!isset($data->questionid) && isset($data->question)) { 308 $data->questionid = $data->question; 309 } 310 if (!isset($data->maxmark) && isset($data->grade)) { 311 $data->maxmark = $data->grade; 312 } 313 314 if (!property_exists($data, 'slot')) { 315 $page = 1; 316 $slot = 1; 317 foreach (explode(',', $this->oldquizlayout) as $item) { 318 if ($item == 0) { 319 $page += 1; 320 continue; 321 } 322 if ($item == $data->questionid) { 323 $data->slot = $slot; 324 $data->page = $page; 325 break; 326 } 327 $slot += 1; 328 } 329 } 330 331 if (!property_exists($data, 'slot')) { 332 // There was a question_instance in the backup file for a question 333 // that was not actually in the quiz. Drop it. 334 $this->log('question ' . $data->questionid . ' was associated with quiz ' . 335 $this->get_new_parentid('quiz') . ' but not actually used. ' . 336 'The instance has been ignored.', backup::LOG_INFO); 337 return; 338 } 339 340 $data->quizid = $this->get_new_parentid('quiz'); 341 $questionmapping = $this->get_mapping('question', $data->questionid); 342 $data->questionid = $questionmapping ? $questionmapping->newitemid : false; 343 344 if (isset($data->questioncategoryid)) { 345 $data->questioncategoryid = $this->get_mappingid('question_category', $data->questioncategoryid); 346 } else if ($questionmapping && $questionmapping->info->qtype == 'random') { 347 // Backward compatibility for backups created using Moodle 3.4 or earlier. 348 $data->questioncategoryid = $this->get_mappingid('question_category', $questionmapping->parentitemid); 349 $data->includingsubcategories = $questionmapping->info->questiontext ? 1 : 0; 350 } 351 352 $newitemid = $DB->insert_record('quiz_slots', $data); 353 // Add mapping, restore of slot tags (for random questions) need it. 354 $this->set_mapping('quiz_question_instance', $oldid, $newitemid); 355 } 356 357 /** 358 * Process a quiz_slot_tags restore 359 * 360 * @param stdClass|array $data The quiz_slot_tags data 361 */ 362 protected function process_quiz_slot_tags($data) { 363 global $DB; 364 365 $data = (object)$data; 366 367 $data->slotid = $this->get_new_parentid('quiz_question_instance'); 368 if ($this->task->is_samesite() && $tag = core_tag_tag::get($data->tagid, 'id, name')) { 369 $data->tagname = $tag->name; 370 } else if ($tag = core_tag_tag::get_by_name(0, $data->tagname, 'id, name')) { 371 $data->tagid = $tag->id; 372 } else { 373 $data->tagid = null; 374 $data->tagname = $tag->name; 375 } 376 377 $DB->insert_record('quiz_slot_tags', $data); 378 } 379 380 protected function process_quiz_section($data) { 381 global $DB; 382 383 $data = (object) $data; 384 $data->quizid = $this->get_new_parentid('quiz'); 385 $newitemid = $DB->insert_record('quiz_sections', $data); 386 $this->sectioncreated = true; 387 } 388 389 protected function process_quiz_feedback($data) { 390 global $DB; 391 392 $data = (object)$data; 393 $oldid = $data->id; 394 395 $data->quizid = $this->get_new_parentid('quiz'); 396 397 $newitemid = $DB->insert_record('quiz_feedback', $data); 398 $this->set_mapping('quiz_feedback', $oldid, $newitemid, true); // Has related files. 399 } 400 401 protected function process_quiz_override($data) { 402 global $DB; 403 404 $data = (object)$data; 405 $oldid = $data->id; 406 407 // Based on userinfo, we'll restore user overides or no. 408 $userinfo = $this->get_setting_value('userinfo'); 409 410 // Skip user overrides if we are not restoring userinfo. 411 if (!$userinfo && !is_null($data->userid)) { 412 return; 413 } 414 415 $data->quiz = $this->get_new_parentid('quiz'); 416 417 if ($data->userid !== null) { 418 $data->userid = $this->get_mappingid('user', $data->userid); 419 } 420 421 if ($data->groupid !== null) { 422 $data->groupid = $this->get_mappingid('group', $data->groupid); 423 } 424 425 // Skip if there is no user and no group data. 426 if (empty($data->userid) && empty($data->groupid)) { 427 return; 428 } 429 430 $data->timeopen = $this->apply_date_offset($data->timeopen); 431 $data->timeclose = $this->apply_date_offset($data->timeclose); 432 433 $newitemid = $DB->insert_record('quiz_overrides', $data); 434 435 // Add mapping, restore of logs needs it. 436 $this->set_mapping('quiz_override', $oldid, $newitemid); 437 } 438 439 protected function process_quiz_grade($data) { 440 global $DB; 441 442 $data = (object)$data; 443 $oldid = $data->id; 444 445 $data->quiz = $this->get_new_parentid('quiz'); 446 447 $data->userid = $this->get_mappingid('user', $data->userid); 448 $data->grade = $data->gradeval; 449 450 $DB->insert_record('quiz_grades', $data); 451 } 452 453 protected function process_quiz_attempt($data) { 454 $data = (object)$data; 455 456 $data->quiz = $this->get_new_parentid('quiz'); 457 $data->attempt = $data->attemptnum; 458 459 // Get user mapping, return early if no mapping found for the quiz attempt. 460 $olduserid = $data->userid; 461 $data->userid = $this->get_mappingid('user', $olduserid, 0); 462 if ($data->userid === 0) { 463 $this->log('Mapped user ID not found for user ' . $olduserid . ', quiz ' . $this->get_new_parentid('quiz') . 464 ', attempt ' . $data->attempt . '. Skipping quiz attempt', backup::LOG_INFO); 465 466 $this->currentquizattempt = null; 467 return; 468 } 469 470 if (!empty($data->timecheckstate)) { 471 $data->timecheckstate = $this->apply_date_offset($data->timecheckstate); 472 } else { 473 $data->timecheckstate = 0; 474 } 475 476 // Deals with up-grading pre-2.3 back-ups to 2.3+. 477 if (!isset($data->state)) { 478 if ($data->timefinish > 0) { 479 $data->state = 'finished'; 480 } else { 481 $data->state = 'inprogress'; 482 } 483 } 484 485 // The data is actually inserted into the database later in inform_new_usage_id. 486 $this->currentquizattempt = clone($data); 487 } 488 489 protected function process_quiz_attempt_legacy($data) { 490 global $DB; 491 492 $this->process_quiz_attempt($data); 493 494 $quiz = $DB->get_record('quiz', array('id' => $this->get_new_parentid('quiz'))); 495 $quiz->oldquestions = $this->oldquizlayout; 496 $this->process_legacy_quiz_attempt_data($data, $quiz); 497 } 498 499 protected function inform_new_usage_id($newusageid) { 500 global $DB; 501 502 $data = $this->currentquizattempt; 503 if ($data === null) { 504 return; 505 } 506 507 $oldid = $data->id; 508 $data->uniqueid = $newusageid; 509 510 $newitemid = $DB->insert_record('quiz_attempts', $data); 511 512 // Save quiz_attempt->id mapping, because logs use it. 513 $this->set_mapping('quiz_attempt', $oldid, $newitemid, false); 514 } 515 516 protected function after_execute() { 517 global $DB; 518 519 parent::after_execute(); 520 // Add quiz related files, no need to match by itemname (just internally handled context). 521 $this->add_related_files('mod_quiz', 'intro', null); 522 // Add feedback related files, matching by itemname = 'quiz_feedback'. 523 $this->add_related_files('mod_quiz', 'feedback', 'quiz_feedback'); 524 525 if (!$this->sectioncreated) { 526 $DB->insert_record('quiz_sections', array( 527 'quizid' => $this->get_new_parentid('quiz'), 528 'firstslot' => 1, 'heading' => '', 529 'shufflequestions' => $this->legacyshufflequestionsoption)); 530 } 531 } 532 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body