MDL-38538 question auto-save back end.

1. Autosave works in some ways just like a normal save. We ultimately
call $behaviour->process_save() to do the work, and create a new step to
hold the data.

2. However, we come in through a completely different route through the
API, starting with separate auto-save methods. This keeps the auto-save
changes mostly separate, and so reduced the chance of breaking existing
working code.

3. When the time comes to store the auto-save step in the database, we
save it using a negative sequence number.

This is a clever trick that not only distinguises these steps, but also
avoids unique key errors when an auto-save and a real action happen
simultaneously. (There are unit tests for these tricky edge cases.)

4. When we load the data back from the database, most of the time the
auto-save steps are loaded back as if they were a real save, and so the
auto-saved data is used when the question is then rendered.

5. However, before we process another action, we remove the auto-saved
step, so it does not appear in the final history.
This commit is contained in:
Tim Hunt
2013-03-28 16:51:59 +00:00
parent eca230b521
commit 0a606a2be2
8 changed files with 886 additions and 23 deletions
+56 -9
View File
@@ -511,7 +511,49 @@ class question_usage_by_activity {
* instead of the data from $_POST.
*/
public function process_all_actions($timestamp = null, $postdata = null) {
// note: we must not use "question_attempt::get_submitted_var()" because there is no attempt instance!!!
foreach ($this->get_slots_in_request($postdata) as $slot) {
if (!$this->validate_sequence_number($slot, $postdata)) {
continue;
}
$submitteddata = $this->extract_responses($slot, $postdata);
$this->process_action($slot, $submitteddata, $timestamp);
}
$this->update_question_flags($postdata);
}
/**
* Process all the question autosave data in the current request.
*
* If there is a parameter slots included in the post data, then only
* those question numbers will be processed, otherwise all questions in this
* useage will be.
*
* This function also does {@link update_question_flags()}.
*
* @param int $timestamp optional, use this timestamp as 'now'.
* @param array $postdata optional, only intended for testing. Use this data
* instead of the data from $_POST.
*/
public function process_all_autosaves($timestamp = null, $postdata = null) {
foreach ($this->get_slots_in_request($postdata) as $slot) {
if (!$this->is_autosave_required($slot, $postdata)) {
continue;
}
$submitteddata = $this->extract_responses($slot, $postdata);
$this->process_autosave($slot, $submitteddata, $timestamp);
}
$this->update_question_flags($postdata);
}
/**
* Get the list of slot numbers that should be processed as part of processing
* the current request.
* @param array $postdata optional, only intended for testing. Use this data
* instead of the data from $_POST.
* @return array of slot numbers.
*/
protected function get_slots_in_request($postdata = null) {
// Note: we must not use "question_attempt::get_submitted_var()" because there is no attempt instance!!!
if (is_null($postdata)) {
$slots = optional_param('slots', null, PARAM_SEQUENCE);
} else if (array_key_exists('slots', $postdata)) {
@@ -526,14 +568,7 @@ class question_usage_by_activity {
} else {
$slots = explode(',', $slots);
}
foreach ($slots as $slot) {
if (!$this->validate_sequence_number($slot, $postdata)) {
continue;
}
$submitteddata = $this->extract_responses($slot, $postdata);
$this->process_action($slot, $submitteddata, $timestamp);
}
$this->update_question_flags($postdata);
return $slots;
}
/**
@@ -560,6 +595,18 @@ class question_usage_by_activity {
$this->observer->notify_attempt_modified($qa);
}
/**
* Process an autosave action on a specific question.
* @param int $slot the number used to identify this question within this usage.
* @param $submitteddata the submitted data that constitutes the action.
*/
public function process_autosave($slot, $submitteddata, $timestamp = null) {
$qa = $this->get_question_attempt($slot);
if ($qa->process_autosave($submitteddata, $timestamp)) {
$this->observer->notify_attempt_modified($qa);
}
}
/**
* Check that the sequence number, that detects weird things like the student
* clicking back, is OK. If the sequence check variable is not present, returns