MDL-20636 Work-in-progress on quiz attempt backup and restore.

This commit is contained in:
Tim Hunt 2011-04-28 21:21:43 +01:00
parent d1de533164
commit bea1a6a73a
4 changed files with 71 additions and 104 deletions

View file

@ -174,78 +174,65 @@ abstract class backup_questions_activity_structure_step extends backup_activity_
/** /**
* Attach to $element (usually attempts) the needed backup structures * Attach to $element (usually attempts) the needed backup structures
* for question_states for a given question_attempt * for question_usages and all the associated data.
*/ */
protected function add_question_attempts_states($element, $questionattemptname) { protected function add_question_usages($element, $usageidname) {
// Check $element is one nested_backup_element // Check $element is one nested_backup_element
if (! $element instanceof backup_nested_element) { if (! $element instanceof backup_nested_element) {
throw new backup_step_exception('question_states_bad_parent_element', $element); throw new backup_step_exception('question_states_bad_parent_element', $element);
} }
// Check that the $questionattemptname is final element in $element
if (! $element->get_final_element($questionattemptname)) { if (! $element->get_final_element($questionattemptname)) {
throw new backup_step_exception('question_states_bad_question_attempt_element', $questionattemptname); throw new backup_step_exception('question_states_bad_question_attempt_element', $questionattemptname);
} }
// TODO: Some day we should stop these "encrypted" state->answers and $quba = new backup_nested_element('question_usage', array('id'),
// TODO: delegate to qtypes plugin to proper XML writting the needed info on each question array('preferredbehaviour'));
// TODO: Should be doing here some introspection in the "answer" element, based on qtype, $qas = new backup_nested_element('question_attempts');
// TODO: to know which real questions are being used (for randoms and other qtypes...) $qa = new backup_nested_element('question_attempt', array('id'), array(
// TODO: Not needed if consistency is guaranteed, but it isn't right now :-( 'slot', 'behaviour', 'questionid', 'maxmark', 'minfraction',
'flagged', 'questionsummary', 'rightanswer', 'responsesummary',
'timemodified'));
// Define the elements $steps = new backup_nested_element('steps');
$states = new backup_nested_element('states'); $step = new backup_nested_element('step', array('id'), array(
$state = new backup_nested_element('state', array('id'), array( 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid'));
'question', 'seq_number', 'answer', 'timestamp',
'event', 'grade', 'raw_grade', 'penalty')); $data = new backup_nested_element('data');
$value = new backup_nested_element('value', array('name'),
array('value'));
// Build the tree // Build the tree
$element->add_child($states); $element->add_child($quba);
$states->add_child($state); $quba->add_child($qas);
$qas->add_child($qa);
$qa->add_child($steps);
$steps->add_child($step);
$step->add_child($data);
$data->add_child($value);
// Set the sources // Set the sources
$state->set_source_table('question_states', array('attempt' => '../../' . $questionattemptname)); $quba->set_source_table('question_usages',
array('id' => '../../' . $usageidname));
$qa->set_source_table('question_attempts',
array('questionusageid' => backup::VAR_PARENTID));
$step->set_source_table('question_attempt_steps',
array('questionattemptid' => backup::VAR_PARENTID));
$value->set_source_table('question_attempt_step_data',
array('attemptstepid' => backup::VAR_PARENTID));
// Annotate ids // Annotate ids
$state->annotate_ids('question', 'question'); $qa->annotate_ids('question', 'questionid');
}
/**
* Attach to $element (usually attempts) the needed backup structures
* for question_sessions for a given question_attempt
*/
protected function add_question_attempts_sessions($element, $questionattemptname) {
// Check $element is one nested_backup_element
if (! $element instanceof backup_nested_element) {
throw new backup_step_exception('question_sessions_bad_parent_element', $element);
}
// Check that the $questionattemptname is final element in $element
if (! $element->get_final_element($questionattemptname)) {
throw new backup_step_exception('question_sessions_bad_question_attempt_element', $questionattemptname);
}
// Define the elements
$sessions = new backup_nested_element('sessions');
$session = new backup_nested_element('session', array('id'), array(
'questionid', 'newest', 'newgraded', 'sumpenalty',
'manualcomment', 'manualcommentformat', 'flagged'));
// Build the tree
$element->add_child($sessions);
$sessions->add_child($session);
// Set the sources
$session->set_source_table('question_sessions', array('attemptid' => '../../' . $questionattemptname));
// Annotate ids
$session->annotate_ids('question', 'questionid');
// Annotate files // Annotate files
// Note: question_sessions haven't files associated. On purpose manualcomment is lacking $fileareas = question_engine_data_mapper::get_all_response_file_areas();
// support for them, so we don't need to annotated them here. foreach ($fileareas as $filearea) {
$step->annotate_files('question', $filearea, 'id');
}
} }
} }
/** /**
* backup structure step in charge of calculating the categories to be * backup structure step in charge of calculating the categories to be
* included in backup, based in the context being backuped (module/course) * included in backup, based in the context being backuped (module/course)

View file

@ -2524,9 +2524,9 @@ abstract class restore_questions_activity_structure_step extends restore_activit
/** /**
* Attach below $element (usually attempts) the needed restore_path_elements * Attach below $element (usually attempts) the needed restore_path_elements
* to restore question_states * to restore question_usages and all they contain.
*/ */
protected function add_question_attempts_states($element, &$paths) { protected function add_question_usages($element, &$paths) {
// Check $element is restore_path_element // Check $element is restore_path_element
if (! $element instanceof restore_path_element) { if (! $element instanceof restore_path_element) {
throw new restore_step_exception('element_must_be_restore_path_element', $element); throw new restore_step_exception('element_must_be_restore_path_element', $element);
@ -2535,31 +2535,22 @@ abstract class restore_questions_activity_structure_step extends restore_activit
if (!is_array($paths)) { if (!is_array($paths)) {
throw new restore_step_exception('paths_must_be_array', $paths); throw new restore_step_exception('paths_must_be_array', $paths);
} }
$paths[] = new restore_path_element('question_state', $element->get_path() . '/states/state'); $paths[] = new restore_path_element('question_usage',
$element->get_path() . '/question_usage');
$paths[] = new restore_path_element('question_attempt',
$element->get_path() . '/question_usage/question_attempts/question_attempt');
$paths[] = new restore_path_element('question_attempt_step',
$element->get_path() . '/question_usage/question_attempts/question_attempt/steps/step');
$paths[] = new restore_path_element('question_attempt_step_data',
$element->get_path() . '/question_usage/question_attempts/question_attempt/steps/step/data/value');
} }
/** /**
* Attach below $element (usually attempts) the needed restore_path_elements * Process question_usages
* to restore question_sessions
*/ */
protected function add_question_attempts_sessions($element, &$paths) { protected function process_question_usage($data) {
// Check $element is restore_path_element
if (! $element instanceof restore_path_element) {
throw new restore_step_exception('element_must_be_restore_path_element', $element);
}
// Check $paths is one array
if (!is_array($paths)) {
throw new restore_step_exception('paths_must_be_array', $paths);
}
$paths[] = new restore_path_element('question_session', $element->get_path() . '/sessions/session');
}
/**
* Process question_states
*/
protected function process_question_state($data) {
global $DB; global $DB;
// TODO
$data = (object)$data; $data = (object)$data;
$oldid = $data->id; $oldid = $data->id;
@ -2579,11 +2570,11 @@ abstract class restore_questions_activity_structure_step extends restore_activit
} }
/** /**
* Process question_sessions * Process question_attempts
*/ */
protected function process_question_session($data) { protected function process_question_attempt($data) {
global $DB; global $DB;
// TODO
$data = (object)$data; $data = (object)$data;
$oldid = $data->id; $oldid = $data->id;
@ -2601,6 +2592,22 @@ abstract class restore_questions_activity_structure_step extends restore_activit
// support for them, so we don't need to handle them here. // support for them, so we don't need to handle them here.
} }
/**
* Process question_attempt_steps
*/
protected function process_question_attempt_step($data) {
global $DB;
// TODO
}
/**
* Process question_attempt_step_data
*/
protected function process_question_attempt_step_data($data) {
global $DB;
// TODO
}
/** /**
* Given a list of question->ids, separated by commas, returns the * Given a list of question->ids, separated by commas, returns the
* recoded list, with all the restore question mappings applied. * recoded list, with all the restore question mappings applied.
@ -2619,27 +2626,4 @@ abstract class restore_questions_activity_structure_step extends restore_activit
} }
return implode(',', $questionids); return implode(',', $questionids);
} }
/**
* Given one question_states record, return the answer
* recoded pointing to all the restored stuff
*/
public function restore_recode_answer($state, $qtype) {
// Build one static cache to store {@link restore_qtype_plugin}
// while we are needing them, just to save zillions of instantiations
// or using static stuff that will break our nice API
static $qtypeplugins = array();
// If we haven't the corresponding restore_qtype_plugin for current qtype
// instantiate it and add to cache
if (!isset($qtypeplugins[$qtype])) {
$classname = 'restore_qtype_' . $qtype . '_plugin';
if (class_exists($classname)) {
$qtypeplugins[$qtype] = new $classname('qtype', $qtype, $this);
} else {
$qtypeplugins[$qtype] = false;
}
}
return !empty($qtypeplugins[$qtype]) ? $qtypeplugins[$qtype]->recode_state_answer($state) : $state->answer;
}
} }

View file

@ -83,8 +83,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
// This module is using questions, so produce the related question states and sessions // This module is using questions, so produce the related question states and sessions
// attaching them to the $attempt element based in 'uniqueid' matching // attaching them to the $attempt element based in 'uniqueid' matching
$this->add_question_attempts_states($attempt, 'uniqueid'); $this->add_question_usages($attempt, 'uniqueid');
$this->add_question_attempts_sessions($attempt, 'uniqueid');
// Build the tree // Build the tree

View file

@ -50,8 +50,7 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
'/activity/quiz/attempts/attempt'); '/activity/quiz/attempts/attempt');
$paths[] = $quizattempt; $paths[] = $quizattempt;
// Add states and sessions // Add states and sessions
$this->add_question_attempts_states($quizattempt, $paths); $this->add_question_usages($quizattempt, $paths);
$this->add_question_attempts_sessions($quizattempt, $paths);
} }
// Return the paths wrapped into standard activity structure // Return the paths wrapped into standard activity structure
@ -274,8 +273,6 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$data->timefinish = $this->apply_date_offset($data->timefinish); $data->timefinish = $this->apply_date_offset($data->timefinish);
$data->timemodified = $this->apply_date_offset($data->timemodified); $data->timemodified = $this->apply_date_offset($data->timemodified);
$data->layout = $this->questions_recode_layout($data->layout);
$newitemid = $DB->insert_record('quiz_attempts', $data); $newitemid = $DB->insert_record('quiz_attempts', $data);
// Save quiz_attempt->uniqueid as quiz_attempt mapping, both question_states and // Save quiz_attempt->uniqueid as quiz_attempt mapping, both question_states and