. /** * Renderers for outputting parts of the question engine. * * @package moodlecore * @subpackage questionengine * @copyright 2009 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * This renderer controls the overall output of questions. It works with a * {@link qbehaviour_renderer} and a {@link qtype_renderer} to output the * type-specific bits. The main entry point is the {@link question()} method. * * @copyright 2009 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class core_question_renderer extends plugin_renderer_base { public function get_page() { return $this->page; } /** * 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) { $output = ''; $output .= html_writer::start_tag('div', array( 'id' => 'q' . $qa->get_slot(), 'class' => 'que ' . $qa->get_question()->qtype->name() . ' ' . $qa->get_behaviour_name(), )); $output .= html_writer::tag('div', $this->info($qa, $behaviouroutput, $qtoutput, $options, $number), array('class' => 'info')); $output .= html_writer::start_tag('div', array('class' => 'content')); $output .= html_writer::tag('div', $this->add_part_heading(get_string('questiontext', 'question'), $this->formulation($qa, $behaviouroutput, $qtoutput, $options)), array('class' => 'formulation')); $output .= html_writer::nonempty_tag('div', $this->add_part_heading(get_string('feedback', 'question'), $this->outcome($qa, $behaviouroutput, $qtoutput, $options)), array('class' => 'outcome')); $output .= html_writer::nonempty_tag('div', $this->add_part_heading(get_string('comments', 'question'), $this->manual_comment($qa, $behaviouroutput, $qtoutput, $options)), array('class' => 'comment')); $output .= html_writer::nonempty_tag('div', $this->response_history($qa, $behaviouroutput, $qtoutput, $options), array('class' => 'history')); $output .= html_writer::end_tag('div'); $output .= html_writer::end_tag('div'); return $output; } /** * Generate the information bit of the question display that contains the * metadata like the question number, current state, and mark. * @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 HTML fragment. */ protected function info(question_attempt $qa, qbehaviour_renderer $behaviouroutput, qtype_renderer $qtoutput, question_display_options $options, $number) { $output = ''; $output .= $this->number($number); $output .= $this->status($qa, $behaviouroutput, $options); $output .= $this->mark_summary($qa, $options); $output .= $this->question_flag($qa, $options->flags); return $output; } /** * Generate the display of the question number. * @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 HTML fragment. */ protected function number($number) { $numbertext = ''; if (is_numeric($number)) { $numbertext = get_string('questionx', 'question', html_writer::tag('span', $number, array('class' => 'qno'))); } else if ($number == 'i') { $numbertext = get_string('information', 'question'); } if (!$numbertext) { return ''; } return html_writer::tag('h2', $numbertext, array('class' => 'no')); } /** * Add an invisible heading like 'question text', 'feebdack' at the top of * a section's contents, but only if the section has some content. * @param string $heading the heading to add. * @param string $content the content of the section. * @return string HTML fragment with the heading added. */ protected function add_part_heading($heading, $content) { if ($content) { $content = html_writer::tag('h3', $heading, array('class' => 'accesshide')) . $content; } return $content; } /** * Generate the display of the status line that gives the current state of * the question. * @param question_attempt $qa the question attempt to display. * @param qbehaviour_renderer $behaviouroutput the renderer to output the behaviour * specific parts. * @param question_display_options $options controls what should and should not be displayed. * @return HTML fragment. */ protected function status(question_attempt $qa, qbehaviour_renderer $behaviouroutput, question_display_options $options) { return html_writer::tag('div', $qa->get_state_string($options->correctness), array('class' => 'state')); } /** * Generate the display of the marks for this question. * @param question_attempt $qa the question attempt to display. * @param question_display_options $options controls what should and should not be displayed. * @return HTML fragment. */ protected function mark_summary(question_attempt $qa, question_display_options $options) { if (!$options->marks) { return ''; } if ($qa->get_max_mark() == 0) { $summary = get_string('notgraded', 'question'); } else if ($options->marks == question_display_options::MAX_ONLY || is_null($qa->get_fraction())) { $summary = get_string('markedoutofmax', 'question', $qa->format_max_mark($options->markdp)); } else { $a = new stdClass(); $a->mark = $qa->format_mark($options->markdp); $a->max = $qa->format_max_mark($options->markdp); $summary = get_string('markoutofmax', 'question', $a); } return html_writer::tag('div', $summary, array('class' => 'grade')); } /** * Render the question flag, assuming $flagsoption allows it. * * @param question_attempt $qa the question attempt to display. * @param int $flagsoption the option that says whether flags should be displayed. */ protected function question_flag(question_attempt $qa, $flagsoption) { global $CFG; switch ($flagsoption) { case question_display_options::VISIBLE: $flagcontent = $this->get_flag_html($qa->is_flagged()); break; case question_display_options::EDITABLE: $id = $qa->get_flag_field_name(); if ($qa->is_flagged()) { $checked = 'checked="checked" '; } else { $checked = ''; } $postdata = question_flags::get_postdata($qa); // The checkbox id must be different from any element name, because // of a stupid IE bug: // http://www.456bereastreet.com/archive/200802/beware_of_id_and_name_attribute_mixups_when_using_getelementbyid_in_internet_explorer/ $flagcontent = '' . '' . '' . '' . "\n"; break; default: $flagcontent = ''; } if ($flagcontent) { return '