Skip to content

Commit

Permalink
MDL-77625 question restore: fix restore of existing questions
Browse files Browse the repository at this point in the history
Continuing from the last commit, the code to restore question
bank entries, versions and questions, was not taking note of
whether the first stage had indentified questions that did not
need to be restored. This is tricky, but the mapping can only be
worked out for questions, but in the backup file, the questions are
inside the question_bank_entries, and versions, so we encounter
those first.

Now, the code just saves the QBE and QV when it encounters them,
and then does all the processing when it gets to the question,
correctly taking note of whether each question should be restored or not.
In cases where a particular question does not need to be restored,
we still set up the corresponding mappings.
  • Loading branch information
timhunt committed Sep 16, 2024
1 parent 51250f6 commit 0c0ae09
Showing 1 changed file with 90 additions and 97 deletions.
187 changes: 90 additions & 97 deletions backup/moodle2/restore_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -4925,6 +4925,12 @@ class restore_create_categories_and_questions extends restore_structure_step {
/** @var array $cachedcategory store a question category */
protected $cachedcategory = null;

/** @var stdClass the last question_bank_entry seen during the restore. Processed when we get to a question. */
protected $latestqbe;

/** @var stdClass the last question_version seen during the restore. Processed when we get to a question. */
protected $latestversion;

protected function define_structure() {

// Check if the backup is a pre 4.0 one.
Expand Down Expand Up @@ -5050,45 +5056,27 @@ protected function process_question_category($data) {
}

/**
* Process pre 4.0 question data where in creates the record for version and entry table.
* Set up date to allow restore of questions from pre-4.0 backups.
*
* @param array $data the data from the XML file.
* @param stdClass $data the data from the XML file.
*/
protected function process_question_legacy_data($data) {
global $DB;

$oldid = $data->id;
// Process question bank entry.
$entrydata = new stdClass();
$entrydata->questioncategoryid = $data->category;
$userid = $this->get_mappingid('user', $data->createdby);
if ($userid) {
$entrydata->ownerid = $userid;
} else {
if (!$this->task->is_samesite()) {
$entrydata->ownerid = $this->task->get_userid();
}
}
// The idnumber if it exists also needs to be unique within a category or reset it to null.
if (isset($data->idnumber) && !$DB->record_exists('question_bank_entries',
['idnumber' => $data->idnumber, 'questioncategoryid' => $data->category])) {
$entrydata->idnumber = $data->idnumber;
}
$this->latestqbe = (object) [
'id' => $data->id,
'questioncategoryid' => $data->category,
'ownerid' => $data->createdby,
'idnumber' => $data->idnumber ?? null,
];

$newentryid = $DB->insert_record('question_bank_entries', $entrydata);
// Process question versions.
$versiondata = new stdClass();
$versiondata->questionbankentryid = $newentryid;
$versiondata->version = 1;
// Question id is updated after inserting the question.
$versiondata->questionid = 0;
$versionstatus = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
if ((int)$data->hidden === 1) {
$versionstatus = \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN;
}
$versiondata->status = $versionstatus;
$newversionid = $DB->insert_record('question_versions', $versiondata);
$this->set_mapping('question_version_created', $oldid, $newversionid);
$this->latestversion = (object) [
'id' => $data->id,
'version' => 1,
'status' => $data->hidden ?
\core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN :
\core_question\local\bank\question_version_status::QUESTION_STATUS_READY,
];
}

/**
Expand All @@ -5097,37 +5085,9 @@ protected function process_question_legacy_data($data) {
* @param array $data the data from the XML file.
*/
protected function process_question_bank_entry($data) {
global $DB;

$data = (object)$data;
$oldid = $data->id;

$questioncreated = $this->get_mappingid('question_category_created', $data->questioncategoryid) ? true : false;
$recordexist = $DB->record_exists('question_bank_entries', ['id' => $data->id,
'questioncategoryid' => $data->questioncategoryid]);
// Check we have category created.
if (!$questioncreated && $recordexist) {
return self::SKIP_ALL_CHILDREN;
}

$data->questioncategoryid = $this->get_new_parentid('question_category');
$userid = $this->get_mappingid('user', $data->ownerid);
if ($userid) {
$data->ownerid = $userid;
} else {
if (!$this->task->is_samesite()) {
$data->ownerid = $this->task->get_userid();
}
}

// The idnumber if it exists also needs to be unique within a category or reset it to null.
if (!empty($data->idnumber) && $DB->record_exists('question_bank_entries',
['idnumber' => $data->idnumber, 'questioncategoryid' => $data->questioncategoryid])) {
unset($data->idnumber);
}

$newitemid = $DB->insert_record('question_bank_entries', $data);
$this->set_mapping('question_bank_entry', $oldid, $newitemid);
// We can only determine the right way to process this once we get to
// process_question and have more information, so for now just store.
$this->latestqbe = (object) $data;
}

/**
Expand All @@ -5136,16 +5096,9 @@ protected function process_question_bank_entry($data) {
* @param array $data the data from the XML file.
*/
protected function process_question_versions($data) {
global $DB;

$data = (object)$data;
$oldid = $data->id;

$data->questionbankentryid = $this->get_new_parentid('question_bank_entry');
// Question id is updated after inserting the question.
$data->questionid = 0;
$newitemid = $DB->insert_record('question_versions', $data);
$this->set_mapping('question_versions', $oldid, $newitemid);
// We can only determine the right way to process this once we get to
// process_question and have more information, so for now just store.
$this->latestversion = (object) $data;
}

/**
Expand All @@ -5156,17 +5109,19 @@ protected function process_question_versions($data) {
protected function process_question($data) {
global $DB;

$data = (object)$data;
$data = (object) $data;
$oldid = $data->id;

// Check if the backup is a pre 4.0 one.
// Check we have one mapping for this question.
if (!$questionmapping = $this->get_mapping('question', $oldid)) {
// No mapping = this question doesn't need to be created/mapped.
return;
}

// Check if this is a pre 4.0 backup, then there will not be a question bank entry
// or question version in the file. So, we need to set up that data ready to be used below.
$restoretask = $this->get_task();
if ($restoretask->backup_release_compare('4.0', '<') || $restoretask->backup_version_compare(20220202, '<')) {
// Check we have one mapping for this question.
if (!$questionmapping = $this->get_mapping('question', $oldid)) {
return; // No mapping = this question doesn't need to be created/mapped.
}

// Get the mapped category (cannot use get_new_parentid() because not
// all the categories have been created, so it is not always available
// Instead we get the mapping for the question->parentitemid because
Expand Down Expand Up @@ -5210,28 +5165,66 @@ protected function process_question($data) {
}
}

$newitemid = $DB->insert_record('question', $data);
$this->set_mapping('question', $oldid, $newitemid);
// Also annotate them as question_created, we need
// that later when remapping parents (keeping the old categoryid as parentid).
$parentcatid = $this->get_old_parentid('question_category');
$this->set_mapping('question_created', $oldid, $newitemid, false, null, $parentcatid);
// Now update the question_versions table with the new question id. we dont need to do that for random qtypes.
$legacyquestiondata = $this->get_mappingid('question_version_created', $oldid) ? true : false;
if ($legacyquestiondata) {
$parentitemid = $this->get_mappingid('question_version_created', $oldid);
// With newitemid = 0, let's create the question.
if (!$questionmapping->newitemid) {
// Now we know we are inserting a question, we may need to insert the questionbankentry.
if (empty($this->latestqbe->newid)) {
$this->latestqbe->oldid = $this->latestqbe->id;

$this->latestqbe->questioncategoryid = $this->get_new_parentid('question_category');
$userid = $this->get_mappingid('user', $this->latestqbe->ownerid);
if ($userid) {
$this->latestqbe->ownerid = $userid;
} else {
if (!$this->task->is_samesite()) {
$this->latestqbe->ownerid = $this->task->get_userid();
}
}

// The idnumber if it exists also needs to be unique within a category or reset it to null.
if (!empty($this->latestqbe->idnumber) && $DB->record_exists('question_bank_entries',
['idnumber' => $this->latestqbe->idnumber, 'questioncategoryid' => $this->latestqbe->questioncategoryid])) {
unset($this->latestqbe->idnumber);
}

$this->latestqbe->newid = $DB->insert_record('question_bank_entries', $this->latestqbe);
$this->set_mapping('question_bank_entry', $this->latestqbe->oldid, $this->latestqbe->newid);
}

// Now store the question.
$newitemid = $DB->insert_record('question', $data);
$this->set_mapping('question', $oldid, $newitemid);
// Also annotate them as question_created, we need
// that later when remapping parents (keeping the old categoryid as parentid).
$parentcatid = $this->get_old_parentid('question_category');
$this->set_mapping('question_created', $oldid, $newitemid, false, null, $parentcatid);

// Also insert this question_version.
$oldqvid = $this->latestversion->id;
$this->latestversion->questionbankentryid = $this->latestqbe->newid;
$this->latestversion->questionid = $newitemid;
$newqvid = $DB->insert_record('question_versions', $this->latestversion);
$this->set_mapping('question_versions', $oldqvid, $newqvid);

} else {
$parentitemid = $this->get_new_parentid('question_versions');
// By performing this set_mapping() we make get_old/new_parentid() to work for all the
// children elements of the 'question' one (so qtype plugins will know the question they belong to).
$this->set_mapping('question', $oldid, $questionmapping->newitemid);

// Also create the question_bank_entry and version mappings, if required.
$newquestionversion = $DB->get_record('question_versions', ['questionid' => $questionmapping->newitemid]);
$this->set_mapping('question_versions', $this->latestversion->id, $newquestionversion->id);
if (empty($this->latestqbe->newid)) {
$this->latestqbe->oldid = $this->latestqbe->id;
$this->latestqbe->newid = $newquestionversion->questionbankentryid;
$this->set_mapping('question_bank_entry', $this->latestqbe->oldid, $this->latestqbe->newid);
}
}
$version = new stdClass();
$version->id = $parentitemid;
$version->questionid = $newitemid;
$DB->update_record('question_versions', $version);

// Note, we don't restore any question files yet
// as far as the CONTEXT_MODULE categories still
// haven't their contexts to be restored to
// The {@link restore_create_question_files}, executed in the final step
// The {@see restore_create_question_files}, executed in the final
// step will be in charge of restoring all the question files.
}

Expand Down

0 comments on commit 0c0ae09

Please sign in to comment.