/** * Generate the display of a question in a particular state, and with certain * display options. Normally you do not call this method directly. Intsead * you call {@link question_usage_by_activity::render_question()} which will * call this method with appropriate arguments. * * @param question_attempt $qa the question attempt to display. * @param qbehaviour_renderer $behaviouroutput the renderer to output the behaviour * specific parts. * @param qtype_renderer $qtoutput the renderer to output the question type * specific parts. * @param question_display_options $options controls what should and should not be displayed. * @param string|null $number The question number to display. 'i' is a special * value that gets displayed as Information. Null means no number is displayed. * @return string HTML representation of the question. */ public function question(question_attempt $qa, qbehaviour_renderer $behaviouroutput, qtype_renderer $qtoutput, question_display_options $options, $number) { //start a new output buffer $output = ''; //add the quesiton number (TODO: style?) //$output .= '<strong>' . $number .'.</strong> '; $output .= html_writer::start_tag('table', array('style' => 'width: 100%; padding-bottom: 4px;')); $output .= html_writer::start_tag('tr', array()); $output .= html_writer::tag('td', $number . '.', array('valign' => 'top', 'width' => '10%', 'style' => 'padding-right: 10px;')); $output .= html_writer::start_tag('td', array('width' => '90%')); //get the question from the attempt object $question = $qa->get_question(); $pragmas = self::extract_pragmas($question->format_questiontext($qa)); //add the question's formulation $output .= $this->formulation($qa, $behaviouroutput, $qtoutput, $options); //an indication of output, if appropriate $output .= $this->outcome($qa, $behaviouroutput, $qtoutput, $options); //any manual comments, if appropriate $output .= $this->manual_comment($qa, $behaviouroutput, $qtoutput, $options); //the user's response history, if appropriate $output .= $this->response_history($qa, $behaviouroutput, $qtoutput, $options); $output .= html_writer::end_tag('td'); $output .= html_writer::end_tag('tr'); $output .= html_writer::end_tag('table'); //if a pragma exists specifying the space after a given quesiton, use it; otherwise, assume 5px //$space_after = array_key_exists('space_after', $pragmas) ? $pragmas['space_after'] : '5px'; $space_after = array_key_exists('space_after', $pragmas) ? $pragmas['space_after'] : 0; //and add a spacer after the given question if ($space_after !== 0) { $output .= html_writer::tag('div', ' ', array('style' => 'height: ' . $space_after . ';')); } //return the contents of the output buffer return $output; }
protected function embedded_element(question_attempt $qa, $place, question_display_options $options) { $question = $qa->get_question(); $group = $question->places[$place]; $fieldname = $question->field($place); $value = $qa->get_last_qt_var($question->field($place)); $attributes = array('id' => $this->box_id($qa, 'p' . $place)); $groupclass = 'group' . $group; if ($options->readonly) { $attributes['disabled'] = 'disabled'; } $orderedchoices = $question->get_ordered_choices($group); $selectoptions = array(); foreach ($orderedchoices as $orderedchoicevalue => $orderedchoice) { $selectoptions[$orderedchoicevalue] = $orderedchoice->text; } $feedbackimage = ''; if ($options->correctness) { $response = $qa->get_last_qt_data(); if (array_key_exists($fieldname, $response)) { $fraction = (int) ($response[$fieldname] == $question->get_right_choice_for($place)); $attributes['class'] = $this->feedback_class($fraction); $feedbackimage = $this->feedback_image($fraction); } } $selecthtml = html_writer::select($selectoptions, $qa->get_qt_field_name($fieldname), $value, get_string('choosedots'), $attributes) . ' ' . $feedbackimage; return html_writer::tag('span', $selecthtml, array('class' => 'control ' . $groupclass)); }
public function manual_comment(question_attempt $qa, question_display_options $options) { if ($options->manualcomment != question_display_options::EDITABLE) { return ''; } $question = $qa->get_question(); return html_writer::nonempty_tag('div', $question->format_text($question->graderinfo, $question->graderinfo, $qa, 'qtype_poodllrecording', 'graderinfo', $question->id), array('class' => 'graderinfo')); }
/** * Normally you should not call this constuctor directly. The appropriate * behaviour object is created automatically as part of * {@link question_attempt::start()}. * @param question_attempt $qa the question attempt we will be managing. * @param string $preferredbehaviour the type of behaviour that was actually * requested. This information is not needed in most cases, the type of * subclass is enough, but occasionally it is needed. */ public function __construct(question_attempt $qa, $preferredbehaviour) { $this->qa = $qa; $this->question = $qa->get_question(); 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) . ')'); } }
public function correct_response(question_attempt $qa) { $question = $qa->get_question(); $answer = $question->get_matching_answer($question->get_correct_response()); if (!$answer) { return ''; } return get_string('correctansweris', 'qtype_shortanswer', s($answer->answer)); }
/** * Normally you should not call this constuctor directly. The appropriate * behaviour object is created automatically as part of * {@link question_attempt::start()}. * @param question_attempt $qa the question attempt we will be managing. * @param string $preferredbehaviour the type of behaviour that was actually * requested. This information is not needed in most cases, the type of * subclass is enough, but occasionally it is needed. */ public function __construct(question_attempt $qa, $preferredbehaviour) { $this->qa = $qa; $this->question = $qa->get_question(); $requiredclass = $this->required_question_definition_type(); if (!$this->question instanceof $requiredclass) { throw new coding_exception('This behaviour (' . $this->get_name() . ') cannot work with this question (' . get_class($this->question) . ')'); } }
public function specific_feedback(question_attempt $qa) { $question = $qa->get_question(); $response = array(); foreach ($question->get_parameters() as $param) { $response['answer_' . $param] = $qa->get_last_qt_var('answer_' . $param); } $question->compute_feedbackperconditions($response); return $question->computedfeedbackperconditions; }
public function controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); if (!empty($question->precheck)) { $buttons = $this->precheck_button($qa, $options) . "\n" . $this->submit_button($qa, $options); } else { $buttons = $this->submit_button($qa, $options); } return $buttons; }
/** * Generates the specific feedback from the database when the attempt is finished and the question is answered. */ public function specific_feedback(question_attempt $qa) { global $DB, $CFG; // get feedback from the database $record = $DB->get_record('qtype_javaunittest_feedback', array('questionattemptid' => $qa->get_database_id()), 'feedback'); if ($record === false) { return ''; } $feedback = $record->feedback; $question = $qa->get_question(); return $question->format_text($feedback, 0, $qa, 'question', 'answerfeedback', 1); }
public function correct_response(question_attempt $qa) { $question = $qa->get_question(); $answer = $question->get_correct_response(); if (!$answer) { return ''; } $response = $answer['answer']; if ($question->unitdisplay != qtype_numerical::UNITNONE && $question->unitdisplay != qtype_numerical::UNITINPUT) { $response = $question->ap->add_unit($response); } return get_string('correctansweris', 'qtype_shortanswer', $response); }
public function formulation_and_controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); $output = ''; foreach ($question->textfragments as $i => $fragment) { if ($i > 0) { $index = $question->places[$i]; $output .= $this->subquestion($qa, $options, $index, $question->subquestions[$index]); } $output .= $question->format_text($fragment, $question->questiontextformat, $qa, 'question', 'questiontext', $question->id); } $this->page->requires->js_init_call('M.qtype_multianswer.init', array('#q' . $qa->get_slot()), false, array('name' => 'qtype_multianswer', 'fullpath' => '/question/type/multianswer/module.js', 'requires' => array('base', 'node', 'event', 'overlay'))); return $output; }
public function correct_response(question_attempt $qa) { $question = $qa->get_question(); $correctanswer = ''; foreach ($question->textfragments as $i => $fragment) { if ($i > 0) { $group = $question->places[$i]; $choice = $question->choices[$group][$question->rightchoices[$i]]; $correctanswer .= '[' . str_replace('-', '‑', $choice->text) . ']'; } $correctanswer .= $fragment; } if (!empty($correctanswer)) { return get_string('correctansweris', 'qtype_gapselect', $question->format_text($correctanswer, $question->questiontextformat, $qa, 'question', 'questiontext', $question->id)); } }
/** * Display the information about the penalty calculations. * @param question_attempt $qa the question attempt. * @param object $mark contains information about the current mark. * @param question_display_options $options display options. */ protected function penalty_info(question_attempt $qa, $mark, question_display_options $options) { if (!$qa->get_question()->penalty) { return ''; } $output = ''; // print details of grade adjustment due to penalties if ($mark->raw != $mark->cur) { $output .= ' ' . get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark); } // print info about new penalty // penalty is relevant only if the answer is not correct and further attempts are possible if (!$qa->get_state()->is_finished()) { $output .= ' ' . get_string('gradingdetailspenalty', 'qbehaviour_adaptive', format_float($qa->get_question()->penalty, $options->markdp)); } return $output; }
/** * Generate the display of the formulation part of the question. This is the * area that contains the quetsion text, and the controls for students to * input their answers. Some question types also embed bits of feedback, for * example ticks and crosses, in this area. * * @param question_attempt $qa the question attempt to display. * @param question_display_options $options controls what should and should not be displayed. * @return string HTML fragment. */ public function formulation_and_controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); $response = $qa->get_last_qt_data(); $table = new html_table(); $table->attributes['class'] = 'matrix'; $table->head = array(); $table->head[] = ''; foreach ($question->cols as $col) { $table->head[] = self::matrix_header($col); } if ($options->correctness) { $table->head[] = ''; } foreach ($question->rows as $row) { $row_data = array(); $row_data[] = self::matrix_header($row); foreach ($question->cols as $col) { $key = $question->key($row, $col); $cell_name = $qa->get_field_prefix() . $key; $is_readonly = $options->readonly; $is_checked = $question->is_answered($response, $row, $col); if ($question->multiple) { $cell = self::checkbox($cell_name, $is_checked, $is_readonly); } else { $cell = self::radio($cell_name, $col->id, $is_checked, $is_readonly); } if ($options->correctness) { $weight = $question->weight($row, $col); $cell .= $this->feedback_image($weight); } $row_data[] = $cell; } if ($options->correctness) { $row_grade = $question->grading()->grade_row($question, $row, $response); $feedback = $row->feedback; $feedback = strip_tags($feedback) ? $feedback : ''; $row_data[] = $this->feedback_image($row_grade) . $feedback; } $table->data[] = $row_data; //$row_index++; } $result = $question->questiontext; $result .= html_writer::table($table, true); return $result; }
public function formulation_and_controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); $questiontext = $question->format_questiontext($qa); $placeholder = false; if (preg_match('/_____+/', $questiontext, $matches)) { $placeholder = $matches[0]; } $input = '**subq controls go in here**'; if ($placeholder) { $questiontext = substr_replace($questiontext, $input, strpos($questiontext, $placeholder), strlen($placeholder)); } $result = html_writer::tag('div', $questiontext, array('class' => 'qtext')); /* if ($qa->get_state() == question_state::$invalid) { $result .= html_writer::nonempty_tag('div', $question->get_validation_error(array('answer' => $currentanswer)), array('class' => 'validationerror')); }*/ return $result; }
/** * Store an entire {@link question_attempt} in the database, * including all the question_attempt_steps that comprise it. * @param question_attempt $qa the question attempt to store. * @param object $context the context of the owning question_usage_by_activity. */ public function insert_question_attempt(question_attempt $qa, $context) { $record = new stdClass(); $record->questionusageid = $qa->get_usage_id(); $record->slot = $qa->get_slot(); $record->behaviour = $qa->get_behaviour_name(); $record->questionid = $qa->get_question()->id; $record->variant = $qa->get_variant(); $record->maxmark = $qa->get_max_mark(); $record->minfraction = $qa->get_min_fraction(); $record->flagged = $qa->is_flagged(); $record->questionsummary = $qa->get_question_summary(); $record->rightanswer = $qa->get_right_answer_summary(); $record->responsesummary = $qa->get_response_summary(); $record->timemodified = time(); $record->id = $this->db->insert_record('question_attempts', $record); foreach ($qa->get_step_iterator() as $seq => $step) { $this->insert_question_attempt_step($step, $record->id, $seq, $context); } }
public function correct_response(question_attempt $qa) { if ($qa->get_state()->is_correct()) { // The answer was correct so we don't need to do anything further. return ''; } $question = $qa->get_question(); $stemorder = $question->get_stem_order(); $choices = $this->format_choices($qa, true); $table = new html_table(); $table->attributes['class'] = 'generaltable correctanswertable'; $table->size = array('50%', '50%'); foreach ($stemorder as $key => $stemid) { $row = new html_table_row(); $row->cells[] = $question->format_text($question->stems[$stemid], $question->stemformat[$stemid], $qa, 'qtype_ddmatch', 'subquestion', $stemid); $row->cells[] = $choices[$question->get_right_choice_for($stemid)]; $table->data[] = $row; } return get_string('correctansweris', 'qtype_match', html_writer::table($table)); }
/** * Display the information about the penalty calculations. * @param question_attempt $qa the question attempt. * @param object $mark contains information about the current mark. * @param question_display_options $options display options. */ protected function penalty_info(question_attempt $qa, $mark, question_display_options $options) { $currentpenalty = $qa->get_question()->penalty * $qa->get_max_mark(); $totalpenalty = $currentpenalty * $qa->get_last_behaviour_var('_try', 0); if ($currentpenalty == 0) { return ''; } $output = ''; // Print details of grade adjustment due to penalties if ($mark->raw != $mark->cur) { $output .= ' ' . get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark); } // Print information about any new penalty, only relevant if the answer can be improved. if ($qa->get_behaviour()->is_state_improvable($qa->get_state())) { $output .= ' ' . get_string('gradingdetailspenalty', 'qbehaviour_adaptive', format_float($currentpenalty, $options->markdp)); // Print information about total penalties so far, if larger than current penalty. if ($totalpenalty > $currentpenalty) { $output .= ' ' . get_string('gradingdetailspenaltytotal', 'qbehaviour_adaptive', format_float($totalpenalty, $options->markdp)); } } return $output; }
public function formulation_and_controls(question_attempt $qa, question_display_options $options) { $question = $qa->get_question(); $output = ''; $subquestions = array(); foreach ($question->textfragments as $i => $fragment) { if ($i > 0) { $index = $question->places[$i]; $token = 'qtypemultianswer' . $i . 'marker'; $token = '<span class="nolink">' . $token . '</span>'; $output .= $token; $subquestions[$token] = $this->subquestion($qa, $options, $index, $question->subquestions[$index]); } $output .= $fragment; } $output = $question->format_text($output, $question->questiontextformat, $qa, 'question', 'questiontext', $question->id); $output = str_replace(array_keys($subquestions), array_values($subquestions), $output); if ($qa->get_state() == question_state::$invalid) { $output .= html_writer::nonempty_tag('div', $question->get_validation_error($qa->get_last_qt_data()), array('class' => 'validationerror')); } $this->page->requires->js_init_call('M.qtype_multianswer.init', array('#q' . $qa->get_slot()), false, array('name' => 'qtype_multianswer', 'fullpath' => '/question/type/multianswer/module.js', 'requires' => array('base', 'node', 'event', 'overlay'))); return $output; }
protected function edit_question_link(question_attempt $qa, question_display_options $options) { global $CFG; if (empty($options->editquestionparams)) { return ''; } $params = $options->editquestionparams; if ($params['returnurl'] instanceof moodle_url) { $params['returnurl'] = $params['returnurl']->out_as_local_url(false); } $params['id'] = $qa->get_question()->id; $editurl = new moodle_url('/question/question.php', $params); return html_writer::tag('div', html_writer::link($editurl, $this->pix_icon('t/edit', get_string('edit'), '', array('class' => 'iconsmall')) . get_string('editquestion', 'question')), array('class' => 'editquestion')); }
public function test_constructor_sets_maxmark() { $qa = new question_attempt($this->question, $this->usageid); $this->assertSame($this->question, $qa->get_question()); $this->assertEquals(3, $qa->get_max_mark()); }
protected function hidden_fields(question_attempt $qa) { $question = $qa->get_question(); $hiddenfieldshtml = ''; $inputids = new stdClass(); $responsefields = array_keys($question->get_expected_data()); foreach ($responsefields as $responsefield) { $hiddenfieldshtml .= $this->hidden_field_for_qt_var($qa, $responsefield); } return $hiddenfieldshtml; }
protected function get_state_string(question_attempt $qa, $showcorrectness) { if ($qa->get_question()->length > 0) { return $qa->get_state_string($showcorrectness); } // Special case handling for 'information' items. if ($qa->get_state() == question_state::$todo) { return get_string('notyetviewed', 'quiz'); } else { return get_string('viewed', 'quiz'); } }
protected function penalty_info(question_attempt $qa, $mark, question_display_options $options) { if (!$qa->get_question()->penalty && !$qa->get_last_behaviour_var('_hashint', false)) { // No penalty for the attempts and no hinting done. return ''; } $output = ''; // Print details of grade adjustment due to penalties. if ($mark->raw != $mark->cur) { $output .= ' ' . get_string('gradingdetailsadjustment', 'qbehaviour_adaptive', $mark); } // Print information about any new penalty, only relevant if the answer can be improved. if ($qa->get_behaviour()->is_state_improvable($qa->get_state())) { $output .= ' ' . get_string('gradingdetailspenalty', 'qbehaviour_adaptive', format_float($qa->get_last_step()->get_behaviour_var('_penalty'), $options->markdp)); } return $output; }
public function get_marked_gaps(question_attempt $qa, question_display_options $options) { $marked_gaps = array(); $question = $qa->get_question(); $correct_gaps = array(); foreach ($question->textfragments as $place => $fragment) { if ($place < 1) { continue; } $fieldname = $question->field($place); $rightanswer = $question->get_right_choice_for($place); if ($options->correctness or $options->numpartscorrect) { $response = $qa->get_last_qt_data(); if (array_key_exists($fieldname, $response)) { if ($question->is_correct_response($response[$fieldname], $rightanswer)) { $marked_gaps[$fieldname]['value'] = $response[$fieldname]; $marked_gaps[$fieldname]['fraction'] = 1; $correct_gaps[] = $response[$fieldname]; } else { $marked_gaps[$fieldname]['value'] = $response[$fieldname]; $marked_gaps[$fieldname]['fraction'] = 0; } } } } $arr_unique = array_unique($correct_gaps); $arr_duplicates = array_diff_assoc($correct_gaps, $arr_unique); foreach ($marked_gaps as $fieldname => $gap) { if (in_array($gap['value'], $arr_duplicates)) { $marked_gaps[$fieldname]['duplicate'] = 'true'; } else { $marked_gaps[$fieldname]['duplicate'] = 'false'; } } return $marked_gaps; }
public function correct_response(question_attempt $qa) { $question = $qa->get_question(); $stemorder = $question->get_stem_order(); $choices = $this->format_choices($question); $right = array(); foreach ($stemorder as $key => $stemid) { if (!isset($choices[$question->get_right_choice_for($stemid)])) { continue; } $right[] = $question->make_html_inline($this->format_stem_text($qa, $stemid)) . ' – ' . $choices[$question->get_right_choice_for($stemid)]; } if (!empty($right)) { return get_string('correctansweris', 'qtype_match', implode(', ', $right)); } }
public function correct_response(question_attempt $qa) { $question = $qa->get_question(); $answer = $question->get_correct_answer(); if (!$answer) { return ''; } $response = str_replace('.', $question->ap->get_point(), $answer->answer); if ($question->unitdisplay != qtype_numerical::UNITNONE) { $response = $question->ap->add_unit($response); } return get_string('correctansweris', 'qtype_shortanswer', $response); }
/** * Store an entire {@link question_attempt} in the database, * including all the question_attempt_steps that comprise it. * @param question_attempt $qa the question attempt to store. * @param context $context the context of the owning question_usage_by_activity. */ public function insert_question_attempt(question_attempt $qa, $context) { $record = new stdClass(); $record->questionusageid = $qa->get_usage_id(); $record->slot = $qa->get_slot(); $record->behaviour = $qa->get_behaviour_name(); $record->questionid = $qa->get_question()->id; $record->variant = $qa->get_variant(); $record->maxmark = $qa->get_max_mark(); $record->minfraction = $qa->get_min_fraction(); $record->maxfraction = $qa->get_max_fraction(); $record->flagged = $qa->is_flagged(); $record->questionsummary = $qa->get_question_summary(); if (core_text::strlen($record->questionsummary) > question_bank::MAX_SUMMARY_LENGTH) { // It seems some people write very long quesions! MDL-30760 $record->questionsummary = core_text::substr($record->questionsummary, 0, question_bank::MAX_SUMMARY_LENGTH - 3) . '...'; } $record->rightanswer = $qa->get_right_answer_summary(); $record->responsesummary = $qa->get_response_summary(); $record->timemodified = time(); $record->id = $this->db->insert_record('question_attempts', $record); $qa->set_database_id($record->id); foreach ($qa->get_step_iterator() as $seq => $step) { $this->insert_question_attempt_step($step, $record->id, $seq, $context); } }
protected function num_parts_correct(question_attempt $qa) { if ($qa->get_question()->get_num_selected_choices($qa->get_last_qt_data()) > $qa->get_question()->get_num_correct_choices()) { return get_string('toomanyselected', 'qtype_multichoice'); } return parent::num_parts_correct($qa); }
/** * Get the postdata that needs to be sent to question/toggleflag.php to change the flag state. * You need to append &newstate=0/1 to this. * @return the post data to send. */ public static function get_postdata(question_attempt $qa) { $qaid = $qa->get_database_id(); $qubaid = $qa->get_usage_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=" . sesskey() . '&newstate='; }