diff --git a/question/type/multianswer/question.php b/question/type/multianswer/question.php
index 0ae5a8dea0b49..07d9ac6f545ba 100644
--- a/question/type/multianswer/question.php
+++ b/question/type/multianswer/question.php
@@ -44,6 +44,18 @@ class qtype_multianswer_question extends question_graded_automatically {
/** @var array of question_graded_automatically. */
public $subquestions = array();
+ /**
+ * @var array place number => insex in the $subquestions array. Places are
+ * numbered from 1.
+ */
+ public $places;
+
+ /**
+ * @var array of strings, one longer than $places, which is achieved by
+ * indexing from 0. The bits of question text that go between the subquestions.
+ */
+ public $textfragments;
+
/**
* Get a question_attempt_step_subquestion_adapter
* @param question_attempt_step $step the step to adapt.
diff --git a/question/type/multianswer/questiontype.php b/question/type/multianswer/questiontype.php
index 0158a5a8d36b9..83167c7ca0fcf 100644
--- a/question/type/multianswer/questiontype.php
+++ b/question/type/multianswer/questiontype.php
@@ -185,6 +185,17 @@ public function delete_question($questionid, $contextid) {
protected function initialise_question_instance($question, $questiondata) {
parent::initialise_question_instance($question, $questiondata);
+
+ $bits = preg_split('/\{#(\d+)\}/', $question->questiontext,
+ null, PREG_SPLIT_DELIM_CAPTURE);
+ $question->textfragments[0] = array_shift($bits);
+ $i = 1;
+ while (!empty($bits)) {
+ $question->places[$i] = array_shift($bits);
+ $question->textfragments[$i] = array_shift($bits);
+ $i += 1;
+ }
+
foreach ($questiondata->options->questions as $key => $subqdata) {
$subqdata->contextid = $questiondata->contextid;
$question->subquestions[$key] = question_bank::make_question($subqdata);
@@ -192,313 +203,6 @@ protected function initialise_question_instance($question, $questiondata) {
}
}
- public function get_html_head_contributions(&$question, &$state) {
- global $PAGE;
- parent::get_html_head_contributions($question, $state);
- $PAGE->requires->js('/lib/overlib/overlib.js', true);
- $PAGE->requires->js('/lib/overlib/overlib_cssstyle.js', true);
- }
-
- public function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
- global $CFG, $USER, $OUTPUT, $PAGE;
-
- $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
- $disabled = empty($options->readonly) ? '' : 'disabled="disabled"';
- $formatoptions = new stdClass();
- $formatoptions->noclean = true;
- $formatoptions->para = false;
- $nameprefix = $question->name_prefix;
-
- // adding an icon with alt to warn user this is a fill in the gap question
- // MDL-7497
- if (!empty($USER->screenreader)) {
- echo " pix_url('icon', 'qtype_'.$question->qtype)."\" ".
- "class=\"icon\" alt=\"".get_string('clozeaid', 'qtype_multichoice')."\" /> ";
- }
-
- echo '
';
-
- $qtextremaining = format_text($question->questiontext,
- $question->questiontextformat, $formatoptions, $cmoptions->course);
-
- $strfeedback = get_string('feedback', 'question');
-
- // The regex will recognize text snippets of type {#X}
- // where the X can be any text not containg } or white-space characters.
- while (preg_match('~\{#([^[:space:]}]*)}~', $qtextremaining, $regs)) {
- $qtextsplits = explode($regs[0], $qtextremaining, 2);
- echo $qtextsplits[0];
- echo "
"; // MDL-7497
- $qtextremaining = $qtextsplits[1];
-
- $positionkey = $regs[1];
- if (isset($question->options->questions[$positionkey]) && $question->options->questions[$positionkey] != '') {
- $wrapped = &$question->options->questions[$positionkey];
- $answers = &$wrapped->options->answers;
-
- $inputname = $nameprefix.$positionkey;
- if (isset($state->responses[$positionkey])) {
- $response = $state->responses[$positionkey];
- } else {
- $response = null;
- }
-
- // Determine feedback popup if any
- $popup = '';
- $style = '';
- $feedbackimg = '';
- $feedback = '';
- $correctanswer = '';
- $strfeedbackwrapped = $strfeedback;
- $testedstate = clone($state);
- if ($correctanswers = $QTYPES[$wrapped->qtype]->get_correct_responses($wrapped, $state)) {
- if ($options->readonly && $options->correct_responses) {
- $delimiter = '';
- if ($correctanswers) {
- foreach ($correctanswers as $ca) {
- switch($wrapped->qtype) {
- case 'numerical':
- case 'shortanswer':
- $correctanswer .= $delimiter.$ca;
- break;
- case 'multichoice':
- if (isset($answers[$ca])) {
- $correctanswer .= $delimiter.$answers[$ca]->answer;
- }
- break;
- }
- $delimiter = ', ';
- }
- }
- }
- if ($correctanswer != '') {
- $feedback = '';
- $feedback .= get_string('correctansweris', 'question', s($correctanswer));
- $feedback .= '
';
- }
- }
-
- if ($options->feedback) {
- $chosenanswer = null;
- switch ($wrapped->qtype) {
- case 'numerical':
- case 'shortanswer':
- $testedstate = clone($state);
- $testedstate->responses[''] = $response;
- foreach ($answers as $answer) {
- if ($QTYPES[$wrapped->qtype]->test_response($wrapped, $testedstate, $answer)) {
- $chosenanswer = clone($answer);
- break;
- }
- }
- break;
- case 'multichoice':
- if (isset($answers[$response])) {
- $chosenanswer = clone($answers[$response]);
- }
- break;
- default:
- break;
- }
-
- // Set up a default chosenanswer so that all non-empty wrong
- // answers are highlighted red
- if (empty($chosenanswer) && $response != '') {
- $chosenanswer = new stdClass();
- $chosenanswer->fraction = 0.0;
- }
-
- if (!empty($chosenanswer->feedback)) {
- $feedback = s(str_replace(array("\\", "'"), array("\\\\", "\\'"), $feedback.$chosenanswer->feedback));
- if ($options->readonly && $options->correct_responses) {
- $strfeedbackwrapped = get_string('correctanswerandfeedback', 'qtype_multianswer');
- } else {
- $strfeedbackwrapped = get_string('feedback', 'question');
- }
- $popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
- " onmouseout=\"return nd();\" ";
- }
-
- /// Determine style
- if ($options->feedback && $response != '') {
- $style = 'class = "'.question_get_feedback_class($chosenanswer->fraction).'"';
- $feedbackimg = question_get_feedback_image($chosenanswer->fraction);
- } else {
- $style = '';
- $feedbackimg = '';
- }
- }
- if ($feedback != '' && $popup == '') {
- $strfeedbackwrapped = get_string('correctanswer', 'qtype_multianswer');
- $feedback = s(str_replace(array("\\", "'"), array("\\\\", "\\'"), $feedback));
- $popup = " onmouseover=\"return overlib('$feedback', STICKY, MOUSEOFF, CAPTION, '$strfeedbackwrapped', FGCOLOR, '#FFFFFF');\" ".
- " onmouseout=\"return nd();\" ";
- }
-
- // Print the input control
- switch ($wrapped->qtype) {
- case 'shortanswer':
- case 'numerical':
- $size = 1;
- foreach ($answers as $answer) {
- if (strlen(trim($answer->answer)) > $size) {
- $size = strlen(trim($answer->answer));
- }
- }
- if (strlen(trim($response))> $size) {
- $size = strlen(trim($response))+1;
- }
- $size = $size + rand(0, $size*0.15);
- $size > 60 ? $size = 60 : $size = $size;
- $styleinfo = "size=\"$size\"";
-
- echo " ";
- if (!empty($feedback) && !empty($USER->screenreader)) {
- echo " pix_url('i/feedback') . "\" alt=\"$feedback\" />";
- }
- echo $feedbackimg;
- break;
- case 'multichoice':
- if ($wrapped->options->layout == 0) {
- $outputoptions = ' '; // Default empty option
- foreach ($answers as $mcanswer) {
- $selected = '';
- if ($response == $mcanswer->id) {
- $selected = ' selected="selected"';
- }
- $outputoptions .= "id\"$selected>" .
- s($mcanswer->answer) . ' ';
- }
- // In the next line, $readonly is invalid HTML, but it works in
- // all browsers. $disabled would be valid, but then the JS for
- // displaying the feedback does not work. Of course, we should
- // not be relying on JS (for accessibility reasons), but that is
- // a bigger problem.
- //
- // The span is used for safari, which does not allow styling of
- // selects.
- echo "";
- echo $outputoptions;
- echo ' ';
- if (!empty($feedback) && !empty($USER->screenreader)) {
- echo " pix_url('i/feedback') . "\" alt=\"$feedback\" />";
- }
- echo $feedbackimg;
- } else if ($wrapped->options->layout == 1 || $wrapped->options->layout == 2) {
- $ordernumber = 0;
- $anss = array();
- foreach ($answers as $mcanswer) {
- $ordernumber++;
- $checked = '';
- $chosen = false;
- $type = 'type="radio"';
- $name = "name=\"{$inputname}\"";
- if ($response == $mcanswer->id) {
- $checked = 'checked="checked"';
- $chosen = true;
- }
- $a = new stdClass();
- $a->id = $question->name_prefix . $mcanswer->id;
- $a->class = '';
- $a->feedbackimg = '';
-
- // Print the control
- $a->control = " id\" $name $checked $type value=\"$mcanswer->id\" />";
- if ($options->correct_responses && $mcanswer->fraction > 0) {
- $a->class = question_get_feedback_class(1);
- }
- if (($options->feedback && $chosen) || $options->correct_responses) {
- if ($type == ' type="checkbox" ') {
- $a->feedbackimg = question_get_feedback_image($mcanswer->fraction > 0 ? 1 : 0, $chosen && $options->feedback);
- } else {
- $a->feedbackimg = question_get_feedback_image($mcanswer->fraction, $chosen && $options->feedback);
- }
- }
-
- // Print the answer text: no automatic numbering
- $a->text = format_text($mcanswer->answer, $mcanswer->answerformat, $formatoptions, $cmoptions->course);
-
- // Print feedback if feedback is on
- if (($options->feedback || $options->correct_responses) && ($checked)) { //|| $options->readonly
- $a->feedback = format_text($mcanswer->feedback, $mcanswer->feedbackformat, $formatoptions, $cmoptions->course);
- } else {
- $a->feedback = '';
- }
-
- $anss[] = clone($a);
- }
- if ($wrapped->options->layout == 1) {
- ?>
-
- control; ?>
-
-
-
- text; ?>
- feedbackimg; ?>
-
-
-
- feedback; ?>
-
-
options->layout == 2) {
- ?>
-
-
-
- control; ?>
-
-
-
- text; ?>
- feedbackimg; ?>
-
-
-
- feedback; ?>
-
-
-
type = $wrapped->qtype;
- $a->sub = $positionkey;
- print_error('unknownquestiontypeofsubquestion', 'qtype_multianswer', '', $a);
- break;
- }
- echo " "; // MDL-7497
- } else {
- if (! isset($question->options->questions[$positionkey])) {
- echo $regs[0]."";
- } else {
- echo '
'.get_string('questionnotfound', 'qtype_multianswer', $positionkey).'
';
- }
- }
- }
-
- // Print the final piece of question text:
- echo $qtextremaining;
- $this->print_question_submit_buttons($question, $state, $cmoptions, $options);
- echo '
';
- }
-
public function get_random_guess_score($questiondata) {
$fractionsum = 0;
$fractionmax = 0;
diff --git a/question/type/multianswer/renderer.php b/question/type/multianswer/renderer.php
index 1e7dee6330b06..24482ecab13dc 100644
--- a/question/type/multianswer/renderer.php
+++ b/question/type/multianswer/renderer.php
@@ -15,16 +15,20 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-
/**
* Multianswer question renderer classes.
* Handle shortanswer, numerical and various multichoice subquestions
*
- * @package qtype_multianswer
- * @copyright 2009 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package qtype
+ * @subpackage multianswer
+ * @copyright 2010 Pierre Pichet
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+
+require_once($CFG->dirroot . '/question/type/shortanswer/renderer.php');
+
+
/**
* Base class for generating the bits of output common to multianswer
* (Cloze) questions.
@@ -41,51 +45,50 @@ public function formulation_and_controls(question_attempt $qa,
question_display_options $options) {
$question = $qa->get_question();
- $result = '';
-
- $qtextremaining = $question->format_questiontext();
-
- $strfeedback = get_string('feedback', 'quiz');
-
- // The regex will recognize text snippets of type {#X}
- // where the X can be any text not containg } or white-space characters.
-
- while (ereg('\{#([^[:space:]}]*)}', $qtextremaining, $regs)) {
- $qtextsplits = explode($regs[0], $qtextremaining, 2);
- $result .= $qtextsplits[0];
- // $result .= ""; // MDL-7497
- $qtextremaining = $qtextsplits[1];
-
- $positionkey = $regs[1];
- // transfer to the specific subquestion renderer
- if (isset($question->subquestions[$positionkey]) && $question->subquestions[$positionkey] != ''){
- $subquestion = &$question->subquestions[$positionkey];
- $qout = $subquestion->get_renderer();
- $qa->subquestionindex = $positionkey ;
- $result .= $qout->formulation_and_controls($qa,$options); //
-
- // $result .= " "; // MDL-7497
-
- } else {
- if(! isset($question->subquestions[$positionkey])){
- $result .= $regs[0]; //."";
- }else { //
- $result .= ''.get_string('questionnotfound','qtype_multianswer',$positionkey).'
';
- }
- }
- } // end while
+ $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);
+ }
- // Print the final piece of question text:
- $result .= $qtextremaining;
+ $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 $result;
+ return $output;
}
+ 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';
+ } else if ($subtype == 'multichoice') {
+ if ($subq->layout == qtype_multichoice_base::LAYOUT_DROPDOWN) {
+ $subrenderer = 'multichoice_inline';
+ } else if ($subq->layout == qtype_multichoice_base::LAYOUT_HORIZONTAL) {
+ $subrenderer = 'multichoice_horizontal';
+ } else {
+ $subrenderer = 'multichoice_vertical';
+ }
+ } else {
+ throw new coding_exception('Unexpected subquestion type.', $subq);
+ }
+ $renderer = $this->page->get_renderer('qtype_multianswer', $subrenderer);
+ return $renderer->subquestion($qa, $options, $index, $subq);
+ }
public function correct_response(question_attempt $qa) {
return '';
}
-
}
@@ -93,138 +96,90 @@ public function correct_response(question_attempt $qa) {
* Subclass for generating the bits of output specific to shortanswer
* subquestions.
*
- * @copyright 2009 The Open University
+ * @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
- require_once($CFG->dirroot . '/question/type/shortanswer/renderer.php');
- class qtype_multianswer_shortanswer_renderer extends qtype_shortanswer_renderer {
- /**
- * function normally part of core_question_renderer
- * that is copy here
- */
-
- public function correct_response(question_attempt $qa) {
- $questiontot = $qa->get_question();
- $subquestion = $questiontot->subquestions[$qa->subquestionindex];
- $answer = reset($subquestion->get_answers());
- if (!$answer) {
- return '';
- }
- return get_string('correctansweris', 'qtype_multianswer', s($answer->answer));
- }
+class qtype_multianswer_textfield_renderer extends qtype_renderer {
- public function formulation_and_controls(question_attempt $qa,
- question_display_options $options) {
- $questiontot = $qa->get_question();
- $subquestion = $questiontot->subquestions[$qa->subquestionindex];
- $answername = $subquestion->fieldid.'answer' ;
- $response = $qa->get_last_qt_var($answername);
- $inputname = $qa->get_qt_field_name($answername);
- $size = 1 ;
- foreach ($subquestion->answers as $answer) {
- if (strlen(trim($answer->answer)) > $size ){
- $size = strlen(trim($answer->answer));
- }
-
+ 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 (!$matchinganswer) {
+ $matchinganswer = new question_answer(0, '', 0, '', FORMAT_HTML);
}
- if (strlen(trim($response))> $size ){
- $size = strlen(trim($response))+1;
+
+ // Work out a good input field size.
+ $size = max(1, strlen(trim($response)) + 1);
+ foreach ($subq->answers as $ans) {
+ $size = max($size, strlen(trim($ans->answer)));
}
- $size = round($size + rand(0,$size*0.15));
- $size > 60 ? $size = 60 : $size = $size;
+ $size = min(60, round($size + rand(0, $size*0.15)));
+ // The rand bit is to make guessing harder
+
$inputattributes = array(
'type' => 'text',
- 'name' => $inputname,
+ 'name' => $qa->get_qt_field_name($fieldname),
'value' => $response,
- 'id' => $inputname,
+ 'id' => $qa->get_qt_field_name($fieldname),
'size' => $size,
);
- // readonly cannot by put in input
if ($options->readonly) {
$inputattributes['readonly'] = 'readonly';
-
}
- $class = '';
- $feedbackimg = '';
- // Determine feedback popup if any
- $popup = '';
- $feedback = '' ;
- $fraction = 0 ;
- if ($options->feedback) {
- $answer = $subquestion->get_matching_answer(array('answer' => $response));
- if ($answer) {
- $inputattributes['class'] = question_get_feedback_class($answer->fraction);
- $feedbackimg = question_get_feedback_image($answer->fraction);
- $fraction = $answer->fraction ;
- if ($answer->feedback) {
- // $feedback .= $subquestion->format_text(htmlspecialchars($answer->feedback, ENT_QUOTES ));
- $feedback .= $subquestion->format_text($answer->feedback );
- }
+ $feedbackimg = '';
+ if ($options->correctness) {
+ if ($matchinganswer) {
+ $fraction = $matchinganswer->fraction;
} else {
- $inputattributes['class'] = question_get_feedback_class(0);
- $feedbackimg = question_get_feedback_image(0);
+ $fraction = 0;
}
+ $inputattributes['class'] = $this->feedback_class($fraction);
+ $feedbackimg = $this->feedback_image($fraction);
}
- $readonly ='';
- if ($options->readonly) {
- $inputattributes['readonly'] = 'readonly';
- $readonly = 'readonly="readonly"';
- }
- // determine popup
- // answer feedback (specific)i.e if options->feedback already set
- // subquestion status correctness or Finished validator if correctness
- // Correct response
- // marks
+
+ $feedbackpopup = '';
if ($options->feedback) {
- $strfeedbackwrapped = 'Response Status';
- $subfraction = '' ;
- if ($options->correctness ) {
- if ( ! $answer ){
- $state = $qa->get_state();
- $state = question_state::$invalid;
- $strfeedbackwrapped .= ":".$state->default_string()." " ;
- $feedback = "".$subquestion->get_validation_error(array('answer' => $response)) ." ";
- }else {
- $state = $qa->get_state();
- $state = question_state::graded_state_for_fraction($fraction);
- $strfeedbackwrapped .= ":".$state->default_string();
- }
+ $feedback = array();
+ if ($options->correctness) {
+ if ($matchinganswer) {
+ $state = question_state::graded_state_for_fraction($matchinganswer->fraction);
+ } else {
+ $state = question_state::$gaveup;
}
-
- if ($options->correctresponse ) {
- $feedback .= " ".$this->correct_response( $qa);//
+ $feedback[] = $state->default_string(true);
}
- if ($options->marks ) {
- $res = $subquestion->grade_response(array('answer'=>$response)); // fraction=>state
- $subfraction = $res[0];
- $subgrade= $subfraction * $subquestion->defaultmark ;
- $feedback .= " ".$questiontot->mark_summary($options, $subquestion->defaultmark , $subgrade );
-
+
+ if ($options->rightanswer) {
+ $correct = $subq->get_matching_answer($subq->get_correct_response());
+ $feedback[] = get_string('correctansweris', 'qtype_shortanswer', s($correct->answer));
}
- $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();\" ";
- } //if feedback
-
- $result = '';
- $result .= ""; // MDL-7497
- $classes = 'control';
- $result .="" ;
- $input = html_writer::empty_tag('input', $inputattributes) ;
- $result .= $input;
- if (!empty($feedback) && !empty($USER->screenreader)) {
- $result .= " pixpath/i/feedback.gif\" alt=\"$feedback\" />";
+
+ $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(' ', $feedback),
+ array('class' => 'feedbackspan accesshide'));
}
- $result .= $feedbackimg." ";
- $result .= " "; // MDL-7497
- return $result;
- }
+ $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');
+ return $output;
+ }
}
/**
diff --git a/question/type/multianswer/styles.css b/question/type/multianswer/styles.css
index cafb6ab9a95ca..0bc1b7ed8d1d1 100644
--- a/question/type/multianswer/styles.css
+++ b/question/type/multianswer/styles.css
@@ -1,3 +1,7 @@
-.que.multianswer .submit {
+.que.multianswer .feedbackspan {
+ display: block;
+ background: #fff3bf;
+ padding: 0.5em;
margin-top: 1em;
+ box-shadow: 0.5em 0.5em 1em #000000;
}