Skip to content

Commit

Permalink
Merge branch 'MDL-77625' of https://github.com/timhunt/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
rezaies committed Sep 23, 2024
2 parents e9f789f + 5ec6096 commit e5290c4
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 110 deletions.
189 changes: 90 additions & 99 deletions backup/moodle2/restore_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -5006,6 +5006,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 @@ -5131,45 +5137,25 @@ 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 @@ -5178,37 +5164,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 @@ -5217,16 +5175,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 @@ -5237,17 +5188,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 @@ -5291,28 +5244,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
7 changes: 3 additions & 4 deletions backup/util/dbops/restore_dbops.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,7 @@ public static function prechek_precheck_qbanks_by_level($restoreid, $courseid, $
$questions = self::restore_get_questions($restoreid, $category->id);

// Collect all the questions for this category into memory so we only talk to the DB once.
$questioncache = $DB->get_records_sql_menu('SELECT q.id,
q.stamp
$questioncache = $DB->get_records_sql_menu('SELECT q.stamp, q.id
FROM {question} q
JOIN {question_versions} qv
ON qv.questionid = q.id
Expand All @@ -683,8 +682,8 @@ public static function prechek_precheck_qbanks_by_level($restoreid, $courseid, $
WHERE qc.id = ?', array($matchcat->id));

foreach ($questions as $question) {
if (isset($questioncache[$question->stamp." ".$question->version])) {
$matchqid = $questioncache[$question->stamp." ".$question->version];
if (isset($questioncache[$question->stamp])) {
$matchqid = $questioncache[$question->stamp];
} else {
$matchqid = false;
}
Expand Down
28 changes: 21 additions & 7 deletions backup/util/helper/restore_questions_parser_processor.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,44 @@
* TODO: Complete phpdocs
*/
class restore_questions_parser_processor extends grouped_parser_processor {
/** @var string XML path in the questions.xml backup file to question categories. */
protected const CATEGORY_PATH = '/question_categories/question_category';

protected $restoreid;
protected $lastcatid;
/** @var string XML path in the questions.xml to question elements within question_category (Moodle 4.0+). */
protected const QUESTION_SUBPATH =
'/question_bank_entries/question_bank_entry/question_version/question_versions/questions/question';

/** @var string XML path in the questions.xml to question elements within question_category (before Moodle 4.0). */
protected const LEGACY_QUESTION_SUBPATH = '/questions/question';

/** @var string identifies the current restore. */
protected string $restoreid;

/** @var int during the restore, this tracks the last category we saw. Any questions we see will be in here. */
protected int $lastcatid;

public function __construct($restoreid) {
$this->restoreid = $restoreid;
$this->lastcatid = 0;
parent::__construct(array());
parent::__construct();
// Set the paths we are interested on
$this->add_path('/question_categories/question_category');
$this->add_path('/question_categories/question_category/questions/question');
$this->add_path(self::CATEGORY_PATH);
$this->add_path(self::CATEGORY_PATH . self::QUESTION_SUBPATH);
$this->add_path(self::CATEGORY_PATH . self::LEGACY_QUESTION_SUBPATH);
}

protected function dispatch_chunk($data) {
// Prepare question_category record
if ($data['path'] == '/question_categories/question_category') {
if ($data['path'] == self::CATEGORY_PATH) {
$info = (object)$data['tags'];
$itemname = 'question_category';
$itemid = $info->id;
$parentitemid = $info->contextid;
$this->lastcatid = $itemid;

// Prepare question record
} else if ($data['path'] == '/question_categories/question_category/questions/question') {
} else if ($data['path'] == self::CATEGORY_PATH . self::QUESTION_SUBPATH ||
$data['path'] == self::CATEGORY_PATH . self::LEGACY_QUESTION_SUBPATH) {
$info = (object)$data['tags'];
$itemname = 'question';
$itemid = $info->id;
Expand Down
Loading

0 comments on commit e5290c4

Please sign in to comment.