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 - } 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 - - } 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 - 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; }