MDL-27413 qtype_multianswer now works for all embedded types, I think.

This commit is contained in:
Tim Hunt 2011-05-26 16:42:54 +01:00
parent 7ac7977cbe
commit dcedbb0e25
6 changed files with 127 additions and 309 deletions

View file

@ -118,7 +118,7 @@ class qtype_multianswer_question extends question_graded_automatically {
$substep = $this->get_substep(null, $i);
foreach ($subq->get_expected_data() as $name => $type) {
if ($subq->qtype->name() == 'multichoice' &&
$subq->layout = qtype_multichoice_base::LAYOUT_DROPDOWN) {
$subq->layout == qtype_multichoice_base::LAYOUT_DROPDOWN) {
// Hack or MC inline does not work.
$expected[$substep->add_prefix($name)] = PARAM_RAW;
} else {

View file

@ -26,6 +26,8 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/question/type/multichoice/question.php');
/**
* The multi-answer question type class.

View file

@ -227,7 +227,7 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render
/**
* Render an embedded multiple-choice question that is displayed as a select menu.
*
* @copyright 2010 Pierre Pichet
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multianswer_multichoice_inline_renderer
@ -285,346 +285,145 @@ class qtype_multianswer_multichoice_inline_renderer
return $output;
}
public function formulation_and_controls(question_attempt $qa,
question_display_options $options) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
$answers = $subquestion->answers;
$correctanswers = $subquestion->get_correct_response();
foreach ($correctanswers as $key => $value) {
$correct = $value;
}
$order = $subquestion->get_order($qa);
$response = $this->get_response($qa);
$currentanswer = $response;
$answername = $subquestion->fieldid.'answer';
$inputname = $qa->get_qt_field_name($answername);
$inputattributes = array(
'type' => $this->get_input_type(),
'name' => $inputname,
);
/**
* Render an embedded multiple-choice question vertically, like for a normal
* multiple-choice question.
*
* @copyright 2010 Pierre Pichet
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multianswer_multichoice_vertical_renderer extends qtype_multianswer_subq_renderer_base {
public function subquestion(question_attempt $qa, question_display_options $options,
$index, question_graded_automatically $subq) {
$fieldprefix = 'sub' . $index . '_';
$fieldname = $fieldprefix . 'answer';
$response = $qa->get_last_qt_var($fieldname);
$inputattributes = array(
'type' => 'radio',
'name' => $qa->get_qt_field_name($fieldname),
);
if ($options->readonly) {
$inputattributes['disabled'] = 'disabled';
$readonly = 'disabled ="disabled"';
}
$choices = array();
$popup = '';
$feedback = '';
$answer = '';
$classes = 'control';
$feedbackimage = '';
$fraction = 0;
$chosen = 0;
foreach ($order as $value => $ansid) {
$mcanswer = $subquestion->answers[$ansid];
$choices[$value] = strip_tags($mcanswer->answer);
$selected = '';
$isselected = false;
if ( $response != '') {
$isselected = $this->is_choice_selected($response, $value);
}
$result = $this->all_choices_wrapper_start();
$fraction = null;
foreach ($subq->get_order($qa) as $value => $ansid) {
$ans = $subq->answers[$ansid];
$inputattributes['value'] = $value;
$inputattributes['id'] = $inputattributes['name'] . $value;
$isselected = $subq->is_choice_selected($response, $value);
if ($isselected) {
$chosen = $value;
$answer = $mcanswer;
$fraction = $mcanswer->fraction;
$selected = ' selected="selected"';
}
}
if ($options->feedback) {
if ($answer) {
$classes .= ' ' . question_get_feedback_class($fraction);
$feedbackimage = question_get_feedback_image($answer->fraction);
if ($answer->feedback) {
$feedback .= $subquestion->format_text($answer->feedback);
}
$inputattributes['checked'] = 'checked';
$fraction = $ans->fraction;
} else {
$classes .= ' ' . question_get_feedback_class(0);
$feedbackimage = question_get_feedback_image(0);
unset($inputattributes['checked']);
}
}
// determine popup
// answer feedback (specific)i.e if options->feedback already set
// subquestion status correctness or Finished validator if correctness
// Correct response
// marks
$strfeedbackwrapped = 'Response Status';
if ($options->feedback) {
$feedback = get_string('feedback', 'quiz').":".$feedback."<br />";
if ($options->correctness) {
if (!$answer) {
$state = $qa->get_state();
$state = question_state::$invalid;
$strfeedbackwrapped .= ":<font color=red >".$state->default_string()."</font>";
$feedback = "<font color=red >".get_string('singleanswer', 'quiz') ."</font><br />";
$class = 'r' . ($value % 2);
if ($options->correctness && $isselected) {
$feedbackimg = $this->feedback_image($ans->fraction);
$class .= ' ' . $this->feedback_class($ans->fraction);
} else {
$state = $qa->get_state();
$state = question_state::graded_state_for_fraction($fraction);
$strfeedbackwrapped .= ":".$state->default_string();
}
$feedbackimg = '';
}
if ($options->correctresponse) {
$feedback .= $this->correct_response($qa)."<br />";
}
if ($options->marks) {
$subgrade= $fraction * $subquestion->defaultmark;
$feedback .= $questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade);
$result .= $this->choice_wrapper_start($class);
$result .= html_writer::empty_tag('input', $inputattributes);
$result .= html_writer::tag('label', $subq->format_text($ans->answer,
$ans->answerformat, $qa, 'question', 'answer', $ansid),
array('for' => $inputattributes['id']));
$result .= $feedbackimg;
if ($options->feedback && $isselected && trim($ans->feedback)) {
$result .= html_writer::tag('div',
$subq->format_text($ans->feedback, $ans->feedbackformat,
$qa, 'question', 'answerfeedback', $ansid),
array('class' => 'specificfeedback'));
}
$feedback .= '</div>';
$result .= $this->choice_wrapper_end();
}
if ($options->feedback) {
// need to replace ' and " as they could break the popup string
// as the text comes from database, slashes have been removed
// addslashes will not work as it keeps the "
// HTML &#039; for ' does not work
$feedback = str_replace("'", "\'", $feedback);
$feedback = str_replace('"', "\'", $feedback);
$strfeedbackwrapped = str_replace("'", "\'", $strfeedbackwrapped);
$strfeedbackwrapped = str_replace('"', "\'", $strfeedbackwrapped);
$result .= $this->all_choices_wrapper_end();
$popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
" onmouseout=\"return nd();\" ";
if ($options->feedback && $options->marks >= question_display_options::MARK_AND_MAX &&
$subq->maxmark > 0) {
$a = new stdClass();
$a->mark = format_float($fraction * $subq->maxmark, $options->markdp);
$a->max = format_float($subq->maxmark, $options->markdp);
$result .= html_writer::tag('div', get_string('markoutofmax', 'question', $a),
array('class' => 'outcome'));
}
$result = '';
$result .= "<span $popup >";
$result .= html_writer::start_tag('span', array('class' => $classes), '');
$result .= choose_from_menu($choices, $inputname, $chosen,
' ', '', '', true, $options->readonly) . $feedbackimage;
$result .= html_writer::end_tag('span');
$result .= html_writer::end_tag('span');
return $result;
}
protected function format_choices($question) {
$choices = array();
foreach ($question->get_choice_order() as $key => $choiceid) {
$choices[$key] = strip_tags($question->format_text($question->choices[$choiceid]));
/**
* @param string $class class attribute value.
* @return string HTML to go before each choice.
*/
protected function choice_wrapper_start($class) {
return html_writer::start_tag('div', array('class' => $class));
}
return $choices;
/**
* @return string HTML to go after each choice.
*/
protected function choice_wrapper_end() {
return html_writer::end_tag('div');
}
/**
* @return string HTML to go before all the choices.
*/
protected function all_choices_wrapper_start() {
return html_writer::start_tag('div', array('class' => 'answer'));
}
/**
* @return string HTML to go after all the choices.
*/
protected function all_choices_wrapper_end() {
return html_writer::end_tag('div');
}
}
/**
* As multianswer have specific display requirements for multichoice display
* a new class was defined although largely following the multichoice one
* Render an embedded multiple-choice question vertically, like for a normal
* multiple-choice question.
*
* @copyright 2010 Pierre Pichet
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class qtype_multianswer_multichoice_renderer_base extends qtype_renderer {
abstract protected function get_input_type();
class qtype_multianswer_multichoice_horizontal_renderer
extends qtype_multianswer_multichoice_vertical_renderer {
abstract protected function get_input_name(question_attempt $qa, $value);
abstract protected function get_input_value($value);
abstract protected function get_input_id(question_attempt $qa, $value);
abstract protected function is_choice_selected($response, $value);
abstract protected function is_right(question_answer $ans);
abstract protected function get_response(question_attempt $qa);
public function specific_feedback(question_attempt $qa) {
return '';
protected function choice_wrapper_start($class) {
return html_writer::start_tag('td', array('class' => $class));
}
public function formulation_and_controls(question_attempt $qa,
question_display_options $options) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
$order = $subquestion->get_order($qa);
$response = $this->get_response($qa);
$inputattributes = array(
'type' => $this->get_input_type(),
);
if ($options->readonly) {
$inputattributes['disabled'] = 'disabled';
}
$radiobuttons = array();
$feedbackimg = array();
$feedback = array();
$classes = array();
$totfraction = 0;
$nullresponse = true;
foreach ($order as $value => $ansid) {
$ans = $subquestion->answers[$ansid];
$inputattributes['name'] = $this->get_input_name($qa, $value);
$inputattributes['value'] = $this->get_input_value($value);
$inputattributes['id'] = $this->get_input_id($qa, $value);
if ($subquestion->single) {
$isselected = $this->is_choice_selected($response, $value);
} else {
$isselected = $this->is_choice_selected($response, $value);
}
if ($isselected) {
$inputattributes['checked'] = 'checked';
$totfraction += $ans->fraction;
$nullresponse = false;
} else {
unset($inputattributes['checked']);
}
$radiobuttons[] = html_writer::empty_tag('input', $inputattributes) .
html_writer::tag('label', $subquestion->format_text($ans->answer), array('for' => $inputattributes['id']));
if (($options->feedback || $options->correctresponse) && $response !== -1) {
$feedbackimg[] = question_get_feedback_image($this->is_right($ans), $isselected && $options->feedback);
} else {
$feedbackimg[] = '';
}
if (($options->feedback || $options->correctresponse) && $isselected) {
$feedback[] = $subquestion->format_text($ans->feedback);
} else {
$feedback[] = '';
}
$class = 'r' . ($value % 2);
if ($options->correctresponse && $ans->fraction > 0) {
$class .= ' ' . question_get_feedback_class($ans->fraction);
}
$classes[] = $class;
protected function choice_wrapper_end() {
return html_writer::end_tag('td');
}
$result = '';
$answername = 'answer';
if ($subquestion->layout == 1) {
$result .= html_writer::start_tag('div', array('class' => 'ablock'));
$result .= html_writer::start_tag('table', array('class' => $answername));
foreach ($radiobuttons as $key => $radio) {
$result .= html_writer::start_tag('tr', array('class' => $answername));
$result .= html_writer::start_tag('td', array('class' => $answername));
$result .= html_writer::tag('span', $radio . $feedbackimg[$key] . $feedback[$key], array('class' => $classes[$key])) . "\n";
$result .= html_writer::end_tag('td');
$result .= html_writer::end_tag('tr');
}
$result .= html_writer::end_tag('table'); // answer
$result .= html_writer::end_tag('div'); // ablock
}
if ($subquestion->layout == 2) {
$result .= html_writer::start_tag('div', array('class' => 'ablock'));
$result .= html_writer::start_tag('table', array('class' => $answername));
$result .= html_writer::start_tag('tr', array('class' => $answername));
foreach ($radiobuttons as $key => $radio) {
$result .= html_writer::start_tag('td', array('class' => $answername));
$result .= html_writer::tag('span', $radio . $feedbackimg[$key] . $feedback[$key]
, array('class' => $classes[$key])) . "\n";
$result .= html_writer::end_tag('td');
}
$result .= html_writer::end_tag('tr');
$result .= html_writer::end_tag('table'); // answer
$result .= html_writer::end_tag('div'); // ablock
protected function all_choices_wrapper_start() {
return html_writer::start_tag('table', array('class' => 'answer')) .
html_writer::start_tag('tbody') . html_writer::start_tag('tr');
}
if ($options->feedback) {
$result .= html_writer::start_tag('div', array('class' => 'outcome'));
if ($options->correctness) {
if ( $nullresponse) {
$state = $qa->get_state();
$state = question_state::$invalid;
$result1 = $state->default_string();
$result .= html_writer::nonempty_tag('div', $result1,
array('class' => 'validationerror'));
$result1 = ($subquestion->single) ? get_string('singleanswer', 'quiz') : get_string('multipleanswers', 'quiz');
$result .= html_writer::nonempty_tag('div', $result1,
array('class' => 'validationerror'));
} else {
$state = $qa->get_state();
$state = question_state::graded_state_for_fraction($totfraction);
$result1 = $state->default_string();
$result .= html_writer::nonempty_tag('div', $result1,
array('class' => 'outcome'));
}
}
if ($options->correctresponse) {
$result1 = $this->correct_response($qa);
$result .= html_writer::nonempty_tag('div', $result1, array('class' => 'outcome'));
}
if ($options->marks) {
$subgrade= $totfraction * $subquestion->defaultmark;
$result .= $questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade);
}
if ($qa->get_state() == question_state::$invalid) {
$result .= html_writer::nonempty_tag('div', array('class' => 'validationerror'),
$subquestion->get_validation_error($qa->get_last_qt_data()));
}
$result .= html_writer::end_tag('div');
}
return $result;
}
}
class qtype_multianswer_multichoice_single_renderer extends qtype_multianswer_multichoice_renderer_base {
protected function get_input_type() {
return 'radio';
}
protected function is_choice_selected($response, $value) {
return $response == $value;
}
protected function is_right(question_answer $ans) {
return $ans->fraction > 0.9999999;
}
protected function get_input_name(question_attempt $qa, $value) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
$answername = $subquestion->fieldid.'answer';
return $qa->get_qt_field_name($answername);
}
protected function get_input_value($value) {
return $value;
}
protected function get_input_id(question_attempt $qa, $value) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
$answername = $subquestion->fieldid.'answer';
return $qa->get_qt_field_name($answername);
}
protected function get_response(question_attempt $qa) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
return $qa->get_last_qt_var($subquestion->fieldid.'answer', -1);
}
public function correct_response(question_attempt $qa) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
foreach ($subquestion->answers as $ans) {
if ($ans->fraction > 0.9999999) {
return get_string('correctansweris', 'qtype_multichoice',
$subquestion->format_text($ans->answer));
}
}
return '';
protected function all_choices_wrapper_end() {
return html_writer::end_tag('tr') . html_writer::end_tag('tbody') .
html_writer::end_tag('table');
}
}

View file

@ -87,7 +87,7 @@ class qtype_multianswer_test_helper extends question_test_helper {
$mc->shuffleanswers = 1;
$mc->answernumbering = 'none';
$mc->layout = 1;
$mc->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
$mc->answers = array(
13 => new question_answer(13, 'Bow-wow', 0,

View file

@ -37,8 +37,8 @@ require_once($CFG->dirroot . '/question/engine/simpletest/helpers.php');
class qtype_multianswer_question_test extends UnitTestCase {
public function test_get_expected_data() {
$question = test_question_maker::make_question('multianswer');
$this->assertEqual(array('sub1_answer' => PARAM_RAW_TRIMMED, 'sub2_answer' => PARAM_RAW),
$question->get_expected_data());
$this->assertEqual(array('sub1_answer' => PARAM_RAW_TRIMMED,
'sub2_answer' => PARAM_RAW), $question->get_expected_data());
}
public function test_is_complete_response() {

View file

@ -5,3 +5,20 @@
margin-top: 1em;
box-shadow: 0.5em 0.5em 1em #000000;
}
.que.multianswer .answer .specificfeedback {
display: inline;
padding: 0 0.7em;
background: #FFF3BF;
}
.que.multianswer .answer .specificfeedback * {
display: inline;
background: #FFF3BF;
}
.que.multianswer .answer div.r0,
.que.multianswer .answer div.r1 {
padding: 0.3em;
}
.que.multianswer table.answer {
margin-bottom: 0;
width: 100%;
}