Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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  }