From 7ac7977cbee7f8f697c2f668c3e17f2c0cdd8014 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 26 May 2011 15:03:37 +0100 Subject: [PATCH] MDL-27413 qtype_multianswer embedded inline multichoice now work. --- question/type/multianswer/question.php | 8 +- question/type/multianswer/questiontype.php | 9 +- question/type/multianswer/renderer.php | 497 +++++++++++---------- .../type/multianswer/simpletest/testquestion.php | 2 +- question/type/multichoice/question.php | 2 +- question/type/multichoice/renderer.php | 3 +- 6 files changed, 282 insertions(+), 239 deletions(-) diff --git a/question/type/multianswer/question.php b/question/type/multianswer/question.php index 7ae0411f2e3..1f8a572e5a6 100644 --- a/question/type/multianswer/question.php +++ b/question/type/multianswer/question.php @@ -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; diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php index 748d5a2aa96..0994d622af8 100644 --- a/question/type/multianswer/questiontype.php +++ b/question/type/multianswer/questiontype.php @@ -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; diff --git a/question/type/multianswer/renderer.php b/question/type/multianswer/renderer.php index 349e2c73cfb..09c79cd333b 100644 --- a/question/type/multianswer/renderer.php +++ b/question/type/multianswer/renderer.php @@ -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('
', $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,55 +197,220 @@ 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 ($subq->qtype->name() == 'shortanswer') { + $correctanswer = $subq->get_matching_answer($subq->get_correct_response()); + } else { + $correctanswer = $subq->get_correct_answer(); + } - if ($options->rightanswer) { - $correct = $subq->get_matching_answer($subq->get_correct_response()); - $feedback[] = get_string('correctansweris', 'qtype_shortanswer', - s($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); + $output .= $feedbackimg; + $output .= $feedbackpopup; + $output .= html_writer::end_tag('label'); - $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); + return $output; + } +} + + +/** + * 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; } + } - $feedbackpopup = html_writer::tag('span', implode('
', $feedback), - array('class' => 'feedbackspan accesshide')); + $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 .= html_writer::empty_tag('input', $inputattributes); + $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."
"; + + if ($options->correctness) { + if (!$answer) { + $state = $qa->get_state(); + $state = question_state::$invalid; + $strfeedbackwrapped .= ":".$state->default_string().""; + $feedback = "".get_string('singleanswer', 'quiz') ."
"; + } 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)."
"; + } + if ($options->marks) { + $subgrade= $fraction * $subquestion->defaultmark; + $feedback .= $questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade); + } + + $feedback .= ''; + } + + 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 ' 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 .= ""; + $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; + } } @@ -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."
"; - - if ($options->correctness) { - if (!$answer) { - $state = $qa->get_state(); - $state = question_state::$invalid; - $strfeedbackwrapped .= ":".$state->default_string().""; - $feedback = "".get_string('singleanswer', 'quiz') ."
"; - } 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)."
"; - } - if ($options->marks) { - $subgrade= $fraction * $subquestion->defaultmark; - $feedback .= $questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade); - } - - $feedback .= ''; - } - - 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 ' 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 .= ""; - $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 ''; - } - - - -} diff --git a/question/type/multianswer/simpletest/testquestion.php b/question/type/multianswer/simpletest/testquestion.php index fe786ebf7e3..a22971b0de2 100644 --- a/question/type/multianswer/simpletest/testquestion.php +++ b/question/type/multianswer/simpletest/testquestion.php @@ -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()); } diff --git a/question/type/multichoice/question.php b/question/type/multichoice/question.php index c697674430a..a20fe43c9cb 100644 --- a/question/type/multichoice/question.php +++ b/question/type/multichoice/question.php @@ -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; } } diff --git a/question/type/multichoice/renderer.php b/question/type/multichoice/renderer.php index f71b0a8319d..f227beaadfd 100644 --- a/question/type/multichoice/renderer.php +++ b/question/type/multichoice/renderer.php @@ -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); -- 2.11.4.GIT