MDL-27413 qtype_multianswer embedded inline multichoice now work.

This commit is contained in:
Tim Hunt
2011-05-26 15:03:47 +01:00
parent c83d6cc1eb
commit 7ac7977cbe
6 changed files with 285 additions and 242 deletions
+7 -1
View File
@@ -117,7 +117,13 @@ class qtype_multianswer_question extends question_graded_automatically {
foreach ($this->subquestions as $i => $subq) {
$substep = $this->get_substep(null, $i);
foreach ($subq->get_expected_data() as $name => $type) {
$expected[$substep->add_prefix($name)] = $type;
if ($subq->qtype->name() == 'multichoice' &&
$subq->layout = qtype_multichoice_base::LAYOUT_DROPDOWN) {
// Hack or MC inline does not work.
$expected[$substep->add_prefix($name)] = PARAM_RAW;
} else {
$expected[$substep->add_prefix($name)] = $type;
}
}
}
return $expected;
+6 -3
View File
@@ -203,6 +203,9 @@ class qtype_multianswer extends question_type {
$subqdata->contextid = $questiondata->contextid;
$question->subquestions[$key] = question_bank::make_question($subqdata);
$question->subquestions[$key]->maxmark = $subqdata->defaultmark;
if (isset($subqdata->options->layout)) {
$question->subquestions[$key]->layout = $subqdata->options->layout;
}
}
}
@@ -323,7 +326,7 @@ function qtype_multianswer_extract_question($text) {
$wrapped->incorrectfeedback['text'] = '';
$wrapped->incorrectfeedback['format'] = '1';
$wrapped->incorrectfeedback['itemid'] = '';
$wrapped->layout = 0;
$wrapped->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
} else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_REGULAR])) {
$wrapped->qtype = 'multichoice';
$wrapped->single = 1;
@@ -337,7 +340,7 @@ function qtype_multianswer_extract_question($text) {
$wrapped->incorrectfeedback['text'] = '';
$wrapped->incorrectfeedback['format'] = '1';
$wrapped->incorrectfeedback['itemid'] = '';
$wrapped->layout = 1;
$wrapped->layout = qtype_multichoice_base::LAYOUT_VERTICAL;
} else if (!empty($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE_HORIZONTAL])) {
$wrapped->qtype = 'multichoice';
$wrapped->single = 1;
@@ -351,7 +354,7 @@ function qtype_multianswer_extract_question($text) {
$wrapped->incorrectfeedback['text'] = '';
$wrapped->incorrectfeedback['format'] = '1';
$wrapped->incorrectfeedback['itemid'] = '';
$wrapped->layout = 2;
$wrapped->layout = qtype_multichoice_base::LAYOUT_HORIZONTAL;
} else {
print_error('unknownquestiontype', 'question', '', $answerregs[2]);
return false;
+269 -234
View File
@@ -67,6 +67,7 @@ class qtype_multianswer_renderer extends qtype_renderer {
public function subquestion(question_attempt $qa,
question_display_options $options, $index, question_graded_automatically $subq) {
$subtype = $subq->qtype->name();
if ($subtype == 'numerical' || $subtype == 'shortanswer') {
$subrenderer = 'textfield';
@@ -98,17 +99,81 @@ class qtype_multianswer_renderer extends qtype_renderer {
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multianswer_textfield_renderer extends qtype_renderer {
abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer {
abstract public function subquestion(question_attempt $qa,
question_display_options $options, $index,
question_graded_automatically $subq);
/**
* Render the feedback pop-up contents.
*
* @param question_graded_automatically $subq the subquestion.
* @param float $fraction the mark the student got. null if this subq was not answered.
* @param string $feedbacktext the feedback text, already processed with format_text etc.
* @param string $rightanswer the right answer, already processed with format_text etc.
* @param question_display_options $options the display options.
* @return string the HTML for the feedback popup.
*/
protected function feedback_popup(question_graded_automatically $subq,
$fraction, $feedbacktext, $rightanswer, question_display_options $options) {
if (!$options->feedback) {
return '';
}
$feedback = array();
if ($options->correctness) {
if (is_null($fraction)) {
$state = question_state::$gaveup;
} else {
$state = question_state::graded_state_for_fraction($fraction);
}
$feedback[] = $state->default_string(true);
}
if ($options->rightanswer) {
$feedback[] = get_string('correctansweris', 'qtype_shortanswer', $rightanswer);
}
$subfraction = '';
if ($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);
$feedback[] = get_string('markoutofmax', 'question', $a);
}
return html_writer::tag('span', implode('<br />', $feedback),
array('class' => 'feedbackspan accesshide'));
}
}
/**
* Subclass for generating the bits of output specific to shortanswer
* subquestions.
*
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multianswer_textfield_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);
$matchinganswer = $subq->get_matching_answer(array('answer' => $response));
if ($subq->qtype->name() == 'shortanswer') {
$matchinganswer = $subq->get_matching_answer(array('answer' => $response));
} else {
$matchinganswer = $subq->get_matching_answer($response);
}
if (!$matchinganswer) {
$matchinganswer = new question_answer(0, '', 0, '', FORMAT_HTML);
$matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML);
}
// Work out a good input field size.
@@ -132,46 +197,21 @@ class qtype_multianswer_textfield_renderer extends qtype_renderer {
$feedbackimg = '';
if ($options->correctness) {
if ($matchinganswer) {
$fraction = $matchinganswer->fraction;
} else {
$fraction = 0;
}
$inputattributes['class'] = $this->feedback_class($fraction);
$feedbackimg = $this->feedback_image($fraction);
$inputattributes['class'] = $this->feedback_class($matchinganswer->fraction);
$feedbackimg = $this->feedback_image($matchinganswer->fraction);
}
$feedbackpopup = '';
if ($options->feedback) {
$feedback = array();
if ($options->correctness) {
if ($matchinganswer) {
$state = question_state::graded_state_for_fraction($matchinganswer->fraction);
} else {
$state = question_state::$gaveup;
}
$feedback[] = $state->default_string(true);
}
if ($options->rightanswer) {
$correct = $subq->get_matching_answer($subq->get_correct_response());
$feedback[] = get_string('correctansweris', 'qtype_shortanswer',
s($correct->answer));
}
$subfraction = '';
if ($options->marks >= question_display_options::MARK_AND_MAX && $subq->maxmark > 0) {
$a = new stdClass();
$a->mark = format_float($matchinganswer->fraction * $subq->maxmark,
$options->markdp);
$a->max = format_float($subq->maxmark, $options->markdp);
$feedback[] = get_string('markoutofmax', 'question', $a);
}
$feedbackpopup = html_writer::tag('span', implode('<br />', $feedback),
array('class' => 'feedbackspan accesshide'));
if ($subq->qtype->name() == 'shortanswer') {
$correctanswer = $subq->get_matching_answer($subq->get_correct_response());
} else {
$correctanswer = $subq->get_correct_answer();
}
$feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction,
$subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat,
$qa, 'question', 'answerfeedback', $matchinganswer->id),
s($correctanswer->answer), $options);
$output = '';
$output .= html_writer::start_tag('label', array('class' => 'subq'));
$output .= html_writer::empty_tag('input', $inputattributes);
@@ -184,6 +224,196 @@ class qtype_multianswer_textfield_renderer extends qtype_renderer {
}
/**
* Render an embedded multiple-choice question that is displayed as a select menu.
*
* @copyright 2010 Pierre Pichet
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multianswer_multichoice_inline_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);
$choices = array();
$matchinganswer = new question_answer(0, '', null, '', FORMAT_HTML);
$rightanswer = null;
foreach ($subq->get_order($qa) as $value => $ansid) {
$ans = $subq->answers[$ansid];
$choices[$value] = $subq->format_text($ans->answer, $ans->answerformat,
$qa, 'question', 'answer', $ansid);
if ($subq->is_choice_selected($response, $value)) {
$matchinganswer = $ans;
}
}
$inputattributes = array(
'id' => $qa->get_qt_field_name($fieldname),
);
if ($options->readonly) {
$inputattributes['disabled'] = 'disabled';
}
$feedbackimg = '';
if ($options->correctness) {
$inputattributes['class'] = $this->feedback_class($matchinganswer->fraction);
$feedbackimg = $this->feedback_image($matchinganswer->fraction);
}
$select = html_writer::select($choices, $qa->get_qt_field_name($fieldname),
$response, array('' => ''), $inputattributes);
$order = $subq->get_order($qa);
$rightanswer = $subq->answers[$order[reset($subq->get_correct_response())]];
$feedbackpopup = $this->feedback_popup($subq, $matchinganswer->fraction,
$subq->format_text($matchinganswer->feedback, $matchinganswer->feedbackformat,
$qa, 'question', 'answerfeedback', $matchinganswer->id),
$subq->format_text($rightanswer->answer, $rightanswer->answerformat,
$qa, 'question', 'answer', $rightanswer->id), $options);
$output = '';
$output .= html_writer::start_tag('label', array('class' => 'subq'));
$output .= $select;
$output .= $feedbackimg;
$output .= $feedbackpopup;
$output .= html_writer::end_tag('label');
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,
);
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);
}
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);
}
} else {
$classes .= ' ' . question_get_feedback_class(0);
$feedbackimage = question_get_feedback_image(0);
}
}
// 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 />";
} else {
$state = $qa->get_state();
$state = question_state::graded_state_for_fraction($fraction);
$strfeedbackwrapped .= ":".$state->default_string();
}
}
if ($options->correctresponse) {
$feedback .= $this->correct_response($qa)."<br />";
}
if ($options->marks) {
$subgrade= $fraction * $subquestion->defaultmark;
$feedback .= $questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade);
}
$feedback .= '</div>';
}
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);
$popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
" onmouseout=\"return nd();\" ";
}
$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]));
}
return $choices;
}
}
/**
* As multianswer have specific display requirements for multichoice display
* a new class was defined although largely following the multichoice one
@@ -398,198 +628,3 @@ class qtype_multianswer_multichoice_single_renderer extends qtype_multianswer_mu
return '';
}
}
class qtype_multianswer_multichoice_single_inline_renderer extends qtype_multianswer_multichoice_single_renderer {
protected function get_input_type() {
return 'select';
}
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,
);
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);
}
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);
}
} else {
$classes .= ' ' . question_get_feedback_class(0);
$feedbackimage = question_get_feedback_image(0);
}
}
// 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 />";
} else {
$state = $qa->get_state();
$state = question_state::graded_state_for_fraction($fraction);
$strfeedbackwrapped .= ":".$state->default_string();
}
}
if ($options->correctresponse) {
$feedback .= $this->correct_response($qa)."<br />";
}
if ($options->marks) {
$subgrade= $fraction * $subquestion->defaultmark;
$feedback .= $questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade);
}
$feedback .= '</div>';
}
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);
$popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
" onmouseout=\"return nd();\" ";
}
$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]));
}
return $choices;
}
}
class qtype_multianswer_multichoice_multi_renderer extends qtype_multianswer_multichoice_renderer_base {
protected function get_input_type() {
return 'checkbox';
}
protected function get_input_name(question_attempt $qa, $value) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
return $qa->get_qt_field_name($subquestion->fieldid.'choice'. $value);
}
protected function get_input_value($value) {
return 1;
}
protected function get_input_id(question_attempt $qa, $value) {
return $this->get_input_name($qa, $value);
}
protected function get_response(question_attempt $qa) {
$responses = $qa->get_last_qt_data();
$questiontot = $qa->get_question();
$subresponses =$questiontot->decode_subquestion_responses($responses);
if ( isset($subresponses[$qa->subquestionindex])) {
return $subresponses[$qa->subquestionindex];
} else {
return '';
}
}
protected function is_choice_selected($response, $value) {
return isset($response['choice'.$value]);
}
protected function is_right(question_answer $ans) {
return $ans->fraction > 0;
}
public function correct_response(question_attempt $qa) {
$questiontot = $qa->get_question();
$subquestion = $questiontot->subquestions[$qa->subquestionindex];
$right = array();
foreach ($subquestion->answers as $ans) {
if ($ans->fraction > 0) {
$right[] = $subquestion->format_text($ans->answer);
}
}
if (!empty($right)) {
return get_string('correctansweris', 'qtype_multichoice',
implode(', ', $right));
}
return '';
}
}
@@ -37,7 +37,7 @@ 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_INT),
$this->assertEqual(array('sub1_answer' => PARAM_RAW_TRIMMED, 'sub2_answer' => PARAM_RAW),
$question->get_expected_data());
}
+1 -1
View File
@@ -217,7 +217,7 @@ class qtype_multichoice_single_question extends qtype_multichoice_base {
}
public function is_choice_selected($response, $value) {
return $response == $value;
return (string) $response === (string) $value;
}
}
+1 -2
View File
@@ -56,7 +56,6 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
question_display_options $options) {
$question = $qa->get_question();
$order = $question->get_order($qa);
$response = $question->get_response($qa);
$inputname = $qa->get_qt_field_name('answer');
@@ -73,7 +72,7 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
$feedbackimg = array();
$feedback = array();
$classes = array();
foreach ($order as $value => $ansid) {
foreach ($question->get_order($qa) as $value => $ansid) {
$ans = $question->answers[$ansid];
$inputattributes['name'] = $this->get_input_name($qa, $value);
$inputattributes['value'] = $this->get_input_value($value);