MDL-46696 backup: Include grade history in backups

This commit is contained in:
Frederic Massart 2014-08-19 16:02:00 +08:00
parent e609e6cf02
commit 167eb0335b
8 changed files with 255 additions and 1 deletions

View File

@ -181,6 +181,9 @@ abstract class backup_activity_task extends backup_task {
// Generate the grading file (conditionally)
$this->add_step(new backup_activity_grading_structure_step('activity_grading', 'grading.xml'));
// Generate the grade history file. The setting 'grade_histories' is handled in the step.
$this->add_step(new backup_activity_grade_history_structure_step('activity_grade_history', 'grade_history.xml'));
// Annotate the scales used in already annotated outcomes
$this->add_step(new backup_annotate_scales_from_outcomes('annotate_scales'));

View File

@ -89,6 +89,9 @@ class backup_final_task extends backup_task {
// execute_condition() so only will be excuted if ALL module grade_items in course have been exported
$this->add_step(new backup_gradebook_structure_step('course_gradebook','gradebook.xml'));
// Generate the grade history file, conditionally.
$this->add_step(new backup_grade_history_structure_step('course_grade_history','grade_history.xml'));
// Generate the course completion
$this->add_step(new backup_course_completion_structure_step('course_completion', 'completion.xml'));

View File

@ -152,6 +152,9 @@ class backup_root_task extends backup_task {
$gradehistories->set_ui(new backup_setting_ui_checkbox($gradehistories, get_string('rootsettinggradehistories', 'backup')));
$this->add_setting($gradehistories);
$users->add_dependency($gradehistories);
// The restore does not process the grade histories when some activities are ignored.
// So let's define a dependency to prevent false expectations from our users.
$activities->add_dependency($gradehistories);
// Define question bank inclusion setting.
$questionbank = new backup_generic_setting('questionbank', base_setting::IS_BOOLEAN, true);

View File

@ -1022,6 +1022,65 @@ class backup_gradebook_structure_step extends backup_structure_step {
}
}
/**
* Step in charge of constructing the grade_history.xml file containing the grade histories.
*/
class backup_grade_history_structure_step extends backup_structure_step {
/**
* Limit the execution.
*
* This applies the same logic than the one applied to {@link backup_gradebook_structure_step},
* because we do not want to save the history of items which are not backed up. At least for now.
*/
protected function execute_condition() {
return backup_plan_dbops::require_gradebook_backup($this->get_courseid(), $this->get_backupid());
}
protected function define_structure() {
// Settings to use.
$userinfo = $this->get_setting_value('users');
$history = $this->get_setting_value('grade_histories');
// Create the nested elements.
$bookhistory = new backup_nested_element('grade_history');
$grades = new backup_nested_element('grade_grades');
$grade = new backup_nested_element('grade_grade', array('id'), array(
'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
'excluded', 'feedback', 'feedbackformat', 'information',
'informationformat', 'timemodified'));
// Build the tree.
$bookhistory->add_child($grades);
$grades->add_child($grade);
// This only happens if we are including user info and history.
if ($userinfo && $history) {
// Only keep the history of grades related to items which have been backed up, The query is
// similar (but not identical) to the one used in backup_gradebook_structure_step::define_structure().
$gradesql = "SELECT ggh.*
FROM {grade_grades_history} ggh
JOIN {grade_items} gi ON ggh.itemid = gi.id
WHERE gi.courseid = :courseid
AND (gi.itemtype = 'manual' OR gi.itemtype = 'course' OR gi.itemtype = 'category')";
$grade->set_source_sql($gradesql, array('courseid' => backup::VAR_COURSEID));
}
// Annotations. (Final annotations as this step is part of the final task).
$grade->annotate_ids('scalefinal', 'rawscaleid');
$grade->annotate_ids('userfinal', 'loggeduser');
$grade->annotate_ids('userfinal', 'userid');
$grade->annotate_ids('userfinal', 'usermodified');
// Return the root element.
return $bookhistory;
}
}
/**
* structure step in charge if constructing the completion.xml file for all the users completion
* information in a given activity
@ -2162,6 +2221,56 @@ class backup_activity_grades_structure_step extends backup_structure_step {
}
}
/**
* Structure step in charge of constructing the grade history of an activity.
*
* This step is added to the task regardless of the setting 'grade_histories'.
* The reason is to allow for a more flexible step in case the logic needs to be
* split accross different settings to control the history of items and/or grades.
*/
class backup_activity_grade_history_structure_step extends backup_structure_step {
protected function define_structure() {
// Settings to use.
$userinfo = $this->get_setting_value('userinfo');
$history = $this->get_setting_value('grade_histories');
// Create the nested elements.
$bookhistory = new backup_nested_element('grade_history');
$grades = new backup_nested_element('grade_grades');
$grade = new backup_nested_element('grade_grade', array('id'), array(
'action', 'oldid', 'source', 'loggeduser', 'itemid', 'userid',
'rawgrade', 'rawgrademax', 'rawgrademin', 'rawscaleid',
'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime', 'exported', 'overridden',
'excluded', 'feedback', 'feedbackformat', 'information',
'informationformat', 'timemodified'));
// Build the tree.
$bookhistory->add_child($grades);
$grades->add_child($grade);
// This only happens if we are including user info and history.
if ($userinfo && $history) {
// Define sources. Only select the history related to existing activity items.
$grade->set_source_sql("SELECT ggh.*
FROM {grade_grades_history} ggh
JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid
WHERE bi.backupid = ?
AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
}
// Annotations.
$grade->annotate_ids('scalefinal', 'rawscaleid'); // Straight as scalefinal because it's > 0.
$grade->annotate_ids('user', 'loggeduser');
$grade->annotate_ids('user', 'userid');
$grade->annotate_ids('user', 'usermodified');
// Return the root element.
return $bookhistory;
}
}
/**
* Backups up the course completion information for the course.
*/

View File

@ -163,6 +163,9 @@ abstract class restore_activity_task extends restore_task {
// Advanced grading methods attached to the module
$this->add_step(new restore_activity_grading_structure_step('activity_grading', 'grading.xml'));
// Grade history. The setting 'grade_history' is handled in the step.
$this->add_step(new restore_activity_grade_history_structure_step('activity_grade_history', 'grade_history.xml'));
// Userscompletion (conditionally)
if ($this->get_setting_value('userscompletion')) {
$this->add_step(new restore_userscompletion_structure_step('activity_userscompletion', 'completion.xml'));

View File

@ -58,6 +58,7 @@ class restore_final_task extends restore_task {
// Gradebook. Don't restore the gradebook unless activities are being restored.
if ($this->get_setting_value('activities')) {
$this->add_step(new restore_gradebook_structure_step('gradebook_step','gradebook.xml'));
$this->add_step(new restore_grade_history_structure_step('grade_history', 'grade_history.xml'));
}
// Course completion, executed conditionally if restoring to new course

View File

@ -246,5 +246,9 @@ class restore_root_task extends restore_task {
$gradehistories->get_ui()->set_changeable($changeable);
$this->add_setting($gradehistories);
$users->add_dependency($gradehistories);
// The restore does not process the grade histories when some activities are ignored.
// So let's define a dependency to prevent false expectations from our users.
$activities->add_dependency($gradehistories);
}
}

View File

@ -224,6 +224,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$olduserid = $data->userid;
$data->itemid = $this->get_new_parentid('grade_item');
@ -243,6 +244,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
$this->log($message, backup::LOG_DEBUG);
} else {
$newitemid = $DB->insert_record('grade_grades', $data);
$this->set_mapping('grade_grades', $oldid, $newitemid);
}
} else {
$message = "Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'";
@ -445,6 +447,85 @@ class restore_gradebook_structure_step extends restore_structure_step {
}
}
/**
* Step in charge of restoring the grade history of a course.
*
* The execution conditions are itendical to {@link restore_gradebook_structure_step} because
* we do not want to restore the history if the gradebook and its content has not been
* restored. At least for now.
*/
class restore_grade_history_structure_step extends restore_structure_step {
protected function execute_condition() {
global $CFG, $DB;
// No gradebook info found, don't execute.
$fullpath = $this->task->get_taskbasepath();
$fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
if (!file_exists($fullpath)) {
return false;
}
// Some module present in backup file isn't available to restore in this site, don't execute.
if ($this->task->is_missing_modules()) {
return false;
}
// Some activity has been excluded to be restored, don't execute.
if ($this->task->is_excluding_activities()) {
return false;
}
// There should only be one grade category (the 1 associated with the course itself).
$category = new stdclass();
$category->courseid = $this->get_courseid();
$catcount = $DB->count_records('grade_categories', (array)$category);
if ($catcount > 1) {
return false;
}
// Arrived here, execute the step.
return true;
}
protected function define_structure() {
$paths = array();
// Settings to use.
$userinfo = $this->get_setting_value('users');
$history = $this->get_setting_value('grade_histories');
if ($userinfo && $history) {
$paths[] = new restore_path_element('grade_grade',
'/grade_history/grade_grades/grade_grade');
}
return $paths;
}
protected function process_grade_grade($data) {
global $DB;
$data = (object)($data);
$olduserid = $data->userid;
unset($data->id);
$data->userid = $this->get_mappingid('user', $data->userid, null);
if (!empty($data->userid)) {
// Do not apply the date offsets as this is history.
$data->itemid = $this->get_mappingid('grade_item', $data->itemid);
$data->oldid = $this->get_mappingid('grade_grades', $data->oldid);
$data->usermodified = $this->get_mappingid('user', $data->usermodified, null);
$data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid);
$DB->insert_record('grade_grades_history', $data);
} else {
$message = "Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'";
$this->log($message, backup::LOG_DEBUG);
}
}
}
/**
* decode all the interlinks present in restored content
* relying 100% in the restore_decode_processor that handles
@ -2895,6 +2976,7 @@ class restore_activity_grades_structure_step extends restore_structure_step {
protected function process_grade_grade($data) {
$data = (object)($data);
$olduserid = $data->userid;
$oldid = $data->id;
unset($data->id);
$data->itemid = $this->get_new_parentid('grade_item');
@ -2908,7 +2990,7 @@ class restore_activity_grades_structure_step extends restore_structure_step {
$grade = new grade_grade($data, false);
$grade->insert('restore');
// no need to save any grade_grade mapping
$this->set_mapping('grade_grades', $oldid, $grade->id);
} else {
debugging("Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'");
}
@ -2941,6 +3023,52 @@ class restore_activity_grades_structure_step extends restore_structure_step {
}
}
/**
* Step in charge of restoring the grade history of an activity.
*
* This step is added to the task regardless of the setting 'grade_histories'.
* The reason is to allow for a more flexible step in case the logic needs to be
* split accross different settings to control the history of items and/or grades.
*/
class restore_activity_grade_history_structure_step extends restore_structure_step {
protected function define_structure() {
$paths = array();
// Settings to use.
$userinfo = $this->get_setting_value('userinfo');
$history = $this->get_setting_value('grade_histories');
if ($userinfo && $history) {
$paths[] = new restore_path_element('grade_grade',
'/grade_history/grade_grades/grade_grade');
}
return $paths;
}
protected function process_grade_grade($data) {
global $DB;
$data = (object) $data;
$olduserid = $data->userid;
unset($data->id);
$data->userid = $this->get_mappingid('user', $data->userid, null);
if (!empty($data->userid)) {
// Do not apply the date offsets as this is history.
$data->itemid = $this->get_mappingid('grade_item', $data->itemid);
$data->oldid = $this->get_mappingid('grade_grades', $data->oldid);
$data->usermodified = $this->get_mappingid('user', $data->usermodified, null);
$data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid);
$DB->insert_record('grade_grades_history', $data);
} else {
$message = "Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'";
$this->log($message, backup::LOG_DEBUG);
}
}
}
/**
* This structure steps restores one instance + positions of one block