mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 00:42:54 +02:00
MDL-40992 question engine: new ways modify question usages
* A method to change the max mark for one question_attempt in the usage * A method to replace one question in a usage with another, moving the old question_attempt to the end. * Methods to set and get metadata (string name value pairs) for each question_attempt in the usage. This gets stored in the first step in a way that should not interfere with anything else.
This commit is contained in:
parent
47be39ef41
commit
f6579bea94
@ -252,6 +252,48 @@ class question_engine_data_mapper {
|
||||
return $this->prepare_step_data($step, $record->id, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store new metadata for an existing {@link question_attempt} in the database.
|
||||
*
|
||||
* Private method, only for use by other parts of the question engine.
|
||||
*
|
||||
* @param question_attempt $qa the question attempt to store meta data for.
|
||||
* @param array $names the names of the metadata variables to store.
|
||||
* @return array of question_attempt_step_data rows, that still need to be inserted.
|
||||
*/
|
||||
public function insert_question_attempt_metadata(question_attempt $qa, array $names) {
|
||||
$firststep = $qa->get_step(0);
|
||||
|
||||
$rows = array();
|
||||
foreach ($names as $name) {
|
||||
$data = new stdClass();
|
||||
$data->attemptstepid = $firststep->get_id();
|
||||
$data->name = ':_' . $name;
|
||||
$data->value = $firststep->get_metadata_var($name);
|
||||
$rows[] = $data;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates existing metadata for an existing {@link question_attempt} in the database.
|
||||
*
|
||||
* Private method, only for use by other parts of the question engine.
|
||||
*
|
||||
* @param question_attempt $qa the question attempt to store meta data for.
|
||||
* @param array $names the names of the metadata variables to store.
|
||||
* @return array of question_attempt_step_data rows, that still need to be inserted.
|
||||
*/
|
||||
public function update_question_attempt_metadata(question_attempt $qa, array $names) {
|
||||
global $DB;
|
||||
list($condition, $params) = $DB->get_in_or_equal($names);
|
||||
$params[] = $qa->get_step(0)->get_id();
|
||||
$DB->delete_records_select('question_attempt_step_data',
|
||||
'name ' . $condition . ' AND attemptstepid = ?', $params);
|
||||
return $this->insert_question_attempt_metadata($qa, $names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a {@link question_attempt_step} from the database.
|
||||
*
|
||||
@ -867,6 +909,7 @@ ORDER BY
|
||||
public function update_question_attempt(question_attempt $qa) {
|
||||
$record = new stdClass();
|
||||
$record->id = $qa->get_database_id();
|
||||
$record->slot = $qa->get_slot();
|
||||
$record->variant = $qa->get_variant();
|
||||
$record->maxmark = $qa->get_max_mark();
|
||||
$record->minfraction = $qa->get_min_fraction();
|
||||
@ -880,16 +923,6 @@ ORDER BY
|
||||
$this->db->update_record('question_attempts', $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a question_attempts row to reflect any changes in a question_attempt
|
||||
* (but not any of its steps).
|
||||
* @param question_attempt $qa the question attempt that has been deleted.
|
||||
*/
|
||||
public function delete_question_attempt(question_attempt $qa) {
|
||||
$conditions = array('questionusageid' => $qa->get_usage_id(), 'slot' => $qa->get_slot());
|
||||
$this->db->delete_records('question_attempts', $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a question_usage_by_activity and all its associated
|
||||
*
|
||||
@ -1254,24 +1287,18 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
/** @var boolean whether any of the fields of the usage have been changed. */
|
||||
protected $modified = false;
|
||||
|
||||
/**
|
||||
* @var array list of slot => {@link question_attempt}s that
|
||||
* were already in the usage, and which have been modified.
|
||||
*/
|
||||
protected $attemptsmodified = array();
|
||||
|
||||
/**
|
||||
* @var array list of slot => {@link question_attempt}s that
|
||||
* were already in the usage, and which have been deleted.
|
||||
*/
|
||||
protected $attemptsdeleted = array();
|
||||
|
||||
/**
|
||||
* @var array list of slot => {@link question_attempt}s that
|
||||
* have been added to the usage.
|
||||
*/
|
||||
protected $attemptsadded = array();
|
||||
|
||||
/**
|
||||
* @var array list of slot => {@link question_attempt}s that
|
||||
* were already in the usage, and which have been modified.
|
||||
*/
|
||||
protected $attemptsmodified = array();
|
||||
|
||||
/**
|
||||
* @var array of array(question_attempt_step, question_attempt id, seq number)
|
||||
* of steps that have been added to question attempts in this usage.
|
||||
@ -1290,6 +1317,16 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
*/
|
||||
protected $stepsdeleted = array();
|
||||
|
||||
/**
|
||||
* @var array int slot => string name => question_attempt.
|
||||
*/
|
||||
protected $metadataadded = array();
|
||||
|
||||
/**
|
||||
* @var array int slot => string name => question_attempt.
|
||||
*/
|
||||
protected $metadatamodified = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param question_usage_by_activity $quba the usage to track.
|
||||
@ -1302,6 +1339,10 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
$this->modified = true;
|
||||
}
|
||||
|
||||
public function notify_attempt_added(question_attempt $qa) {
|
||||
$this->attemptsadded[$qa->get_slot()] = $qa;
|
||||
}
|
||||
|
||||
public function notify_attempt_modified(question_attempt $qa) {
|
||||
$slot = $qa->get_slot();
|
||||
if (!array_key_exists($slot, $this->attemptsadded)) {
|
||||
@ -1309,27 +1350,27 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify when attempt deleted
|
||||
*
|
||||
* @see question_usage_observer::notify_attempt_deleted()
|
||||
*/
|
||||
public function notify_attempt_deleted(question_attempt $qa) {
|
||||
$slot = $qa->get_slot();
|
||||
if (!array_key_exists($slot, $this->attemptsadded)) {
|
||||
$this->attemptsdeleted[$slot] = $qa;
|
||||
}
|
||||
}
|
||||
public function notify_attempt_moved(question_attempt $qa, $oldslot) {
|
||||
$newslot = $qa->get_slot();
|
||||
|
||||
/**
|
||||
* Notify when attempt added
|
||||
*
|
||||
* @see question_usage_observer::notify_attempt_added()
|
||||
*/
|
||||
public function notify_attempt_added(question_attempt $qa) {
|
||||
$slot = $qa->get_slot();
|
||||
if (!array_key_exists($slot, $this->attemptsadded)) {
|
||||
$this->attemptsadded[$slot] = $qa;
|
||||
if (array_key_exists($oldslot, $this->attemptsadded)) {
|
||||
unset($this->attemptsadded[$oldslot]);
|
||||
$this->attemptsadded[$newslot] = $qa;
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_key_exists($oldslot, $this->attemptsmodified)) {
|
||||
unset($this->attemptsmodified[$oldslot]);
|
||||
}
|
||||
$this->attemptsmodified[$newslot] = $qa;
|
||||
|
||||
if (array_key_exists($oldslot, $this->metadataadded)) {
|
||||
$this->metadataadded[$newslot] = $this->metadataadded[$oldslot];
|
||||
unset($this->metadataadded[$oldslot]);
|
||||
}
|
||||
if (array_key_exists($oldslot, $this->metadatamodified)) {
|
||||
$this->metadatamodified[$newslot] = $this->metadatamodified[$oldslot];
|
||||
unset($this->metadatamodified[$oldslot]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1407,6 +1448,42 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
$this->stepsdeleted[$stepid] = $step;
|
||||
}
|
||||
|
||||
public function notify_metadata_added(question_attempt $qa, $name) {
|
||||
if (array_key_exists($qa->get_slot(), $this->attemptsadded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->is_step_added($qa->get_step(0)) !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->metadataadded[$qa->get_slot()][$name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->metadataadded[$qa->get_slot()][$name] = $qa;
|
||||
}
|
||||
|
||||
public function notify_metadata_modified(question_attempt $qa, $name) {
|
||||
if (array_key_exists($qa->get_slot(), $this->attemptsadded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->is_step_added($qa->get_step(0)) !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->metadataadded[$qa->get_slot()][$name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->metadatamodified[$qa->get_slot()][$name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->metadatamodified[$qa->get_slot()][$name] = $qa;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param question_attempt_step $step a step
|
||||
* @return int|false if the step is in the list of steps to be added, return
|
||||
@ -1473,8 +1550,8 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
$step, $questionattemptid, $seq, $this->quba->get_owning_context());
|
||||
}
|
||||
|
||||
foreach ($this->attemptsdeleted as $qa) {
|
||||
$dm->delete_question_attempt($qa);
|
||||
foreach ($this->attemptsmodified as $qa) {
|
||||
$dm->update_question_attempt($qa);
|
||||
}
|
||||
|
||||
foreach ($this->attemptsadded as $qa) {
|
||||
@ -1482,18 +1559,31 @@ class question_engine_unit_of_work implements question_usage_observer {
|
||||
$qa, $this->quba->get_owning_context());
|
||||
}
|
||||
|
||||
foreach ($this->attemptsmodified as $qa) {
|
||||
$dm->update_question_attempt($qa);
|
||||
foreach ($this->metadataadded as $info) {
|
||||
$qa = reset($info);
|
||||
$stepdata[] = $dm->insert_question_attempt_metadata($qa, array_keys($info));
|
||||
}
|
||||
|
||||
foreach ($this->metadatamodified as $info) {
|
||||
$qa = reset($info);
|
||||
$stepdata[] = $dm->update_question_attempt_metadata($qa, array_keys($info));
|
||||
}
|
||||
|
||||
if ($this->modified) {
|
||||
$dm->update_questions_usage_by_activity($this->quba);
|
||||
}
|
||||
|
||||
if (!$stepdata) {
|
||||
return;
|
||||
if ($stepdata) {
|
||||
$dm->insert_all_step_data(call_user_func_array('array_merge', $stepdata));
|
||||
}
|
||||
$dm->insert_all_step_data(call_user_func_array('array_merge', $stepdata));
|
||||
|
||||
$this->stepsdeleted = array();
|
||||
$this->stepsmodified = array();
|
||||
$this->stepsadded = array();
|
||||
$this->attemptsdeleted = array();
|
||||
$this->attemptsadded = array();
|
||||
$this->attemptsmodified = array();
|
||||
$this->modified = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,7 +362,7 @@ class question_attempt {
|
||||
/**
|
||||
* Get one of the steps in this attempt.
|
||||
*
|
||||
* @param int $i the step number.
|
||||
* @param int $i the step number, which counts from 0.
|
||||
* @return question_attempt_step
|
||||
*/
|
||||
public function get_step($i) {
|
||||
@ -748,6 +748,30 @@ class question_attempt {
|
||||
return $this->behaviour->summarise_action($step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return one of the bits of metadata for a this question attempt.
|
||||
* @param string $name the name of the metadata variable to return.
|
||||
* @return string the value of that metadata variable.
|
||||
*/
|
||||
public function get_metadata($name) {
|
||||
return $this->get_step(0)->get_metadata_var($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some metadata for this question attempt.
|
||||
* @param string $name the name of the metadata variable to return.
|
||||
* @param string $value the value to set that metadata variable to.
|
||||
*/
|
||||
public function set_metadata($name, $value) {
|
||||
$firststep = $this->get_step(0);
|
||||
if (!$firststep->has_metadata_var($name)) {
|
||||
$this->observer->notify_metadata_added($this, $name);
|
||||
} else if ($value !== $firststep->get_metadata_var($name)) {
|
||||
$this->observer->notify_metadata_modified($this, $name);
|
||||
}
|
||||
$firststep->set_metadata_var($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used by {@link rewrite_pluginfile_urls()} and
|
||||
* {@link rewrite_response_pluginfile_urls()}.
|
||||
@ -931,6 +955,10 @@ class question_attempt {
|
||||
public function start($preferredbehaviour, $variant, $submitteddata = array(),
|
||||
$timestamp = null, $userid = null, $existingstepid = null) {
|
||||
|
||||
if ($this->get_num_steps() > 0) {
|
||||
throw new coding_exception('Cannot start a question that is already started.');
|
||||
}
|
||||
|
||||
// Initialise the behaviour.
|
||||
$this->variant = $variant;
|
||||
if (is_string($preferredbehaviour)) {
|
||||
@ -1266,6 +1294,15 @@ class question_attempt {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the max mark for this question_attempt.
|
||||
* @param float $maxmark the new max mark.
|
||||
*/
|
||||
public function set_max_mark($maxmark) {
|
||||
$this->maxmark = $maxmark;
|
||||
$this->observer->notify_attempt_modified($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a manual grading action on this attempt.
|
||||
* @param string $comment the comment being added.
|
||||
|
@ -370,6 +370,48 @@ class question_attempt_step {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a metadata variable.
|
||||
*
|
||||
* Do not call this method directly from your code. It is for internal
|
||||
* use only. You should call {@link question_usage::set_question_attempt_metadata()}.
|
||||
*
|
||||
* @param string $name the name of the variable to set. [a-z][a-z0-9]*.
|
||||
* @param string $value the value to set.
|
||||
*/
|
||||
public function set_metadata_var($name, $value) {
|
||||
$this->data[':_' . $name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this step has a metadata variable.
|
||||
*
|
||||
* Do not call this method directly from your code. It is for internal
|
||||
* use only. You should call {@link question_usage::get_question_attempt_metadata()}.
|
||||
*
|
||||
* @param string $name the name of the variable to set. [a-z][a-z0-9]*.
|
||||
* @return bool the value to set previously, or null if this variable was never set.
|
||||
*/
|
||||
public function has_metadata_var($name) {
|
||||
return isset($this->data[':_' . $name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a metadata variable.
|
||||
*
|
||||
* Do not call this method directly from your code. It is for internal
|
||||
* use only. You should call {@link question_usage::get_question_attempt_metadata()}.
|
||||
*
|
||||
* @param string $name the name of the variable to set. [a-z][a-z0-9]*.
|
||||
* @return string the value to set previously, or null if this variable was never set.
|
||||
*/
|
||||
public function get_metadata_var($name) {
|
||||
if (!$this->has_metadata_var($name)) {
|
||||
return null;
|
||||
}
|
||||
return $this->data[':_' . $name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a question_attempt_step from records loaded from the database.
|
||||
* @param Iterator $records Raw records loaded from the database.
|
||||
|
@ -172,6 +172,42 @@ class question_usage_by_activity {
|
||||
return $qa->get_slot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another question to this usage, in the place of an existing slot.
|
||||
* The question_attempt that was in that slot is moved to the end at a new
|
||||
* slot number, which is returned.
|
||||
*
|
||||
* The added question is not started until you call {@link start_question()}
|
||||
* on it.
|
||||
*
|
||||
* @param int $slot the slot-number of the question to replace.
|
||||
* @param question_definition $question the question to add.
|
||||
* @param number $maxmark the maximum this question will be marked out of in
|
||||
* this attempt (optional). If not given, the max mark from the $qa we
|
||||
* are replacing is used.
|
||||
* @return int the new slot number of the question that was displaced.
|
||||
*/
|
||||
public function add_question_in_place_of_other($slot, question_definition $question, $maxmark = null) {
|
||||
$newslot = $this->next_slot_number();
|
||||
|
||||
$oldqa = $this->get_question_attempt($slot);
|
||||
$oldqa->set_slot($newslot);
|
||||
$this->questionattempts[$newslot] = $oldqa;
|
||||
|
||||
if ($maxmark === null) {
|
||||
$maxmark = $oldqa->get_max_mark();
|
||||
}
|
||||
|
||||
$qa = new question_attempt($question, $this->get_id(), $this->observer, $maxmark);
|
||||
$qa->set_slot($slot);
|
||||
$this->questionattempts[$slot] = $qa;
|
||||
|
||||
$this->observer->notify_attempt_moved($oldqa, $slot);
|
||||
$this->observer->notify_attempt_added($qa);
|
||||
|
||||
return $newslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* The slot number that will be allotted to the next question added.
|
||||
*/
|
||||
@ -377,6 +413,27 @@ class question_usage_by_activity {
|
||||
return $this->get_question_attempt($slot)->get_right_answer_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return one of the bits of metadata for a particular question attempt in
|
||||
* this usage.
|
||||
* @param int $slot the slot number of the question of inereest.
|
||||
* @param string $name the name of the metadata variable to return.
|
||||
* @return string the value of that metadata variable.
|
||||
*/
|
||||
public function get_question_attempt_metadata($slot, $name) {
|
||||
return $this->get_question_attempt($slot)->get_metadata($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some metadata for a particular question attempt in this usage.
|
||||
* @param int $slot the slot number of the question of inerest.
|
||||
* @param string $name the name of the metadata variable to return.
|
||||
* @param string $value the value to set that metadata variable to.
|
||||
*/
|
||||
public function set_question_attempt_metadata($slot, $name, $value) {
|
||||
$this->get_question_attempt($slot)->set_metadata($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link core_question_renderer}, in collaboration with appropriate
|
||||
* {@link qbehaviour_renderer} and {@link qtype_renderer} subclasses, to generate the
|
||||
@ -822,22 +879,6 @@ class question_usage_by_activity {
|
||||
$this->observer->notify_attempt_modified($newqa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a question in this usage.
|
||||
* @param int $slot the number used to identify this question within this usage.*
|
||||
*/
|
||||
public function replace_question($slot) {
|
||||
global $OUTPUT;
|
||||
$oldqa = $this->get_question_attempt($slot);
|
||||
$newqa = new question_attempt($oldqa->get_question(), $oldqa->get_usage_id(), $this->observer);
|
||||
$newqa->set_database_id($oldqa->get_database_id());
|
||||
$newqa->set_slot($oldqa->get_slot());
|
||||
$this->questionattempts[$slot] = $newqa;
|
||||
$this->observer->notify_attempt_deleted($oldqa);
|
||||
$this->observer->notify_attempt_added($newqa);
|
||||
$this->start_question($slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regrade all the questions in this usage (without changing their max mark).
|
||||
* @param bool $finished whether each question should be forced to be finished
|
||||
@ -849,6 +890,15 @@ class question_usage_by_activity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the max mark for this question_attempt.
|
||||
* @param int $slot the slot number of the question of inerest.
|
||||
* @param float $maxmark the new max mark.
|
||||
*/
|
||||
public function set_max_mark($slot, $maxmark) {
|
||||
$this->get_question_attempt($slot)->set_max_mark($maxmark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a question_usage_by_activity from records loaded from the database.
|
||||
*
|
||||
@ -983,12 +1033,6 @@ interface question_usage_observer {
|
||||
/** Called when a field of the question_usage_by_activity is changed. */
|
||||
public function notify_modified();
|
||||
|
||||
/**
|
||||
* Called when the fields of a question attempt in this usage are modified.
|
||||
* @param question_attempt $qa the newly added question attempt.
|
||||
*/
|
||||
public function notify_attempt_modified(question_attempt $qa);
|
||||
|
||||
/**
|
||||
* Called when a new question attempt is added to this usage.
|
||||
* @param question_attempt $qa the newly added question attempt.
|
||||
@ -996,10 +1040,17 @@ interface question_usage_observer {
|
||||
public function notify_attempt_added(question_attempt $qa);
|
||||
|
||||
/**
|
||||
* Called when the fields of a question attempt in this usage are deleted.
|
||||
* @param question_attempt $qa
|
||||
* Called when the fields of a question attempt in this usage are modified.
|
||||
* @param question_attempt $qa the newly added question attempt.
|
||||
*/
|
||||
public function notify_attempt_deleted(question_attempt $qa);
|
||||
public function notify_attempt_modified(question_attempt $qa);
|
||||
|
||||
/**
|
||||
* Called when a question_attempt has been moved to a new slot.
|
||||
* @param question_attempt $qa The question attempt that was moved.
|
||||
* @param int $oldslot The previous slot number of that attempt.
|
||||
*/
|
||||
public function notify_attempt_moved(question_attempt $qa, $oldslot);
|
||||
|
||||
/**
|
||||
* Called when a new step is added to a question attempt in this usage.
|
||||
@ -1024,6 +1075,19 @@ interface question_usage_observer {
|
||||
*/
|
||||
public function notify_step_deleted(question_attempt_step $step, question_attempt $qa);
|
||||
|
||||
/**
|
||||
* Called when a new metadata variable is set on a question attempt in this usage.
|
||||
* @param question_attempt $qa the question attempt the metadata is being added to.
|
||||
* @param int $name the name of the metadata variable added.
|
||||
*/
|
||||
public function notify_metadata_added(question_attempt $qa, $name);
|
||||
|
||||
/**
|
||||
* Called when a metadata variable on a question attempt in this usage is updated.
|
||||
* @param question_attempt $qa the question attempt where the metadata is being modified.
|
||||
* @param int $name the name of the metadata variable modified.
|
||||
*/
|
||||
public function notify_metadata_modified(question_attempt $qa, $name);
|
||||
}
|
||||
|
||||
|
||||
@ -1037,11 +1101,11 @@ interface question_usage_observer {
|
||||
class question_usage_null_observer implements question_usage_observer {
|
||||
public function notify_modified() {
|
||||
}
|
||||
public function notify_attempt_added(question_attempt $qa) {
|
||||
}
|
||||
public function notify_attempt_modified(question_attempt $qa) {
|
||||
}
|
||||
public function notify_attempt_deleted(question_attempt $qa) {
|
||||
}
|
||||
public function notify_attempt_added(question_attempt $qa) {
|
||||
public function notify_attempt_moved(question_attempt $qa, $oldslot) {
|
||||
}
|
||||
public function notify_step_added(question_attempt_step $step, question_attempt $qa, $seq) {
|
||||
}
|
||||
@ -1049,4 +1113,8 @@ class question_usage_null_observer implements question_usage_observer {
|
||||
}
|
||||
public function notify_step_deleted(question_attempt_step $step, question_attempt $qa) {
|
||||
}
|
||||
public function notify_metadata_added(question_attempt $qa, $name) {
|
||||
}
|
||||
public function notify_metadata_modified(question_attempt $qa, $name) {
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,14 @@ class testable_question_engine_unit_of_work extends question_engine_unit_of_work
|
||||
public function get_steps_deleted() {
|
||||
return $this->stepsdeleted;
|
||||
}
|
||||
|
||||
public function get_metadata_added() {
|
||||
return $this->metadataadded;
|
||||
}
|
||||
|
||||
public function get_metadata_modified() {
|
||||
return $this->metadatamodified;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,8 +91,8 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-submit', 1),
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo', null, 1256233720, 1, '-_triesleft', 1),
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 3, 2, 'todo', null, 1256233740, 1, '-tryagain', 1),
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', null, 1256233790, 1, 'answer', 'frog'),
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 1.0000000, 1256233790, 1, '-submit', 1),
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 0.6666667, 1256233790, 1, 'answer', 'frog'),
|
||||
array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 1.0000000, 0.0000000, 1.0000000, 0, '', '', '', 1256233790, 5, 3, 'gradedright', 0.6666667, 1256233790, 1, '-submit', 1),
|
||||
);
|
||||
}
|
||||
|
||||
@ -103,6 +103,8 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_update_usage() {
|
||||
@ -120,6 +122,9 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
$this->assertEquals(1, count($newattempts));
|
||||
$this->assertTrue($this->quba->get_question_attempt($slot) === reset($newattempts));
|
||||
$this->assertSame($slot, key($newattempts));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_add_and_start_question() {
|
||||
@ -136,6 +141,9 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
$this->assertTrue($this->quba->get_question_attempt($slot) === reset($newattempts));
|
||||
$this->assertSame($slot, key($newattempts));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_process_action() {
|
||||
@ -157,6 +165,9 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
|
||||
list($newstep, $qaid, $seq) = reset($newsteps);
|
||||
$this->assertSame($this->quba->get_question_attempt($this->slot)->get_last_step(), $newstep);
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_regrade_same_steps() {
|
||||
@ -184,6 +195,9 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
$this->assertSame(array($step, $updatedattempt->get_database_id(), $seq),
|
||||
$updatedsteps[$seq]);
|
||||
}
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_regrade_losing_steps() {
|
||||
@ -220,6 +234,9 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
$seconddeletedstep = end($deletedsteps);
|
||||
$this->assertEquals(array('answer' => 'frog', '-submit' => 1),
|
||||
$seconddeletedstep->get_all_data());
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_tricky_regrade() {
|
||||
@ -258,5 +275,237 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
|
||||
}
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_move_question() {
|
||||
|
||||
$q = test_question_maker::make_question('truefalse');
|
||||
$newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
|
||||
$this->quba->start_question($this->slot);
|
||||
|
||||
$addedattempts = $this->observer->get_attempts_added();
|
||||
$this->assertEquals(1, count($addedattempts));
|
||||
$addedattempt = reset($addedattempts);
|
||||
$this->assertSame($this->quba->get_question_attempt($this->slot), $addedattempt);
|
||||
|
||||
$updatedattempts = $this->observer->get_attempts_modified();
|
||||
$this->assertEquals(1, count($updatedattempts));
|
||||
$updatedattempt = reset($updatedattempts);
|
||||
$this->assertSame($this->quba->get_question_attempt($newslot), $updatedattempt);
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_move_question_then_modify() {
|
||||
|
||||
$q = test_question_maker::make_question('truefalse');
|
||||
$newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
|
||||
$this->quba->start_question($this->slot);
|
||||
$this->quba->process_action($this->slot, array('answer' => 'frog', '-submit' => 1));
|
||||
$this->quba->manual_grade($newslot, 'Test', 0.5, FORMAT_HTML);
|
||||
|
||||
$addedattempts = $this->observer->get_attempts_added();
|
||||
$this->assertEquals(1, count($addedattempts));
|
||||
$addedattempt = reset($addedattempts);
|
||||
$this->assertSame($this->quba->get_question_attempt($this->slot), $addedattempt);
|
||||
|
||||
$updatedattempts = $this->observer->get_attempts_modified();
|
||||
$this->assertEquals(1, count($updatedattempts));
|
||||
$updatedattempt = reset($updatedattempts);
|
||||
$this->assertSame($this->quba->get_question_attempt($newslot), $updatedattempt);
|
||||
|
||||
$newsteps = $this->observer->get_steps_added();
|
||||
$this->assertEquals(1, count($newsteps));
|
||||
list($newstep, $qaid, $seq) = reset($newsteps);
|
||||
$this->assertSame($this->quba->get_question_attempt($newslot)->get_last_step(), $newstep);
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_move_question_then_move_again() {
|
||||
$originalqa = $this->quba->get_question_attempt($this->slot);
|
||||
|
||||
$q1 = test_question_maker::make_question('truefalse');
|
||||
$newslot = $this->quba->add_question_in_place_of_other($this->slot, $q1);
|
||||
$this->quba->start_question($this->slot);
|
||||
|
||||
$q2 = test_question_maker::make_question('truefalse');
|
||||
$newslot2 = $this->quba->add_question_in_place_of_other($newslot, $q2);
|
||||
$this->quba->start_question($newslot);
|
||||
|
||||
$addedattempts = $this->observer->get_attempts_added();
|
||||
$this->assertEquals(2, count($addedattempts));
|
||||
|
||||
$updatedattempts = $this->observer->get_attempts_modified();
|
||||
$this->assertEquals(1, count($updatedattempts));
|
||||
$updatedattempt = reset($updatedattempts);
|
||||
$this->assertSame($originalqa, $updatedattempt);
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_set_max_mark() {
|
||||
$this->quba->set_max_mark($this->slot, 6.0);
|
||||
$this->assertEquals(4.0, $this->quba->get_total_mark(), '', 0.0000005);
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_added()));
|
||||
|
||||
$updatedattempts = $this->observer->get_attempts_modified();
|
||||
$this->assertEquals(1, count($updatedattempts));
|
||||
$updatedattempt = reset($updatedattempts);
|
||||
$this->assertSame($this->quba->get_question_attempt($this->slot), $updatedattempt);
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_set_question_attempt_metadata() {
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
|
||||
$this->assertEquals('a value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_modified()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(array($this->slot => array('metathingy' => $this->quba->get_question_attempt($this->slot))),
|
||||
$this->observer->get_metadata_added());
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_set_question_attempt_metadata_then_change() {
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'different value');
|
||||
$this->assertEquals('different value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_modified()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(array($this->slot => array('metathingy' => $this->quba->get_question_attempt($this->slot))),
|
||||
$this->observer->get_metadata_added());
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_set_metadata_previously_set_but_dont_actually_change() {
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
|
||||
$this->observer = new testable_question_engine_unit_of_work($this->quba);
|
||||
$this->quba->set_observer($this->observer);
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
|
||||
$this->assertEquals('a value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_modified()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_set_metadata_previously_set() {
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
|
||||
$this->observer = new testable_question_engine_unit_of_work($this->quba);
|
||||
$this->quba->set_observer($this->observer);
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'different value');
|
||||
$this->assertEquals('different value', $this->quba->get_question_attempt_metadata($this->slot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_modified()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(array($this->slot => array('metathingy' => $this->quba->get_question_attempt($this->slot))),
|
||||
$this->observer->get_metadata_modified());
|
||||
}
|
||||
|
||||
public function test_set_metadata_in_new_question() {
|
||||
$newslot = $this->quba->add_question(test_question_maker::make_question('truefalse'));
|
||||
$this->quba->start_question($newslot);
|
||||
$this->quba->set_question_attempt_metadata($newslot, 'metathingy', 'a value');
|
||||
$this->assertEquals('a value', $this->quba->get_question_attempt_metadata($newslot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(array($newslot => $this->quba->get_question_attempt($newslot)),
|
||||
$this->observer->get_attempts_added());
|
||||
$this->assertEquals(0, count($this->observer->get_attempts_modified()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_set_metadata_then_move() {
|
||||
$this->quba->set_question_attempt_metadata($this->slot, 'metathingy', 'a value');
|
||||
$q = test_question_maker::make_question('truefalse');
|
||||
$newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
|
||||
$this->quba->start_question($this->slot);
|
||||
$this->assertEquals('a value', $this->quba->get_question_attempt_metadata($newslot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(array($this->slot => $this->quba->get_question_attempt($this->slot)),
|
||||
$this->observer->get_attempts_added());
|
||||
$this->assertEquals(array($newslot => $this->quba->get_question_attempt($newslot)),
|
||||
$this->observer->get_attempts_modified());
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(array($newslot => array('metathingy' => $this->quba->get_question_attempt($newslot))),
|
||||
$this->observer->get_metadata_added());
|
||||
$this->assertEquals(0, count($this->observer->get_metadata_modified()));
|
||||
}
|
||||
|
||||
public function test_move_then_set_metadata() {
|
||||
$q = test_question_maker::make_question('truefalse');
|
||||
$newslot = $this->quba->add_question_in_place_of_other($this->slot, $q);
|
||||
$this->quba->start_question($this->slot);
|
||||
$this->quba->set_question_attempt_metadata($newslot, 'metathingy', 'a value');
|
||||
$this->assertEquals('a value', $this->quba->get_question_attempt_metadata($newslot, 'metathingy'));
|
||||
|
||||
$this->assertEquals(array($this->slot => $this->quba->get_question_attempt($this->slot)),
|
||||
$this->observer->get_attempts_added());
|
||||
$this->assertEquals(array($newslot => $this->quba->get_question_attempt($newslot)),
|
||||
$this->observer->get_attempts_modified());
|
||||
|
||||
$this->assertEquals(0, count($this->observer->get_steps_added()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_modified()));
|
||||
$this->assertEquals(0, count($this->observer->get_steps_deleted()));
|
||||
|
||||
$this->assertEquals(array($newslot => array('metathingy' => $this->quba->get_question_attempt($newslot))),
|
||||
$this->observer->get_metadata_added());
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,47 @@
|
||||
This files describes API changes for the core question system.
|
||||
This files describes API changes for the core question engine.
|
||||
|
||||
|
||||
=== 2.9 ===
|
||||
|
||||
1) Some new methods on the question_usage class (and corresponding methods on
|
||||
question_attempt, question_attempt_step, question_usage_observer, ... requried
|
||||
to implement them, but almost certainly you should only be calling the
|
||||
question_usage methods from your code.
|
||||
|
||||
* question_usage::add_question_in_place_of_other($slot, $question, $maxmark = null)
|
||||
|
||||
This creates a new questoin_attempt in place of an existing one, moving the
|
||||
existing question_attempt to the end of the usage, in a new slot number.
|
||||
The new slot number is returned. The goal is to replace the old attempt, but
|
||||
not lose the old data.
|
||||
|
||||
* question_usage::set_question_max_mark($slot, $maxmark)
|
||||
|
||||
Sets the max mark for one question in this usage. Previously, you could
|
||||
only change this using the bulk operation question_usage::set_max_mark_in_attempts;
|
||||
|
||||
* question_usage::set_question_attempt_metadata($slot, $name, $value);
|
||||
question_usage::get_question_attempt_metadata($slot, $name);
|
||||
|
||||
You can now record metadata, that is, values stored by name, against
|
||||
question_attempts. The question engine ignores this data (other than storing
|
||||
and loading it) but you may find it useful in your code.
|
||||
|
||||
To see examples of where these are used, look at the chagnes from MDL-40992.
|
||||
|
||||
|
||||
=== 2.6 ===
|
||||
|
||||
1) The method question_behaviour::is_manual_grade_in_range and move and become
|
||||
question_engine::is_manual_grade_in_range.
|
||||
question_engine::is_manual_grade_in_range.
|
||||
|
||||
2) The arguments to core_question_renderer::mark_summary changed from
|
||||
($qa, $options) to ($qa, $behaviouroutput, $options). If you have overridden
|
||||
that method you will need to update your code.
|
||||
($qa, $options) to ($qa, $behaviouroutput, $options). If you have overridden
|
||||
that method you will need to update your code.
|
||||
|
||||
3) Heading level for number(), add_part_heading() and respond_history()
|
||||
has been lowered by one level. These changes are part of improving the page
|
||||
accessibility and making heading to have proper nesting. (MDL-41615)
|
||||
has been lowered by one level. These changes are part of improving the page
|
||||
accessibility and making heading to have proper nesting. (MDL-41615)
|
||||
|
||||
=== Earlier changes ===
|
||||
|
||||
|
@ -22,7 +22,7 @@ This files describes API changes for code that uses the question API.
|
||||
|
||||
=== 2.8 ===
|
||||
|
||||
1) This is jsut a warning that some methods of the question_engine_data_mapper
|
||||
1) This is just a warning that some methods of the question_engine_data_mapper
|
||||
class have changed. All these methods are ones that you should not have been
|
||||
calling directly from your code, so this should not cause any problems.
|
||||
The changed methods are:
|
||||
|
Loading…
x
Reference in New Issue
Block a user