Commit 8df402e4 authored by Shamim Rezaie's avatar Shamim Rezaie
Browse files

MDL-61132 Questions: Adapt logic on backup/restore

Modifying backup/restore to adapt with "Top" categories.
parent 3b8f3198
...@@ -4394,6 +4394,21 @@ class restore_create_categories_and_questions extends restore_structure_step { ...@@ -4394,6 +4394,21 @@ class restore_create_categories_and_questions extends restore_structure_step {
} }
$data->contextid = $mapping->parentitemid; $data->contextid = $mapping->parentitemid;
// Before 3.5, question categories could be created at top level.
// From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
$backuprelease = floatval($this->get_task()->get_info()->backup_release);
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
$backupbuild = (int)$matches[1];
$before35 = false;
if ($backuprelease < 3.5 || $backupbuild < 20180205) {
$before35 = true;
}
if (empty($mapping->info->parent) &&
($before35 || $mapping->info->contextlevel == CONTEXT_MODULE)) {
$top = question_get_top_category($data->contextid, true);
$data->parent = $top->id;
}
// Before 3.1, the 'stamp' field could be erroneously duplicated. // Before 3.1, the 'stamp' field could be erroneously duplicated.
// From 3.1 onwards, there's a unique index of (contextid, stamp). // From 3.1 onwards, there's a unique index of (contextid, stamp).
// If we encounter a duplicate in an old restore file, just generate a new stamp. // If we encounter a duplicate in an old restore file, just generate a new stamp.
...@@ -4554,7 +4569,6 @@ class restore_create_categories_and_questions extends restore_structure_step { ...@@ -4554,7 +4569,6 @@ class restore_create_categories_and_questions extends restore_structure_step {
'backupid' => $this->get_restoreid(), 'backupid' => $this->get_restoreid(),
'itemname' => 'question_category_created')); 'itemname' => 'question_category_created'));
foreach ($qcats as $qcat) { foreach ($qcats as $qcat) {
$newparent = 0;
$dbcat = $DB->get_record('question_categories', array('id' => $qcat->newitemid)); $dbcat = $DB->get_record('question_categories', array('id' => $qcat->newitemid));
// Get new parent (mapped or created, so we look in quesiton_category mappings) // Get new parent (mapped or created, so we look in quesiton_category mappings)
if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array( if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
...@@ -4570,8 +4584,11 @@ class restore_create_categories_and_questions extends restore_structure_step { ...@@ -4570,8 +4584,11 @@ class restore_create_categories_and_questions extends restore_structure_step {
} }
} }
// Here with $newparent empty, problem with contexts or remapping, set it to top cat // Here with $newparent empty, problem with contexts or remapping, set it to top cat
if (!$newparent) { if (!$newparent && $dbcat->parent) {
$DB->set_field('question_categories', 'parent', 0, array('id' => $dbcat->id)); $topcat = question_get_top_category($dbcat->contextid, true);
if ($dbcat->parent != $topcat->id) {
$DB->set_field('question_categories', 'parent', $topcat->id, array('id' => $dbcat->id));
}
} }
} }
...@@ -4580,7 +4597,6 @@ class restore_create_categories_and_questions extends restore_structure_step { ...@@ -4580,7 +4597,6 @@ class restore_create_categories_and_questions extends restore_structure_step {
'backupid' => $this->get_restoreid(), 'backupid' => $this->get_restoreid(),
'itemname' => 'question_created')); 'itemname' => 'question_created'));
foreach ($qs as $q) { foreach ($qs as $q) {
$newparent = 0;
$dbq = $DB->get_record('question', array('id' => $q->newitemid)); $dbq = $DB->get_record('question', array('id' => $q->newitemid));
// Get new parent (mapped or created, so we look in question mappings) // Get new parent (mapped or created, so we look in question mappings)
if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array( if ($newparent = $DB->get_field('backup_ids_temp', 'newitemid', array(
...@@ -4609,18 +4625,38 @@ class restore_move_module_questions_categories extends restore_execution_step { ...@@ -4609,18 +4625,38 @@ class restore_move_module_questions_categories extends restore_execution_step {
protected function define_execution() { protected function define_execution() {
global $DB; global $DB;
$backuprelease = floatval($this->task->get_info()->backup_release);
preg_match('/(\d{8})/', $this->task->get_info()->moodle_release, $matches);
$backupbuild = (int)$matches[1];
$before35 = false;
if ($backuprelease < 3.5 || $backupbuild < 20180205) {
$before35 = true;
}
$contexts = restore_dbops::restore_get_question_banks($this->get_restoreid(), CONTEXT_MODULE); $contexts = restore_dbops::restore_get_question_banks($this->get_restoreid(), CONTEXT_MODULE);
foreach ($contexts as $contextid => $contextlevel) { foreach ($contexts as $contextid => $contextlevel) {
// Only if context mapping exists (i.e. the module has been restored) // Only if context mapping exists (i.e. the module has been restored)
if ($newcontext = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $contextid)) { if ($newcontext = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $contextid)) {
// Update all the qcats having their parentitemid set to the original contextid // Update all the qcats having their parentitemid set to the original contextid
$modulecats = $DB->get_records_sql("SELECT itemid, newitemid $modulecats = $DB->get_records_sql("SELECT itemid, newitemid, info
FROM {backup_ids_temp} FROM {backup_ids_temp}
WHERE backupid = ? WHERE backupid = ?
AND itemname = 'question_category' AND itemname = 'question_category'
AND parentitemid = ?", array($this->get_restoreid(), $contextid)); AND parentitemid = ?", array($this->get_restoreid(), $contextid));
foreach ($modulecats as $modulecat) { foreach ($modulecats as $modulecat) {
$DB->set_field('question_categories', 'contextid', $newcontext->newitemid, array('id' => $modulecat->newitemid)); $cat = new stdClass();
$cat->id = $modulecat->newitemid;
$cat->contextid = $newcontext->newitemid;
// Before 3.5, question categories could be created at top level.
// From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
$info = backup_controller_dbops::decode_backup_temp_info($modulecat->info);
if ($before35 && empty($info->parent)) {
$top = question_get_top_category($newcontext->newitemid, true);
$cat->parent = $top->id;
}
$DB->update_record('question_categories', $cat);
// And set new contextid also in question_category mapping (will be // And set new contextid also in question_category mapping (will be
// used by {@link restore_create_question_files} later // used by {@link restore_create_question_files} later
restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $modulecat->itemid, $modulecat->newitemid, $newcontext->newitemid); restore_dbops::set_backup_ids_record($this->get_restoreid(), 'question_category', $modulecat->itemid, $modulecat->newitemid, $newcontext->newitemid);
......
...@@ -558,9 +558,16 @@ abstract class restore_dbops { ...@@ -558,9 +558,16 @@ abstract class restore_dbops {
* *
* The function returns 2 arrays, one containing errors and another containing * The function returns 2 arrays, one containing errors and another containing
* warnings. Both empty if no errors/warnings are found. * warnings. Both empty if no errors/warnings are found.
*
* @param int $restoreid The restore ID
* @param int $courseid The ID of the course
* @param int $userid The id of the user doing the restore
* @param bool $samesite True if restore is to same site
* @param int $contextlevel (CONTEXT_SYSTEM, etc.)
* @return array A separate list of all error and warnings detected
*/ */
public static function prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, $contextlevel) { public static function prechek_precheck_qbanks_by_level($restoreid, $courseid, $userid, $samesite, $contextlevel) {
global $CFG, $DB; global $DB;
// To return any errors and warnings found // To return any errors and warnings found
$errors = array(); $errors = array();
...@@ -571,6 +578,17 @@ abstract class restore_dbops { ...@@ -571,6 +578,17 @@ abstract class restore_dbops {
CONTEXT_SYSTEM => CONTEXT_COURSE, CONTEXT_SYSTEM => CONTEXT_COURSE,
CONTEXT_COURSECAT => CONTEXT_COURSE); CONTEXT_COURSECAT => CONTEXT_COURSE);
$rc = restore_controller_dbops::load_controller($restoreid);
$restoreinfo = $rc->get_info();
$rc->destroy(); // Always need to destroy.
$backuprelease = floatval($restoreinfo->backup_release);
preg_match('/(\d{8})/', $restoreinfo->moodle_release, $matches);
$backupbuild = (int)$matches[1];
$after35 = false;
if ($backuprelease >= 3.5 && $backupbuild > 20180205) {
$after35 = true;
}
// For any contextlevel, follow this process logic: // For any contextlevel, follow this process logic:
// //
// 0) Iterate over each context (qbank) // 0) Iterate over each context (qbank)
...@@ -587,6 +605,7 @@ abstract class restore_dbops { ...@@ -587,6 +605,7 @@ abstract class restore_dbops {
// 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop // 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
// 7b) No fallback, error. End qcat loop // 7b) No fallback, error. End qcat loop
// 5b) Match, mark q to be mapped // 5b) Match, mark q to be mapped
// 8) Check if backup is from Moodle >= 3.5 and error if more than one top-level category in the context.
// Get all the contexts (question banks) in restore for the given contextlevel // Get all the contexts (question banks) in restore for the given contextlevel
$contexts = self::restore_get_question_banks($restoreid, $contextlevel); $contexts = self::restore_get_question_banks($restoreid, $contextlevel);
...@@ -596,6 +615,8 @@ abstract class restore_dbops { ...@@ -596,6 +615,8 @@ abstract class restore_dbops {
// Init some perms // Init some perms
$canmanagecategory = false; $canmanagecategory = false;
$canadd = false; $canadd = false;
// Top-level category counter.
$topcats = 0;
// get categories in context (bank) // get categories in context (bank)
$categories = self::restore_get_question_categories($restoreid, $contextid); $categories = self::restore_get_question_categories($restoreid, $contextid);
// cache permissions if $targetcontext is found // cache permissions if $targetcontext is found
...@@ -605,6 +626,10 @@ abstract class restore_dbops { ...@@ -605,6 +626,10 @@ abstract class restore_dbops {
} }
// 1) Iterate over each qcat in the context, matching by stamp for the found target context // 1) Iterate over each qcat in the context, matching by stamp for the found target context
foreach ($categories as $category) { foreach ($categories as $category) {
if ($category->parent == 0) {
$topcats++;
}
$matchcat = false; $matchcat = false;
if ($targetcontext) { if ($targetcontext) {
$matchcat = $DB->get_record('question_categories', array( $matchcat = $DB->get_record('question_categories', array(
...@@ -690,6 +715,12 @@ abstract class restore_dbops { ...@@ -690,6 +715,12 @@ abstract class restore_dbops {
} }
} }
} }
// 8) Check if backup is made on Moodle >= 3.5 and there are more than one top-level category in the context.
if ($after35 && $topcats > 1) {
$errors[] = get_string('restoremultipletopcats', 'questions', $contextid);
}
} }
return array($errors, $warnings); return array($errors, $warnings);
......
...@@ -26,7 +26,7 @@ require_once($CFG->dirroot.'/backup/util/xml/parser/processors/grouped_parser_pr ...@@ -26,7 +26,7 @@ require_once($CFG->dirroot.'/backup/util/xml/parser/processors/grouped_parser_pr
/** /**
* helper implementation of grouped_parser_processor that will * helper implementation of grouped_parser_processor that will
* load all the categories and questions (header info only) from then questions.xml file * load all the categories and questions (header info only) from the questions.xml file
* to the backup_ids table storing the whole structure there for later processing. * to the backup_ids table storing the whole structure there for later processing.
* Note: only "needed" categories are loaded (must have question_categoryref record in backup_ids) * Note: only "needed" categories are loaded (must have question_categoryref record in backup_ids)
* Note: parentitemid will contain the category->contextid for categories * Note: parentitemid will contain the category->contextid for categories
......
...@@ -399,6 +399,7 @@ $string['requiresgrading'] = 'Requires grading'; ...@@ -399,6 +399,7 @@ $string['requiresgrading'] = 'Requires grading';
$string['responsehistory'] = 'Response history'; $string['responsehistory'] = 'Response history';
$string['restart'] = 'Start again'; $string['restart'] = 'Start again';
$string['restartwiththeseoptions'] = 'Start again with these options'; $string['restartwiththeseoptions'] = 'Start again with these options';
$string['restoremultipletopcats'] = 'The backup file contains more than one top-level question categories for context {$a}.';
$string['rightanswer'] = 'Right answer'; $string['rightanswer'] = 'Right answer';
$string['rightanswer_help'] = 'an automatically generated summary of the correct response. This can be limited, so you may wish to consider explaining the correct solution in the general feedback for the question, and turning this option off.'; $string['rightanswer_help'] = 'an automatically generated summary of the correct response. This can be limited, so you may wish to consider explaining the correct solution in the general feedback for the question, and turning this option off.';
$string['saved'] = 'Saved: {$a}'; $string['saved'] = 'Saved: {$a}';
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment