/** * When an in-progress {@link question_attempt} is re-loaded from the * database, this method is called so that the question can re-initialise * its internal state as needed by this attempt. * * For example, the multiple choice question type needs to set the order * of the choices to the order that was set up when start_attempt was called * originally. All the information required to do this should be in the * $step object, which is the first step of the question_attempt being loaded. * * @param question_attempt_step The first step of the {@link question_attempt} * being loaded. */ public function apply_attempt_state(\question_attempt_step $step) { global $DB; $attemptid = $this->get_attemptid_by_stepid($step->get_id()); $this->usageid = $this->get_question_usageid($attemptid); // Retrive all vars initialized in start_attempt(). foreach ($step->get_qt_data() as $name => $value) { if (substr($name, 0, 5) === '_var_') { $varid = substr($name, 5); $varname = $this->vars[$varid]->varname; $this->questiontext = str_replace('{$' . $varname . '}', $value, $this->questiontext); // Store vars (as array form) to be used later to get the correct response $this->varvalues[$varid] = explode(',', $value); if (!($values = $DB->get_field('qtype_programmedresp_val', 'varvalues', array('attemptid' => $this->usageid, 'varid' => $varid)))) { // Add a new random value $val = new stdClass(); $val->attemptid = $this->usageid; $val->varid = $varid; $val->varvalues = programmedresp_serialize($this->varvalues[$varid]); if (!$DB->insert_record('qtype_programmedresp_val', $val)) { print_error('errordb', 'qtype_programmedresp'); } } } } }
public function summarise_action(question_attempt_step $step) { if ($step->has_behaviour_var('comment')) { return $this->summarise_manual_comment($step); } else if ($step->has_behaviour_var('finish')) { return $this->summarise_finish($step); } else { return $this->summarise_save($step); } }
public function summarise_hint(question_attempt_step $step, $hintkey) { $response = $step->get_qt_data(); $hintkey = $this->adjust_hintkey($hintkey); $hintobj = $this->question->hint_object($hintkey, $step->get_qt_data()); $hintdescription = $hintobj->hint_description(); $a = new stdClass(); $a->hint = $hintdescription; $a->response = $this->question->summarise_response($response); return get_string('hintused', 'qbehaviour_adaptivehintsnopenalties', $a); }
public function apply_attempt_state(question_attempt_step $step) { $saquestions = explode(',', $step->get_qt_var('_stemorder')); foreach ($saquestions as $questionid) { $this->stems[$questionid] = $step->get_qt_var('_stem_' . $questionid); $this->stemformat[$questionid] = $step->get_qt_var('_stemformat_' . $questionid); $key = $step->get_qt_var('_right_' . $questionid); $this->right[$questionid] = $key; $this->choices[$key] = $step->get_qt_var('_choice_' . $key); } parent::apply_attempt_state($step); }
public function apply_attempt_state(question_attempt_step $step) { $this->order = explode(',', $step->get_qt_var('_order')); // Add any missing answers. Sometimes people edit questions after they // have been attempted which breaks things. foreach ($this->order as $ansid) { if (isset($this->answers[$ansid])) { continue; } $a = new stdClass(); $a->id = 0; $a->answer = html_writer::span(get_string('deletedchoice', 'qtype_multichoice'), 'notifyproblem'); $a->answerformat = FORMAT_HTML; $a->fraction = 0; $a->feedback = ''; $a->feedbackformat = FORMAT_HTML; $this->answers[$ansid] = $this->qtype->make_answer($a); $this->answers[$ansid]->answerformat = FORMAT_HTML; } }
public function apply_attempt_state(question_attempt_step $step) { $this->stemorder = explode(',', $step->get_qt_var('_stemorder')); $this->set_choiceorder(explode(',', $step->get_qt_var('_choiceorder'))); // Add any missing subquestions. Sometimes people edit questions after they // have been attempted which breaks things. foreach ($this->stemorder as $stemid) { if (!isset($this->stems[$stemid])) { $this->stems[$stemid] = html_writer::span(get_string('deletedsubquestion', 'qtype_match'), 'notifyproblem'); $this->stemformat[$stemid] = FORMAT_HTML; $this->right[$stemid] = 0; } } // Add any missing choices. Sometimes people edit questions after they // have been attempted which breaks things. foreach ($this->choiceorder as $choiceid) { if (!isset($this->choices[$choiceid])) { $this->choices[$choiceid] = get_string('deletedchoice', 'qtype_match'); } } }
public function summarise_action(question_attempt_step $step) { if ($step->has_behaviour_var('comment')) { return $this->summarise_manual_comment($step); } else { if ($step->has_behaviour_var('finish')) { return $this->summarise_finish($step); } else { if ($step->has_behaviour_var('tryagain')) { return get_string('tryagain', 'qbehaviour_interactive'); } else { if ($step->has_behaviour_var('submit')) { return $this->summarise_submit($step); } else { return $this->summarise_save($step); } } } } }
/** * Load a {@link question_attempt_step} from the database. * @param int $stepid the id of the step to load. * @param question_attempt_step the step that was loaded. */ public function load_question_attempt_step($stepid) { $records = $this->db->get_recordset_sql("\nSELECT\n quba.contextid,\n COALLESCE(q.qtype, 'missingtype') AS qtype,\n qas.id AS attemptstepid,\n qas.questionattemptid,\n qas.sequencenumber,\n qas.state,\n qas.fraction,\n qas.timecreated,\n qas.userid,\n qasd.name,\n qasd.value\n\nFROM {question_attempt_steps} qas\nJOIN {question_attempts} qa ON qa.id = qas.questionattemptid\nJOIN {question_usages} quba ON quba.id = qa.questionusageid\nLEFT JOIN {question} q ON q.id = qa.questionid\nLEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id\n\nWHERE\n qas.id = :stepid\n ", array('stepid' => $stepid)); if (!$records->valid()) { throw new coding_exception('Failed to load question_attempt_step ' . $stepid); } $step = question_attempt_step::load_from_records($records, $stepid); $records->close(); return $step; }
public function test_constructor_given_params() { global $USER; $step = new question_attempt_step(array(), 123, 5); $this->assertEquals(123, $step->get_timecreated()); $this->assertEquals(5, $step->get_user_id()); $this->assertEquals(array(), $step->get_qt_data()); $this->assertEquals(array(), $step->get_behaviour_data()); }
/** * Actually populate the qbehaviour_adaptive_mark_details object. * @param question_attempt_step $gradedstep the step that holds the relevant mark details. * @param question_state $state the state corresponding to $gradedstep. * @param unknown_type $maxmark the maximum mark for this question_attempt. * @param unknown_type $penalty the penalty for this question, as a fraction. */ protected function adaptive_mark_details_from_step(question_attempt_step $gradedstep, question_state $state, $maxmark, $penalty) { $details = new qbehaviour_adaptive_mark_details($state); $details->maxmark = $maxmark; $details->actualmark = $gradedstep->get_fraction() * $details->maxmark; $details->rawmark = $gradedstep->get_behaviour_var('_rawfraction') * $details->maxmark; $details->currentpenalty = $penalty * $details->maxmark; $details->totalpenalty = $details->currentpenalty * $this->qa->get_last_behaviour_var('_try', 0); $details->improvable = $this->is_state_improvable($gradedstep->get_state()); return $details; }
public function apply_attempt_state(question_attempt_step $step) { $this->answers = $this->get_ordering_answers(); $this->options = $this->get_ordering_options(); $this->currentresponse = array_filter(explode(',', $step->get_qt_var('_currentresponse'))); $this->correctresponse = array_filter(explode(',', $step->get_qt_var('_correctresponse'))); }
/** * Create a question_attempt_step from records loaded from the database. * * For internal use only. * * @param Iterator $records Raw records loaded from the database. * @param int $questionattemptid The id of the question_attempt to extract. * @return question_attempt The newly constructed question_attempt. */ public static function load_from_records($records, $questionattemptid, question_usage_observer $observer, $preferredbehaviour) { $record = $records->current(); while ($record->questionattemptid != $questionattemptid) { $record = $records->next(); if (!$records->valid()) { throw new coding_exception("Question attempt {$questionattemptid} not found in the database."); } $record = $records->current(); } try { $question = question_bank::load_question($record->questionid); } catch (Exception $e) { // The question must have been deleted somehow. Create a missing // question to use in its place. $question = question_bank::get_qtype('missingtype')->make_deleted_instance($record->questionid, $record->maxmark + 0); } $qa = new question_attempt($question, $record->questionusageid, null, $record->maxmark + 0); $qa->set_database_id($record->questionattemptid); $qa->set_slot($record->slot); $qa->variant = $record->variant + 0; $qa->minfraction = $record->minfraction + 0; $qa->maxfraction = $record->maxfraction + 0; $qa->set_flagged($record->flagged); $qa->questionsummary = $record->questionsummary; $qa->rightanswer = $record->rightanswer; $qa->responsesummary = $record->responsesummary; $qa->timemodified = $record->timemodified; $qa->behaviour = question_engine::make_behaviour($record->behaviour, $qa, $preferredbehaviour); $qa->observer = $observer; // If attemptstepid is null (which should not happen, but has happened // due to corrupt data, see MDL-34251) then the current pointer in $records // will not be advanced in the while loop below, and we get stuck in an // infinite loop, since this method is supposed to always consume at // least one record. Therefore, in this case, advance the record here. if (is_null($record->attemptstepid)) { $records->next(); } $i = 0; $autosavedstep = null; $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()); if ($sequencenumber < 0) { if (!$autosavedstep) { $autosavedstep = $nextstep; $autosavedsequencenumber = -$sequencenumber; } else { // Old redundant data. Mark it for deletion. $qa->observer->notify_step_deleted($nextstep, $qa); } } else { $qa->steps[$i] = $nextstep; if ($i == 0) { $question->apply_attempt_state($qa->steps[0]); } $i++; } if ($records->valid()) { $record = $records->current(); } else { $record = false; } } if ($autosavedstep) { if ($autosavedsequencenumber >= $i) { $qa->autosavedstep = $autosavedstep; $qa->steps[$i] = $qa->autosavedstep; } else { $qa->observer->notify_step_deleted($autosavedstep, $qa); } } return $qa; }
public function apply_attempt_state(question_attempt_step $step) { if ($step->has_behaviour_var('_applypenalties')) { $this->applypenalties = (bool) $step->get_behaviour_var('_applypenalties'); } parent::apply_attempt_state($step); }
public function summarise_helpme(question_attempt_step $step) { return get_string('submittedwithhelp', 'qbehaviour_regexpadaptivewithhelp', $this->question->summarise_response_withhelp($step->get_qt_data())); }
/** * Create a question_attempt_step from records loaded from the database. * * For internal use only. * * @param Iterator $records Raw records loaded from the database. * @param int $questionattemptid The id of the question_attempt to extract. * @return question_attempt The newly constructed question_attempt. */ public static function load_from_records($records, $questionattemptid, question_usage_observer $observer, $preferredbehaviour) { $record = $records->current(); while ($record->questionattemptid != $questionattemptid) { $record = $records->next(); if (!$records->valid()) { throw new coding_exception("Question attempt $questionattemptid not found in the database."); } $record = $records->current(); } try { $question = question_bank::load_question($record->questionid); } catch (Exception $e) { // The question must have been deleted somehow. Create a missing // question to use in its place. $question = question_bank::get_qtype('missingtype')->make_deleted_instance( $record->questionid, $record->maxmark + 0); } $qa = new question_attempt($question, $record->questionusageid, null, $record->maxmark + 0); $qa->set_database_id($record->questionattemptid); $qa->set_slot($record->slot); $qa->variant = $record->variant + 0; $qa->minfraction = $record->minfraction + 0; $qa->set_flagged($record->flagged); $qa->questionsummary = $record->questionsummary; $qa->rightanswer = $record->rightanswer; $qa->responsesummary = $record->responsesummary; $qa->timemodified = $record->timemodified; $qa->behaviour = question_engine::make_behaviour( $record->behaviour, $qa, $preferredbehaviour); $i = 0; while ($record && $record->questionattemptid == $questionattemptid && !is_null($record->attemptstepid)) { $qa->steps[$i] = question_attempt_step::load_from_records($records, $record->attemptstepid); if ($i == 0) { $question->apply_attempt_state($qa->steps[0]); } $i++; if ($records->valid()) { $record = $records->current(); } else { $record = false; } } $qa->observer = $observer; return $qa; }
protected function do_grading(question_attempt_step $responsesstep, question_attempt_pending_step $pendingstep) { if (!$this->question->is_gradable_response($responsesstep->get_qt_data())) { $pendingstep->set_state(question_state::$gaveup); } else { $response = $responsesstep->get_qt_data(); list($fraction, $state) = $this->question->grade_response($response); if ($responsesstep->has_behaviour_var('certainty')) { $certainty = $responsesstep->get_behaviour_var('certainty'); } else { $certainty = question_cbm::default_certainty(); $pendingstep->set_behaviour_var('_assumedcertainty', $certainty); } $pendingstep->set_behaviour_var('_rawfraction', $fraction); $pendingstep->set_fraction(question_cbm::adjust_fraction($fraction, $certainty)); $pendingstep->set_state($state); $pendingstep->set_new_response_summary(question_cbm::summary_with_certainty( $this->question->summarise_response($response), $responsesstep->get_behaviour_var('certainty'))); } return question_attempt::KEEP; }
/** * When an in-progress {@link question_attempt} is re-loaded from the database, this method is called so that the * question can re-initialise its internal state as needed by this attempt. For example, the multiple choice * question type needs to set the order of the choices to the order that was set up when start_attempt was called * originally. All the information required to do this should be in the $step object, which is the first step of the * question_attempt being loaded. * * @param question_attempt_step The first step of the {@link question_attempt} being loaded. */ public function apply_attempt_state(question_attempt_step $step) { global $DB; $stepid = $step->get_id(); // We need a place to store the feedback generated by JUnit. // Therefore we want to know the questionattemptid. if (!empty($stepid)) { $record = $DB->get_record('question_attempt_steps', array('id' => $stepid), 'questionattemptid'); if ($record) { $this->questionattemptid = $record->questionattemptid; } } }
public function apply_attempt_state(question_attempt_step $step) { $this->order = explode(',', $step->get_qt_var('_order')); }
public function apply_attempt_state(question_attempt_step $step) { $this->seed = (int) $step->get_qt_var('_seed'); $this->initialise_question_from_seed(); }
public function test_render_missing() { $questiondata = $this->get_unknown_questiondata(); $q = question_bank::make_question($questiondata); $qa = new testable_question_attempt($q, 0); $step = new question_attempt_step(array('answer' => 'frog')); $step->set_state(question_state::$todo); $qa->add_step($step); $qa->set_behaviour(new qbehaviour_deferredfeedback($qa, 'deferredfeedback')); $output = $qa->render(new question_display_options(), '1'); $this->assertRegExp('/' . preg_quote($qa->get_question()->questiontext, '/') . '/', $output); $this->assertRegExp('/' . preg_quote(get_string('missingqtypewarning', 'qtype_missingtype'), '/') . '/', $output); $this->assert(new question_contains_tag_with_attribute('div', 'class', 'warning missingqtypewarning'), $output); }
public function start_attempt(question_attempt_step $step, $variant) { shuffle($this->allanswers); $step->set_qt_var('_allanswers', implode(',', $this->allanswers)); }
public function apply_attempt_state(question_attempt_step $step) { $this->stemorder = explode(',', $step->get_qt_var('_stemorder')); $this->set_choiceorder(explode(',', $step->get_qt_var('_choiceorder'))); }
public function summarise_save(question_attempt_step $step) { $data = $step->get_submitted_data(); if (empty($data)) { return $this->summarise_start($step); } return get_string('saved', 'question', $this->question->summarise_response($step->get_qt_data())); }
public function summarise_action(question_attempt_step $step) { if ($step->has_behaviour_var('comment')) { return $this->summarise_manual_comment($step); } else { if ($step->has_behaviour_var('finish')) { return $this->summarise_finish($step); } else { if ($step->has_behaviour_var('seen')) { return get_string('seen', 'qbehaviour_informationitem'); } } } return $this->summarise_start($step); }
protected function prepare_response_for_editing($name, question_attempt_step $step, $context) { return $step->prepare_response_files_draft_itemid_with_text($name, $context->id, $step->get_qt_var($name)); }
public function apply_attempt_state(question_attempt_step $step) { list($point, $separator) = explode('$', $step->get_qt_var('_separators')); $this->ap->set_characters($point, $separator); }
public static function apply_attempt_state( qtype_calculated_question_with_expressions $question, question_attempt_step $step) { $values = array(); foreach ($step->get_qt_data() as $name => $value) { if (substr($name, 0, 5) === '_var_') { $values[substr($name, 5)] = $value; } } $question->vs = new qtype_calculated_variable_substituter( $values, get_string('decsep', 'langconfig')); $question->calculate_all_expressions(); }
/** * Load a {@link question_attempt_step} from the database. * @param int $stepid the id of the step to load. * @param question_attempt_step the step that was loaded. */ public function load_question_attempt_step($stepid) { $records = $this->db->get_recordset_sql(" SELECT qas.id AS attemptstepid, qas.questionattemptid, qas.sequencenumber, qas.state, qas.fraction, qas.timecreated, qas.userid, qasd.name, qasd.value FROM {question_attempt_steps} qas LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id WHERE qas.id = :stepid ", array('stepid' => $stepid)); if (!$records->valid()) { throw new coding_exception('Failed to load question_attempt_step ' . $stepid); } $step = question_attempt_step::load_from_records($records, $stepid); $records->close(); return $step; }
public function apply_attempt_state(question_attempt_step $step) { foreach ($this->choices as $group => $choices) { $this->set_choiceorder($group, explode(',', $step->get_qt_var('_choiceorder' . $group))); } }
public function test_load_dont_be_too_greedy() { $records = new question_test_recordset(array(array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value', 'contextid'), array(1, 1, 0, 'todo', null, 1256228502, 13, 'x', 'right', 1), array(2, 2, 0, 'complete', null, 1256228505, 13, 'x', 'wrong', 1))); $step = question_attempt_step::load_from_records($records, 1, 'description'); $this->assertEquals(array('x' => 'right'), $step->get_all_data()); }