Merge branch 'MDL-67183' of git://github.com/timhunt/moodle

This commit is contained in:
Víctor Déniz Falcón 2020-05-20 15:54:14 +01:00
commit 7380c31afe
26 changed files with 160 additions and 52 deletions

View File

@ -1162,7 +1162,7 @@ class quiz_attempt {
return $options;
}
$question = $this->quba->get_question($slot);
$question = $this->quba->get_question($slot, false);
if (!question_has_capability_on($question, 'edit', $question->category)) {
return $options;
}
@ -1267,7 +1267,7 @@ class quiz_attempt {
* question length, which could theoretically be greater than one.
*/
public function is_real_question($slot) {
return $this->quba->get_question($slot)->length;
return $this->quba->get_question($slot, false)->length;
}
/**
@ -1368,7 +1368,7 @@ class quiz_attempt {
* by the quiz.
*/
public function get_question_name($slot) {
return $this->quba->get_question($slot)->name;
return $this->quba->get_question($slot, false)->name;
}
/**
@ -1441,7 +1441,7 @@ class quiz_attempt {
* @since Moodle 3.1
*/
public function get_question_type_name($slot) {
return $this->quba->get_question($slot)->get_type_name();
return $this->quba->get_question($slot, false)->get_type_name();
}
/**
@ -1767,7 +1767,7 @@ class quiz_attempt {
* @return question_attempt the placeholder question attempt.
*/
protected function make_blocked_question_placeholder($slot) {
$replacedquestion = $this->get_question_attempt($slot)->get_question();
$replacedquestion = $this->get_question_attempt($slot)->get_question(false);
question_bank::load_question_definition_classes('description');
$question = new qtype_description_question();
@ -2690,7 +2690,7 @@ abstract class quiz_nav_panel_base {
}
protected function get_state_string(question_attempt $qa, $showcorrectness) {
if ($qa->get_question()->length > 0) {
if ($qa->get_question(false)->length > 0) {
return $qa->get_state_string($showcorrectness);
}

View File

@ -87,7 +87,7 @@ if (data_submitted() && confirm_sesskey()) {
// Log this action.
$params = array(
'objectid' => $attemptobj->get_question_attempt($slot)->get_question()->id,
'objectid' => $attemptobj->get_question_attempt($slot)->get_question_id(),
'courseid' => $attemptobj->get_courseid(),
'context' => context_module::instance($attemptobj->get_cmid()),
'other' => array(

View File

@ -316,7 +316,7 @@ function quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt) {
$oldnumberstonew = array();
foreach ($oldquba->get_attempt_iterator() as $oldslot => $oldqa) {
$newslot = $quba->add_question($oldqa->get_question(), $oldqa->get_max_mark());
$newslot = $quba->add_question($oldqa->get_question(false), $oldqa->get_max_mark());
$quba->start_question_based_on($newslot, $oldqa);

View File

@ -541,7 +541,7 @@ class quiz_grading_report extends quiz_default_report {
// Add the event we will trigger later.
$params = [
'objectid' => $attemptobj->get_question_attempt($assumedslotforevents)->get_question()->id,
'objectid' => $attemptobj->get_question_attempt($assumedslotforevents)->get_question_id(),
'courseid' => $attemptobj->get_courseid(),
'context' => context_module::instance($attemptobj->get_cmid()),
'other' => [

View File

@ -141,7 +141,7 @@ class quiz_first_or_all_responses_table extends quiz_last_responses_table {
*/
public function get_summary_after_try($tablerow, $slot) {
$qa = $this->get_question_attempt($tablerow->usageid, $slot);
if (!($qa->get_question() instanceof question_manually_gradable)) {
if (!($qa->get_question(false) instanceof question_manually_gradable)) {
// No responses, and we cannot call summarise_response below.
return null;
}

View File

@ -187,7 +187,7 @@ class mod_quiz_attempt_walkthrough_testcase extends advanced_testcase {
$this->assertFalse($attemptobj->has_response_to_at_least_one_graded_question());
$tosubmit = array();
$selectedquestionid = $quba->get_question_attempt(1)->get_question()->id;
$selectedquestionid = $quba->get_question_attempt(1)->get_question_id();
$tosubmit[1] = array('answer' => $randqanswer);
$tosubmit[2] = array(
'frog' => 'amphibian',

View File

@ -58,7 +58,7 @@ abstract class question_behaviour {
*/
public function __construct(question_attempt $qa, $preferredbehaviour) {
$this->qa = $qa;
$this->question = $qa->get_question();
$this->question = $qa->get_question(false);
if (!$this->is_compatible_question($this->question)) {
throw new coding_exception('This behaviour (' . $this->get_name() .
') cannot work with this question (' . get_class($this->question) . ')');

View File

@ -117,7 +117,7 @@ class qbehaviour_missing_test extends advanced_testcase {
$this->assertEquals(array('-submit' => '1', 'choice0' => '1'), $step->get_all_data());
$output = $qa->render(new question_display_options(), '1');
$this->assertRegExp('/' . preg_quote($qa->get_question()->questiontext, '/') . '/', $output);
$this->assertRegExp('/' . preg_quote($qa->get_question(false)->questiontext, '/') . '/', $output);
$this->assertRegExp('/' . preg_quote(
get_string('questionusedunknownmodel', 'qbehaviour_missing'), '/') . '/', $output);
$this->assertTag(array('tag'=>'div', 'attributes'=>array('class'=>'warning')), $output);

View File

@ -188,7 +188,8 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
public function manual_comment_view(question_attempt $qa, question_display_options $options) {
$output = '';
if ($qa->has_manual_comment()) {
$output .= get_string('commentx', 'question', $qa->get_behaviour()->format_comment(null, null, $options->context));
$output .= get_string('commentx', 'question',
$qa->get_behaviour(false)->format_comment(null, null, $options->context));
}
if ($options->manualcommentlink) {
$url = new moodle_url($options->manualcommentlink, array('slot' => $qa->get_slot()));

View File

@ -165,7 +165,7 @@ class provider implements
]);
foreach ($quba->get_attempt_iterator() as $qa) {
$question = $qa->get_question();
$question = $qa->get_question(false);
$slotno = $qa->get_slot();
$questionnocontext = array_merge($questionscontext, [$slotno]);
@ -197,7 +197,6 @@ class provider implements
}
if ($options->manualcomment != \question_display_options::HIDDEN) {
$behaviour = $qa->get_behaviour();
if ($qa->has_manual_comment()) {
// Note - the export of the step data will ensure that the files are exported.
// No need to do it again here.
@ -211,7 +210,7 @@ class provider implements
$step->get_id(),
$comment
);
$data->comment = $behaviour->format_comment($comment, $commentformat);
$data->comment = $qa->get_behaviour(false)->format_comment($comment, $commentformat);
}
}
@ -272,7 +271,6 @@ class provider implements
}
if ($step->has_behaviour_var('comment')) {
$behaviour = $qa->get_behaviour();
$comment = $step->get_behaviour_var('comment');
$commentformat = $step->get_behaviour_var('commentformat');
@ -300,7 +298,7 @@ class provider implements
$step->get_id()
);
$stepdata->comment = $behaviour->format_comment($comment, $commentformat);
$stepdata->comment = $qa->get_behaviour(false)->format_comment($comment, $commentformat);
}
// Export any response files associated with this step.

View File

@ -118,7 +118,7 @@ class question_engine_data_mapper {
$record->questionusageid = $qa->get_usage_id();
$record->slot = $qa->get_slot();
$record->behaviour = $qa->get_behaviour_name();
$record->questionid = $qa->get_question()->id;
$record->questionid = $qa->get_question_id();
$record->variant = $qa->get_variant();
$record->maxmark = $qa->get_max_mark();
$record->minfraction = $qa->get_min_fraction();

View File

@ -696,7 +696,7 @@ abstract class question_flags {
public static function get_postdata(question_attempt $qa) {
$qaid = $qa->get_database_id();
$qubaid = $qa->get_usage_id();
$qid = $qa->get_question()->id;
$qid = $qa->get_question_id();
$slot = $qa->get_slot();
$checksum = self::get_toggle_checksum($qubaid, $qid, $qaid, $slot);
return "qaid={$qaid}&qubaid={$qubaid}&qid={$qid}&slot={$slot}&checksum={$checksum}&sesskey=" .

View File

@ -68,19 +68,32 @@ class question_attempt {
/**
* @var string means first try at a question during an attempt by a user.
* Constant used when calling classify response.
*/
const FIRST_TRY = 'firsttry';
/**
* @var string means last try at a question during an attempt by a user.
* Constant used when calling classify response.
*/
const LAST_TRY = 'lasttry';
/**
* @var string means all tries at a question during an attempt by a user.
* Constant used when calling classify response.
*/
const ALL_TRIES = 'alltries';
/**
* @var bool used to manage the lazy-initialisation of question objects.
*/
const QUESTION_STATE_NOT_APPLIED = false;
/**
* @var bool used to manage the lazy-initialisation of question objects.
*/
const QUESTION_STATE_APPLIED = true;
/** @var integer if this attempts is stored in the question_attempts table, the id of that row. */
protected $id = null;
@ -99,6 +112,12 @@ class question_attempt {
/** @var question_definition the question this is an attempt at. */
protected $question;
/**
* @var bool tracks whether $question has had {@link question_definition::start_attempt()} or
* {@link question_definition::apply_attempt_state()} called.
*/
protected $questioninitialised;
/** @var int which variant of the question to use. */
protected $variant;
@ -184,6 +203,7 @@ class question_attempt {
public function __construct(question_definition $question, $usageid,
question_usage_observer $observer = null, $maxmark = null) {
$this->question = $question;
$this->questioninitialised = self::QUESTION_STATE_NOT_APPLIED;
$this->usageid = $usageid;
if (is_null($observer)) {
$observer = new question_usage_null_observer();
@ -205,11 +225,29 @@ class question_attempt {
return $this;
}
/** @return question_definition the question this is an attempt at. */
public function get_question() {
/**
* Get the question that is being attempted.
*
* @param bool $requirequestioninitialised set this to false if you don't need
* the behaviour initialised, which may improve performance.
* @return question_definition the question this is an attempt at.
*/
public function get_question($requirequestioninitialised = true) {
if ($requirequestioninitialised && !empty($this->steps)) {
$this->ensure_question_initialised();
}
return $this->question;
}
/**
* Get the id of the question being attempted.
*
* @return int question id.
*/
public function get_question_id() {
return $this->question->id;
}
/**
* Get the variant of the question being used in a given slot.
* @return int the variant number.
@ -279,9 +317,15 @@ class question_attempt {
/**
* For internal use only.
*
* @param bool $requirequestioninitialised set this to false if you don't need
* the behaviour initialised, which may improve performance.
* @return question_behaviour the behaviour that is controlling this attempt.
*/
public function get_behaviour() {
public function get_behaviour($requirequestioninitialised = true) {
if ($requirequestioninitialised && !empty($this->steps)) {
$this->ensure_question_initialised();
}
return $this->behaviour;
}
@ -769,10 +813,11 @@ class question_attempt {
/**
* Produce a plain-text summary of what the user did during a step.
* @param question_attempt_step $step the step in quetsion.
* @param question_attempt_step $step the step in question.
* @return string a summary of what was done during that step.
*/
public function summarise_action(question_attempt_step $step) {
$this->ensure_question_initialised();
return $this->behaviour->summarise_action($step);
}
@ -852,6 +897,7 @@ class question_attempt {
* @return string HTML fragment representing the question.
*/
public function render($options, $number, $page = null) {
$this->ensure_question_initialised();
if (is_null($page)) {
global $PAGE;
$page = $PAGE;
@ -867,6 +913,7 @@ class question_attempt {
* @return string HTML fragment.
*/
public function render_head_html($page = null) {
$this->ensure_question_initialised();
if (is_null($page)) {
global $PAGE;
$page = $PAGE;
@ -889,6 +936,7 @@ class question_attempt {
* @return string HTML fragment representing the question.
*/
public function render_at_step($seq, $options, $number, $preferredbehaviour) {
$this->ensure_question_initialised();
$restrictedqa = new question_attempt_with_restricted_history($this, $seq, $preferredbehaviour);
return $restrictedqa->render($options, $number);
}
@ -903,6 +951,7 @@ class question_attempt {
* @return bool true if the user can access this file.
*/
public function check_file_access($options, $component, $filearea, $args, $forcedownload) {
$this->ensure_question_initialised();
return $this->behaviour->check_file_access($options, $component, $filearea, $args, $forcedownload);
}
@ -976,7 +1025,7 @@ class question_attempt {
* {@link question_usage_by_activity::start_question()} instead.
*
* @param string|question_behaviour $preferredbehaviour the name of the
* desired archetypal behaviour, or an actual model instance.
* desired archetypal behaviour, or an actual behaviour instance.
* @param int $variant the variant of the question to start. Between 1 and
* $this->get_question()->get_num_variants() inclusive.
* @param array $submitteddata optional, used when re-starting to keep the same initial state.
@ -1014,6 +1063,7 @@ class question_attempt {
} else {
$this->behaviour->init_first_step($firststep, $variant);
}
$this->questioninitialised = self::QUESTION_STATE_APPLIED;
$this->add_step($firststep);
// Record questionline and correct answer.
@ -1041,6 +1091,7 @@ class question_attempt {
* @return array name => value pairs.
*/
protected function get_resume_data() {
$this->ensure_question_initialised();
$resumedata = $this->behaviour->get_resume_data();
foreach ($resumedata as $name => $value) {
if ($value instanceof question_file_loader) {
@ -1195,6 +1246,8 @@ class question_attempt {
* @return array name => value pairs that could be passed to {@link process_action()}.
*/
public function get_submitted_data($postdata = null) {
$this->ensure_question_initialised();
$submitteddata = $this->get_expected_data(
$this->behaviour->get_expected_data(), $postdata, '-');
@ -1233,6 +1286,7 @@ class question_attempt {
* @return array|null name => value pairs that could be passed to {@link process_action()}.
*/
public function get_correct_response() {
$this->ensure_question_initialised();
$response = $this->question->get_correct_response();
if (is_null($response)) {
return null;
@ -1283,6 +1337,7 @@ class question_attempt {
* @return boolean whether this attempt can finish naturally.
*/
public function can_finish_during_attempt() {
$this->ensure_question_initialised();
return $this->behaviour->can_finish_during_attempt();
}
@ -1294,6 +1349,7 @@ class question_attempt {
* @param int $existingstepid used by the regrade code.
*/
public function process_action($submitteddata, $timestamp = null, $userid = null, $existingstepid = null) {
$this->ensure_question_initialised();
$pendingstep = new question_attempt_pending_step($submitteddata, $timestamp, $userid, $existingstepid);
$this->discard_autosaved_step();
if ($this->behaviour->process_action($pendingstep) == self::KEEP) {
@ -1315,6 +1371,7 @@ class question_attempt {
* @return bool whether anything was saved.
*/
public function process_autosave($submitteddata, $timestamp = null, $userid = null) {
$this->ensure_question_initialised();
$pendingstep = new question_attempt_pending_step($submitteddata, $timestamp, $userid);
if ($this->behaviour->process_autosave($pendingstep) == self::KEEP) {
$this->add_autosaved_step($pendingstep);
@ -1333,6 +1390,7 @@ class question_attempt {
* @param int $userid the user to attribute the aciton to. (If not given, use the current user.)
*/
public function finish($timestamp = null, $userid = null) {
$this->ensure_question_initialised();
$this->convert_autosaved_step_to_real_step();
$this->process_action(array('-finish' => 1), $timestamp, $userid);
}
@ -1345,6 +1403,7 @@ class question_attempt {
* after the regrade, or whether it may still be in progress (default false).
*/
public function regrade(question_attempt $oldqa, $finished) {
$oldqa->ensure_question_initialised();
$first = true;
foreach ($oldqa->get_step_iterator() as $step) {
$this->observer->notify_step_deleted($step, $this);
@ -1404,6 +1463,7 @@ class question_attempt {
* @param int $userid the user to attribute the aciton to. (If not given, use the current user.)
*/
public function manual_grade($comment, $mark, $commentformat = null, $timestamp = null, $userid = null) {
$this->ensure_question_initialised();
$submitteddata = array('-comment' => $comment);
if (is_null($commentformat)) {
debugging('You should pass $commentformat to manual_grade.', DEBUG_DEVELOPER);
@ -1476,6 +1536,7 @@ class question_attempt {
* and the second key is subpartid.
*/
public function classify_response($whichtries = self::LAST_TRY) {
$this->ensure_question_initialised();
return $this->behaviour->classify_response($whichtries);
}
@ -1541,7 +1602,8 @@ class question_attempt {
$autosavedsequencenumber = null;
while ($record && $record->questionattemptid == $questionattemptid && !is_null($record->attemptstepid)) {
$sequencenumber = $record->sequencenumber;
$nextstep = question_attempt_step::load_from_records($records, $record->attemptstepid, $qa->get_question()->get_type_name());
$nextstep = question_attempt_step::load_from_records($records, $record->attemptstepid,
$qa->get_question(false)->get_type_name());
if ($sequencenumber < 0) {
if (!$autosavedstep) {
@ -1553,9 +1615,6 @@ class question_attempt {
}
} else {
$qa->steps[$i] = $nextstep;
if ($i == 0) {
$question->apply_attempt_state($qa->steps[0]);
}
$i++;
}
@ -1578,6 +1637,26 @@ class question_attempt {
return $qa;
}
/**
* This method is part of the lazy-initialisation of question objects.
*
* Methods which require $this->question to be fully initialised
* (to have had init_first_step or apply_attempt_state called on it)
* should call this method before proceeding.
*/
protected function ensure_question_initialised() {
if ($this->questioninitialised === self::QUESTION_STATE_APPLIED) {
return; // Already done.
}
if (empty($this->steps)) {
throw new coding_exception('You must call start() before doing anything to a question_attempt().');
}
$this->question->apply_attempt_state($this->steps[0]);
$this->questioninitialised = self::QUESTION_STATE_APPLIED;
}
/**
* Allow access to steps with responses submitted by students for grading in a question attempt.
*

View File

@ -69,7 +69,7 @@ class question_usage_by_activity {
/** @var string plugin name of the plugin this usage belongs to. */
protected $owningcomponent;
/** @var array {@link question_attempt}s that make up this usage. */
/** @var question_attempt[] {@link question_attempt}s that make up this usage. */
protected $questionattempts = array();
/** @var question_usage_observer that tracks changes to this usage. */
@ -218,10 +218,12 @@ class question_usage_by_activity {
/**
* Get the question_definition for a question in this attempt.
* @param int $slot the number used to identify this question within this usage.
* @param bool $requirequestioninitialised set this to false if you don't need
* the behaviour initialised, which may improve performance.
* @return question_definition the requested question object.
*/
public function get_question($slot) {
return $this->get_question_attempt($slot)->get_question();
public function get_question($slot, $requirequestioninitialised = true) {
return $this->get_question_attempt($slot)->get_question($requirequestioninitialised);
}
/** @return array all the identifying numbers of all the questions in this usage. */
@ -887,7 +889,7 @@ class question_usage_by_activity {
$newmaxmark = $oldqa->get_max_mark();
}
$newqa = new question_attempt($oldqa->get_question(), $oldqa->get_usage_id(),
$newqa = new question_attempt($oldqa->get_question(false), $oldqa->get_usage_id(),
$this->observer, $newmaxmark);
$newqa->set_database_id($oldqa->get_database_id());
$newqa->set_slot($oldqa->get_slot());

View File

@ -92,7 +92,7 @@ class core_question_renderer extends plugin_renderer_base {
'id' => $qa->get_outer_question_div_unique_id(),
'class' => implode(' ', array(
'que',
$qa->get_question()->qtype->name(),
$qa->get_question(false)->get_type_name(),
$qa->get_behaviour_name(),
$qa->get_state_class($options->correctness && $qa->has_marks()),
))
@ -355,7 +355,7 @@ class core_question_renderer extends plugin_renderer_base {
if ($params['returnurl'] instanceof moodle_url) {
$params['returnurl'] = $params['returnurl']->out_as_local_url(false);
}
$params['id'] = $qa->get_question()->id;
$params['id'] = $qa->get_question_id();
$editurl = new moodle_url('/question/question.php', $params);
return html_writer::tag('div', html_writer::link(

View File

@ -66,7 +66,7 @@ class question_attempt_db_test extends data_loading_method_test_base {
$qa = question_attempt::load_from_records($records, 1, new question_usage_null_observer(), 'deferredfeedback');
question_bank::end_unit_test();
$this->assertEquals($question->questiontext, $qa->get_question()->questiontext);
$this->assertEquals($question->questiontext, $qa->get_question(false)->questiontext);
$this->assertEquals(6, $qa->get_num_steps());
@ -129,7 +129,7 @@ class question_attempt_db_test extends data_loading_method_test_base {
question_bank::end_unit_test();
$missingq = question_bank::get_qtype('missingtype')->make_deleted_instance(-1, 2);
$this->assertEquals($missingq, $qa->get_question());
$this->assertEquals($missingq, $qa->get_question(false));
$this->assertEquals(1, $qa->get_num_steps());
@ -162,7 +162,7 @@ class question_attempt_db_test extends data_loading_method_test_base {
$qa = question_attempt::load_from_records($records, 1, new question_usage_null_observer(), 'deferredfeedback');
question_bank::end_unit_test();
$this->assertEquals($question->questiontext, $qa->get_question()->questiontext);
$this->assertEquals($question->questiontext, $qa->get_question(false)->questiontext);
$this->assertEquals(4, $qa->get_num_steps());
$this->assertTrue($qa->has_autosaved_step());
@ -224,7 +224,7 @@ class question_attempt_db_test extends data_loading_method_test_base {
$qa = question_attempt::load_from_records($records, 1, $observer, 'deferredfeedback');
question_bank::end_unit_test();
$this->assertEquals($question->questiontext, $qa->get_question()->questiontext);
$this->assertEquals($question->questiontext, $qa->get_question(false)->questiontext);
$this->assertEquals(3, $qa->get_num_steps());
$this->assertFalse($qa->has_autosaved_step());

View File

@ -65,7 +65,7 @@ class question_attempt_testcase extends advanced_testcase {
public function test_constructor_sets_maxmark() {
$qa = new question_attempt($this->question, $this->usageid);
$this->assertSame($this->question, $qa->get_question());
$this->assertSame($this->question, $qa->get_question(false));
$this->assertEquals(3, $qa->get_max_mark());
}

View File

@ -66,7 +66,7 @@ class question_usage_db_test extends data_loading_method_test_base {
$qa = $quba->get_question_attempt(1);
$this->assertEquals($question->questiontext, $qa->get_question()->questiontext);
$this->assertEquals($question->questiontext, $qa->get_question(false)->questiontext);
$this->assertEquals(3, $qa->get_num_steps());

View File

@ -92,10 +92,10 @@ class question_usage_by_activity_test extends advanced_testcase {
$slot = $quba->add_question($tf);
// Exercise SUT and verify.
$this->assertSame($tf, $quba->get_question($slot));
$this->assertSame($tf, $quba->get_question($slot, false));
$this->expectException('moodle_exception');
$quba->get_question($slot + 1);
$quba->get_question($slot + 1, false);
}
public function test_extract_responses() {

View File

@ -173,7 +173,7 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
public function test_regrade_same_steps() {
// Change the question in a minor way and regrade.
$this->quba->get_question($this->slot)->answers[14]->fraction = 0.5;
$this->quba->get_question($this->slot, false)->answers[14]->fraction = 0.5;
$this->quba->regrade_all_questions();
// Here, the qa, and all the steps, should be marked as updated.
@ -205,7 +205,7 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
// Change the question so that 'toad' is also right, and regrade. This
// will mean that the try again, and second try states are no longer
// needed, so they should be dropped.
$this->quba->get_question($this->slot)->answers[14]->fraction = 1;
$this->quba->get_question($this->slot, false)->answers[14]->fraction = 1;
$this->quba->regrade_all_questions();
$this->assertEquals(0, count($this->observer->get_attempts_added()));
@ -253,7 +253,7 @@ class question_engine_unit_of_work_test extends data_loading_method_test_base {
$this->quba->process_action($this->slot, array('answer' => 'frog', '-submit' => 1));
$this->quba->finish_all_questions();
$this->quba->get_question($this->slot)->answers[14]->fraction = 1;
$this->quba->get_question($this->slot, false)->answers[14]->fraction = 1;
$this->quba->regrade_all_questions();
$this->assertEquals(0, count($this->observer->get_attempts_added()));

View File

@ -1,5 +1,33 @@
This files describes API changes for the core question engine.
=== 3.9 ===
1) In the past, whenever a question_usage_by_activity was loaded from the database,
the apply_attempt_state was immediately called on every question, whether the
results of doing that were ever used, or not.
Now we have changed the code flow, so that apply_attempt_state is only called
when some data or processing is requested (e.g. analysing a response or rendering
the question) which requires the question to be fully initialised. This is MDL-67183.
This change should be completely invisible with everything handled by the question
engine. If you don't change your code, it should continue to work.
However, to get the full advantage of this change, you should review your code,
and look at every call to get_question or get_behaviour (on a question_attempt or
question_usage_by_activity). The problem with these methods is that the question engine
cannot know what you are planning to do with the question once you have got it.
Therefore, they have to assume that apply_attempt_state must be called - which can be expensive.
If you know that you don't need that (because, for example, you are just going to
look at ->id or ->questiontext or something simple) then you should pass
false to these functions, to get the possible performance benefit.
In addition, there is a new method $qa->get_question_id() to handle that case more simply.
Note that you don't have worry about this in places like the renderer for your question
type, because by the time you are in the renderer, the question will already have been
initialised.
=== 3.7 ===
1) When a question is rendered, the outer div of the question has an id="q123"

View File

@ -95,7 +95,7 @@ if ($previewid) {
}
$slot = $quba->get_first_question_number();
$usedquestion = $quba->get_question($slot);
$usedquestion = $quba->get_question($slot, false);
if ($usedquestion->id != $question->id) {
print_error('questionidmismatch', 'question');
}

View File

@ -240,7 +240,7 @@ function question_preview_question_pluginfile($course, $context, $component,
$quba = question_engine::load_questions_usage_by_activity($qubaid);
if (!question_has_capability_on($quba->get_question($slot), 'use')) {
if (!question_has_capability_on($quba->get_question($slot, false), 'use')) {
send_file_not_found();
}

View File

@ -50,7 +50,7 @@ trait core_question_privacy_helper {
$data
) {
$attempt = $quba->get_question_attempt($slotno);
$question = $attempt->get_question();
$question = $attempt->get_question(false);
// Check the question data exported.
$this->assertEquals($data->name, $question->name);

View File

@ -46,7 +46,7 @@ class qtype_ddmarker_question extends qtype_ddtoimage_question_base {
$validfilearea = false;
}
if ($component == 'qtype_ddmarker' && $validfilearea) {
$question = $qa->get_question();
$question = $qa->get_question(false);
$itemid = reset($args);
return $itemid == $question->id;
} else {

View File

@ -110,7 +110,7 @@ class qtype_missing_test extends question_testcase {
$output = $qa->render(new question_display_options(), '1');
$this->assertRegExp('/' .
preg_quote($qa->get_question()->questiontext, '/') . '/', $output);
preg_quote($qa->get_question(false)->questiontext, '/') . '/', $output);
$this->assertRegExp('/' .
preg_quote(get_string('missingqtypewarning', 'qtype_missingtype'), '/') . '/', $output);
$this->assert(new question_contains_tag_with_attribute(