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