Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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